完善生图5.0的功能

This commit is contained in:
andy committed 2026-08-25 14:06:04 +08:00
1 parent 88a4268d40
commit 678d213f40
31 files changed
+3281 -107

No files matched your search

+3 -1
View File
@@ -1,12 +1,14 @@
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { assetPreviewUrl, materialPreviewUrl, storedAssetPreviewUrl } from "@/lib/client/asset-urls";
import { assetContentUrl, assetPreviewUrl, materialContentUrl, materialPreviewUrl, storedAssetPreviewUrl } from "@/lib/client/asset-urls";
describe("private asset browser URLs", () => {
it("uses the authenticated inline endpoint for stored assets", () => {
expect(assetPreviewUrl({ id: "asset/a b" })).toBe("/api/assets/asset%2Fa%20b/download?inline=1");
expect(assetContentUrl({ id: "asset/a b" })).toBe("/api/assets/asset%2Fa%20b/download");
expect(materialPreviewUrl({ id: "asset-1", url: "https://private.test/raw.png" })).toBe("/api/assets/asset-1/download?inline=1");
expect(materialContentUrl({ id: "asset-1", url: "https://private.test/raw.png" })).toBe("/api/assets/asset-1/download");
expect(materialPreviewUrl({ url: "https://external.test/reference.png" })).toBe("https://external.test/reference.png");
expect(storedAssetPreviewUrl("https://private.test/legacy.png", [{ id: "asset-legacy", url: "https://private.test/legacy.png" }])).toBe("/api/assets/asset-legacy/download?inline=1");
expect(storedAssetPreviewUrl("https://external.test/template.png", [])).toBe("https://external.test/template.png");
+63
View File
@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import {
buildSeedreamInteractivePrompt,
buildSeedreamLayerPrompt,
missingSeedreamAnnotationTokens,
seedreamAnnotationReferences,
type SeedreamAnnotation
} from "@/lib/seedream/creation";
const annotations: SeedreamAnnotation[] = [
{ id: "box", index: 1, kind: "box", box: { left: 100, top: 200, right: 700, bottom: 800 } },
{ id: "point", index: 3, kind: "point", point: { x: 456, y: 321 } }
];
describe("Seedream interactive prompt", () => {
it("offers existing canvas annotations as @ input references", () => {
expect(seedreamAnnotationReferences([
...annotations,
{ id: "mark", index: 4, kind: "stroke", points: [{ x: 1, y: 1 }, { x: 2, y: 2 }] }
], "标")).toEqual([
{ token: "@标注1", label: "框选区域 1", caption: "画布标注" },
{ token: "@标注3", label: "点选位置 3", caption: "画布标注" },
{ token: "@标记4", label: "蓝色标记 4", caption: "画布标注" }
]);
});
it("replaces visible annotation tokens with normalized provider tags", () => {
expect(buildSeedreamInteractivePrompt("把 @标注1 改成蓝色", annotations)).toBe(
"把 图1<bbox>100 200 700 800</bbox> 改成蓝色\n编辑位置:标注3 图1<point>456 321</point>。"
);
});
it("supports cross-image coordinates and visible mark references", () => {
const crossImageAnnotations: SeedreamAnnotation[] = [
{ id: "subject", index: 1, kind: "box", sourceKey: "image-a", box: { left: 50, top: 60, right: 400, bottom: 500 } },
{ id: "target", index: 2, kind: "point", sourceKey: "image-b", point: { x: 720, y: 310 } },
{ id: "mark", index: 4, kind: "stroke", sourceKey: "image-b", points: [{ x: 500, y: 500 }, { x: 650, y: 610 }] }
];
expect(buildSeedreamInteractivePrompt("把 @标注1 移到 @标注2,并清除 @标记4", crossImageAnnotations, ["image-a", "image-b"])).toBe(
"把 图1<bbox>50 60 400 500</bbox> 移到 图2<point>720 310</point>,并清除 图2中的蓝色标记区域"
);
});
it("detects references to annotations that were removed", () => {
expect(missingSeedreamAnnotationTokens("修改 @标注3 和 @标注2", annotations)).toEqual(["@标注2"]);
});
});
describe("Seedream layer prompt", () => {
it("supports optional region instructions for layer decomposition", () => {
expect(buildSeedreamLayerPrompt("拆出 @标注1 内的标题", annotations)).toBe(
"拆出 <bbox>100 200 700 800</bbox> 内的标题\n需拆分区域:<point>456 321</point>。"
);
});
it("can describe a blue mark when the prompt is left empty", () => {
const marks: SeedreamAnnotation[] = [
{ id: "mark", index: 1, kind: "stroke", points: [{ x: 100, y: 100 }, { x: 200, y: 250 }] }
];
expect(buildSeedreamLayerPrompt("", marks)).toBe("需拆分区域:图片中的蓝色标记区域。");
});
});
+49
View File
@@ -0,0 +1,49 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { downloadSeedreamLayerComposition } from "@/lib/client/seedream-layer-compose";
describe("Seedream layer composition download", () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it("streams private assets through the same-origin download route instead of following the signed preview redirect", async () => {
const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("inline=1")) throw new TypeError("Failed to fetch");
return new Response(new Blob(["image"]), { status: 200 });
});
const close = vi.fn();
const drawImage = vi.fn();
const click = vi.fn();
const anchor = { href: "", download: "", click, remove: vi.fn() };
const canvas = {
width: 0,
height: 0,
getContext: () => ({ clearRect: vi.fn(), drawImage }),
toBlob: (callback: BlobCallback) => callback(new Blob(["png"], { type: "image/png" }))
};
vi.stubGlobal("fetch", fetchMock);
vi.stubGlobal("createImageBitmap", vi.fn(async () => ({ width: 100, height: 100, close })));
vi.stubGlobal("document", {
createElement: (tag: string) => tag === "canvas" ? canvas : anchor,
body: { appendChild: vi.fn() }
});
vi.stubGlobal("window", { setTimeout: (callback: () => void) => { callback(); return 1; } });
vi.stubGlobal("URL", { createObjectURL: () => "blob:composition", revokeObjectURL: vi.fn() });
await expect(downloadSeedreamLayerComposition({
items: [{ url: "/api/assets/private-layer/download?inline=1", visible: true, box: [0, 0, 1000, 1000] }],
width: 100,
height: 100,
fileName: "composition.png"
})).resolves.toBeUndefined();
expect(fetchMock).toHaveBeenCalledWith("/api/assets/private-layer/download", { credentials: "same-origin" });
expect(drawImage).toHaveBeenCalledOnce();
expect(click).toHaveBeenCalledOnce();
expect(close).toHaveBeenCalledOnce();
});
});