SarahXia0405's picture
Update web/src/lib/api.ts
c4ca284 verified
// 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 };
}