feat(agent): 添加视频创作工作流技能系统和流程工具
新增基于 SKILL.md 的视频创作工作流系统,Agent 可通过 skills 目录加载结构化的导演指令;实现 validate_storyboard、update_manifest_items、confirm_images 三个流程工具支撑分镜校验、提示词更新和图片确认。
This commit is contained in:
70
web/server/agent/tools/validate-storyboard.ts
Normal file
70
web/server/agent/tools/validate-storyboard.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
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')}` : ''}`;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user