SarahXia0405 commited on
Commit
8c6e3e1
·
verified ·
1 Parent(s): 8c006a1

Update web/src/components/ChatArea.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ChatArea.tsx +20 -74
web/src/components/ChatArea.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import React, { useState, useRef, useEffect } from 'react';
2
  import { Button } from './ui/button';
3
  import { Textarea } from './ui/textarea';
@@ -7,16 +8,11 @@ import { FileUploadArea } from './FileUploadArea';
7
  import { MemoryLine } from './MemoryLine';
8
  import type { Message as MessageType, LearningMode, UploadedFile, FileType, SpaceType } from '../App';
9
  import { toast } from 'sonner';
10
- import {
11
- DropdownMenu,
12
- DropdownMenuContent,
13
- DropdownMenuItem,
14
- DropdownMenuTrigger,
15
- } from './ui/dropdown-menu';
16
 
17
  interface ChatAreaProps {
18
  messages: MessageType[];
19
- onSendMessage: (content: string) => Promise<void> | void;
20
  uploadedFiles: UploadedFile[];
21
  onFileUpload: (files: File[]) => void;
22
  onRemoveFile: (index: number) => void;
@@ -27,7 +23,6 @@ interface ChatAreaProps {
27
  onClearConversation: () => void;
28
  onLearningModeChange: (mode: LearningMode) => void;
29
  spaceType: SpaceType;
30
- isTyping: boolean;
31
  }
32
 
33
  export function ChatArea({
@@ -43,9 +38,9 @@ export function ChatArea({
43
  onClearConversation,
44
  onLearningModeChange,
45
  spaceType,
46
- isTyping,
47
  }: ChatAreaProps) {
48
  const [input, setInput] = useState('');
 
49
  const [showScrollButton, setShowScrollButton] = useState(false);
50
  const messagesEndRef = useRef<HTMLDivElement>(null);
51
  const scrollContainerRef = useRef<HTMLDivElement>(null);
@@ -56,7 +51,7 @@ export function ChatArea({
56
 
57
  useEffect(() => {
58
  scrollToBottom();
59
- }, [messages, isTyping]);
60
 
61
  useEffect(() => {
62
  const handleScroll = () => {
@@ -71,19 +66,20 @@ export function ChatArea({
71
  return () => container?.removeEventListener('scroll', handleScroll);
72
  }, []);
73
 
74
- const handleSubmit = async (e: React.FormEvent) => {
75
  e.preventDefault();
76
  if (!input.trim() || !isLoggedIn) return;
77
 
78
- const text = input;
79
  setInput('');
80
- await onSendMessage(text);
 
81
  };
82
 
83
  const handleKeyDown = (e: React.KeyboardEvent) => {
84
  if (e.key === 'Enter' && !e.shiftKey) {
85
  e.preventDefault();
86
- void handleSubmit(e as any);
87
  }
88
  };
89
 
@@ -100,9 +96,9 @@ export function ChatArea({
100
  toast.info('No conversation to clear');
101
  return;
102
  }
103
-
104
  if (window.confirm('Are you sure you want to clear the conversation? This cannot be undone.')) {
105
  onClearConversation();
 
106
  }
107
  };
108
 
@@ -111,7 +107,6 @@ export function ChatArea({
111
  toast.info('No conversation to share');
112
  return;
113
  }
114
-
115
  const conversationText = messages
116
  .map((msg) => `${msg.role === 'user' ? 'You' : 'Clare'}: ${msg.content}`)
117
  .join('\n\n');
@@ -124,9 +119,7 @@ export function ChatArea({
124
 
125
  return (
126
  <div className="flex flex-col h-full">
127
- {/* Chat Area with Floating Input */}
128
  <div className="flex-1 relative border-b-2 border-border">
129
- {/* Action Buttons - Fixed at top right */}
130
  {messages.length > 1 && (
131
  <div className="absolute top-4 right-12 z-10 flex gap-2">
132
  <Button
@@ -152,7 +145,6 @@ export function ChatArea({
152
  </div>
153
  )}
154
 
155
- {/* Messages Area */}
156
  <div ref={scrollContainerRef} className="h-full max-h-[600px] overflow-y-auto px-4 py-6 pb-36">
157
  <div className="max-w-4xl mx-auto space-y-6">
158
  {messages.map((message) => (
@@ -178,7 +170,6 @@ export function ChatArea({
178
  </div>
179
  </div>
180
 
181
- {/* Scroll to Bottom Button - Floating above input */}
182
  {showScrollButton && (
183
  <div className="absolute bottom-24 left-1/2 -translate-x-1/2 z-20">
184
  <Button
@@ -192,12 +183,10 @@ export function ChatArea({
192
  </div>
193
  )}
194
 
195
- {/* Floating Input Area */}
196
  <div className="absolute bottom-0 left-0 right-0 bg-background/95 backdrop-blur-sm z-10">
197
  <div className="max-w-4xl mx-auto px-4 py-4">
198
  <form onSubmit={handleSubmit}>
199
  <div className="relative">
200
- {/* Mode Selector */}
201
  <DropdownMenu>
202
  <DropdownMenuTrigger asChild>
203
  <Button
@@ -213,57 +202,16 @@ export function ChatArea({
213
  </svg>
214
  </Button>
215
  </DropdownMenuTrigger>
216
-
217
  <DropdownMenuContent align="start" className="w-56">
218
- <DropdownMenuItem
219
- onClick={() => onLearningModeChange('concept')}
220
- className={learningMode === 'concept' ? 'bg-accent' : ''}
221
- >
222
- <div className="flex flex-col">
223
- <span className="font-medium">Concept Explainer</span>
224
- <span className="text-xs text-muted-foreground">Get detailed explanations of concepts</span>
225
- </div>
226
- </DropdownMenuItem>
227
-
228
- <DropdownMenuItem
229
- onClick={() => onLearningModeChange('socratic')}
230
- className={learningMode === 'socratic' ? 'bg-accent' : ''}
231
- >
232
- <div className="flex flex-col">
233
- <span className="font-medium">Socratic Tutor</span>
234
- <span className="text-xs text-muted-foreground">Learn through guided questions</span>
235
- </div>
236
- </DropdownMenuItem>
237
-
238
- <DropdownMenuItem
239
- onClick={() => onLearningModeChange('exam')}
240
- className={learningMode === 'exam' ? 'bg-accent' : ''}
241
- >
242
- <div className="flex flex-col">
243
- <span className="font-medium">Exam Prep</span>
244
- <span className="text-xs text-muted-foreground">Practice with quiz questions</span>
245
- </div>
246
- </DropdownMenuItem>
247
-
248
- <DropdownMenuItem
249
- onClick={() => onLearningModeChange('assignment')}
250
- className={learningMode === 'assignment' ? 'bg-accent' : ''}
251
- >
252
- <div className="flex flex-col">
253
- <span className="font-medium">Assignment Helper</span>
254
- <span className="text-xs text-muted-foreground">Get help with assignments</span>
255
- </div>
256
- </DropdownMenuItem>
257
-
258
- <DropdownMenuItem
259
- onClick={() => onLearningModeChange('summary')}
260
- className={learningMode === 'summary' ? 'bg-accent' : ''}
261
- >
262
- <div className="flex flex-col">
263
- <span className="font-medium">Quick Summary</span>
264
- <span className="text-xs text-muted-foreground">Get concise summaries</span>
265
- </div>
266
- </DropdownMenuItem>
267
  </DropdownMenuContent>
268
  </DropdownMenu>
269
 
@@ -281,7 +229,6 @@ export function ChatArea({
281
  disabled={!isLoggedIn}
282
  className="min-h-[80px] pl-4 pr-12 resize-none bg-background border-2 border-border"
283
  />
284
-
285
  <Button type="submit" size="icon" disabled={!input.trim() || !isLoggedIn} className="absolute bottom-2 right-2 rounded-full">
286
  <Send className="h-4 w-4" />
287
  </Button>
@@ -291,7 +238,6 @@ export function ChatArea({
291
  </div>
292
  </div>
293
 
294
- {/* Course Materials Section */}
295
  <div className="bg-card">
296
  <div className="max-w-4xl mx-auto px-4 py-4">
297
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
 
1
+ // web/src/components/ChatArea.tsx
2
  import React, { useState, useRef, useEffect } from 'react';
3
  import { Button } from './ui/button';
4
  import { Textarea } from './ui/textarea';
 
8
  import { MemoryLine } from './MemoryLine';
9
  import type { Message as MessageType, LearningMode, UploadedFile, FileType, SpaceType } from '../App';
10
  import { toast } from 'sonner';
11
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './ui/dropdown-menu';
 
 
 
 
 
12
 
13
  interface ChatAreaProps {
14
  messages: MessageType[];
15
+ onSendMessage: (content: string) => void;
16
  uploadedFiles: UploadedFile[];
17
  onFileUpload: (files: File[]) => void;
18
  onRemoveFile: (index: number) => void;
 
23
  onClearConversation: () => void;
24
  onLearningModeChange: (mode: LearningMode) => void;
25
  spaceType: SpaceType;
 
26
  }
27
 
28
  export function ChatArea({
 
38
  onClearConversation,
39
  onLearningModeChange,
40
  spaceType,
 
41
  }: ChatAreaProps) {
42
  const [input, setInput] = useState('');
43
+ const [isTyping, setIsTyping] = useState(false);
44
  const [showScrollButton, setShowScrollButton] = useState(false);
45
  const messagesEndRef = useRef<HTMLDivElement>(null);
46
  const scrollContainerRef = useRef<HTMLDivElement>(null);
 
51
 
52
  useEffect(() => {
53
  scrollToBottom();
54
+ }, [messages]);
55
 
56
  useEffect(() => {
57
  const handleScroll = () => {
 
66
  return () => container?.removeEventListener('scroll', handleScroll);
67
  }, []);
68
 
69
+ const handleSubmit = (e: React.FormEvent) => {
70
  e.preventDefault();
71
  if (!input.trim() || !isLoggedIn) return;
72
 
73
+ onSendMessage(input);
74
  setInput('');
75
+ setIsTyping(true);
76
+ setTimeout(() => setIsTyping(false), 1200);
77
  };
78
 
79
  const handleKeyDown = (e: React.KeyboardEvent) => {
80
  if (e.key === 'Enter' && !e.shiftKey) {
81
  e.preventDefault();
82
+ handleSubmit(e);
83
  }
84
  };
85
 
 
96
  toast.info('No conversation to clear');
97
  return;
98
  }
 
99
  if (window.confirm('Are you sure you want to clear the conversation? This cannot be undone.')) {
100
  onClearConversation();
101
+ toast.success('Conversation cleared');
102
  }
103
  };
104
 
 
107
  toast.info('No conversation to share');
108
  return;
109
  }
 
110
  const conversationText = messages
111
  .map((msg) => `${msg.role === 'user' ? 'You' : 'Clare'}: ${msg.content}`)
112
  .join('\n\n');
 
119
 
120
  return (
121
  <div className="flex flex-col h-full">
 
122
  <div className="flex-1 relative border-b-2 border-border">
 
123
  {messages.length > 1 && (
124
  <div className="absolute top-4 right-12 z-10 flex gap-2">
125
  <Button
 
145
  </div>
146
  )}
147
 
 
148
  <div ref={scrollContainerRef} className="h-full max-h-[600px] overflow-y-auto px-4 py-6 pb-36">
149
  <div className="max-w-4xl mx-auto space-y-6">
150
  {messages.map((message) => (
 
170
  </div>
171
  </div>
172
 
 
173
  {showScrollButton && (
174
  <div className="absolute bottom-24 left-1/2 -translate-x-1/2 z-20">
175
  <Button
 
183
  </div>
184
  )}
185
 
 
186
  <div className="absolute bottom-0 left-0 right-0 bg-background/95 backdrop-blur-sm z-10">
187
  <div className="max-w-4xl mx-auto px-4 py-4">
188
  <form onSubmit={handleSubmit}>
189
  <div className="relative">
 
190
  <DropdownMenu>
191
  <DropdownMenuTrigger asChild>
192
  <Button
 
202
  </svg>
203
  </Button>
204
  </DropdownMenuTrigger>
 
205
  <DropdownMenuContent align="start" className="w-56">
206
+ {(['concept', 'socratic', 'exam', 'assignment', 'summary'] as LearningMode[]).map((m) => (
207
+ <DropdownMenuItem
208
+ key={m}
209
+ onClick={() => onLearningModeChange(m)}
210
+ className={learningMode === m ? 'bg-accent' : ''}
211
+ >
212
+ <span className="font-medium">{modeLabels[m]}</span>
213
+ </DropdownMenuItem>
214
+ ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  </DropdownMenuContent>
216
  </DropdownMenu>
217
 
 
229
  disabled={!isLoggedIn}
230
  className="min-h-[80px] pl-4 pr-12 resize-none bg-background border-2 border-border"
231
  />
 
232
  <Button type="submit" size="icon" disabled={!input.trim() || !isLoggedIn} className="absolute bottom-2 right-2 rounded-full">
233
  <Send className="h-4 w-4" />
234
  </Button>
 
238
  </div>
239
  </div>
240
 
 
241
  <div className="bg-card">
242
  <div className="max-w-4xl mx-auto px-4 py-4">
243
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">