76 lines
6.2 KiB
TypeScript
76 lines
6.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import { serverLoader } from "../helpers/canvas-download-server-loader";
|
|
import { applyAgentImagePreferences, agentProviderImageParameters } from "@/lib/ai/canvas-agent-runtime/image-preferences";
|
|
|
|
const preferences = {
|
|
modelId: "user-image-model",
|
|
parameters: { imageCount: 3, aspectRatio: "3:4", imageSize: "4K", negativePrompt: "不要文字",
|
|
quality: "high" as const, size: "1536*2048", promptExtend: false, watermark: false, seed: 0 },
|
|
};
|
|
function harness() {
|
|
const generated: any[] = [];
|
|
const operations: any[] = [];
|
|
const mock = serverLoader({
|
|
"@/lib/canvas/capabilities/ai-sdk-tools": { createCanvasCapabilityTools: async () => ({ editTargetImage: {} }) },
|
|
"@/lib/canvas/capabilities/registry": { CANVAS_CAPABILITY_REGISTRY: [{ id: "image.prompt_regenerate", title: "重绘", risk: "external", agent: { toolName: "editTargetImage" } }] },
|
|
"@/lib/canvas/agent-mutations": { applyCanvasOperationPlan: async (value: any) => { operations.push(value); return { createdNodeIds: ["new-image"] }; } },
|
|
"@/lib/canvas/generate-image": { generateCanvasImage: async (value: any) => { generated.push(value); return { modelRef: value.model, images: [{ originalUrl: "https://test.example/result.png", thumbnailUrl: "https://test.example/thumb.png" }] }; } },
|
|
"@/lib/ai/runtime/generated-image-persistence": { saveGeneratedImages: async () => {} },
|
|
"@/lib/ai/model-router": { routeToModel: async () => ({ id: "system-default" }) },
|
|
"@/lib/db/queries": { checkUserImageQuota: async () => ({ allowed: true, remaining: 10 }), getPointsSystemConfig: async () => ({ enabled: false }), getUserById: async () => ({}) },
|
|
"@/lib/style-pro/model-options": {
|
|
getStyleProImageModelEntries: async () => ["system-default", "user-image-model"].map(id => ({ model: { id, modelId: id, isDefault: id === "system-default", imageGenConfig: {} } })),
|
|
sortStyleProImageModelEntries: (entries: any) => entries,
|
|
},
|
|
"./canvas": { readOwnedCanvas: async () => ({ nodes: [{ id: "reference", type: "image" }] }), readCanvasImages: async () => [{ id: "reference", data: { src: "https://test.example/reference.png" } }] },
|
|
"./review": {},
|
|
"./image-access-tools": { registerImageCanvasAccessTools() {} },
|
|
});
|
|
const build = async (imagePreferences: any = preferences) => mock("lib/ai/canvas-agent-runtime/tools.ts").buildCanvasRuntimeTools({
|
|
userId: "u", chatId: "c", projectId: "p", runId: "r", assistantMessageId: "a", selectedNodeIds: ["reference"],
|
|
request: new Request("https://test.example/api/canvas/agent-chat"), model: {}, supportsVision: true,
|
|
preferredModelId: "system-default", imagePreferences, emit() {},
|
|
});
|
|
return { build, generated, operations };
|
|
}
|
|
test("explicit controls override conflicting planner settings without losing false or zero values", () => {
|
|
const resolved = applyAgentImagePreferences({ modelId: "planner-choice", imageCount: 1, imageSize: "1K", aspectRatio: "1:1", prompt: "产品图" }, preferences);
|
|
expect(resolved).toMatchObject({ ...preferences.parameters, modelId: preferences.modelId, prompt: "产品图" });
|
|
expect(agentProviderImageParameters(resolved)).toEqual({ quality: "high", size: "1536*2048", watermark: false, seed: 0 });
|
|
});
|
|
test("real generation tool honors the chosen image model and all parameters despite another default and planner choice", async () => {
|
|
const h = harness(); const tools = await h.build();
|
|
const output = await tools.executable.generateCanvasImages.execute({ prompt: "生成产品图", modelId: "planner-choice", imageCount: 1, imageSize: "1K", aspectRatio: "1:1" }, "call-1");
|
|
expect(h.generated).toHaveLength(1);
|
|
expect(h.generated[0]).toMatchObject({ prompt: "生成产品图", model: "user-image-model", imageCount: 3, imageSize: "4K", aspectRatio: "3:4", negativePrompt: "不要文字", promptExtend: false, allowFailover: false,
|
|
providerImageParams: { quality: "high", size: "1536*2048", watermark: false, seed: 0 } });
|
|
expect(output.appliedImagePreferences).toEqual({ modelId: "user-image-model", imageCount: 3, imageSize: "4K", aspectRatio: "3:4" });
|
|
});
|
|
test("redraw and a corrective retry inherit the same user model, batch and image controls", async () => {
|
|
const h = harness(); const tools = await h.build();
|
|
await tools.executable.editTargetImage.execute({ nodeIds: ["reference"], args: { prompt: "换背景", modelId: "system-default" } }, "edit-1");
|
|
await tools.executable.generateCanvasImages.execute({ prompt: "修正背景", modelId: "system-default" }, "retry-1");
|
|
expect(h.generated).toHaveLength(2);
|
|
for (const invocation of h.generated) expect(invocation).toMatchObject({ model: preferences.modelId, imageSize: "4K", imageCount: 3, aspectRatio: "3:4", allowFailover: false });
|
|
});
|
|
test("an unavailable explicit choice never silently generates or charges through the default", async () => {
|
|
const h = harness(); const tools = await h.build({ ...preferences, modelId: "disabled-or-missing" });
|
|
await expect(tools.executable.generateCanvasImages.execute({ prompt: "画图" }, "bad")).rejects.toThrow("指定的图片模型不可用");
|
|
expect(h.generated).toHaveLength(0); expect(h.operations).toHaveLength(0);
|
|
});
|
|
test("Auto selection retains routing and the n count alias without overriding a supplied planner ratio", async () => {
|
|
const h = harness(); const tools = await h.build({ parameters: { n: 2, imageSize: "8K" } });
|
|
await tools.executable.generateCanvasImages.execute({ prompt: "画图", aspectRatio: "16:9" }, "auto");
|
|
expect(h.generated[0]).toMatchObject({ model: "system-default", imageCount: 2, imageSize: "8K", aspectRatio: "16:9", allowFailover: true });
|
|
});
|
|
|
|
test("planning uses the image count for generation tasks but does not turn analysis into an image task", async () => {
|
|
const h = harness(); const tools = await h.build();
|
|
const plan = { steps: ["查看画布"], visualCriteria: [] };
|
|
const analysis = await tools.executable.planCanvasTask.execute(plan, "analysis-plan");
|
|
expect(analysis.expectedImageCount).toBeUndefined();
|
|
const generation = await tools.executable.planCanvasTask.execute({ ...plan, expectedImageCount: 1, aspectRatio: "1:1" }, "generation-plan");
|
|
expect(generation).toMatchObject({ expectedImageCount: 3, aspectRatio: "3:4" });
|
|
expect(h.generated).toHaveLength(0);
|
|
});
|