新增基于 SKILL.md 的视频创作工作流系统,Agent 可通过 skills 目录加载结构化的导演指令;实现 validate_storyboard、update_manifest_items、confirm_images 三个流程工具支撑分镜校验、提示词更新和图片确认。
71 lines
3.1 KiB
TypeScript
71 lines
3.1 KiB
TypeScript
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')}` : ''}`;
|
||
},
|
||
};
|