ai-chatbot/lib/video-canvas/story-task-attachment.ts

97 lines
3.0 KiB
TypeScript

import { z } from "zod";
export const storyTaskAttachmentSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("story-asset"),
nodeId: z.string().min(1),
asset: z.object({
id: z.string().min(1),
name: z.string(),
description: z.string(),
imageUrl: z.string(),
type: z.string(),
}),
}),
z.object({
type: z.literal("story-prompts"),
nodeId: z.string().min(1),
rows: z
.array(z.object({ id: z.string().min(1) }).catchall(z.unknown()))
.min(1)
.max(120),
}),
]);
export type StoryTaskAttachment = z.infer<typeof storyTaskAttachmentSchema>;
export function requireStoryPromptResult(data: any, attachment?: StoryTaskAttachment) {
if (attachment?.type !== "story-prompts") return;
if (
!Array.isArray(data?.analysisRows) ||
data.analysisRows.length !== attachment.rows.length ||
data.analysisRows.some(
(row: any) => !String(row.imagePrompt || "").trim() || !String(row.motionPrompt || "").trim(),
)
)
throw Error("未返回完整镜头提示词,原内容已保留");
}
export function mergeStoryTaskResult(
draft: Record<string, any>,
attachment: StoryTaskAttachment,
data: any,
taskId: string,
) {
if (draft.storyTaskResults?.[taskId]) return draft;
let conflict = false;
let patch: Record<string, any> = {};
if (attachment.type === "story-asset") {
let found = false;
const url = data.images?.[0]?.originalUrl || data.images?.[0]?.thumbnailUrl;
patch.storyAssets = (draft.storyAssets || []).map((asset: any) => {
if (asset.id !== attachment.asset.id) return asset;
found = true;
if (
!url ||
["name", "description", "type", "imageUrl"].some(
(key) => asset[key] !== attachment.asset[key as keyof typeof attachment.asset],
)
) {
conflict = true;
return asset;
}
return { ...asset, imageUrl: url };
});
if (!found) conflict = true;
} else {
requireStoryPromptResult(data, attachment);
const byId = new Map(attachment.rows.map((row, index) => [row.id, { row, index }]));
let found = 0;
patch.rows = (draft.rows || []).map((row: any) => {
const expected = byId.get(row.id);
if (!expected) return row;
found++;
if (
Object.keys(expected.row).some(
(key) => JSON.stringify(row[key]) !== JSON.stringify(expected.row[key]),
)
) {
conflict = true;
return row;
}
const generated = data.analysisRows[expected.index];
return { ...row, imagePrompt: generated.imagePrompt, motionPrompt: generated.motionPrompt };
});
if (found !== attachment.rows.length) conflict = true;
}
return {
...draft,
...patch,
storyTaskResults: {
...draft.storyTaskResults,
[taskId]: {
status: conflict ? "conflict" : "applied",
message: conflict
? "生成期间内容有修改或删除,已保留原内容;新结果在画布的生成节点中。"
: "生成结果已同步到脚本。",
},
},
};
}