feat: route canvas agent link operations
This commit is contained in:
parent
f2ed508cb4
commit
3f8219f415
@ -11,6 +11,7 @@ type ExplicitOperationKind =
|
||||
| "add_annotation"
|
||||
| "create_agent_slot"
|
||||
| "create_text_node"
|
||||
| "link_nodes"
|
||||
| "rename_node"
|
||||
| "update_text"
|
||||
| "update_note";
|
||||
@ -254,6 +255,38 @@ function buildUpdateTextOperation(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function buildLinkNodesOperation(params: {
|
||||
context: CanvasAgentContext;
|
||||
projectId: string;
|
||||
messageId: string;
|
||||
chatId: string;
|
||||
userText: string;
|
||||
requestedNodeIds?: string[];
|
||||
}): CanvasAgentExplicitOperationPlan | null {
|
||||
const text = params.userText.trim();
|
||||
const hasLinkIntent = /连起来|连线|连接|关联|参考关系|版本关系|建立关系/.test(text);
|
||||
if (!hasLinkIntent || hasMediaMutationIntent(text)) return null;
|
||||
|
||||
const targetNodeIds = getTargetNodeIds({
|
||||
context: params.context,
|
||||
requestedNodeIds: params.requestedNodeIds,
|
||||
}).slice(0, 20);
|
||||
if (targetNodeIds.length < 2) return null;
|
||||
|
||||
return {
|
||||
operationPlan: {
|
||||
...buildPlanBase({ ...params, operation: "link_nodes" }),
|
||||
sourceNodeIds: targetNodeIds,
|
||||
edgeType: "reference",
|
||||
},
|
||||
reply: "已为选中的画布节点建立参考连线。",
|
||||
progressTitle: "正在连接画布节点",
|
||||
progressMessage: "我会给选中的画布节点建立参考关系,不修改节点内容。",
|
||||
completedTitle: "画布节点已连接",
|
||||
failedTitle: "画布节点连接失败",
|
||||
};
|
||||
}
|
||||
|
||||
function getSlotTypeLabel(slotType: "image" | "video" | "ui-screen") {
|
||||
if (slotType === "video") return "视频";
|
||||
if (slotType === "ui-screen") return "UI";
|
||||
@ -463,6 +496,7 @@ export function buildCanvasAgentExplicitOperationPlan(params: {
|
||||
buildCreateAgentSlotOperation(commonParams) ||
|
||||
buildCreateTextOperation(commonParams) ||
|
||||
buildUpdateTextOperation(commonParams) ||
|
||||
buildLinkNodesOperation(commonParams) ||
|
||||
buildRenameOperation(commonParams) ||
|
||||
buildUpdateNoteOperation(commonParams) ||
|
||||
buildAddAnnotationOperation(commonParams)
|
||||
|
||||
@ -818,6 +818,60 @@ test.describe("canvas agent operation mutation contract", () => {
|
||||
expect(retryText.data.metadata.previousContent).toBe("旧文案");
|
||||
});
|
||||
|
||||
test("links selected canvas nodes without duplicating existing edges", () => {
|
||||
const source = buildImageNode("image-link-source");
|
||||
const target = buildTextNode("text-link-target");
|
||||
const plan: CanvasOperationPlan = {
|
||||
version: 1,
|
||||
operationId: "op-link-nodes",
|
||||
operation: "link_nodes",
|
||||
projectId,
|
||||
sourceNodeIds: ["image-link-source", "text-link-target"],
|
||||
edgeType: "reference",
|
||||
metadata: {
|
||||
messageId: "assistant-message-link",
|
||||
chatId: "chat-1",
|
||||
},
|
||||
};
|
||||
|
||||
const first = applyCanvasOperationPlanToCanvasData({
|
||||
canvasData: getCanvasData([source, target]),
|
||||
plan,
|
||||
});
|
||||
|
||||
expect(first.createdNodeIds).toEqual([]);
|
||||
expect(first.updatedNodeIds).toEqual([
|
||||
"image-link-source",
|
||||
"text-link-target",
|
||||
]);
|
||||
expect(first.createdEdgeIds).toHaveLength(1);
|
||||
expect(getResultEdges(first.canvasData)).toEqual([
|
||||
expect.objectContaining({
|
||||
source: "image-link-source",
|
||||
target: "text-link-target",
|
||||
type: "reference",
|
||||
}),
|
||||
]);
|
||||
expect(getResultNodes(first.canvasData)).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "image-link-source",
|
||||
connections: ["text-link-target"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "text-link-target",
|
||||
connections: ["image-link-source"],
|
||||
}),
|
||||
]);
|
||||
|
||||
const retry = applyCanvasOperationPlanToCanvasData({
|
||||
canvasData: first.canvasData,
|
||||
plan,
|
||||
});
|
||||
|
||||
expect(retry.createdEdgeIds).toEqual([]);
|
||||
expect(getResultEdges(retry.canvasData)).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("fills an agent slot and returns media item node mappings", () => {
|
||||
const slot = buildAgentSlotNode();
|
||||
const result = applyCanvasOperationPlanToCanvasData({
|
||||
|
||||
@ -1455,6 +1455,29 @@ test.describe("canvas chat pure helpers", () => {
|
||||
expect(outcome?.reply).toContain("文本");
|
||||
});
|
||||
|
||||
test("builds deterministic link plans for selected canvas nodes", () => {
|
||||
const outcome = buildCanvasAgentExplicitOperationPlan({
|
||||
context: plannerContext,
|
||||
projectId: plannerContext.projectId || "",
|
||||
messageId: "message-1",
|
||||
chatId: "chat-1",
|
||||
userText: "把选中的两个节点连成参考关系",
|
||||
});
|
||||
|
||||
expect(outcome?.operationPlan).toMatchObject({
|
||||
operation: "link_nodes",
|
||||
sourceNodeIds: ["image-1", "text-1"],
|
||||
edgeType: "reference",
|
||||
preserveOriginal: true,
|
||||
metadata: {
|
||||
messageId: "message-1",
|
||||
chatId: "chat-1",
|
||||
planner: "deterministic",
|
||||
},
|
||||
});
|
||||
expect(outcome?.reply).toContain("参考连线");
|
||||
});
|
||||
|
||||
test("builds deterministic agent slot creation plans from explicit canvas prompts", () => {
|
||||
const context: CanvasAgentContext = {
|
||||
...plannerContext,
|
||||
@ -1634,6 +1657,16 @@ test.describe("canvas chat pure helpers", () => {
|
||||
userText: "把图片里的文字改成新品上市",
|
||||
})
|
||||
).toBeNull();
|
||||
|
||||
expect(
|
||||
buildCanvasAgentExplicitOperationPlan({
|
||||
context: plannerContext,
|
||||
projectId: plannerContext.projectId || "",
|
||||
messageId: "message-1",
|
||||
chatId: "chat-1",
|
||||
userText: "生成一张图并和选中图连接",
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("normalizes model-planned annotations to selected media nodes", () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user