ai-chatbot/lib/video-agent/skills/storyboard-mapper.ts
Your Name ec988be30a
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
Update agent workflow and video task handling
2026-03-13 11:21:42 +08:00

94 lines
2.6 KiB
TypeScript

import "server-only";
import type {
MappedStoryboardShot,
StoryboardTile,
NormalizedStoryboardScript,
} from "@/lib/video-agent/types";
function clampIndex(value: number, length: number) {
if (length <= 0) return 0;
const rounded = Math.max(1, Math.round(value));
return Math.min(length, rounded) - 1;
}
function buildShotPrompt(params: {
style: string;
action: string;
camera: string;
mood: string;
language: "zh" | "en" | "mixed";
}) {
const { style, action, camera, mood, language } = params;
if (language === "en") {
return [
"Animate this still image into a coherent short scene.",
`Action: ${action}.`,
`Camera: ${camera}.`,
`Mood: ${mood}.`,
`Style: ${style}.`,
"Keep identity and key structure stable.",
].join(" ");
}
return [
"请基于当前输入图片生成连贯的视频镜头。",
`动作:${action}。`,
`镜头:${camera}。`,
`氛围:${mood}。`,
`风格:${style}。`,
"保持主体身份、构图和关键细节稳定。",
].join("");
}
export function runStoryboardMapperSkill(params: {
script: NormalizedStoryboardScript;
tiles: StoryboardTile[];
}): MappedStoryboardShot[] {
const { script, tiles } = params;
if (!tiles.length) return [];
const usedTileIndexes = new Set<number>();
const nextAvailableTile = (startIndex = 1) => {
for (let index = Math.max(1, startIndex); index <= tiles.length; index += 1) {
if (!usedTileIndexes.has(index)) return index;
}
for (let index = 1; index <= tiles.length; index += 1) {
if (!usedTileIndexes.has(index)) return index;
}
return Math.max(1, Math.min(tiles.length, startIndex));
};
return script.shots.map((shot, index) => {
const preferredTileIndex =
shot.preferredTileIndex && shot.preferredTileIndex >= 1 && shot.preferredTileIndex <= tiles.length
? shot.preferredTileIndex
: shot.index || index + 1;
const pickedTileIndex = usedTileIndexes.has(preferredTileIndex)
? nextAvailableTile(index + 1)
: preferredTileIndex;
usedTileIndexes.add(pickedTileIndex);
const tile =
tiles[clampIndex(pickedTileIndex, tiles.length)] || tiles[index] || tiles[0];
const prompt = buildShotPrompt({
style: script.globalStyle,
action: shot.action,
camera: shot.camera,
mood: shot.mood,
language: script.language,
});
return {
shotIndex: shot.index,
tileIndex: tile.index,
tileImageUrl: tile.imageUrl,
durationSec: shot.durationSec,
prompt,
negativePrompt: shot.negativePrompt,
action: shot.action,
camera: shot.camera,
mood: shot.mood,
};
});
}