feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG

web (Vite+React19+TS, Cloudflare Pages 배포):
- 디자인토큰(세이지틸/테라코타 SSOT), 앱셸, 공통 UI 프리미티브
- 7화면: 로그인/학습자홈/상담세션/회기리뷰/교수자/관리자/설정
- ClientAvatar: SVG 반구상 흉상 4상태 + RMS 립싱크 + 6파라미터 정서
- 회기리뷰는 외부 레퍼런스 디자인을 Vignette 토큰으로 리스킨

api (FastAPI):
- 게이트웨이 /v1/generate·/v1/stream 어댑터(상주풀/EngineSession 보존)
- services: 페르소나 L0~L6 빌더 / 결정론 상태머신 / 가드레일 /
  턴 오케스트레이터 / 회기간 메모리 / 평가AI / 음성 / RAG
- store: DB off 폴백(in-memory), sessions 실구현

검증:
- web: node22 tsc+vite build 통과(node23 segfault 회피), Pages 배포 200
- api: app.main import 통과
- 핫픽스: Topbar initials undefined-safe (undefined.trim 크래시)
- E2E: 서연(P1) 상담 1턴 — 좋은/나쁜 상담에 차등 반응 실증
This commit is contained in:
Yun Chan 2026-06-25 23:37:22 +09:00
parent 859ab26314
commit 24b1b7a6e1
84 changed files with 19645 additions and 107 deletions

View file

