cerevox 2.43.1 → 3.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/core/ai.d.ts +40 -5
  2. package/dist/core/ai.d.ts.map +1 -1
  3. package/dist/core/ai.js +92 -23
  4. package/dist/core/ai.js.map +1 -1
  5. package/dist/mcp/servers/prompts/actions/storyboard_optimization.md +5 -5
  6. package/dist/{utils/videoproject-schema.json → mcp/servers/prompts/draft_content-schema.json} +3 -3
  7. package/dist/mcp/servers/prompts/rules/creative-ad.md +6 -6
  8. package/dist/mcp/servers/prompts/rules/expert.md +25 -25
  9. package/dist/mcp/servers/prompts/rules/freeform.md +2 -3
  10. package/dist/mcp/servers/prompts/rules/general-video.md +10 -10
  11. package/dist/mcp/servers/prompts/rules/material-creation.md +2 -2
  12. package/dist/mcp/servers/prompts/rules/music-video.md +3 -3
  13. package/dist/mcp/servers/prompts/rules/stage-play.md +4 -4
  14. package/dist/mcp/servers/prompts/rules/story-telling.md +8 -8
  15. package/dist/mcp/servers/prompts/skills/storyboard/storyboard-optimization-skill.md +5 -5
  16. package/dist/mcp/servers/prompts/skills/video/continuity-techniques.md +1 -1
  17. package/dist/mcp/servers/prompts/skills/workflows/general-video.md +10 -10
  18. package/dist/mcp/servers/prompts/skills/workflows/music-video.md +3 -3
  19. package/dist/mcp/servers/prompts/zerocut-core.md +26 -27
  20. package/dist/mcp/servers/zerocut.d.ts.map +1 -1
  21. package/dist/mcp/servers/zerocut.js +464 -529
  22. package/dist/mcp/servers/zerocut.js.map +1 -1
  23. package/dist/utils/coze.d.ts +1 -0
  24. package/dist/utils/coze.d.ts.map +1 -1
  25. package/dist/utils/coze.js +19 -0
  26. package/dist/utils/coze.js.map +1 -1
  27. package/package.json +2 -2
  28. package/dist/timeline-editor/index.d.ts +0 -42
  29. package/dist/timeline-editor/index.d.ts.map +0 -1
  30. package/dist/timeline-editor/index.js +0 -82
  31. package/dist/timeline-editor/index.js.map +0 -1
  32. package/dist/timeline-editor/public/app.js +0 -2086
  33. package/dist/timeline-editor/public/index.html +0 -141
  34. package/dist/timeline-editor/public/style.css +0 -695
  35. package/dist/timeline-editor/server.d.ts +0 -137
  36. package/dist/timeline-editor/server.d.ts.map +0 -1
  37. package/dist/timeline-editor/server.js +0 -418
  38. package/dist/timeline-editor/server.js.map +0 -1
  39. /package/dist/{utils → mcp/servers/prompts}/storyboard-schema.json +0 -0
@@ -53,86 +53,9 @@ const node_path_1 = __importStar(require("node:path"));
53
53
  const doubao_voices_full_1 = require("./helper/doubao_voices_full");
54
54
  const node_fs_1 = require("node:fs");
55
55
  const coze_1 = require("../../utils/coze");
56
- const node_child_process_1 = require("node:child_process");
57
- const node_util_1 = require("node:util");
58
- const os = __importStar(require("node:os"));
59
56
  const mp3_duration_1 = __importDefault(require("mp3-duration"));
60
57
  const image_size_1 = __importDefault(require("image-size"));
61
- const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
62
- // 错误处理工具函数
63
- const handleError = (error) => {
64
- if (error instanceof Error) {
65
- return error.message;
66
- }
67
- return String(error);
68
- };
69
- // 检查端口是否被占用 - 跨平台版本
70
- const isPortInUse = async (port) => {
71
- try {
72
- const platform = os.platform();
73
- let command;
74
- if (platform === 'win32') {
75
- // Windows 使用 netstat
76
- command = `netstat -ano | findstr :${port}`;
77
- }
78
- else {
79
- // Unix/Linux/macOS 使用 lsof
80
- command = `lsof -ti:${port}`;
81
- }
82
- const { stdout } = await execAsync(command);
83
- return stdout.trim().length > 0;
84
- }
85
- catch {
86
- return false;
87
- }
88
- };
89
- // 停止占用端口的进程 - 跨平台版本
90
- const killPortProcess = async (port) => {
91
- try {
92
- const platform = os.platform();
93
- if (platform === 'win32') {
94
- // Windows 版本
95
- const { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
96
- const lines = stdout.trim().split('\n');
97
- for (const line of lines) {
98
- const parts = line.trim().split(/\s+/);
99
- const pid = parts[parts.length - 1];
100
- if (pid && /^\d+$/.test(pid)) {
101
- try {
102
- await execAsync(`taskkill /F /PID ${pid}`);
103
- console.log(`Killed process ${pid} on port ${port}`);
104
- }
105
- catch (error) {
106
- console.warn(`Failed to kill process ${pid}:`, error);
107
- }
108
- }
109
- }
110
- }
111
- else {
112
- // Unix/Linux/macOS 版本
113
- const { stdout } = await execAsync(`lsof -ti:${port}`);
114
- const pids = stdout
115
- .trim()
116
- .split('\n')
117
- .filter(pid => pid);
118
- for (const pid of pids) {
119
- try {
120
- await execAsync(`kill -9 ${pid}`);
121
- console.log(`Killed process ${pid} on port ${port}`);
122
- }
123
- catch (error) {
124
- console.warn(`Failed to kill process ${pid}:`, error);
125
- }
126
- }
127
- }
128
- // 等待一下确保进程被杀死
129
- await new Promise(resolve => setTimeout(resolve, 1000));
130
- }
131
- catch (error) {
132
- console.warn(`Failed to kill processes on port ${port}:`, error);
133
- }
134
- };
135
- function createErrorResponse(error, operation) {
58
+ function createErrorResponse(error, operation, details) {
136
59
  const errorMessage = error instanceof Error ? error.message : String(error);
137
60
  console.error(`[${operation}] Error:`, error);
138
61
  return {
@@ -144,6 +67,7 @@ function createErrorResponse(error, operation) {
144
67
  error: errorMessage,
145
68
  operation,
146
69
  timestamp: new Date().toISOString(),
70
+ details,
147
71
  }),
148
72
  },
149
73
  ],
@@ -159,6 +83,10 @@ async function validateSession(operation) {
159
83
  session = null;
160
84
  throw new Error(`Session not initialized. Please call 'project-open' first before using ${operation}.`);
161
85
  }
86
+ const projectRulesFile = (0, node_path_1.resolve)(projectLocalDir, '.trae', 'rules', `project_rules.md`);
87
+ if (!(0, node_fs_1.existsSync)(projectRulesFile)) {
88
+ throw new Error(`Project rules file not found: ${projectRulesFile}. Please call 'retrieve-rules-context' first.`);
89
+ }
162
90
  return session;
163
91
  }
164
92
  // 文件名验证
@@ -173,20 +101,6 @@ function validateFileName(fileName) {
173
101
  }
174
102
  return fileName.trim();
175
103
  }
176
- // 图片文件验证
177
- function validateImageFile(localPath) {
178
- if (!localPath || localPath.trim() === '') {
179
- throw new Error('Local path cannot be empty');
180
- }
181
- const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
182
- const extension = localPath
183
- .toLowerCase()
184
- .substring(localPath.lastIndexOf('.'));
185
- if (!allowedExtensions.includes(extension)) {
186
- throw new Error(`Unsupported image format: ${extension}. Allowed formats: ${allowedExtensions.join(', ')}`);
187
- }
188
- return localPath.trim();
189
- }
190
104
  function getMaterialUri(session, fileName) {
191
105
  return session.sandbox.getUrl(`/zerocut/${session.terminal.id}/materials/${(0, node_path_1.basename)(fileName)}`);
192
106
  }
@@ -211,7 +125,7 @@ async function initProject(session) {
211
125
  // 创建素材目录和成品目录 materials、output
212
126
  await (await terminal.run('mkdir materials output')).end();
213
127
  // 文件包括(大部分不需要此时创建)
214
- // story_board.json 故事版
128
+ // storyboard.json 故事版
215
129
  // draft_content.json 草稿内容,Agent在创作过程中,会根据项目规范,自动生成和修改该文件
216
130
  return workDir;
217
131
  }
