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

Update web/src/lib/api.ts

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