feat(previs): 图片生成预演台 - 从图片节点交给 Agent 复现为可编辑 3D 预演
图片工具栏新增「生成预演台」,自动选用支持看图的文本模型并提交复现指令。对话用节点卡片展示「此图」,普通图片演员只写名称时不再要求角色卡绑定。
This commit is contained in:
@@ -64,6 +64,23 @@ test("renders an agent node link as a titled inline node card", () => {
|
||||
expect(visibleText).not.toContain("video-1790385749473-q4157");
|
||||
});
|
||||
|
||||
test("renders the image-to-previs user request with the existing canvas node card", () => {
|
||||
const imageReference = { ...videoReference, kind: "image" as const, sourceType: "image" as const, label: "道姑", title: "道姑" };
|
||||
const html = renderToStaticMarkup(
|
||||
<AgentChatMessage
|
||||
item={{ id: "user-previs", role: "user", text: "根据此图生成 3D 预演台", canvasReferenceNodeId: imageReference.nodeId }}
|
||||
theme={canvasThemes.dark}
|
||||
references={[imageReference]}
|
||||
onFocusNode={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(html).toContain('class="agent-message-node-link"');
|
||||
expect(html).toContain('class="agent-message-node-link-preview"');
|
||||
expect(html).toContain("道姑");
|
||||
expect(html).toContain("3D 预演台");
|
||||
});
|
||||
|
||||
test("labels character cards by resource kind instead of the underlying text type", () => {
|
||||
const nodeId = "text-1790385749473-char1";
|
||||
const reference = { id: nodeId, nodeId, kind: "character" as const, label: "李莫愁", title: "李莫愁", sourceType: "text" as const };
|
||||
|
||||
@@ -91,5 +91,7 @@ describe("canvas node toolbar model", () => {
|
||||
expect(tools.find((tool) => tool.id === "crop")?.section).toBe("构图与尺寸");
|
||||
expect(tools.find((tool) => tool.id === "resize")?.active).toBe(true);
|
||||
expect(tools.find((tool) => tool.id === "copyPrompt")?.group).toBe("more");
|
||||
expect(tools.find((tool) => tool.id === "previs")?.group).toBe("primary");
|
||||
expect(tools.find((tool) => tool.id === "previs")?.description).toContain("Agent");
|
||||
});
|
||||
});
|
||||
|
||||
111
web/test/image-to-previs-agent.test.ts
Normal file
111
web/test/image-to-previs-agent.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { buildImageToPrevisAgentPrompt, buildImageToPrevisDisplayText, modelSupportsImageInspection, selectImageInspectionModel } from "@/lib/canvas/image-to-previs-agent";
|
||||
import type { AiConfig } from "@/stores/use-config-store";
|
||||
|
||||
describe("image to previs Agent prompt", () => {
|
||||
test("requires visual inspection and editable semantic objects", () => {
|
||||
const prompt = buildImageToPrevisAgentPrompt({
|
||||
id: "image-ref",
|
||||
nodeId: "image-node-1",
|
||||
kind: "image",
|
||||
label: "场景图",
|
||||
title: "场景图",
|
||||
active: true,
|
||||
mentionToken: "@[node:image-node-1]",
|
||||
});
|
||||
|
||||
expect(prompt).toContain("@[node:image-node-1]");
|
||||
expect(prompt).toContain("canvas_inspect_image");
|
||||
expect(prompt).toContain("不要改用 image_text_detect");
|
||||
expect(prompt).toContain("previs_scene_create");
|
||||
expect(prompt).toContain("previs_scene_read");
|
||||
expect(prompt).toContain("previs_apply_patch");
|
||||
expect(prompt).toContain("actor");
|
||||
expect(prompt).toContain("不要把原图作为 billboard");
|
||||
expect(prompt).toContain("普通图片中的人物不是角色卡");
|
||||
expect(prompt).toContain("禁止传 characterName");
|
||||
expect(prompt).not.toContain("createPrevisBillboard");
|
||||
expect(buildImageToPrevisDisplayText()).toBe("根据此图生成 3D 预演台");
|
||||
});
|
||||
|
||||
test("routes the image workflow to a configured vision-capable text model", () => {
|
||||
const config = {
|
||||
channels: [
|
||||
{
|
||||
id: "plain",
|
||||
scope: "system",
|
||||
models: ["plain-text"],
|
||||
modelCosts: [
|
||||
{
|
||||
model: "plain-text",
|
||||
capability: "text",
|
||||
protocol: "chat-completion",
|
||||
billingMode: "fixed_request",
|
||||
unitPriceMicrocredits: 1,
|
||||
capabilityConfig: { version: 1, text: { contextWindowTokens: 128000, maxOutputTokens: 16384, references: { promptMaxChars: 32000, maxImages: 0, maxImageBytes: 0, maxVideos: 0, maxVideoBytes: 0 } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "vision",
|
||||
scope: "system",
|
||||
models: ["vision-text"],
|
||||
modelCosts: [
|
||||
{
|
||||
model: "vision-text",
|
||||
capability: "text",
|
||||
protocol: "chat-completion",
|
||||
billingMode: "fixed_request",
|
||||
unitPriceMicrocredits: 1,
|
||||
capabilityConfig: { version: 1, text: { contextWindowTokens: 128000, maxOutputTokens: 16384, references: { promptMaxChars: 32000, maxImages: 3, maxImageBytes: 30 * 1024 * 1024, maxVideos: 0, maxVideoBytes: 0 } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as AiConfig;
|
||||
|
||||
expect(modelSupportsImageInspection(config, "plain::plain-text")).toBe(false);
|
||||
expect(selectImageInspectionModel(config, "plain::plain-text")).toBe("vision::vision-text");
|
||||
expect(selectImageInspectionModel(config, "vision::vision-text")).toBe("vision::vision-text");
|
||||
});
|
||||
|
||||
test("does not route the cloud Agent through a browser-only custom channel", () => {
|
||||
const config = {
|
||||
channels: [
|
||||
{
|
||||
id: "custom",
|
||||
scope: "user",
|
||||
models: ["custom-vision"],
|
||||
modelCosts: [
|
||||
{
|
||||
model: "custom-vision",
|
||||
capability: "text",
|
||||
protocol: "chat-completion",
|
||||
billingMode: "fixed_request",
|
||||
unitPriceMicrocredits: 1,
|
||||
capabilityConfig: { version: 1, text: { contextWindowTokens: 128000, maxOutputTokens: 16384, references: { promptMaxChars: 32000, maxImages: 3, maxImageBytes: 30 * 1024 * 1024, maxVideos: 0, maxVideoBytes: 0 } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "managed",
|
||||
scope: "system",
|
||||
models: ["managed-vision"],
|
||||
modelCosts: [
|
||||
{
|
||||
model: "managed-vision",
|
||||
capability: "text",
|
||||
protocol: "chat-completion",
|
||||
billingMode: "fixed_request",
|
||||
unitPriceMicrocredits: 1,
|
||||
capabilityConfig: { version: 1, text: { contextWindowTokens: 128000, maxOutputTokens: 16384, references: { promptMaxChars: 32000, maxImages: 3, maxImageBytes: 30 * 1024 * 1024, maxVideos: 0, maxVideoBytes: 0 } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as unknown as AiConfig;
|
||||
|
||||
expect(selectImageInspectionModel(config, "custom::custom-vision")).toBe("managed::managed-vision");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user