diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index ecb3d380e..2b3adb440 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -286,9 +286,24 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF }, [historyData, setMessages]); // 监听消息变化,提取图片并添加到画布 + // 只处理新生成的图片,不处理历史消息中的图片 const processedMessageIds = useRef>(new Set()); + const isInitialLoad = useRef(true); useEffect(() => { + // 首次加载历史消息时,只记录已处理的消息 ID,不添加图片到画布 + // (画布数据应该从 canvasData 中加载,而不是从消息中提取) + if (isInitialLoad.current) { + messages.forEach(message => { + if (message.role === 'assistant') { + processedMessageIds.current.add(message.id); + } + }); + isInitialLoad.current = false; + return; + } + + // 只处理新消息 messages.forEach(message => { if (message.role === 'assistant' && !processedMessageIds.current.has(message.id)) { const content = message.parts?.map((p: any) => p.text || '').join('') || ''; @@ -297,8 +312,16 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF if (imageUrls.length > 0) { processedMessageIds.current.add(message.id); - // 为每张图片创建画布节点 - imageUrls.forEach((url, index) => { + // 检查画布中是否已存在相同 URL 的图片 + const existingUrls = new Set( + nodes.filter(n => n.type === 'image' && (n.data as any)?.src) + .map(n => (n.data as any).src) + ); + + // 只添加不存在的图片 + const newUrls = imageUrls.filter(url => !existingUrls.has(url)); + + newUrls.forEach((url, index) => { // 计算位置(避免重叠) const existingNodes = nodes.length; const x = 100 + ((existingNodes + index) % 4) * 320; @@ -317,13 +340,13 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF }); }); - if (imageUrls.length > 0) { - toast.success(`已将 ${imageUrls.length} 张图片添加到画布`); + if (newUrls.length > 0) { + toast.success(`已将 ${newUrls.length} 张图片添加到画布`); } } } }); - }, [messages, addNode, nodes.length]); + }, [messages, addNode, nodes]); // 滚动到底部 useEffect(() => { diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx index 71da15e41..c595f8e09 100644 --- a/components/canvas/nodes/ImageNode.tsx +++ b/components/canvas/nodes/ImageNode.tsx @@ -43,7 +43,6 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { const [resizeWidth, setResizeWidth] = useState(1024); const [resizeHeight, setResizeHeight] = useState(1024); const [isUploading, setIsUploading] = useState(false); - const [toolbarPosition, setToolbarPosition] = useState<{ top: number; left: number } | null>(null); // 文件输入的 ref const fileInputRef = useRef(null); @@ -53,18 +52,17 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { const deleteNode = useCanvasStore((state) => state.deleteNode); const addNode = useCanvasStore((state) => state.addNode); - // 计算工具栏位置 - useEffect(() => { - if (isSelected && containerRef.current) { + // 获取工具栏位置 - 在渲染时直接计算 + const getToolbarPosition = () => { + if (containerRef.current) { const rect = containerRef.current.getBoundingClientRect(); - setToolbarPosition({ - top: rect.top - 50, // 工具栏在图片上方 50px - left: rect.left + rect.width / 2, // 居中 - }); - } else { - setToolbarPosition(null); + return { + top: rect.top - 50, + left: rect.left + rect.width / 2, + }; } - }, [isSelected, node.position, node.size]); + return { top: 0, left: 0 }; + }; // 触发文件选择 const handleSelectFile = (e: React.MouseEvent) => { @@ -536,12 +534,12 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) { {/* 工具栏使用 Portal 渲染到 body,避免被裁剪 */} - {isSelected && data.src && !isProcessing && toolbarPosition && typeof document !== 'undefined' && createPortal( + {isSelected && data.src && !isProcessing && typeof document !== 'undefined' && createPortal(