SarahXia0405 commited on
Commit
abe91a0
·
verified ·
1 Parent(s): e09f320

Update web/src/lib/api.ts

Browse files
Files changed (1) hide show
  1. web/src/lib/api.ts +35 -304
web/src/lib/api.ts CHANGED
@@ -1,350 +1,81 @@
1
  // web/src/lib/api.ts
2
- // Aligns with api/server.py routes:
3
- // POST /api/login, /api/chat, /api/upload, /api/export, /api/summary, /api/feedback
4
- // GET /api/memoryline
5
 
6
- export type LearningMode =
7
- | "general"
8
- | "concept"
9
- | "socratic"
10
- | "exam"
11
- | "assignment"
12
- | "summary";
13
  export type LanguagePref = "Auto" | "English" | "中文";
14
- export type DocType =
15
- | "Syllabus"
16
- | "Lecture Slides / PPT"
17
- | "Literature Review / Paper"
18
- | "Other Course Document";
19
 
20
  const DEFAULT_TIMEOUT_MS = 20000;
21
 
22
  function getBaseUrl() {
23
- // Vite env: VITE_API_BASE can be "", "http://localhost:8000", etc.
24
  const v = (import.meta as any)?.env?.VITE_API_BASE as string | undefined;
25
  return v && v.trim() ? v.trim() : "";
26
  }
27
 
28
- async function fetchWithTimeout(
29
- input: RequestInfo,
30
- init?: RequestInit,
31
- timeoutMs = DEFAULT_TIMEOUT_MS
32
- ) {
33
  const controller = new AbortController();
34
  const id = setTimeout(() => controller.abort(), timeoutMs);
35
  try {
36
- return await fetch(input, { ...init, signal: controller.signal });
 
37
  } finally {
38
  clearTimeout(id);
39
  }
40
  }
41
 
42
- async function parseJsonSafe(res: Response) {
43
- const text = await res.text();
44
- try {
45
- return text ? JSON.parse(text) : null;
46
- } catch {
47
- return { _raw: text };
48
- }
49
- }
50
-
51
- function errMsg(data: any, fallback: string) {
52
- return data && (data.error || data.detail || data.message)
53
- ? String(data.error || data.detail || data.message)
54
- : fallback;
55
- }
56
-
57
- // --------------------
58
- // /api/login
59
- // --------------------
60
- export type ApiLoginReq = {
61
- name: string;
62
- user_id: string;
63
- };
64
-
65
- export type ApiLoginResp =
66
- | { ok: true; user: { name: string; user_id: string } }
67
- | { ok: false; error: string };
68
-
69
- export async function apiLogin(payload: ApiLoginReq): Promise<ApiLoginResp> {
70
- const base = getBaseUrl();
71
- const res = await fetchWithTimeout(`${base}/api/login`, {
72
- method: "POST",
73
- headers: { "Content-Type": "application/json" },
74
- body: JSON.stringify(payload),
75
- });
76
-
77
- const data = await parseJsonSafe(res);
78
- if (!res.ok) throw new Error(errMsg(data, `apiLogin failed (${res.status})`));
79
- return data as ApiLoginResp;
80
- }
81
-
82
- // --------------------
83
- // /api/chat
84
- // --------------------
85
- export type ApiChatReq = {
86
  user_id: string;
87
  message: string;
88
- learning_mode: string; // backend expects string (not strict union)
89
- language_preference?: string; // "Auto" | "English" | "中文"
90
- doc_type?: string; // "Syllabus" | "Lecture Slides / PPT" | ...
91
- course_id?: string; // e.g. "course_ist345"
92
  };
93
 
