From 763696e8d0b596725f8c089c976c8c2fd1229584 Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 23 Jun 2026 16:14:27 +0800 Subject: [PATCH] fix: trace annotation edits in canvas writeback metadata --- lib/canvas/agent-direct-writeback.ts | 51 ++++++++++++++++++++++-- tests/routes/canvas-chat-helpers.test.ts | 46 +++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/lib/canvas/agent-direct-writeback.ts b/lib/canvas/agent-direct-writeback.ts index ecf61d8a9..e5c02a5fc 100644 --- a/lib/canvas/agent-direct-writeback.ts +++ b/lib/canvas/agent-direct-writeback.ts @@ -3,7 +3,9 @@ import { getSelectedMediaNodes, getSelectedSlots, } from "@/lib/canvas/agent-context"; +import { hasCanvasAgentAnnotationEditIntent } from "@/lib/canvas/agent-annotation-prompt"; import type { + CanvasAgentAnnotationRef, CanvasAgentContext, CanvasAgentNodeRef, CanvasMediaItem, @@ -69,6 +71,43 @@ function resolveTargetMediaNodes(params: { ); } +function formatAnnotationBrief(annotation: CanvasAgentAnnotationRef) { + const parts = [ + annotation.id, + annotation.type, + annotation.text ? `text=${annotation.text}` : "", + annotation.bounds + ? `bounds=${annotation.bounds.x},${annotation.bounds.y},${annotation.bounds.width},${annotation.bounds.height}` + : "", + annotation.targetPoint + ? `target=${annotation.targetPoint.x},${annotation.targetPoint.y}` + : "", + ].filter(Boolean); + return parts.join(";"); +} + +function buildAnnotationTraceMetadata(params: { + context: CanvasAgentContext; + sourceNodeIds: string[]; + userText?: string | null; +}) { + if (!hasCanvasAgentAnnotationEditIntent(params.userText || "")) return {}; + const sourceNodeIdSet = new Set(params.sourceNodeIds); + const annotations = params.context.annotations + .filter((annotation) => sourceNodeIdSet.has(annotation.sourceNodeId)) + .slice(0, 8); + if (annotations.length === 0) return {}; + + return { + canvasAgentAnnotationIds: annotations.map((annotation) => annotation.id), + canvasAgentAnnotationCount: annotations.length, + canvasAgentAnnotationBrief: annotations + .map(formatAnnotationBrief) + .join("\n") + .slice(0, 2000), + }; +} + export function buildCanvasAgentDirectWritebackPlan( params: BuildCanvasAgentDirectWritebackPlanParams ): CanvasAgentDirectWritebackPlanResult | null { @@ -131,6 +170,9 @@ export function buildCanvasAgentDirectWritebackPlan( : "insert_beside"; const mediaItems: CanvasMediaItem[] = resultUrls.map((url, index) => { const mediaItemId = `${params.assistantMessageId}:${params.operationLabel}:${index + 1}`; + const itemSourceNodeIds = perResultSourceNodeIds[index] + ? [perResultSourceNodeIds[index]] + : sourceNodeIds; return { mediaItemId, type: params.resultType, @@ -139,14 +181,17 @@ export function buildCanvasAgentDirectWritebackPlan( title: params.resultType === "video" ? "Agent 视频结果" : "Agent 图片结果", prompt: params.prompt, - sourceNodeIds: perResultSourceNodeIds[index] - ? [perResultSourceNodeIds[index]] - : sourceNodeIds, + sourceNodeIds: itemSourceNodeIds, metadata: { messageId: params.assistantMessageId, chatId: params.chatId, model: params.model || undefined, sourceImageUrls: params.sourceImageUrls || [], + ...buildAnnotationTraceMetadata({ + context: params.context, + sourceNodeIds: itemSourceNodeIds, + userText: params.requestTextContent || params.prompt, + }), }, }; }); diff --git a/tests/routes/canvas-chat-helpers.test.ts b/tests/routes/canvas-chat-helpers.test.ts index c6e908d98..3384071f7 100644 --- a/tests/routes/canvas-chat-helpers.test.ts +++ b/tests/routes/canvas-chat-helpers.test.ts @@ -570,6 +570,52 @@ test.describe("canvas chat pure helpers", () => { }); }); + test("records annotation trace metadata for direct annotation edits", () => { + const context: CanvasAgentContext = { + ...plannerContext, + annotations: [ + { + id: "annotation-keep-bg", + sourceNodeId: "image-1", + type: "text-note", + text: "背景换成浅灰", + bounds: { x: 0.1, y: 0.2, width: 0.3, height: 0.4 }, + }, + { + id: "annotation-other", + sourceNodeId: "image-other", + type: "text-note", + text: "不应进入 image-1 的结果 metadata", + }, + ], + }; + + const result = buildCanvasAgentDirectWritebackPlan({ + projectId: plannerContext.projectId || "", + chatId: "chat-1", + assistantMessageId: "assistant-annotation-edit", + operationLabel: "agent-edit-image", + resultType: "image", + resultUrls: ["https://cdn.example.com/annotation-edit.png"], + prompt: "按批注修改这张图", + requestTextContent: "按批注修改这张图", + mode: "edit", + context, + targetRefs: [{ nodeId: "image-1" }], + }); + + expect(result?.mediaItems[0]?.metadata).toMatchObject({ + canvasAgentAnnotationIds: ["annotation-keep-bg"], + canvasAgentAnnotationCount: 1, + }); + expect( + String(result?.mediaItems[0]?.metadata?.canvasAgentAnnotationBrief || "") + ).toContain("背景换成浅灰"); + expect( + String(result?.mediaItems[0]?.metadata?.canvasAgentAnnotationBrief || "") + ).not.toContain("annotation-other"); + }); + test("plans direct batch generations beside each matched source", () => { const context: CanvasAgentContext = { ...plannerContext,