diff --git a/data/skills/production_agent_decision.md b/data/skills/production_agent_decision.md index 6c23eaa..7e6f519 100644 --- a/data/skills/production_agent_decision.md +++ b/data/skills/production_agent_decision.md @@ -1,223 +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完成且用户确认 | -| 审核 | 不需要 | - -**决策层行为:** 执行层返回确认后,告知用户图片生成已启动,流程结束。 - ---- - -## 调度与派发规范 - -### 派发指令要求 - -**派发给执行层和监督层的任务指令正文严格不超过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/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index 082c430..3f1bbae 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/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index b0b1c5a..f705382 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -176,7 +176,7 @@ function createSubAgent(parentCtx: AgentContext) { prompt, system: systemPrompt + - `\n你必须使用如下XML格式写入工作区:\nXML不得添加任何额外标签剧本内容剧本内容剧本内容`, + `\n你必须使用如下XML格式写入工作区:\nXML不得添加任何额外标签剧本内容剧本内容剧本内容`, name: "编剧", memoryKey: "assistant:execution:script", }); diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index 2a267e4..2950ed6 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -11,6 +11,7 @@ export const ScriptSchema = z.object({ export const planData = z.object({ storySkeleton: z.string().describe("故事骨架"), adaptationStrategy: z.string().describe("改编策略"), + script: z.string().describe("剧本内容"), }); export type planData = z.infer; @@ -60,12 +61,10 @@ export default (toolCpnfig: ToolConfig) => { console.log("[tools] get_planData", key); const thinking = msg.thinking(`正在获取${planDataKeyLabels[key]}工作区数据...`); const planData: planData = await new Promise((resolve) => socket.emit("getPlanData", { key }, (res: any) => resolve(res))); - const value = planData[key]; - const valueStr = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value ?? ""); - thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + valueStr); + thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + planData[key]); thinking.updateTitle(`获取${planDataKeyLabels[key]}完成`); thinking.complete(); - return valueStr || "无数据"; + return planData[key] ?? "无数据"; }, }), get_novel_text: tool({ diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index cf28c09..e1a344b 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -480,22 +480,13 @@ description: 专注于从剧本内容中提取所使用的资产(角色、场 builder: (table) => { table.integer("id").notNullable(); table.integer("scriptId"); - table.text("title"); table.text("prompt"); - table.text("videoPrompt"); - table.text("description"); table.text("filePath"); - table.text("model"); - table.text("mode"); table.text("duration"); - table.text("resolution"); - table.text("frameMode"); - table.text("camera"); - table.text("sound"); - table.text("lines"); table.text("state"); - table.text("group"); + table.integer("trackId"); table.text("reason"); + table.integer("projectId"); table.integer("index"); table.integer("createTime"); table.primary(["id"]); diff --git a/src/routes/production/editImage/saveImageFlow.ts b/src/routes/production/editImage/saveImageFlow.ts index 46fba9a..544afdd 100644 --- a/src/routes/production/editImage/saveImageFlow.ts +++ b/src/routes/production/editImage/saveImageFlow.ts @@ -14,9 +14,10 @@ export default router.post( id: z.number().nullable().optional(), type: z.enum(["role", "scene", "storyboard", "clip", "tool"]), episodesId: z.number(), + projectId: z.number(), }), async (req, res) => { - const { edges, nodes, imageUrl, id, type, episodesId } = req.body; + const { edges, nodes, imageUrl, id, type, episodesId, projectId } = req.body; let imagePath = ""; try { imagePath = new URL(imageUrl).pathname; @@ -59,6 +60,7 @@ export default router.post( filePath: imagePath, scriptId: episodesId, createTime: Date.now(), + projectId, }); insertFlowId = storyboardId; } diff --git a/src/routes/production/getFlowData.ts b/src/routes/production/getFlowData.ts index 46a859d..f42d068 100644 --- a/src/routes/production/getFlowData.ts +++ b/src/routes/production/getFlowData.ts @@ -73,24 +73,7 @@ export default router.post( storyboard: [], //todo:矫正workbench数据 workbench: { - videoList: [ - { - id: 1, - prompt: "动起来", - filePath: await u.oss.getFileUrl("/artStyle/5d96256a-1610-43a6-a469-c2385cc2287e.jpg"), - duration: 4, - scriptId: 1, - selectedVideoId: 1, - }, - { - id: 2, - prompt: "跳起来", - filePath: await u.oss.getFileUrl("/artStyle/5d96256a-1610-43a6-a469-c2385cc2287e.jpg"), - duration: 4, - scriptId: 1, - selectedVideoId: 1, - }, - ], + videoList: [], }, //todo:矫正封面数据 poster: { @@ -178,14 +161,8 @@ export default router.post( ...existing, id: i.id, index: i.index, - title: i.title, - description: i.description, - camera: i.camera, duration: i.duration ? +i.duration : 0, - frameMode: i.frameMode, prompt: i.prompt, - lines: i.lines, - sound: i.sound, associateAssetsIds: assets2StoryboardMap[i.id!] ?? [], src: i.filePath, state: i.state, diff --git a/src/routes/production/getStoryboardData.ts b/src/routes/production/getStoryboardData.ts index b307380..62466a6 100644 --- a/src/routes/production/getStoryboardData.ts +++ b/src/routes/production/getStoryboardData.ts @@ -17,7 +17,6 @@ export default router.post( storyboardData.map(async (i) => { return { ...i, - title: i.title, filePath: i.filePath ? await u.oss.getFileUrl(i.filePath!) : "", }; }), @@ -66,19 +65,11 @@ export default router.post( ); return { id: String(item.id), - camera: item.camera ? Number(item.camera) : undefined, createTime: item.createTime ?? undefined, - description: item.description ?? undefined, duration: item.duration ? Number(item.duration) : undefined, filePath: item.filePath || undefined, - frameMode: item.frameMode ? Number(item.frameMode) : undefined, - mode: item.mode ?? "", - model: item.model ?? "", prompt: item.prompt ?? undefined, - resolution: item.resolution ?? undefined, scriptId: item.scriptId ?? undefined, - sound: item.sound ? Number(item.sound) : undefined, - title: item.title ?? undefined, characters: charactersWithUrl, }; }), diff --git a/src/routes/production/storyboard/batchAddStoryboardInfo.ts b/src/routes/production/storyboard/batchAddStoryboardInfo.ts index 64fba53..f238cd2 100644 --- a/src/routes/production/storyboard/batchAddStoryboardInfo.ts +++ b/src/routes/production/storyboard/batchAddStoryboardInfo.ts @@ -1,10 +1,17 @@ import express from "express"; import u from "@/utils"; import { z } from "zod"; -import { success } from "@/lib/responseFormat"; +import { error, success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; const router = express.Router(); - +interface Storyboard { + id: number; + track: string; + src: string | null; + associateAssetsIds: number[]; + duration: number; + state: string; +} export default router.post( "/", validateFields({ @@ -12,24 +19,25 @@ export default router.post( z.object({ prompt: z.string(), duration: z.number(), - group: z.number(), + track: z.string(), state: z.string(), src: z.string().nullable(), associateAssetsIds: z.array(z.number()), }), ), scriptId: z.number(), + projectId: z.number(), }), async (req, res) => { - const { data, scriptId } = req.body; + const { data, scriptId, projectId } = req.body; if (!data.length) return res.status(400).send({ success: false, message: "数据不能为空" }); for (const item of data) { const [id] = await u.db("o_storyboard").insert({ prompt: item.prompt, duration: String(item.duration), - group: String(item.group), state: item.state, scriptId, + projectId, createTime: Date.now(), }); if (item.associateAssetsIds?.length) { @@ -40,8 +48,46 @@ export default router.post( })), ); } + item.id = id; } - const lastStoryboard = await u.db("o_storyboard").where("scriptId", scriptId); - return res.status(200).send(success(lastStoryboard)); + //根据track分组 + const storyboardGroupByTrack: Record = {}; + data.forEach((item: any) => { + if (!storyboardGroupByTrack[item.track]) { + storyboardGroupByTrack[item.track] = []; + } + storyboardGroupByTrack[item.track].push(item.id); + }); + + //循环 + for (const track in storyboardGroupByTrack) { + const [trackId] = await u.db("o_videoTrack").insert({ + scriptId, + projectId, + }); + const storyboardIds = storyboardGroupByTrack[track] ?? []; + await u.db("o_storyboard").whereIn("id", storyboardIds).update({ trackId }); + } + const lastStoryboard = await u + .db("o_storyboard") + .where("scriptId", scriptId) + .select("id", "trackId", "prompt", "duration", "state", "scriptId", "reason", "filePath"); + if (!lastStoryboard || !lastStoryboard.length) return res.status(400).send(error("为查到分镜数据")); + const storyboardData = await Promise.all( + lastStoryboard.map(async (i) => { + return { + associateAssetsIds: await u.db("o_assets2Storyboard").where("storyboardId", i.id).select("assetId").pluck("assetId"), + src: i.filePath ? await u.oss.getFileUrl(i.filePath) : "", + id: i.id, + trackId: i.trackId, + prompt: i.prompt, + duration: Number(i.duration), + state: i.state, + scriptId: i.scriptId, + reason: i.reason, + }; + }), + ); + return res.status(200).send(success(storyboardData)); }, ); diff --git a/src/routes/production/storyboard/getStoryboardData.ts b/src/routes/production/storyboard/getStoryboardData.ts index 6a90c68..4125257 100644 --- a/src/routes/production/storyboard/getStoryboardData.ts +++ b/src/routes/production/storyboard/getStoryboardData.ts @@ -31,12 +31,7 @@ export default router.post( storyboardData.map(async (i: any) => { return { id: i.id, - title: i.title, prompt: i.prompt, - description: i.description, - camera: i.camera, - lines: i.lines, - sound: i.sound, state: i.state, src: i.filePath ? await u.oss.getFileUrl(i.filePath!) : "", }; diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 6635502..6d8457b 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,6 +1,49 @@ -// @db-hash c2029b55b7dcdcf64788dafc34799fea +// @db-hash c145f43374602285beea82bbd51eaec8 //该文件由脚本自动生成,请勿手动修改 +export interface _o_storyboard_old_20260331 { + 'camera'?: string | null; + 'createTime'?: number | null; + 'description'?: string | null; + 'duration'?: string | null; + 'filePath'?: string | null; + 'frameMode'?: string | null; + 'id'?: number; + 'index'?: number | null; + 'lines'?: string | null; + 'mode'?: string | null; + 'model'?: string | null; + 'prompt'?: string | null; + 'reason'?: string | null; + 'resolution'?: string | null; + 'scriptId'?: number | null; + 'sound'?: string | null; + 'state'?: string | null; + 'title'?: string | null; + 'videoPrompt'?: string | null; +} +export interface _o_storyboard_old_20260331_1 { + 'camera'?: string | null; + 'createTime'?: number | null; + 'description'?: string | null; + 'duration'?: string | null; + 'filePath'?: string | null; + 'frameMode'?: string | null; + 'id'?: number; + 'index'?: number | null; + 'lines'?: string | null; + 'mode'?: string | null; + 'model'?: string | null; + 'prompt'?: string | null; + 'reason'?: string | null; + 'resolution'?: string | null; + 'scriptId'?: number | null; + 'sound'?: string | null; + 'state'?: string | null; + 'title'?: string | null; + 'track'?: string | null; + 'videoPrompt'?: string | null; +} export interface memories { 'content': string; 'createTime': number; @@ -114,7 +157,6 @@ export interface o_project { 'imageModel'?: string | null; 'imageQuality'?: string | null; 'intro'?: string | null; - 'mode'?: string | null; 'name'?: string | null; 'projectType'?: string | null; 'type'?: string | null; @@ -162,26 +204,17 @@ export interface o_skillList { 'updateTime': number; } export interface o_storyboard { - 'camera'?: string | null; 'createTime'?: number | null; - 'description'?: string | null; 'duration'?: string | null; 'filePath'?: string | null; - 'frameMode'?: string | null; - 'group'?: string | null; 'id'?: number; 'index'?: number | null; - 'lines'?: string | null; - 'mode'?: string | null; - 'model'?: string | null; + 'projectId'?: number | null; 'prompt'?: string | null; 'reason'?: string | null; - 'resolution'?: string | null; 'scriptId'?: number | null; - 'sound'?: string | null; 'state'?: string | null; - 'title'?: string | null; - 'videoPrompt'?: string | null; + 'trackId'?: number | null; } export interface o_tasks { 'describe'?: string | null; @@ -218,8 +251,22 @@ export interface o_video { 'projectId'?: number | null; 'scriptId'?: number | null; 'state'?: string | null; + 'storyboardId'?: number | null; 'time'?: number | null; - 'videoTrackId'?: number | null; +} +export interface o_videoConfig { + 'audio'?: number | null; + 'createTime'?: number | null; + 'data'?: string | null; + 'duration'?: number | null; + 'id'?: number; + 'mode'?: string | null; + 'model'?: string | null; + 'prompt'?: string | null; + 'resolution'?: string | null; + 'storyboardId'?: number | null; + 'updateTime'?: number | null; + 'videoId'?: number | null; } export interface o_videoTrack { 'id'?: number; @@ -229,6 +276,8 @@ export interface o_videoTrack { } export interface DB { + "_o_storyboard_old_20260331": _o_storyboard_old_20260331; + "_o_storyboard_old_20260331_1": _o_storyboard_old_20260331_1; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData; @@ -254,5 +303,6 @@ export interface DB { "o_user": o_user; "o_vendorConfig": o_vendorConfig; "o_video": o_video; + "o_videoConfig": o_videoConfig; "o_videoTrack": o_videoTrack; }