fix: keep canvas annotation operations idempotent
This commit is contained in:
parent
909f1c506c
commit
c0550988e0
@ -786,6 +786,13 @@ function applyAddAnnotationOperation(params: {
|
||||
const existingAnnotations = Array.isArray(metadata.annotations)
|
||||
? metadata.annotations.filter((item) => item && typeof item === "object")
|
||||
: [];
|
||||
const hasExistingOperationAnnotation = existingAnnotations.some(
|
||||
(item) =>
|
||||
(item as Record<string, unknown>).canvasAgentOperationId ===
|
||||
params.plan.operationId
|
||||
);
|
||||
if (hasExistingOperationAnnotation) return;
|
||||
|
||||
const nextAnnotations = annotations.map((annotation, index) => ({
|
||||
...annotation,
|
||||
id:
|
||||
|
||||
@ -688,6 +688,70 @@ test.describe("canvas agent operation mutation contract", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("adds annotation metadata to media nodes without duplicating retries", () => {
|
||||
const target = buildImageNode("image-annotated");
|
||||
const plan: CanvasOperationPlan = {
|
||||
version: 1,
|
||||
operationId: "op-add-annotation",
|
||||
operation: "add_annotation",
|
||||
projectId,
|
||||
targetNodeIds: ["image-annotated"],
|
||||
textContent: "背景换浅灰,主体保持不变",
|
||||
annotations: [
|
||||
{
|
||||
type: "box",
|
||||
text: "处理这块背景",
|
||||
bounds: { x: 0.12, y: 0.2, width: 0.5, height: 0.32 },
|
||||
color: "#f59e0b",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
messageId: "assistant-message-annotation",
|
||||
chatId: "chat-1",
|
||||
},
|
||||
};
|
||||
|
||||
const first = applyCanvasOperationPlanToCanvasData({
|
||||
canvasData: getCanvasData([target]),
|
||||
plan,
|
||||
});
|
||||
const annotatedNode = getResultNodes(first.canvasData).find(
|
||||
(node) => node.id === "image-annotated"
|
||||
);
|
||||
|
||||
expect(first.createdNodeIds).toEqual([]);
|
||||
expect(first.updatedNodeIds).toEqual(["image-annotated"]);
|
||||
expect(annotatedNode).toMatchObject({
|
||||
data: {
|
||||
hasAnnotations: true,
|
||||
metadata: {
|
||||
annotations: [
|
||||
expect.objectContaining({
|
||||
id: "op-add-annotation:annotation-image-annotated-1",
|
||||
sourceNodeId: "image-annotated",
|
||||
type: "box",
|
||||
text: "处理这块背景",
|
||||
bounds: { x: 0.12, y: 0.2, width: 0.5, height: 0.32 },
|
||||
createdByAgent: true,
|
||||
canvasAgentOperationId: "op-add-annotation",
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const retry = applyCanvasOperationPlanToCanvasData({
|
||||
canvasData: first.canvasData,
|
||||
plan,
|
||||
});
|
||||
const retryNode = getResultNodes(retry.canvasData).find(
|
||||
(node) => node.id === "image-annotated"
|
||||
);
|
||||
|
||||
expect(retry.updatedNodeIds).toEqual([]);
|
||||
expect(retryNode.data.metadata.annotations).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("keeps batch media writes idempotent by operation id", () => {
|
||||
const anchor = buildImageNode("image-anchor");
|
||||
const plan = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user