SarahXia0405 commited on
Commit
76c4923
·
verified ·
1 Parent(s): 224ad70

Update web/src/App.tsx

Browse files
Files changed (1) hide show
  1. web/src/App.tsx +183 -183
web/src/App.tsx CHANGED
@@ -1,14 +1,12 @@
1
- // web/src/App.tsx
2
  import React, { useState, useEffect } from 'react';
3
  import { Header } from './components/Header';
4
  import { LeftSidebar } from './components/LeftSidebar';
5
  import { ChatArea } from './components/ChatArea';
6
  import { RightPanel } from './components/RightPanel';
7
  import { FloatingActionButtons } from './components/FloatingActionButtons';
8
- import { X } from 'lucide-react';
9
  import { Button } from './components/ui/button';
10
  import { Toaster } from './components/ui/sonner';
11
- import { ChevronLeft, ChevronRight } from 'lucide-react';
12
  import { toast } from 'sonner';
13
 
14
  export interface Message {
@@ -22,7 +20,8 @@ export interface Message {
22
 
23
  export interface User {
24
  name: string;
25
- email: string; // we also use as user_id
 
26
  }
27
 
28
  export interface GroupMember {
@@ -40,12 +39,54 @@ export type FileType = 'syllabus' | 'lecture-slides' | 'literature-review' | 'ot
40
  export interface UploadedFile {
41
  file: File;
42
  type: FileType;
 
 
43
  }
44
 
45
  export type LearningMode = 'concept' | 'socratic' | 'exam' | 'assignment' | 'summary';
46
  export type Language = 'auto' | 'en' | 'zh';
47
 
48
- type ResultType = 'export' | 'quiz' | 'summary' | null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  function App() {
51
  const [isDarkMode, setIsDarkMode] = useState(() => {
@@ -60,16 +101,15 @@ function App() {
60
  id: '1',
61
  role: 'assistant',
62
  content:
63
- "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Upload your course materials and ask me anything.",
64
  timestamp: new Date(),
65
  },
66
  ]);
67
 
68
  const [learningMode, setLearningMode] = useState<LearningMode>('concept');
69
  const [language, setLanguage] = useState<Language>('auto');
70
-
71
  const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
72
- const [memoryProgress, setMemoryProgress] = useState(40);
73
 
74
  const [leftSidebarOpen, setLeftSidebarOpen] = useState(false);
75
  const [rightPanelOpen, setRightPanelOpen] = useState(false);
@@ -78,7 +118,7 @@ function App() {
78
  const [spaceType, setSpaceType] = useState<SpaceType>('individual');
79
 
80
  const [exportResult, setExportResult] = useState('');
81
- const [resultType, setResultType] = useState<ResultType>(null);
82
 
83
  const [groupMembers] = useState<GroupMember[]>([
84
  { id: 'clare', name: 'Clare AI', email: 'clare@ai.assistant', isAI: true },
@@ -92,72 +132,125 @@ function App() {
92
  localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
93
  }, [isDarkMode]);
94
 
95
- // -----------------------------
96
- // API helpers
97
- // -----------------------------
98
- const apiPost = async <T,>(path: string, body: any): Promise<T> => {
99
- const res = await fetch(path, {
100
- method: 'POST',
101
- headers: { 'Content-Type': 'application/json' },
102
- body: JSON.stringify(body),
103
- });
104
- if (!res.ok) {
105
- const text = await res.text().catch(() => '');
106
- throw new Error(`${path} failed: ${res.status} ${text}`);
107
- }
108
- return res.json();
109
- };
110
 
111
- const apiGet = async <T,>(path: string): Promise<T> => {
112
- const res = await fetch(path, { method: 'GET' });
113
- if (!res.ok) {
114
- const text = await res.text().catch(() => '');
115
- throw new Error(`${path} failed: ${res.status} ${text}`);
 
 
116
  }
117
- return res.json();
118
  };
119
 
120
- const fileTypeToDocType = (t: FileType): string => {
121
- // backend normalizes too, but keep consistent
122
- if (t === 'syllabus') return 'Syllabus';
123
- if (t === 'lecture-slides') return 'Lecture Slides';
124
- if (t === 'literature-review') return 'Literature Review / Paper';
125
- return 'Other';
126
  };
127
 
128
- // -----------------------------
129
- // Auth
130
- // -----------------------------
131
- const handleLogin = async (u: User) => {
132
- // use email as user_id
133
- const user_id = u.email.trim();
134
- const name = u.name.trim();
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- if (!user_id || !name) {
137
- toast.error('Missing name/email');
 
 
 
138
  return;
139
  }
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  try {
142
- await apiPost<{ ok: boolean }>('/api/login', { user_id, name });
143
- setUser({ name, email: user_id });
144
- toast.success(`Welcome, ${name}`);
145
- // sync memoryline
146
- const ml = await apiGet<{ progress_pct: number }>('/api/memoryline?user_id=' + encodeURIComponent(user_id));
147
- setMemoryProgress(Math.round((ml.progress_pct || 0) * 100));
 
 
148
  } catch (e: any) {
149
- toast.error(e.message || 'Login failed');
 
150
  }
151
  };
152
 
153
- const handleLogout = () => {
154
- setUser(null);
155
- toast.success('Logged out');
156
  };
157
 
158
- // -----------------------------
159
- // Chat
160
- // -----------------------------
161
  const handleSendMessage = async (content: string) => {
162
  if (!content.trim() || !user) return;
163
 
@@ -178,161 +271,77 @@ function App() {
178
  if (!shouldAIRespond) return;
179
 
180
  try {
181
- const resp = await apiPost<{
182
  reply: string;
183
  session_status_md: string;
184
  refs: { source_file?: string; section?: string }[];
185
  latency_ms?: number;
186
  }>('/api/chat', {
187
- user_id: user.email,
188
  message: content,
189
  learning_mode: learningMode,
190
  language_preference: language === 'auto' ? 'Auto' : language === 'en' ? 'English' : 'Chinese',
191
  doc_type: 'Syllabus',
192
  });
193
 
194
- const refs = (resp.refs || []).map((r) => {
195
- const a = r.source_file || 'source';
196
- const b = r.section || 'section';
197
- return `${a} — ${b}`;
198
- });
199
 
200
  const assistantMessage: Message = {
201
  id: (Date.now() + 1).toString(),
202
  role: 'assistant',
203
- content: resp.reply || '(empty response)',
204
  timestamp: new Date(),
205
- references: refs,
206
  sender: spaceType === 'group' ? groupMembers.find((m) => m.isAI) : undefined,
207
  };
208
 
209
  setMessages((prev) => [...prev, assistantMessage]);
210
  } catch (e: any) {
211
- toast.error(e.message || 'Chat failed');
212
- const assistantMessage: Message = {
213
- id: (Date.now() + 1).toString(),
214
- role: 'assistant',
215
- content: 'Sorry — the server returned an error. Please retry.',
216
- timestamp: new Date(),
217
- };
218
- setMessages((prev) => [...prev, assistantMessage]);
219
- }
220
- };
221
-
222
- // -----------------------------
223
- // Upload
224
- // -----------------------------
225
- const handleFileUpload = (files: File[]) => {
226
- const newFiles: UploadedFile[] = files.map((file) => ({
227
- file,
228
- type: 'other',
229
- }));
230
- setUploadedFiles((prev) => [...prev, ...newFiles]);
231
- };
232
-
233
- const handleRemoveFile = (index: number) => {
234
- setUploadedFiles((prev) => prev.filter((_, i) => i !== index));
235
- };
236
-
237
- const handleFileTypeChange = (index: number, type: FileType) => {
238
- setUploadedFiles((prev) => prev.map((f, i) => (i === index ? { ...f, type } : f)));
239
- };
240
-
241
- // Your FileUploadArea likely triggers an "Upload" confirm button.
242
- // If not, you can call this after user confirms in the modal.
243
- const uploadSingle = async (f: UploadedFile) => {
244
- if (!user) throw new Error('Not logged in');
245
-
246
- const form = new FormData();
247
- form.append('user_id', user.email);
248
- form.append('doc_type', fileTypeToDocType(f.type));
249
- form.append('file', f.file);
250
-
251
- const res = await fetch('/api/upload', { method: 'POST', body: form });
252
- if (!res.ok) {
253
- const text = await res.text().catch(() => '');
254
- throw new Error(`/api/upload failed: ${res.status} ${text}`);
255
- }
256
- return res.json() as Promise<{ ok: boolean; added_chunks: number; status_md: string }>;
257
- };
258
-
259
- // If your UI already has an "Upload" button in the modal,
260
- // wire it to this function via FileUploadArea; otherwise keep as-is.
261
- const handleUploadAllPending = async () => {
262
- if (!user) {
263
- toast.error('Please log in first');
264
- return;
265
- }
266
- try {
267
- for (const f of uploadedFiles) {
268
- // naive: upload all; you can also only upload newly added ones
269
- const out = await uploadSingle(f);
270
- toast.success(`${f.file.name} uploaded (+${out.added_chunks} chunks)`);
271
- }
272
- const ml = await apiGet<{ progress_pct: number }>('/api/memoryline?user_id=' + encodeURIComponent(user.email));
273
- setMemoryProgress(Math.round((ml.progress_pct || 0) * 100));
274
- } catch (e: any) {
275
- toast.error(e.message || 'Upload failed');
276
  }
277
  };
278
 
279
- // -----------------------------
280
- // Export / Summary / Quiz
281
- // -----------------------------
282
  const handleClearConversation = () => {
283
  setMessages([
284
  {
285
  id: '1',
286
  role: 'assistant',
287
  content:
288
- "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Upload your course materials and ask me anything.",
289
  timestamp: new Date(),
290
  },
291
  ]);
292
  };
293
 
294
- const handleExport = async () => {
295
- if (!user) return toast.error('Please log in');
296
- try {
297
- const out = await apiPost<{ markdown: string }>('/api/export', {
298
- user_id: user.email,
299
- learning_mode: learningMode,
300
- });
301
- setExportResult(out.markdown || '');
302
- setResultType('export');
303
- toast.success('Export generated');
304
- } catch (e: any) {
305
- toast.error(e.message || 'Export failed');
306
- }
307
- };
308
 
309
- const handleSummary = async () => {
310
- if (!user) return toast.error('Please log in');
311
- try {
312
- const out = await apiPost<{ markdown: string }>('/api/summary', {
313
- user_id: user.email,
314
- learning_mode: learningMode,
315
- language_preference: language === 'auto' ? 'Auto' : language === 'en' ? 'English' : 'Chinese',
316
- });
317
- setExportResult(out.markdown || '');
318
- setResultType('summary');
319
- toast.success('Summary generated');
320
- } catch (e: any) {
321
- toast.error(e.message || 'Summary failed');
322
- }
323
  };
324
 
325
  const handleQuiz = () => {
326
- // No backend endpoint yet; keep mock for now
327
- const quiz = `# Micro-Quiz (MVP)
328
-
329
- 1) What is one core principle of Responsible AI?
330
- 2) Give one example of fairness risk.
331
- 3) Explain transparency in one sentence.
332
- `;
333
  setExportResult(quiz);
334
  setResultType('quiz');
335
- toast.success('Quiz generated (mock)');
 
 
 
 
 
 
 
 
336
  };
337
 
338
  return (
@@ -394,13 +403,6 @@ function App() {
394
  onLearningModeChange={setLearningMode}
395
  spaceType={spaceType}
396
  />
397
-
398
- {/* Optional: if you want a manual "Upload All" button for MVP */}
399
- {/* <div className="p-3 border-t">
400
- <Button onClick={handleUploadAllPending} disabled={!user || uploadedFiles.length === 0}>
401
- Upload all pending files
402
- </Button>
403
- </div> */}
404
  </main>
405
 
406
  {rightPanelOpen && (
@@ -420,11 +422,12 @@ function App() {
420
  `}
421
  >
422
  <div className="lg:hidden p-4 border-b border-border flex justify-between items-center">
423
- <h3>Account & Actions</h3>
424
  <Button variant="ghost" size="icon" onClick={() => setRightPanelOpen(false)}>
425
  <X className="h-5 w-5" />
426
  </Button>
427
  </div>
 
428
  <RightPanel
429
  user={user}
430
  onLogin={handleLogin}
@@ -435,9 +438,6 @@ function App() {
435
  setExportResult={setExportResult}
436
  resultType={resultType}
437
  setResultType={setResultType}
438
- onExport={handleExport}
439
- onQuiz={handleQuiz}
440
- onSummary={handleSummary}
441
  />
442
  </aside>
443
  )}
 
 
1
  import React, { useState, useEffect } from 'react';
2
  import { Header } from './components/Header';
3
  import { LeftSidebar } from './components/LeftSidebar';
4
  import { ChatArea } from './components/ChatArea';
5
  import { RightPanel } from './components/RightPanel';
6
  import { FloatingActionButtons } from './components/FloatingActionButtons';
7
+ import { X, ChevronLeft, ChevronRight } from 'lucide-react';
8
  import { Button } from './components/ui/button';
9
  import { Toaster } from './components/ui/sonner';
 
10
  import { toast } from 'sonner';
11
 
12
  export interface Message {
 
20
 
21
  export interface User {
22
  name: string;
23
+ email: string;
24
+ user_id: string; // IMPORTANT: server session key
25
  }
26
 
27
  export interface GroupMember {
 
39
  export interface UploadedFile {
40
  file: File;
41
  type: FileType;
42
+ uploaded?: boolean;
43
+ uploadedChunks?: number;
44
  }
45
 
46
  export type LearningMode = 'concept' | 'socratic' | 'exam' | 'assignment' | 'summary';
47
  export type Language = 'auto' | 'en' | 'zh';
48
 
49
+ // ----------------------------
50
+ // API helpers (same-origin)
51
+ // ----------------------------
52
+ async function apiPostJson<T>(path: string, body: unknown): Promise<T> {
53
+ const res = await fetch(path, {
54
+ method: 'POST',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify(body),
57
+ });
58
+ if (!res.ok) {
59
+ const txt = await res.text();
60
+ throw new Error(`POST ${path} failed: ${res.status} ${txt}`);
61
+ }
62
+ return res.json();
63
+ }
64
+
65
+ async function apiPostForm<T>(path: string, formData: FormData): Promise<T> {
66
+ const res = await fetch(path, {
67
+ method: 'POST',
68
+ body: formData,
69
+ });
70
+ if (!res.ok) {
71
+ const txt = await res.text();
72
+ throw new Error(`POST ${path} failed: ${res.status} ${txt}`);
73
+ }
74
+ return res.json();
75
+ }
76
+
77
+ // FE FileType -> BE doc_type mapping
78
+ function mapFileTypeToDocType(t: FileType): string {
79
+ switch (t) {
80
+ case 'syllabus':
81
+ return 'Syllabus';
82
+ case 'lecture-slides':
83
+ return 'Lecture Slides';
84
+ case 'literature-review':
85
+ return 'Literature Review / Paper';
86
+ default:
87
+ return 'Other';
88
+ }
89
+ }
90
 
91
  function App() {
92
  const [isDarkMode, setIsDarkMode] = useState(() => {
 
101
  id: '1',
102
  role: 'assistant',
103
  content:
104
+ "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Upload your syllabus/notes and ask me anything.",
105
  timestamp: new Date(),
106
  },
107
  ]);
108
 
109
  const [learningMode, setLearningMode] = useState<LearningMode>('concept');
110
  const [language, setLanguage] = useState<Language>('auto');
 
111
  const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
112
+ const [memoryProgress, setMemoryProgress] = useState(36);
113
 
114
  const [leftSidebarOpen, setLeftSidebarOpen] = useState(false);
115
  const [rightPanelOpen, setRightPanelOpen] = useState(false);
 
118
  const [spaceType, setSpaceType] = useState<SpaceType>('individual');
119
 
120
  const [exportResult, setExportResult] = useState('');
121
+ const [resultType, setResultType] = useState<'export' | 'quiz' | 'summary' | null>(null);
122
 
123
  const [groupMembers] = useState<GroupMember[]>([
124
  { id: 'clare', name: 'Clare AI', email: 'clare@ai.assistant', isAI: true },
 
132
  localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
133
  }, [isDarkMode]);
134
 
135
+ // ----------------------------
136
+ // Login: call backend /api/login
137
+ // ----------------------------
138
+ const handleLogin = async (name: string, email: string) => {
139
+ // Use email as stable user_id for MVP
140
+ const user_id = email.trim().toLowerCase();
141
+
142
+ try {
143
+ const resp = await apiPostJson<{ ok: boolean; user: { name: string; user_id: string } }>(
144
+ '/api/login',
145
+ { name, user_id }
146
+ );
 
 
 
147
 
148
+ if (!resp.ok) throw new Error('login failed');
149
+
150
+ setUser({ name: resp.user.name, email, user_id: resp.user.user_id });
151
+ toast.success(`Welcome, ${resp.user.name}!`);
152
+ } catch (e: any) {
153
+ console.error(e);
154
+ toast.error(`Login failed: ${e?.message || 'unknown error'}`);
155
  }
 
156
  };
157
 
158
+ const handleLogout = () => {
159
+ setUser(null);
160
+ toast.success('Logged out successfully');
 
 
 
161
  };
162
 
163
+ // ----------------------------
164
+ // Upload: call backend /api/upload
165
+ // ----------------------------
166
+ const uploadOneFile = async (f: UploadedFile) => {
167
+ if (!user) throw new Error('not logged in');
168
+
169
+ const form = new FormData();
170
+ form.append('user_id', user.user_id);
171
+ form.append('doc_type', mapFileTypeToDocType(f.type));
172
+ form.append('file', f.file);
173
+
174
+ const resp = await apiPostForm<{ ok: boolean; added_chunks: number; status_md: string }>(
175
+ '/api/upload',
176
+ form
177
+ );
178
+
179
+ return resp;
180
+ };
181
 
182
+ // When FileUploadArea fires onFileUpload(files), we add them + immediately upload them (type default other).
183
+ // If you want strict "must choose type before upload", then your FileUploadArea should call onFileTypeChange first.
184
+ const handleFileUpload = async (files: File[]) => {
185
+ if (!user) {
186
+ toast.error('Please log in first');
187
  return;
188
  }
189
 
190
+ const newFiles: UploadedFile[] = files.map((file) => ({
191
+ file,
192
+ type: 'other',
193
+ uploaded: false,
194
+ }));
195
+
196
+ // Optimistic add
197
+ setUploadedFiles((prev) => [...prev, ...newFiles]);
198
+
199
+ // Upload sequentially to reduce memory spikes
200
+ for (const uf of newFiles) {
201
+ try {
202
+ toast.message(`Uploading: ${uf.file.name}`);
203
+ const r = await uploadOneFile(uf);
204
+
205
+ setUploadedFiles((prev) =>
206
+ prev.map((x) =>
207
+ x.file === uf.file
208
+ ? { ...x, uploaded: true, uploadedChunks: r.added_chunks }
209
+ : x
210
+ )
211
+ );
212
+ toast.success(`Uploaded ${uf.file.name} (+${r.added_chunks} chunks)`);
213
+ } catch (e: any) {
214
+ console.error(e);
215
+ toast.error(`Upload failed: ${uf.file.name} — ${e?.message || 'unknown error'}`);
216
+ }
217
+ }
218
+ };
219
+
220
+ // If user changes file type AFTER selecting, re-upload with correct doc_type (important for syllabus)
221
+ const handleFileTypeChange = async (index: number, type: FileType) => {
222
+ setUploadedFiles((prev) =>
223
+ prev.map((file, i) => (i === index ? { ...file, type } : file))
224
+ );
225
+
226
+ if (!user) return;
227
+
228
+ // Re-upload this file with the new type (this is what fixes: "doc_type=Other" bug)
229
+ const target = uploadedFiles[index];
230
+ if (!target) return;
231
+
232
  try {
233
+ toast.message(`Re-uploading as ${type}: ${target.file.name}`);
234
+ const r = await uploadOneFile({ ...target, type });
235
+ setUploadedFiles((prev) =>
236
+ prev.map((x, i) =>
237
+ i === index ? { ...x, uploaded: true, uploadedChunks: r.added_chunks } : x
238
+ )
239
+ );
240
+ toast.success(`Updated type and uploaded (+${r.added_chunks} chunks)`);
241
  } catch (e: any) {
242
+ console.error(e);
243
+ toast.error(`Re-upload failed: ${e?.message || 'unknown error'}`);
244
  }
245
  };
246
 
247
+ const handleRemoveFile = (index: number) => {
248
+ setUploadedFiles((prev) => prev.filter((_, i) => i !== index));
 
249
  };
250
 
251
+ // ----------------------------
252
+ // Chat: call backend /api/chat
253
+ // ----------------------------
254
  const handleSendMessage = async (content: string) => {
255
  if (!content.trim() || !user) return;
256
 
 
271
  if (!shouldAIRespond) return;
272
 
273
  try {
274
+ const resp = await apiPostJson<{
275
  reply: string;
276
  session_status_md: string;
277
  refs: { source_file?: string; section?: string }[];
278
  latency_ms?: number;
279
  }>('/api/chat', {
280
+ user_id: user.user_id,
281
  message: content,
282
  learning_mode: learningMode,
283
  language_preference: language === 'auto' ? 'Auto' : language === 'en' ? 'English' : 'Chinese',
284
  doc_type: 'Syllabus',
285
  });
286
 
287
+ const refs = (resp.refs || [])
288
+ .map((r) => [r.source_file, r.section].filter(Boolean).join(' '))
289
+ .filter(Boolean);
 
 
290
 
291
  const assistantMessage: Message = {
292
  id: (Date.now() + 1).toString(),
293
  role: 'assistant',
294
+ content: resp.reply || '(empty reply)',
295
  timestamp: new Date(),
296
+ references: refs.length ? refs : undefined,
297
  sender: spaceType === 'group' ? groupMembers.find((m) => m.isAI) : undefined,
298
  };
299
 
300
  setMessages((prev) => [...prev, assistantMessage]);
301
  } catch (e: any) {
302
+ console.error(e);
303
+ toast.error(`Chat failed: ${e?.message || 'unknown error'}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  }
305
  };
306
 
 
 
 
307
  const handleClearConversation = () => {
308
  setMessages([
309
  {
310
  id: '1',
311
  role: 'assistant',
312
  content:
313
+ "Hi! I'm Clare, your AI teaching assistant for Module 10 – Responsible AI. Upload your syllabus/notes and ask me anything.",
314
  timestamp: new Date(),
315
  },
316
  ]);
317
  };
318
 
319
+ // Your existing mock export/quiz/summary can stay. (Optional: wire them to /api/export /api/summary later.)
320
+ const handleExport = () => {
321
+ const result = `# Conversation Export
322
+ Date: ${new Date().toLocaleDateString()}
323
+ Student: ${user?.name || ''}
 
 
 
 
 
 
 
 
 
324
 
325
+ (Placeholder export; wire to /api/export if needed.)`;
326
+ setExportResult(result);
327
+ setResultType('export');
328
+ toast.success('Conversation exported!');
 
 
 
 
 
 
 
 
 
 
329
  };
330
 
331
  const handleQuiz = () => {
332
+ const quiz = `# Micro-Quiz (Placeholder)
333
+ (You can wire to backend later.)`;
 
 
 
 
 
334
  setExportResult(quiz);
335
  setResultType('quiz');
336
+ toast.success('Quiz generated!');
337
+ };
338
+
339
+ const handleSummary = () => {
340
+ const summary = `# Summary (Placeholder)
341
+ (You can wire to backend /api/summary later.)`;
342
+ setExportResult(summary);
343
+ setResultType('summary');
344
+ toast.success('Summary generated!');
345
  };
346
 
347
  return (
 
403
  onLearningModeChange={setLearningMode}
404
  spaceType={spaceType}
405
  />
 
 
 
 
 
 
 
406
  </main>
407
 
408
  {rightPanelOpen && (
 
422
  `}
423
  >
424
  <div className="lg:hidden p-4 border-b border-border flex justify-between items-center">
425
+ <h3>Account</h3>
426
  <Button variant="ghost" size="icon" onClick={() => setRightPanelOpen(false)}>
427
  <X className="h-5 w-5" />
428
  </Button>
429
  </div>
430
+
431
  <RightPanel
432
  user={user}
433
  onLogin={handleLogin}
 
438
  setExportResult={setExportResult}
439
  resultType={resultType}
440
  setResultType={setResultType}
 
 
 
441
  />
442
  </aside>
443
  )}