cerevox 2.15.2 → 2.16.0

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.
@@ -285,6 +285,8 @@ projects/<id>/
285
285
 
286
286
  3. 镜头自然延伸:这是一种和“一镜到底”不同的连续画面技术,当用户说“镜头自然延伸”时,你应当在生成视频时,设置saveLastFrameAs参数,将其返回的图片作为下一场景的首帧图片,不断延伸直至所有场景生成完成。如果你采用镜头自然延伸,那么你只需要设置第一个场景的start_frame,在story_board中其余场景可标记不需要start_frame,后续不断生成各场景视频,同时保存下一场景start_frame即可。
287
287
 
288
+ 4. 手工修改草稿:在 draft_content 生成后,如果用户要求自行修改草稿,可运行 `custom-edit-draft` 工具(这个工具是本地工具,不需要先启动会话),启动服务让用户自行修改草稿。
289
+
288
290
  ---
289
291
 
290
292
  ## 工具优先级
@@ -1 +1 @@
1
- {"version":3,"file":"zerocut.d.ts","sourceRoot":"","sources":["../../../src/mcp/servers/zerocut.ts"],"names":[],"mappings":";AAy1HA,wBAAsB,GAAG,kBAKxB"}
1
+ {"version":3,"file":"zerocut.d.ts","sourceRoot":"","sources":["../../../src/mcp/servers/zerocut.ts"],"names":[],"mappings":";AAukIA,wBAAsB,GAAG,kBAKxB"}
@@ -53,7 +53,51 @@ 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_http_1 = __importDefault(require("node:http"));
57
+ const node_child_process_1 = require("node:child_process");
58
+ const node_util_1 = require("node:util");
59
+ const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
56
60
  // 错误处理工具函数
