feat(canvas): 画布媒体工作流 - 支持批量连线、失败重试与模型能力增强

合并 PR #230。保留当前主线样式与组件结构,吸收批量连线、失败批次重试、媒体模型能力和相关后端增强,并修复集成检查发现的分辨率选择器启用条件与测试稳定性问题。
This commit is contained in:
WindC0X
2026-08-15 15:48:16 +08:00
committed by GitHub
parent c7eb5e5886
commit 9ee71d4650
48 changed files with 1582 additions and 83 deletions

View File

@@ -0,0 +1,45 @@
import { describe, expect, test } from "bun:test";
import { buildEmotionPrompt, emotionProviderMask, normalizeEmotionPromptForProvider, resolveEmotionEditPlan } from "../src/lib/canvas/canvas-emotion";
const faceBox = { id: "face-1", x: 120, y: 80, width: 160, height: 180, source: "detected" as const };
const editRegion = { x: 40, y: 20, width: 320, height: 360 };
describe("emotion edit provider plan", () => {
test("keeps provider mask when the selected image model supports it", () => {
expect(resolveEmotionEditPlan(true)).toEqual({ mode: "provider-mask", includeMask: true });
});
test("falls back visibly to local compositing when the provider does not support masks", () => {
const plan = resolveEmotionEditPlan(false);
expect(plan).toEqual({
mode: "local-composite",
includeMask: false,
notice: "当前渠道不支持蒙版,使用脸部裁切与本地羽化融合",
});
expect(emotionProviderMask(plan, { dataUrl: "data:image/png;base64,bWFzaw==" })).toBeUndefined();
});
test("does not promise a provider mask in the shared emotion prompt", () => {
const prompt = buildEmotionPrompt(
{ presetId: "emotion-0-0", intimacy: 0, arousal: 0, characterName: "角色1", faceBox },
editRegion,
);
expect(prompt).not.toContain("透明蒙版");
expect(prompt).not.toContain("白色蒙版");
expect(prompt).toContain("目标人脸框");
});
test("removes legacy mask promises before retrying an existing emotion node", () => {
const legacyPrompt = [
"只允许修改目标框及其透明蒙版对应的这一张脸。",
"输入图已裁切到目标人物头部区域;透明蒙版内允许编辑,白色蒙版区域必须保持不变,蒙版外绝对不要生成或修改任何内容。",
].join("\n");
const prompt = normalizeEmotionPromptForProvider(legacyPrompt);
expect(prompt).not.toContain("透明蒙版");
expect(prompt).not.toContain("白色蒙版");
expect(prompt).toContain("目标人脸框及其邻近表情区域");
});
});