SarahXia0405 commited on
Commit
8c39f5c
·
verified ·
1 Parent(s): 2519d03

Update web/src/lib/api.ts

Browse files
Files changed (1) hide show
  1. web/src/lib/api.ts +103 -23
web/src/lib/api.ts CHANGED
@@ -3,9 +3,19 @@
3
  // POST /api/login, /api/chat, /api/upload, /api/export, /api/summary, /api/feedback
4
  // GET /api/memoryline
5
 
6
- export type LearningMode = "general" | "concept" | "socratic" | "exam" | "assignment" | "summary";
 
 
 
 
 
 
7
  export type LanguagePref = "Auto" | "English" | "中文";
8
- export type DocType = "Syllabus" | "Lecture Slides / PPT" | "Literature Review / Paper" | "Other Course Document";
 
 
 
 
9
 
10
  const DEFAULT_TIMEOUT_MS = 20000;
11
 
@@ -15,7 +25,11 @@ function getBaseUrl() {
15
  return v && v.trim() ? v.trim() : "";
16
  }
17
 
18
- async function fetchWithTimeout(input: RequestInfo, init?: RequestInit, timeoutMs = DEFAULT_TIMEOUT_MS) {
 
 
 
 
19
  const controller = new AbortController();
20
  const id = setTimeout(() => controller.abort(), timeoutMs);
21
  try {
@@ -35,7 +49,7 @@ async function parseJsonSafe(res: Response) {
35
  }
36
 
37
  function errMsg(data: any, fallback: string) {
38
- return (data && (data.error || data.detail || data.message))
39
  ? String(data.error || data.detail || data.message)
40
  : fallback;
41
  }
@@ -71,20 +85,53 @@ export async function apiLogin(payload: ApiLoginReq): Promise<ApiLoginResp> {
71
  export type ApiChatReq = {
72
  user_id: string;
73
  message: string;
74
- learning_mode: string; // backend expects string (not strict union)
75
  language_preference?: string; // "Auto" | "English" | "中文"
76
- doc_type?: string; // "Syllabus" | "Lecture Slides / PPT" | ...
77
  };
78
 
79
- export type ApiChatRef = { source_file?: string; section?: string };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  export type ApiChatResp = {
82
  reply: string;
83
  session_status_md: string;
84
- refs: ApiChatRef[];
 
 
 
85
  latency_ms: number;
86
 
87
- // ✅ NEW: optional tracing run id returned by backend
88
  run_id?: string | null;
89
  };
90
 
@@ -108,7 +155,16 @@ export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
108
  if (!res.ok) throw new Error(errMsg(data, `apiChat failed (${res.status})`));
109
 
110
  // backend returns { reply, session_status_md, refs, latency_ms, run_id? }
111
- return data as ApiChatResp;
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  // --------------------
@@ -117,21 +173,21 @@ export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
117
  export type ApiQuizStartReq = {
118
  user_id: string;
119
  language_preference?: string; // "Auto" | "English" | "中文"
120
- doc_type?: string; // default: "Literature Review / Paper" (backend default ok)
121
- learning_mode?: string; // default: "quiz"
122
  };
123
 
124
  export type ApiQuizStartResp = {
125
  reply: string;
126
  session_status_md: string;
127
- refs: ApiChatRef[];
128
  latency_ms: number;
129
-
130
- // ✅ NEW: optional tracing run id returned by backend (if enabled)
131
  run_id?: string | null;
132
  };
133
 
134
- export async function apiQuizStart(payload: ApiQuizStartReq): Promise<ApiQuizStartResp> {
 
 
135
  const base = getBaseUrl();
136
  const res = await fetchWithTimeout(
137
  `${base}/api/quiz/start`,
@@ -150,7 +206,16 @@ export async function apiQuizStart(payload: ApiQuizStartReq): Promise<ApiQuizSta
150
 
151
  const data = await parseJsonSafe(res);
152
  if (!res.ok) throw new Error(errMsg(data, `apiQuizStart failed (${res.status})`));
153
- return data as ApiQuizStartResp;
 
 
 
 
 
 
 
 
 
154
  }
155
 
156
  // --------------------
@@ -163,14 +228,22 @@ export type ApiUploadResp = {
163
  error?: string;
164
  };
165
 
166
- export async function apiUpload(args: { user_id: string; doc_type: string; file: File }): Promise<ApiUploadResp> {
 
 
 
 
167
  const base = getBaseUrl();
168
  const fd = new FormData();
169
  fd.append("user_id", args.user_id);
170
  fd.append("doc_type", args.doc_type);
171
  fd.append("file", args.file);
172
 
173
- const res = await fetchWithTimeout(`${base}/api/upload`, { method: "POST", body: fd }, 120000);
 
 
 
 
174
  const data = await parseJsonSafe(res);
175
  if (!res.ok) throw new Error(errMsg(data, `apiUpload failed (${res.status})`));
176
  return data as ApiUploadResp;
@@ -179,7 +252,10 @@ export async function apiUpload(args: { user_id: string; doc_type: string; file:
179
  // --------------------
180
  // /api/export
181
  // --------------------
182
- export async function apiExport(payload: { user_id: string; learning_mode: string }): Promise<{ markdown: string }> {
 
 
 
183
  const base = getBaseUrl();
184
  const res = await fetchWithTimeout(`${base}/api/export`, {
185
  method: "POST",
@@ -219,7 +295,7 @@ export type ApiFeedbackReq = {
219
  user_id: string;
220
  rating: "helpful" | "not_helpful";
221
 
222
- // ✅ NEW: run id so backend can attach feedback to tracing run
223
  run_id?: string | null;
224
 
225
  assistant_message_id?: string;
@@ -236,7 +312,9 @@ export type ApiFeedbackReq = {
236
  timestamp_ms?: number;
237
  };
238
 
239
- export async function apiFeedback(payload: ApiFeedbackReq): Promise<{ ok: boolean }> {
 
 
240
  const base = getBaseUrl();
241
  const res = await fetchWithTimeout(`${base}/api/feedback`, {
242
  method: "POST",
@@ -252,7 +330,9 @@ export async function apiFeedback(payload: ApiFeedbackReq): Promise<{ ok: boolea
252
  // --------------------
253
  // /api/memoryline
254
  // --------------------
255
- export async function apiMemoryline(user_id: string): Promise<{ next_review_label: string; progress_pct: number }> {
 
 
256
  const base = getBaseUrl();
257
  const res = await fetchWithTimeout(
258
  `${base}/api/memoryline?user_id=${encodeURIComponent(user_id)}`,
 
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
 
 
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 {
 
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
  }
 
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
  };
92
 
93
+ // allow backend to return either object refs or preformatted string refs
94
+ export type ApiChatRefObj = { source_file?: string; section?: string };
95
+ export type ApiChatRefRaw = ApiChatRefObj | string;
96
+
97
+ // ✅ normalize ANY ref format into {source_file, section} so App can map reliably
98
+ function normalizeRefs(raw: any): ApiChatRefObj[] {
99
+ const arr: any[] = Array.isArray(raw) ? raw : [];
100
+ return arr
101
+ .map((x) => {
102
+ // Case A: already object
103
+ if (x && typeof x === "object" && !Array.isArray(x)) {
104
+ const a = x.source_file != null ? String(x.source_file) : "";
105
+ const b = x.section != null ? String(x.section) : "";
106
+ return { source_file: a || undefined, section: b || undefined };
107
+ }
108
+
109
+ // Case B: string like "file.pdf — p3#1" (or just "file.pdf")
110
+ if (typeof x === "string") {
111
+ const s = x.trim();
112
+ if (!s) return null;
113
+ const parts = s.split("—").map((p) => p.trim()).filter(Boolean);
114
+ if (parts.length >= 2) {
115
+ return { source_file: parts[0], section: parts.slice(1).join(" — ") };
116
+ }
117
+ return { source_file: s, section: undefined };
118
+ }
119
+
120
+ return null;
121
+ })
122
+ .filter(Boolean) as ApiChatRefObj[];
123
+ }
124
 
125
  export type ApiChatResp = {
126
  reply: string;
127
  session_status_md: string;
128
+
129
+ // ✅ after normalization, always object array
130
+ refs: ApiChatRefObj[];
131
+
132
  latency_ms: number;
133
 
134
+ // optional tracing run id returned by backend
135
  run_id?: string | null;
136
  };
137
 
 
155
  if (!res.ok) throw new Error(errMsg(data, `apiChat failed (${res.status})`));
156
 
157
  // backend returns { reply, session_status_md, refs, latency_ms, run_id? }
158
+ // but refs may be ApiChatRefObj[] OR string[]
159
+ const out = data as any;
160
+
161
+ return {
162
+ reply: String(out?.reply ?? ""),
163
+ session_status_md: String(out?.session_status_md ?? ""),
164
+ refs: normalizeRefs(out?.refs ?? out?.references),
165
+ latency_ms: Number(out?.latency_ms ?? 0),
166
+ run_id: out?.run_id ?? null,
167
+ };
168
  }
169
 
170
  // --------------------
 
173
  export type ApiQuizStartReq = {
174
  user_id: string;
175
  language_preference?: string; // "Auto" | "English" | "中文"
176
+ doc_type?: string; // default: "Literature Review / Paper" (backend default ok)
177
+ learning_mode?: string; // default: "quiz"
178
  };
179
 
180
  export type ApiQuizStartResp = {
181
  reply: string;
182
  session_status_md: string;
183
+ refs: ApiChatRefObj[];
184
  latency_ms: number;
 
 
185
  run_id?: string | null;
186
  };
187
 
188
+ export async function apiQuizStart(
189
+ payload: ApiQuizStartReq
190
+ ): Promise<ApiQuizStartResp> {
191
  const base = getBaseUrl();
192
  const res = await fetchWithTimeout(
193
  `${base}/api/quiz/start`,
 
206
 
207
  const data = await parseJsonSafe(res);
208
  if (!res.ok) throw new Error(errMsg(data, `apiQuizStart failed (${res.status})`));
209
+
210
+ const out = data as any;
211
+
212
+ return {
213
+ reply: String(out?.reply ?? ""),
214
+ session_status_md: String(out?.session_status_md ?? ""),
215
+ refs: normalizeRefs(out?.refs ?? out?.references),
216
+ latency_ms: Number(out?.latency_ms ?? 0),
217
+ run_id: out?.run_id ?? null,
218
+ };
219
  }
220
 
221
  // --------------------
 
228
  error?: string;
229
  };
230
 
231
+ export async function apiUpload(args: {
232
+ user_id: string;
233
+ doc_type: string;
234
+ file: File;
235
+ }): Promise<ApiUploadResp> {
236
  const base = getBaseUrl();
237
  const fd = new FormData();
238
  fd.append("user_id", args.user_id);
239
  fd.append("doc_type", args.doc_type);
240
  fd.append("file", args.file);
241
 
242
+ const res = await fetchWithTimeout(
243
+ `${base}/api/upload`,
244
+ { method: "POST", body: fd },
245
+ 120000
246
+ );
247
  const data = await parseJsonSafe(res);
248
  if (!res.ok) throw new Error(errMsg(data, `apiUpload failed (${res.status})`));
249
  return data as ApiUploadResp;
 
252
  // --------------------
253
  // /api/export
254
  // --------------------
255
+ export async function apiExport(payload: {
256
+ user_id: string;
257
+ learning_mode: string;
258
+ }): Promise<{ markdown: string }> {
259
  const base = getBaseUrl();
260
  const res = await fetchWithTimeout(`${base}/api/export`, {
261
  method: "POST",
 
295
  user_id: string;
296
  rating: "helpful" | "not_helpful";
297
 
298
+ // run id so backend can attach feedback to tracing run
299
  run_id?: string | null;
300
 
301
  assistant_message_id?: string;
 
312
  timestamp_ms?: number;
313
  };
314
 
315
+ export async function apiFeedback(
316
+ payload: ApiFeedbackReq
317
+ ): Promise<{ ok: boolean }> {
318
  const base = getBaseUrl();
319
  const res = await fetchWithTimeout(`${base}/api/feedback`, {
320
  method: "POST",
 
330
  // --------------------
331
  // /api/memoryline
332
  // --------------------
333
+ export async function apiMemoryline(
334
+ user_id: string
335
+ ): Promise<{ next_review_label: string; progress_pct: number }> {
336
  const base = getBaseUrl();
337
  const res = await fetchWithTimeout(
338
  `${base}/api/memoryline?user_id=${encodeURIComponent(user_id)}`,