SarahXia0405 commited on
Commit
24c1d05
·
verified ·
1 Parent(s): cebaa45

Update web/src/components/ChatArea.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/ChatArea.tsx +39 -35
web/src/components/ChatArea.tsx CHANGED
@@ -50,9 +50,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
50
  import { SmartReview } from "./SmartReview";
51
  import clareAvatar from "../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png";
52
 
53
- // ✅ NEW: object URL cache for image thumbnails
54
- import { useObjectUrlCache } from "../lib/useObjectUrlCache";
55
-
56
  type ReviewEventType = "send_message" | "review_topic" | "review_all";
57
 
58
  interface ChatAreaProps {
@@ -398,11 +395,7 @@ export function ChatArea({
398
 
399
  return chat.messages.every((savedMsg, idx) => {
400
  const currentMsg = messages[idx];
401
- return (
402
- savedMsg.id === currentMsg.id &&
403
- savedMsg.role === currentMsg.role &&
404
- savedMsg.content === currentMsg.content
405
- );
406
  });
407
  });
408
  };
@@ -574,10 +567,7 @@ export function ChatArea({
574
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
575
  };
576
 
577
- // ✅ objectURL cache for image thumbnails (display only)
578
- const cacheFiles = React.useMemo(() => uploadedFiles.map((u) => u.file), [uploadedFiles]);
579
- const { getOrCreate } = useObjectUrlCache(cacheFiles);
580
-
581
  const FileThumbnail = ({
582
  file,
583
  Icon,
@@ -591,11 +581,29 @@ export function ChatArea({
591
  fileInfo: { bgColor: string; type: string };
592
  isImage: boolean;
593
  onPreview: () => void;
594
- onRemove: (e: React.MouseEvent) => void;
595
  }) => {
596
- if (isImage) {
597
- const src = getOrCreate(file);
598
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
599
  return (
600
  <div
601
  className="relative cursor-pointer w-16 h-16 flex-shrink-0"
@@ -604,19 +612,23 @@ export function ChatArea({
604
  >
605
  <div className="w-full h-full relative bg-card border border-border rounded-lg hover:border-primary/50 transition-colors">
606
  <div className="w-full h-full overflow-hidden rounded-lg absolute inset-0">
607
- {src ? (
 
 
 
 
608
  <img
609
- src={src}
610
  alt={file.name}
611
  className="w-full h-full object-cover"
612
- draggable={false}
613
  onError={(e) => {
614
  e.currentTarget.style.display = "none";
 
615
  }}
616
  />
617
  ) : (
618
  <div className="w-full h-full flex items-center justify-center bg-muted">
619
- <Icon className="h-5 w-5 text-muted-foreground animate-pulse" />
620
  </div>
621
  )}
622
  </div>
@@ -626,7 +638,7 @@ export function ChatArea({
626
  className="absolute top-1 right-1 h-4 w-4 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-sm flex items-center justify-center cursor-pointer"
627
  onClick={(e) => {
628
  e.stopPropagation();
629
- onRemove(e);
630
  }}
631
  style={{ zIndex: 100 }}
632
  >
@@ -656,7 +668,7 @@ export function ChatArea({
656
  className="absolute top-1 right-1 h-4 w-4 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-sm flex items-center justify-center cursor-pointer z-10"
657
  onClick={(e) => {
658
  e.stopPropagation();
659
- onRemove(e);
660
  }}
661
  style={{ zIndex: 10 }}
662
  >
@@ -961,8 +973,8 @@ export function ChatArea({
961
  setSelectedFile({ file: uploadedFile.file, index });
962
  setShowFileViewer(true);
963
  }}
964
- onRemove={(e) => {
965
- e.stopPropagation();
966
  setFileToDelete(index);
967
  setShowDeleteDialog(true);
968
  }}
@@ -1175,13 +1187,7 @@ export function ChatArea({
1175
  <div className="border rounded-lg bg-muted/40 flex flex-col max-h-64">
1176
  <div className="flex items-center justify-between p-4 sticky top-0 bg-muted/40 border-b z-10">
1177
  <span className="text-sm font-medium">Preview</span>
1178
- <Button
1179
- variant="outline"
1180
- size="sm"
1181
- className="h-7 px-2 text-xs gap-1.5"
1182
- onClick={handleCopyPreview}
1183
- title="Copy preview"
1184
- >
1185
  <Copy className="h-3 w-3" />
1186
  Copy
1187
  </Button>
@@ -1267,7 +1273,8 @@ export function ChatArea({
1267
 
1268
  {/* Delete File Confirmation Dialog */}
1269
  <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
1270
- <AlertDialogContent>
 
1271
  <AlertDialogHeader>
1272
  <AlertDialogTitle>Delete File</AlertDialogTitle>
1273
  <AlertDialogDescription>
@@ -1332,10 +1339,7 @@ export function ChatArea({
1332
 
1333
  <div className="space-y-1">
1334
  <label className="text-xs text-muted-foreground">File Type</label>
1335
- <Select
1336
- value={pendingFile.type}
1337
- onValueChange={(value) => handlePendingFileTypeChange(index, value as FileType)}
1338
- >
1339
  <SelectTrigger className="h-8 text-xs">
1340
  <SelectValue />
1341
  </SelectTrigger>
 
50
  import { SmartReview } from "./SmartReview";
51
  import clareAvatar from "../assets/dfe44dab3ad8cd93953eac4a3e68bd1a5f999653.png";
52
 
 
 
 
53
  type ReviewEventType = "send_message" | "review_topic" | "review_all";
54
 
55
  interface ChatAreaProps {
 
395
 
396
  return chat.messages.every((savedMsg, idx) => {
397
  const currentMsg = messages[idx];
398
+ return savedMsg.id === currentMsg.id && savedMsg.role === currentMsg.role && savedMsg.content === currentMsg.content;
 
 
 
 
399
  });
400
  });
401
  };
 
567
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
568
  };
569
 
570
+ // ✅ CHANGE 1: onRemove 改成无参,避免事件传递导致“点了没反应”
 
 
 
571
  const FileThumbnail = ({
572
  file,
573
  Icon,
 
581
  fileInfo: { bgColor: string; type: string };
582
  isImage: boolean;
583
  onPreview: () => void;
584
+ onRemove: () => void; // ✅ CHANGED
585
  }) => {
586
+ const [imagePreview, setImagePreview] = useState<string | null>(null);
587
+ const [imageLoading, setImageLoading] = useState(true);
588
 
589
+ useEffect(() => {
590
+ if (!isImage) {
591
+ setImagePreview(null);
592
+ setImageLoading(false);
593
+ return;
594
+ }
595
+
596
+ setImageLoading(true);
597
+ const reader = new FileReader();
598
+ reader.onload = (e) => {
599
+ setImagePreview(e.target?.result as string);
600
+ setImageLoading(false);
601
+ };
602
+ reader.onerror = () => setImageLoading(false);
603
+ reader.readAsDataURL(file);
604
+ }, [file, isImage]);
605
+
606
+ if (isImage) {
607
  return (
608
  <div
609
  className="relative cursor-pointer w-16 h-16 flex-shrink-0"
 
612
  >
613
  <div className="w-full h-full relative bg-card border border-border rounded-lg hover:border-primary/50 transition-colors">
614
  <div className="w-full h-full overflow-hidden rounded-lg absolute inset-0">
615
+ {imageLoading ? (
616
+ <div className="w-full h-full flex items-center justify-center bg-muted">
617
+ <Icon className="h-5 w-5 text-muted-foreground animate-pulse" />
618
+ </div>
619
+ ) : imagePreview ? (
620
  <img
621
+ src={imagePreview}
622
  alt={file.name}
623
  className="w-full h-full object-cover"
 
624
  onError={(e) => {
625
  e.currentTarget.style.display = "none";
626
+ setImageLoading(false);
627
  }}
628
  />
629
  ) : (
630
  <div className="w-full h-full flex items-center justify-center bg-muted">
631
+ <Icon className="h-5 w-5 text-muted-foreground" />
632
  </div>
633
  )}
634
  </div>
 
638
  className="absolute top-1 right-1 h-4 w-4 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-sm flex items-center justify-center cursor-pointer"
639
  onClick={(e) => {
640
  e.stopPropagation();
641
+ onRemove(); // ✅ CHANGED
642
  }}
643
  style={{ zIndex: 100 }}
644
  >
 
668
  className="absolute top-1 right-1 h-4 w-4 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-sm flex items-center justify-center cursor-pointer z-10"
669
  onClick={(e) => {
670
  e.stopPropagation();
671
+ onRemove(); // ✅ CHANGED
672
  }}
673
  style={{ zIndex: 10 }}
674
  >
 
973
  setSelectedFile({ file: uploadedFile.file, index });
974
  setShowFileViewer(true);
975
  }}
976
+ // CHANGE 2: 直接 set state,不传事件
977
+ onRemove={() => {
978
  setFileToDelete(index);
979
  setShowDeleteDialog(true);
980
  }}
 
1187
  <div className="border rounded-lg bg-muted/40 flex flex-col max-h-64">
1188
  <div className="flex items-center justify-between p-4 sticky top-0 bg-muted/40 border-b z-10">
1189
  <span className="text-sm font-medium">Preview</span>
1190
+ <Button variant="outline" size="sm" className="h-7 px-2 text-xs gap-1.5" onClick={handleCopyPreview} title="Copy preview">
 
 
 
 
 
 
1191
  <Copy className="h-3 w-3" />
1192
  Copy
1193
  </Button>
 
1273
 
1274
  {/* Delete File Confirmation Dialog */}
1275
  <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
1276
+ {/* ✅ CHANGE 3: 提高 z-index,确保弹窗不会被遮挡 */}
1277
+ <AlertDialogContent className="z-[99999]" style={{ zIndex: 99999 }}>
1278
  <AlertDialogHeader>
1279
  <AlertDialogTitle>Delete File</AlertDialogTitle>
1280
  <AlertDialogDescription>
 
1339
 
1340
  <div className="space-y-1">
1341
  <label className="text-xs text-muted-foreground">File Type</label>
1342
+ <Select value={pendingFile.type} onValueChange={(value) => handlePendingFileTypeChange(index, value as FileType)}>
 
 
 
1343
  <SelectTrigger className="h-8 text-xs">
1344
  <SelectValue />
1345
  </SelectTrigger>