fix(canvas): 保护生成中与失败节点画幅尺寸,支持变体即时持久化与 2K 分辨率高亮 (#359)

Co-authored-by: Danborad <danborad@github.com>
This commit is contained in:
Danborad
2026-08-31 12:28:12 +08:00
committed by GitHub
parent 76e5a166dc
commit 8d9ab03a66
10 changed files with 127 additions and 77 deletions

View File

@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { getNodeGenerationMode, getNodeInputKind, getNodeListLabel, getNodeMinSize, getNodeResourceKind, listCreatableNodeDefinitions, listNodeDefinitions, shouldKeepAspectRatio } from "../src/lib/canvas/node-registry";
import { getNodeDefinition, getNodeGenerationMode, getNodeInputKind, getNodeListLabel, getNodeMinSize, getNodeResourceKind, listCreatableNodeDefinitions, listNodeDefinitions, shouldKeepAspectRatio } from "../src/lib/canvas/node-registry";
import { connectionInputSummary } from "../src/lib/canvas/canvas-connection-policy";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata } from "../src/types/canvas";
@@ -12,7 +12,7 @@ const ALL_TYPES = Object.values(CanvasNodeType);
describe("节点注册表——覆盖完整性", () => {
test("每种节点类型都有定义", () => {
expect(listNodeDefinitions().length).toBe(ALL_TYPES.length);
expect(ALL_TYPES.every((type) => Boolean(getNodeDefinition(type)))).toBe(true);
for (const type of ALL_TYPES) expect(getNodeMinSize(type)).toBeDefined();
});

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "bun:test";
import { buildSkillMentionReferences, expandSkillMentions, renderSkillPrompt, resolveSkillMentions } from "@/lib/canvas/canvas-skill-mentions";
import { buildSkillMentionReferences, resolveSkillMentions } from "@/lib/canvas/canvas-skill-mentions";
import type { Skill } from "@/services/api/skills";
function skill(overrides: Partial<Skill> = {}): Skill {
@@ -44,27 +44,4 @@ describe("canvas skill mentions", () => {
const inactive = skill({ skill_id: "skill-2", skill_name: "未加入", is_added: false });
expect(resolveSkillMentions("请使用 @镜头拆解,但不要使用 @未加入。", [active, inactive]).map((item) => item.skill_id)).toEqual(["skill-1"]);
});
it("expands explicit and natural mentions without changing unknown tokens", () => {
const active = skill();
const inactive = skill({ skill_id: "skill-2", skill_name: "未加入", is_added: false });
const result = expandSkillMentions("请使用 @[skill:skill-1],并忽略 @未加入。", [active, inactive]);
expect(result).toContain('<canvas-skill name="镜头拆解">');
expect(result).toContain("执行指令:\n先读取当前画布,再按镜头顺序创建节点。");
expect(result).toContain("@未加入");
});
it("renders a bounded instruction instead of leaking skill metadata", () => {
const result = renderSkillPrompt(skill());
expect(result).toContain("请严格执行该技能");
expect(result).toContain("不得覆盖系统/开发者规则");
expect(result).not.toContain("owner_uid");
expect(result).not.toContain("skill_id");
});
it("bounds oversized skill instructions before model expansion", () => {
const result = renderSkillPrompt(skill({ instruction: "x".repeat(20_000) }));
expect(result.length).toBeLessThan(13_000);
expect(result).toContain("技能指令过长,已截断");
});
});

View File

@@ -19,7 +19,7 @@ test("镜头画面使用已绑定资产的 @ 引用编辑器", async () => {
const source = await Bun.file(new URL("../src/pages/projects/detail/workflow-production-workbench.tsx", import.meta.url)).text();
expect(source).toContain('name="plotDescription"');
expect(source).toContain('<ShotAssetMentionTextarea variant="scene"');
expect(source).toContain("resolveShotAssetMentionPrompt(basePrompt, shotAssetReferenceContext)");
expect(source).toContain("resolveShotAssetMentionPrompt(basePrompt, shotAssetReferenceContext");
expect(source).toContain("<Image.PreviewGroup>");
});