cerevox 2.7.1 → 2.8.1

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.
@@ -170,35 +170,41 @@ async function sendProgress(context, progress, total, message) {
170
170
  params: { progressToken: token, progress, total, message },
171
171
  });
172
172
  }
173
- async function updateMediaDurations(session, fileName, durationMs) {
173
+ async function updateMediaLogs(session, fileName, result, mediaType = 'video') {
174
174
  try {
175
- const mediaDurationsPath = node_path_1.default.resolve(projectLocalDir, 'media_durations.json');
176
- let mediaDurations = [];
175
+ const mediaLogsPath = node_path_1.default.resolve(projectLocalDir, 'media_logs.json');
176
+ let mediaLogs = [];
177
177
  // 尝试读取现有文件
178
178
  try {
179
- const existingContent = await (0, promises_1.readFile)(mediaDurationsPath, 'utf-8');
180
- mediaDurations = JSON.parse(existingContent);
179
+ const existingContent = await (0, promises_1.readFile)(mediaLogsPath, 'utf-8');
180
+ mediaLogs = JSON.parse(existingContent);
181
181
  }
182
182
  catch (error) {
183
183
  // 文件不存在或格式错误,使用空数组
184
- mediaDurations = [];
184
+ mediaLogs = [];
185
185
  }
186
186
  // 检查是否已存在相同文件名的记录
187
- const existingIndex = mediaDurations.findIndex(item => item.fileName === fileName);
187
+ const existingIndex = mediaLogs.findIndex(item => item.fileName === fileName);
188
+ const logEntry = {
189
+ fileName,
190
+ mediaType,
191
+ timestamp: new Date().toISOString(),
192
+ result,
193
+ };
188
194
  if (existingIndex >= 0) {
189
195
  // 更新现有记录
190
- mediaDurations[existingIndex].durationMs = durationMs;
196
+ mediaLogs[existingIndex] = logEntry;
191
197
  }
192
198
  else {
193
199
  // 添加新记录
194
- mediaDurations.push({ fileName, durationMs });
200
+ mediaLogs.push(logEntry);
195
201
  }
196
202
  // 写入文件
197
- await (0, promises_1.writeFile)(mediaDurationsPath, JSON.stringify(mediaDurations, null, 2));
198
- console.log(`Updated media_durations.json: ${fileName} -> ${durationMs}ms`);
203
+ await (0, promises_1.writeFile)(mediaLogsPath, JSON.stringify(mediaLogs, null, 2));
204
+ console.log(`Updated media_logs.json: ${fileName} -> ${JSON.stringify(result)} (${mediaType})`);
199
205
  }
200
206
  catch (error) {
201
- console.warn(`Failed to update media_durations.json for ${fileName}:`, error);
207
+ console.warn(`Failed to update media_logs.json for ${fileName}:`, error);
202
208
  }
203
209
  }
204
210
  async function listFiles(dir) {
@@ -266,6 +272,8 @@ server.registerTool('retrieve-rules-context', {
266
272
  'music-video',
267
273
  'stage-play',
268
274
  'anime-series',
275
+ 'story-telling',
276
+ 'creative-ad',
269
277
  'custom',
270
278
  ])
271
279
  .default('general-video')
@@ -285,7 +293,9 @@ server.registerTool('retrieve-rules-context', {
285
293
  else if (purpose !== 'general-video' &&
286
294
  purpose !== 'music-video' &&
287
295
  purpose !== 'stage-play' &&
288
- purpose !== 'anime-series') {
296
+ purpose !== 'anime-series' &&
297
+ purpose !== 'story-telling' &&
298
+ purpose !== 'creative-ad') {
289
299
  return createErrorResponse(`Project rules file not found: ${projectRulesFile}`, 'retrieve-rules-context');
290
300
  }
291
301
  try {
@@ -554,8 +564,8 @@ server.registerTool('search-image', {
554
564
  }
555
565
  });
556
566
  server.registerTool('generate-character-image', {
557
- title: 'Generate Turnaround Image',
558
- description: 'Generate a turnaround image for any character.',
567
+ title: 'Generate Character Image',
568
+ description: 'Generate a turnaround image or portrait for any character.',
559
569
  inputSchema: {
560
570
  name: zod_1.z.string().describe('The name of the character.'),
561
571
  gender: zod_1.z
@@ -576,14 +586,23 @@ server.registerTool('generate-character-image', {
576
586
  .string()
577
587
  .default('形象参考[图1]的人物形象\n')
578
588
  .describe('形象参考图的提示文本.'),
589
+ isTurnaround: zod_1.z
590
+ .boolean()
591
+ .default(true)
592
+ .describe('是否生成三视图。true: 生成1280x720的三视图,false: 生成720x1280的竖版人物正视图'),
579
593
  saveToFileName: zod_1.z.string().describe('The filename to save.'),
580
594
  },
581
- }, async ({ name, gender, age, appearance, clothing, personality, style, saveToFileName, referenceImage, referenceImagePrompt, }) => {
595
+ }, async ({ name, gender, age, appearance, clothing, personality, style, saveToFileName, referenceImage, referenceImagePrompt, isTurnaround, }) => {
582
596
  try {
583
597
  // 验证session状态
584
598
  const currentSession = await validateSession('generate-character-image');
585
599
  const validatedFileName = validateFileName(saveToFileName);
586
- const prompt = `
600
+ // 根据 isTurnaround 参数生成不同的提示词和尺寸
601
+ let prompt;
602
+ let size;
603
+ if (isTurnaround) {
604
+ // 生成三视图
605
+ prompt = `
587
606
  你是一个专业的角色设计师,请根据设定生成角色全身三视图,图片为白底,图中不带任何文字。设定为:
588
607
 
589
608
  角色名称:${name}
@@ -595,7 +614,26 @@ server.registerTool('generate-character-image', {
595
614
  构图风格:${style}
596
615
  ${referenceImagePrompt}
597
616
  三视图分别是指侧视图、正视图和背视图,从左到右按照这个顺序生成,三者都必须是全身图。
598
- `;
617
+ `;
618
+ size = '1280x720';
619
+ }
620
+ else {
621
+ // 生成竖版人物正视图
622
+ prompt = `
623
+ 你是一个专业的角色设计师,请根据设定生成角色全身正视图,图片为白底,图中不带任何文字。设定为:
624
+
625
+ 角色名称:${name}
626
+ 角色性别:${gender}
627
+ 角色年龄:${age}
628
+ 角色外观:${appearance}
629
+ 角色服装:${clothing}
630
+ 角色性格:${personality}
631
+ 构图风格:${style}
632
+ ${referenceImagePrompt}
633
+ 请生成一张完整的全身正视图,角色面向观众,展现完整的身体比例和服装细节。
634
+ `;
635
+ size = '720x1280';
636
+ }
599
637
  let imageBase64Array;
600
638
  if (referenceImage) {
601
639
  try {
@@ -623,7 +661,6 @@ ${referenceImagePrompt}
623
661
  }
624
662
  }
625
663
  const ai = currentSession.ai;
626
- const size = '1280x720';
627
664
  const res = await ai.generateImage({
628
665
  prompt,
629
666
  size,
@@ -640,6 +677,7 @@ ${referenceImagePrompt}
640
677
  // source: res.url,
641
678
  uri,
642
679
  prompt,
680
+ isTurnaround,
643
681
  size,
644
682
  timestamp: new Date().toISOString(),
645
683
  };
@@ -763,7 +801,7 @@ server.registerTool('generate-image', {
763
801
  .describe('Whether this is a turnaround image.如果是三视图,这个参数务必传true'),
764
802
  }))
765
803
  .optional()
766
- .describe(`Array of reference images with character or object names.如果stage_atmosphere中有角色apply_turnaround_image,那么必须要传这个参数生成角色图片
804
+ .describe(`Array of reference images with character or object names.如果stage_atmosphere中有角色apply_reference_image,那么必须要传这个参数生成角色图片
767
805
 
768
806
  传参示例
769
807
  \`\`\`
@@ -867,7 +905,7 @@ server.registerTool('generate-image', {
867
905
  }
868
906
  }
869
907
  const turnaroundMessage = hasTurnaround
870
- ? '⚠️ 注意**三视图**是指**同一个**人或物体的不同视角合成图,三部分都表示同一个人或物体,只能参考其信息画出**一个人或一个物体**,不要画成多个人或多个物体!\n\n'
908
+ ? '⚠️ 注意**三视图**是指**同一个**人或物体的不同视角合成图,三部分都表示同一个人或物体,只能参考其信息画出**一个人或一个物体**,不要画成多个人或多个物体!\n'
871
909
  : '';
872
910
  if (objectPrefix.length > 0) {
873
911
  processedPrompt = `${objectPrefix.join('\n')}
@@ -927,6 +965,231 @@ ${processedPrompt}`.trim();
927
965
  return createErrorResponse(error, 'generate-image');
928
966
  }
929
967
  });
968
+ server.registerTool('generate-image-series', {
969
+ title: 'Generate Image Series',
970
+ description: 'Generate a series of images based on prompts.',
971
+ inputSchema: {
972
+ prompts: zod_1.z
973
+ .union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())])
974
+ .describe('The prompts to generate images. Can be a single string or array of strings.'),
975
+ size: zod_1.z
976
+ .enum([
977
+ '1024x1024',
978
+ '864x1152',
979
+ '1152x864',
980
+ '1280x720',
981
+ '720x1280',
982
+ '832x1248',
983
+ '1248x832',
984
+ '1512x648',
985
+ ])
986
+ .optional()
987
+ .describe('The size of the images.'),
988
+ watermark: zod_1.z
989
+ .boolean()
990
+ .optional()
991
+ .default(false)
992
+ .describe('Whether to add watermark to the images.'),
993
+ max_count: zod_1.z
994
+ .number()
995
+ .min(1)
996
+ .max(15)
997
+ .optional()
998
+ .default(15)
999
+ .describe('Maximum number of images to generate (1-15).'),
1000
+ referenceImages: zod_1.z
1001
+ .array(zod_1.z.object({
1002
+ image: zod_1.z.string().describe('Local image file path'),
1003
+ type: zod_1.z
1004
+ .enum(['character', 'object', 'background'])
1005
+ .describe('Type of the reference image. 必须传,如果是参考角色三视图,传character,如果是参考背景图,传background,否则传object'),
1006
+ name: zod_1.z.string().describe('Name for this reference image'),
1007
+ isTurnaround: zod_1.z
1008
+ .boolean()
1009
+ .describe('Whether this is a turnaround image.如果是三视图,这个参数务必传true'),
1010
+ }))
1011
+ .optional()
1012
+ .describe(`Array of reference images with character or object names.如果stage_atmosphere中有角色apply_reference_image,那么必须要传这个参数生成角色图片
1013
+
1014
+ 传参示例
1015
+ \`\`\`
1016
+ {
1017
+ "image": "latiao.jpeg",
1018
+ "type": "object",
1019
+ "name": "卫龙辣条",
1020
+ }
1021
+ \`\`\`
1022
+ `),
1023
+ saveToFileNames: zod_1.z
1024
+ .array(zod_1.z.string())
1025
+ .describe('Array of filenames to save the generated images.'),
1026
+ },
1027
+ }, async ({ prompts, size, watermark, max_count, referenceImages, saveToFileNames }, context) => {
1028
+ try {
1029
+ const currentSession = await validateSession('generate-image-series');
1030
+ // Validate file names
1031
+ const validatedFileNames = saveToFileNames.map(fileName => validateFileName(fileName));
1032
+ if (typeof prompts === 'string' && prompts.startsWith('[')) {
1033
+ try {
1034
+ prompts = JSON.parse(prompts);
1035
+ }
1036
+ catch (ex) {
1037
+ // 解析失败,保持原始字符串
1038
+ }
1039
+ }
1040
+ console.log(`Generating image series with ${Array.isArray(prompts) ? prompts.length : 1} prompts...`);
1041
+ // 处理参考图片
1042
+ let imageBase64Array;
1043
+ let refPrefix = '';
1044
+ if (referenceImages && referenceImages.length > 0) {
1045
+ imageBase64Array = [];
1046
+ const objectPrefix = [];
1047
+ let index = 0;
1048
+ let hasTurnaround = false;
1049
+ for (const refImage of referenceImages) {
1050
+ const imagePath = (0, node_path_1.dirname)(refImage.image) !== '.'
1051
+ ? refImage.image
1052
+ : `./materials/${refImage.image}`;
1053
+ // 需要得到当前项目的绝对路径
1054
+ const imageFilePath = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir, imagePath);
1055
+ try {
1056
+ // 直接读取本地文件
1057
+ if (!(0, node_fs_1.existsSync)(imageFilePath)) {
1058
+ return createErrorResponse(`Reference image not found: ${imageFilePath}`, 'generate-image-series');
1059
+ }
1060
+ const imageBuffer = await (0, promises_1.readFile)(imageFilePath);
1061
+ const fileName = (0, node_path_1.basename)(imagePath);
1062
+ const mimeType = fileName.toLowerCase().endsWith('.png')
1063
+ ? 'image/png'
1064
+ : fileName.toLowerCase().endsWith('.jpg') ||
1065
+ fileName.toLowerCase().endsWith('.jpeg')
1066
+ ? 'image/jpeg'
1067
+ : 'image/png';
1068
+ const base64String = `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
1069
+ imageBase64Array.push(base64String);
1070
+ console.log(`Loaded reference image: ${imagePath} for character: ${refImage.name}`);
1071
+ if (refImage.type === 'character') {
1072
+ if (refImage.isTurnaround) {
1073
+ objectPrefix.push(`[参考图${++index}]是名为"${refImage.name}"的人物角色三视图`);
1074
+ hasTurnaround = true;
1075
+ }
1076
+ else {
1077
+ objectPrefix.push(`[参考图${++index}]是名为"${refImage.name}"的人物角色形象`);
1078
+ }
1079
+ }
1080
+ else if (refImage.type === 'object') {
1081
+ if (refImage.isTurnaround) {
1082
+ objectPrefix.push(`[参考图${++index}]是名为"${refImage.name}"的物件三视图`);
1083
+ hasTurnaround = true;
1084
+ }
1085
+ else {
1086
+ objectPrefix.push(`[参考图${++index}]是名为"${refImage.name}"的物件`);
1087
+ }
1088
+ }
1089
+ else if (refImage.type === 'background') {
1090
+ objectPrefix.push(`[参考图${++index}]是背景图,描述了"${refImage.name}"`);
1091
+ }
1092
+ }
1093
+ catch (error) {
1094
+ console.error(`Failed to load reference image ${imageFilePath} for ${refImage.name}:`, error);
1095
+ return createErrorResponse(`Failed to load reference image ${imageFilePath} for ${refImage.name}: ${error}`, 'generate-image-series');
1096
+ }
1097
+ }
1098
+ const turnaroundMessage = hasTurnaround
1099
+ ? '\n\n⚠️ 注意**三视图**是指**同一个**人或物体的不同视角合成图,三部分都表示同一个人或物体,只能参考其信息画出**一个人或一个物体**,不要画成多个人或多个物体!\n'
1100
+ : '';
1101
+ if (objectPrefix.length > 0) {
1102
+ refPrefix = `${objectPrefix.join('\n')}${turnaroundMessage}`;
1103
+ }
1104
+ }
1105
+ const ai = currentSession.ai;
1106
+ let progress = 0;
1107
+ const res = await ai.generateImageSeries({
1108
+ prompts,
1109
+ refPrefix,
1110
+ size,
1111
+ watermark,
1112
+ image: imageBase64Array,
1113
+ max_count,
1114
+ onProgress: async (metaData) => {
1115
+ // 心跳机制,每分钟调用一次,传递空的 metaData
1116
+ try {
1117
+ await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
1118
+ }
1119
+ catch (progressError) {
1120
+ console.warn('Failed to send progress update:', progressError);
1121
+ }
1122
+ },
1123
+ });
1124
+ if (!res) {
1125
+ throw new Error('Failed to generate image series: no response from AI service');
1126
+ }
1127
+ if (res.urls && Array.isArray(res.urls)) {
1128
+ console.log(`Image series generated successfully (${res.urls.length} images), saving to materials...`);
1129
+ const results = [];
1130
+ const maxImages = Math.min(res.urls.length, validatedFileNames.length);
1131
+ for (let i = 0; i < maxImages; i++) {
1132
+ const url = res.urls[i];
1133
+ const fileName = validatedFileNames[i];
1134
+ try {
1135
+ const uri = await saveMaterial(currentSession, url, fileName);
1136
+ results.push({
1137
+ success: true,
1138
+ uri,
1139
+ fileName,
1140
+ index: i,
1141
+ timestamp: new Date().toISOString(),
1142
+ });
1143
+ }
1144
+ catch (error) {
1145
+ console.error(`Failed to save image ${i + 1}:`, error);
1146
+ results.push({
1147
+ success: false,
1148
+ error: `Failed to save image ${i + 1}: ${error}`,
1149
+ fileName,
1150
+ index: i,
1151
+ timestamp: new Date().toISOString(),
1152
+ });
1153
+ }
1154
+ }
1155
+ const result = {
1156
+ success: true,
1157
+ prompt: res.prompt,
1158
+ totalGenerated: res.urls.length,
1159
+ totalSaved: results.filter(r => r.success).length,
1160
+ results,
1161
+ timestamp: new Date().toISOString(),
1162
+ };
1163
+ return {
1164
+ content: [
1165
+ {
1166
+ type: 'text',
1167
+ text: JSON.stringify(result),
1168
+ },
1169
+ ],
1170
+ };
1171
+ }
1172
+ else {
1173
+ console.warn('Image series generation completed but no URLs returned');
1174
+ return {
1175
+ content: [
1176
+ {
1177
+ type: 'text',
1178
+ text: JSON.stringify({
1179
+ success: false,
1180
+ error: 'No image URLs returned from AI service',
1181
+ response: res,
1182
+ timestamp: new Date().toISOString(),
1183
+ }),
1184
+ },
1185
+ ],
1186
+ };
1187
+ }
1188
+ }
1189
+ catch (error) {
1190
+ return createErrorResponse(error, 'generate-image-series');
1191
+ }
1192
+ });
930
1193
  server.registerTool('edit-image', {
931
1194
  title: 'Edit Image',
932
1195
  description: 'Edit the image.',
@@ -1148,12 +1411,12 @@ server.registerTool('generate-video', {
1148
1411
  timestamp: new Date().toISOString(),
1149
1412
  ...opts,
1150
1413
  };
1151
- // Update media_durations.json
1414
+ // Update media_logs.json
1152
1415
  try {
1153
- await updateMediaDurations(currentSession, validatedFileName, result.durationMs);
1416
+ await updateMediaLogs(currentSession, validatedFileName, result);
1154
1417
  }
1155
1418
  catch (error) {
1156
- console.warn(`Failed to update media_durations.json for ${validatedFileName}:`, error);
1419
+ console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
1157
1420
  }
1158
1421
  return {
1159
1422
  content: [
@@ -1363,13 +1626,12 @@ server.registerTool('generate-sound-effect', {
1363
1626
  });
1364
1627
  if (res.url) {
1365
1628
  const uri = await saveMaterial(currentSession, res.url, saveToFileName);
1366
- // Update media_durations.json
1629
+ // Update media_logs.json
1367
1630
  try {
1368
- const durationMs = res.duration ? Math.floor(res.duration * 1000) : 0;
1369
- await updateMediaDurations(currentSession, saveToFileName, durationMs);
1631
+ await updateMediaLogs(currentSession, saveToFileName, res, 'audio');
1370
1632
  }
1371
1633
  catch (error) {
1372
- console.warn(`Failed to update media_durations.json for ${saveToFileName}:`, error);
1634
+ console.warn(`Failed to update media_logs.json for ${saveToFileName}:`, error);
1373
1635
  }
1374
1636
  return {
1375
1637
  content: [
@@ -1562,12 +1824,12 @@ server.registerTool('generate-song', {
1562
1824
  timestamp: new Date().toISOString(),
1563
1825
  ...opts,
1564
1826
  };
1565
- // Update media_durations.json
1827
+ // Update media_logs.json
1566
1828
  try {
1567
- await updateMediaDurations(currentSession, validatedFileName, result.durationMs);
1829
+ await updateMediaLogs(currentSession, validatedFileName, result, 'audio');
1568
1830
  }
1569
1831
  catch (error) {
1570
- console.warn(`Failed to update media_durations.json for ${validatedFileName}:`, error);
1832
+ console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
1571
1833
  }
1572
1834
  return {
1573
1835
  content: [
@@ -1648,12 +1910,12 @@ server.registerTool('generate-bgm', {
1648
1910
  timestamp: new Date().toISOString(),
1649
1911
  ...opts,
1650
1912
  };
1651
- // Update media_durations.json
1913
+ // Update media_logs.json
1652
1914
  try {
1653
- await updateMediaDurations(currentSession, validatedFileName, result.durationMs);
1915
+ await updateMediaLogs(currentSession, validatedFileName, result, 'audio');
1654
1916
  }
1655
1917
  catch (error) {
1656
- console.warn('Failed to update media_durations.json:', error);
1918
+ console.warn('Failed to update media_logs.json:', error);
1657
1919
  }
1658
1920
  return {
1659
1921
  content: [
@@ -1809,12 +2071,12 @@ server.registerTool('generate-scene-tts', {
1809
2071
  timestamp: new Date().toISOString(),
1810
2072
  ...opts,
1811
2073
  };
1812
- // Update media_durations.json
2074
+ // Update media_logs.json
1813
2075
  try {
1814
- await updateMediaDurations(currentSession, validatedFileName, result.durationMs);
2076
+ await updateMediaLogs(currentSession, validatedFileName, result, 'audio');
1815
2077
  }
1816
2078
  catch (error) {
1817
- console.warn(`Failed to update media_durations.json for ${validatedFileName}:`, error);
2079
+ console.warn(`Failed to update media_logs.json for ${validatedFileName}:`, error);
1818
2080
  }
1819
2081
  return {
1820
2082
  content: [
@@ -2234,7 +2496,7 @@ server.registerTool('lip-sync', {
2234
2496
  .string()
2235
2497
  .describe('The filename to save the lip-synced video.'),
2236
2498
  },
2237
- }, async ({ videoFileName, audioFileName, saveToFileName }) => {
2499
+ }, async ({ videoFileName, audioFileName, saveToFileName }, context) => {
2238
2500
  try {
2239
2501
  // 验证session状态
2240
2502
  const currentSession = await validateSession('lip-sync');
@@ -2252,11 +2514,18 @@ server.registerTool('lip-sync', {
2252
2514
  console.log(`Video URL: ${videoUrl}`);
2253
2515
  console.log(`Audio URL: ${audioUrl}`);
2254
2516
  // 调用AI的lipSync方法,使用处理后的音频
2517
+ let progress = 0;
2255
2518
  const result = await currentSession.ai.lipSync({
2256
2519
  videoUrl,
2257
2520
  audioUrl,
2258
- onProgress: metaData => {
2521
+ onProgress: async (metaData) => {
2259
2522
  console.log('Lip sync progress:', metaData);
2523
+ try {
2524
+ await sendProgress(context, ++progress, undefined, JSON.stringify(metaData));
2525
+ }
2526
+ catch (progressError) {
2527
+ console.warn('Failed to send progress update:', progressError);
2528
+ }
2260
2529
  },
2261
2530
  });
2262
2531
  if (result.url) {