diff --git a/data/skills/production_agent_decision.md b/data/skills/production_agent_decision.md index 055f210..f3eb4ee 100644 --- a/data/skills/production_agent_decision.md +++ b/data/skills/production_agent_decision.md @@ -1,233 +1,3 @@ -# 决策层 Agent 技能指令 - -你是视频制作项目的**决策层 Agent**,**只负责决策和任务派发**:理解用户意图、拆解任务、调度执行层与监督层、把控质量。 -你是唯一与用户直接对接的 Agent,执行层和监督层只接收你派发的指令。 - -**核心原则:** -- **决策层不执行具体任务**,不读取工作区数据(不调用 get_flowData),不直接操作任何资产或分镜数据。所有具体工作由执行层完成。 -- **决策层不做执行层的判断**,执行层返回什么结论就基于该结论决策下一步。 - -## 核心职责 - -1. **需求分析**:解析用户请求,判断属于流水线哪个阶段 -2. **任务拆解**:将复杂请求分解为可执行的子任务 -3. **调度执行**:通过 `run_sub_agent_execution` 派发任务到执行层 -4. **质量管控**:通过 `run_sub_agent_supervision` 调用监督层审核产出物 -5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆 - ---- - -## 制作流水线 - -五个阶段**必须按顺序执行**: - -``` -阶段1: 衍生资产分析 → 阶段2: 衍生资产生成(可选) → 阶段3: 导演规划 → 阶段4: 构建分镜表 → 阶段5: 生成分镜 -``` - -### 全局约束 - -- **资产约束**:阶段3、4、5 只能使用资产库中已存在的资产(含阶段1已写入的衍生资产) -- **异步操作**:阶段2的图片生成、阶段5的分镜图片生成均为异步操作,派发后告知用户等待即可 -- **审核规则**:仅阶段3(导演规划)和阶段4(构建分镜表)需要审核,执行完毕后自动派发监督层 - ---- - -### 阶段1:衍生资产分析 - -| 项 | 说明 | -|----|------| -| 派发 | 执行层分析剧本,识别并写入衍生资产信息 | -| 输出 | 衍生资产分析报告 + 衍生资产写入结果(或"无需衍生"结论) | -| 前置条件 | 剧本和资产已存在于工作区 | -| 审核 | 不需要 | - -**决策层行为:** - -| 执行层返回 | 决策层操作 | -|-----------|-----------| -| "不需要衍生资产" | 向用户简要告知,直接进入阶段3 | -| 衍生资产清单(已写入) | 展示给用户,询问是否确认生成图片 | - -**用户确认分支(仅有新增资产时):** - -| 用户反馈 | 操作 | -|----------|------| -| 确认全部生成 | 进入阶段2 | -| 部分生成 | 将用户选择的子集传递给阶段2 | -| 跳过 | 直接进入阶段3,告知后续仅使用现有资产 | -| 调整清单 | 重新派发分析或将调整后清单传递给阶段2 | - -> 约束:阶段1必须完成衍生资产信息写入;分析结果需展示给用户确认是否进入图片生成,且不可自动进入阶段2。 - ---- - -### 阶段2:衍生资产生成(可选) - -| 项 | 说明 | -|----|------| -| 派发 | 执行层对已写入的衍生资产生成图片 | -| 输入 | 用户确认需要生成图片的衍生资产清单(来自阶段1) | -| 输出 | 图片生成启动 | -| 前置条件 | 阶段1完成且用户确认生成 | -| 审核 | 不需要 | - -**决策层行为:** 将用户确认的资产清单(或子集)派发给执行层。返回确认后,告知用户图片生成中,直接进入阶段3。 - ---- - -### 阶段3:导演规划 - -| 项 | 说明 | -|----|------| -| 派发 | 执行层制定导演拍摄计划 | -| 输出 | 导演拍摄计划(执行层通过 set_plane 同步到前端) | -| 质量门 | 计划覆盖全部剧情、节奏合理、与资产匹配 | -| 前置条件 | 阶段1完成(含跳过阶段2的情况) | -| 审核 | **需要** → 执行完毕后自动派发监督层 | - -**阶段特有约束:** 规划中引用的角色、道具、场景必须在资产列表中存在。 - ---- - -### 阶段4:构建分镜表 - -| 项 | 说明 | -|----|------| -| 派发 | 执行层将剧本拆分为分镜,生成结构化分镜表 | -| 输出 | 结构化分镜表(执行层通过 set_flowData 保存) | -| 质量门 | 分镜拆分粒度合理、字段完整、关联资产正确 | -| 前置条件 | 阶段3(导演规划)完成 | -| 审核 | **需要** → 执行完毕后自动派发监督层 | - -**阶段特有约束:** `associateAssetsIds` 中的索引必须指向资产库中实际存在的资产。 - ---- - -### 阶段5:构建分镜面板 - -| 项 | 说明 | -|----|------| -| 派发 | 执行层调将分镜表拆分生成分镜面板 | -| 输出 | 生成的分镜面板 | -| 前置条件 | 阶段4完成且用户确认 | -| 审核 | 不需要 | - - -### 阶段6:生成分镜 - -| 项 | 说明 | -|----|------| -| 派发 | 执行层调用图片生成接口生成分镜图片 | -| 输出 | 生成的分镜图片 | -| 前置条件 | 阶段5完成且用户确认 | -| 审核 | 不需要 | - -**阶段特有约束:** `generate_storyboard_images({ ids: [真实分镜ID列表] })` 中的 ids参数 必须指向分镜面板中实际存在的分镜Id。 -**决策层行为:** 执行层返回确认后,告知用户图片生成已启动,流程结束。 ---- - -## 调度与派发规范 - -### 派发指令要求 - -**派发给执行层和监督层的任务指令正文严格不超过100字。** 执行层已具备完整技能指令,只需告知任务类型和关键参数。 - -### 执行层派发 - -使用 `run_sub_agent_execution` 调用执行层: - -``` -run_sub_agent_execution( - prompts: "<按模板构建的具体指令>" -) -``` - -### 审核派发与结果处理 - -阶段3或阶段4执行完毕后: -1. 将执行层返回的确认消息展示给用户 -2. **紧接着自动调用监督层审核**(无需等待用户指示) - -``` -run_sub_agent_supervision( - prompts: "请审核【{阶段名}】的产出物。审核维度:{维度列表}" -) -``` - -监督层审核完毕后将报告展示给用户。决策层**等待用户回复**,根据反馈操作: - -| 用户反馈 | 操作 | -|----------|------| -| 通过 / 下一阶段 | 派发下一阶段任务 | -| 需要修复 | 根据用户指示构建修复指令,派发执行层 | -| 重做 | 重新派发当前阶段任务 | - -### 调度决策树 - -| 用户请求 | 处理规则 | -|----------|----------| -| 明确指定阶段 | 检查前置条件 → 派发该阶段 | -| "从头开始" / "完整制作" | 从阶段1顺序执行 | -| "继续" / "下一步" | `deepRetrieve` 获取进度 → 从当前阶段继续 | -| "修改/优化 X" | 定位对应阶段 → 派发修改任务 | -| 模糊请求 | `deepRetrieve` 获取进度 → 从当前阶段继续 | - ---- - -## 指令模板 - -### 执行派发格式 - -``` -你是执行层Agent,请执行【{任务类型}】任务。 -目标:{一句话目标} -上下文:{必要数据摘要} -要求: -1. {具体步骤1} -2. {具体步骤2} -约束:{特殊约束条件} -``` - -### 修复派发格式 - -``` -你是执行层Agent,请修复【{任务类型}】的以下问题。 -用户确认的修复项: -1. {问题} → 修改为:{方案} -保持其余内容不变。 -``` - -> 修复指令中只包含用户明确确认要修的项,不包含用户未回应或跳过的问题。 - ---- - -## 记忆检索策略 - -在以下场景使用 `deepRetrieve`: -1. **新会话开始**:检索项目当前进度、已完成阶段 -2. **用户提到之前的内容**:检索相关历史产出摘要 -3. **质量问题追溯**:检索之前的审核结果和修改记录 -4. **判断前置条件**:检索各阶段是否已完成 - -> `deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。 - ---- - -## 与用户交互规范 - -1. **进度汇报**:每完成一个阶段,汇报结果摘要和下一步计划 -2. **审核结果展示**:阶段3、4由监督层审核后展示报告,等待用户反馈 -3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定 -4. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节 - ---- - -## 错误处理 - -| 场景 | 处理 | -|------|------| -| 执行层返回错误 | 分析原因,调整指令重新派发(最多重试2次) | -| 监督层发现质量问题 | 等待用户确认修复方案 → 派发修复指令 | -| 前置条件不满足 | 提示用户需先完成哪个阶段 | -| 记忆检索无结果 | 请求用户提供必要上下文 | +你是一个测试流程助手,你专门生成假数据: +你必须使用XML格式写入工作区分镜面板: +现在请根据哟用户指令直接输出xml假数据 diff --git a/data/skills/production_agent_execution.md b/data/skills/production_agent_execution.md index 6b3f39f..8631606 100644 --- a/data/skills/production_agent_execution.md +++ b/data/skills/production_agent_execution.md @@ -379,7 +379,7 @@ add_deriveAsset({ --- -## 五、生成分镜图片 +## 五、生成分镜面板与生成分镜图片 ### 工具 @@ -393,11 +393,11 @@ add_deriveAsset({ ### 执行流程 1. 获取 `script` 、`stoaryTable` -2. 使用XML格式写入工作区分镜面板: -3. 先获取 `storyboard`数据 再调用 `generate_storyboard_images({ ids: [真实分镜ID列表] })` 生成分镜图片(异步,发起即返回) +2. 使用XML格式写入工作区分镜面板: +3. 生成分镜面板后,先获取 `storyboard`数据 再调用 `generate_storyboard_images({ ids: [真实分镜ID列表] })` 生成分镜图片(异步,发起即返回) ### 约束 - 前置条件:分镜表已构建完成且用户已确认 - 图片必须与分镜描述匹配 -- 你必须使用XML格式写入工作区分镜面板: \ No newline at end of file +- 你必须使用XML格式写入工作区分镜面板: \ No newline at end of file diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index 3f1bbae..07ea389 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -138,7 +138,7 @@ function createSubAgent(parentCtx: AgentContext) { "你必须使用如下XML格式写入工作区:\n```", "拍摄计划:内容", "分镜表:内容", - "分镜面板: ", + "分镜面板:", "```", ].join("\n"); // "剧本:", diff --git a/src/routes/novel/event/generateEvents.ts b/src/routes/novel/event/generateEvents.ts index e025430..83b422c 100644 --- a/src/routes/novel/event/generateEvents.ts +++ b/src/routes/novel/event/generateEvents.ts @@ -12,13 +12,14 @@ export default router.post( validateFields({ projectId: z.number(), novelIds: z.array(z.number()), + concurrentCount: z.number().min(1).optional(), }), async (req, res) => { - const { projectId, novelIds } = req.body; + const { projectId, novelIds, concurrentCount = 5 } = req.body; const [allChapters, novel] = await Promise.all([ u.db("o_novel").where("projectId", projectId).whereIn("id", novelIds), - Promise.resolve(new u.cleanNovel()), + Promise.resolve(new u.cleanNovel(concurrentCount)), ]); if (allChapters.length === 0) { return res.status(400).send(success("没有对应章节")); diff --git a/src/routes/production/assets/batchGenerateAssetsImage.ts b/src/routes/production/assets/batchGenerateAssetsImage.ts index 5ba4f67..a1371d0 100644 --- a/src/routes/production/assets/batchGenerateAssetsImage.ts +++ b/src/routes/production/assets/batchGenerateAssetsImage.ts @@ -14,9 +14,10 @@ export default router.post( assetIds: z.array(z.number()), projectId: z.number(), scriptId: z.number(), + concurrentCount: z.number().min(1).optional(), }), async (req, res) => { - const { assetIds, projectId, scriptId } = req.body; + const { assetIds, projectId, scriptId, concurrentCount = 5 } = req.body; const projectSettingData = await u.db("o_project").where("id", projectId).select("imageModel", "imageQuality", "artStyle").first(); @@ -53,8 +54,24 @@ export default router.post( prompt: scenePrompt, }, }; - const imageData = []; + // 先批量为所有 assets 创建 image 记录并标记为"生成中" + const imageIdMap: Record = {}; for (const item of assetsDataArr) { + const [imageId] = await u.db("o_image").insert({ + assetsId: item.id, + type: item.type, + state: "生成中", + resolution: projectSettingData?.imageQuality, + model: projectSettingData?.imageModel, + }); + imageIdMap[item.id!] = imageId; + await u.db("o_assets").where("id", item.id).update({ imageId: imageId }); + } + + const imageData: { id: number; state: string; src: string }[] = []; + res.status(200).send(success("开始生成资产图片")); + const generateSingleAsset = async (item: (typeof assetsDataArr)[number]) => { + const imageId = imageIdMap[item.id!]; const typeConfig = promptRecord[item.type!] || promptRecord["role"]; const { text } = await u.Ai.Text("universalAi").invoke({ @@ -67,13 +84,6 @@ export default router.post( ], }); - const [imageId] = await u.db("o_image").insert({ - assetsId: item.id, - type: item.type, - state: "生成中", - resolution: projectSettingData?.imageQuality, - model: projectSettingData?.imageModel, - }); const imageBase64 = imageUrlRecord[item.assetsId!] ? await urlToBase64(imageUrlRecord[item.assetsId!]) : null; try { const repeloadObj = { @@ -93,29 +103,33 @@ export default router.post( projectId: projectId, }, ); - const savePath = `/${projectId}/assets/${scriptId}/${u.uuid()}.jpg`; + const savePath = `/${projectId}/assets/${scriptId}/${item.type}/${u.uuid()}.jpg`; await imageCls.save(savePath); - // 更新对应数据库 - await u.db("o_assets").where("id", item.id).update({ imageId: imageId, prompt: text }); + await u.db("o_assets").where("id", item.id).update({ prompt: text }); await u.db("o_image").where({ id: imageId }).update({ state: "已完成", filePath: savePath }); - imageData.push({ - id: item.id, + return { + id: item.id!, state: "已完成", src: await u.oss.getFileUrl(savePath), - }); + }; } catch (e) { await u .db("o_image") .where({ id: imageId }) .update({ state: "生成失败", reason: u.error(e).message }); - imageData.push({ - id: item.id, + return { + id: item.id!, state: "生成失败", src: "", - }); + }; } - } + }; - return res.status(200).send(success(imageData)); + // 按 concurrentCount 分批并发执行 + for (let i = 0; i < assetsDataArr.length; i += concurrentCount) { + const batch = assetsDataArr.slice(i, i + concurrentCount); + const batchResults = await Promise.all(batch.map(generateSingleAsset)); + imageData.push(...batchResults); + } }, ); diff --git a/src/routes/production/saveFlowData.ts b/src/routes/production/saveFlowData.ts index 021021f..c48dcb4 100644 --- a/src/routes/production/saveFlowData.ts +++ b/src/routes/production/saveFlowData.ts @@ -24,13 +24,16 @@ export default router.post( episodesId: number; } = req.body; const sqlData = await u.db("o_agentWorkData").where("projectId", String(projectId)).andWhere("episodesId", String(episodesId)).first(); - if (data.storyboard && data.storyboard.length) + const filterDatas = data.storyboard.filter((i) => !i.id); + if (data.storyboard && data.storyboard.length && !filterDatas.length) await Promise.all( - data.storyboard.map(async (i, index) => { - await u.db("o_storyboard").where("id", i.id).update({ - index: index, - }); - }), + data.storyboard + .filter((i) => i.id) + .map(async (i, index) => { + await u.db("o_storyboard").where("id", i.id).update({ + index: index, + }); + }), ); if (!sqlData) { await u.db("o_agentWorkData").insert({ diff --git a/src/routes/production/storyboard/batchGenerateImage.ts b/src/routes/production/storyboard/batchGenerateImage.ts index ceed8ae..398fe31 100644 --- a/src/routes/production/storyboard/batchGenerateImage.ts +++ b/src/routes/production/storyboard/batchGenerateImage.ts @@ -16,16 +16,19 @@ export default router.post( storyboardIds: z.array(z.number()), projectId: z.number(), scriptId: z.number(), + concurrentCount: z.number().min(1).optional(), }), async (req, res) => { const { storyboardIds, projectId, scriptId, + concurrentCount = 5, }: { storyboardIds: number[]; projectId: number; scriptId: number; + concurrentCount: number; } = req.body; if (!storyboardIds || storyboardIds.length === 0) return res.status(400).send(error("storyboardIds不能为空")); // 当没有 storyboardIds 时,通过 AI 生成新的分镜面板数据 @@ -47,6 +50,9 @@ export default router.post( } assetRecord[item.storyboardId].push(item.imageId); }); + await u.db("o_storyboard").whereIn("id", finalStoryboardIds).update({ + state: "生成中", + }); res.status(200).send( success( storyboardData.map((i) => ({ @@ -58,16 +64,14 @@ export default router.post( })), ), ); - for (const item of storyboardData) { + const generateTask = async (item: (typeof storyboardData)[number]) => { const repeloadObj = { prompt: item.prompt!, size: projectSettingData?.imageQuality as "1K" | "2K" | "4K", aspectRatio: "16:9" as `${number}:${number}`, }; - await u.db("o_storyboard").where("id", item.id).update({ - state: "生成中", - }); - u.Ai.Image(projectSettingData?.imageModel as `${string}:${string}`) + + await u.Ai.Image(projectSettingData?.imageModel as `${string}:${string}`) .run( { imageBase64: await getAssetsImageBase64(assetRecord[item.id!] || []), @@ -97,6 +101,12 @@ export default router.post( state: "生成失败", }); }); + }; + + // 按 concurrentCount 控制并发数,分批执行 + for (let i = 0; i < storyboardData.length; i += concurrentCount) { + const batch = storyboardData.slice(i, i + concurrentCount); + await Promise.all(batch.map(generateTask)); } }, ); diff --git a/src/routes/production/storyboard/updateStoryboardUrl.ts b/src/routes/production/storyboard/updateStoryboardUrl.ts index e985e0f..95294f9 100644 --- a/src/routes/production/storyboard/updateStoryboardUrl.ts +++ b/src/routes/production/storyboard/updateStoryboardUrl.ts @@ -21,7 +21,8 @@ export default router.post( .update({ filePath: new URL(url).pathname, flowId, + state: "已完成", }); - res.status(200).send(success({ message: "更新提示词成功" })); + res.status(200).send(success({ message: "更新分镜成功" })); }, ); diff --git a/src/routes/script/extractAssets.ts b/src/routes/script/extractAssets.ts index 33219a2..1c9f026 100644 --- a/src/routes/script/extractAssets.ts +++ b/src/routes/script/extractAssets.ts @@ -54,7 +54,7 @@ export default router.post( validateFields({ scriptIds: z.array(z.number()), projectId: z.number(), - groupSize: z.number().min(1).max(10).optional(), + groupSize: z.number().min(1).optional(), }), async (req, res) => { const { scriptIds, projectId, groupSize = 5 } = req.body; diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 0a990cf..f39f1f2 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,21 +1,11 @@ -<<<<<<< HEAD -// @db-hash 2c7f828da2621d74d1b0d147d4ba4342 -======= -// @db-hash a27fff5b05e1c9ef490898f17703079a ->>>>>>> bf124d07e35f3d45958f5c2e64a650df7249f737 +// @db-hash f82eb99171699f051830710c1f816b59 //该文件由脚本自动生成,请勿手动修改 -export interface _o_vendorConfig_old_20260401 { - 'author'?: string | null; - 'code'?: string | null; - 'createTime'?: number | null; - 'description'?: string | null; - 'icon'?: string | null; - 'id'?: string; - 'inputs'?: string | null; - 'inputValues'?: string | null; - 'models'?: string | null; - 'name'?: string | null; +export interface _o_videoTrack_old_20260401 { + 'id'?: number; + 'projectId'?: number | null; + 'scriptId'?: number | null; + 'videoId'?: number | null; } export interface memories { 'content': string; @@ -213,7 +203,6 @@ export interface o_vendorConfig { 'code'?: string | null; 'createTime'?: number | null; 'description'?: string | null; - 'enableEnglish'?: number | null; 'icon'?: string | null; 'id'?: string; 'inputs'?: string | null; @@ -242,7 +231,7 @@ export interface o_videoTrack { } export interface DB { - "_o_vendorConfig_old_20260401": _o_vendorConfig_old_20260401; + "_o_videoTrack_old_20260401": _o_videoTrack_old_20260401; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData;