Files
video-create/web/server/agent/tools/validate-storyboard.ts
sion123 e16305840b feat(agent): 添加视频创作工作流技能系统和流程工具
新增基于 SKILL.md 的视频创作工作流系统,Agent 可通过 skills 目录加载结构化的导演指令;实现 validate_storyboard、update_manifest_items、confirm_images 三个流程工具支撑分镜校验、提示词更新和图片确认。
2026-05-08 01:54:04 +08:00

71 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ToolDefinition } from './types';
export const validateStoryboard: ToolDefinition = {
name: 'validate_storyboard',
description: '校验分镜脚本质量TTS 估算 ≤ 6s、ratio 预检、script 拼接校验。返回校验结果和问题列表。',
input_schema: {
type: 'object',
properties: {
items: { type: 'string', description: '分镜 JSON 数组字符串,每个元素需包含 shotDesc、script 字段,可选 duration、directorRef' },
videoModelDuration: { type: 'number', description: '视频模型固定时长(秒),默认 6' },
},
required: ['items'],
},
execute: async (params) => {
const { items, videoModelDuration = 6 } = params as { items: string; videoModelDuration?: number };
let parsed: any[];
try { parsed = JSON.parse(items); } catch { return '错误: items 不是合法 JSON'; }
if (!Array.isArray(parsed) || parsed.length === 0) return '错误: items 必须是非空数组';
const errors: string[] = [];
const warnings: string[] = [];
const TTS_SPEED = 1.15;
const CHARS_PER_SEC = 5;
for (const item of parsed) {
const idx = item.id ?? parsed.indexOf(item) + 1;
const script: string = item.script || '';
const charCount = script.length;
const ttsEstimate = charCount / CHARS_PER_SEC;
const audioDur = ttsEstimate * TTS_SPEED;
const ratio = videoModelDuration / audioDur;
if (!item.shotDesc) errors.push(`Shot ${idx}: 缺少 shotDesc`);
if (!script) errors.push(`Shot ${idx}: 缺少 script`);
// TTS 估算检查
if (ttsEstimate > 6) {
errors.push(`Shot ${idx}: TTS 估算 ${ttsEstimate.toFixed(1)}s > 6s必须拆分 (script: ${script.slice(0, 30)}...)`);
}
// ratio 预检
if (ratio < 0.9) {
errors.push(`Shot ${idx}: ratio ${ratio.toFixed(2)} < 0.9,音频太长需拆分 (audio=${audioDur.toFixed(1)}s, video=${videoModelDuration}s)`);
}
if (!item.directorRef) {
warnings.push(`Shot ${idx}: 建议填写 directorRef`);
}
}
// script 拼接校验 - 返回统计而非原文比对(原文由用户提供)
const totalChars = parsed.reduce((sum: number, i: any) => sum + (i.script?.length || 0), 0);
const totalAudio = (totalChars / CHARS_PER_SEC) * TTS_SPEED;
const result = {
valid: errors.length === 0,
shotCount: parsed.length,
totalChars,
estimatedTotalAudio: `${totalAudio.toFixed(1)}s`,
errors,
warnings,
};
if (errors.length > 0) {
return `❌ 分镜校验未通过 (${errors.length} 个问题):\n\n${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}${warnings.length ? `\n\n⚠ 警告:\n${warnings.map((w, i) => `${i + 1}. ${w}`).join('\n')}` : ''}\n\n统计: ${parsed.length} 个镜头, 总字数 ${totalChars}, 音频估算 ${totalAudio.toFixed(1)}s`;
}
return `✅ 分镜校验通过\n\n统计: ${parsed.length} 个镜头, 总字数 ${totalChars}, 音频估算 ${totalAudio.toFixed(1)}s${warnings.length ? `\n\n⚠ 警告:\n${warnings.map((w, i) => `${i + 1}. ${w}`).join('\n')}` : ''}`;
},
};