61
+ const handleError = (error) => {
62
+ if (error instanceof Error) {
63
+ return error.message;
64
+ }
65
+ return String(error);
66
+ };
67
+ // 检查端口是否被占用
68
+ const isPortInUse = async (port) => {
69
+ try {
70
+ const { stdout } = await execAsync(`lsof -ti:${port}`);
71
+ return stdout.trim().length > 0;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ };
77
+ // 停止占用端口的进程
78
+ const killPortProcess = async (port) => {
79
+ try {
80
+ const { stdout } = await execAsync(`lsof -ti:${port}`);
81
+ const pids = stdout
82
+ .trim()
83
+ .split('\n')
84
+ .filter(pid => pid);
85
+ for (const pid of pids) {
86
+ try {
87
+ await execAsync(`kill -9 ${pid}`);
88
+ console.log(`Killed process ${pid} on port ${port}`);
89
+ }
90
+ catch (error) {
91
+ console.warn(`Failed to kill process ${pid}:`, error);
92
+ }
93
+ }
94
+ // 等待一下确保进程被杀死
95
+ await new Promise(resolve => setTimeout(resolve, 1000));
96
+ }
97
+ catch (error) {
98
+ console.warn(`Failed to kill processes on port ${port}:`, error);
99
+ }
100
+ };
57
101
  function createErrorResponse(error, operation) {
58
102
  const errorMessage = error instanceof Error ? error.message : String(error);
59
103
  console.error(`[${operation}] Error:`, error);
@@ -841,13 +885,13 @@ server.registerTool('generate-image', {
841
885
  },
842
886
  }, async ({ prompt, sceneIndex, storyBoardFile = 'story_board.json', skipConsistencyCheck = false, size, saveToFileName, watermark, referenceImages, optimizePrompt, }) => {
843
887
  try {
888
+ // 验证session状态
889
+ const currentSession = await validateSession('generate-image');
844
890
  // 检查 storyboard 标志
845
891
  if (!checkStoryboardFlag) {
846
892
  checkStoryboardFlag = true;
847
893
  return createErrorResponse('必须先审查生成的 story_board.json 内容,确保每个场景中的stage_atmosphere内容按照规则被正确融合到start_frame和video_prompt中,不得遗漏,检查完成后先汇报,如果有问题,应当先修改 story_board.json 内容,然后再调用 generate-image 生成图片', 'generate-image');
848
894
  }
849
- // 验证session状态
850
- const currentSession = await validateSession('generate-image');
851
895
  const validatedFileName = validateFileName(saveToFileName);
852
896
  // 校验 prompt 与 story_board.json 中场景设定的一致性
853
897
  if (!skipConsistencyCheck) {
@@ -3046,6 +3090,160 @@ server.registerTool('run-ffmpeg-command', {
3046
3090
  return createErrorResponse(error, 'run-ffmpeg-command');
3047
3091
  }
3048
3092
  });
3093
+ server.registerTool('custom-edit-draft', {
3094
+ title: 'Custom Edit Draft',
3095
+ description: 'Launch timeline editor server for custom draft editing. Starts a local server at specified port with draft file configuration.',
3096
+ inputSchema: {
3097
+ draftContentFileName: zod_1.z
3098
+ .string()
3099
+ .optional()
3100
+ .default('draft_content.json')
3101
+ .describe('Draft content filename (default: draft_content.json)'),
3102
+ port: zod_1.z
3103
+ .number()
3104
+ .optional()
3105
+ .default(6789)
3106
+ .describe('Server port (default: 6789)'),
3107
+ },
3108
+ }, async ({ draftContentFileName = 'draft_content.json', port = 6789 }) => {
3109
+ try {
3110
+ // 使用项目本地目录
3111
+ const workDir = (0, node_path_1.resolve)(process.env.ZEROCUT_PROJECT_CWD || process.cwd(), projectLocalDir);
3112
+ // 检查 draft_content.json 文件是否存在
3113
+ const draftFilePath = (0, node_path_1.resolve)(workDir, draftContentFileName);
3114
+ if (!(0, node_fs_1.existsSync)(draftFilePath)) {
3115
+ return createErrorResponse(new Error(`Draft content file not found: ${draftContentFileName}. Please generate draft content first using the generate-draft-content tool.`), 'custom-edit-draft');
3116
+ }
3117
+ // 备份原始的 draft_content.json 文件
3118
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
3119
+ const backupFileName = `${draftContentFileName}.backup.${timestamp}`;
3120
+ const backupFilePath = (0, node_path_1.resolve)(workDir, backupFileName);
3121
+ try {
3122
+ const { copyFileSync } = await Promise.resolve().then(() => __importStar(require('fs')));
3123
+ copyFileSync(draftFilePath, backupFilePath);
3124
+ console.log(`Draft file backed up to: ${backupFileName}`);
3125
+ }
3126
+ catch (backupError) {
3127
+ console.warn(`Failed to backup draft file: ${backupError}`);
3128
+ // 继续执行,不因备份失败而中断
3129
+ }
3130
+ // 构建启动命令参数
3131
+ const timelineEditorPath = (0, node_path_1.resolve)(__dirname, '../../../dist/timeline-editor/index.js');
3132
+ console.log(`Starting timeline editor server...`);
3133
+ console.log(`Working directory: ${workDir}`);
3134
+ console.log(`Draft file: ${draftContentFileName}`);
3135
+ console.log(`Server port: ${port}`);
3136
+ console.log(`Timeline editor path: ${timelineEditorPath}`);
3137
+ // 检查端口是否被占用,如果被占用则停止占用的进程
3138
+ if (await isPortInUse(port)) {
3139
+ console.log(`Port ${port} is in use, killing existing processes...`);
3140
+ await killPortProcess(port);
3141
+ }
3142
+ // 直接启动服务器进程
3143
+ const serverProcess = (0, node_child_process_1.spawn)('node', [
3144
+ timelineEditorPath,
3145
+ '--draft-file',
3146
+ draftContentFileName,
3147
+ '--project-dir',
3148
+ workDir,
3149
+ '--port',
3150
+ port.toString(),
3151
+ ], {
3152
+ cwd: workDir,
3153
+ stdio: 'pipe',
3154
+ });
3155
+ const serverUrl = `http://localhost:${port}`;
3156
+ // 等待服务器启动 - 使用健康检查 API 轮询
3157
+ await new Promise((resolve, reject) => {
3158
+ const timeout = setTimeout(() => {
3159
+ reject(new Error('Server startup timeout'));
3160
+ }, 30000); // 增加超时时间到30秒
3161
+ const checkHealth = async () => {
3162
+ try {
3163
+ const healthUrl = `${serverUrl}/health`;
3164
+ const req = node_http_1.default.get(healthUrl, (res) => {
3165
+ let data = '';
3166
+ res.on('data', (chunk) => {
3167
+ data += chunk;
3168
+ });
3169
+ res.on('end', () => {
3170
+ try {
3171
+ const healthData = JSON.parse(data);
3172
+ if (healthData.status === 'ok') {
3173
+ clearTimeout(timeout);
3174
+ console.log('Server health check passed');
3175
+ resolve(void 0);
3176
+ }
3177
+ else {
3178
+ // 继续轮询
3179
+ setTimeout(checkHealth, 1000);
3180
+ }
3181
+ }
3182
+ catch (parseError) {
3183
+ // 继续轮询
3184
+ setTimeout(checkHealth, 1000);
3185
+ }
3186
+ });
3187
+ });
3188
+ req.on('error', () => {
3189
+ // 服务器还未启动,继续轮询
3190
+ setTimeout(checkHealth, 1000);
3191
+ });
3192
+ req.setTimeout(2000, () => {
3193
+ req.destroy();
3194
+ // 继续轮询
3195
+ setTimeout(checkHealth, 1000);
3196
+ });
3197
+ }
3198
+ catch (error) {
3199
+ // 继续轮询
3200
+ setTimeout(checkHealth, 1000);
3201
+ }
3202
+ };
3203
+ serverProcess.stdout?.on('data', (data) => {
3204
+ console.log('Server output:', data.toString());
3205
+ });
3206
+ serverProcess.stderr?.on('data', (data) => {
3207
+ console.error('Server error:', data.toString());
3208
+ });
3209
+ serverProcess.on('error', (error) => {
3210
+ clearTimeout(timeout);
3211
+ reject(error);
3212
+ });
3213
+ // 开始健康检查轮询
3214
+ setTimeout(checkHealth, 2000); // 2秒后开始第一次检查
3215
+ });
3216
+ return {
3217
+ content: [
3218
+ {
3219
+ type: 'text',
3220
+ text: JSON.stringify({
3221
+ success: true,
3222
+ message: 'Timeline editor server started successfully',
3223
+ serverUrl: serverUrl,
3224
+ draftFile: draftContentFileName,
3225
+ backupFile: backupFileName,
3226
+ projectDirectory: workDir,
3227
+ port: port,
3228
+ pid: serverProcess.pid,
3229
+ instructions: [
3230
+ `Timeline editor is now running at: ${serverUrl}`,
3231
+ 'Open the URL in your browser to edit the draft',
3232
+ 'The server will automatically save changes to the draft file',
3233
+ `Original draft file has been backed up as: ${backupFileName}`,
3234
+ `Server process ID: ${serverProcess.pid}`,
3235
+ 'The server will continue running in the background',
3236
+ ],
3237
+ }, null, 2),
3238
+ },
3239
+ ],
3240
+ };
3241
+ }
3242
+ catch (error) {
3243
+ console.error('Error starting timeline editor:', error);
3244
+ return createErrorResponse(error, 'custom-edit-draft');
3245
+ }
3246
+ });
3049
3247
  async function run() {
3050
3248
  // Start receiving messages on stdin and sending messages on stdout
3051
3249
  const transport = new stdio_js_1.StdioServerTransport();