// web/src/lib/api.ts export type LearningMode = 'concept' | 'socratic' | 'exam' | 'assignment' | 'summary'; export type Language = 'auto' | 'en' | 'zh'; export type FileType = 'syllabus' | 'lecture-slides' | 'literature-review' | 'other'; export interface User { name: string; email: string; // use email as user_id } const modeMap: Record = { concept: 'Concept Explainer', socratic: 'Socratic Tutor', exam: 'Exam Prep / Quiz', assignment: 'Assignment Helper', summary: 'Quick Summary', }; const langMap: Record = { auto: 'Auto', en: 'English', zh: '简体中文', }; const docTypeMap: Record = { syllabus: 'Syllabus', 'lecture-slides': 'Lecture Slides', 'literature-review': 'Literature Review / Paper', other: 'Other', }; async function mustJson(res: Response): Promise { if (res.ok) return (await res.json()) as T; const txt = await res.text().catch(() => ''); throw new Error(`${res.status} ${res.statusText}${txt ? ` — ${txt}` : ''}`); } // ----------------------------- // Helpers // ----------------------------- function resolveDocType(docType?: FileType | string): string | undefined { if (!docType) return undefined; // If docType is one of FileType keys, map it; otherwise assume it's already backend-ready. const mapped = (docTypeMap as Record)[docType]; return mapped ?? docType; } // ----------------------------- // Core APIs // ----------------------------- export async function apiLogin(user: User) { const res = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: user.name, user_id: user.email }), }); return mustJson<{ ok: boolean; user: { name: string; user_id: string } }>(res); } export async function apiChat(params: { user: User; message: string; learningMode: LearningMode; language: Language; docType?: FileType | string; // optional override }) { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: params.user.email, message: params.message, learning_mode: modeMap[params.learningMode], language_preference: langMap[params.language], doc_type: resolveDocType(params.docType) ?? 'Syllabus', }), }); return mustJson<{ reply: string; refs?: Array<{ source_file?: string; section?: string }>; latency_ms?: number; session_status_md?: string; }>(res); } export async function apiUpload(params: { user: User; file: File; fileType: FileType; }) { const fd = new FormData(); fd.append('user_id', params.user.email); fd.append('doc_type', docTypeMap[params.fileType]); fd.append('file', params.file); const res = await fetch('/api/upload', { method: 'POST', body: fd }); return mustJson<{ ok: boolean; added_chunks: number; status_md: string }>(res); } export async function apiExport(params: { user: User; learningMode: LearningMode; }) { const res = await fetch('/api/export', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: params.user.email, learning_mode: modeMap[params.learningMode], }), }); return mustJson<{ markdown: string }>(res); } export async function apiSummary(params: { user: User; learningMode: LearningMode; language: Language; }) { const res = await fetch('/api/summary', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: params.user.email, learning_mode: modeMap[params.learningMode], language_preference: langMap[params.language], }), }); return mustJson<{ markdown: string }>(res); } // ----------------------------- // Feedback API (NEW) // ----------------------------- export type FeedbackRating = 'helpful' | 'not_helpful'; export interface FeedbackParams { user: User; rating: FeedbackRating; // which assistant msg is being rated (optional but recommended) assistantMessageId?: string; // texts assistantText: string; userText?: string; // UI details tags?: string[]; // structured tags/chips comment?: string; // free-text // context refs?: string[]; learningMode?: LearningMode; docType?: FileType | string; timestampMs?: number; } export async function apiFeedback(params: FeedbackParams) { const payload: Record = { user_id: params.user.email, rating: params.rating, // Only include if present (avoid null noise) ...(params.assistantMessageId ? { assistant_message_id: params.assistantMessageId } : {}), assistant_text: params.assistantText, user_text: (params.userText ?? '').trim(), tags: params.tags ?? [], comment: (params.comment ?? '').trim(), refs: params.refs ?? [], learning_mode: params.learningMode ? modeMap[params.learningMode] : undefined, doc_type: resolveDocType(params.docType), timestamp_ms: params.timestampMs ?? Date.now(), }; // Remove undefined keys (extra safety; FastAPI pydantic will ignore missing but this keeps payload clean) Object.keys(payload).forEach((k) => { if (payload[k] === undefined) delete payload[k]; }); const res = await fetch('/api/feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); return mustJson<{ ok: boolean }>(res); } // Optional: export maps if other components need them export const _maps = { modeMap, langMap, docTypeMap };