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,668 @@
/* =====================================================================
SessionReview 페이지 전용 스타일 ( 페이지에서만 import).
토큰(tokens.css) 변수만 참조 /간격 추측 금지.
철칙: border-left 강조선 0 · 이모지 0(아이콘은 inline SVG) · 카드덤프 0 ·
순흑/순백 금지(paper/ink 토큰) · 강조는 weight+tint+kicker+dot ·
점수/등급/합불 표기 없음(§5.7 톤다운: 성장 신호).
외부 ref(session-review-external-ref.html) 레이아웃/구조/인터랙션만 차용,
Vignette 세이지틸·테라코타로 리스킨.
===================================================================== */
.sr-root {
max-width: var(--maxw);
margin: 0 auto;
}
/* ── (1) 세션 헤더: 아바타 이니셜 + 메타 + 우측 스탯(성장 신호) ── */
.sr-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-5);
flex-wrap: wrap;
padding: var(--sp-5);
}
.sr-head__id {
display: flex;
align-items: center;
gap: var(--sp-4);
min-width: 0;
}
/* 아바타 이니셜 — 원형 예외 허용(아바타) */
.sr-avatar {
width: 52px;
height: 52px;
border-radius: 50%;
flex: none;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-num);
font-size: 22px;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--clay-deep);
background: var(--clay-tint);
}
.sr-head__name {
font-size: var(--fs-h3);
font-weight: 600;
letter-spacing: -0.02em;
color: var(--text-strong);
line-height: 1.3;
}
.sr-head__meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 5px;
font-size: var(--fs-xs);
color: var(--text-muted);
}
.sr-head__meta .sr-mono {
font-family: var(--font-num);
letter-spacing: 0.02em;
}
.sr-meta-sep {
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--neutral-200);
flex: none;
}
/* 우측 스탯 묶음 (SESSION 신호 + SUPERVISOR) — 점수 박스 대신 한줄 신호 */
.sr-head__stats {
display: flex;
align-items: stretch;
gap: 0;
}
.sr-stat {
display: flex;
flex-direction: column;
gap: 6px;
padding: 0 var(--sp-5);
}
.sr-stat + .sr-stat {
border-left: 1px solid var(--hair); /* 데이터 구분선(강조바 아님, 1px 헤어라인) */
}
.sr-stat__lab {
font-family: var(--font-num);
font-size: var(--fs-kicker);
font-weight: 600;
letter-spacing: 0.06em;
color: var(--text-muted);
}
.sr-stat__val {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: var(--fs-sm);
font-weight: 600;
color: var(--text-strong);
}
.sr-stat__val--accent {
color: var(--accent-deep);
}
/* ── 2-3 컬럼 레이아웃 ── */
.sr-cols {
display: grid;
grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
gap: var(--sp-6);
align-items: start;
margin-top: var(--sp-6);
}
.sr-left {
display: flex;
flex-direction: column;
gap: var(--sp-6);
min-width: 0;
}
.sr-right {
display: flex;
flex-direction: column;
gap: var(--sp-6);
position: sticky;
top: calc(var(--topbar-h) + var(--sp-5));
min-width: 0;
}
/* ── 한 줄 요약 (24px / 600 — 에디토리얼 미니멀 핵심) ── */
.sr-summary {
font-size: var(--fs-h2);
line-height: 1.5;
font-weight: 600;
letter-spacing: -0.02em;
color: var(--text-strong);
max-width: 56ch;
}
.sr-summary .sr-hl {
color: var(--accent-deep);
}
/* ── 감정 밸런스 타임라인 (SVG 라인 차트) ── */
.sr-chart {
position: relative;
width: 100%;
}
.sr-chart__legend {
display: flex;
gap: var(--sp-4);
margin-bottom: var(--sp-4);
}
.sr-legend-item {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: var(--fs-xs);
color: var(--text-body);
}
.sr-legend-line {
width: 18px;
height: 0;
border-top-width: 2px;
border-top-style: solid;
flex: none;
}
.sr-legend-line--client {
border-top-color: var(--clay);
}
.sr-legend-line--baseline {
border-top-style: dashed;
border-top-color: var(--accent-bright);
}
.sr-chart__plot {
position: relative;
height: 168px;
width: 100%;
}
.sr-chart__yaxis {
position: absolute;
left: 0;
top: 0;
bottom: 18px;
width: 30px;
display: flex;
flex-direction: column;
justify-content: space-between;
text-align: right;
padding-right: 8px;
font-family: var(--font-num);
font-size: 10px;
color: var(--text-muted);
}
.sr-chart__canvas {
position: absolute;
left: 34px;
right: 0;
top: 0;
bottom: 18px;
}
.sr-chart__svg {
width: 100%;
height: 100%;
display: block;
overflow: visible;
}
.sr-chart__xaxis {
position: absolute;
left: 34px;
right: 0;
bottom: 0;
display: flex;
justify-content: space-between;
font-family: var(--font-num);
font-size: 10px;
color: var(--text-muted);
}
/* ── 회기 흐름 단계 막대 (가로, 너비=소요 비례) ── */
.sr-phasebar__track {
display: flex;
height: 36px;
border-radius: var(--radius);
overflow: hidden;
border: 1px solid var(--hair);
}
.sr-phase {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
font-size: var(--fs-xs);
font-weight: 600;
color: var(--text-body);
border-right: 1px solid var(--hair);
min-width: 0;
white-space: nowrap;
overflow: hidden;
}
.sr-phase:last-child {
border-right: none;
}
.sr-phase__dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-bright);
flex: none;
}
.sr-phasebar__axis {
display: flex;
justify-content: space-between;
margin-top: 7px;
font-family: var(--font-num);
font-size: 11px;
color: var(--text-muted);
letter-spacing: 0.02em;
}
/* ── 세션 트랜스크립트 ── */
.sr-tx__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
margin-bottom: var(--sp-3);
}
.sr-tx__filters {
display: inline-flex;
gap: 6px;
}
.sr-chip-toggle {
font-family: var(--font-sans);
font-size: var(--fs-xs);
font-weight: 600;
color: var(--text-muted);
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 100px;
padding: 4px 12px;
cursor: pointer;
transition:
background var(--dur-base) var(--ease-out),
color var(--dur-base) var(--ease-out),
border-color var(--dur-base) var(--ease-out);
}
.sr-chip-toggle:hover {
color: var(--text-strong);
border-color: var(--border-strong);
}
.sr-chip-toggle[aria-pressed="true"] {
color: var(--accent-deep);
background: var(--accent-tint);
border-color: transparent;
}
.sr-turns {
display: flex;
flex-direction: column;
}
.sr-turn {
display: grid;
grid-template-columns: 56px minmax(0, 1fr);
column-gap: var(--sp-4);
padding: var(--sp-4) 0;
}
.sr-turn + .sr-turn {
border-top: 1px solid var(--paper-2);
}
.sr-turn__rail {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding-top: 3px;
}
.sr-turn__ts {
font-family: var(--font-num);
font-size: 10px;
color: var(--text-muted);
letter-spacing: 0.02em;
}
.sr-turn__node {
width: 11px;
height: 11px;
border-radius: 50%;
flex: none;
}
.sr-turn__node--learner {
background: var(--accent-bright);
}
.sr-turn__node--client {
background: var(--clay);
}
.sr-turn__stem {
width: 2px;
flex: 1;
min-height: 8px;
background: var(--hair);
}
.sr-turn__body {
min-width: 0;
}
.sr-turn__who {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 9px;
margin-bottom: 5px;
font-size: var(--fs-xs);
font-weight: 600;
letter-spacing: 0.02em;
}
.sr-turn__who--learner {
color: var(--accent-deep);
}
.sr-turn__who--client {
color: var(--clay-deep);
}
.sr-turn__said {
font-size: var(--fs-body);
line-height: 1.6;
color: var(--text-strong);
max-width: 62ch;
}
.sr-turn__said--client {
color: var(--text-body);
}
/* 기법 라벨 칩 (배경 틴트, border 없음, 11px) */
.sr-technique {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: var(--font-sans);
font-size: 11px;
font-weight: 600;
padding: 2px 9px;
border-radius: 100px;
line-height: 1.5;
}
.sr-technique__dot {
width: 5px;
height: 5px;
border-radius: 50%;
flex: none;
}
.sr-technique--empathy {
color: var(--pos-text);
background: var(--pos-tint);
}
.sr-technique--empathy .sr-technique__dot {
background: var(--pos-solid);
}
.sr-technique--explore {
color: var(--accent-deep);
background: var(--accent-tint);
}
.sr-technique--explore .sr-technique__dot {
background: var(--accent);
}
.sr-technique--reflect {
color: var(--info-text);
background: var(--info-tint);
}
.sr-technique--reflect .sr-technique__dot {
background: var(--info-solid);
}
.sr-technique--confront {
color: var(--clay-deep);
background: var(--clay-tint);
}
.sr-technique--confront .sr-technique__dot {
background: var(--clay);
}
.sr-technique--closed {
color: var(--warn-text);
background: var(--warn-tint);
}
.sr-technique--closed .sr-technique__dot {
background: var(--warn-solid);
}
/* 인라인 슈퍼바이저 노트 — 말풍선 금지, 발화 아래 들여쓴 tint 콜아웃 */
.sr-note {
margin-top: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
border-radius: var(--radius);
max-width: 62ch;
transition:
opacity var(--dur-base) var(--ease-out),
transform var(--dur-base) var(--ease-out);
}
.sr-note--ai {
background: var(--accent-tint);
}
.sr-note--warn {
background: var(--warn-tint);
}
.sr-note__head {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 5px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.03em;
}
.sr-note--ai .sr-note__head {
color: var(--accent-deep);
}
.sr-note--warn .sr-note__head {
color: var(--warn-text);
}
.sr-note__head svg {
flex: none;
}
.sr-note__tag {
font-family: var(--font-num);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--text-muted);
margin-left: auto;
text-transform: uppercase;
}
.sr-note__txt {
font-size: var(--fs-sm);
line-height: 1.6;
color: var(--text-body);
}
.sr-note__txt .sr-quote {
color: var(--text-strong);
font-style: italic;
}
/* ── 우측: 스킬 루브릭 (가로 바 = 빈도+적절성, 점수 아님) ── */
.sr-rubric {
display: flex;
flex-direction: column;
gap: var(--sp-5);
}
.sr-rubric__row .sr-rubric__top {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--sp-3);
margin-bottom: 8px;
}
.sr-rubric__name {
font-size: var(--fs-sm);
font-weight: 600;
color: var(--text-strong);
}
.sr-rubric__qual {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: var(--fs-xs);
font-weight: 600;
}
.sr-rubric__qual--good {
color: var(--pos-text);
}
.sr-rubric__qual--watch {
color: var(--warn-text);
}
.sr-rubric__qual .sr-technique__dot {
width: 6px;
height: 6px;
}
.sr-rubric__qual--good .sr-technique__dot {
background: var(--pos-solid);
}
.sr-rubric__qual--watch .sr-technique__dot {
background: var(--warn-solid);
}
.sr-rubric__cluster {
font-family: var(--font-num);
font-size: 11px;
color: var(--text-muted);
letter-spacing: 0.02em;
margin-top: 6px;
}
/* ── 우측: AI 내담자 피드백 (다크 카드 = bg-stage 톤, italic 1인칭) ── */
.sr-feedback {
background: var(--bg-stage);
border-radius: var(--radius-lg);
padding: var(--sp-5);
box-shadow: var(--shadow-sm);
}
.sr-feedback__kicker {
font-family: var(--font-num);
font-size: var(--fs-kicker);
font-weight: 600;
letter-spacing: 0.08em;
color: var(--accent-bright);
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: var(--sp-3);
}
.sr-feedback__kicker .sr-technique__dot {
width: 6px;
height: 6px;
background: var(--accent-bright);
}
.sr-feedback__quote {
font-size: var(--fs-sm);
line-height: 1.65;
color: #d7ddda; /* bg-stage 위 본문 — 순백 금지, 밝은 세이지그레이 */
font-style: italic;
}
.sr-feedback__src {
display: flex;
align-items: center;
gap: 8px;
margin-top: var(--sp-4);
padding-top: var(--sp-3);
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-family: var(--font-num);
font-size: 11px;
letter-spacing: 0.02em;
color: #8a9794;
}
/* ── 잘한 순간 / 개선점 리스트 (각 항목 타임라인 마커 연결) ── */
.sr-points {
display: flex;
flex-direction: column;
gap: var(--sp-4);
}
.sr-point {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
column-gap: 11px;
align-items: start;
}
.sr-point__mk {
margin-top: 7px;
width: 7px;
height: 7px;
border-radius: 50%;
flex: none;
}
.sr-points--good .sr-point__mk {
background: var(--pos-solid);
}
.sr-points--grow .sr-point__mk {
background: var(--warn-solid);
}
.sr-point__h {
font-size: var(--fs-sm);
font-weight: 600;
color: var(--text-strong);
margin-bottom: 2px;
}
.sr-point__d {
font-size: var(--fs-xs);
line-height: 1.6;
color: var(--text-body);
}
.sr-point__at {
font-family: var(--font-num);
font-size: 11px;
color: var(--accent);
font-weight: 600;
margin-left: 6px;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
.sr-point__at:hover {
color: var(--accent-deep);
text-decoration: underline;
}
/* 다음에 시도할 한 문장 */
.sr-nextline {
background: var(--accent-tint);
border-radius: var(--radius);
padding: var(--sp-4) var(--sp-5);
margin-top: var(--sp-4);
}
.sr-nextline__lab {
font-family: var(--font-num);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.06em;
color: var(--accent-deep);
margin-bottom: 7px;
text-transform: uppercase;
}
.sr-nextline__q {
font-size: var(--fs-sm);
line-height: 1.6;
color: var(--text-strong);
font-weight: 500;
}
/* 활성 발화(타임라인 마커 클릭으로 점프) 강조 — 좌측바 아님, tint 펄스 */
.sr-turn--active .sr-turn__said {
background: var(--accent-tint);
border-radius: var(--radius-sm);
margin: 0 -8px;
padding: 4px 8px;
}
@media (max-width: 1024px) {
.sr-cols {
grid-template-columns: minmax(0, 1fr);
}
.sr-right {
position: static;
}
}
@media (prefers-reduced-motion: reduce) {
.sr-note,
.sr-chip-toggle,
.sr-turn--active .sr-turn__said {
transition-duration: 0.01ms;
}
}