94
- // allow backend to return either object refs or preformatted string refs
95
- export type ApiChatRefObj = { source_file?: string; section?: string };
96
- export type ApiChatRefRaw = ApiChatRefObj | string;
97
-
98
- // ✅ normalize ANY ref format into {source_file, section} so App can map reliably
99
- function normalizeRefs(raw: any): ApiChatRefObj[] {
100
- const arr: any[] = Array.isArray(raw) ? raw : [];
101
- return arr
102
- .map((x) => {
103
- // Case A: already object
104
- if (x && typeof x === "object" && !Array.isArray(x)) {
105
- const a = x.source_file != null ? String(x.source_file) : "";
106
- const b = x.section != null ? String(x.section) : "";
107
- return { source_file: a || undefined, section: b || undefined };
108
- }
109
-
110
- // Case B: string like "file.pdf — p3#1" (or just "file.pdf")
111
- if (typeof x === "string") {
112
- const s = x.trim();
113
- if (!s) return null;
114
- const parts = s.split("—").map((p) => p.trim()).filter(Boolean);
115
- if (parts.length >= 2) {
116
- return { source_file: parts[0], section: parts.slice(1).join(" — ") };
117
- }
118
- return { source_file: s, section: undefined };
119
- }
120
-
121
- return null;
122
- })
123
- .filter(Boolean) as ApiChatRefObj[];
124
- }
125
-
126
- export type ApiChatResp = {
127
  reply: string;
128
- session_status_md: string;
129
-
130
- // ✅ after normalization, always object array
131
- refs: ApiChatRefObj[];
132
-
133
- latency_ms: number;
134
-
135
- // optional tracing run id returned by backend
136
- run_id?: string | null;
137
  };
138
 
139
- export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
140
  const base = getBaseUrl();
