fix: 修复画布图片重复添加和工具栏位置问题
1. 首次加载时不将历史消息中的图片添加到画布(避免重复) 2. 检查画布中是否已存在相同 URL 的图片 3. 工具栏位置在渲染时实时计算(跟随画布平移缩放) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
dbdd188c9e
commit
2524ec551b
@ -286,9 +286,24 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
}, [historyData, setMessages]);
|
||||
|
||||
// 监听消息变化,提取图片并添加到画布
|
||||
// 只处理新生成的图片,不处理历史消息中的图片
|
||||
const processedMessageIds = useRef<Set<string>>(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(() => {
|
||||
|
||||
@ -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<HTMLInputElement>(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) {
|
||||
</Dialog>
|
||||
|
||||
{/* 工具栏使用 Portal 渲染到 body,避免被裁剪 */}
|
||||
{isSelected && data.src && !isProcessing && toolbarPosition && typeof document !== 'undefined' && createPortal(
|
||||
{isSelected && data.src && !isProcessing && typeof document !== 'undefined' && createPortal(
|
||||
<div
|
||||
className="fixed flex items-center gap-0.5 bg-white border border-gray-200 rounded-full shadow-xl px-3 py-1.5 z-[9999]"
|
||||
style={{
|
||||
top: toolbarPosition.top,
|
||||
left: toolbarPosition.left,
|
||||
top: getToolbarPosition().top,
|
||||
left: getToolbarPosition().left,
|
||||
transform: 'translateX(-50%)',
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user