From 93e5493993f5117b95c090e7e500faf56f5288ef Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 4 Mar 2026 00:06:14 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E5=9B=BE=E7=89=87=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E4=BB=A3=E7=90=86=E4=BB=A5=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/canvas/CanvasChatPanel.tsx | 94 ++++++++++++++++++++--- components/canvas/CanvasFileList.tsx | 14 +++- components/canvas/CanvasFileListPanel.tsx | 14 +++- components/image-preview.tsx | 18 +++-- 4 files changed, 123 insertions(+), 17 deletions(-) diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index eac7ed1a7..67cdc40cd 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -44,6 +44,7 @@ import { extractOriginalImageUrlFromAlt, inferOriginalImageUrl, } from "@/lib/image-url"; +import { buildImageProxyUrl } from "@/lib/image-delivery"; import type { Attachment, ChatMessage } from "@/lib/types"; import { type CanvasLaunchPayload, getCanvasLaunchStorageKey } from "@/lib/canvas-launch"; import { ImageParamsBar, type ImageGenerationParams } from "@/components/image-params-bar"; @@ -140,6 +141,14 @@ function extractPlainImageUrls(text: string): string[] { return matches ? Array.from(new Set(matches)) : []; } +function buildPreviewImageUrl(url: string, width: number): string { + return buildImageProxyUrl(url, { + width, + quality: 86, + format: "webp", + }); +} + function normalizeMediaUrl(url: string): string { const raw = String(url || "").trim(); if (!raw) return ""; @@ -6015,7 +6024,18 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF {model.avatarUrl ? ( - {model.displayName} + {model.displayName} { + if (event.currentTarget.src !== model.avatarUrl) { + event.currentTarget.src = model.avatarUrl || ""; + } + }} + /> ) : ( {model.id === "auto" ? "A" : model.displayName.slice(0, 1)} @@ -6370,9 +6390,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF className="inline-flex items-center gap-1 rounded-xl bg-white/90 px-2 py-1 text-xs" > { + if (event.currentTarget.src !== fileUrl) { + event.currentTarget.src = fileUrl; + } + }} /> {part.name || 'Image'} @@ -6403,9 +6430,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF className="inline-flex items-center gap-1 rounded-xl bg-white/90 px-2 py-1 text-xs" > { + if (event.currentTarget.src !== att.url) { + event.currentTarget.src = att.url; + } + }} /> {att.name || 'Image'} @@ -6580,10 +6614,17 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF )} > { + if (event.currentTarget.src !== url) { + event.currentTarget.src = url; + } + }} onClick={() => setFullscreenPreview({ url })} onDoubleClick={(e) => { e.preventDefault(); @@ -7218,9 +7259,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF onClick={() => detailSlotInputRefs.current[index]?.click()} > {config.label} { + if (event.currentTarget.src !== slotAttachment.url) { + event.currentTarget.src = slotAttachment.url; + } + }} />
@@ -7591,9 +7639,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF {slotAttachment ? ( <> {slotAttachment.name { + if (event.currentTarget.src !== slotAttachment.url) { + event.currentTarget.src = slotAttachment.url; + } + }} />
{label} @@ -7782,9 +7837,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF > {agentAvatarUrl ? ( {agentDisplayName} { + if (event.currentTarget.src !== agentAvatarUrl) { + event.currentTarget.src = agentAvatarUrl; + } + }} /> ) : ( @@ -7825,9 +7887,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF {selectDisplayModel?.avatarUrl ? ( {selectDisplayModel.displayName { + if (event.currentTarget.src !== selectDisplayModel.avatarUrl) { + event.currentTarget.src = selectDisplayModel.avatarUrl || ""; + } + }} /> ) : ( @@ -7931,9 +8000,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
{tagPreview.name} { + if (event.currentTarget.src !== tagPreview.url) { + event.currentTarget.src = tagPreview.url; + } + }} /> {/* 如果是物品标签,显示标注位置 */} {tagPreview.pickedObject && ( diff --git a/components/canvas/CanvasFileList.tsx b/components/canvas/CanvasFileList.tsx index 6de22cb16..1201159d9 100644 --- a/components/canvas/CanvasFileList.tsx +++ b/components/canvas/CanvasFileList.tsx @@ -17,6 +17,7 @@ import { import { canvasService } from "@/services/canvas"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; +import { buildImageProxyUrl } from "@/lib/image-delivery"; import type { CanvasProject } from "@/types/canvas"; interface CanvasFileListProps { @@ -139,9 +140,20 @@ export function CanvasFileList({ open, onOpenChange, currentId }: CanvasFileList
{project.thumbnail ? ( {project.title} { + if (event.currentTarget.src !== project.thumbnail) { + event.currentTarget.src = project.thumbnail || ""; + } + }} /> ) : (
diff --git a/components/canvas/CanvasFileListPanel.tsx b/components/canvas/CanvasFileListPanel.tsx index 4c5d06903..4dea7b5dd 100644 --- a/components/canvas/CanvasFileListPanel.tsx +++ b/components/canvas/CanvasFileListPanel.tsx @@ -10,6 +10,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { canvasService } from "@/services/canvas"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; +import { buildImageProxyUrl } from "@/lib/image-delivery"; import type { CanvasProject } from "@/types/canvas"; interface CanvasFileListPanelProps { @@ -138,9 +139,20 @@ export function CanvasFileListPanel({ currentId, onClose }: CanvasFileListPanelP
{project.thumbnail ? ( {project.title} { + if (event.currentTarget.src !== project.thumbnail) { + event.currentTarget.src = project.thumbnail || ""; + } + }} /> ) : (
diff --git a/components/image-preview.tsx b/components/image-preview.tsx index a851edd80..e5afaa5b6 100644 --- a/components/image-preview.tsx +++ b/components/image-preview.tsx @@ -5,6 +5,7 @@ import { X, Download, Edit, Target, Paintbrush, Sparkles, Scissors, Expand, Maxi import { toast } from "sonner"; import { ImageAnnotator } from "./image-annotator"; import { AngleEditDialog } from "./angle-edit-dialog"; +import { buildImageProxyUrl } from "@/lib/image-delivery"; export function ImagePreview({ src, @@ -174,11 +175,12 @@ export function ImagePreview({ }; const { thumbnail, original, maxWidth, is4K } = parseImageUrls(); - const preferOriginalForPreview = Boolean(original && original !== thumbnail); - const previewSrc = preferOriginalForPreview ? original : thumbnail; - const previewSrcSet = original - ? `${thumbnail} 1x, ${original} 2x` - : undefined; + const previewWidth = Math.min(2200, Math.max(640, Math.round(maxWidth * 2))); + const previewSrc = buildImageProxyUrl(thumbnail || original, { + width: previewWidth, + quality: 86, + format: "webp", + }); // 下载图片函数(下载原图) const handleDownload = async () => { @@ -252,7 +254,6 @@ export function ImagePreview({
Generated Image { + if (event.currentTarget.src !== (thumbnail || original)) { + event.currentTarget.src = thumbnail || original; + } + }} onClick={() => setIsOpen(true)} />