@ -0,0 +1,151 @@
<!--
외부 레퍼런스 — 윤찬이 2026-06-25 제공한 "Session Review" 디자인.
⚠️ 이건 Tailwind + Inter + indigo/emerald/amber 톤이라 Vignette 디자인 토큰과 다르다.
사용법: 이 파일의 *레이아웃/구조/인터랙션*만 차용한다 —
· 좌측 네비(Platform/Training 그룹)
· 세션 헤더(아바타 이니셜 + 메타 + SESSION SCORE/SUPERVISOR 스탯)
· 감정 타임라인 그래프(Patient vs Therapist baseline)
· 세션 트랜스크립트 + 인라인 슈퍼바이저 노트(말풍선 아님, 들여쓴 콜아웃)
· 우측 스킬 루브릭(가로 바) + AI 내담자 피드백(다크 카드)
색/폰트/철칙은 Vignette(DESIGN_CONCEPT.md §3, dev_dashboard.html :root)로 리스킨:
세이지틸 #3E7A6E / 테라코타 #B0735C, Pretendard, border-left·이모지·카드덤프·순흑백 금지.
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shrink Session - Session Review (external ref)</title>
<script src="https://cdn.tailwindcss.com/3.4.17"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
:root { --bg-base:#f5f4f0; --bg-surface:#ffffff; --text-main:#1C2331; --text-muted:#62615a; --border-color:#e5e4de; --rhythm:40px; }
body { font-family:'Inter',sans-serif; background-color:var(--bg-base); color:var(--text-main); }
.font-mono { font-family:'JetBrains Mono',monospace; }
.card-shadow { box-shadow:0 4px 6px -1px rgba(0,0,0,0.02), 0 2px 4px -1px rgba(0,0,0,0.02); }
.graph-line { stroke-dasharray:1000; stroke-dashoffset:0; animation:draw 2s ease-out forwards; }
@keyframes draw { to { stroke-dashoffset:0; } }
</style>
</head>
<body class="flex h-screen overflow-hidden text-slate-800">
<aside class="w-64 flex-shrink-0 border-r border-[#e5e4de] bg-[#fdfdfc] flex flex-col justify-between z-20">
<div>
<div class="h-16 flex items-center px-6 border-b border-[#e5e4de]">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-[#1C2331] flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path></svg>
</div>
<span class="font-bold text-[15px] tracking-tight text-slate-900">Shrink Session</span>
</div>
</div>
<nav class="p-4 space-y-1">
<div class="text-[10px] font-mono text-stone-400 font-semibold uppercase tracking-widest mb-3 px-3 mt-4">Platform</div>
<a href="#" class="flex items-center gap-3 px-3 py-2.5 text-stone-600 rounded-lg text-sm font-medium">Dashboard</a>
<a href="#" class="flex items-center gap-3 px-3 py-2.5 text-stone-600 rounded-lg text-sm font-medium">AI Patients</a>
<a href="#" class="flex items-center gap-3 px-3 py-2.5 bg-[#1C2331] text-white rounded-lg text-sm font-medium">Session Logs</a>
<a href="#" class="flex items-center gap-3 px-3 py-2.5 text-stone-600 rounded-lg text-sm font-medium">Analytics</a>
<div class="text-[10px] font-mono text-stone-400 font-semibold uppercase tracking-widest mb-3 px-3 mt-8">Training</div>
<a href="#" class="flex items-center justify-between px-3 py-2.5 text-stone-600 rounded-lg text-sm font-medium">Professor Feedback <span class="w-[22px] h-[22px] rounded-full bg-amber-100 text-amber-700 flex items-center justify-center text-[11px] font-bold">2</span></a>
<a href="#" class="flex items-center gap-3 px-3 py-2.5 text-stone-600 rounded-lg text-sm font-medium">Skill Modules</a>
</nav>
</div>
<div class="p-4 border-t border-[#e5e4de]">
<a href="#" class="flex items-center gap-3 px-3 py-2.5 text-stone-600 rounded-lg text-sm font-medium">Settings</a>
<div class="mt-4 flex items-center gap-3 px-3">
<div class="w-[34px] h-[34px] rounded-full bg-stone-200 flex items-center justify-center text-stone-600 font-semibold text-xs">JD</div>
<div><div class="text-sm font-semibold text-slate-900">Dr. J. Doe</div><div class="text-[10px] font-mono text-stone-500">RESIDENT · YR 2</div></div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col min-w-0 relative">
<header class="h-16 flex-shrink-0 flex items-center justify-between px-8 bg-[#fdfdfc]/90 border-b border-[#e5e4de] sticky top-0 z-10">
<div class="flex items-center gap-2 text-sm">
<span class="text-stone-500">Session Logs</span><span class="text-stone-300">/</span>
<span class="text-stone-500">Marcus Vance</span><span class="text-stone-300">/</span>
<span class="font-semibold text-slate-800">Session Review</span>
</div>
<button class="px-4 py-1.5 bg-white border border-stone-200 rounded-lg text-[13px] font-semibold text-stone-600 flex items-center gap-2">Export PDF</button>
</header>
<div class="flex-1 overflow-y-auto p-8">
<div class="max-w-[1100px] mx-auto space-y-6">
<!-- 세션 헤더: 아바타 이니셜 + 메타 + 스탯(SESSION SCORE / SUPERVISOR) -->
<div class="bg-white border border-[#e5e4de] rounded-xl p-6 card-shadow flex justify-between items-center">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-full bg-purple-100 text-purple-700 flex items-center justify-center text-xl font-bold font-mono">M</div>
<div>
<h1 class="text-xl font-bold text-slate-900">Marcus Vance</h1>
<div class="flex items-center gap-3 text-sm text-stone-500 mt-1">
<span class="font-mono text-[11px] uppercase">Oct 24, 2023 · 14:30</span><span></span>
<span>Duration: 45m 12s</span><span></span>
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200/60 rounded text-[10px] font-mono font-bold uppercase">Review Pend.</span>
</div>
</div>
</div>
<div class="flex gap-4">
<div class="text-center px-4 border-r border-[#e5e4de]">
<div class="text-[10px] font-mono font-semibold text-stone-400 mb-1">SESSION SCORE</div>
<div class="text-2xl font-bold text-slate-800">82<span class="text-sm text-stone-400 font-medium">/100</span></div>
</div>
<div class="text-center px-4">
<div class="text-[10px] font-mono font-semibold text-stone-400 mb-1">SUPERVISOR</div>
<div class="flex items-center gap-1.5 justify-center mt-1"><div class="w-6 h-6 rounded-full bg-amber-100 flex items-center justify-center"><span class="text-amber-800 font-bold text-[10px]">Dr.K</span></div><span class="text-sm font-semibold text-slate-700">Pending</span></div>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-6">
<div class="col-span-2 space-y-6">
<!-- 감정 타임라인: Patient(실선) vs Therapist baseline(점선) -->
<div class="bg-white border border-[#e5e4de] rounded-xl p-6 card-shadow">
<h2 class="text-[15px] font-semibold text-slate-900 mb-4">Emotional Valence Timeline</h2>
<div class="h-48 w-full relative">
<div class="absolute left-0 top-0 bottom-0 w-8 flex flex-col justify-between text-[10px] font-mono text-stone-400 text-right pr-2"><span>+1.0</span><span>0.0</span><span>-1.0</span></div>
<div class="absolute left-8 right-0 top-1/2 h-px bg-stone-200"></div>
<svg class="w-full h-full absolute left-8 right-0" viewBox="0 0 100 100" preserveAspectRatio="none">
<path d="M0,70 Q10,75 20,60 T40,40 T60,50 T80,30 T100,45" fill="none" stroke="#6366f1" stroke-width="2" class="graph-line" vector-effect="non-scaling-stroke"></path>
<path d="M0,50 Q15,40 25,55 T45,60 T65,45 T85,55 T100,50" fill="none" stroke="#10b981" stroke-width="2" stroke-dasharray="4" class="graph-line" vector-effect="non-scaling-stroke" opacity="0.5"></path>
</svg>
<div class="absolute bottom-0 left-8 right-0 flex justify-between text-[10px] font-mono text-stone-400 pt-2"><span>0m</span><span>10m</span><span>20m</span><span>30m</span><span>45m</span></div>
<div class="absolute top-2 right-2 flex gap-3 text-[10px] font-mono"><div class="flex items-center gap-1 text-indigo-600"><span class="w-2 h-2 rounded-full bg-indigo-500"></span> Patient</div><div class="flex items-center gap-1 text-emerald-600"><span class="w-2 h-2 rounded-full bg-emerald-500 opacity-50"></span> Therapist Baseline</div></div>
</div>
</div>
<!-- 트랜스크립트 + 인라인 슈퍼바이저 노트 -->
<div class="bg-white border border-[#e5e4de] rounded-xl overflow-hidden card-shadow flex flex-col h-[500px]">
<div class="px-6 py-4 border-b border-[#e5e4de] flex justify-between items-center bg-stone-50/50">
<h2 class="text-[15px] font-semibold text-slate-900">Session Transcript</h2>
<button class="text-[12px] font-medium text-stone-500 bg-white border border-stone-200 px-2 py-1 rounded">Search</button>
</div>
<div class="flex-1 overflow-y-auto p-6 space-y-6">
<div class="flex gap-4"><div class="w-8 h-8 rounded-full bg-stone-200 flex items-center justify-center text-xs font-bold font-mono text-stone-600 shrink-0 mt-1">JD</div><div><div class="flex items-baseline gap-2 mb-1"><span class="font-semibold text-sm">You</span><span class="text-[10px] font-mono text-stone-400">14:30:12</span></div><p class="text-[14px] text-slate-700">Hi Marcus, it's good to see you again. How has your week been since our last session?</p></div></div>
<div class="flex gap-4"><div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center text-xs font-bold font-mono text-purple-700 shrink-0 mt-1">M</div><div><div class="flex items-baseline gap-2 mb-1"><span class="font-semibold text-sm">Marcus</span><span class="text-[10px] font-mono text-stone-400">14:30:28</span></div><p class="text-[14px] text-slate-700">It's been... alright, I guess. Work is still overwhelming. I just feel like I'm constantly behind and everyone notices.</p></div></div>
<div class="flex gap-4 relative"><div class="w-8 h-8 rounded-full bg-stone-200 flex items-center justify-center text-xs font-bold font-mono text-stone-600 shrink-0 mt-1">JD</div><div class="flex-1"><div class="flex items-baseline gap-2 mb-1"><span class="font-semibold text-sm">You</span><span class="text-[10px] font-mono text-stone-400">14:31:05</span></div><p class="text-[14px] text-slate-700 bg-amber-50/50 p-1 -mx-1 rounded border border-amber-100">You feel like everyone notices. Do you think they are actually judging you, or is that just how you feel?</p>
<!-- 인라인 슈퍼바이저 노트 (말풍선 아님, 들여쓴 콜아웃) -->
<div class="mt-3 bg-white border border-amber-200 rounded-lg p-3 shadow-sm relative"><div class="flex items-center gap-2 mb-1.5"><div class="w-5 h-5 rounded-full bg-amber-100 flex items-center justify-center"><span class="text-amber-800 font-bold text-[8px]">Dr.K</span></div><span class="text-[11px] font-bold text-amber-800 uppercase">Supervisor Note</span></div><p class="text-[12px] text-slate-700">Careful here. This is a leading question that might make the patient defensive. Instead, try an open-ended reflection like: "It sounds like you're carrying a lot of worry about how others perceive you at work."</p></div>
</div></div>
<div class="flex gap-4"><div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center text-xs font-bold font-mono text-purple-700 shrink-0 mt-1">M</div><div><div class="flex items-baseline gap-2 mb-1"><span class="font-semibold text-sm">Marcus</span><span class="text-[10px] font-mono text-stone-400">14:31:42</span></div><p class="text-[14px] text-slate-700">I don't know... my boss asked me about the Q3 report yesterday and I didn't have it ready. He definitely gave me a look.</p></div></div>
</div>
</div>
</div>
<div class="space-y-6">
<!-- 스킬 루브릭 (가로 바) -->
<div class="bg-white border border-[#e5e4de] rounded-xl p-5 card-shadow">
<h2 class="text-[14px] font-semibold text-slate-900 mb-4 uppercase">Skill Rubric</h2>
<div class="space-y-5">
<div><div class="flex justify-between text-[13px] mb-1.5"><span class="font-medium text-slate-700">Active Listening</span><span class="font-mono font-semibold text-emerald-600">90%</span></div><div class="h-1.5 bg-stone-100 rounded-full overflow-hidden"><div class="h-full bg-emerald-500 rounded-full" style="width:90%"></div></div></div>
<div><div class="flex justify-between text-[13px] mb-1.5"><span class="font-medium text-slate-700">Empathy &amp; Validation</span><span class="font-mono font-semibold text-emerald-600">85%</span></div><div class="h-1.5 bg-stone-100 rounded-full overflow-hidden"><div class="h-full bg-emerald-500 rounded-full" style="width:85%"></div></div></div>
<div><div class="flex justify-between text-[13px] mb-1.5"><span class="font-medium text-slate-700">Open-ended Questioning</span><span class="font-mono font-semibold text-amber-500">65%</span></div><div class="h-1.5 bg-stone-100 rounded-full overflow-hidden"><div class="h-full bg-amber-500 rounded-full" style="width:65%"></div></div></div>
<div><div class="flex justify-between text-[13px] mb-1.5"><span class="font-medium text-slate-700">CBT Techniques</span><span class="font-mono font-semibold text-blue-500">75%</span></div><div class="h-1.5 bg-stone-100 rounded-full overflow-hidden"><div class="h-full bg-blue-500 rounded-full" style="width:75%"></div></div></div>
<div><div class="flex justify-between text-[13px] mb-1.5"><span class="font-medium text-slate-700">Pacing &amp; Silence</span><span class="font-mono font-semibold text-emerald-600">88%</span></div><div class="h-1.5 bg-stone-100 rounded-full overflow-hidden"><div class="h-full bg-emerald-500 rounded-full" style="width:88%"></div></div></div>
</div>
</div>
<!-- AI 내담자 피드백 (다크 카드) -->
<div class="bg-[#2A2F3D] border border-slate-700 rounded-xl p-5 card-shadow text-white relative overflow-hidden">
<h2 class="text-[14px] font-semibold text-slate-200 mb-3 uppercase">AI Patient Feedback</h2>
<p class="text-[13px] text-slate-300 italic">"I felt like my therapist was rushing me a bit when I talked about my boss. I wasn't really ready to look at it differently yet, I just wanted them to understand how bad it felt in that moment."</p>
<div class="mt-4 pt-3 border-t border-slate-600/50 flex items-center gap-2 text-[11px] font-mono text-slate-400">Generated by LLM Persona Engine</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>