From 2b7e738e8400af273baec6d86635ecb44d0c357d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 21 Feb 2026 03:00:42 +0800 Subject: [PATCH] fix(image): avoid showing input reference image in output messages --- components/canvas/CanvasChatPanel.tsx | 3 +++ components/message.tsx | 1 + lib/ai/providers/fal-image.ts | 36 +++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 78e017286..2bfb06ce8 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -122,6 +122,9 @@ function getImageDimensions(url: string): Promise<{ // 从消息中提取图片 URL(只提取原始图片,避免重复) function extractImageUrls(text: string): string[] { + if (!/!\[[^\]]*\]\((https?:\/\/[^\s)]+)\)/i.test(text || "")) { + return []; + } return extractImageUrlsFromText(text); } diff --git a/components/message.tsx b/components/message.tsx index 99e884923..806b044ed 100644 --- a/components/message.tsx +++ b/components/message.tsx @@ -179,6 +179,7 @@ function appendImageDoneSummary(text: string): string { if (!text) return text; const normalized = String(text || "").trim(); if (!normalized) return normalized; + if (!/!\[[^\]]*\]\((https?:\/\/[^\s)]+)\)/i.test(normalized)) return normalized; if (extractImageUrlsFromText(normalized).length === 0) return normalized; if (normalized.includes(IMAGE_DONE_SUMMARY)) return normalized; return `${normalized}\n\n${IMAGE_DONE_SUMMARY}`; diff --git a/lib/ai/providers/fal-image.ts b/lib/ai/providers/fal-image.ts index 05a303e72..3e3200bfd 100644 --- a/lib/ai/providers/fal-image.ts +++ b/lib/ai/providers/fal-image.ts @@ -1,5 +1,6 @@ import "server-only"; +import { createHash } from "node:crypto"; import sharp from "sharp"; import { uploadFile } from "@/lib/storage/service"; @@ -59,6 +60,39 @@ function filterSourceImages(urls: string[], sourceUrls: string[]) { return urls.filter((url) => !sourceSet.has(normalizeUrlForCompare(url))); } +async function getImageContentHash(url: string): Promise { + try { + const response = await fetch(url, { signal: AbortSignal.timeout(15_000) }); + if (!response.ok) return null; + const buffer = Buffer.from(await response.arrayBuffer()); + return createHash("sha1").update(buffer).digest("hex"); + } catch { + return null; + } +} + +async function filterSourceImagesByContent(urls: string[], sourceUrls: string[]) { + if (sourceUrls.length === 0 || urls.length === 0) return urls; + + const sourceHashes = new Set(); + for (const sourceUrl of sourceUrls) { + const hash = await getImageContentHash(sourceUrl); + if (hash) sourceHashes.add(hash); + } + if (sourceHashes.size === 0) return urls; + + const filtered: string[] = []; + for (const url of urls) { + const hash = await getImageContentHash(url); + if (hash && sourceHashes.has(hash)) { + continue; + } + filtered.push(url); + } + + return filtered.length > 0 ? filtered : urls; +} + function normalizeAspectRatio(aspectRatio?: string) { if (!aspectRatio) return DEFAULT_ASPECT_RATIO; const ratio = aspectRatio.trim(); @@ -326,9 +360,11 @@ export function createFalImage({ apiKey, baseURL }: FalImageConfig) { }); let outputUrls = filterSourceImages(collectImageUrls(result), imageUrls); + outputUrls = await filterSourceImagesByContent(outputUrls, imageUrls); if (outputUrls.length === 0 && result?.data) { outputUrls = filterSourceImages(collectImageUrls(result.data), imageUrls); + outputUrls = await filterSourceImagesByContent(outputUrls, imageUrls); } if (outputUrls.length === 0) {