fix: route explicit canvas slot creation

This commit is contained in:
134 2026-06-23 18:24:38 +08:00
parent 06834699be
commit cf879c63b6
3 changed files with 146 additions and 1 deletions

View File

@ -4064,6 +4064,55 @@ export async function POST(request: Request) {
const canvasExplicitOperationPlan = resolveCanvasExplicitOperationPlan();
if (canvasExplicitOperationPlan) {
const { operationPlan } = canvasExplicitOperationPlan;
if (
operationPlan.operation === "create_agent_slot" &&
!canvasAgentFeatures.slotEnabled
) {
const canvasSlotDisabledStream = createUIMessageStream({
execute: async ({ writer }) => {
const messageId = generateUUID();
const slotDisabledReply = "当前画布 Agent Slot 能力暂未开启。";
writeAgentProgressChunk(
writer,
{
stage: "completed",
currentStep: "finalize",
title: "画布槽位未创建",
message: slotDisabledReply,
toolName: "canvasOperation",
capability: operationPlan.operation,
},
"canvas-explicit:slot-disabled",
);
writeAssistantTextChunk(
writer,
slotDisabledReply,
"canvas-explicit:slot-disabled-reply",
messageId,
);
await saveMessages({
messages: [
{
id: messageId,
chatId: id,
role: "assistant",
parts: [{ type: "text", text: slotDisabledReply }],
attachments: [],
createdAt: new Date(),
videoStorageStatus: null,
temporaryVideoUrl: null,
permanentVideoUrl: null,
archivedAt: null,
videoMetadata: null,
},
],
});
},
});
return createSseResponse(canvasSlotDisabledStream);
}
const canvasExplicitStream = createUIMessageStream({
execute: async ({ writer }) => {
const messageId = generateUUID();

View File

@ -2,8 +2,16 @@ import type {
CanvasAgentContext,
CanvasOperationPlan,
} from "@/types/canvas/agent-protocol";
import {
buildCanvasAgentSlotCreationPlan,
resolveCanvasAgentSlotCreationIntent,
} from "@/lib/canvas/agent-model-planner";
type ExplicitOperationKind = "create_text_node" | "rename_node" | "update_note";
type ExplicitOperationKind =
| "create_agent_slot"
| "create_text_node"
| "rename_node"
| "update_note";
export type CanvasAgentExplicitOperationPlan = {
operationPlan: CanvasOperationPlan;
@ -190,6 +198,49 @@ function buildCreateTextOperation(params: {
};
}
function getSlotTypeLabel(slotType: "image" | "video" | "ui-screen") {
if (slotType === "video") return "视频";
if (slotType === "ui-screen") return "UI";
return "图片";
}
function buildCreateAgentSlotOperation(params: {
context: CanvasAgentContext;
projectId: string;
messageId: string;
chatId: string;
userText: string;
}): CanvasAgentExplicitOperationPlan | null {
const intent = resolveCanvasAgentSlotCreationIntent({
userText: params.userText,
});
if (!intent.shouldCreate || intent.combinedWithMediaExecution) return null;
const operationPlan = buildCanvasAgentSlotCreationPlan({
...params,
slotType: intent.slotType,
slotLabel: intent.label,
aspectRatio: intent.aspectRatio,
});
if (!operationPlan) return null;
const slotTypeLabel = getSlotTypeLabel(intent.slotType);
return {
operationPlan: {
...operationPlan,
metadata: {
...(operationPlan.metadata || {}),
planner: "deterministic",
},
},
reply: `已在当前画布创建${slotTypeLabel}槽位。`,
progressTitle: "正在创建画布槽位",
progressMessage: `我会在当前视口中心创建一个空${slotTypeLabel}槽位。`,
completedTitle: "画布槽位已创建",
failedTitle: "画布槽位创建失败",
};
}
function buildRenameOperation(params: {
context: CanvasAgentContext;
projectId: string;
@ -300,6 +351,7 @@ export function buildCanvasAgentExplicitOperationPlan(params: {
}
return (
buildCreateAgentSlotOperation(commonParams) ||
buildCreateTextOperation(commonParams) ||
buildRenameOperation(commonParams) ||
buildUpdateNoteOperation(commonParams)

View File

@ -944,6 +944,40 @@ test.describe("canvas chat pure helpers", () => {
});
});
test("builds deterministic agent slot creation plans from explicit canvas prompts", () => {
const context: CanvasAgentContext = {
...plannerContext,
viewport: { x: 0, y: 0, zoom: 1, width: 1000, height: 800 },
focusPolicy: "auto",
};
const outcome = buildCanvasAgentExplicitOperationPlan({
context,
projectId: plannerContext.projectId || "",
messageId: "message-1",
chatId: "chat-1",
userText: "新建一个 9:16 视频槽位",
});
expect(outcome?.operationPlan).toMatchObject({
operation: "create_agent_slot",
targetPosition: { x: 320, y: 290 },
slot: {
slotType: "video",
label: "视频槽位",
aspectRatio: "9:16",
fillPolicy: "replace-empty",
acceptedSources: ["text", "image", "selection", "annotation"],
},
preserveOriginal: true,
metadata: {
messageId: "message-1",
chatId: "chat-1",
planner: "deterministic",
},
});
expect(outcome?.reply).toContain("视频槽位");
});
test("builds deterministic node rename plans without treating titles as image edits", () => {
const outcome = buildCanvasAgentExplicitOperationPlan({
context: plannerContext,
@ -1006,6 +1040,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", () => {