141
- const res = await fetchWithTimeout(
142
- `${base}/api/chat`,
143
- {
144
- method: "POST",
145
- headers: { "Content-Type": "application/json" },
146
- body: JSON.stringify({
147
- language_preference: "Auto",
148
- doc_type: "Syllabus",
149
- course_id: "course_ist345", // ✅ default
150
- ...payload,
151
- }),
152
- },
153
- 60000 // chat can be slow
154
- );
155
-
156
- const data = await parseJsonSafe(res);
157
- if (!res.ok) throw new Error(errMsg(data, `apiChat failed (${res.status})`));
158
-
159
- // backend returns { reply, session_status_md, refs, latency_ms, run_id? }
160
- // but refs may be ApiChatRefObj[] OR string[]
161
- const out = data as any;
162
-
163
- return {
164
- reply: String(out?.reply ?? ""),
165
- session_status_md: String(out?.session_status_md ?? ""),
166
- refs: normalizeRefs(out?.refs ?? out?.references),
167
- latency_ms: Number(out?.latency_ms ?? 0),
168
- run_id: out?.run_id ?? null,
169
- };
170
- }
171
-
172
- // --------------------
173
- // /api/quiz/start
174
- // --------------------
175
- export type ApiQuizStartReq = {
176
- user_id: string;
177
- language_preference?: string; // "Auto" | "English" | "中文"
178
- doc_type?: string; // default: "Literature Review / Paper" (backend default ok)
179
- learning_mode?: string; // default: "quiz"
180
- course_id?: string;
181
- };
182
-
183
- export type ApiQuizStartResp = {
184
- reply: string;
185
- session_status_md: string;
186
- refs: ApiChatRefObj[];
187
- latency_ms: number;
188
- run_id?: string | null;
189
- };
190
-
191
- export async function apiQuizStart(
192
- payload: ApiQuizStartReq
193
- ): Promise<ApiQuizStartResp> {
194
- const base = getBaseUrl();
195
- const res = await fetchWithTimeout(
196
- `${base}/api/quiz/start`,
197
- {
198
- method: "POST",
199
- headers: { "Content-Type": "application/json" },
200
- body: JSON.stringify({
201
- language_preference: "Auto",
202
- doc_type: "Literature Review / Paper",
203
- learning_mode: "quiz",
204
- course_id: "course_ist345", // ✅ default
205
- ...payload,
206
- }),
207
- },
208
- 60000
209
- );
210
-
211
- const data = await parseJsonSafe(res);
212
- if (!res.ok) throw new Error(errMsg(data, `apiQuizStart failed (${res.status})`));
213
-
214
- const out = data as any;
215
-
216
- return {
217
- reply: String(out?.reply ?? ""),
218
- session_status_md: String(out?.session_status_md ?? ""),
219
- refs: normalizeRefs(out?.refs ?? out?.references),
220
- latency_ms: Number(out?.latency_ms ?? 0),
221
- run_id: out?.run_id ?? null,
222
- };
223
- }
224
-
225
- // --------------------
226
- // /api/upload
227
- // --------------------
228
- export type ApiUploadResp = {
229
- ok: boolean;
230
- added_chunks?: number;
231
- status_md?: string;
232
- error?: string;
233
- course_id?: string;
234
- };
235
-
236
- export async function apiUpload(args: {
237
- user_id: string;
238
- doc_type: string;
239
- file: File;
240
- }): Promise<ApiUploadResp> {
241
- const base = getBaseUrl();
242
- const fd = new FormData();
243
- fd.append("user_id", args.user_id);
244
- fd.append("doc_type", args.doc_type);
245
- fd.append("file", args.file);
246
- fd.append("course_id", args.course_id || "course_ist345");
247
-
248
- const res = await fetchWithTimeout(
249
- `${base}/api/upload`,
250
- { method: "POST", body: fd },
251
- 120000
252
- );
253
- const data = await parseJsonSafe(res);
254
- if (!res.ok) throw new Error(errMsg(data, `apiUpload failed (${res.status})`));
255
- return data as ApiUploadResp;
256
- }
257
-
258
- // --------------------
259
- // /api/export
260
- // --------------------
261
- export async function apiExport(payload: {
262
- user_id: string;
263
- learning_mode: string;
264
- }): Promise<{ markdown: string }> {
265
- const base = getBaseUrl();
266
- const res = await fetchWithTimeout(`${base}/api/export`, {
267
  method: "POST",
268
  headers: { "Content-Type": "application/json" },
269
  body: JSON.stringify(payload),
270
  });
271
 
272
- const data = await parseJsonSafe(res);
273
- if (!res.ok) throw new Error(errMsg(data, `apiExport failed (${res.status})`));
274
- return data as { markdown: string };
275
- }
276
-
277
- // --------------------
278
- // /api/summary
279
- // --------------------
280
- export async function apiSummary(payload: {
281
- user_id: string;
282
- learning_mode: string;
283
- language_preference?: string;
284
- }): Promise<{ markdown: string }> {
285
- const base = getBaseUrl();
286
- const res = await fetchWithTimeout(`${base}/api/summary`, {
287
- method: "POST",
288
- headers: { "Content-Type": "application/json" },
289
- body: JSON.stringify({ language_preference: "Auto", ...payload }),
290
- });
291
 
292
- const data = await parseJsonSafe(res);
293
- if (!res.ok) throw new Error(errMsg(data, `apiSummary failed (${res.status})`));
294
- return data as { markdown: string };
295
  }
296
 
297
- // --------------------
298
- // /api/feedback
299
- // --------------------
300
- export type ApiFeedbackReq = {
301
  user_id: string;
302
- rating: "helpful" | "not_helpful";
303
-
304
- // run id so backend can attach feedback to tracing run
305
- run_id?: string | null;
306
-
307
- assistant_message_id?: string;
308
- assistant_text: string;
309
- user_text?: string;
310
-
311
- comment?: string;
312
-
313
- tags?: string[];
314
- refs?: string[];
315
-
316
- learning_mode?: string;
317
- doc_type?: string;
318
- timestamp_ms?: number;
319
  };
320
 
321
- export async function apiFeedback(
322
- payload: ApiFeedbackReq
323
- ): Promise<{ ok: boolean }> {
324
  const base = getBaseUrl();
325
- const res = await fetchWithTimeout(`${base}/api/feedback`, {
326
  method: "POST",
327
  headers: { "Content-Type": "application/json" },
328
  body: JSON.stringify(payload),
329
  });
330
 
331
- const data = await parseJsonSafe(res);
332
- if (!res.ok) throw new Error(errMsg(data, `apiFeedback failed (${res.status})`));
333
- return data as { ok: boolean };
334
- }
335
 
