feat(*): 创作配置 - 支持 Midjourney、多图保存与自定义公告

Co-authored-by: ddcat666 <26564566+ddcat666@users.noreply.github.com>
This commit is contained in:
DAO-QIN
2026-10-04 10:43:31 +08:00
committed by ddcat666
parent 002a45f0c3
commit 853c7d2ce7
107 changed files with 9261 additions and 408 deletions

View File

@@ -4,7 +4,7 @@ import { getActiveUserScope, setActiveUserScope } from "../../src/lib/user-scope
type InstanceHook = (storeName: string, key: string, value: unknown) => Promise<void> | void;
type Scenario = "image-cleanup" | "scope-cleanup-switch" | "scope-cleanup-late-canvas-reference" | "video-commit-race" | "audio-commit-race" | "canvas-batch-commit-race";
type Scenario = "image-cleanup" | "scope-cleanup-switch" | "scope-cleanup-late-canvas-reference" | "video-commit-race" | "audio-commit-race" | "canvas-batch-commit-race" | "canvas-multi-output";
function installStorageHarness() {
const originalCreateInstance = localforage.createInstance.bind(localforage);
@@ -393,7 +393,7 @@ async function runMediaCommitRace(mediaType: "video" | "audio") {
}
}
async function runCanvasBatchCommitRace() {
async function runCanvasBatchCommitRace(multiOutput = false) {
const harness = installStorageHarness();
const previousScope = getActiveUserScope();
const { apiClient } = await import("../../src/services/api/request");
@@ -402,7 +402,7 @@ async function runCanvasBatchCommitRace() {
if (config.method !== "post" || config.url !== "/resources/access") throw new Error(`Unexpected request: ${config.method} ${config.url}`);
const requests = JSON.parse(config.data) as { resourceId: string; variant: string }[];
const items = requests.map(({ resourceId, variant }) => {
if (!["image-0", "image-1"].includes(resourceId)) throw new Error(`Unexpected resource: ${resourceId}`);
if (!["image-0", "image-1", "image-2", "image-3"].includes(resourceId)) throw new Error(`Unexpected resource: ${resourceId}`);
return {
resourceId,
access: {
@@ -438,13 +438,13 @@ async function runCanvasBatchCommitRace() {
width: 320,
height: 240,
metadata: { taskId: `task-${index}`, status: index < 2 ? ("loading" as const) : ("error" as const) },
}));
})).filter((node) => !multiOutput || node.id !== "node-1");
const ref: { current: import("../../src/types/canvas").CanvasNodeData[] } = { current: nodes };
const setNodes = createCanvasStateWriter(ref, () => {});
useCanvasStore.getState().updateProject(projectId, { nodes });
await flushCanvasStorePersistence();
useAssetStore.setState({
assets: [0, 1].map((index) => ({
assets: (multiOutput ? [0, 1, 2, 3] : [0, 1]).map((index) => ({
id: `asset-${index}`,
kind: "image" as const,
title: "generated",
@@ -452,7 +452,7 @@ async function runCanvasBatchCommitRace() {
tags: [],
createdAt: "2026-09-21T00:00:00Z",
updatedAt: "2026-09-21T00:00:00Z",
data: { dataUrl: "/image.png", storageKey: `resource:image-${index}`, width: 640, height: 480, bytes: 100, mimeType: "image/png" },
data: { dataUrl: "/image.png", storageKey: `resource:image-${index}`, width: multiOutput ? 640 + index : 640, height: 480, bytes: 100, mimeType: "image/png" },
})),
});
unregister = registerCanvasGenerationLiveProject({ scope: getActiveUserScope(), projectId, adapter: { read: () => ({ nodes: ref.current, connections: [], chatSessions: [], activeChatId: null }), write: (state) => setNodes(state.nodes) } });
@@ -463,13 +463,24 @@ async function runCanvasBatchCommitRace() {
setNodes((current) => [...current.map((node) => (node.id === "node-0" ? { ...node, title: "用户改名", position: { x: 700, y: 500 } } : node)), { ...nodes[0], id: "new-during-save", metadata: {} }]);
};
await Promise.all(
[0, 1].map((index) =>
(multiOutput ? [0] : [0, 1]).map((index) =>
applyCanvasGenerationTaskNodeEffect({
projectId,
nodeId: `node-${index}`,
nodesRef: ref,
setNodes,
task: { id: `task-${index}`, projectId, type: "canvas_image", status: "succeeded", prompt: "test", attempts: 1, createdAt: "2026-09-21T00:00:00Z", updatedAt: "2026-09-21T00:00:01Z", resultJson: "{}" },
task: {
id: `task-${index}`,
projectId,
type: "canvas_image",
status: "succeeded",
prompt: "test",
attempts: 1,
createdAt: "2026-09-21T00:00:00Z",
updatedAt: "2026-09-21T00:00:01Z",
resultJson: multiOutput ? JSON.stringify({ images: [0, 1, 2, 3].map((i) => ({ dataUrl: "/image.png", storageKey: `resource:image-${i}` })) }) : "{}",
...(multiOutput ? { outputs: [0, 1, 2, 3].map((i) => ({ outputIndex: i, mediaType: "image" as const, materializedAssetId: `asset-${i}` })) } : {}),
},
output: { outputIndex: 0, mediaType: "image", materializedAssetId: `asset-${index}` },
effectKey: `attach:task-${index}:0`,
}),
@@ -478,7 +489,31 @@ async function runCanvasBatchCommitRace() {
useCanvasStore.getState().updateProject(projectId, { nodes: ref.current });
await flushCanvasStorePersistence();
const stored = parseCanvasStorageDocument(await localForageStorageForScope(getActiveUserScope()).getItem(CANVAS_STORE_KEY), []);
return { edited, live: ref.current, restored: stored.state.projects.find((project) => project.id === projectId)?.nodes };
const restored = stored.state.projects.find((project) => project.id === projectId)?.nodes;
const live = ref.current;
if (multiOutput) {
setNodes(restored!);
await applyCanvasGenerationTaskNodeEffect({
projectId,
nodeId: "node-0",
nodesRef: ref,
setNodes,
task: {
id: "task-0",
projectId,
type: "canvas_image",
status: "succeeded",
prompt: "test",
attempts: 1,
createdAt: "",
updatedAt: "",
resultJson: JSON.stringify({ images: [0, 1, 2, 3].map((i) => ({ dataUrl: "/image.png", storageKey: `resource:image-${i}` })) }),
},
output: { outputIndex: 0, mediaType: "image", materializedAssetId: "asset-0" },
effectKey: "attach:task-0:0",
});
}
return { edited, live, restored, replayed: ref.current };
} finally {
unregister?.();
apiClient.defaults.adapter = originalAdapter;
@@ -496,9 +531,11 @@ self.onmessage = async (event: MessageEvent<Scenario>) => {
? await runScopeCleanupAfterSwitch()
: event.data === "scope-cleanup-late-canvas-reference"
? await runScopeCleanupAfterLateCanvasReference()
: event.data === "canvas-batch-commit-race"
? await runCanvasBatchCommitRace()
: await runMediaCommitRace(event.data === "audio-commit-race" ? "audio" : "video");
: event.data === "canvas-multi-output"
? await runCanvasBatchCommitRace(true)
: event.data === "canvas-batch-commit-race"
? await runCanvasBatchCommitRace()
: await runMediaCommitRace(event.data === "audio-commit-race" ? "audio" : "video");
self.postMessage({ ok: true, result });
} catch (error) {
self.postMessage({ ok: false, error: error instanceof Error ? `${error.name}: ${error.message}` : String(error) });