fix: anchor canvas writeback to resolved targets
This commit is contained in:
parent
e73c2da84e
commit
ef0f9bc93d
@ -5,6 +5,7 @@ import {
|
||||
} from "@/lib/canvas/agent-context";
|
||||
import type {
|
||||
CanvasAgentContext,
|
||||
CanvasAgentNodeRef,
|
||||
CanvasMediaItem,
|
||||
CanvasOperationKind,
|
||||
CanvasOperationPlan,
|
||||
@ -46,6 +47,28 @@ export interface CanvasAgentDirectWritebackPlanResult {
|
||||
selectedMediaNodes: ReturnType<typeof getSelectedMediaNodes>;
|
||||
}
|
||||
|
||||
function buildKnownNodeMap(context: CanvasAgentContext) {
|
||||
return new Map<string, CanvasAgentNodeRef>(
|
||||
[...context.candidateNodes, ...context.selectedNodes].map((node) => [
|
||||
node.nodeId,
|
||||
node,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function resolveTargetMediaNodes(params: {
|
||||
context: CanvasAgentContext;
|
||||
targetNodeIds: string[];
|
||||
}) {
|
||||
const knownNodes = buildKnownNodeMap(params.context);
|
||||
return params.targetNodeIds
|
||||
.map((nodeId) => knownNodes.get(nodeId))
|
||||
.filter(
|
||||
(node): node is CanvasAgentNodeRef =>
|
||||
node !== undefined && (node.type === "image" || node.type === "video")
|
||||
);
|
||||
}
|
||||
|
||||
export function buildCanvasAgentDirectWritebackPlan(
|
||||
params: BuildCanvasAgentDirectWritebackPlanParams
|
||||
): CanvasAgentDirectWritebackPlanResult | null {
|
||||
@ -62,17 +85,26 @@ export function buildCanvasAgentDirectWritebackPlan(
|
||||
const targetSlotId = preferredTargetSlotId || selectedSlot?.nodeId || "";
|
||||
const shouldFillSlot = Boolean(targetSlotId);
|
||||
const selectedMediaNodes = getSelectedMediaNodes(params.context);
|
||||
const anchorNode =
|
||||
selectedSlot || selectedMediaNodes[0] || getBestAnchorNode(params.context);
|
||||
const targetRefs = params.targetRefs || [];
|
||||
const targetNodeIds = Array.from(
|
||||
new Set(
|
||||
targetRefs
|
||||
.map((ref) => ref.nodeId)
|
||||
.map((ref) => String(ref.nodeId || "").trim())
|
||||
.filter((value): value is string => Boolean(value))
|
||||
)
|
||||
);
|
||||
const selectedMediaNodeIds = selectedMediaNodes
|
||||
const targetMediaNodes = resolveTargetMediaNodes({
|
||||
context: params.context,
|
||||
targetNodeIds,
|
||||
});
|
||||
const writebackMediaNodes =
|
||||
targetMediaNodes.length > 0 ? targetMediaNodes : selectedMediaNodes;
|
||||
const anchorNode =
|
||||
selectedSlot ||
|
||||
targetMediaNodes[0] ||
|
||||
selectedMediaNodes[0] ||
|
||||
getBestAnchorNode(params.context);
|
||||
const selectedMediaNodeIds = writebackMediaNodes
|
||||
.map((node) => node.nodeId)
|
||||
.filter((value): value is string => Boolean(value));
|
||||
const perResultSourceNodeIds =
|
||||
@ -154,6 +186,6 @@ export function buildCanvasAgentDirectWritebackPlan(
|
||||
operation,
|
||||
targetSlotId,
|
||||
shouldFillSlot,
|
||||
selectedMediaNodes,
|
||||
selectedMediaNodes: writebackMediaNodes,
|
||||
};
|
||||
}
|
||||
|
||||
@ -509,6 +509,67 @@ test.describe("canvas chat pure helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("anchors direct image edits to resolved target refs before fallback selection", () => {
|
||||
const context: CanvasAgentContext = {
|
||||
...plannerContext,
|
||||
selectedNodeIds: [],
|
||||
selectedNodes: [],
|
||||
candidateNodes: [
|
||||
{
|
||||
nodeId: "image-left",
|
||||
type: "image",
|
||||
title: "左图",
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 240,
|
||||
height: 180,
|
||||
},
|
||||
{
|
||||
nodeId: "image-right",
|
||||
type: "image",
|
||||
title: "右图",
|
||||
x: 420,
|
||||
y: 0,
|
||||
width: 240,
|
||||
height: 180,
|
||||
},
|
||||
],
|
||||
focusPolicy: "auto",
|
||||
};
|
||||
|
||||
const result = buildCanvasAgentDirectWritebackPlan({
|
||||
projectId: plannerContext.projectId || "",
|
||||
chatId: "chat-1",
|
||||
assistantMessageId: "assistant-targeted",
|
||||
operationLabel: "agent-edit-image",
|
||||
resultType: "image",
|
||||
resultUrls: ["https://cdn.example.com/right-revision.png"],
|
||||
prompt: "把右边那张背景换成白色",
|
||||
mode: "edit",
|
||||
context,
|
||||
targetRefs: [{ nodeId: " image-right " }],
|
||||
sourceImageUrls: ["https://cdn.example.com/right-source.png"],
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
operation: "append_revision",
|
||||
plan: {
|
||||
operation: "append_revision",
|
||||
anchorNodeId: "image-right",
|
||||
sourceNodeIds: ["image-right"],
|
||||
},
|
||||
});
|
||||
expect(result?.selectedMediaNodes.map((node) => node.nodeId)).toEqual([
|
||||
"image-right",
|
||||
]);
|
||||
expect(result?.mediaItems[0]).toMatchObject({
|
||||
sourceNodeIds: ["image-right"],
|
||||
metadata: {
|
||||
sourceImageUrls: ["https://cdn.example.com/right-source.png"],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("plans direct batch generations beside each matched source", () => {
|
||||
const context: CanvasAgentContext = {
|
||||
...plannerContext,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user