336
- // --------------------
337
- // /api/memoryline
338
- // --------------------
339
- export async function apiMemoryline(
340
- user_id: string
341
- ): Promise<{ next_review_label: string; progress_pct: number }> {
342
- const base = getBaseUrl();
343
- const res = await fetchWithTimeout(
344
- `${base}/api/memoryline?user_id=${encodeURIComponent(user_id)}`,
345
- { method: "GET" }
346
- );
347
- const data = await parseJsonSafe(res);
348
- if (!res.ok) throw new Error(errMsg(data, `apiMemoryline failed (${res.status})`));
349
- return data as { next_review_label: string; progress_pct: number };
350
  }
 
 
 
1
  // web/src/lib/api.ts
 
 
 
2
 
3
+ export type LearningMode = "general" | "concept" | "socratic" | "exam" | "assignment" | "summary" | "quiz";
 
 
 
 
 
 
4
  export type LanguagePref = "Auto" | "English" | "中文";
 
 
 
 
 
5
 
6
  const DEFAULT_TIMEOUT_MS = 20000;
7
 
8
  function getBaseUrl() {
 
9
  const v = (import.meta as any)?.env?.VITE_API_BASE as string | undefined;
10
  return v && v.trim() ? v.trim() : "";
11
  }
12
 
13
+ async function fetchWithTimeout(input: RequestInfo, init?: RequestInit, timeoutMs = DEFAULT_TIMEOUT_MS) {
 
 
 
 
14
  const controller = new AbortController();
15
  const id = setTimeout(() => controller.abort(), timeoutMs);
16
  try {
17
+ const res = await fetch(input, { ...init, signal: controller.signal });
18
+ return res;
19
  } finally {
20
  clearTimeout(id);
21
  }
22
  }
23
 
24
+ export type ChatRequest = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  user_id: string;
26
  message: string;
27
+ learning_mode: LearningMode | string;
28
+ language_preference: LanguagePref | string;
29
+ doc_type: string; // "All" recommended default
30
+ course_id?: string; // NEW: course selector for RAG
31
  };
32
 
33
+ export type ChatResponse = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  reply: string;
35
+ session_status_md?: string;
36
+ refs?: Array<{ source_file?: string; section?: string } | string>;
37
+ latency_ms?: number;
38
+ run_id?: string;
 
 
 
 
 
39
  };
40
 
41
+ export async function apiChat(payload: ChatRequest): Promise<ChatResponse> {
42
  const base = getBaseUrl();
43
+ const res = await fetchWithTimeout(`${base}/api/chat`, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  method: "POST",
45
  headers: { "Content-Type": "application/json" },
46
  body: JSON.stringify(payload),
47
  });
48
 
49
+ if (!res.ok) {
50
+ const txt = await res.text().catch(() => "");
51
+ throw new Error(txt || `apiChat failed: ${res.status}`);
52
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ return (await res.json()) as ChatResponse;
 
 
55
  }
56
 
57
+ export type QuizStartRequest = {
 
 
 
58
  user_id: string;
59
+ language_preference: LanguagePref | string;
60
+ doc_type: string;
61
+ learning_mode: "quiz";
62
+ course_id?: string; // NEW
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  };
64
 
65
+ export async function apiQuizStart(payload: QuizStartRequest): Promise<ChatResponse> {
 
 
66
  const base = getBaseUrl();
67
+ const res = await fetchWithTimeout(`${base}/api/quiz_start`, {
68
  method: "POST",
69
  headers: { "Content-Type": "application/json" },
70
  body: JSON.stringify(payload),
71
  });
72
 
73
+ if (!res.ok) {
74
+ const txt = await res.text().catch(() => "");
75
+ throw new Error(txt || `apiQuizStart failed: ${res.status}`);
76
+ }
77
 
78
+ return (await res.json()) as ChatResponse;
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
+
81
+ // 其他 apiUpload/apiMemoryline/apiFeedback... 你原来怎么写就保留