@@ -692,6 +606,7 @@ server.registerTool('project-open', {
692
606
  }
693
607
  const result = {
694
608
  success: true,
609
+ nextActionSuggest: '检查规则上下文是否已召回,若未召回,调用 retrieve_rules 工具召回规则上下文',
695
610
  sessionId: session.id,
696
611
  workDir,
697
612
  projectLocalDir,
@@ -825,7 +740,10 @@ server.registerTool('generate-character-image', {
825
740
  title: 'Generate Character Image',
826
741
  description: 'Generate a turnaround image or portrait for any character.',
827
742
  inputSchema: {
828
- type: zod_1.z.enum(['banana', 'seedream']).optional().default('banana'),
743
+ type: zod_1.z
744
+ .enum(['banana', 'banana-pro', 'seedream'])
745
+ .optional()
746
+ .default('banana'),
829
747
  name: zod_1.z.string().describe('The name of the character.'),
830
748
  gender: zod_1.z
831
749
  .enum(['male', 'female'])
@@ -1155,10 +1073,13 @@ server.registerTool('generate-image', {
1155
1073
  title: 'Generate Image',
1156
1074
  description: `生成图片`,
1157
1075
  inputSchema: {
1158
- type: zod_1.z.enum(['banana', 'seedream']).optional().default('seedream'),
1076
+ type: zod_1.z
1077
+ .enum(['banana', 'banana-pro', 'seedream'])
1078
+ .optional()
1079
+ .default('seedream'),
1159
1080
  prompt: zod_1.z
1160
1081
  .string()
1161
- .describe('The prompt to generate. 一般要严格对应 story_board 中当前场景的 start_frame 或 end_frame 中的字段描述'),
1082
+ .describe('The prompt to generate. 一般要严格对应 storyboard 中当前场景的 start_frame 或 end_frame 中的字段描述'),
1162
1083
  sceneIndex: zod_1.z
1163
1084
  .number()
1164
1085
  .min(1)
@@ -1167,7 +1088,7 @@ server.registerTool('generate-image', {
1167
1088
  storyBoardFile: zod_1.z
1168
1089
  .string()
1169
1090
  .optional()
1170
- .default('story_board.json')
1091
+ .default('storyboard.json')
1171
1092
  .describe('故事板文件路径'),
1172
1093
  skipConsistencyCheck: zod_1.z
1173
1094
  .boolean()
@@ -1252,7 +1173,7 @@ server.registerTool('generate-image', {
1252
1173
  \`\`\`
1253
1174
  `),
1254
1175
  },
1255
- }, async ({ type = 'seedream', prompt, sceneIndex, storyBoardFile = 'story_board.json', skipConsistencyCheck = false, size = '720x1280', saveToFileName, watermark, referenceImages, optimizePrompt, }) => {
1176
+ }, async ({ type = 'seedream', prompt, sceneIndex, storyBoardFile = 'storyboard.json', skipConsistencyCheck = false, size = '720x1280', saveToFileName, watermark, referenceImages, optimizePrompt, }) => {
1256
1177
  try {
1257
1178
  // 验证session状态
1258
1179
  const currentSession = await validateSession('generate-image');
@@ -1260,10 +1181,10 @@ server.registerTool('generate-image', {
1260
1181
  // 检查 storyboard 标志
1261
1182
  if (!checkStoryboardFlag && (0, node_fs_1.existsSync)(storyBoardPath)) {
1262
1183
  checkStoryboardFlag = true;
1263
- return createErrorResponse('必须先审查生成的 story_board.json 内容,确保每个场景中的stage_atmosphere内容按照规则被正确融合到start_frame和video_prompt中,不得遗漏,检查完成后先汇报,如果有问题,应当先修改 story_board.json 内容,然后再调用 generate-image 生成图片。注意修改 story_board 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!', 'generate-image');
1184
+ return createErrorResponse('必须先审查生成的 storyboard.json 内容,确保每个场景中的stage_atmosphere内容按照规则被正确融合到start_frame和video_prompt中,不得遗漏,检查完成后先汇报,如果有问题,应当先修改 storyboard.json 内容,然后再调用 generate-image 生成图片。注意修改 storyboard 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!', 'generate-image');
1264
1185
  }
1265
1186
  const validatedFileName = validateFileName(saveToFileName);
1266
- // 校验 prompt 与 story_board.json 中场景设定的一致性
1187
+ // 校验 prompt 与 storyboard.json 中场景设定的一致性
1267
1188
  if (sceneIndex && !skipConsistencyCheck) {
1268
1189
  try {
1269
1190
  if ((0, node_fs_1.existsSync)(storyBoardPath)) {
@@ -1283,9 +1204,9 @@ server.registerTool('generate-image', {
1283
1204
  const endFrame = scene.end_frame;
1284
1205
  // 检查 prompt 是否严格等于 start_frame 或 end_frame
1285
1206
  if (prompt !== startFrame && prompt !== endFrame) {
1286
- return createErrorResponse('图片提示词必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-image');
1207
+ return createErrorResponse('图片提示词必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-image');
1287
1208
  }
1288
- // 校验 size 参数与 story_board 的 orientation 属性一致性
1209
+ // 校验 size 参数与 storyboard 的 orientation 属性一致性
1289
1210
  if (size && storyBoard.orientation) {
1290
1211
  const isLandscapeSize = [
1291
1212
  '1152x864',
@@ -1330,7 +1251,7 @@ server.registerTool('generate-image', {
1330
1251
  }
1331
1252
  }
1332
1253
  else {
1333
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
1254
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
1334
1255
  }
1335
1256
  }
1336
1257
  }
@@ -1340,7 +1261,7 @@ server.registerTool('generate-image', {
1340
1261
  }
1341
1262
  catch (error) {
1342
1263
  console.error('Failed to validate prompt with story board:', error);
1343
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
1264
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
1344
1265
  }
1345
1266
  }
1346
1267
  // 检查并替换英文单引号包裹的中文内容为中文双引号
@@ -1520,6 +1441,11 @@ server.registerTool('edit-image', {
1520
1441
  description: 'Edit the image.',
1521
1442
  inputSchema: {
1522
1443
  prompt: zod_1.z.string().describe('要编辑图片的中文提示词'),
1444
+ type: zod_1.z
1445
+ .enum(['banana-pro', 'banana', 'seedream'])
1446
+ .optional()
1447
+ .default('seedream')
1448
+ .describe('The type of image model to use.'),
1523
1449
  sourceImageFileName: zod_1.z.string().describe('The source image file name.'),
1524
1450
  saveToFileName: zod_1.z.string().describe('The filename to save.'),
1525
1451
  size: zod_1.z
@@ -1563,7 +1489,7 @@ server.registerTool('edit-image', {
1563
1489
  .default(false)
1564
1490
  .describe('Whether to add watermark to the image.'),
1565
1491
  },
1566
- }, async ({ prompt, sourceImageFileName, saveToFileName, size, watermark }) => {
1492
+ }, async ({ prompt, type, sourceImageFileName, saveToFileName, size, watermark, }) => {
1567
1493
  try {
1568
1494
  // 验证session状态
1569
1495
  const currentSession = await validateSession('edit-image');
@@ -1589,8 +1515,9 @@ server.registerTool('edit-image', {
1589
1515
  const base64String = `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
1590
1516
  imageBase64Array.push(base64String);
1591
1517
  const ai = currentSession.ai;
1592
- const processedPrompt = `转绘参考图片,${prompt}`;
1518
+ const processedPrompt = `转绘我提供的参考图片,${prompt}`;
1593
1519
  const res = await ai.generateImage({
1520
+ type,
1594
1521
  prompt: processedPrompt,
1595
1522
  size,
1596
1523
  watermark,
@@ -1647,7 +1574,7 @@ server.registerTool('generate-video', {
1647
1574
  inputSchema: {
1648
1575
  prompt: zod_1.z
1649
1576
  .string()
1650
- .describe('The prompt to generate. 一般要严格对应 story_board 中当前场景的 video_prompt 字段描述'),
1577
+ .describe('The prompt to generate. 一般要严格对应 storyboard 中当前场景的 video_prompt 字段描述'),
1651
1578
  sceneIndex: zod_1.z
1652
1579
  .number()
1653
1580
  .min(1)
@@ -1656,7 +1583,7 @@ server.registerTool('generate-video', {
1656
1583
  storyBoardFile: zod_1.z
1657
1584
  .string()
1658
1585
  .optional()
1659
- .default('story_board.json')
1586
+ .default('storyboard.json')
1660
1587
  .describe('故事板文件路径'),
1661
1588
  skipConsistencyCheck: zod_1.z
1662
1589
  .boolean()
@@ -1691,7 +1618,7 @@ server.registerTool('generate-video', {
1691
1618
  .describe('The image file name of the start frame.'),
1692
1619
  duration: zod_1.z
1693
1620
  .number()
1694
- .min(2)
1621
+ .min(1)
1695
1622
  .max(23)
1696
1623
  .describe('The duration of the video. 一般与 tts 配音音频时长向上取整秒(ceil)一致,或者与 timeline_analysis 中确定的歌曲片段时长一致'),
1697
1624
  end_frame: zod_1.z
@@ -1718,7 +1645,7 @@ server.registerTool('generate-video', {
1718
1645
  .default(false)
1719
1646
  .describe('Whether to optimize the prompt.'),
1720
1647
  },
1721
- }, async ({ prompt, sceneIndex, storyBoardFile = 'story_board.json', skipConsistencyCheck = false, saveToFileName, start_frame, end_frame, duration, watermark, resolution, type, optimizePrompt, saveLastFrameAs, }, context) => {
1648
+ }, async ({ prompt, sceneIndex, storyBoardFile = 'storyboard.json', skipConsistencyCheck = false, saveToFileName, start_frame, end_frame, duration, watermark, resolution, type, optimizePrompt, saveLastFrameAs, }, context) => {
1722
1649
  try {
1723
1650
  // 验证session状态
1724
1651
  const currentSession = await validateSession('generate-video');
@@ -1739,7 +1666,7 @@ server.registerTool('generate-video', {
1739
1666
  console.warn(`zero 模型的视频仅支持 1080p 分辨率,用户指定的分辨率为 %s,已自动将 ${resolution} 转换为 1080p`, resolution);
1740
1667
  resolution = '1080p';
1741
1668
  }
1742
- // 校验 prompt 与 story_board.json 中场景设定的一致性以及视频时长与 timeline_analysis.json 中 proposed_video_scenes 的匹配
1669
+ // 校验 prompt 与 storyboard.json 中场景设定的一致性以及视频时长与 timeline_analysis.json 中 proposed_video_scenes 的匹配
1743
1670
  if (sceneIndex && !skipConsistencyCheck) {
1744
1671
  try {
1745
1672
  const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
@@ -1758,7 +1685,7 @@ server.registerTool('generate-video', {
1758
1685
  if (scene) {
1759
1686
  const videoPrompt = scene.video_prompt;
1760
1687
  if (videoPrompt && prompt !== videoPrompt) {
1761
- return createErrorResponse('视频提示词必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video');
1688
+ return createErrorResponse('视频提示词必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video');
1762
1689
  }
1763
1690
  if (scene.is_continuous && !end_frame) {
1764
1691
  return createErrorResponse('连续场景必须指定end_frame参数,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video');
@@ -1771,7 +1698,7 @@ server.registerTool('generate-video', {
1771
1698
  }
1772
1699
  }
1773
1700
  else {
1774
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
1701
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
1775
1702
  }
1776
1703
  }
1777
1704
  }
@@ -1781,7 +1708,7 @@ server.registerTool('generate-video', {
1781
1708
  }
1782
1709
  catch (error) {
1783
1710
  console.error('Failed to validate prompt with story board:', error);
1784
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
1711
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
1785
1712
  }
1786
1713
  // 校验视频时长与 timeline_analysis.json 中 proposed_video_scenes 的匹配
1787
1714
  try {
@@ -1818,10 +1745,10 @@ server.registerTool('generate-video', {
1818
1745
  if (!checkAudioVideoDurationFlag) {
1819
1746
  checkAudioVideoDurationFlag = true;
1820
1747
  if (scene.audio_mode === 'vo_sync') {
1821
- return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 story_board 中视频时长为音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1748
+ return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 storyboard 中视频时长为音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1822
1749
  }
1823
1750
  else if (scene.audio_mode === 'dialogue') {
1824
- return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 story_board 中视频时长**不小于**音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1751
+ return createErrorResponse('请先自我检查 media_logs 中的音频时长,确保 storyboard 中视频时长**不小于**音频时长向上取整 即 ceil(音频时长),然后再按照正确的视频时长创建视频', 'generate-video');
1825
1752
  }
1826
1753
  }
1827
1754
  }
@@ -1946,10 +1873,10 @@ server.registerTool('generate-video', {
1946
1873
  type: 'string',
1947
1874
  description: 'kenburns 视频 宽x高',
1948
1875
  },
1949
- },
1950
- duration: {
1951
- type: 'number',
1952
- description: 'kenburns 视频 时长',
1876
+ duration: {
1877
+ type: 'number',
1878
+ description: 'kenburns 视频 时长',
1879
+ },
1953
1880
  },
1954
1881
  required: ['camera_motion', 'size', 'duration'],
1955
1882
  },
@@ -2091,7 +2018,7 @@ server.registerTool('generate-video', {
2091
2018
  console.warn('Failed to send progress update:', progressError);
2092
2019
  }
2093
2020
  },
2094
- waitForFinish: type !== 'zero', // zero 模型由于时间比较长,因此要异步查询而不是等待
2021
+ waitForFinish: true,
2095
2022
  });
2096
2023
  if (!res) {
2097
2024
  throw new Error('Failed to generate video: no response from AI service');
@@ -2139,7 +2066,7 @@ server.registerTool('generate-video', {
2139
2066
  type: 'text',
2140
2067
  text: JSON.stringify({
2141
2068
  success: true,
2142
- message: '该视频生成任务已提交,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如该工具调用超时,你应立即再次重新调用直到任务结束。',
2069
+ message: '该视频生成任务正在运行中,它是异步任务,且执行时间较长,你应立即调用工具 wait-for-task-finish 来等待任务结束,如该工具调用超时,你应立即再次重新调用直到任务结束。',
2143
2070
  taskUrl: res.taskUrl,
2144
2071
  }),
2145
2072
  },
@@ -2194,6 +2121,7 @@ server.registerTool('wait-for-task-finish', {
2194
2121
  console.warn('Failed to send progress update:', progressError);
2195
2122
  }
2196
2123
  },
2124
+ timeout: 300000,
2197
2125
  });
2198
2126
  if (res.url) {
2199
2127
  const uri = await saveMaterial(currentSession, res.url, saveToFileName);
@@ -2423,7 +2351,7 @@ server.registerTool('generate-scene-tts', {
2423
2351
  storyBoardFile: zod_1.z
2424
2352
  .string()
2425
2353
  .optional()
2426
- .default('story_board.json')
2354
+ .default('storyboard.json')
2427
2355
  .describe('故事板文件路径'),
2428
2356
  skipConsistencyCheck: zod_1.z
2429
2357
  .boolean()
@@ -2478,7 +2406,9 @@ server.registerTool('generate-scene-tts', {
2478
2406
  'ASMR',
2479
2407
  ])
2480
2408
  .optional(),
2481
- voiceID: zod_1.z.string().describe(`适合作为视频配音的音色ID`),
2409
+ voiceID: zod_1.z
2410
+ .string()
2411
+ .describe(`适合作为视频配音的音色ID,除非用户指定,否则你必须已通过 search_voice 工具检查确定该音色确实是存在的。`),
2482
2412
  explicit_language: zod_1.z.enum(['zh', 'en', 'ja']).optional().default('zh'),
2483
2413
  },
2484
2414
  }, async ({ text, sceneIndex, storyBoardFile, skipConsistencyCheck, voiceID, saveToFileName, speed, pitch, volume, emotion, explicit_language, }) => {
@@ -2489,7 +2419,7 @@ server.registerTool('generate-scene-tts', {
2489
2419
  const finalSpeed = speed ?? 1;
2490
2420
  volume = volume ?? 1;
2491
2421
  let scene = null;
2492
- // 校验 text 与 story_board.json 中场景设定的一致性
2422
+ // 校验 text 与 storyboard.json 中场景设定的一致性
2493
2423
  if (sceneIndex && !skipConsistencyCheck) {
2494
2424
  try {
2495
2425
  const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
@@ -2524,11 +2454,11 @@ server.registerTool('generate-scene-tts', {
2524
2454
  }
2525
2455
  }
2526
2456
  if (!isValidText) {
2527
- return createErrorResponse('配音文本必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-scene-tts');
2457
+ return createErrorResponse('配音文本必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-scene-tts');
2528
2458
  }
2529
2459
  }
2530
2460
  else {
2531
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
2461
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
2532
2462
  }
2533
2463
  }
2534
2464
  }
@@ -2538,11 +2468,14 @@ server.registerTool('generate-scene-tts', {
2538
2468
  }
2539
2469
  catch (error) {
2540
2470
  console.error('Failed to validate text with story board:', error);
2541
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
2471
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
2542
2472
  }
2543
2473
  }
2544
2474
  console.log(`Generating TTS with voice: ${voiceID}, speed: ${finalSpeed}, text: ${text.substring(0, 100)}...`);
2545
2475
  const ai = currentSession.ai;
2476
+ if (voiceID.startsWith('BV0')) {
2477
+ throw new Error(`BV0* 系列音色已弃用,你必须已通过 search_voice 工具检查确定该音色确实是存在的。`);
2478
+ }
2546
2479
  const type = voiceID.startsWith('zh_') ||
2547
2480
  voiceID.startsWith('en_') ||
2548
2481
  voiceID.startsWith('multi_') ||
@@ -2592,7 +2525,7 @@ server.registerTool('generate-scene-tts', {
2592
2525
  console.log('TTS generated successfully, saving to materials...');
2593
2526
  const { url, duration, ...opts } = res;
2594
2527
  if (!skipConsistencyCheck && duration > 16) {
2595
- return createErrorResponse('TTS duration exceeds 16 seconds, 建议调整文本长度、提升语速或拆分场景...,⚠️如简化文本内容或拆分文本,需要立即更新 story_board 以保持内容同步!如仍要生成,可设置 skipConsistencyCheck 为 true,跳过一致性检查。', 'generate-scene-tts');
2528
+ return createErrorResponse('TTS duration exceeds 16 seconds, 建议调整文本长度、提升语速或拆分场景...,⚠️如简化文本内容或拆分文本,需要立即更新 storyboard 以保持内容同步!如仍要生成,可设置 skipConsistencyCheck 为 true,跳过一致性检查。', 'generate-scene-tts');
2596
2529
  }
2597
2530
  if (!duration) {
2598
2531
  return createErrorResponse('TTS duration not returned from AI service', 'generate-scene-tts');
@@ -2678,8 +2611,8 @@ server.registerTool('compile-and-run', {
2678
2611
  checkStoryboardSubtitlesFlag = true;
2679
2612
  return createErrorResponse(`请先对 draft_content 进行以下一致性检查:
2680
2613
 
2681
- 1. 检查字幕文字内容是否与 story_board 中各个场景的 script 或 dialog 内容完全一致(⚠️ 允许字幕分段展示,只要最终文本保持一致就行)
2682
- 2. 检查视频 resolution 设定是否与 story_board 的 orientation 设置一致,默认 720p 情况下视频尺寸应为横屏 1280x720,竖屏 720x1280,若视频为 1080p 则尺寸应分别为横屏 1920x1080 和竖屏 1080x1920,切勿设反
2614
+ 1. 检查字幕文字内容是否与 storyboard 中各个场景的 script 或 dialog 内容完全一致(⚠️ 允许字幕分段展示,只要最终文本保持一致就行)
2615
+ 2. 检查视频 resolution 设定是否与 storyboard 的 orientation 设置一致,默认 720p 情况下视频尺寸应为横屏 1280x720,竖屏 720x1280,若视频为 1080p 则尺寸应分别为横屏 1920x1080 和竖屏 1080x1920,切勿设反
2683
2616
  3. 除非用户明确表示不要背景音乐,否则应检查是否有生成并配置了 BGM,若无,则生成 BGM 并将其加入素材和轨道配置
2684
2617
 
2685
2618
  以上检查任何一项有问题,先修复 draft_content 使其符合要求后再进行合成`, 'compile-and-run');
@@ -2837,47 +2770,28 @@ server.registerTool('compile-and-run', {
2837
2770
  return createErrorResponse(error, 'compile-and-run');
2838
2771
  }
2839
2772
  });
2840
- server.registerTool('get-video-project-schema', {
2841
- title: 'Get VideoProject Schema',
2842
- description: 'Get the complete VideoProject JSON Schema definition. Should use this schema to validate draft_content JSON files.',
2843
- inputSchema: {},
2844
- }, async () => {
2773
+ server.registerTool('get-schema', {
2774
+ title: 'Get Storyboard Schema or Draft Content Schema',
2775
+ description: 'Get the complete Storyboard or Draft Content JSON Schema definition. Use this schema to validate storyboard.json or draft_content.json files.',
2776
+ inputSchema: {
2777
+ type: zod_1.z
2778
+ .enum(['storyboard', 'draft_content'])
2779
+ .describe('The type of schema to retrieve. Must be either "storyboard" or "draft_content". 用 type: storyboard 的 schema 生成 storyboard.json;用 type: draft_content 的 schema 生成 draft_content.json'),
2780
+ },
2781
+ }, async ({ type }) => {
2845
2782
  try {
2846
- const schemaPath = (0, node_path_1.resolve)(__dirname, '../../utils/videoproject-schema.json');
2783
+ const schemaPath = (0, node_path_1.resolve)(__dirname, `./prompts/${type}-schema.json`);
2847
2784
  const schemaContent = await (0, promises_1.readFile)(schemaPath, 'utf-8');
2848
2785
  const schema = JSON.parse(schemaContent);
2849
- return {
2850
- content: [
2851
- {
2852
- type: 'text',
2853
- text: JSON.stringify({
2854
- success: true,
2855
- schema,
2856
- important_guidelines: `⚠️ 生成文件时请严格遵守输出规范,字幕文本内容必须与 story_board.json 中的 script(或dialog) 字段的文本内容完全一致。
2786
+ let important_guidelines = '';
2787
+ if (type === 'draft_content') {
2788
+ important_guidelines = `⚠️ 生成文件时请严格遵守输出规范,字幕文本内容必须与 storyboard.json 中的 script(或dialog) 字段的文本内容完全一致。
2857
2789
 
2858
2790
  ** 字幕优化 **
2859
- * 在保证字幕文本内容与 story_board.json 中的 script(或dialog) 字段的文本内容完全一致的前提下,可根据 tts 返回的 \`captions.utterances\` 字段对字幕的显示进行优化,将过长的字幕分段显示,在 draft_content.json 中使用分段字幕,captions 的内容在 media_logs.json 中可查询到。
2791
+ * 在保证字幕文本内容与 storyboard.json 中的 script(或dialog) 字段的文本内容完全一致的前提下,可根据 tts 返回的 \`captions.utterances\` 字段对字幕的显示进行优化,将过长的字幕分段显示,在 draft_content.json 中使用分段字幕,captions 的内容在 media_logs.json 中可查询到。
2860
2792
  * 如用户未特殊指定,字幕样式(字体及大小)务必遵守输出规范
2861
- `,
2862
- timestamp: new Date().toISOString(),
2863
- }),
2864
- },
2865
- ],
2866
- };
2867
- }
2868
- catch (error) {
2869
- return createErrorResponse(error, 'get-video-project-schema');
2870
- }
2871
- });
2872
- server.registerTool('get-storyboard-schema', {
2873
- title: 'Get Storyboard Schema',
2874
- description: 'Get the complete Storyboard JSON Schema definition.',
2875
- inputSchema: {},
2876
- }, async () => {
2877
- try {
2878
- const schemaPath = (0, node_path_1.resolve)(__dirname, '../../utils/storyboard-schema.json');
2879
- const schemaContent = await (0, promises_1.readFile)(schemaPath, 'utf-8');
2880
- const schema = JSON.parse(schemaContent);
2793
+ `;
2794
+ }
2881
2795
  return {
2882
2796
  content: [
2883
2797
  {
@@ -2885,6 +2799,7 @@ server.registerTool('get-storyboard-schema', {
2885
2799
  text: JSON.stringify({
2886
2800
  success: true,
2887
2801
  schema,
2802
+ important_guidelines,
2888
2803
  timestamp: new Date().toISOString(),
2889
2804
  }),
2890
2805
  },
@@ -2892,7 +2807,7 @@ server.registerTool('get-storyboard-schema', {
2892
2807
  };
2893
2808
  }
2894
2809
  catch (error) {
2895
- return createErrorResponse(error, 'get-storyboard-schema');
2810
+ return createErrorResponse(error, 'get-schema');
2896
2811
  }
2897
2812
  });
2898
2813
  server.registerTool('do-storyboard-optimization', {
@@ -2912,7 +2827,7 @@ server.registerTool('do-storyboard-optimization', {
2912
2827
  text: JSON.stringify({
2913
2828
  content: {
2914
2829
  guideline: storyboardOptimizationGuidelines,
2915
- action: '你应当根据guideline优化story_board.json',
2830
+ action: '你应当根据guideline优化storyboard.json',
2916
2831
  },
2917
2832
  }),
2918
2833
  },
@@ -3296,180 +3211,206 @@ server.registerTool('media-analyzer', {
3296
3211
  return createErrorResponse(error, 'media-analyzer');
3297
3212
  }
3298
3213
  });
3299
- server.registerTool('image-aligner', {
3300
- title: 'Image Aligner',
3301
- description: 'Analyze image quality and alignment with prompt using AI Image Quality Inspector.',
3302
- inputSchema: {
3303
- imageFileName: zod_1.z
3304
- .string()
3305
- .describe('The image file name in materials directory to analyze.'),
3306
- sceneIndex: zod_1.z.number().min(1).describe('场景索引,从1开始的下标'),
3307
- storyBoardFile: zod_1.z
3308
- .string()
3309
- .optional()
3310
- .default('story_board.json')
3311
- .describe('故事板文件路径'),
3312
- imagePrompt: zod_1.z
3313
- .string()
3314
- .optional()
3315
- .describe('可选的图片提示词,如果提供则覆盖story_board中的提示词'),
3316
- customPrompt: zod_1.z
3317
- .string()
3318
- .optional()
3319
- .describe('可选的额外用户要求,用于补充图片质量评估的特定需求'),
3320
- },
3321
- }, async ({ imageFileName, sceneIndex, storyBoardFile = 'story_board.json', imagePrompt, customPrompt, }) => {
3322
- try {
3323
- const currentSession = await validateSession('image-aligner');
3324
- // 验证图片文件
3325
- validateImageFile(imageFileName);
3326
- // 获取图片 URL
3327
- const imageUrl = getMaterialUri(currentSession, imageFileName);
3328
- // 确定要使用的提示词
3329
- let finalPrompt = imagePrompt;
3330
- // 如果没有提供imagePrompt,则从story_board中获取
3331
- if (!imagePrompt) {
3332
- try {
3333
- const storyBoardPath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, storyBoardFile);
3334
- if ((0, node_fs_1.existsSync)(storyBoardPath)) {
3335
- const storyBoardContent = await (0, promises_1.readFile)(storyBoardPath, 'utf8');
3336
- const storyBoard = JSON.parse(storyBoardContent);
3337
- if (storyBoard.scenes && Array.isArray(storyBoard.scenes)) {
3338
- const scene = storyBoard.scenes[sceneIndex - 1]; // sceneIndex 从1开始,数组从0开始
3339
- if (scene) {
3340
- // 根据文件名判断优先级:若end_frame存在且imageFileName包含"_end"则优先取end_frame,否则取start_frame
3341
- if (scene.end_frame && imageFileName.includes('_end')) {
3342
- finalPrompt = scene.end_frame;
3343
- }
3344
- else {
3345
- finalPrompt = scene.start_frame || scene.end_frame;
3346
- }
3347
- if (!finalPrompt) {
3348
- return createErrorResponse(`场景 ${sceneIndex} 中未找到 start_frame 或 end_frame 提示词`, 'image-aligner');
3349
- }
3350
- }
3351
- else {
3352
- return createErrorResponse(`在 ${storyBoardFile} 中未找到场景索引 ${sceneIndex}`, 'image-aligner');
3353
- }
3354
- }
3355
- else {
3356
- return createErrorResponse(`${storyBoardFile} 文件格式不正确,缺少 scenes 数组`, 'image-aligner');
3357
- }
3358
- }
3359
- else {
3360
- return createErrorResponse(`故事板文件不存在: ${storyBoardPath}`, 'image-aligner');
3361
- }
3362
- }
3363
- catch (error) {
3364
- return createErrorResponse(`读取或解析故事板文件失败: ${error}`, 'image-aligner');
3365
- }
3366
- }
3367
- // 如果仍然没有提示词,返回错误
3368
- if (!finalPrompt) {
3369
- return createErrorResponse('未提供 imagePrompt 且无法从故事板中获取提示词', 'image-aligner');
3370
- }
3371
- // 读取图片质量检查指南
3372
- const alignerGuidelinePath = (0, node_path_1.resolve)(__dirname, './prompts/reasonings/image_aligner.md');
3373
- let alignerGuideline = '';
3374
- try {
3375
- alignerGuideline = await (0, promises_1.readFile)(alignerGuidelinePath, 'utf8');
3376
- }
3377
- catch (error) {
3378
- console.warn('无法读取图片质量检查指南:', error);
3379
- alignerGuideline =
3380
- '请对图片质量进行评估,包括构图、色彩、清晰度等方面。';
3381
- }
3382
- // 构建系统提示
3383
- const systemPrompt = `你是一个专业的AI图片质量检查员。请根据以下指南对图片进行评估:
3384
-
3385
- ${alignerGuideline}
3386
-
3387
- 请严格按照指南中的JSON格式返回评估结果。`;
3388
- // 构建用户提示
3389
- const userPrompt = `请对这张图片进行质量评估。
3390
-
3391
- 原始提示词:${finalPrompt}${customPrompt
3392
- ? `
3393
-
3394
- 额外要求:${customPrompt}`
3395
- : ''}
3396
-
3397
- 请按照指南要求,返回包含评分、问题列表和优化建议的JSON格式结果。`;
3398
- // 调用AI模型进行图片质量评估
3399
- const ai = currentSession.ai;
3400
- const completion = await ai.getCompletions({
3401
- model: 'Doubao-Seed-1.6',
3402
- messages: [
3403
- {
3404
- role: 'system',
3405
- content: systemPrompt,
3406
- },
3407
- {
3408
- role: 'user',
3409
- content: [
3410
- {
3411
- type: 'image_url',
3412
- image_url: {
3413
- url: imageUrl,
3414
- },
3415
- },
3416
- {
3417
- type: 'text',
3418
- text: userPrompt,
3419
- },
3420
- ],
3421
- },
3422
- ],
3423
- });
3424
- const result = completion.choices[0]?.message?.content;
3425
- if (!result) {
3426
- throw new Error('No response from AI model');
3427
- }
3428
- // 解析AI响应
3429
- let alignmentResult;
3430
- try {
3431
- // 尝试从响应中提取JSON
3432
- const jsonMatch = result.match(/```json\s*([\s\S]*?)\s*```/) ||
3433
- result.match(/\{[\s\S]*\}/);
3434
- if (jsonMatch) {
3435
- alignmentResult = JSON.parse(jsonMatch[1] || jsonMatch[0]);
3436
- }
3437
- else {
3438
- // 如果没有找到JSON格式,尝试直接解析整个响应
3439
- alignmentResult = JSON.parse(result);
3440
- }
3441
- }
3442
- catch (error) {
3443
- // 如果解析失败,返回原始响应
3444
- alignmentResult = {
3445
- error: 'JSON解析失败',
3446
- raw_response: result,
3447
- };
3448
- }
3449
- return {
3450
- content: [
3451
- {
3452
- type: 'text',
3453
- text: JSON.stringify({
3454
- success: true,
3455
- imageFileName,
3456
- sceneIndex,
3457
- storyBoardFile,
3458
- imagePrompt: finalPrompt,
3459
- customPrompt,
3460
- promptSource: imagePrompt ? 'manual_override' : 'story_board',
3461
- analysis: alignmentResult,
3462
- imageUrl,
3463
- nextActionSuggest: '可根据分析结果调整提示词,修改story_board后,重新生成图片。',
3464
- }),
3465
- },
3466
- ],
3467
- };
3468
- }
3469
- catch (error) {
3470
- return createErrorResponse(error, 'image-aligner');
3471
- }
3472
- });
3214
+ // server.registerTool(
3215
+ // 'image-aligner',
3216
+ // {
3217
+ // title: 'Image Aligner',
3218
+ // description:
3219
+ // 'Analyze image quality and alignment with prompt using AI Image Quality Inspector.',
3220
+ // inputSchema: {
3221
+ // imageFileName: z
3222
+ // .string()
3223
+ // .describe('The image file name in materials directory to analyze.'),
3224
+ // sceneIndex: z.number().min(1).describe('场景索引,从1开始的下标'),
3225
+ // storyBoardFile: z
3226
+ // .string()
3227
+ // .optional()
3228
+ // .default('storyboard.json')
3229
+ // .describe('故事板文件路径'),
3230
+ // imagePrompt: z
3231
+ // .string()
3232
+ // .optional()
3233
+ // .describe('可选的图片提示词,如果提供则覆盖storyboard中的提示词'),
3234
+ // customPrompt: z
3235
+ // .string()
3236
+ // .optional()
3237
+ // .describe('可选的额外用户要求,用于补充图片质量评估的特定需求'),
3238
+ // },
3239
+ // },
3240
+ // async ({
3241
+ // imageFileName,
3242
+ // sceneIndex,
3243
+ // storyBoardFile = 'storyboard.json',
3244
+ // imagePrompt,
3245
+ // customPrompt,
3246
+ // }) => {
3247
+ // try {
3248
+ // const currentSession = await validateSession('image-aligner');
3249
+ // // 验证图片文件
3250
+ // validateImageFile(imageFileName);
3251
+ // // 获取图片 URL
3252
+ // const imageUrl = getMaterialUri(currentSession, imageFileName);
3253
+ // // 确定要使用的提示词
3254
+ // let finalPrompt = imagePrompt;
3255
+ // // 如果没有提供imagePrompt,则从storyboard中获取
3256
+ // if (!imagePrompt) {
3257
+ // try {
3258
+ // const storyBoardPath = resolve(
3259
+ // process.env.ZEROCUT_PROJECT_CWD || process.cwd(),
3260
+ // projectLocalDir,
3261
+ // storyBoardFile
3262
+ // );
3263
+ // if (existsSync(storyBoardPath)) {
3264
+ // const storyBoardContent = await readFile(storyBoardPath, 'utf8');
3265
+ // const storyBoard = JSON.parse(storyBoardContent);
3266
+ // if (storyBoard.scenes && Array.isArray(storyBoard.scenes)) {
3267
+ // const scene = storyBoard.scenes[sceneIndex - 1]; // sceneIndex 从1开始,数组从0开始
3268
+ // if (scene) {
3269
+ // // 根据文件名判断优先级:若end_frame存在且imageFileName包含"_end"则优先取end_frame,否则取start_frame
3270
+ // if (scene.end_frame && imageFileName.includes('_end')) {
3271
+ // finalPrompt = scene.end_frame;
3272
+ // } else {
3273
+ // finalPrompt = scene.start_frame || scene.end_frame;
3274
+ // }
3275
+ // if (!finalPrompt) {
3276
+ // return createErrorResponse(
3277
+ // `场景 ${sceneIndex} 中未找到 start_frame 或 end_frame 提示词`,
3278
+ // 'image-aligner'
3279
+ // );
3280
+ // }
3281
+ // } else {
3282
+ // return createErrorResponse(
3283
+ // `在 ${storyBoardFile} 中未找到场景索引 ${sceneIndex}`,
3284
+ // 'image-aligner'
3285
+ // );
3286
+ // }
3287
+ // } else {
3288
+ // return createErrorResponse(
3289
+ // `${storyBoardFile} 文件格式不正确,缺少 scenes 数组`,
3290
+ // 'image-aligner'
3291
+ // );
3292
+ // }
3293
+ // } else {
3294
+ // return createErrorResponse(
3295
+ // `故事板文件不存在: ${storyBoardPath}`,
3296
+ // 'image-aligner'
3297
+ // );
3298
+ // }
3299
+ // } catch (error) {
3300
+ // return createErrorResponse(
3301
+ // `读取或解析故事板文件失败: ${error}`,
3302
+ // 'image-aligner'
3303
+ // );
3304
+ // }
3305
+ // }
3306
+ // // 如果仍然没有提示词,返回错误
3307
+ // if (!finalPrompt) {
3308
+ // return createErrorResponse(
3309
+ // '未提供 imagePrompt 且无法从故事板中获取提示词',
3310
+ // 'image-aligner'
3311
+ // );
3312
+ // }
3313
+ // // 读取图片质量检查指南
3314
+ // const alignerGuidelinePath = resolve(
3315
+ // __dirname,
3316
+ // './prompts/reasonings/image_aligner.md'
3317
+ // );
3318
+ // let alignerGuideline = '';
3319
+ // try {
3320
+ // alignerGuideline = await readFile(alignerGuidelinePath, 'utf8');
3321
+ // } catch (error) {
3322
+ // console.warn('无法读取图片质量检查指南:', error);
3323
+ // alignerGuideline =
3324
+ // '请对图片质量进行评估,包括构图、色彩、清晰度等方面。';
3325
+ // }
3326
+ // // 构建系统提示
3327
+ // const systemPrompt = `你是一个专业的AI图片质量检查员。请根据以下指南对图片进行评估:
3328
+ // ${alignerGuideline}
3329
+ // 请严格按照指南中的JSON格式返回评估结果。`;
3330
+ // // 构建用户提示
3331
+ // const userPrompt = `请对这张图片进行质量评估。
3332
+ // 原始提示词:${finalPrompt}${
3333
+ // customPrompt
3334
+ // ? `
3335
+ // 额外要求:${customPrompt}`
3336
+ // : ''
3337
+ // }
3338
+ // 请按照指南要求,返回包含评分、问题列表和优化建议的JSON格式结果。`;
3339
+ // // 调用AI模型进行图片质量评估
3340
+ // const ai = currentSession.ai;
3341
+ // const completion = await ai.getCompletions({
3342
+ // model: 'Doubao-Seed-1.6',
3343
+ // messages: [
3344
+ // {
3345
+ // role: 'system',
3346
+ // content: systemPrompt,
3347
+ // },
3348
+ // {
3349
+ // role: 'user',
3350
+ // content: [
3351
+ // {
3352
+ // type: 'image_url',
3353
+ // image_url: {
3354
+ // url: imageUrl,
3355
+ // },
3356
+ // },
3357
+ // {
3358
+ // type: 'text',
3359
+ // text: userPrompt,
3360
+ // },
3361
+ // ],
3362
+ // },
3363
+ // ],
3364
+ // });
3365
+ // const result = completion.choices[0]?.message?.content;
3366
+ // if (!result) {
3367
+ // throw new Error('No response from AI model');
3368
+ // }
3369
+ // // 解析AI响应
3370
+ // let alignmentResult;
3371
+ // try {
3372
+ // // 尝试从响应中提取JSON
3373
+ // const jsonMatch =
3374
+ // result.match(/```json\s*([\s\S]*?)\s*```/) ||
3375
+ // result.match(/\{[\s\S]*\}/);
3376
+ // if (jsonMatch) {
3377
+ // alignmentResult = JSON.parse(jsonMatch[1] || jsonMatch[0]);
3378
+ // } else {
3379
+ // // 如果没有找到JSON格式,尝试直接解析整个响应
3380
+ // alignmentResult = JSON.parse(result);
3381
+ // }
3382
+ // } catch (error) {
3383
+ // // 如果解析失败,返回原始响应
3384
+ // alignmentResult = {
3385
+ // error: 'JSON解析失败',
3386
+ // raw_response: result,
3387
+ // };
3388
+ // }
3389
+ // return {
3390
+ // content: [
3391
+ // {
3392
+ // type: 'text',
3393
+ // text: JSON.stringify({
3394
+ // success: true,
3395
+ // imageFileName,
3396
+ // sceneIndex,
3397
+ // storyBoardFile,
3398
+ // imagePrompt: finalPrompt,
3399
+ // customPrompt,
3400
+ // promptSource: imagePrompt ? 'manual_override' : 'storyboard',
3401
+ // analysis: alignmentResult,
3402
+ // imageUrl,
3403
+ // nextActionSuggest:
3404
+ // '可根据分析结果调整提示词,修改storyboard后,重新生成图片。',
3405
+ // }),
3406
+ // },
3407
+ // ],
3408
+ // };
3409
+ // } catch (error) {
3410
+ // return createErrorResponse(error, 'image-aligner');
3411
+ // }
3412
+ // }
3413
+ // );
3473
3414
  server.registerTool('audio-video-sync', {
3474
3415
  title: 'Audio Video Sync',
3475
3416
  description: 'Generate audio-video-synced video by matching video with audio. 还可以对口型。',
@@ -3625,6 +3566,11 @@ server.registerTool('generate-video-by-ref', {
3625
3566
  prompt: zod_1.z
3626
3567
  .string()
3627
3568
  .describe('The prompt to generate video with or without reference images.'),
3569
+ rewritePrompt: zod_1.z
3570
+ .boolean()
3571
+ .optional()
3572
+ .default(true)
3573
+ .describe('Whether to rewrite the prompt.'),
3628
3574
  referenceImages: zod_1.z
3629
3575
  .array(zod_1.z.object({
3630
3576
  name: zod_1.z
@@ -3640,7 +3586,7 @@ server.registerTool('generate-video-by-ref', {
3640
3586
  .describe('Array of reference image objects with name, url and type. Can be empty for text-only generation.'),
3641
3587
  duration: zod_1.z
3642
3588
  .number()
3643
- .min(2)
3589
+ .min(1)
3644
3590
  .max(16)
3645
3591
  .optional()
3646
3592
  .default(5)
@@ -3671,7 +3617,7 @@ server.registerTool('generate-video-by-ref', {
3671
3617
  storyBoardFile: zod_1.z
3672
3618
  .string()
3673
3619
  .optional()
3674
- .default('story_board.json')
3620
+ .default('storyboard.json')
3675
3621
  .describe('故事板文件路径'),
3676
3622
  skipConsistencyCheck: zod_1.z
3677
3623
  .boolean()
@@ -3688,7 +3634,7 @@ server.registerTool('generate-video-by-ref', {
3688
3634
  .default(false)
3689
3635
  .describe('Whether to optimize the prompt.'),
3690
3636
  },
3691
- }, async ({ prompt, referenceImages, duration, size, watermark, type, mute, saveToFileName, sceneIndex, storyBoardFile, skipConsistencyCheck, optimizePrompt, }, context) => {
3637
+ }, async ({ prompt, rewritePrompt, referenceImages, duration, size, watermark, type, mute, saveToFileName, sceneIndex, storyBoardFile, skipConsistencyCheck, optimizePrompt, }, context) => {
3692
3638
  try {
3693
3639
  // 验证session状态
3694
3640
  const currentSession = await validateSession('generate-video-by-ref');
@@ -3696,17 +3642,17 @@ server.registerTool('generate-video-by-ref', {
3696
3642
  // 检查 storyboard 标志
3697
3643
  if (!checkStoryboardFlag && (0, node_fs_1.existsSync)(storyBoardPath)) {
3698
3644
  checkStoryboardFlag = true;
3699
- return createErrorResponse(`必须先审查生成的 story_board.json 内容,按照如下步骤:
3645
+ return createErrorResponse(`必须先审查生成的 storyboard.json 内容,按照如下步骤:
3700
3646
 
3701
3647
  1. 确保每个场景中的stage_atmosphere内容按照规则被正确融合到video_prompt中,不得遗漏
3702
3648
  2. 如有main_characters设定且包含了reference_image,或有reference_objects,需确保video_prompt描述已包含该场景相关main_characters和所有reference_objects中的物品或背景,并确保参考图具体内容已经在video_prompt中有明确描述,如果没有,可忽略。
3703
3649
  3. 如有配音,先自我检查 media_logs 中的查音频时长,确保以匹配音频时长来生成视频
3704
3650
 
3705
- 检查完上述问题后先汇报,如果有需要,应当先修改 story_board.json 内容,然后再调用 generate-video-by-ref 生成视频。注意修改 story_board 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!
3651
+ 检查完上述问题后先汇报,如果有需要,应当先修改 storyboard.json 内容,然后再调用 generate-video-by-ref 生成视频。注意修改 storyboard 内容时,仅修改相应字段的字符串值,不要破坏JSON格式!
3706
3652
 
3707
3653
  再次调用 generate-video-by-ref 时,如需要参考图,要确保referenceImages使用正确(main_characters中的reference_image作为参考人物,reference_objects中的image作为参考物品或参考背景)`, 'generate-image');
3708
3654
  }
3709
- // 校验 prompt 与 story_board.json 中场景设定的一致性(如果提供了 sceneIndex)
3655
+ // 校验 prompt 与 storyboard.json 中场景设定的一致性(如果提供了 sceneIndex)
3710
3656
  if (!skipConsistencyCheck && sceneIndex) {
3711
3657
  try {
3712
3658
  if ((0, node_fs_1.existsSync)(storyBoardPath)) {
@@ -3724,7 +3670,7 @@ server.registerTool('generate-video-by-ref', {
3724
3670
  if (scene) {
3725
3671
  const videoPrompt = scene.video_prompt;
3726
3672
  if (videoPrompt && prompt !== videoPrompt) {
3727
- return createErrorResponse('视频提示词必须严格遵照story_board的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video-by-ref');
3673
+ return createErrorResponse('视频提示词必须严格遵照storyboard的设定,如果用户明确指出不需要遵守,请将skipConsistencyCheck设置为true后再次调用', 'generate-video-by-ref');
3728
3674
  }
3729
3675
  // 检查 scene.is_continuous 是否为 true
3730
3676
  if (scene.is_continuous === true) {
@@ -3791,7 +3737,7 @@ server.registerTool('generate-video-by-ref', {
3791
3737
  }
3792
3738
  }
3793
3739
  else {
3794
- console.warn(`Scene index ${sceneIndex} not found in story_board.json`);
3740
+ console.warn(`Scene index ${sceneIndex} not found in storyboard.json`);
3795
3741
  }
3796
3742
  }
3797
3743
  }
@@ -3801,7 +3747,7 @@ server.registerTool('generate-video-by-ref', {
3801
3747
  }
3802
3748
  catch (error) {
3803
3749
  console.error('Failed to validate prompt with story board:', error);
3804
- // 如果读取或解析 story_board.json 失败,继续执行但记录警告
3750
+ // 如果读取或解析 storyboard.json 失败,继续执行但记录警告
3805
3751
  }
3806
3752
  }
3807
3753
  const validatedFileName = validateFileName(saveToFileName);
@@ -3855,14 +3801,17 @@ server.registerTool('generate-video-by-ref', {
3855
3801
  url: imageUrl,
3856
3802
  });
3857
3803
  console.log(`Added reference image URL: ${imageUrl} (name: ${imageRef.name}, type: ${imageRef.type})`);
3858
- promptPrefix += `参考“${imageRef.name}”(图${referenceImageUrls.length})${imageRef.type === 'subject' ? '主体形象' : '背景'}\n`;
3804
+ if (rewritePrompt) {
3805
+ promptPrefix += `参考“${imageRef.name}”(图${referenceImageUrls.length})${imageRef.type === 'subject' ? '主体形象' : '背景'}\n`;
3806
+ }
3859
3807
  }
3860
3808
  if (promptPrefix) {
3861
3809
  promptPrefix += '\n';
3862
3810
  }
3811
+ const finalPrompt = `${promptPrefix}${prompt}`;
3863
3812
  // 调用 referencesToVideo 函数
3864
3813
  const result = await currentSession.ai.referencesToVideo({
3865
- prompt: `${promptPrefix}${prompt}`,
3814
+ prompt: finalPrompt,
3866
3815
  reference_images: referenceImageUrls, // 使用URL数组而不是base64数组
3867
3816
  duration,
3868
3817
  size,
@@ -3905,6 +3854,8 @@ server.registerTool('generate-video-by-ref', {
3905
3854
  ratio: result.ratio,
3906
3855
  url: result.url,
3907
3856
  last_frame_url: result.last_frame_url,
3857
+ referenceImageUrls,
3858
+ prompt: finalPrompt,
3908
3859
  }, null, 2),
3909
3860
  },
3910
3861
  ],
@@ -4026,6 +3977,166 @@ server.registerTool('extend-video-duration', {
4026
3977
  return createErrorResponse(error, 'extend-video');
4027
3978
  }
4028
3979
  });
3980
+ server.registerTool('generate-video-by-template', {
3981
+ title: 'Generate Video by Template',
3982
+ description: 'Generate a video based on a template. The template must be a valid JSON string.',
3983
+ inputSchema: {
3984
+ user_request: zod_1.z.string().describe('用户请求,根据意图自动匹配模板'),
3985
+ text_prompts: zod_1.z
3986
+ .array(zod_1.z.string().describe('Text prompt for the template to build video.'))
3987
+ .optional()
3988
+ .describe('Optional text prompts to use in the template.'),
3989
+ materials: zod_1.z
3990
+ .array(zod_1.z.string().describe('Material file name in materials directory.'))
3991
+ .optional()
3992
+ .describe('Optional materials to use in the template.'),
3993
+ saveToFileName: zod_1.z
3994
+ .string()
3995
+ .describe('The filename to save the generated video.'),
3996
+ },
3997
+ }, async ({ user_request, text_prompts, saveToFileName, materials }) => {
3998
+ try {
3999
+ const templates = [
4000
+ {
4001
+ id: '7569583728302817331',
4002
+ name: '宠物唱歌',
4003
+ description: '用一张宠物照片,生成一段宠物唱歌的视频。',
4004
+ trigger: '根据{A图片}生成一段{宠物A}唱歌的视频',
4005
+ },
4006
+ {
4007
+ id: '7569605825011367976',
4008
+ name: '万圣节宠物弹吉他',
4009
+ description: '用一张宠物照片,生成一段宠物弹吉他的视频。',
4010
+ trigger: '根据{A图片}生成一段{宠物A}弹吉他的视频',
4011
+ },
4012
+ {
4013
+ id: '7572443489834844223',
4014
+ name: '图生动作模仿视频',
4015
+ description: '用一张图片和动作视频,生成一段图片主体模仿该动作视频的新视频。',
4016
+ trigger: '生成{A图片}模仿{B视频}的视频',
4017
+ },
4018
+ {
4019
+ id: '7575160546555674670',
4020
+ name: '文生动作模仿视频',
4021
+ description: '用一段提示词和视频,生成一段模仿该视频的新视频。',
4022
+ trigger: '生成一段{提示词A}模仿{B视频}的视频',
4023
+ },
4024
+ ];
4025
+ const currentSession = await validateSession('generate-video-by-template');
4026
+ const validatedFileName = validateFileName(saveToFileName);
4027
+ const ai = currentSession.ai;
4028
+ let completion = await ai.getCompletions({
4029
+ model: 'Doubao-Seed-1.6',
4030
+ messages: [
4031
+ {
4032
+ role: 'system',
4033
+ content: `你根据用户需求,从以下模板中选择一个匹配的模板,返回模板ID:\n\n${JSON.stringify(templates)}\n\n**约束**:只输出模板ID,不需要其他解释,如果没有匹配的模版,输出"无匹配模版"`,
4034
+ },
4035
+ {
4036
+ role: 'user',
4037
+ content: user_request,
4038
+ },
4039
+ ],
4040
+ });
4041
+ const templateId = completion.choices[0]?.message?.content.trim();
4042
+ if (!templateId || templateId === '无匹配模版') {
4043
+ throw new Error('Failed to get template ID');
4044
+ }
4045
+ const workflowInfo = await ai.getCozeWorkflowInfo(templateId);
4046
+ const materialUrls = materials?.map(material => getMaterialUri(currentSession, material));
4047
+ const schema = {
4048
+ name: 'workflow_parameters',
4049
+ schema: {
4050
+ type: 'object',
4051
+ properties: {
4052
+ parameters: {
4053
+ type: 'object',
4054
+ description: 'The parameters for the workflow.',
4055
+ },
4056
+ },
4057
+ required: ['parameters'],
4058
+ },
4059
+ };
4060
+ const prompt = `你根据模板工作流输入 schema 和 prompt、materials 生成一个 JSON 字符串,作为模板工作流的参数。
4061
+
4062
+ ## **工作流输入 schema**:
4063
+ ${JSON.stringify(workflowInfo.data.workflow_detail.description)}
4064
+
4065
+ ## **prompt**:
4066
+ ${text_prompts}
4067
+
4068
+ ## **materials**:
4069
+ ${JSON.stringify(materialUrls)}`;
4070
+ // console.log(prompt);
4071
+ completion = await ai.getCompletions({
4072
+ model: 'Doubao-Seed-1.6-flash',
4073
+ messages: [
4074
+ {
4075
+ role: 'system',
4076
+ content: prompt,
4077
+ },
4078
+ {
4079
+ role: 'user',
4080
+ content: `生成模板调用的正确parameters参数`,
4081
+ },
4082
+ ],
4083
+ response_format: {
4084
+ type: 'json_schema',
4085
+ json_schema: schema,
4086
+ },
4087
+ });
4088
+ const parameters = completion.choices[0]?.message?.content.trim();
4089
+ if (!parameters) {
4090
+ throw new Error('Failed to get parameters');
4091
+ }
4092
+ // console.log(parameters);
4093
+ // return {
4094
+ // content: [
4095
+ // {
4096
+ // type: 'text',
4097
+ // text: JSON.stringify({
4098
+ // success: true,
4099
+ // prompt,
4100
+ // templateId,
4101
+ // parameters,
4102
+ // }),
4103
+ // },
4104
+ // ],
4105
+ // };
4106
+ const result = await ai.runCozeWorkflow(templateId, JSON.parse(parameters).parameters);
4107
+ if (result.url) {
4108
+ // 保存到项目材料目录
4109
+ const uri = await saveMaterial(currentSession, result.url, validatedFileName);
4110
+ return {
4111
+ content: [
4112
+ {
4113
+ type: 'text',
4114
+ text: JSON.stringify({
4115
+ success: true,
4116
+ parameters,
4117
+ uri,
4118
+ }),
4119
+ },
4120
+ ],
4121
+ };
4122
+ }
4123
+ return {
4124
+ content: [
4125
+ {
4126
+ type: 'text',
4127
+ text: JSON.stringify({
4128
+ success: false,
4129
+ result,
4130
+ parameters,
4131
+ }),
4132
+ },
4133
+ ],
4134
+ };
4135
+ }
4136
+ catch (error) {
4137
+ return createErrorResponse(error, 'generate-video-by-template');
4138
+ }
4139
+ });
4029
4140
  server.registerTool('run-ffmpeg-command', {
4030
4141
  title: 'Run FFmpeg Command',
4031
4142
  description: 'Run ffmpeg or ffprobe commands in the sandbox environment. Only ffmpeg and ffprobe commands are allowed. The default working directory is the materials directory.',
@@ -4269,182 +4380,6 @@ server.registerTool('build-capcat-draft', {
4269
4380
  return createErrorResponse(error, 'build-capcat-draft');
4270
4381
  }
4271
4382
  });
4272
- // server.registerTool(
4273
- // 'custom-edit-draft',
4274
- // {
4275
- // title: 'Custom Edit Draft',
4276
- // description:
4277
- // 'Launch timeline editor server for custom draft editing. Starts a local server at specified port with draft file configuration.',
4278
- // inputSchema: {
4279
- // draftContentFileName: z
4280
- // .string()
4281
- // .optional()
4282
- // .default('draft_content.json')
4283
- // .describe('Draft content filename (default: draft_content.json)'),
4284
- // port: z
4285
- // .number()
4286
- // .optional()
4287
- // .default(6789)
4288
- // .describe('Server port (default: 6789)'),
4289
- // },
4290
- // },
4291
- // async ({ draftContentFileName = 'draft_content.json', port = 6789 }) => {
4292
- // try {
4293
- // // 使用项目本地目录
4294
- // const workDir = resolve(
4295
- // process.env.ZEROCUT_PROJECT_CWD || process.cwd(),
4296
- // projectLocalDir
4297
- // );
4298
- // // 检查 draft_content.json 文件是否存在
4299
- // const draftFilePath = resolve(workDir, draftContentFileName);
4300
- // if (!existsSync(draftFilePath)) {
4301
- // return createErrorResponse(
4302
- // new Error(
4303
- // `Draft content file not found: ${draftContentFileName}. Please generate draft content first using the generate-draft-content tool.`
4304
- // ),
4305
- // 'custom-edit-draft'
4306
- // );
4307
- // }
4308
- // // 备份原始的 draft_content.json 文件
4309
- // const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
4310
- // const backupFileName = `${draftContentFileName}.backup.${timestamp}`;
4311
- // const backupFilePath = resolve(workDir, backupFileName);
4312
- // try {
4313
- // const { copyFileSync } = await import('fs');
4314
- // copyFileSync(draftFilePath, backupFilePath);
4315
- // console.log(`Draft file backed up to: ${backupFileName}`);
4316
- // } catch (backupError) {
4317
- // console.warn(`Failed to backup draft file: ${backupError}`);
4318
- // // 继续执行,不因备份失败而中断
4319
- // }
4320
- // // 构建启动命令参数
4321
- // const timelineEditorPath = resolve(
4322
- // __dirname,
4323
- // '../../../dist/timeline-editor/index.js'
4324
- // );
4325
- // console.log(`Starting timeline editor server...`);
4326
- // console.log(`Working directory: ${workDir}`);
4327
- // console.log(`Draft file: ${draftContentFileName}`);
4328
- // console.log(`Server port: ${port}`);
4329
- // console.log(`Timeline editor path: ${timelineEditorPath}`);
4330
- // // 检查端口是否被占用,如果被占用则停止占用的进程
4331
- // if (await isPortInUse(port)) {
4332
- // console.log(`Port ${port} is in use, killing existing processes...`);
4333
- // await killPortProcess(port);
4334
- // }
4335
- // // 直接启动服务器进程
4336
- // const platform = os.platform();
4337
- // const nodeCommand = platform === 'win32' ? 'node.exe' : 'node';
4338
- // const serverProcess = spawn(
4339
- // nodeCommand,
4340
- // [
4341
- // timelineEditorPath,
4342
- // '--draft-file',
4343
- // draftContentFileName,
4344
- // '--project-dir',
4345
- // workDir,
4346
- // '--port',
4347
- // port.toString(),
4348
- // ],
4349
- // {
4350
- // cwd: workDir,
4351
- // stdio: 'pipe',
4352
- // shell: platform === 'win32', // 在 Windows 下使用 shell
4353
- // }
4354
- // );
4355
- // const serverUrl = `http://localhost:${port}`;
4356
- // // 等待服务器启动 - 使用健康检查 API 轮询
4357
- // await new Promise((resolve, reject) => {
4358
- // const timeout = setTimeout(() => {
4359
- // reject(new Error('Server startup timeout'));
4360
- // }, 30000); // 增加超时时间到30秒
4361
- // const checkHealth = async () => {
4362
- // try {
4363
- // const healthUrl = `${serverUrl}/health`;
4364
- // const req = http.get(healthUrl, (res: any) => {
4365
- // let data = '';
4366
- // res.on('data', (chunk: any) => {
4367
- // data += chunk;
4368
- // });
4369
- // res.on('end', () => {
4370
- // try {
4371
- // const healthData = JSON.parse(data);
4372
- // if (healthData.status === 'ok') {
4373
- // clearTimeout(timeout);
4374
- // console.log('Server health check passed');
4375
- // resolve(void 0);
4376
- // } else {
4377
- // // 继续轮询
4378
- // setTimeout(checkHealth, 1000);
4379
- // }
4380
- // } catch (parseError) {
4381
- // // 继续轮询
4382
- // setTimeout(checkHealth, 1000);
4383
- // }
4384
- // });
4385
- // });
4386
- // req.on('error', () => {
4387
- // // 服务器还未启动,继续轮询
4388
- // setTimeout(checkHealth, 1000);
4389
- // });
4390
- // req.setTimeout(2000, () => {
4391
- // req.destroy();
4392
- // // 继续轮询
4393
- // setTimeout(checkHealth, 1000);
4394
- // });
4395
- // } catch (error) {
4396
- // // 继续轮询
4397
- // setTimeout(checkHealth, 1000);
4398
- // }
4399
- // };
4400
- // serverProcess.stdout?.on('data', (data: any) => {
4401
- // console.log('Server output:', data.toString());
4402
- // });
4403
- // serverProcess.stderr?.on('data', (data: any) => {
4404
- // console.error('Server error:', data.toString());
4405
- // });
4406
- // serverProcess.on('error', (error: any) => {
4407
- // clearTimeout(timeout);
4408
- // reject(error);
4409
- // });
4410
- // // 开始健康检查轮询
4411
- // setTimeout(checkHealth, 2000); // 2秒后开始第一次检查
4412
- // });
4413
- // return {
4414
- // content: [
4415
- // {
4416
- // type: 'text',
4417
- // text: JSON.stringify(
4418
- // {
4419
- // success: true,
4420
- // message: 'Timeline editor server started successfully',
4421
- // serverUrl: serverUrl,
4422
- // draftFile: draftContentFileName,
4423
- // backupFile: backupFileName,
4424
- // projectDirectory: workDir,
4425
- // port: port,
4426
- // pid: serverProcess.pid,
4427
- // instructions: [
4428
- // `Timeline editor is now running at: ${serverUrl}`,
4429
- // 'Open the URL in your browser to edit the draft',
4430
- // 'The server will automatically save changes to the draft file',
4431
- // `Original draft file has been backed up as: ${backupFileName}`,
4432
- // `Server process ID: ${serverProcess.pid}`,
4433
- // 'The server will continue running in the background',
4434
- // ],
4435
- // },
4436
- // null,
4437
- // 2
4438
- // ),
4439
- // },
4440
- // ],
4441
- // };
4442
- // } catch (error) {
4443
- // console.error('Error starting timeline editor:', error);
4444
- // return createErrorResponse(error, 'custom-edit-draft');
4445
- // }
4446
- // }
4447
- // );
4448
4383
  async function run() {
4449
4384
  // Start receiving messages on stdin and sending messages on stdout
4450
4385
  const transport = new stdio_js_1.StdioServerTransport();