feat: 画布图片添加到对话框改为手动触发
- 在图片工具栏添加"添加到对话"按钮 - 移除选中图片时自动添加标签的逻辑 - 通过 canvas store 的 pendingChatAttachments 实现跨组件通信 - 用户点击按钮后才会将图片添加到对话框,避免标签越来越多
This commit is contained in:
parent
ba99ad212e
commit
52867404f7
@ -108,6 +108,8 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
const nodes = useCanvasStore((state) => state.nodes);
|
const nodes = useCanvasStore((state) => state.nodes);
|
||||||
const selectedNodeIds = useCanvasStore((state) => state.selectedNodeIds);
|
const selectedNodeIds = useCanvasStore((state) => state.selectedNodeIds);
|
||||||
const clearSelection = useCanvasStore((state) => state.clearSelection);
|
const clearSelection = useCanvasStore((state) => state.clearSelection);
|
||||||
|
const pendingChatAttachments = useCanvasStore((state) => state.pendingChatAttachments);
|
||||||
|
const clearPendingChatAttachments = useCanvasStore((state) => state.clearPendingChatAttachments);
|
||||||
|
|
||||||
// 获取纯 UUID(去掉 canvas- 前缀)用于 API 调用
|
// 获取纯 UUID(去掉 canvas- 前缀)用于 API 调用
|
||||||
const rawChatId = chatId.startsWith('canvas-')
|
const rawChatId = chatId.startsWith('canvas-')
|
||||||
@ -159,33 +161,30 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 当选中画布中的图片时,自动添加到附件并插入标签
|
// 🆕 监听 pendingChatAttachments,当用户点击"添加到对话"按钮时添加标签
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedImageNodes.length > 0) {
|
if (pendingChatAttachments.length > 0) {
|
||||||
const newAttachments: Attachment[] = selectedImageNodes.map(node => {
|
|
||||||
const data = node.data as any;
|
|
||||||
return {
|
|
||||||
url: data.src,
|
|
||||||
name: data.prompt || '画布图片',
|
|
||||||
contentType: 'image/jpeg',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 只添加还没在附件中的图片
|
// 只添加还没在附件中的图片
|
||||||
setAttachments(prev => {
|
setAttachments(prev => {
|
||||||
const existingUrls = new Set(prev.map(a => a.url));
|
const existingUrls = new Set(prev.map(a => a.url));
|
||||||
const toAdd = newAttachments.filter(a => !existingUrls.has(a.url));
|
const toAdd = pendingChatAttachments.filter(a => !existingUrls.has(a.url));
|
||||||
if (toAdd.length > 0) {
|
if (toAdd.length > 0) {
|
||||||
// 插入图片标签到编辑器
|
// 插入图片标签到编辑器
|
||||||
toAdd.forEach((att, i) => {
|
toAdd.forEach((att, i) => {
|
||||||
insertImageTagInEditor(att.url, att.name, prev.length + i);
|
insertImageTagInEditor(att.url, att.name, prev.length + i);
|
||||||
});
|
});
|
||||||
|
// 聚焦到输入框
|
||||||
|
setTimeout(() => {
|
||||||
|
(textareaRef.current as HTMLDivElement | null)?.focus();
|
||||||
|
}, 0);
|
||||||
return [...prev, ...toAdd];
|
return [...prev, ...toAdd];
|
||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
});
|
});
|
||||||
|
// 清空待添加列表
|
||||||
|
clearPendingChatAttachments();
|
||||||
}
|
}
|
||||||
}, [selectedImageNodes.length, selectedNodeIds, insertImageTagInEditor]);
|
}, [pendingChatAttachments, insertImageTagInEditor, clearPendingChatAttachments]);
|
||||||
|
|
||||||
// 获取模型列表
|
// 获取模型列表
|
||||||
const { data: modelsData } = useSWR<{
|
const { data: modelsData } = useSWR<{
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { ImagePlus, Loader2, AlertCircle, MoreVertical, Scissors, Sparkles, Expand, Maximize2, Move3D, Layers, Download, Trash2, RefreshCw, Pencil } from "lucide-react";
|
import { ImagePlus, Loader2, AlertCircle, MoreVertical, Scissors, Sparkles, Expand, Maximize2, Move3D, Layers, Download, Trash2, RefreshCw, Pencil, MessageSquarePlus } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -53,6 +53,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
const updateNode = useCanvasStore((state) => state.updateNode);
|
const updateNode = useCanvasStore((state) => state.updateNode);
|
||||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||||
const addNode = useCanvasStore((state) => state.addNode);
|
const addNode = useCanvasStore((state) => state.addNode);
|
||||||
|
const addPendingChatAttachment = useCanvasStore((state) => state.addPendingChatAttachment);
|
||||||
|
|
||||||
// 获取工具栏位置 - 在渲染时直接计算
|
// 获取工具栏位置 - 在渲染时直接计算
|
||||||
const getToolbarPosition = () => {
|
const getToolbarPosition = () => {
|
||||||
@ -572,6 +573,28 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
|
{/* 🆕 添加到对话 */}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-9 px-3 text-xs gap-1.5 rounded-full hover:bg-blue-50 text-blue-600 hover:text-blue-700"
|
||||||
|
onClick={() => {
|
||||||
|
addPendingChatAttachment({
|
||||||
|
url: data.src!,
|
||||||
|
name: data.prompt || '画布图片',
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
});
|
||||||
|
toast.success('已添加到对话框');
|
||||||
|
}}
|
||||||
|
title="添加到对话框"
|
||||||
|
>
|
||||||
|
<MessageSquarePlus className="h-4 w-4" />
|
||||||
|
<span>对话</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 分隔线 */}
|
||||||
|
<div className="h-5 w-px bg-gray-200 mx-1" />
|
||||||
|
|
||||||
{/* 放大/高清 */}
|
{/* 放大/高清 */}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@ -22,6 +22,13 @@ const defaultViewport: Viewport = {
|
|||||||
zoom: 1,
|
zoom: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 待添加到对话框的图片附件
|
||||||
|
interface PendingChatAttachment {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
contentType: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Store 状态接口
|
// Store 状态接口
|
||||||
interface CanvasStore {
|
interface CanvasStore {
|
||||||
// 项目信息
|
// 项目信息
|
||||||
@ -43,6 +50,9 @@ interface CanvasStore {
|
|||||||
isPanelOpen: boolean;
|
isPanelOpen: boolean;
|
||||||
isGenerating: boolean;
|
isGenerating: boolean;
|
||||||
|
|
||||||
|
// 🆕 待添加到对话框的图片(用于从画布添加图片到对话)
|
||||||
|
pendingChatAttachments: PendingChatAttachment[];
|
||||||
|
|
||||||
// 项目操作
|
// 项目操作
|
||||||
setProject: (id: string | null, title?: string) => void;
|
setProject: (id: string | null, title?: string) => void;
|
||||||
setProjectTitle: (title: string) => void;
|
setProjectTitle: (title: string) => void;
|
||||||
@ -96,6 +106,10 @@ interface CanvasStore {
|
|||||||
getNode: (id: string) => CanvasNode | undefined;
|
getNode: (id: string) => CanvasNode | undefined;
|
||||||
getSelectedNodes: () => CanvasNode[];
|
getSelectedNodes: () => CanvasNode[];
|
||||||
getNodesByType: (type: NodeType) => CanvasNode[];
|
getNodesByType: (type: NodeType) => CanvasNode[];
|
||||||
|
|
||||||
|
// 🆕 对话框附件操作
|
||||||
|
addPendingChatAttachment: (attachment: PendingChatAttachment) => void;
|
||||||
|
clearPendingChatAttachments: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCanvasStore = create<CanvasStore>()(
|
export const useCanvasStore = create<CanvasStore>()(
|
||||||
@ -114,6 +128,7 @@ export const useCanvasStore = create<CanvasStore>()(
|
|||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
isPanelOpen: true,
|
isPanelOpen: true,
|
||||||
isGenerating: false,
|
isGenerating: false,
|
||||||
|
pendingChatAttachments: [], // 🆕 待添加到对话框的图片
|
||||||
|
|
||||||
// 项目操作
|
// 项目操作
|
||||||
setProject: (id, title) => set((state) => {
|
setProject: (id, title) => set((state) => {
|
||||||
@ -369,6 +384,18 @@ export const useCanvasStore = create<CanvasStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
getNodesByType: (type) => get().nodes.filter(n => n.type === type),
|
getNodesByType: (type) => get().nodes.filter(n => n.type === type),
|
||||||
|
|
||||||
|
// 🆕 对话框附件操作
|
||||||
|
addPendingChatAttachment: (attachment) => set((state) => {
|
||||||
|
// 避免重复添加
|
||||||
|
if (!state.pendingChatAttachments.some(a => a.url === attachment.url)) {
|
||||||
|
state.pendingChatAttachments.push(attachment);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
clearPendingChatAttachments: () => set((state) => {
|
||||||
|
state.pendingChatAttachments = [];
|
||||||
|
}),
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: 'canvas-storage',
|
name: 'canvas-storage',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user