Spaces:
Sleeping
Sleeping
Update web/src/components/ChatArea.tsx
Browse files- 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 |
-
// ✅
|
| 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: (
|
| 595 |
}) => {
|
| 596 |
-
|
| 597 |
-
|
| 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 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
<img
|
| 609 |
-
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
|
| 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(
|
| 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(
|
| 660 |
}}
|
| 661 |
style={{ zIndex: 10 }}
|
| 662 |
>
|
|
@@ -961,8 +973,8 @@ export function ChatArea({
|
|
| 961 |
setSelectedFile({ file: uploadedFile.file, index });
|
| 962 |
setShowFileViewer(true);
|
| 963 |
}}
|
| 964 |
-
|
| 965 |
-
|
| 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 |
-
|
|
|
|
| 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>
|