SarahXia0405 commited on
Commit
0ddb203
·
verified ·
1 Parent(s): 91aa146

Update web/src/components/ChatArea.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ChatArea.tsx +28 -93
web/src/components/ChatArea.tsx CHANGED
@@ -137,7 +137,7 @@ export function ChatArea({
137
  workspaces,
138
  currentWorkspaceId,
139
  onSaveFile,
140
- leftPanelVisible = false,
141
  currentCourseId,
142
  onCourseChange,
143
  availableCourses = [],
@@ -166,7 +166,6 @@ export function ChatArea({
166
  const [shareLink, setShareLink] = useState("");
167
  const [targetWorkspaceId, setTargetWorkspaceId] = useState<string>("");
168
 
169
- // Use availableCourses if provided, otherwise fallback
170
  const courses =
171
  availableCourses.length > 0
172
  ? availableCourses
@@ -194,7 +193,6 @@ export function ChatArea({
194
  isInitialMount.current = false;
195
  previousMessagesLength.current = messages.length;
196
 
197
- // stay at top on initial load
198
  if (scrollContainerRef.current) {
199
  scrollContainerRef.current.scrollTop = 0;
200
  }
@@ -227,26 +225,6 @@ export function ChatArea({
227
  return () => container.removeEventListener("scroll", handleScroll);
228
  }, [messages]);
229
 
230
- // ✅ Prevent scroll chaining to left panel / outer containers
231
- useEffect(() => {
232
- const el = scrollContainerRef.current;
233
- if (!el) return;
234
-
235
- const onWheel = (e: WheelEvent) => {
236
- e.stopPropagation();
237
-
238
- const atTop = el.scrollTop <= 0;
239
- const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
240
-
241
- if ((atTop && e.deltaY < 0) || (atBottom && e.deltaY > 0)) {
242
- e.preventDefault();
243
- }
244
- };
245
-
246
- el.addEventListener("wheel", onWheel, { passive: false });
247
- return () => el.removeEventListener("wheel", onWheel);
248
- }, []);
249
-
250
  const handleSubmit = (e: React.FormEvent) => {
251
  e.preventDefault();
252
  if (!input.trim() || !isLoggedIn) return;
@@ -294,7 +272,9 @@ export function ChatArea({
294
 
295
  const buildPreviewContent = () => {
296
  if (!messages.length) return "";
297
- return messages.map((msg) => `${msg.role === "user" ? "You" : "Clare"}: ${msg.content}`).join("\n\n");
 
 
298
  };
299
 
300
  const buildSummaryContent = () => {
@@ -441,7 +421,6 @@ export function ChatArea({
441
  const saved = isCurrentChatSaved();
442
 
443
  if (saved) {
444
- // current logic: if saved, don't prompt — just start new
445
  onConfirmClear(false);
446
  return;
447
  }
@@ -484,8 +463,8 @@ export function ChatArea({
484
 
485
  const validFiles = files.filter((file) => {
486
  const ext = file.name.toLowerCase();
487
- return [".pdf", ".docx", ".pptx", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".doc", ".ppt"].some((allowed) =>
488
- ext.endsWith(allowed)
489
  );
490
  });
491
 
@@ -502,8 +481,8 @@ export function ChatArea({
502
  if (files.length > 0) {
503
  const validFiles = files.filter((file) => {
504
  const ext = file.name.toLowerCase();
505
- return [".pdf", ".docx", ".pptx", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".doc", ".ppt"].some((allowed) =>
506
- ext.endsWith(allowed)
507
  );
508
  });
509
 
@@ -521,7 +500,6 @@ export function ChatArea({
521
  onFileUpload(pendingFiles.map((pf) => pf.file));
522
  const startIndex = uploadedFiles.length;
523
 
524
- // After uploadedFiles state grows (in parent), we call type change on next tick
525
  pendingFiles.forEach((pf, idx) => {
526
  setTimeout(() => {
527
  onFileTypeChange(startIndex + idx, pf.type);
@@ -756,9 +734,10 @@ export function ChatArea({
756
  };
757
 
758
  return (
759
- <div className="flex flex-col h-full min-h-0 overflow-hidden" style={{ overscrollBehavior: "none" }}>
760
- <div className="flex-1 relative min-h-0 flex flex-col overflow-hidden" style={{ overscrollBehavior: "none" }}>
761
- {/* Messages Area is the ONLY scroll container */}
 
762
  <div
763
  ref={scrollContainerRef}
764
  className="flex-1 min-h-0 overflow-y-auto"
@@ -787,10 +766,7 @@ export function ChatArea({
787
  }
788
 
789
  return (
790
- <Select
791
- value={currentCourseId || "course1"}
792
- onValueChange={(val) => onCourseChange && onCourseChange(val)}
793
- >
794
  <SelectTrigger className="w-[200px] h-9 font-semibold">
795
  <SelectValue placeholder="Select course" />
796
  </SelectTrigger>
@@ -934,18 +910,9 @@ export function ChatArea({
934
  </div>
935
  <div className="bg-muted rounded-2xl px-4 py-3">
936
  <div className="flex gap-1">
937
- <div
938
- className="w-2 h-2 rounded-full bg-muted-foreground/50 animate-bounce"
939
- style={{ animationDelay: "0ms" }}
940
- />
941
- <div
942
- className="w-2 h-2 rounded-full bg-muted-foreground/50 animate-bounce"
943
- style={{ animationDelay: "150ms" }}
944
- />
945
- <div
946
- className="w-2 h-2 rounded-full bg-muted-foreground/50 animate-bounce"
947
- style={{ animationDelay: "300ms" }}
948
- />
949
  </div>
950
  </div>
951
  </div>
@@ -956,15 +923,11 @@ export function ChatArea({
956
  </div>
957
  </div>
958
 
959
- {/* Scroll to Bottom Button */}
960
  {showScrollButton && (
961
  <div
962
- className="fixed z-30 flex justify-center pointer-events-none"
963
- style={{
964
- bottom: "120px",
965
- left: leftPanelVisible ? "320px" : "0px",
966
- right: "0px",
967
- }}
968
  >
969
  <Button
970
  variant="secondary"
@@ -978,14 +941,8 @@ export function ChatArea({
978
  </div>
979
  )}
980
 
981
- {/* Floating Input Area */}
982
- <div
983
- className="fixed bottom-0 bg-background/95 backdrop-blur-sm z-10"
984
- style={{
985
- left: leftPanelVisible ? "320px" : "0px",
986
- right: "0px",
987
- }}
988
- >
989
  <div className="max-w-4xl mx-auto px-4 py-4">
990
  {uploadedFiles.length > 0 && (
991
  <div className="mb-2 flex flex-wrap gap-2 max-h-32 overflow-y-auto">
@@ -1052,9 +1009,7 @@ export function ChatArea({
1052
  >
1053
  <div className="flex flex-col">
1054
  <span className="font-medium">General</span>
1055
- <span className="text-xs text-muted-foreground">
1056
- Answer various questions (context required)
1057
- </span>
1058
  </div>
1059
  </DropdownMenuItem>
1060
 
@@ -1229,13 +1184,7 @@ export function ChatArea({
1229
  <div className="border rounded-lg bg-muted/40 flex flex-col max-h-64">
1230
  <div className="flex items-center justify-between p-4 sticky top-0 bg-muted/40 border-b z-10">
1231
  <span className="text-sm font-medium">Preview</span>
1232
- <Button
1233
- variant="outline"
1234
- size="sm"
1235
- className="h-7 px-2 text-xs gap-1.5"
1236
- onClick={handleCopyPreview}
1237
- title="Copy preview"
1238
- >
1239
  <Copy className="h-3 w-3" />
1240
  Copy
1241
  </Button>
@@ -1310,9 +1259,7 @@ export function ChatArea({
1310
  ))}
1311
  </SelectContent>
1312
  </Select>
1313
- <p className="text-xs text-muted-foreground">
1314
- Sends this conversation to the selected workspace&apos;s Saved Files.
1315
- </p>
1316
  <Button onClick={handleShareSendToWorkspace} className="w-full">
1317
  Send
1318
  </Button>
@@ -1327,9 +1274,7 @@ export function ChatArea({
1327
  <AlertDialogHeader>
1328
  <AlertDialogTitle>Delete File</AlertDialogTitle>
1329
  <AlertDialogDescription>
1330
- Are you sure you want to delete &quot;
1331
- {fileToDelete !== null ? uploadedFiles[fileToDelete]?.file.name : ""}
1332
- &quot;? This action cannot be undone.
1333
  </AlertDialogDescription>
1334
  </AlertDialogHeader>
1335
  <AlertDialogFooter>
@@ -1355,18 +1300,11 @@ export function ChatArea({
1355
  <DialogHeader className="min-w-0 flex-shrink-0">
1356
  <DialogTitle
1357
  className="pr-8 break-words break-all overflow-wrap-anywhere leading-relaxed"
1358
- style={{
1359
- wordBreak: "break-all",
1360
- overflowWrap: "anywhere",
1361
- maxWidth: "100%",
1362
- lineHeight: "1.6",
1363
- }}
1364
  >
1365
  {selectedFile?.file.name}
1366
  </DialogTitle>
1367
- <DialogDescription>
1368
- File size: {selectedFile ? formatFileSize(selectedFile.file.size) : ""}
1369
- </DialogDescription>
1370
  </DialogHeader>
1371
  <div className="flex-1 min-h-0 overflow-y-auto mt-4">{selectedFile && <FileViewerContent file={selectedFile.file} />}</div>
1372
  </DialogContent>
@@ -1396,10 +1334,7 @@ export function ChatArea({
1396
 
1397
  <div className="space-y-1">
1398
  <label className="text-xs text-muted-foreground">File Type</label>
1399
- <Select
1400
- value={pendingFile.type}
1401
- onValueChange={(value) => handlePendingFileTypeChange(index, value as FileType)}
1402
- >
1403
  <SelectTrigger className="h-8 text-xs">
1404
  <SelectValue />
1405
  </SelectTrigger>
 
137
  workspaces,
138
  currentWorkspaceId,
139
  onSaveFile,
140
+ leftPanelVisible = false, // kept for props compatibility; not used for positioning anymore
141
  currentCourseId,
142
  onCourseChange,
143
  availableCourses = [],
 
166
  const [shareLink, setShareLink] = useState("");
167
  const [targetWorkspaceId, setTargetWorkspaceId] = useState<string>("");
168
 
 
169
  const courses =
170
  availableCourses.length > 0
171
  ? availableCourses
 
193
  isInitialMount.current = false;
194
  previousMessagesLength.current = messages.length;
195
 
 
196
  if (scrollContainerRef.current) {
197
  scrollContainerRef.current.scrollTop = 0;
198
  }
 
225
  return () => container.removeEventListener("scroll", handleScroll);
226
  }, [messages]);
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  const handleSubmit = (e: React.FormEvent) => {
229
  e.preventDefault();
230
  if (!input.trim() || !isLoggedIn) return;
 
272
 
273
  const buildPreviewContent = () => {
274
  if (!messages.length) return "";
275
+ return messages
276
+ .map((msg) => `${msg.role === "user" ? "You" : "Clare"}: ${msg.content}`)
277
+ .join("\n\n");
278
  };
279
 
280
  const buildSummaryContent = () => {
 
421
  const saved = isCurrentChatSaved();
422
 
423
  if (saved) {
 
424
  onConfirmClear(false);
425
  return;
426
  }
 
463
 
464
  const validFiles = files.filter((file) => {
465
  const ext = file.name.toLowerCase();
466
+ return [".pdf", ".docx", ".pptx", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".doc", ".ppt"].some(
467
+ (allowed) => ext.endsWith(allowed)
468
  );
469
  });
470
 
 
481
  if (files.length > 0) {
482
  const validFiles = files.filter((file) => {
483
  const ext = file.name.toLowerCase();
484
+ return [".pdf", ".docx", ".pptx", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".doc", ".ppt"].some(
485
+ (allowed) => ext.endsWith(allowed)
486
  );
487
  });
488
 
 
500
  onFileUpload(pendingFiles.map((pf) => pf.file));
501
  const startIndex = uploadedFiles.length;
502
 
 
503
  pendingFiles.forEach((pf, idx) => {
504
  setTimeout(() => {
505
  onFileTypeChange(startIndex + idx, pf.type);
 
734
  };
735
 
736
  return (
737
+ // IMPORTANT: relative enables internal absolute overlays (input + scroll button)
738
+ <div className="relative flex flex-col h-full min-h-0 overflow-hidden">
739
+ <div className="flex-1 relative min-h-0 flex flex-col overflow-hidden">
740
+ {/* Messages Area is the ONLY scroll container */}
741
  <div
742
  ref={scrollContainerRef}
743
  className="flex-1 min-h-0 overflow-y-auto"
 
766
  }
767
 
768
  return (
769
+ <Select value={currentCourseId || "course1"} onValueChange={(val) => onCourseChange && onCourseChange(val)}>
 
 
 
770
  <SelectTrigger className="w-[200px] h-9 font-semibold">
771
  <SelectValue placeholder="Select course" />
772
  </SelectTrigger>
 
910
  </div>
911
  <div className="bg-muted rounded-2xl px-4 py-3">
912
  <div className="flex gap-1">
913
+ <div className="w-2 h-2 rounded-full bg-muted-foreground/50 animate-bounce" style={{ animationDelay: "0ms" }} />
914
+ <div className="w-2 h-2 rounded-full bg-muted-foreground/50 animate-bounce" style={{ animationDelay: "150ms" }} />
915
+ <div className="w-2 h-2 rounded-full bg-muted-foreground/50 animate-bounce" style={{ animationDelay: "300ms" }} />
 
 
 
 
 
 
 
 
 
916
  </div>
917
  </div>
918
  </div>
 
923
  </div>
924
  </div>
925
 
926
+ {/* Scroll to Bottom Button (NOW absolute inside ChatArea) */}
927
  {showScrollButton && (
928
  <div
929
+ className="absolute z-30 left-0 right-0 flex justify-center pointer-events-none"
930
+ style={{ bottom: "120px" }}
 
 
 
 
931
  >
932
  <Button
933
  variant="secondary"
 
941
  </div>
942
  )}
943
 
944
+ {/* Floating Input Area (NOW absolute inside ChatArea) */}
945
+ <div className="absolute bottom-0 left-0 right-0 bg-background/95 backdrop-blur-sm z-10">
 
 
 
 
 
 
946
  <div className="max-w-4xl mx-auto px-4 py-4">
947
  {uploadedFiles.length > 0 && (
948
  <div className="mb-2 flex flex-wrap gap-2 max-h-32 overflow-y-auto">
 
1009
  >
1010
  <div className="flex flex-col">
1011
  <span className="font-medium">General</span>
1012
+ <span className="text-xs text-muted-foreground">Answer various questions (context required)</span>
 
 
1013
  </div>
1014
  </DropdownMenuItem>
1015
 
 
1184
  <div className="border rounded-lg bg-muted/40 flex flex-col max-h-64">
1185
  <div className="flex items-center justify-between p-4 sticky top-0 bg-muted/40 border-b z-10">
1186
  <span className="text-sm font-medium">Preview</span>
1187
+ <Button variant="outline" size="sm" className="h-7 px-2 text-xs gap-1.5" onClick={handleCopyPreview} title="Copy preview">
 
 
 
 
 
 
1188
  <Copy className="h-3 w-3" />
1189
  Copy
1190
  </Button>
 
1259
  ))}
1260
  </SelectContent>
1261
  </Select>
1262
+ <p className="text-xs text-muted-foreground">Sends this conversation to the selected workspace&apos;s Saved Files.</p>
 
 
1263
  <Button onClick={handleShareSendToWorkspace} className="w-full">
1264
  Send
1265
  </Button>
 
1274
  <AlertDialogHeader>
1275
  <AlertDialogTitle>Delete File</AlertDialogTitle>
1276
  <AlertDialogDescription>
1277
+ Are you sure you want to delete &quot;{fileToDelete !== null ? uploadedFiles[fileToDelete]?.file.name : ""}&quot;? This action cannot be undone.
 
 
1278
  </AlertDialogDescription>
1279
  </AlertDialogHeader>
1280
  <AlertDialogFooter>
 
1300
  <DialogHeader className="min-w-0 flex-shrink-0">
1301
  <DialogTitle
1302
  className="pr-8 break-words break-all overflow-wrap-anywhere leading-relaxed"
1303
+ style={{ wordBreak: "break-all", overflowWrap: "anywhere", maxWidth: "100%", lineHeight: "1.6" }}
 
 
 
 
 
1304
  >
1305
  {selectedFile?.file.name}
1306
  </DialogTitle>
1307
+ <DialogDescription>File size: {selectedFile ? formatFileSize(selectedFile.file.size) : ""}</DialogDescription>
 
 
1308
  </DialogHeader>
1309
  <div className="flex-1 min-h-0 overflow-y-auto mt-4">{selectedFile && <FileViewerContent file={selectedFile.file} />}</div>
1310
  </DialogContent>
 
1334
 
1335
  <div className="space-y-1">
1336
  <label className="text-xs text-muted-foreground">File Type</label>
1337
+ <Select value={pendingFile.type} onValueChange={(value) => handlePendingFileTypeChange(index, value as FileType)}>
 
 
 
1338
  <SelectTrigger className="h-8 text-xs">
1339
  <SelectValue />
1340
  </SelectTrigger>