import type { CanvasAgentContext, CanvasAgentNodeRef, CanvasViewOperationPlan, } from "@/types/canvas/agent-protocol"; function normalizeNodeIds(values: Array, maxItems = 20) { return Array.from( new Set(values.map((value) => String(value || "").trim()).filter(Boolean)) ).slice(0, maxItems); } function getNodeSourceIds(node?: CanvasAgentNodeRef | null) { const metadata = node?.metadata || {}; const sourceIds = Array.isArray(metadata.sourceNodeIds) ? metadata.sourceNodeIds .map((value) => String(value || "").trim()) .filter(Boolean) : []; return normalizeNodeIds([ typeof metadata.revisionOf === "string" ? metadata.revisionOf : undefined, typeof metadata.sourceNodeId === "string" ? metadata.sourceNodeId : undefined, ...sourceIds, ]); } function isAgentResultNode(node?: CanvasAgentNodeRef | null) { const metadata = node?.metadata || {}; return ( metadata.generatedByAgent === true || typeof metadata.canvasAgentOperationId === "string" || typeof metadata.canvasAgentMediaItemId === "string" || getNodeSourceIds(node).length > 0 ); } export function hasCanvasAgentComparisonViewIntent(userText: string) { return /对比|比较|一起看|同时看|原图.*新图|新图.*原图/.test( userText.toLowerCase() ); } export function hasCanvasAgentLatestResultViewIntent(userText: string) { const text = userText.trim().toLowerCase(); if (!text) return false; const hasLatestResultTarget = /刚生成|刚才生成|最新(?:的)?(?:结果|图片|图|生成)|最近(?:的)?(?:结果|图片|图|生成)|上次生成|最后生成|刚出(?:的)?(?:图|结果)|新结果|新图/.test( text ); if (!hasLatestResultTarget) return false; const hasViewVerb = /聚焦|定位|居中|找到|跳到|看(?:看|一下|清楚)?|放大|拉近|focus|locate|zoom/.test( text ); if (!hasViewVerb) return false; return !/生成(?:一|个|张|幅|段)|出图|做一版|再出|换|改|编辑|抠图|去水印|高清|清晰|扩图|裁剪|删除|替换|背景|视频|模型|洗图|重绘|尺寸|像素|分辨率/.test( text ); } export function resolveCanvasAgentComparisonNodeIds(params: { context: CanvasAgentContext; maxItems?: number; }) { const maxItems = Math.max(2, Math.min(20, params.maxItems || 10)); const allNodes = [...params.context.selectedNodes, ...params.context.candidateNodes]; const nodeMap = new Map(allNodes.map((node) => [node.nodeId, node])); const selectedNodeIds = normalizeNodeIds( [ ...params.context.selectedNodeIds, ...params.context.selectedNodes .filter((node) => node.isSelected) .map((node) => node.nodeId), ], maxItems ).filter((nodeId) => nodeMap.has(nodeId)); if (selectedNodeIds.length >= 2) return selectedNodeIds.slice(0, maxItems); const selectedNode = selectedNodeIds[0] ? nodeMap.get(selectedNodeIds[0]) : null; const selectedSourceIds = getNodeSourceIds(selectedNode).filter((nodeId) => nodeMap.has(nodeId) ); if (selectedNode?.nodeId && selectedSourceIds.length > 0) { return normalizeNodeIds([...selectedSourceIds, selectedNode.nodeId], maxItems); } if (selectedNode?.nodeId) { const relatedResult = allNodes.find( (node) => node.nodeId !== selectedNode.nodeId && isAgentResultNode(node) && getNodeSourceIds(node).includes(selectedNode.nodeId) ); if (relatedResult) { return normalizeNodeIds([selectedNode.nodeId, relatedResult.nodeId], maxItems); } } const latestResultWithSource = allNodes.find((node) => { if (!isAgentResultNode(node)) return false; return getNodeSourceIds(node).some((nodeId) => nodeMap.has(nodeId)); }); if (latestResultWithSource) { return normalizeNodeIds( [...getNodeSourceIds(latestResultWithSource), latestResultWithSource.nodeId], maxItems ).filter((nodeId) => nodeMap.has(nodeId)); } return []; } export function buildCanvasAgentComparisonViewPlan(params: { context: CanvasAgentContext; projectId: string; operationId: string; reason?: string; }): CanvasViewOperationPlan | null { const nodeIds = resolveCanvasAgentComparisonNodeIds({ context: params.context, maxItems: 10, }); if (nodeIds.length < 2) return null; return { version: 1, viewOperationId: `${params.operationId}:view`, operation: "focus_nodes", projectId: params.projectId, nodeIds, anchorNodeId: nodeIds[0], padding: 140, persist: false, reason: params.reason || "focus_canvas_comparison_targets", }; } export function buildCanvasAgentLatestResultViewPlan(params: { userText: string; projectId: string; operationId: string; reason?: string; }): CanvasViewOperationPlan | null { const projectId = String(params.projectId || "").trim(); const operationId = String(params.operationId || "").trim(); if (!projectId || !operationId) return null; if (!hasCanvasAgentLatestResultViewIntent(params.userText)) return null; return { version: 1, viewOperationId: `${operationId}:view`, operation: "focus_latest_result", projectId, padding: 120, durationMs: 360, persist: false, reason: params.reason || "focus_latest_canvas_agent_result", }; }