SarahXia0405 commited on
Commit
a1486d6
·
verified ·
1 Parent(s): 3cdbc9d

Update web/src/App.tsx

Browse files
Files changed (1) hide show
  1. web/src/App.tsx +97 -25
web/src/App.tsx CHANGED
@@ -15,12 +15,13 @@ export interface Message {
15
  content: string;
16
  timestamp: Date;
17
  references?: string[];
18
- sender?: GroupMember; // For group chat
19
  }
20
 
21
  export interface User {
22
  name: string;
23
- email: string;
 
24
  }
25
 
26
  export interface GroupMember {
@@ -32,7 +33,6 @@ export interface GroupMember {
32
  }
33
 
34
  export type SpaceType = 'individual' | 'group';
35
-
36
  export type FileType = 'syllabus' | 'lecture-slides' | 'literature-review' | 'other';
37
 
38
  export interface UploadedFile {
@@ -58,6 +58,11 @@ type UploadApiResp = {
58
  status_md: string;
59
  };
60
 
 
 
 
 
 
61
  function mapFileTypeToDocType(t: FileType): string {
62
  switch (t) {
63
  case 'syllabus':
@@ -106,7 +111,7 @@ function App() {
106
  id: '1',
107
  role: 'assistant',
108
  content:
109
- "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Upload your materials (e.g., syllabus/lecture slides) and ask me anything.",
110
  timestamp: new Date(),
111
  },
112
  ]);
@@ -138,16 +143,11 @@ function App() {
138
  localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
139
  }, [isDarkMode]);
140
 
141
- const userId = useMemo(() => {
142
- // 登录时:email 作为 user_id;不登录也可跑通:0405
143
- return (user?.email || '0405').trim();
144
- }, [user]);
145
 
146
- const isLoggedIn = useMemo(() => {
147
- // 你如果希望“必须登录才能聊天/上传”,这里改回 !!user
148
- // 目前为了便于调试后端,允许未登录也走通(user_id=0405)
149
- return true;
150
- }, []);
151
 
152
  const currentDocTypeForChat = useMemo(() => {
153
  const hasSyllabus = uploadedFiles.some((f) => f.type === 'syllabus' && f.uploaded);
@@ -159,14 +159,56 @@ function App() {
159
  return 'Other';
160
  }, [uploadedFiles]);
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  const handleSendMessage = async (content: string) => {
163
  if (!content.trim()) return;
164
 
 
 
 
 
 
165
  const shouldAIRespond = spaceType === 'individual' || content.toLowerCase().includes('@clare');
166
 
167
  const sender: GroupMember | undefined =
168
  spaceType === 'group' && user
169
- ? { id: user.email, name: user.name, email: user.email }
170
  : undefined;
171
 
172
  const userMessage: Message = {
@@ -220,6 +262,10 @@ function App() {
220
 
221
  // ✅ 选文件:只入库,不上传
222
  const handleFileUpload = (files: File[]) => {
 
 
 
 
223
  const newFiles: UploadedFile[] = files.map((file) => ({
224
  file,
225
  type: 'other',
@@ -237,8 +283,13 @@ function App() {
237
  setUploadedFiles((prev) => prev.map((f, i) => (i === index ? { ...f, type } : f)));
238
  };
239
 
240
- // ✅ 上传单个文件(关键:读取“最新的 type”,写入 doc_type)
241
  const handleUploadSingle = async (index: number) => {
 
 
 
 
 
242
  const target = uploadedFiles[index];
243
  if (!target) return;
244
 
@@ -264,8 +315,12 @@ function App() {
264
  }
265
  };
266
 
267
- // ✅ 上传所有未上传
268
  const handleUploadAllPending = async () => {
 
 
 
 
 
269
  const pendingIdx = uploadedFiles
270
  .map((f, i) => ({ f, i }))
271
  .filter((x) => !x.f.uploaded)
@@ -277,7 +332,6 @@ function App() {
277
  }
278
 
279
  for (const idx of pendingIdx) {
280
- // 顺序上传便于 debug
281
  // eslint-disable-next-line no-await-in-loop
282
  await handleUploadSingle(idx);
283
  }
@@ -289,13 +343,17 @@ function App() {
289
  id: '1',
290
  role: 'assistant',
291
  content:
292
- "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Upload your materials (e.g., syllabus/lecture slides) and ask me anything.",
293
  timestamp: new Date(),
294
  },
295
  ]);
296
  };
297
 
298
  const handleExport = async () => {
 
 
 
 
299
  try {
300
  const r = await apiPostJson<{ markdown: string }>('/api/export', {
301
  user_id: userId,
@@ -310,6 +368,10 @@ function App() {
310
  };
311
 
312
  const handleSummary = async () => {
 
 
 
 
313
  try {
314
  const r = await apiPostJson<{ markdown: string }>('/api/summary', {
315
  user_id: userId,
@@ -339,6 +401,8 @@ D) Cost reduction
339
  };
340
 
341
  useEffect(() => {
 
 
342
  const run = async () => {
343
  try {
344
  const res = await fetch(`/api/memoryline?user_id=${encodeURIComponent(userId)}`);
@@ -351,7 +415,7 @@ D) Cost reduction
351
  }
352
  };
353
  run();
354
- }, [userId]);
355
 
356
  return (
357
  <div className="min-h-screen bg-background flex flex-col">
@@ -366,7 +430,10 @@ D) Cost reduction
366
 
367
  <div className="flex-1 flex overflow-hidden">
368
  {leftSidebarOpen && (
369
- <div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setLeftSidebarOpen(false)} />
 
 
 
370
  )}
371
 
372
  <aside
@@ -401,6 +468,7 @@ D) Cost reduction
401
  <main className="flex-1 flex flex-col min-w-0">
402
  <ChatArea
403
  userId={userId}
 
404
  messages={messages}
405
  onSendMessage={handleSendMessage}
406
  uploadedFiles={uploadedFiles}
@@ -412,6 +480,7 @@ D) Cost reduction
412
  memoryProgress={memoryProgress}
413
  isLoggedIn={isLoggedIn}
414
  learningMode={learningMode}
 
415
  onClearConversation={handleClearConversation}
416
  onLearningModeChange={setLearningMode}
417
  spaceType={spaceType}
@@ -419,7 +488,10 @@ D) Cost reduction
419
  </main>
420
 
421
  {rightPanelOpen && (
422
- <div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setRightPanelOpen(false)} />
 
 
 
423
  )}
424
 
425
  {rightPanelVisible && (
@@ -443,9 +515,9 @@ D) Cost reduction
443
 
444
  <RightPanel
445
  user={user}
446
- onLogin={setUser}
447
- onLogout={() => setUser(null)}
448
- isLoggedIn={!!user}
449
  onClose={() => setRightPanelVisible(false)}
450
  exportResult={exportResult}
451
  setExportResult={setExportResult}
@@ -470,7 +542,7 @@ D) Cost reduction
470
  {!rightPanelVisible && (
471
  <FloatingActionButtons
472
  user={user}
473
- isLoggedIn={!!user}
474
  onOpenPanel={() => setRightPanelVisible(true)}
475
  onExport={handleExport}
476
  onQuiz={handleQuiz}
 
15
  content: string;
16
  timestamp: Date;
17
  references?: string[];
18
+ sender?: GroupMember;
19
  }
20
 
21
  export interface User {
22
  name: string;
23
+ email: string; // login 输入
24
+ user_id: string; // 实际传后端的 user_id(这里等同 email/ID)
25
  }
26
 
27
  export interface GroupMember {
 
33
  }
34
 
35
  export type SpaceType = 'individual' | 'group';
 
36
  export type FileType = 'syllabus' | 'lecture-slides' | 'literature-review' | 'other';
37
 
38
  export interface UploadedFile {
 
58
  status_md: string;
59
  };
60
 
61
+ type LoginApiResp = {
62
+ ok: boolean;
63
+ user: { name: string; user_id: string };
64
+ };
65
+
66
  function mapFileTypeToDocType(t: FileType): string {
67
  switch (t) {
68
  case 'syllabus':
 
111
  id: '1',
112
  role: 'assistant',
113
  content:
114
+ "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Please log in on the right, upload materials (optional), and ask me anything.",
115
  timestamp: new Date(),
116
  },
117
  ]);
 
143
  localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
144
  }, [isDarkMode]);
145
 
146
+ // 彻底去掉 hardcode:未登录 userId 为空
147
+ const userId = useMemo(() => (user?.user_id || '').trim(), [user]);
 
 
148
 
149
+ // 未登录不可聊天/上传/feedback
150
+ const isLoggedIn = useMemo(() => !!user && !!userId, [user, userId]);
 
 
 
151
 
152
  const currentDocTypeForChat = useMemo(() => {
153
  const hasSyllabus = uploadedFiles.some((f) => f.type === 'syllabus' && f.uploaded);
 
159
  return 'Other';
160
  }, [uploadedFiles]);
161
 
162
+ // ✅ 登录必须打后端 /api/login,确保 server session 有 name
163
+ const handleLogin = async (name: string, emailOrId: string) => {
164
+ const nameTrim = name.trim();
165
+ const idTrim = emailOrId.trim();
166
+
167
+ if (!nameTrim || !idTrim) {
168
+ toast.error('Please fill in both name and Email/ID');
169
+ return;
170
+ }
171
+
172
+ try {
173
+ const resp = await apiPostJson<LoginApiResp>('/api/login', {
174
+ name: nameTrim,
175
+ user_id: idTrim,
176
+ });
177
+
178
+ if (!resp?.ok) throw new Error('Login failed (ok=false)');
179
+
180
+ // 后端回来的 user_id 为准(保持一致)
181
+ setUser({
182
+ name: resp.user.name,
183
+ email: idTrim,
184
+ user_id: resp.user.user_id,
185
+ });
186
+
187
+ toast.success('Logged in');
188
+ } catch (e: any) {
189
+ console.error(e);
190
+ toast.error(`Login failed: ${e?.message || 'unknown error'}`);
191
+ }
192
+ };
193
+
194
+ const handleLogout = () => {
195
+ setUser(null);
196
+ toast.message('Logged out');
197
+ };
198
+
199
  const handleSendMessage = async (content: string) => {
200
  if (!content.trim()) return;
201
 
202
+ if (!isLoggedIn) {
203
+ toast.error('Please log in first');
204
+ return;
205
+ }
206
+
207
  const shouldAIRespond = spaceType === 'individual' || content.toLowerCase().includes('@clare');
208
 
209
  const sender: GroupMember | undefined =
210
  spaceType === 'group' && user
211
+ ? { id: user.user_id, name: user.name, email: user.email }
212
  : undefined;
213
 
214
  const userMessage: Message = {
 
262
 
263
  // ✅ 选文件:只入库,不上传
264
  const handleFileUpload = (files: File[]) => {
265
+ if (!isLoggedIn) {
266
+ toast.error('Please log in first');
267
+ return;
268
+ }
269
  const newFiles: UploadedFile[] = files.map((file) => ({
270
  file,
271
  type: 'other',
 
283
  setUploadedFiles((prev) => prev.map((f, i) => (i === index ? { ...f, type } : f)));
284
  };
285
 
286
+ // ✅ 上传单个文件
287
  const handleUploadSingle = async (index: number) => {
288
+ if (!isLoggedIn) {
289
+ toast.error('Please log in first');
290
+ return;
291
+ }
292
+
293
  const target = uploadedFiles[index];
294
  if (!target) return;
295
 
 
315
  }
316
  };
317
 
 
318
  const handleUploadAllPending = async () => {
319
+ if (!isLoggedIn) {
320
+ toast.error('Please log in first');
321
+ return;
322
+ }
323
+
324
  const pendingIdx = uploadedFiles
325
  .map((f, i) => ({ f, i }))
326
  .filter((x) => !x.f.uploaded)
 
332
  }
333
 
334
  for (const idx of pendingIdx) {
 
335
  // eslint-disable-next-line no-await-in-loop
336
  await handleUploadSingle(idx);
337
  }
 
343
  id: '1',
344
  role: 'assistant',
345
  content:
346
+ "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Please log in on the right, upload materials (optional), and ask me anything.",
347
  timestamp: new Date(),
348
  },
349
  ]);
350
  };
351
 
352
  const handleExport = async () => {
353
+ if (!isLoggedIn) {
354
+ toast.error('Please log in first');
355
+ return;
356
+ }
357
  try {
358
  const r = await apiPostJson<{ markdown: string }>('/api/export', {
359
  user_id: userId,
 
368
  };
369
 
370
  const handleSummary = async () => {
371
+ if (!isLoggedIn) {
372
+ toast.error('Please log in first');
373
+ return;
374
+ }
375
  try {
376
  const r = await apiPostJson<{ markdown: string }>('/api/summary', {
377
  user_id: userId,
 
401
  };
402
 
403
  useEffect(() => {
404
+ if (!isLoggedIn) return;
405
+
406
  const run = async () => {
407
  try {
408
  const res = await fetch(`/api/memoryline?user_id=${encodeURIComponent(userId)}`);
 
415
  }
416
  };
417
  run();
418
+ }, [isLoggedIn, userId]);
419
 
420
  return (
421
  <div className="min-h-screen bg-background flex flex-col">
 
430
 
431
  <div className="flex-1 flex overflow-hidden">
432
  {leftSidebarOpen && (
433
+ <div
434
+ className="fixed inset-0 bg-black/50 z-40 lg:hidden"
435
+ onClick={() => setLeftSidebarOpen(false)}
436
+ />
437
  )}
438
 
439
  <aside
 
468
  <main className="flex-1 flex flex-col min-w-0">
469
  <ChatArea
470
  userId={userId}
471
+ user={user} // ✅ 让 ChatArea/Message 能拿到 name/id 做 feedback
472
  messages={messages}
473
  onSendMessage={handleSendMessage}
474
  uploadedFiles={uploadedFiles}
 
480
  memoryProgress={memoryProgress}
481
  isLoggedIn={isLoggedIn}
482
  learningMode={learningMode}
483
+ currentDocTypeForChat={currentDocTypeForChat} // ✅ 反馈也需要
484
  onClearConversation={handleClearConversation}
485
  onLearningModeChange={setLearningMode}
486
  spaceType={spaceType}
 
488
  </main>
489
 
490
  {rightPanelOpen && (
491
+ <div
492
+ className="fixed inset-0 bg-black/50 z-40 lg:hidden"
493
+ onClick={() => setRightPanelOpen(false)}
494
+ />
495
  )}
496
 
497
  {rightPanelVisible && (
 
515
 
516
  <RightPanel
517
  user={user}
518
+ onLogin={handleLogin}
519
+ onLogout={handleLogout}
520
+ isLoggedIn={isLoggedIn}
521
  onClose={() => setRightPanelVisible(false)}
522
  exportResult={exportResult}
523
  setExportResult={setExportResult}
 
542
  {!rightPanelVisible && (
543
  <FloatingActionButtons
544
  user={user}
545
+ isLoggedIn={isLoggedIn}
546
  onOpenPanel={() => setRightPanelVisible(true)}
547
  onExport={handleExport}
548
  onQuiz={handleQuiz}