Spaces:
Sleeping
Sleeping
| // web/src/lib/api.ts | |
| // Aligns with api/server.py routes: | |
| // POST /api/login, /api/chat, /api/upload, /api/export, /api/summary, /api/feedback | |
| // GET /api/memoryline | |
| export type LearningMode = "general" | "concept" | "socratic" | "exam" | "assignment" | "summary"; | |
| export type LanguagePref = "Auto" | "English" | "中文"; | |
| export type DocType = "Syllabus" | "Lecture Slides / PPT" | "Literature Review / Paper" | "Other Course Document"; | |
| const DEFAULT_TIMEOUT_MS = 20000; | |
| function getBaseUrl() { | |
| // Vite env: VITE_API_BASE can be "", "http://localhost:8000", etc. | |
| const v = (import.meta as any)?.env?.VITE_API_BASE as string | undefined; | |
| return v && v.trim() ? v.trim() : ""; | |
| } | |
| async function fetchWithTimeout(input: RequestInfo, init?: RequestInit, timeoutMs = DEFAULT_TIMEOUT_MS) { | |
| const controller = new AbortController(); | |
| const id = setTimeout(() => controller.abort(), timeoutMs); | |
| try { | |
| return await fetch(input, { ...init, signal: controller.signal }); | |
| } finally { | |
| clearTimeout(id); | |
| } | |
| } | |
| async function parseJsonSafe(res: Response) { | |
| const text = await res.text(); | |
| try { | |
| return text ? JSON.parse(text) : null; | |
| } catch { | |
| return { _raw: text }; | |
| } | |
| } | |
| function errMsg(data: any, fallback: string) { | |
| return (data && (data.error || data.detail || data.message)) | |
| ? String(data.error || data.detail || data.message) | |
| : fallback; | |
| } | |
| // -------------------- | |
| // /api/login | |
| // -------------------- | |
| export type ApiLoginReq = { | |
| name: string; | |
| user_id: string; | |
| }; | |
| export type ApiLoginResp = | |
| | { ok: true; user: { name: string; user_id: string } } | |
| | { ok: false; error: string }; | |
| export async function apiLogin(payload: ApiLoginReq): Promise<ApiLoginResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/login`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiLogin failed (${res.status})`)); | |
| return data as ApiLoginResp; | |
| } | |
| // -------------------- | |
| // /api/chat | |
| // -------------------- | |
| export type ApiChatReq = { | |
| user_id: string; | |
| message: string; | |
| learning_mode: string; // backend expects string (not strict union) | |
| language_preference?: string; // "Auto" | "English" | "中文" | |
| doc_type?: string; // "Syllabus" | "Lecture Slides / PPT" | ... | |
| }; | |
| export type ApiChatRef = { source_file?: string; section?: string }; | |
| export type ApiChatResp = { | |
| reply: string; | |
| session_status_md: string; | |
| refs: ApiChatRef[]; | |
| latency_ms: number; | |
| // ✅ NEW: optional tracing run id returned by backend | |
| run_id?: string | null; | |
| }; | |
| export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/chat`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| language_preference: "Auto", | |
| doc_type: "Syllabus", | |
| ...payload, | |
| }), | |
| }, | |
| 60000 // chat can be slow | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiChat failed (${res.status})`)); | |
| // backend returns { reply, session_status_md, refs, latency_ms, run_id? } | |
| return data as ApiChatResp; | |
| } | |
| // -------------------- | |
| // /api/quiz/start | |
| // -------------------- | |
| export type ApiQuizStartReq = { | |
| user_id: string; | |
| language_preference?: string; // "Auto" | "English" | "中文" | |
| doc_type?: string; // default: "Literature Review / Paper" (backend default ok) | |
| learning_mode?: string; // default: "quiz" | |
| }; | |
| export type ApiQuizStartResp = { | |
| reply: string; | |
| session_status_md: string; | |
| refs: ApiChatRef[]; | |
| latency_ms: number; | |
| // ✅ NEW: optional tracing run id returned by backend (if enabled) | |
| run_id?: string | null; | |
| }; | |
| export async function apiQuizStart(payload: ApiQuizStartReq): Promise<ApiQuizStartResp> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/quiz/start`, | |
| { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| language_preference: "Auto", | |
| doc_type: "Literature Review / Paper", | |
| learning_mode: "quiz", | |
| ...payload, | |
| }), | |
| }, | |
| 60000 | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiQuizStart failed (${res.status})`)); | |
| return data as ApiQuizStartResp; | |
| } | |
| // -------------------- | |
| // /api/upload | |
| // -------------------- | |
| export type ApiUploadResp = { | |
| ok: boolean; | |
| added_chunks?: number; | |
| status_md?: string; | |
| error?: string; | |
| }; | |
| export async function apiUpload(args: { user_id: string; doc_type: string; file: File }): Promise<ApiUploadResp> { | |
| const base = getBaseUrl(); | |
| const fd = new FormData(); | |
| fd.append("user_id", args.user_id); | |
| fd.append("doc_type", args.doc_type); | |
| fd.append("file", args.file); | |
| const res = await fetchWithTimeout(`${base}/api/upload`, { method: "POST", body: fd }, 120000); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiUpload failed (${res.status})`)); | |
| return data as ApiUploadResp; | |
| } | |
| // -------------------- | |
| // /api/export | |
| // -------------------- | |
| export async function apiExport(payload: { user_id: string; learning_mode: string }): Promise<{ markdown: string }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/export`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiExport failed (${res.status})`)); | |
| return data as { markdown: string }; | |
| } | |
| // -------------------- | |
| // /api/summary | |
| // -------------------- | |
| export async function apiSummary(payload: { | |
| user_id: string; | |
| learning_mode: string; | |
| language_preference?: string; | |
| }): Promise<{ markdown: string }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/summary`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ language_preference: "Auto", ...payload }), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiSummary failed (${res.status})`)); | |
| return data as { markdown: string }; | |
| } | |
| // -------------------- | |
| // /api/feedback | |
| // -------------------- | |
| export type ApiFeedbackReq = { | |
| user_id: string; | |
| rating: "helpful" | "not_helpful"; | |
| // ✅ NEW: run id so backend can attach feedback to tracing run | |
| run_id?: string | null; | |
| assistant_message_id?: string; | |
| assistant_text: string; | |
| user_text?: string; | |
| comment?: string; | |
| tags?: string[]; | |
| refs?: string[]; | |
| learning_mode?: string; | |
| doc_type?: string; | |
| timestamp_ms?: number; | |
| }; | |
| export async function apiFeedback(payload: ApiFeedbackReq): Promise<{ ok: boolean }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout(`${base}/api/feedback`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiFeedback failed (${res.status})`)); | |
| return data as { ok: boolean }; | |
| } | |
| // -------------------- | |
| // /api/memoryline | |
| // -------------------- | |
| export async function apiMemoryline(user_id: string): Promise<{ next_review_label: string; progress_pct: number }> { | |
| const base = getBaseUrl(); | |
| const res = await fetchWithTimeout( | |
| `${base}/api/memoryline?user_id=${encodeURIComponent(user_id)}`, | |
| { method: "GET" } | |
| ); | |
| const data = await parseJsonSafe(res); | |
| if (!res.ok) throw new Error(errMsg(data, `apiMemoryline failed (${res.status})`)); | |
| return data as { next_review_label: string; progress_pct: number }; | |
| } | |