fix: trace annotation edits in canvas writeback metadata

This commit is contained in:
134 2026-06-23 16:14:27 +08:00
parent ef0f9bc93d
commit 763696e8d0
2 changed files with 94 additions and 3 deletions

View File

@ -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,
}),
},
};
});

View File

@ -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,