From 23c326b1fb27e8521f13cf4616936a706d688673 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 25 Mar 2026 15:35:04 +0800 Subject: [PATCH] fix: reduce infinite canvas rate limit bursts --- app/canvas/[id]/page.tsx | 72 ++++++++++++++++++--------- components/canvas/CanvasChatPanel.tsx | 8 ++- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index bcc896437..c0495e780 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -393,6 +393,9 @@ export default function CanvasEditorPage({ params }: PageProps) { const previousNodeIdsRef = useRef>(new Set()); const previousBrandForModelRef = useRef(""); const autoSelectedBrandModelBrandRef = useRef(""); + const autoTitleAttemptedRef = useRef>(new Set()); + const autoTitleInFlightRef = useRef(null); + const autoThumbnailSyncedRef = useRef(null); const lastLoadedCanvasIdRef = useRef(""); const requestedAgentMode = mode === "agent"; const isAgentWorkspaceEnabled = siteSettingsData?.agentEnabled !== false; @@ -1325,31 +1328,52 @@ export default function CanvasEditorPage({ params }: PageProps) { // 智能命名与缩略图逻辑 useEffect(() => { if (isAgentMode) return; + const hasDefaultTitle = + projectTitle === "未命名画布" || projectTitle === "新画布"; + if (!hasDefaultTitle || nodes.length === 0) return; + + const imageNode = nodes.find((n) => n.type === "image" && (n.data as any)?.src); + const imageSrc = (imageNode?.data as any)?.src; + if (!imageSrc || typeof imageSrc !== "string") return; + + const autoTitleKey = `${id}:${imageSrc}`; + if ( + autoTitleAttemptedRef.current.has(autoTitleKey) || + autoTitleInFlightRef.current === autoTitleKey + ) { + return; + } + const autoTitleAndThumbnail = async () => { - // 如果标题是默认值,且有图片节点 - if ((projectTitle === "未命名画布" || projectTitle === "新画布") && nodes.length > 0) { - const imageNode = nodes.find(n => n.type === 'image' && (n.data as any)?.src); - const imageSrc = (imageNode?.data as any)?.src; - if (imageSrc) { - try { - // 1. 设置缩略图 - await canvasService.updateProject(id, { thumbnail: imageSrc }); - - // 2. 智能命名 - const res = await fetch('/api/canvas/auto-title', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ imageUrl: imageSrc }) - }); - const data = await res.json(); - if (data.title) { - setProjectTitle(data.title); - await canvasService.updateProject(id, { title: data.title }); - toast.success(`画布已自动命名为:${data.title}`); - } - } catch (error) { - console.error("Auto title failed:", error); - } + autoTitleAttemptedRef.current.add(autoTitleKey); + autoTitleInFlightRef.current = autoTitleKey; + + try { + if (autoThumbnailSyncedRef.current !== autoTitleKey) { + await canvasService.updateProject(id, { thumbnail: imageSrc }); + autoThumbnailSyncedRef.current = autoTitleKey; + } + + const res = await fetch("/api/canvas/auto-title", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ imageUrl: imageSrc }), + }); + if (!res.ok) { + throw new Error(`Auto title request failed: ${res.status}`); + } + + const data = await res.json(); + if (data.title) { + setProjectTitle(data.title); + await canvasService.updateProject(id, { title: data.title }); + toast.success(`画布已自动命名为:${data.title}`); + } + } catch (error) { + console.error("Auto title failed:", error); + } finally { + if (autoTitleInFlightRef.current === autoTitleKey) { + autoTitleInFlightRef.current = null; } } }; diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 39640fb9c..0a8878907 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -2203,7 +2203,6 @@ export function CanvasChatPanel({ // 获取纯 UUID(去掉 canvas- 前缀)用于 API 调用 const rawChatId = toRawCanvasChatId(activeChatId); - const voteSWRKey = rawChatId ? `/api/vote?chatId=${rawChatId}` : null; const { mutate } = useSWRConfig(); const [votingMessageId, setVotingMessageId] = useState(null); const handleDownloadOriginalImage = useCallback(async (imageUrl: string) => { @@ -2249,6 +2248,13 @@ export function CanvasChatPanel({ revalidateOnReconnect: false, dedupingInterval: 10000, }); + const initialCanvasMessageCount = + activeChatId === chatId ? initialMessages.length : 0; + const voteSWRKey = + rawChatId && + (historyData?.messages?.length ?? initialCanvasMessageCount) >= 2 + ? `/api/vote?chatId=${rawChatId}` + : null; const { data: messageVotes } = useSWR( voteSWRKey, fetcher,