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