Spaces:
Sleeping
Sleeping
| // 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<LearningMode, string> = { | |
| concept: 'Concept Explainer', | |
| socratic: 'Socratic Tutor', | |
| exam: 'Exam Prep / Quiz', | |
| assignment: 'Assignment Helper', | |
| summary: 'Quick Summary', | |
| }; | |
| const langMap: Record<Language, string> = { | |
| auto: 'Auto', | |
| en: 'English', | |
| zh: '简体中文', | |
| }; | |
| const docTypeMap: Record<FileType, string> = { | |
| syllabus: 'Syllabus', | |
| 'lecture-slides': 'Lecture Slides', | |
| 'literature-review': 'Literature Review / Paper', | |
| other: 'Other', | |
| }; | |
| async function mustJson<T>(res: Response): Promise<T> { | |
| 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<string, string>)[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<string, unknown> = { | |
| 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 }; | |