SarahXia0405 commited on
Commit
f67f2d8
·
verified ·
1 Parent(s): 9cdd0ed

Update web/src/lib/api.ts

Browse files
Files changed (1) hide show
  1. web/src/lib/api.ts +62 -98
web/src/lib/api.ts CHANGED
@@ -1,4 +1,8 @@
1
  // web/src/lib/api.ts
 
 
 
 
2
 
3
  export type LearningMode =
4
  | "general"
@@ -11,39 +15,19 @@ export type LearningMode =
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() {
 
42
  const v = (import.meta as any)?.env?.VITE_API_BASE as string | undefined;
43
  return v && v.trim() ? v.trim() : "";
44
  }
45
 
46
- async function fetchWithTimeout(input: RequestInfo, init?: RequestInit, timeoutMs = DEFAULT_TIMEOUT_MS) {
 
 
 
 
47
  const controller = new AbortController();
48
  const id = setTimeout(() => controller.abort(), timeoutMs);
49
  try {
@@ -53,45 +37,6 @@ async function fetchWithTimeout(input: RequestInfo, init?: RequestInit, timeoutM
53
  }
54
  }
55
 
56
- export async function apiChat(payload: ChatRequest): Promise<ChatResponse> {
57
- const base = getBaseUrl();
58
- const res = await fetchWithTimeout(`${base}/api/chat`, {
59
- method: "POST",
60
- headers: { "Content-Type": "application/json" },
61
- body: JSON.stringify(payload),
62
- });
63
-
64
- if (!res.ok) {
65
- const txt = await res.text().catch(() => "");
66
- throw new Error(txt || `apiChat failed: ${res.status}`);
67
- }
68
-
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`, {
75
- method: "POST",
76
- headers: { "Content-Type": "application/json" },
77
- body: JSON.stringify(payload),
78
- });
79
-
80
- if (!res.ok) {
81
- const txt = await res.text().catch(() => "");
82
- throw new Error(txt || `apiQuizStart failed: ${res.status}`);
83
- }
84
-
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 {
@@ -140,7 +85,7 @@ export type ApiChatReq = {
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
 
@@ -148,15 +93,16 @@ export type ApiChatReq = {
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
 
@@ -164,7 +110,10 @@ function normalizeRefs(raw: any): ApiChatRefObj[] {
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
  }
@@ -179,18 +128,14 @@ function normalizeRefs(raw: any): ApiChatRefObj[] {
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
  {
@@ -198,7 +143,7 @@ export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
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
  }),
@@ -209,8 +154,6 @@ export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
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 {
@@ -223,12 +166,12 @@ export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
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
  };
@@ -241,28 +184,44 @@ export type ApiQuizStartResp = {
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
 
@@ -290,19 +249,21 @@ 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;
@@ -397,7 +358,10 @@ export async function apiMemoryline(
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
- }
 
1
  // web/src/lib/api.ts
2
+ // Aligns with api/server.py routes (supports both variants where relevant):
3
+ // POST /api/login, /api/chat, /api/upload, /api/export, /api/summary, /api/feedback
4
+ // POST /api/quiz_start OR /api/quiz/start (both supported via fallback)
5
+ // GET /api/memoryline
6
 
7
  export type LearningMode =
8
  | "general"
 
15
 
16
  export type LanguagePref = "Auto" | "English" | "中文";
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  const DEFAULT_TIMEOUT_MS = 20000;
19
 
20
  function getBaseUrl() {
21
+ // Vite env: VITE_API_BASE can be "", "http://localhost:8000", etc.
22
  const v = (import.meta as any)?.env?.VITE_API_BASE as string | undefined;
23
  return v && v.trim() ? v.trim() : "";
24
  }
25
 
26
+ async function fetchWithTimeout(
27
+ input: RequestInfo,
28
+ init?: RequestInit,
29
+ timeoutMs = DEFAULT_TIMEOUT_MS
30
+ ) {
31
  const controller = new AbortController();
32
  const id = setTimeout(() => controller.abort(), timeoutMs);
33
  try {
 
37
  }
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  async function parseJsonSafe(res: Response) {
41
  const text = await res.text();
42
  try {
 
85
  message: string;
86
  learning_mode: string; // backend expects string (not strict union)
87
  language_preference?: string; // "Auto" | "English" | "中文"
88
+ doc_type?: string; // "All" | "Syllabus" | "Lecture Slides / PPT" | ...
89
  course_id?: string; // e.g. "course_ist345"
90
  };
91
 
 
93
  export type ApiChatRefObj = { source_file?: string; section?: string };
94
  export type ApiChatRefRaw = ApiChatRefObj | string;
95
 
96
+ // ✅ normalize ANY ref format into {source_file, section}
97
  function normalizeRefs(raw: any): ApiChatRefObj[] {
98
  const arr: any[] = Array.isArray(raw) ? raw : [];
99
  return arr
100
  .map((x) => {
101
  // Case A: already object
102
  if (x && typeof x === "object" && !Array.isArray(x)) {
103
+ const a = x.source_file != null ? String(x.source_file).trim() : "";
104
+ const b = x.section != null ? String(x.section).trim() : "";
105
+ if (!a && !b) return null;
106
  return { source_file: a || undefined, section: b || undefined };
107
  }
108
 
 
110
  if (typeof x === "string") {
111
  const s = x.trim();
112
  if (!s) return null;
113
+ const parts = s
114
+ .split("—")
115
+ .map((p) => p.trim())
116
+ .filter(Boolean);
117
  if (parts.length >= 2) {
118
  return { source_file: parts[0], section: parts.slice(1).join(" — ") };
119
  }
 
128
  export type ApiChatResp = {
129
  reply: string;
130
  session_status_md: string;
 
 
131
  refs: ApiChatRefObj[];
 
132
  latency_ms: number;
 
 
133
  run_id?: string | null;
134
  };
135
 
136
  export async function apiChat(payload: ApiChatReq): Promise<ApiChatResp> {
137
  const base = getBaseUrl();
138
+
139
  const res = await fetchWithTimeout(
140
  `${base}/api/chat`,
141
  {
 
143
  headers: { "Content-Type": "application/json" },
144
  body: JSON.stringify({
145
  language_preference: "Auto",
146
+ doc_type: "All",
147
  course_id: "course_ist345", // ✅ default
148
  ...payload,
149
  }),
 
154
  const data = await parseJsonSafe(res);
155
  if (!res.ok) throw new Error(errMsg(data, `apiChat failed (${res.status})`));
156
 
 
 
157
  const out = data as any;
158
 
159
  return {
 
166
  }
167
 
168
  // --------------------
169
+ // /api/quiz_start (OR /api/quiz/start) - support both
170
  // --------------------
171
  export type ApiQuizStartReq = {
172
  user_id: string;
173
  language_preference?: string; // "Auto" | "English" | "中文"
174
+ doc_type?: string; // recommended "All"
175
  learning_mode?: string; // default: "quiz"
176
  course_id?: string;
177
  };
 
184
  run_id?: string | null;
185
  };
186
 
187
+ async function postQuizStart(base: string, path: string, body: any) {
188
+ return await fetchWithTimeout(
189
+ `${base}${path}`,
 
 
 
190
  {
191
  method: "POST",
192
  headers: { "Content-Type": "application/json" },
193
+ body: JSON.stringify(body),
 
 
 
 
 
 
194
  },
195
  60000
196
  );
197
+ }
198
 
199
+ export async function apiQuizStart(
200
+ payload: ApiQuizStartReq
201
+ ): Promise<ApiQuizStartResp> {
202
+ const base = getBaseUrl();
203
+
204
+ const reqBody = {
205
+ language_preference: "Auto",
206
+ doc_type: "All",
207
+ learning_mode: "quiz",
208
+ course_id: "course_ist345",
209
+ ...payload,
210
+ };
211
+
212
+ // Try /api/quiz_start first (your earlier backend pattern)
213
+ let res = await postQuizStart(base, "/api/quiz_start", reqBody);
214
+ let data = await parseJsonSafe(res);
215
+
216
+ // If 404, fallback to /api/quiz/start (your later frontend pattern)
217
+ if (!res.ok && res.status === 404) {
218
+ res = await postQuizStart(base, "/api/quiz/start", reqBody);
219
+ data = await parseJsonSafe(res);
220
+ }
221
+
222
+ if (!res.ok) {
223
+ throw new Error(errMsg(data, `apiQuizStart failed (${res.status})`));
224
+ }
225
 
226
  const out = data as any;
227
 
 
249
  user_id: string;
250
  doc_type: string;
251
  file: File;
252
+ course_id?: string;
253
  }): Promise<ApiUploadResp> {
254
  const base = getBaseUrl();
255
  const fd = new FormData();
256
  fd.append("user_id", args.user_id);
257
  fd.append("doc_type", args.doc_type);
258
  fd.append("file", args.file);
259
+ fd.append("course_id", args.course_id || "course_ist345");
260
 
261
  const res = await fetchWithTimeout(
262
  `${base}/api/upload`,
263
  { method: "POST", body: fd },
264
  120000
265
  );
266
+
267
  const data = await parseJsonSafe(res);
268
  if (!res.ok) throw new Error(errMsg(data, `apiUpload failed (${res.status})`));
269
  return data as ApiUploadResp;
 
358
  `${base}/api/memoryline?user_id=${encodeURIComponent(user_id)}`,
359
  { method: "GET" }
360
  );
361
+
362
  const data = await parseJsonSafe(res);
363
+ if (!res.ok) {
364
+ throw new Error(errMsg(data, `apiMemoryline failed (${res.status})`));
365
+ }
366
  return data as { next_review_label: string; progress_pct: number };
367
+ }