feat(canvas): 画布节点 - 合并发送到 Agent 快捷入口 (#523)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-17 09:30:13 +08:00
10 changed files with 49 additions and 8 deletions

View File

@@ -42,6 +42,7 @@ type CanvasProjectContextMenuProps = {
onSpreadSelection: () => void;
onCopySelection: () => void;
onDeleteSelection: () => void;
onSendToAgent: () => void;
};
export function CanvasProjectContextMenu({ menu, node, screenToCanvas, ...props }: CanvasProjectContextMenuProps) {
@@ -114,6 +115,7 @@ export function CanvasProjectContextMenu({ menu, node, screenToCanvas, ...props
onSpreadSelection={props.onSpreadSelection}
onCopySelection={props.onCopySelection}
onDeleteSelection={props.onDeleteSelection}
onSendToAgent={props.onSendToAgent}
/>
);
}

View File

@@ -20,13 +20,14 @@ type CanvasProjectSelectionToolbarProps = {
onCreateReferenceGroup: () => void;
onBatchConnect: () => void;
onMergeVideos: () => void;
onSendSelectionToAgent: () => void;
};
export function CanvasProjectSelectionToolbar({ anchorRef, containerRef, count, selectedVideoCount, mergingVideos, onAlign, onArrange, onCreateStoryboard, onCreateReferenceGroup, onBatchConnect, onMergeVideos }: CanvasProjectSelectionToolbarProps) {
export function CanvasProjectSelectionToolbar({ anchorRef, containerRef, count, selectedVideoCount, mergingVideos, onAlign, onArrange, onCreateStoryboard, onCreateReferenceGroup, onBatchConnect, onMergeVideos, onSendSelectionToAgent }: CanvasProjectSelectionToolbarProps) {
const theme = canvasThemes[useCanvasThemeStore((state) => state.theme)];
const handlers = {
onAlign, onArrange, onCreateStoryboard, onCreateReferenceGroup, onBatchConnect, onMergeVideos,
onAlign, onArrange, onCreateStoryboard, onCreateReferenceGroup, onBatchConnect, onMergeVideos, onSendSelectionToAgent,
} as Partial<ToolbarHandlers> as ToolbarHandlers;
const ctx: ToolContext = {

View File

@@ -77,6 +77,7 @@ import { writeCanvasNodePrompt } from "@/lib/canvas/canvas-node-prompt";
import {
applyCanvasConnectionPromptSync,
buildCanvasAgentMentionReferences,
canvasResourceMentionToken,
buildCanvasNodeMentionReferenceMap,
buildCanvasResourceReferences,
getContextResourceNodes,
@@ -292,6 +293,7 @@ function InfiniteCanvasPage() {
const [selectedConnectionId, setSelectedConnectionId] = useState<string | null>(null);
const [hoveredNodeId, setHoveredNodeId] = useState<string | null>(null);
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
const [agentPrefillPrompt, setAgentPrefillPrompt] = useState("");
const [isMiniMapOpen, setIsMiniMapOpen] = useState(false);
const [canvasAppearance, setCanvasAppearance] = useState<CanvasAppearance>(() => canvasAppearanceForTheme(colorTheme));
const [backgroundMode, setBackgroundMode] = useState<CanvasBackgroundMode>(DEFAULT_CANVAS_BACKGROUND_MODE);
@@ -333,6 +335,15 @@ function InfiniteCanvasPage() {
const [cinematicAgentEntry, setCinematicAgentEntry] = useState(false);
const { assistantOpen, closeAgent, openAgent } = useCanvasAssistantVisibility();
const agentMentionReferences = useMemo(() => buildCanvasAgentMentionReferences(nodes), [nodes]);
const sendSelectionToAgent = useCallback((nodeId?: string) => {
const ids = nodeId ? [nodeId] : Array.from(selectedNodeIdsRef.current);
const references = ids.map((id) => agentMentionReferences.find((reference) => reference.nodeId === id)).filter((reference): reference is CanvasResourceReference => Boolean(reference));
if (!references.length) return;
setAgentPrefillPrompt(`${references.map(canvasResourceMentionToken).join(" ")} `);
openAgent();
setContextMenu(null);
}, [agentMentionReferences, openAgent]);
const { tasks: activeTasks } = useCanvasActiveTasks(projectId, projectLoaded);
const { focusMode, enterFocusMode, exitFocusMode, toggleFocusMode } = useFocusMode();
const [focusDockRevealed, setFocusDockRevealed] = useState(false);
@@ -2582,7 +2593,7 @@ function InfiniteCanvasPage() {
) : null}
</div>
<CanvasCloudAgentPanel canvasId={projectId} domainProjectId={currentProject?.projectId} nodeCount={nodes.length} references={agentMentionReferences} open={assistantOpen} onOpen={openAgent} onCollapse={closeAgent} onFocusNode={(nodeId) => {
<CanvasCloudAgentPanel canvasId={projectId} domainProjectId={currentProject?.projectId} nodeCount={nodes.length} references={agentMentionReferences} prefillPrompt={agentPrefillPrompt} open={assistantOpen} onOpen={openAgent} onCollapse={closeAgent} onFocusNode={(nodeId) => {
if (!nodesRef.current.some((node) => node.id === nodeId)) { message.info("该节点已删除或尚未同步到画布"); return; }
focusCanvasNode(nodeId);
}} />
@@ -2697,6 +2708,7 @@ function InfiniteCanvasPage() {
onCreateReferenceGroup={createReferenceGroup}
onBatchConnect={() => beginBatchConnectionMode(Array.from(selectedNodeIds))}
onMergeVideos={() => void mergeSelectedVideos()}
onSendSelectionToAgent={() => sendSelectionToAgent()}
/>
) : null}
@@ -2843,6 +2855,7 @@ function InfiniteCanvasPage() {
onSpreadSelection={spreadSelectedNodes}
onCopySelection={copySelectedNodes}
onDeleteSelection={() => deleteNodes(selectedNodeIds)}
onSendToAgent={() => sendSelectionToAgent(contextMenu?.type === "node" && selectedNodeIds.size <= 1 ? contextMenu.nodeId : undefined)}
/>
<CanvasUploadModal open={uploadModalOpen} onClose={closeUploadModal} onUpload={handleUploadFiles} />