SarahXia0405 commited on
Commit
22fd672
·
verified ·
1 Parent(s): e20dffa

Update web/src/components/ChatArea.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ChatArea.tsx +32 -32
web/src/components/ChatArea.tsx CHANGED
@@ -60,7 +60,10 @@ interface ChatAreaProps {
60
  onSendMessage: (content: string) => void;
61
  uploadedFiles: UploadedFile[];
62
  onFileUpload: (files: File[]) => void;
63
- onRemoveFile: (index: File) => void;
 
 
 
64
  onFileTypeChange: (index: number, type: FileType) => void;
65
  memoryProgress: number;
66
  isLoggedIn: boolean;
@@ -561,14 +564,11 @@ export function ChatArea({
561
 
562
  // ✅ useObjectUrlCache: for image thumbnails (uploaded + pending)
563
  const allThumbFiles = React.useMemo(() => {
564
- return [
565
- ...uploadedFiles.map((u) => u.file),
566
- ...pendingFiles.map((p) => p.file),
567
- ];
568
  }, [uploadedFiles, pendingFiles]);
569
  const { getOrCreate } = useObjectUrlCache(allThumbFiles);
570
 
571
- // ✅ NEW: a compact "chip" UI (the one with left X)
572
  const FileChip = ({
573
  file,
574
  index,
@@ -580,7 +580,7 @@ export function ChatArea({
580
  }) => {
581
  const ext = file.name.toLowerCase();
582
  const isImage = [".jpg", ".jpeg", ".png", ".gif", ".webp"].some((e) => ext.endsWith(e));
583
-
584
  const label = ext.endsWith(".pdf")
585
  ? "PDF"
586
  : ext.endsWith(".pptx") || ext.endsWith(".ppt")
@@ -590,24 +590,25 @@ export function ChatArea({
590
  : isImage
591
  ? "Image"
592
  : "File";
593
-
594
  const thumbUrl = isImage ? getOrCreate(file) : null;
595
-
 
596
  const handleRemove = () => {
597
  if (source === "uploaded") {
598
- onRemoveFile(index);
599
  } else {
600
- setPendingFiles((prev) => prev.filter((_, i) => i !== index));
601
  }
602
  };
603
-
604
  const stopAll = (e: any) => {
605
  e.preventDefault();
606
  e.stopPropagation();
607
  // @ts-ignore
608
  e.nativeEvent?.stopImmediatePropagation?.();
609
  };
610
-
611
  return (
612
  <div className="flex items-center gap-3 rounded-xl border border-border bg-card px-3 py-2 shadow-sm w-[340px] max-w-full">
613
  <button
@@ -625,14 +626,14 @@ export function ChatArea({
625
  >
626
  <X className="h-4 w-4" />
627
  </button>
628
-
629
  <div className="min-w-0 flex-1">
630
  <div className="text-sm font-medium truncate" title={file.name}>
631
  {file.name}
632
  </div>
633
  <div className="text-xs text-muted-foreground">{label}</div>
634
  </div>
635
-
636
  {isImage ? (
637
  <div className="relative h-14 w-14 flex-shrink-0 rounded-lg overflow-hidden border border-border">
638
  {thumbUrl ? (
@@ -732,7 +733,11 @@ export function ChatArea({
732
  const current = workspaces.find((w) => w.id === currentWorkspaceId);
733
  if (current?.type === "group") {
734
  if (current.category === "course" && current.courseName) {
735
- return <div className="h-9 px-3 inline-flex items-center rounded-md border font-semibold">{current.courseName}</div>;
 
 
 
 
736
  }
737
  return null;
738
  }
@@ -830,7 +835,11 @@ export function ChatArea({
830
  </div>
831
 
832
  {/* Scroll Container */}
833
- <div ref={scrollContainerRef} className="flex-1 min-h-0 overflow-y-auto overscroll-contain" style={{ overscrollBehavior: "contain" }}>
 
 
 
 
834
  <div className="py-6" style={{ paddingBottom: bottomPad }}>
835
  <div className="w-full space-y-6 max-w-4xl mx-auto">
836
  {messages.map((message) => (
@@ -909,22 +918,12 @@ export function ChatArea({
909
  <div className="mb-2 flex flex-wrap gap-2 max-h-32 overflow-y-auto">
910
  {/* uploaded */}
911
  {uploadedFiles.map((u, idx) => (
912
- <FileChip
913
- key={`u-${u.file.name}-${u.file.size}-${u.file.lastModified}`}
914
- file={u.file}
915
- index={idx}
916
- source="uploaded"
917
- />
918
  ))}
919
-
920
  {/* pending (type dialog 之前也能显示的话) */}
921
  {pendingFiles.map((p, idx) => (
922
- <FileChip
923
- key={`p-${p.file.name}-${p.file.size}-${p.file.lastModified}`}
924
- file={p.file}
925
- index={idx}
926
- source="pending"
927
- />
928
  ))}
929
  </div>
930
  )}
@@ -1200,7 +1199,7 @@ export function ChatArea({
1200
  </DialogContent>
1201
  </Dialog>
1202
 
1203
- {/* Delete File Confirmation Dialog (kept, but chip delete is instant now) */}
1204
  <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
1205
  <AlertDialogContent>
1206
  <AlertDialogHeader>
@@ -1214,7 +1213,8 @@ export function ChatArea({
1214
  <AlertDialogAction
1215
  onClick={() => {
1216
  if (fileToDelete !== null) {
1217
- onRemoveFile(fileToDelete);
 
1218
  setFileToDelete(null);
1219
  }
1220
  setShowDeleteDialog(false);
 
60
  onSendMessage: (content: string) => void;
61
  uploadedFiles: UploadedFile[];
62
  onFileUpload: (files: File[]) => void;
63
+
64
+ // ✅ FIX: remove by File (NOT by index)
65
+ onRemoveFile: (file: File) => void;
66
+
67
  onFileTypeChange: (index: number, type: FileType) => void;
68
  memoryProgress: number;
69
  isLoggedIn: boolean;
 
564
 
565
  // ✅ useObjectUrlCache: for image thumbnails (uploaded + pending)
566
  const allThumbFiles = React.useMemo(() => {
567
+ return [...uploadedFiles.map((u) => u.file), ...pendingFiles.map((p) => p.file)];
 
 
 
568
  }, [uploadedFiles, pendingFiles]);
569
  const { getOrCreate } = useObjectUrlCache(allThumbFiles);
570
 
571
+ // ✅ NEW: a compact "chip" UI (the one with left X)
572
  const FileChip = ({
573
  file,
574
  index,
 
580
  }) => {
581
  const ext = file.name.toLowerCase();
582
  const isImage = [".jpg", ".jpeg", ".png", ".gif", ".webp"].some((e) => ext.endsWith(e));
583
+
584
  const label = ext.endsWith(".pdf")
585
  ? "PDF"
586
  : ext.endsWith(".pptx") || ext.endsWith(".ppt")
 
590
  : isImage
591
  ? "Image"
592
  : "File";
593
+
594
  const thumbUrl = isImage ? getOrCreate(file) : null;
595
+
596
+ // ✅ FIX: uploaded remove by File; pending remove by File (not index)
597
  const handleRemove = () => {
598
  if (source === "uploaded") {
599
+ onRemoveFile(file);
600
  } else {
601
+ setPendingFiles((prev) => prev.filter((p) => p.file !== file));
602
  }
603
  };
604
+
605
  const stopAll = (e: any) => {
606
  e.preventDefault();
607
  e.stopPropagation();
608
  // @ts-ignore
609
  e.nativeEvent?.stopImmediatePropagation?.();
610
  };
611
+
612
  return (
613
  <div className="flex items-center gap-3 rounded-xl border border-border bg-card px-3 py-2 shadow-sm w-[340px] max-w-full">
614
  <button
 
626
  >
627
  <X className="h-4 w-4" />
628
  </button>
629
+
630
  <div className="min-w-0 flex-1">
631
  <div className="text-sm font-medium truncate" title={file.name}>
632
  {file.name}
633
  </div>
634
  <div className="text-xs text-muted-foreground">{label}</div>
635
  </div>
636
+
637
  {isImage ? (
638
  <div className="relative h-14 w-14 flex-shrink-0 rounded-lg overflow-hidden border border-border">
639
  {thumbUrl ? (
 
733
  const current = workspaces.find((w) => w.id === currentWorkspaceId);
734
  if (current?.type === "group") {
735
  if (current.category === "course" && current.courseName) {
736
+ return (
737
+ <div className="h-9 px-3 inline-flex items-center rounded-md border font-semibold">
738
+ {current.courseName}
739
+ </div>
740
+ );
741
  }
742
  return null;
743
  }
 
835
  </div>
836
 
837
  {/* Scroll Container */}
838
+ <div
839
+ ref={scrollContainerRef}
840
+ className="flex-1 min-h-0 overflow-y-auto overscroll-contain"
841
+ style={{ overscrollBehavior: "contain" }}
842
+ >
843
  <div className="py-6" style={{ paddingBottom: bottomPad }}>
844
  <div className="w-full space-y-6 max-w-4xl mx-auto">
845
  {messages.map((message) => (
 
918
  <div className="mb-2 flex flex-wrap gap-2 max-h-32 overflow-y-auto">
919
  {/* uploaded */}
920
  {uploadedFiles.map((u, idx) => (
921
+ <FileChip key={`u-${u.file.name}-${u.file.size}-${u.file.lastModified}`} file={u.file} index={idx} source="uploaded" />
 
 
 
 
 
922
  ))}
923
+
924
  {/* pending (type dialog 之前也能显示的话) */}
925
  {pendingFiles.map((p, idx) => (
926
+ <FileChip key={`p-${p.file.name}-${p.file.size}-${p.file.lastModified}`} file={p.file} index={idx} source="pending" />
 
 
 
 
 
927
  ))}
928
  </div>
929
  )}
 
1199
  </DialogContent>
1200
  </Dialog>
1201
 
1202
+ {/* Delete File Confirmation Dialog (kept, but if triggered, delete is correct now) */}
1203
  <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
1204
  <AlertDialogContent>
1205
  <AlertDialogHeader>
 
1213
  <AlertDialogAction
1214
  onClick={() => {
1215
  if (fileToDelete !== null) {
1216
+ const f = uploadedFiles[fileToDelete]?.file;
1217
+ if (f) onRemoveFile(f);
1218
  setFileToDelete(null);
1219
  }
1220
  setShowDeleteDialog(false);