ai-chatbot/lib/canvas/agent-view-intent.ts

164 lines
5.2 KiB
TypeScript

import type {
CanvasAgentContext,
CanvasAgentNodeRef,
CanvasViewOperationPlan,
} from "@/types/canvas/agent-protocol";
function normalizeNodeIds(values: Array<string | null | undefined>, 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",
};
}