현재 작업 전체 반영

This commit is contained in:
Yun Chan 2026-06-27 16:08:41 +09:00
parent 5560638e54
commit c0dddab594
85 changed files with 11322 additions and 539 deletions

3723
apps/web/src/lib/api.gen.ts Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,10 @@
- SSE: POST /sessions/{id}/stream token/done/ping/error .
===================================================================== */
import type { components } from "./api.gen";
type ApiSchema<Name extends keyof components["schemas"]> = components["schemas"][Name];
// Vite 환경변수. 기본 "/api" (vite proxy 또는 nginx 가 백엔드로 라우팅).
const PUBLIC_API_ORIGIN_BY_HOST: Record<string, string> = {
"vignette.chanpaca.net": "https://api-vignette.chanpaca.net",
@ -209,6 +213,9 @@ export interface PersonaReviewSummary {
approved_at: string | null;
}
export type PersonaDraftPayload = ApiSchema<"PersonaDraftPayload">;
export type PersonaDraftDetail = ApiSchema<"PersonaDraftDetail">;
/** POST /sessions — sessions.py SessionStartResponse */
export interface SessionStartResponse {
session_id: string;
@ -221,19 +228,15 @@ export interface SessionStartResponse {
}
/** POST /sessions/{id}/turn — sessions.py TurnResponse */
export interface TurnResponse {
turn_seq: number;
stage: SessionStage;
effective_openness: number;
client_reply: string | null;
safety_flagged: boolean;
}
export type TurnResponse = ApiSchema<"TurnResponse">;
/** POST /sessions/{id}/end — sessions.py SessionEndResponse */
export interface SessionEndResponse {
session_id: string;
session_no: number;
digest_pending: boolean;
export type SessionEndResponse = ApiSchema<"SessionEndResponse">;
export interface CrisisResource {
title: string;
number: string;
message: string;
}
export interface LearnerSessionSummary {
@ -290,6 +293,12 @@ export interface ReviewTechnique {
label: string;
}
export interface ReviewNonverbalEvent {
kind: "audio" | "silence" | "pace" | "barge_in";
label: string;
detail: string;
}
export interface ReviewNote {
author: "ai" | "instructor" | string;
tone: "good" | "watch";
@ -305,6 +314,7 @@ export interface ReviewTurn {
who: string;
text: string;
techniques: ReviewTechnique[];
nonverbal: ReviewNonverbalEvent[];
note?: ReviewNote | null;
}
@ -333,6 +343,34 @@ export interface ReviewPoint {
jumpTo?: string | null;
}
export interface ReviewWorksheetEvidence {
turnId: string;
speaker: "learner" | "client";
quote: string;
}
export interface ReviewWorksheetItem {
key: string;
label: string;
value?: string | null;
evidence: ReviewWorksheetEvidence[];
confidence: "none" | "low" | "medium";
emptyReason?: string | null;
}
export interface ReviewWorksheetSection {
key: string;
title: string;
items: ReviewWorksheetItem[];
}
export interface ReviewCaseWorksheet {
status: "empty" | "draft_from_transcript";
generatedBy: string;
sections: ReviewWorksheetSection[];
limitations: string[];
}
export interface SessionReviewResponse {
session_id: string;
client: ReviewClient;
@ -353,6 +391,7 @@ export interface SessionReviewResponse {
rubric: ReviewRubricRow[];
goodMoments: ReviewPoint[];
growthPoints: ReviewPoint[];
caseWorksheet?: ReviewCaseWorksheet | null;
nextLine?: string | null;
clientFeedback?: string | null;
audioUrl?: string | null;
@ -388,6 +427,9 @@ export interface SessionStreamDone {
effective_openness?: number;
turn_seq?: number;
safety_flagged?: boolean;
crisis_kind?: string;
crisis_resource?: CrisisResource | null;
conversation_stopped?: boolean;
}
function safeParse(data: string): unknown {
@ -453,6 +495,9 @@ export async function openSessionStream(
effective_openness: parsed.effective_openness,
turn_seq: parsed.turn_seq,
safety_flagged: parsed.safety_flagged,
crisis_kind: parsed.crisis_kind,
crisis_resource: parsed.crisis_resource,
conversation_stopped: parsed.conversation_stopped,
};
handlers.onDone?.(donePayload);
return;
@ -520,6 +565,12 @@ export const personaReviewApi = {
api.post<PersonaReviewSummary>(`/personas/review/${encodeURIComponent(personaId)}`, {
action,
}),
createDraft: (payload: PersonaDraftPayload) =>
api.post<PersonaReviewSummary>("/personas/drafts", payload),
getDraft: (personaId: string) =>
api.get<PersonaDraftDetail>(`/personas/drafts/${encodeURIComponent(personaId)}`),
updateDraft: (personaId: string, payload: PersonaDraftPayload) =>
api.put<PersonaReviewSummary>(`/personas/drafts/${encodeURIComponent(personaId)}`, payload),
};
export const sessionApi = {
@ -568,8 +619,39 @@ export interface AdminHealthResponse {
services: AdminServiceHealth[];
}
export interface AdminUsageBreakdown {
provider: string;
model: string;
turns: number;
tokens_in: number;
tokens_out: number;
cost_usd: number;
}
export interface AdminUsageBudget {
limit_usd: number;
used_ratio: number;
remaining_usd: number | null;
status: "disabled" | "ok" | "warn" | "exceeded";
}
export interface AdminUsageResponse {
source: "database" | "server_session_registry";
durable: boolean;
window_days: number;
generated_at: number;
total_turns: number;
metered_turns: number;
tokens_in: number;
tokens_out: number;
cost_usd: number;
budget: AdminUsageBudget;
by_provider: AdminUsageBreakdown[];
}
export const adminApi = {
health: () => api.get<AdminHealthResponse>("/admin/health"),
usage: (windowDays = 7) => api.get<AdminUsageResponse>(`/admin/usage?window_days=${windowDays}`),
};
export interface AdminManagedUser {
@ -629,12 +711,58 @@ export interface TeacherSessionSummary {
ended_at: string | null;
}
export interface TeacherSafetyAlert {
id: string;
session_id: string;
learner_id: string;
learner_label: string;
persona_code: string;
session_no: number;
trigger_type: string;
ko_risk_level: number;
escalated: boolean;
created_at: string;
resource_title: string;
resource_number: string;
}
export interface TeacherGrowthPoint {
session_id: string;
session_no: number;
persona_code: string;
stage: string;
started_at: string;
ended_at: string | null;
score: number | null;
rapport: number | null;
technique_count: number;
watch_count: number;
}
export interface TeacherLearnerGrowth {
learner_id: string;
learner_label: string;
sessions: number;
ended_sessions: number;
latest_at: string;
first_score: number | null;
latest_score: number | null;
score_delta: number | null;
avg_score: number | null;
avg_rapport: number | null;
trend: "up" | "down" | "flat" | "insufficient" | string;
top_techniques: string[];
points: TeacherGrowthPoint[];
}
export interface TeacherDashboardResponse {
source: string;
cohort_label: string;
total_learners: number;
active_sessions: number;
ended_sessions: number;
safety_alerts: TeacherSafetyAlert[];
learner_growth: TeacherLearnerGrowth[];
pending_reviews: TeacherSessionSummary[];
recent_sessions: TeacherSessionSummary[];
message: string;