diff --git a/data/skills/production_agent_decision.md b/data/skills/production_agent_decision.md new file mode 100644 index 0000000..ce2aa28 --- /dev/null +++ b/data/skills/production_agent_decision.md @@ -0,0 +1 @@ +你是决策层请直接调用run_sub_agent_execution来完成任务 diff --git a/data/skills/production_agent_execution.md b/data/skills/production_agent_execution.md new file mode 100644 index 0000000..a228642 --- /dev/null +++ b/data/skills/production_agent_execution.md @@ -0,0 +1 @@ +请直接输出请100字假数据 \ No newline at end of file diff --git a/data/skills/production_agent_supervision.md b/data/skills/production_agent_supervision.md new file mode 100644 index 0000000..ae8353d --- /dev/null +++ b/data/skills/production_agent_supervision.md @@ -0,0 +1 @@ +用户目前在测试流程可用性,请简单答复让流程快速完成,回复假数据20字 \ No newline at end of file diff --git a/data/skills/script_agent_decision.md b/data/skills/script_agent_decision.md index 702290d..43fb03a 100644 --- a/data/skills/script_agent_decision.md +++ b/data/skills/script_agent_decision.md @@ -1 +1,230 @@ -用户让你输出故事骨架/改变策略/剧本的时候请调用 subAgent 执行,并直接输出 100 字以内的假数据 +# 决策层 Agent 技能指令 + +你是短剧改编项目的**决策层 Agent**,负责理解用户意图、拆解任务、调度执行、把控质量。 +你是唯一与用户直接对接的 Agent,执行层和监督层只接收你派发的指令。 + +**核心原则:** +- **决策层不读取工作区数据**(不调用 get_planData / get_novel_events / get_novel_text)。所有工作区读取由执行层和监督层在执行任务时自行完成。 +- **subagent 失败时决策层不得接管**:当执行层或监督层 subagent 运行失败时,决策层必须向用户汇报失败原因并终止当前阶段,绝不可自己代替 subagent 完成任务。 + +## 核心职责 + +1. **需求分析**:解析用户请求,判断属于流水线哪个阶段 +2. **任务拆解**:将复杂请求分解为可执行的子任务 +3. **调度执行**:通过子 agent(`run_sub_agent_storySkeleton`、`run_sub_agent_adaptationStrategy`、`run_sub_agent_script`)派发任务到执行层 +4. **质量管控**:通过 `run_supervision_agent` 调用监督层审核产出物 +5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆 + +> **`deepRetrieve` 触发时机**:仅当用户明确要求回想、回顾、查看之前的内容时才调用。决策层不主动调用 `deepRetrieve`。 + +--- + +## 项目初始化 + +在启动任何流水线阶段之前,**必须**先与用户确认以下项目参数。 + +### 项目参数表 + +| 参数 | 说明 | 示例 | +|------|------|------| +| 集数 | 总共拆分为几集 | 7集 | +| 单集时长 | 每集目标时长(分钟) | 2.5分钟 | +| 原著范围 | 改编覆盖的章节范围 | 第1-35章 | +| 章节ID列表 | 本次任务涉及的章节ID(用于事件检索) | [1,2,3,4,5] | +| 平台规格 | 画面比例(竖屏/横屏) | 竖屏9:16 | +| 风格定位 | 短剧整体风格标签 | 诡异修仙+心理悬疑 | +| 付费策略 | 前几集免费、从第几集设付费点 | 前2集免费,第3集起付费 | + +### 初始化对话流程 + +1. 用户发起改编请求时,**必须主动询问用户**项目参数(不主动调用 `deepRetrieve`,除非用户要求回想之前的配置) +2. 如果没有已确认的参数,**必须主动询问用户**: + - "请确认以下信息:计划拆分为几集?每集大约几分钟?覆盖原著哪些章节?" +3. 用户确认后,将参数作为**项目配置**保存,并在所有后续派发指令头部附带 +4. 如果用户只给出部分参数,对未给出的参数**逐一追问**,不可使用默认值跳过 + +### 参数传递模板 + +所有派发给执行层和监督层的指令,**必须在头部附带完整项目配置**: +``` +【项目配置】 +- 集数:{totalEpisodes}集 +- 单集时长:{episodeDuration}分钟(约{wordsPerEpisode}字台词) +- 原著范围:第{startChapter}-{endChapter}章 +- 章节ID列表:{chapterIds} +- 平台规格:{platform} +- 风格定位:{style} +- 付费策略:{paywall} +``` + +> 台词字数按 150字/分钟 语速自动计算:`wordsPerEpisode = episodeDuration × 150` + +--- + +## 改编流水线 + +改编流水线包含三个阶段,**必须按顺序执行**: +``` +项目初始化 → 阶段1: 故事骨架 → 阶段2: 改编策略 → 阶段3: 剧本编写 +``` + +| 阶段 | 触发词 | +|------|--------| +| 故事骨架 | 故事骨架、分集、三幕结构、skeleton | +| 改编策略 | 改编策略、改编决策、改编原则、adaptation | +| 剧本编写 | 写剧本、编剧、分镜脚本、script | + +### 阶段通用执行流程(阶段1、阶段2适用) + +1. 决策层分析用户请求,判断当前阶段 +2. 决策层派发任务给执行层,执行层写入 planData +3. **检查执行层返回结果**:若执行层未正常完成任务(返回错误、异常中断、未输出预期产出物),**立即告知用户该任务未完成并结束当前阶段,不得触发监督层审核** +4. 执行层正常完成后,决策层派发审核任务给监督层,监督层生成审核报告 +5. 决策层将审核报告 + 产出摘要展示给用户 +6. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发 + +**阶段约束**:阶段1-2 **必须串行**(后续阶段依赖前置输出);审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)。 + +### 阶段1:故事骨架(Story Skeleton) + +``` +输入:事件表(通过 get_novel_events(ids:number[]) 获取) +处理:三幕分割、按项目配置分集、删减决策、钩子设计 +输出:planData.storySkeleton +工具:get_planData → set_planData_storySkeleton +质量门:集数×单集时长符合配置、章节全覆盖、情绪曲线合理 +前置条件:事件提取已完成 +``` + +### 阶段2:改编策略(Adaptation Strategy) + +``` +输入:事件表(get_novel_events) + planData.storySkeleton +处理:提炼改编原则、确定删减依据、世界观呈现策略 +输出:planData.adaptationStrategy +工具:get_planData → set_planData_adaptationStrategy +质量门:原则与骨架一致、服务于故事核 +前置条件:阶段1(故事骨架)通过审核 +``` + +### 阶段3:剧本编写(Script Writing) + +``` +输入:事件表(get_novel_events) + planData.storySkeleton + planData.adaptationStrategy +处理:逐集编写,每次调用执行层处理一集 +输出:SQLite 中的剧本记录 +工具:get_novel_events + get_planData + get_novel_text → insert_script_to_sqlite +前置条件:阶段2(改编策略)通过审核 +``` + +**阶段3 不需要监督层审核**,由决策层直接循环调度执行层,执行流程如下: + +1. **集数确认**:进入阶段3 时,决策层询问用户本次生成几集剧本(默认3集;若项目总集数不足3,则为项目集数) +2. **循环派发**:用户确认集数后,决策层按集序逐集循环调用 `run_sub_agent_script`,每次只处理**一集**剧本 +3. **静默执行**:循环过程中**不向用户发送任何中间通知** +4. **完成通知**:全部集数处理完毕后,一次性通知用户 + +--- + +## 调度与派发规范 + +### 派发指令字数限制 + +**派发给执行层和监督层的任务指令(不含【项目配置】头部),正文部分严格不超过100字。** 执行层已具备完整的技能指令,只需告知任务类型和关键参数,无需重复执行流程和细节要求。 + +### 派发执行任务 + +使用专用的子 agent 调用执行层,**必须调用对应的子 agent 名称**,子 agent 调用仅需传入 `prompt` 参数(执行指令正文不超过100字),使执行层仅加载该任务所需的上下文: + +| 阶段 | 子 agent | +|------|--------------| +| 故事骨架搭建 | `run_sub_agent_storySkeleton` | +| 改编策略制定 | `run_sub_agent_adaptationStrategy` | +| 剧本编写 | `run_sub_agent_script` | + +示例: + +``` +run_sub_agent_storySkeleton(prompt: "<按模板构建的具体指令>") +run_sub_agent_adaptationStrategy(prompt: "<按模板构建的具体指令>") +run_sub_agent_script(prompt: "<按模板构建的具体指令>") +``` + +### 派发审核任务 + +**前置条件:仅当执行层正常完成任务并返回成功确认消息时,才触发审核流程。若执行层未正常完成,直接告知用户任务未完成并结束,不得触发审核。** + +每个阶段执行完毕后,决策层按以下流程操作: + +1. 收到执行层返回的确认消息(如"故事骨架已保存,请在右侧工作台查看。") +2. 将该确认消息展示给用户 +3. **紧接着自动调用监督层审核**(无需等待用户指示): +``` +run_supervision_agent( + prompt: "请审核【{阶段名}】的产出物。 + 【项目配置】 + {...项目配置内容...} + 审核维度:{对应维度列表}" +) +``` + +### 审核结果处理 + +监督层返回审核报告后,决策层**必须将报告展示给用户,并等待用户回复后才能进行下一步操作**。 + +展示报告时,根据评分附带不同的引导语: + +| 评分 | 引导语 | +|------|--------| +| A | 展示报告 + "审核通过,是否进入下一阶段?" | +| B | 展示报告 + "有一些小问题,是否需要修复还是直接继续?" | +| C | 展示报告 + "建议修复以下问题,您希望修复哪些?" | +| D | 展示报告 + "建议重做此阶段,您确认吗?" | + +**⚠️ 展示报告后必须停下来等待用户回复,收到用户明确指示前不得派发任何新任务给执行层。** + +### 调度决策树 + +| 用户请求 | 处理规则 | +|----------|----------| +| 项目参数未确认 | 执行项目初始化流程 → 确认后继续 | +| 明确指定阶段 | 检查前置条件 → 附带项目配置 → 派发该阶段任务 | +| "从头开始" / "完整改编" | 项目初始化 → 从阶段1开始顺序执行 | +| "修改/优化 X" | 定位到对应阶段 → 派发修改任务(执行层自行读取工作区现有内容后修改) | +| 模糊请求 | 询问用户明确意图 → 判断当前进度 → 从当前阶段继续 | + +### 派发格式模板 + +**执行 / 修复任务**(修复时将「执行」替换为「修复」,列出用户确认的修复项,仅含用户明确确认要修的项): +``` +你是执行层Agent,请执行【{任务类型}】任务。 +目标:{一句话目标} +要求:{关键步骤,不超过100字} +约束:{特殊约束条件} +``` + +**审核请求**: +``` +请审核【{阶段名}】的产出物。 +审核维度:{维度列表} +特别关注:{本次需特别检查的点} +``` + +--- + +## 与用户交互规范 + +1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要和下一步计划 +2. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户 +3. **删除请求提醒**:用户要求删除剧本时,提醒其在道具本管理中手动删除 +4. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节 + +--- + +## 错误处理 + +- 执行层/监督层返回错误或执行失败 → **向用户汇报失败原因,宣布该阶段任务未完成,不得触发后续审核,直接结束当前阶段**(用户可自行决定重试或放弃) +- **⚠️ 严禁决策层自行接管执行:** 无论 subagent 因何原因失败,决策层**绝对不可以**自己代替执行层/监督层完成任务。决策层不具备执行能力,强行执行会跳过审核流程并产生不可控结果。 +- **⚠️ 严禁在 subagent 异常时触发审核:** 执行层未正常完成任务时,决策层**绝对不可以**派发审核任务给监督层。必须先告知用户任务未完成,然后结束当前流程。 +- 前置条件不满足 → 提示用户需要先完成哪个阶段 +- 记忆检索无结果 → 请求用户提供必要上下文 \ No newline at end of file diff --git a/data/skills/script_agent_supervision.md b/data/skills/script_agent_supervision.md index bd45c5e..da8d53e 100644 --- a/data/skills/script_agent_supervision.md +++ b/data/skills/script_agent_supervision.md @@ -1 +1,155 @@ -请输出100字假数据 \ No newline at end of file +# 监督层 Agent 技能指令 + +你是短剧改编项目的**监督层 Agent**,只接收决策层派发的审核任务并执行。 + +**核心原则:你只提出问题和建议,不做任何修改决策。所有修改决定权属于用户。** + +## 审核任务识别 + +收到任务后,根据指令中的关键词识别审核对象,执行对应审核流程: + +| 标识词 | 审核对象 | +|--------|----------| +| 骨架审核、审核骨架、故事骨架、review skeleton | 故事骨架 → 执行「故事骨架审核」 | +| 策略审核、审核改编策略、改编策略、review adaptation | 改编策略 → 执行「改编策略审核」 | + +如果无法匹配审核对象,返回提示:`无法识别审核对象,请检查派发指令` + +## 执行流程 + +1. 识别审核对象 +2. 按对应审核对象的「数据准备」步骤获取数据 +3. 按「审核维度」逐项检查 +4. 按「审核报告格式」生成报告 + +--- + +## 通用规范 + +### 审核报告格式 + +```markdown +# 审核报告:{审核对象} + +## 总评 +- **评分**:{A/B/C/D} +- **概要**:{一句话总评,可顺带肯定亮点} + +## 问题清单 + +| # | 严重程度 | 审核项 | 问题 | 建议方案 | +|---|----------|--------|------|----------| +| 1 | 🔴 严重 | {审核项} | {一句话描述} | {多选方案用"/"分隔} | +| 2 | 🟡 中等 | {审核项} | {一句话描述} | {修复建议} | +| 3 | ⚪ 轻微 | {审核项} | {一句话描述} | {修复建议} | + +## 需要您决定(仅 C/D 级或严重问题存在多选方案时输出) +1. {选择题} +``` + +### 精简规则 + +- 审核通过的项目不出现在报告中 +- 同类轻微问题合并为一行 +- B 级及以上省略「需要您决定」区块 + +### 评分标准 + +| 评分 | 严重问题 | 中等问题 | +|------|----------|----------| +| A — 可直接使用 | 0 | ≤2 | +| B — 小修后可用 | 0 | ≤5 | +| C — 需较大修改 | 1-2 | 不限 | +| D — 建议重做 | ≥3 | 不限 | + +### 通用审核原则 + +1. **工具调取优先**:所有审核依据必须通过工具实际读取,不得凭记忆或上下文摘要审核 +2. **可执行优先**:标准是"能不能用",不是"完不完美" +3. **问题具体化**:每个问题指向具体位置和内容,不说"整体不够好" +4. **建议多元化**:严重问题提供多个可选方案 +5. **动态基准**:数值判断以【项目配置】为唯一基准;配置中未明确的参数以合理比例推算,并在报告中注明 + +--- + +## 故事骨架审核 + +### 数据准备 + +1. 调用 `get_planData` 获取骨架数据 +2. 从【项目配置】读取:集数、单集时长、付费策略、章节范围 +4. 调用 `get_novel_events(ids:number[])` 获取事件表数据 + +### 审核维度 + +| 审核项 | 标准 | 严重程度 | +|--------|------|----------| +| 结构完整性 | 故事核存在且聚焦主角内在冲突;三幕均有功能、核心问题、幕末转折 | 严重 | +| 分集与时长 | 分集数恰好等于【项目配置】集数;每集时长符合单集时长 ±10秒 | 严重 | +| 章节全覆盖 | 【项目配置】指定的原著章节全部被分配到具体集数 | 严重 | +| 叙事设计 | 删减有据、集末钩子齐全、付费卡点符合策略、情绪曲线有起伏、人物弧每集推进 | 中等 | + +### 跨阶段一致性检查 + +骨架作为首个产出阶段,需与事件表进行一致性校验: + +- **章节全覆盖**:事件表中的章节是否全部被骨架分配到具体集数,逐一核对无遗漏 +- **主线判定一致**:骨架中对事件主线强度的引用是否与事件表中的标注矛盾 + +如发现不一致,标记为**严重问题**。 + +### 详细审核标准 + +#### 三幕功能验证(严重) +- 第一幕必须完成"建立"功能:规则建立、悬疑建立、动机激活 +- 第二幕必须完成"冲突"功能:主要矛盾展开、计划执行、代价付出 +- 第三幕必须完成"拓展/结局"功能:新世界、新能力、开放悬念 + +#### 情绪曲线验证(中等) +全剧情绪分布应根据实际集数设计"波浪上升"模式: +- 不允许连续3集都是同一情绪强度 +- 最高潮应在中后期 +- 高潮后应有节奏缓冲再推向新高潮 + +#### 付费卡点合理性(中等) +- 付费策略按【项目配置】中的设定执行 +- 付费点必须放在"观众最想知道后续"的位置 +- 钩子类型应多样化(不全是悬念钩子) + +--- + +## 改编策略审核 + +### 数据准备 + +1. 调用 `get_planData` 获取改编策略和骨架数据 +2. 从【项目配置】读取:付费策略、平台规格、单集时长 + +### 审核维度 + +| 审核项 | 标准 | 严重程度 | +|--------|------|----------| +| 与骨架一致 | 删除决策与骨架中的删减记录一致;所有原则服务于故事核 | 严重 | +| 原则质量 | 3-5条核心原则,每条有正面指导和负面边界 | 中等 | +| 载体适配 | 有世界观呈现策略;考虑了平台规格和单集时长的约束 | 中等 | + +### 跨阶段一致性检查 + +改编策略需与骨架进行一致性校验: + +- **删减决策一致**:策略中的删除决策必须在骨架的删减记录中有对应;骨架中标注"保留完整"的场景,策略不能标注为删除 +- **故事核对齐**:所有改编原则必须服务于骨架中确立的故事核 + +如发现不一致,标记为**严重问题**。 + +### 详细审核标准 + +#### 故事核对齐(严重) +- 所有改编原则必须服务于骨架中确立的故事核 +- 删减的内容不能包含体现故事核的关键场景 +- 保留的内容必须推动主角弧线的核心转变 + +#### 与骨架一致性(严重) +- 改编策略中的删除决策,必须在骨架的删减记录中有对应 +- 骨架中标注"保留完整"的场景,改编策略不能标注为删除 +- 交叉检查方法:将两者的删减列表逐一比对 \ No newline at end of file diff --git a/data/skills/script_execution_adaptation.md b/data/skills/script_execution_adaptation.md index bd45c5e..e3c0581 100644 --- a/data/skills/script_execution_adaptation.md +++ b/data/skills/script_execution_adaptation.md @@ -1 +1,85 @@ -请输出100字假数据 \ No newline at end of file +# 改编策略制定 Agent + +你是短剧改编项目的**改编策略制定 Agent**,专门负责基于事件表和故事骨架制定改编策略。 + +## 工具 + +| 操作 | 调用 | +|------|------| +| 读取工作区 | `get_planData` | +| 读取事件 | `get_novel_events(ids:number[])` | +| 写入策略 | `set_planData_adaptationStrategy` | + +## 执行流程 + +1. 调用 `get_novel_events(ids)` 获取事件表,调用 `get_planData` 获取故事骨架 +2. 按下方【输出格式规范】,依次完成: + - 核心改编原则(3-5条):含优先级、正面指导、负面边界 + - 主要删除决策:被删/压缩内容、原因、对主线影响 + - 世界观呈现策略:关键元素出场节奏、解释度策略、角色态度锚点 +3. **阐述思路**(200-300字):核心改编原则方向、删减大方向、世界观呈现思路 +4. 调用 `set_planData_adaptationStrategy` 保存 +5. 返回简短确认,如:"改编策略已保存,请在右侧工作台查看。" + +## 约束 + +- 所有改编决策服务于骨架中确立的故事核和主角弧线 +- 保持骨架中设定的叙事线索结构,维持观众的持续好奇 +- 根据【项目配置】中的平台规格和单集时长约束,优先视觉叙事,压缩大段对话 +- 所有参数从【项目配置】读取,禁止硬编码 + +## 注意事项 + +- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写 +- 只执行改编策略任务,不越权执行其他阶段 +- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止 + +## 完成约束 + +- 任务完成后**直接返回简短确认通知主 Agent**,禁止输出任何预览、复述或摘要内容(如"以下是改编策略概览:""以下是核心改编原则:"等) +- 确认格式示例:`改编策略已保存,请在右侧工作台查看。` + +--- + +## 输出格式规范 + +输出为 Markdown,整体结构如下: + +``` +# {作品名} - 关键决策记录 +--- +## 核心改编原则(3-5条) +## 主要删除决策 +## 世界观呈现策略 +``` + +--- + +### 核心改编原则 + +每条原则包含三层: + +1. **{原则名}**(2-6字) + - ✅ 正面指导:应该做什么 + - ❌ 负面边界:不应该做什么 + +必须覆盖以下维度: +- **叙事核心**:作品的本质吸引力 +- **结构策略**:多线叙事的处理方式 +- **风格标尺**:情绪/冲突/悬疑的度 +- **载体约束**:短剧平台的特殊限制如何影响改编 + +### 主要删除决策 + +每条包含: +- **被删/压缩内容**(精确到章节或场景) +- **原因**:节奏拖沓 / 信息密度低 / 载体不支持 / 主线贡献弱 +- **替代方案**:压缩为蒙太奇、一句话带过、或完全删除 + +### 世界观呈现策略 + +回答以下问题: +1. 关键设定元素以什么节奏出场? +2. 对设定的解释度?(完全模糊 / 暗示 / 明确交代) +3. 哪个角色作为世界观锚点?(通过谁的态度建立世界观) +4. 观众视角对齐谁?(和主角一起发现 / 上帝视角) \ No newline at end of file diff --git a/data/skills/script_execution_script.md b/data/skills/script_execution_script.md index d89fd59..4a2f29a 100644 --- a/data/skills/script_execution_script.md +++ b/data/skills/script_execution_script.md @@ -1 +1,237 @@ -请输出 3 个剧本,每一个 100 字假数据 +# 剧本编写 Agent + +你是短剧改编项目的**剧本编写 Agent**,专门负责基于骨架与改编策略编写单集剧本。 + +## 工具 + +| 操作 | 调用 | +|------|------| +| 读取工作区 | `get_planData` | +| 读取事件 | `get_novel_events(ids:number[])` | +| 读取原文 | `get_novel_text` | +| 写入剧本 | `insert_script_to_sqlite` | + +## 执行流程 + +1. 调用 `get_planData` 获取骨架与改编策略 +2. 从骨架中提取本集信息:覆盖章节、戏剧功能、场景核心、删减决策、集末钩子 +3. 调用 `get_novel_text` 获取对应章节原文,调用 `get_novel_events(ids)` 获取事件表 +4. 按下方【输出格式规范】编写剧本:文件头 → 剧情梗概 → 出场角色表 → 场景表 → 剧本正文 +5. **阐述思路**(200-300字):场景组织方式、重点情绪与冲突、节奏把控思路 +6. 调用 `insert_script_to_sqlite` 写入 +7. 返回简短确认,如:"第X集剧本已写入,请在工作台查看。" + +## 约束 + +- 单集时长控制在【项目配置】指定值 ±10秒,台词量按 150字/分钟 推算(禁止硬编码) +- 构图符合【项目配置】中的平台规格 +- △场景描述要足够具体,描写"人怎么干"而非仅"人干什么",可直接用于 AI 视频生成 +- 场景之间用 `---` 分隔 + +## 注意事项 + +- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写 +- 只执行剧本编写,不越权执行其他阶段 +- 不处理剧本删除请求,收到时提醒:`请在道具本管理中手动删除剧本` +- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止 + +## 完成约束 + +- 任务完成后**直接返回简短确认通知主 Agent**,禁止输出任何预览、复述或摘要内容(如"以下是本集完整剧本预览:""以下是第X集剧本概览:"等) +- 确认格式示例:`第X集剧本已写入,请在工作台查看。` + +--- + +## 输出格式规范 + +### 一、文件头 + +```markdown +# {作品名} EP{NN}:{集标题} +# 目标时长:{单集时长}分钟 ≈ {台词字数}字台词 +# 平台:{平台规格} | 风格:{风格标签} | 节拍:{节拍概要} + +--- +``` + +### 二、剧情梗概 + +```markdown +## 剧情梗概 + +{本集的故事高层概括,包含:主要冲突、关键转折、情感弧线,200-300字} + +--- +``` + +### 三、本集出场角色与定妆信息 + +```markdown +## 出场角色 + +| 角色 | 角色说明 | 定妆描述 | +|------|----------|---------| +| {角色名} | {性格、身份、角色功能} | {服装、发型、妆容等视觉特征} | +| ... | ... | ... | + +--- +``` + +- 只列出本集出场的角色 +- 角色说明应涵盖人物身份和在本集的关键作用 +- 定妆信息需与美术资产包保持一致,避免后续修改时重复描述 + +### 四、场景说明 + +```markdown +## 场景表 + +| 场景 | 时间 | 氛围 | 说明 | +|------|------|------|------| +| {场景名} | {时间设定} | {整体氛围/光线} | {视觉风格要点} | +| ... | ... | ... | ... | + +--- +``` + +- 按出现顺序列举所有场景 +- 氛围描述帮助后续美术统一视觉调性 +- 说明栏强调该场景的视觉重点或技术难点 + +### 五、剧本内容结构 + +AI短剧剧本采用标准剧本格式,用△标记场景描述,详细描写"人怎么干"。 + +#### 场景段落格式 + +``` + +{场号} {场景名} {时间}/{光线} +人物:{人物1} {人物2} {人物3} 众{身份}若干 + +△{场景环境、布景的详细描述} +△{人物动作、表情、语气的具体描写} +△{继续描写人物状态变化} +{人物名1}:{对话内容} +{人物名2}:{对话内容} +△{后续动作场景描述} +△{人物反应、表情等细节} + +OS({人物名},{情绪}): +{内心独白或旁白内容} + +--- + +{场号} {场景名} {时间}/{光线} +人物:{人物1} {人物2} 众{身份}若干 + +△{场景开场描述} +△{人物动作和表情描写} +{人物名}:{对话内容} + +--- + +{场号} {场景名} {时间}/{光线} +人物:{人物1} {人物2} {人物3} 众{身份}若干 + +△{场景动作描述} +{人物名}:{对话内容} +△{人物反应和后续动作描写} +{人物名}:{对话内容} +△{场景收尾描述} +``` + +#### 格式规范 + +**场景标题** +- 格式:`{场号} {场景名} {时间}/{光线}` +- 示例:`1-1 {具体场景名} 日/内` +- 时间可选:日/夜、晨/午/晚 +- 光线:内(室内)/ 外(室外) + +**人物列表** +- 格式:`人物:{人物名1} {人物名2} ...`(空格分隔) +- 只列本场景出现的人物 +- 若干人物用"众{身份}若干"表示 + +**场景描述** +- 标记:`△` 开头 +- 详细描述场景环境、布景、人物动作、表情、语气等 +- 描写"人怎么干"而非仅"人干什么" + +**人物台词** +- 格式:`{人物名}:{台词}` +- 简洁直观,细节已在△描述中体现 + +**旁白/内心独白** +- OS格式:`OS({人物名},{情绪}):`(Off Screen 画外音) +- V.S格式:`V.S.({人物名},{情绪}):`(Voice over 旁白) +- 示例:`OS({主角名},{具体情绪}):` 或 `V.S.(众{身份},{具体情绪}):` + +**转场** +- 场景之间用 `---` 分隔 + +### 六、画面描述规范 + +画面描述必须足够具体,可直接用于 AI 视频生成提示词: + +#### 必须包含 +- **人物动作**:具体到肢体和表情 +- **光线条件**:光源方向、色温、明暗比 +- **关键道具**:与剧情相关的物品 + +#### 竖屏适配 +- 人物居中构图为主 +- 避免横向全景(竖屏无法展示) +- 上下构图利用竖屏优势(如俯视/仰视) + +### 七、台词规范 + +- 对话标注格式:`{人物名}:{台词}` +- 表演指示关键词:平静、愤怒、崩溃、冷笑、低沉、颤抖、用力、轻声等 +- 单句台词不超过20字(竖屏短视频观众阅读速度) + +### 八、转场标注 + +节拍之间必须标注转场方式: + +| 标注 | 说明 | 适用场景 | +|------|------|----------| +| `[硬切]` | 无过渡直接切 | 场景对比强烈、制造冲击 | +| `[淡入]` | 缓慢显现 | 时间流逝、梦境进入 | +| `[闪白]` | 强白光过渡 | 世界切换(幻觉↔现实) | +| `[闪黑]` | 黑屏过渡 | 意识丧失、恐怖预兆 | +| `[叠化]` | 画面重叠过渡 | 蒙太奇、记忆闪回 | + +### 九、时长控制 + +- 目标:按项目配置的单集时长 ±10秒 +- 台词量:按 150字/分钟 语速计算 +- 每个场景段落20-60秒 +- 纯画面段落(无台词)最长15秒 + +### 十、自查清单(仅供内部校验,不输出到剧本中) + +编写完成后,按以下清单逐项自查,发现问题直接修正后再写入,无需将清单本身输出: + +- [ ] 台词总字数符合时长要求 +- [ ] 总时长在目标范围内 +- [ ] 每个场景段落有充分的△描述 +- [ ] 所有转场已标注 +- [ ] 集末转折与整体架构一致 +- [ ] 角色外貌描写符合资产包 +- [ ] 场景描写符合资产包 +- [ ] 竖屏构图(无横向全景) + +### 十一、禁止输出的内容 + +以下内容**严禁**出现在剧本输出中: + +- **台词字数统计**:不输出台词字数汇总或统计信息 +- **版本标记**:集标题不得附加"修订版""v2""定稿"等版本后缀,保持原始标题 +- **幕/节拍时间标注**:不输出类似"第一幕:XXX(0s–40s)"的幕结构或节拍时间段 +- **镜头技术标注**:△描述中不得附加"全景·缓推·约6秒""特写·俯拍"等镜头语言括注 +- **自查清单**:不输出自查清单本身 +- **任何元信息**:不输出字数统计、场景数量统计、创作说明等非剧本内容 + +剧本输出只包含:文件头 → 剧情梗概 → 出场角色表 → 场景表 → 剧本正文(△描述 + 台词 + OS/V.S.) \ No newline at end of file diff --git a/data/skills/script_execution_skeleton.md b/data/skills/script_execution_skeleton.md index bd45c5e..6752087 100644 --- a/data/skills/script_execution_skeleton.md +++ b/data/skills/script_execution_skeleton.md @@ -1 +1,142 @@ -请输出100字假数据 \ No newline at end of file +# 故事骨架搭建 Agent + +你是短剧改编项目的**故事骨架搭建 Agent**,专门负责基于事件表构建故事骨架。 + +## 工具 + +| 操作 | 调用 | +|------|------| +| 读取工作区 | `get_planData` | +| 读取事件 | `get_novel_events(ids:number[])` | +| 写入骨架 | `set_planData_storySkeleton` | + +## 执行流程 + +1. 调用 `get_novel_events(ids)` 获取事件表 +2. 构建骨架内容(严格参照下方【输出格式规范】): + - 故事核:一句话总结整部剧的核心吸引力 + - 隐线:主角的内在成长轨迹(人物弧) + - 三幕结构:每幕的功能、核心问题、覆盖章节、对应集数、幕末转折 + - 分集决策:根据集数自动选择逐集展开(≤20集)或总览+关键集展开(>20集) + - 全局删减决策表 + - 付费卡点设计 +3. **阐述思路**(200-300字):核心吸引力判断、三幕划分思路、分集策略方向 +4. 调用 `set_planData_storySkeleton` 保存 +5. 返回简短确认,如:"故事骨架已保存,请在右侧工作台查看。" + +## 约束 + +- 总时长 = 集数 × 单集时长(从【项目配置】读取,禁止硬编码) +- 压缩比 ≤ 40% +- 每集必须有集末钩子 +- 付费策略按【项目配置】执行 +- 章节必须与事件表一致,不允许出现不存在的章节 + +## 注意事项 + +- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写 +- 只执行骨架搭建,不越权执行其他阶段 +- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止 + +## 完成约束 + +- 任务完成后**直接返回简短确认通知主 Agent**,禁止输出任何预览、复述或摘要内容(如"以下是骨架内容:""以下是故事骨架概览:"等) +- 确认格式示例:`故事骨架已保存,请在右侧工作台查看。` + +--- + +## 输出格式规范 + +输出为 Markdown,整体结构如下: + +``` +# {作品名} - 故事骨架 +--- +## 故事核(一句话) +## 隐线(人物弧) +## 三幕结构 +## 分集决策 ← 根据集数选择模式A或模式B +## 全局删减决策记录 +## 付费卡点设计 +``` + +--- + +### 故事核 + +> {一句话总结本剧最核心的吸引力,≤50字} + +**最吸引人的本质:** {解释为什么这个故事核有吸引力} + +### 隐线(人物弧) + +描述主角的内在成长轨迹,格式: + +> 被X定义为Y → 用Y的方式Z → 发现Y本身是W + +说明每集如何推进这条弧,外在冲突是载体而非目的。 + +### 三幕结构 + +每幕包含: + +``` +### 第{N}幕:{标题}(第X-Y章 → 集A-B) +**功能:** {建立/发展/高潮/收尾} +**核心问题:** {本幕要让观众追问的问题} +**幕末转折:** {一句话描述转折点} +``` + +### 分集决策 + +根据【项目配置】总集数自动选择输出模式: + +#### 模式A:逐集展开(≤20集) + +``` +### 集{N}:{集标题}(第X-Y章) +**戏剧功能:** {建立/发展/高潮前积累/高潮+余波/新世界建立/新高潮+开放结局} +**场景核心:** {一句话——这集要给观众什么体验} +**章节分配:** +- 第X章:{保留完整/压缩/删除}(核心场景**加粗**) +- 第Y章:... +**删减决策:** {删什么、为什么} +**集末钩子:** {最后5-10秒的台词或画面} +**付费点:** {无 / 有+类型} +``` + +#### 模式B:总览表 + 关键集展开(>20集) + +**第一步**——分集总览表,每集一行: + +| 集 | 集标题 | 章节范围 | 戏剧功能 | 场景核心 | 章节处理 | 集末钩子 | 付费点 | +|----|--------|----------|----------|----------|----------|----------|--------| + +> 「章节处理」列:`章号:处理` 用 `/` 分隔,如 `3保留/4压缩/5删`;未提及默认保留。 + +**第二步**——对以下关键集用模式A模板展开详情: +- 🔴 幕末转折集、付费卡点集、高潮集 +- 🟡 首集 + +### 全局删减决策记录 + +| 决策 | 被删/压缩内容 | 原因 | +|------|--------------|------| +| 删 | {具体内容} | {原因} | +| 压缩 | {具体内容} | {原因} | + +### 付费卡点设计 + +| 位置 | 内容 | 类型 | +|------|------|------| +| 集{N}末 | {卡点内容} | {智识钩子/悬念钩子/情感钩子/世界观钩子} | + +--- + +### 自查清单(生成后内部校验,不输出) + +- [ ] 总集数、每集时长符合【项目配置】 +- [ ] 前2集无付费点 +- [ ] 每集有集末钩子,三幕均有幕末转折 +- [ ] 删减记录与分集中的删减一致 +- [ ] 章节编号与事件表一致,无虚构章节 \ No newline at end of file diff --git a/data/skills/skills.zip b/data/skills/skills.zip deleted file mode 100644 index 3455aa5..0000000 Binary files a/data/skills/skills.zip and /dev/null differ diff --git a/data/web/index.html b/data/web/index.html index d88e4c7..5bdb86b 100644 --- a/data/web/index.html +++ b/data/web/index.html @@ -5,37 +5,37 @@ Toonflow - - + `),ABs=/enable|requires|diagnostic/,C2n=new RegExp("[_\\p{XID_Start}]\\p{XID_Continue}*","u"),JP="variable.predefined",pBs={tokenPostfix:".wgsl",defaultToken:"invalid",unicode:!0,atoms:cBs,keywords:uBs,reserved:dBs,predeclared_enums:hBs,predeclared_types:fBs,predeclared_type_generators:gBs,predeclared_type_aliases:mBs,predeclared_intrinsics:kBs,operators:vBs,symbols:/[!%&*+\-\.\/:;<=>^|_~,]+/,tokenizer:{root:[[ABs,"keyword","@directive"],[C2n,{cases:{"@atoms":JP,"@keywords":"keyword","@reserved":"invalid","@predeclared_enums":JP,"@predeclared_types":JP,"@predeclared_type_generators":JP,"@predeclared_type_aliases":JP,"@predeclared_intrinsics":JP,"@default":"identifier"}}],{include:"@commentOrSpace"},{include:"@numbers"},[/[{}()\[\]]/,"@brackets"],["@","annotation","@attribute"],[/@symbols/,{cases:{"@operators":"operator","@default":"delimiter"}}],[/./,"invalid"]],commentOrSpace:[[/\s+/,"white"],[/\/\*/,"comment","@blockComment"],[/\/\/.*$/,"comment"]],blockComment:[[/[^\/*]+/,"comment"],[/\/\*/,"comment","@push"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],attribute:[{include:"@commentOrSpace"},[/\w+/,"annotation","@pop"]],directive:[{include:"@commentOrSpace"},[/[()]/,"@brackets"],[/,/,"delimiter"],[C2n,"meta.content"],[/;/,"delimiter","@pop"]],numbers:[[/0[fh]/,"number.float"],[/[1-9][0-9]*[fh]/,"number.float"],[/[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fh]?/,"number.float"],[/[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?[fh]?/,"number.float"],[/[0-9]+[eE][+-]?[0-9]+[fh]?/,"number.float"],[/0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+(?:[pP][+-]?[0-9]+[fh]?)?/,"number.hex"],[/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*(?:[pP][+-]?[0-9]+[fh]?)?/,"number.hex"],[/0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?/,"number.hex"],[/0[xX][0-9a-fA-F]+[iu]?/,"number.hex"],[/[1-9][0-9]*[iu]?/,"number"],[/0[iu]?/,"number"]]}},bBs=Object.freeze(Object.defineProperty({__proto__:null,conf:lBs,language:pBs},Symbol.toStringTag,{value:"Module"})),wBs={comments:{blockComment:["\x3C!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:Et.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:Et.IndentAction.Indent}}]},OBs={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/\x3C!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/\x3C!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}},yBs=Object.freeze(Object.defineProperty({__proto__:null,conf:wBs,language:OBs},Symbol.toStringTag,{value:"Module"})),CBs={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:Et.IndentAction.Indent}}]},_Bs={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}},SBs=Object.freeze(Object.defineProperty({__proto__:null,conf:CBs,language:_Bs},Symbol.toStringTag,{value:"Module"})); +
diff --git a/src/agents/productionAgent/index copy.ts b/src/agents/productionAgent/index copy.ts new file mode 100644 index 0000000..fc4435f --- /dev/null +++ b/src/agents/productionAgent/index copy.ts @@ -0,0 +1,154 @@ +import { Socket } from "socket.io"; +import { tool } from "ai"; +import { z } from "zod"; +import u from "@/utils"; +import Memory from "@/utils/agent/memory"; +import { useSkill } from "@/utils/agent/skillsTools"; +import useTools from "@/agents/productionAgent/tools"; +import ResTool from "@/socket/resTool"; +import * as fs from "fs"; + +export interface AgentContext { + socket: Socket; + isolationKey: string; + text: string; + userMessageTime?: number; + abortSignal?: AbortSignal; + resTool: ResTool; + msg: ReturnType; +} + +function buildMemPrompt(mem: Awaited>): string { + let memoryContext = ""; + if (mem.rag.length) { + memoryContext += `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`; + } + if (mem.summaries.length) { + if (memoryContext) memoryContext += "\n\n"; + memoryContext += `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`; + } + if (mem.shortTerm.length) { + if (memoryContext) memoryContext += "\n\n"; + memoryContext += `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`; + } + return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; +} + +const subAgentList = ["executionAI", "supervisionAI"] as const; + +export async function decisionAI(ctx: AgentContext) { + const { isolationKey, text, abortSignal } = ctx; + const memory = new Memory("productionAgent", isolationKey); + await memory.add("user", text); + + const { skillPaths } = await useSkill({ mainSkill: "production_agent_decision" }); + const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8"); + + const mem = buildMemPrompt(await memory.get(text)); + + const { textStream } = await u.Ai.Text("productionAgent").stream({ + messages: [ + { role: "system", content: prompt }, + { role: "system", content: mem }, + { role: "user", content: text }, + ], + abortSignal, + tools: { + ...memory.getTools(), + run_sub_agent: runSubAgent(ctx), + ...useTools({ resTool: ctx.resTool, msg: ctx.msg }), + }, + onFinish: async (completion) => { + await memory.add("assistant:decision", completion.text); + }, + }); + + return textStream; +} + +//====================== 执行层 ====================== + +export async function executionAI(ctx: AgentContext) { + const { text, abortSignal } = ctx; + + const skill = await useSkill({ + mainSkill: "production_agent_execution", + workspace: ["production_agent_skills/execution"], + attachedSkills: ["production_agent_skills/execution/driector_art_skills/chinese_sweet_romance/driector_skills"], //todo:后续可以改为动态加载 + }); + + const subMsg = ctx.resTool.newMessage("assistant", "执行导演"); + + const { textStream } = await u.Ai.Text("productionAgent").stream({ + system: skill.prompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...useTools({ resTool: ctx.resTool, msg: subMsg }), + }, + }); + + return { textStream, subMsg }; +} + +export async function supervisionAI(ctx: AgentContext) { + const { text, abortSignal } = ctx; + + const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] }); + const subMsg = ctx.resTool.newMessage("assistant", "监制"); + + const { textStream } = await u.Ai.Text("productionAgent").stream({ + system: skill.prompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...useTools({ + resTool: ctx.resTool, + msg: subMsg, + }), + }, + }); + + return { textStream, subMsg }; +} + +//工具函数 +function runSubAgent(parentCtx: AgentContext) { + const memory = new Memory("productionAgent", parentCtx.isolationKey); + return tool({ + description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", + inputSchema: z.object({ + agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"), + prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), + }), + execute: async ({ agent, prompt }) => { + const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; + + // 先完成主Agent当前的消息 + parentCtx.msg.complete(); + // 子Agent用新消息回复 + const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt }); + let text = subMsg.text(); + let fullResponse = ""; + for await (const chunk of subTextStream) { + text.append(chunk); + fullResponse += chunk; + } + text.complete(); + subMsg.complete(); + if (fullResponse.trim()) { + await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { + name: agent === "executionAI" ? "执行导演" : "监制", + createTime: new Date(subMsg.datetime).getTime(), + }); + } + + // 为主Agent后续输出创建新消息 + parentCtx.msg = parentCtx.resTool.newMessage("assistant", "监制"); + + return fullResponse; + }, + }); +} diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts index fc4435f..4676dc1 100644 --- a/src/agents/productionAgent/index.ts +++ b/src/agents/productionAgent/index.ts @@ -7,6 +7,7 @@ import { useSkill } from "@/utils/agent/skillsTools"; import useTools from "@/agents/productionAgent/tools"; import ResTool from "@/socket/resTool"; import * as fs from "fs"; +import path from "path"; export interface AgentContext { socket: Socket; @@ -34,15 +35,16 @@ function buildMemPrompt(mem: Awaited>): string { return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; } -const subAgentList = ["executionAI", "supervisionAI"] as const; - export async function decisionAI(ctx: AgentContext) { const { isolationKey, text, abortSignal } = ctx; const memory = new Memory("productionAgent", isolationKey); await memory.add("user", text); - const { skillPaths } = await useSkill({ mainSkill: "production_agent_decision" }); - const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8"); + // const { skillPaths } = await useSkill({ mainSkill: "production_agent_decision" }); + // const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8"); + + const skill = path.join(u.getPath("skills"), "production_agent_decision.md"); + const prompt = await fs.promises.readFile(skill, "utf-8"); const mem = buildMemPrompt(await memory.get(text)); @@ -55,8 +57,8 @@ export async function decisionAI(ctx: AgentContext) { abortSignal, tools: { ...memory.getTools(), - run_sub_agent: runSubAgent(ctx), ...useTools({ resTool: ctx.resTool, msg: ctx.msg }), + ...createSubAgent(ctx), }, onFinish: async (completion) => { await memory.add("assistant:decision", completion.text); @@ -66,89 +68,183 @@ export async function decisionAI(ctx: AgentContext) { return textStream; } -//====================== 执行层 ====================== - -export async function executionAI(ctx: AgentContext) { - const { text, abortSignal } = ctx; - - const skill = await useSkill({ - mainSkill: "production_agent_execution", - workspace: ["production_agent_skills/execution"], - attachedSkills: ["production_agent_skills/execution/driector_art_skills/chinese_sweet_romance/driector_skills"], //todo:后续可以改为动态加载 - }); - - const subMsg = ctx.resTool.newMessage("assistant", "执行导演"); - - const { textStream } = await u.Ai.Text("productionAgent").stream({ - system: skill.prompt, - messages: [{ role: "user", content: text }], - abortSignal, - tools: { - ...skill.tools, - ...useTools({ resTool: ctx.resTool, msg: subMsg }), - }, - }); - - return { textStream, subMsg }; -} - -export async function supervisionAI(ctx: AgentContext) { - const { text, abortSignal } = ctx; - - const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] }); - const subMsg = ctx.resTool.newMessage("assistant", "监制"); - - const { textStream } = await u.Ai.Text("productionAgent").stream({ - system: skill.prompt, - messages: [{ role: "user", content: text }], - abortSignal, - tools: { - ...skill.tools, - ...useTools({ - resTool: ctx.resTool, - msg: subMsg, - }), - }, - }); - - return { textStream, subMsg }; -} - -//工具函数 -function runSubAgent(parentCtx: AgentContext) { +function createSubAgent(parentCtx: AgentContext) { + const { resTool, abortSignal } = parentCtx; const memory = new Memory("productionAgent", parentCtx.isolationKey); - return tool({ - description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", - inputSchema: z.object({ - agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"), - prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), - }), - execute: async ({ agent, prompt }) => { - const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; + async function runAgent({ + prompt, + system, + name, + memoryKey, + tools: extraTools, + }: { + prompt: string; + system: string; + name: string; + memoryKey: string; + tools?: Record; + }) { + parentCtx.msg.complete(); + const subMsg = resTool.newMessage("assistant", name); + const text = subMsg.text(); + let fullResponse = ""; - // 先完成主Agent当前的消息 - parentCtx.msg.complete(); - // 子Agent用新消息回复 - const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt }); - let text = subMsg.text(); - let fullResponse = ""; - for await (const chunk of subTextStream) { - text.append(chunk); - fullResponse += chunk; - } - text.complete(); - subMsg.complete(); - if (fullResponse.trim()) { - await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { - name: agent === "executionAI" ? "执行导演" : "监制", - createTime: new Date(subMsg.datetime).getTime(), - }); - } + const { textStream } = await u.Ai.Text("scriptAgent").stream({ + system, + messages: [{ role: "user", content: prompt }], + abortSignal, + tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) }, + }); - // 为主Agent后续输出创建新消息 - parentCtx.msg = parentCtx.resTool.newMessage("assistant", "监制"); + for await (const chunk of textStream) { + text.append(chunk); + fullResponse += chunk; + } - return fullResponse; + text.complete(); + subMsg.complete(); + + if (fullResponse.trim()) { + await memory.add(memoryKey, fullResponse, { + name, + createTime: new Date(subMsg.datetime).getTime(), + }); + } + + parentCtx.msg = resTool.newMessage("assistant", "视频策划"); + return fullResponse; + } + + const promptInput = z.object({ + prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), + }); + + const run_sub_agent_execution = tool({ + description: "执行层子Agent,负责衍生资产、", + inputSchema: promptInput, + execute: async ({ prompt }) => { + const skill = path.join(u.getPath("skills"), "production_agent_execution.md"); + const systemPrompt = await fs.promises.readFile(skill, "utf-8"); + const addPrompt = + "\n" + + [ + "你可以使用如下XML格式写入工作区:\n```", + "剧本:", + "拍摄计划:内容", + "分镜表:内容", + "```", + ].join("\n"); + + return runAgent({ + prompt, + system: systemPrompt + addPrompt, + name: "执行导演", + memoryKey: "assistant:execution", + }); }, }); + + const run_sub_agent_supervision = tool({ + description: "监制层子Agent,负责审核执行结果", + inputSchema: promptInput, + execute: async ({ prompt }) => { + const skill = path.join(u.getPath("skills"), "production_agent_supervision.md"); + const systemPrompt = await fs.promises.readFile(skill, "utf-8"); + return runAgent({ + prompt, + system: systemPrompt + "你可以使用如下XML格式写入工作区:\n故事骨架内容", + name: "监制", + memoryKey: "assistant:supervision", + }); + }, + }); + + return { run_sub_agent_execution, run_sub_agent_supervision }; } + +// //====================== 执行层 ====================== + +// export async function executionAI(ctx: AgentContext) { +// const { text, abortSignal } = ctx; + +// const skill = await useSkill({ +// mainSkill: "production_agent_execution", +// workspace: ["production_agent_skills/execution"], +// attachedSkills: ["production_agent_skills/execution/driector_art_skills/chinese_sweet_romance/driector_skills"], //todo:后续可以改为动态加载 +// }); + +// const subMsg = ctx.resTool.newMessage("assistant", "执行导演"); + +// const { textStream } = await u.Ai.Text("productionAgent").stream({ +// system: skill.prompt, +// messages: [{ role: "user", content: text }], +// abortSignal, +// tools: { +// ...skill.tools, +// ...useTools({ resTool: ctx.resTool, msg: subMsg }), +// }, +// }); + +// return { textStream, subMsg }; +// } + +// export async function supervisionAI(ctx: AgentContext) { +// const { text, abortSignal } = ctx; + +// const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] }); +// const subMsg = ctx.resTool.newMessage("assistant", "监制"); + +// const { textStream } = await u.Ai.Text("productionAgent").stream({ +// system: skill.prompt, +// messages: [{ role: "user", content: text }], +// abortSignal, +// tools: { +// ...skill.tools, +// ...useTools({ +// resTool: ctx.resTool, +// msg: subMsg, +// }), +// }, +// }); + +// return { textStream, subMsg }; +// } + +// //工具函数 +// function runSubAgent(parentCtx: AgentContext) { +// const memory = new Memory("productionAgent", parentCtx.isolationKey); +// return tool({ +// description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", +// inputSchema: z.object({ +// agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"), +// prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), +// }), +// execute: async ({ agent, prompt }) => { +// const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; + +// // 先完成主Agent当前的消息 +// parentCtx.msg.complete(); +// // 子Agent用新消息回复 +// const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt }); +// let text = subMsg.text(); +// let fullResponse = ""; +// for await (const chunk of subTextStream) { +// text.append(chunk); +// fullResponse += chunk; +// } +// text.complete(); +// subMsg.complete(); +// if (fullResponse.trim()) { +// await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { +// name: agent === "executionAI" ? "执行导演" : "监制", +// createTime: new Date(subMsg.datetime).getTime(), +// }); +// } + +// // 为主Agent后续输出创建新消息 +// parentCtx.msg = parentCtx.resTool.newMessage("assistant", "监制"); + +// return fullResponse; +// }, +// }); +// } diff --git a/src/agents/productionAgent/tools copy.ts b/src/agents/productionAgent/tools copy.ts new file mode 100644 index 0000000..bb20b61 --- /dev/null +++ b/src/agents/productionAgent/tools copy.ts @@ -0,0 +1,751 @@ +import { tool, Tool } from "ai"; +import { z } from "zod"; +import _ from "lodash"; +import ResTool from "@/socket/resTool"; +import u from "@/utils"; +import { urlToBase64 } from "@/utils/vm"; +export const deriveAssetSchema = z.object({ + id: z.number().describe("衍生资产ID,如果新增则为空"), + assetsId: z.number().describe("关联的资产ID"), + prompt: z.string().describe("生成提示词"), + name: z.string().describe("衍生资产名称"), + desc: z.string().describe("衍生资产描述"), + src: z.string().nullable().describe("衍生资产资源路径"), + state: z.enum(["未生成", "生成中", "已完成", "生成失败"]).describe("衍生资产生成状态"), + type: z.enum(["role", "tool", "scene", "clip"]).describe("衍生资产类型"), +}); +export const assetItemSchema = z.object({ + id: z.number().describe("资产唯一标识"), + name: z.string().describe("资产名称"), + type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"), + prompt: z.string().describe("生成提示词"), + desc: z.string().describe("资产描述"), + derive: z.array(deriveAssetSchema).describe("衍生资产列表"), +}); +export const storyboardSchema = z.object({ + id: z.number().describe("分镜ID,必须为真实id"), + title: z.string().describe("分镜标题"), + description: z.string().describe("分镜描述"), + camera: z.string().describe("镜头信息"), + duration: z.number().describe("持续时长(秒)"), + frameMode: z.enum(["firstFrame", "endFrame", "linesSoundEffects"]).describe("帧模式: 首帧/尾帧/台词音效"), + prompt: z.string().describe("生成提示词"), + lines: z.string().nullable().describe("台词内容"), + sound: z.string().nullable().describe("音效内容"), + mode: z + .union([ + z.enum(["singleImage", "multiImage", "gridImage", "startEndRequired", "endFrameOptional", "startFrameOptional", "text"]), + z.array(z.enum(["video", "image", "audio", "text"])), + ]) + .describe("视频模式"), + associateAssetsIds: z.array(z.number()).describe("关联资产ID列表"), + src: z.string().nullable().describe("分镜资源路径"), +}); +export const workbenchDataSchema = z.object({ + name: z.string().describe("项目名称"), + duration: z.string().describe("视频时长"), + resolution: z.string().describe("分辨率"), + fps: z.string().describe("帧率"), + cover: z.string().optional().describe("封面图片路径"), + gradient: z.string().optional().describe("渐变色配置"), +}); +export const posterItemSchema = z.object({ + id: z.number().describe("海报ID"), + image: z.string().describe("海报图片路径"), +}); +export const flowDataSchema = z.object({ + script: z.string().describe("剧本内容"), + scriptPlan: z.string().describe("拍摄计划"), + assets: z.array(assetItemSchema).describe("衍生资产"), + storyboardTable: z.string().describe("分镜表"), + storyboard: z.array(storyboardSchema).describe("分镜面板"), + workbench: workbenchDataSchema.describe("工作台配置"), + poster: z + .object({ + items: z.array(posterItemSchema).describe("海报项目列表"), + }) + .describe("海报配置"), +}); + +export type FlowData = z.infer; + +const keySchema = z.enum(Object.keys(flowDataSchema.shape) as [keyof FlowData, ...Array]); +const flowDataKeyLabels = Object.fromEntries( + Object.entries(flowDataSchema.shape).map(([key, schema]) => [key, (schema as z.ZodTypeAny).description ?? key]), +) as Record; + +interface ToolConfig { + resTool: ResTool; + toolsNames?: string[]; + msg: ReturnType; +} + +export default (toolCpnfig: ToolConfig) => { + const { resTool, toolsNames, msg } = toolCpnfig; + const { socket } = resTool; + const tools: Record = { + get_flowData: tool({ + description: "获取工作区数据", + inputSchema: z.object({ + key: keySchema.describe("数据key"), + }), + execute: async ({ key }) => { + const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`); + console.log("[tools] get_flowData", key); + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res))); + thinking.appendText(`获取到${flowDataKeyLabels[key]}:\n` + flowData[key]); + thinking.updateTitle(`获取${flowDataKeyLabels[key]}完成`); + thinking.complete(); + return flowData[key]; + }, + }), + set_flowData_script: tool({ + description: "保存剧本内容到工作区", + inputSchema: z.object({ value: flowDataSchema.shape.script }), + execute: async ({ value }) => { + console.log("[tools] set_flowData script", value); + const thinking = msg.thinking("正在保存 剧本 数据"); + socket.emit("setFlowData", { key: "script", value }); + thinking.updateTitle("保存 剧本 数据完成"); + thinking.complete(); + return true; + }, + }), + set_flowData_scriptPlan: tool({ + description: "保存拍摄计划到工作区", + inputSchema: z.object({ value: flowDataSchema.shape.scriptPlan }), + execute: async ({ value }) => { + console.log("[tools] set_flowData scriptPlan", value); + const thinking = msg.thinking("正在保存 拍摄计划 数据"); + socket.emit("setFlowData", { key: "scriptPlan", value }); + thinking.updateTitle("保存 拍摄计划 数据完成"); + thinking.complete(); + return true; + }, + }), + add_flowData_assets: tool({ + description: "新增对应衍生资产列表到工作区,严禁包含 不需要新增的数据", + inputSchema: z.object({ value: z.array(deriveAssetSchema.omit({ id: true })).describe("需要新增的衍生资产列表") }), + execute: async ({ value }) => { + console.log("[tools] set_flowData add_flowData_assets", value); + const thinking = msg.thinking("正在保存 衍生资产 数据"); + const setData = [...value] as z.infer[]; + const { projectId, scriptId } = resTool.data; + const startTime = Date.now(); + + // 并行插入所有 o_assets 记录 + await Promise.all( + setData.map(async (i) => { + const [insertedId] = await u.db("o_assets").insert({ + assetsId: +i.assetsId || null, + projectId, + name: i.name, + type: i.type, + prompt: i.prompt, + describe: i.desc, + startTime, + }); + i.id = insertedId; + }), + ); + + // 批量插入 o_scriptAssets + await u.db("o_scriptAssets").insert(setData.map((i) => ({ scriptId, assetId: i.id }))); + + const watiAddAssetsMap: Record[]> = {}; + setData.forEach((i) => { + if (watiAddAssetsMap[i.assetsId]) { + watiAddAssetsMap[i.assetsId].push(i); + } else { + watiAddAssetsMap[i.assetsId] = [i]; + } + }); + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); + const assetsData = flowData.assets; + assetsData.forEach((i) => { + if (watiAddAssetsMap[i.id]) { + i.derive = [...(i.derive || []), ...watiAddAssetsMap[i.id]]; + } + }); + thinking.updateTitle("保存 衍生资产 数据完成"); + thinking.complete(); + + socket.emit("setFlowData", { key: "assets", value: assetsData }); + return true; + }, + }), + update_flowData_assets: tool({ + description: "更新对应衍生资产列表到工作区", + inputSchema: z.object({ value: z.array(deriveAssetSchema).describe("需要更新的衍生资产列表") }), + execute: async ({ value }) => { + console.log("[tools] update_flowData update_flowData_assets", value); + const thinking = msg.thinking("正在保存 衍生资产 数据"); + for (const i of value) { + await u + .db("o_assets") + .where("id", i.id) + .update({ + assetsId: +i.assetsId || null, + projectId: resTool.data.projectId, + name: i.name, + type: i.type, + prompt: i.prompt, + describe: i.desc, + }); + } + // 按 assetsId 分组,构建更新映射 + const updateAssetsMap: Record[]> = {}; + value.forEach((i) => { + if (updateAssetsMap[i.assetsId]) { + updateAssetsMap[i.assetsId].push(i); + } else { + updateAssetsMap[i.assetsId] = [i]; + } + }); + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); + const assetsData = flowData.assets; + // 将 derive 中已存在的条目替换为更新后的数据 + assetsData.forEach((asset) => { + if (updateAssetsMap[asset.id]) { + const updatedMap = Object.fromEntries(updateAssetsMap[asset.id].map((d) => [d.id, d])); + asset.derive = (asset.derive || []).map((d) => updatedMap[d.id] ?? d); + } + }); + thinking.updateTitle("保存 衍生资产 数据完成"); + thinking.complete(); + socket.emit("setFlowData", { key: "assets", value: assetsData }); + return true; + }, + }), + delete_flowData_assets: tool({ + description: "删除对应衍生资产", + inputSchema: z.object({ ids: z.array(z.number()).describe("需要删除的 衍生资产id ") }), + execute: async ({ ids }) => { + console.log("[tools] delete_flowData delete_flowData_assets", ids); + const thinking = msg.thinking("正在删除指定 衍生资产 数据..."); + await u.db("o_assets").whereIn("id", ids).delete(); + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); + const assetsData = flowData.assets; + assetsData.forEach((i) => { + i.derive = (i.derive || []).filter((d) => !ids.includes(d.id)); + }); + thinking.updateTitle("删除指定 衍生资产 数据完成"); + thinking.complete(); + // 将 derive 中已存在的条目替换为更新后的数据 + socket.emit("setFlowData", { key: "assets", value: assetsData }); + return true; + }, + }), + // set_flowData_assets: tool({ + // description: "保存衍生资产列表到工作区", + // inputSchema: z.object({ value: flowDataSchema.shape.assets }), + // execute: async ({ value }) => { + // console.log("[tools] set_flowData assets", value); + // resTool.systemMessage("正在保存 衍生资产 数据"); + // if (value && Array.isArray(value) && value.length) { + // for (const i of value) { + // if (!i?.id) { + // const [insertedId] = await u.db("o_assets").insert({ + // assetsId: null, + // name: i.name, + // type: i.type, + // prompt: i.prompt, + // describe: i.desc, + // startTime: Date.now(), + // }); + // i.id = insertedId; + // } + // if (i.derive && Array.isArray(i.derive) && i.derive.length) { + // for (const sub of i.derive) { + // if (sub.id) continue; + // const [insertedId] = await u.db("o_assets").insert({ + // assetsId: +i.id || null, + // projectId: resTool.data.projectId, + // name: sub.name, + // type: sub.type, + // prompt: sub.prompt, + // describe: sub.desc, + // startTime: Date.now(), + // }); + // await u.db("o_scriptAssets").insert({ + // scriptId: resTool.data.scriptId, + // assetId: insertedId, + // }); + // sub.id = insertedId; + // } + // } + // } + // } + // socket.emit("setFlowData", { key: "assets", value }); + // return true; + // }, + // }), + set_flowData_storyboardTable: tool({ + description: "保存分镜表到工作区", + inputSchema: z.object({ value: flowDataSchema.shape.storyboardTable }), + execute: async ({ value }) => { + console.log("[tools] set_flowData storyboardTable", value); + const thinking = msg.thinking("正在保存 分镜表 数据..."); + socket.emit("setFlowData", { key: "storyboardTable", value }); + thinking.updateTitle("保存 分镜表 数据完成"); + thinking.complete(); + return true; + }, + }), + add_flowData_storyboard: tool({ + description: "新增分镜面板到工作区", + inputSchema: z.object({ value: z.array(storyboardSchema.omit({ id: true })) }), + execute: async ({ value }) => { + console.log("[tools] add_flowData storyboard", value); + const thinking = msg.thinking("正在保存 分镜面板 数据..."); + const setData = [...value] as z.infer[]; + for (const item of setData) { + item.src = ""; + const [insertedId] = await u.db("o_storyboard").insert({ + title: item.title, + prompt: item.prompt, + description: item.description, + frameMode: item.frameMode, + duration: String(item.duration), + camera: item.camera, + sound: item.sound, + lines: item.lines, + state: "未生成", + scriptId: resTool.data.scriptId, + createTime: Date.now(), + }); + if (item.associateAssetsIds.length) { + await u.db("o_assets2Storyboard").insert(item.associateAssetsIds.map((i) => ({ storyboardId: insertedId, assetId: i }))); + } + item.id = insertedId; + } + //为了防止丢失分镜其他数据,例如:依赖分镜Id、依赖资产idc + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "storyboard" }, (res: any) => resolve(res))); + const storyboardData = flowData["storyboard"].concat([...setData]); + socket.emit("setFlowData", { key: "storyboard", value: storyboardData }); + + thinking.updateTitle("保存 分镜面板 数据完成"); + thinking.complete(); + + return true; + }, + }), + update_flowData_storyboard: tool({ + description: "更新指定分镜面板到工作区", + inputSchema: z.object({ value: flowDataSchema.shape.storyboard }), + execute: async ({ value }) => { + console.log("[tools] update_flowData storyboard", value); + const thinking = msg.thinking("正在保存 分镜面板 数据..."); + for (const item of value) { + await u + .db("o_storyboard") + .where("id", item.id) + .update({ + title: item.title, + prompt: item.prompt, + description: item.description, + frameMode: item.frameMode, + duration: String(item.duration), + camera: item.camera, + sound: item.sound, + lines: item.lines, + }); + } + //直接拉取前端数据,为了防止丢失分镜其他数据,例如:依赖分镜Id、依赖资产idc + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "storyboard" }, (res: any) => resolve(res))); + const storyboardData = flowData["storyboard"].map((existing) => { + const updated = value.find((v) => v.id === existing.id); + if (!updated) return existing; + return { + ...existing, + title: updated.title, + prompt: updated.prompt, + description: updated.description, + frameMode: updated.frameMode, + duration: updated.duration, + camera: updated.camera, + sound: updated.sound, + lines: updated.lines, + }; + }); + socket.emit("setFlowData", { key: "storyboard", value: storyboardData }); + thinking.updateTitle("保存 分镜面板 数据完成"); + thinking.complete(); + return true; + }, + }), + delete_flowData_storyboard: tool({ + description: "删除指定分镜面板并更新工作区", + inputSchema: z.object({ ids: z.array(z.number()).describe("需要删除的 分镜id ") }), + execute: async ({ ids }) => { + console.log("[tools] delete_flowData storyboard", ids); + const thinking = msg.thinking("正在删除指定 分镜面板 数据..."); + await u.db("o_storyboard").whereIn("id", ids).delete(); + await u.db("o_assets2Storyboard").whereIn("storyboardId", ids).delete(); + await u.db("o_storyboardFlow").whereIn("storyboardId", ids).delete(); + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "storyboard" }, (res: any) => resolve(res))); + const storyboardData = flowData["storyboard"].filter((item) => !ids.includes(item.id)); + socket.emit("setFlowData", { key: "storyboard", value: storyboardData }); + thinking.updateTitle("删除指定 分镜面板 数据完成"); + thinking.complete(); + return true; + }, + }), + // set_flowData_storyboard: tool({ + // description: "保存分镜面板到工作区", + // inputSchema: z.object({ value: flowDataSchema.shape.storyboard }), + // execute: async ({ value }) => { + // console.log("[tools] set_flowData storyboard", value); + // resTool.systemMessage("正在保存 分镜面板 数据..."); + // for (const item of value) { + // if (!item.id) { + // const [insertedId] = await u.db("o_storyboard").insert({ + // title: item.title, + // prompt: item.prompt, + // description: item.description, + // filePath: item.src, + // frameMode: item.frameMode, + // duration: String(item.duration), + // camera: item.camera, + // sound: item.sound, + // lines: item.lines, + // state: "未生成", + // scriptId: resTool.data.scriptId, + // }); + // console.log("%c Line:216 🥥 item.associateAssetsIds", "background:#6ec1c2", item.associateAssetsIds); + + // if (item.associateAssetsIds.length) { + // await u.db("o_assets2Storyboard").insert(item.associateAssetsIds.map((i) => ({ storyboardId: insertedId, assetId: i }))); + // } + // item.id = insertedId; + // } + // } + // socket.emit("setFlowData", { key: "storyboard", value }); + // return true; + // }, + // }), + set_flowData_workbench: tool({ + description: "保存工作台配置数据到工作区", + inputSchema: z.object({ value: flowDataSchema.shape.workbench }), + execute: async ({ value }) => { + console.log("[tools] set_flowData workbench", value); + const thinking = msg.thinking("正在保存 工作台配置 数据..."); + socket.emit("setFlowData", { key: "workbench", value }); + thinking.updateTitle("保存 工作台配置 数据完成"); + thinking.complete(); + return true; + }, + }), + set_flowData_poster: tool({ + description: "保存海报配置到工作区", + inputSchema: z.object({ value: flowDataSchema.shape.poster }), + execute: async ({ value }) => { + console.log("[tools] set_flowData poster", value); + const thinking = msg.thinking("正在保存 海报配置 数据..."); + thinking.updateTitle("保存 海报配置 数据完成"); + thinking.complete(); + socket.emit("setFlowData", { key: "poster", value }); + return true; + }, + }), + // todo 提示词待调 + generate_storyboard_images: tool({ + description: `生成一组图片任务,支持图片间的依赖关系(以图生图),基于有向无环图(DAG)拓扑排序执行。 + + 参数说明: + - images: 图片任务数组 + - id: 图片唯一标识符(分镜id) + - prompt: 图片生成提示词 + - referenceIds: 依赖的参考图id数组,无依赖填空数组[] + - assetIds: 参考的资产图id数组(可选) + + 依赖规则: + 1. referenceIds中的id必须存在于images数组中 + 2. 禁止循环依赖(如A依赖B,B依赖A) + 3. 被依赖的图片会先生成,其结果作为参考图传入 + + 示例:生成猫图,再以猫图为参考生成狗图 + images: [ + {id: 1, prompt: "一只橘猫", referenceIds: [], assetIds: []}, + {id: 2, prompt: "风格相同的金毛犬", referenceIds: [1], assetIds: []} + ]`, + inputSchema: z.object({ + images: z.array( + z.object({ + id: z.number().describe("从工作区获取到的分镜id"), + prompt: z.string().describe("图片生成提示词"), + referenceIds: z.array(z.number()).describe("依赖的参考 分镜图id数组,无依赖填空数组[]"), + assetIds: z.array(z.number()).describe("参考的资产图"), + }), + ), + }), + execute: async ({ images }) => { + console.log("[tools] generate_storyboard_images", images); + const thinking = msg.thinking("正在生成 分镜图片 数据..."); + // --- 构建任务id集合 --- + const taskIds = new Set(images.map((item) => item.id)); + const imageMap = new Map(images.map((item) => [item.id, item])); + + // --- 检测循环依赖 (Kahn算法拓扑排序) --- + // 将 referenceIds 分为:本批次内依赖 vs 外部已有依赖 + // 只有本批次内的依赖才参与 DAG 调度,外部依赖直接从数据库获取 + const inDegree = new Map(); + // adjacency: 被依赖者 -> 依赖它的节点列表 + const adjacency = new Map(); + + for (const item of images) { + // 只统计本批次内的依赖作为入度 + const internalDeps = item.referenceIds.filter((refId) => taskIds.has(refId)); + inDegree.set(item.id, internalDeps.length); + for (const depId of internalDeps) { + if (!adjacency.has(depId)) adjacency.set(depId, []); + adjacency.get(depId)!.push(item.id); + } + } + + // 拓扑排序,按层级分组(同层可并行) + const levels: number[][] = []; + let queue = images.filter((item) => (inDegree.get(item.id) ?? 0) === 0).map((item) => item.id); + + const visited = new Set(); + while (queue.length > 0) { + levels.push([...queue]); + const nextQueue: number[] = []; + for (const nodeId of queue) { + visited.add(nodeId); + for (const childId of adjacency.get(nodeId) ?? []) { + inDegree.set(childId, (inDegree.get(childId) ?? 1) - 1); + if (inDegree.get(childId) === 0) { + nextQueue.push(childId); + } + } + } + queue = nextQueue; + } + // 循环依赖检测 + if (visited.size !== images.length) { + const cyclicIds = images.filter((item) => !visited.has(item.id)).map((item) => item.id); + thinking.appendText(`检测到循环依赖,涉及分镜id: ${cyclicIds.join(", ")},请修正后重试`); + thinking.updateTitle("循环依赖错误"); + thinking.error(); + return `错误:检测到循环依赖,涉及分镜id: ${cyclicIds.join(", ")}`; + } + + thinking.appendText(`图片生成调度计划:共 ${levels.length} 层,${images.length} 张图片`); + + // --- 准备公共数据 --- + const projectData = await u.db("o_project").where("id", resTool.data.projectId).select("videoRatio").first(); + const imageModelData = await u.db("o_project").where("id", resTool.data.projectId).select("imageModel", "imageQuality").first(); + + // 生成单张图片的函数 + const generateOneImage = async (item: (typeof images)[0]) => { + const thinking = msg.thinking(`正在生成分镜 id:${item.id} 图片`); + // 更新数据库状态为生成中 + await u.db("o_storyboard").where("id", item.id).update({ state: "生成中" }); + // 更新前端为生成中 + socket.emit("setFlowData", { + key: "setStoryboardImage", + value: { ...item, id: item.id, src: "", state: "生成中", referenceIds: item.referenceIds }, + }); + + // 获取参考图base64(包括资产图和已生成的分镜参考图) + const [assetsBase64, referenceBase64] = await Promise.all([ + getAssetsImageBase64(item.assetIds ?? []), + getStoryboardImageBase64(item.referenceIds), + ]); + + const imageCls = await u.Ai.Image(imageModelData.imageModel).run({ + prompt: item.prompt, + imageBase64: [...assetsBase64, ...referenceBase64], + size: imageModelData.imageQuality, + aspectRatio: (projectData?.videoRatio as `${number}:${number}`) ?? "16:9", + taskClass: "生成图片", + describe: "分镜图片生成", + relatedObjects: "hhhh", + projectId: resTool.data.projectId, + }); + + const savePath = `/${resTool.data.projectId}/storyboard/${u.uuid()}.jpg`; + await imageCls.save(savePath); + + // 更新数据库状态为已完成 + await u.db("o_storyboard").where("id", item.id).update({ state: "已完成", filePath: savePath }); + + const obj = { + ...item, + id: item.id, + src: await u.oss.getFileUrl(savePath), + state: "已完成", + referenceIds: item.referenceIds, + }; + // 前端对话框提示 + thinking.appendText(`分镜 id:${item.id} 图片生成完成`); + thinking.complete(); + // 更新前端界面展示 + socket.emit("setFlowData", { key: "setStoryboardImage", value: obj }); + }; + + // --- 按层级顺序执行:同层并行,层间串行 --- + for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) { + const levelIds = levels[levelIndex]; + const levelItems = levelIds.map((id) => imageMap.get(id)!); + const thinking = msg.thinking( + `开始生成第 ${levelIndex + 1}/${levels.length} 层,共 ${levelItems.length} 张图片 (ids: ${levelIds.join(", ")})`, + ); + + // 同层内所有图片并行生成,使用 allSettled 确保不会因单张失败中断整层 + const results = await Promise.allSettled(levelItems.map((item) => generateOneImage(item))); + + // 处理失败的任务 + for (let i = 0; i < results.length; i++) { + if (results[i].status === "rejected") { + const failedId = levelIds[i]; + const reason = (results[i] as PromiseRejectedResult).reason; + console.error(`[tools] 分镜 id:${failedId} 图片生成失败`, reason); + thinking.appendText(`分镜 id:${failedId} 图片生成失败: ${reason?.message || reason}`); + await u.db("o_storyboard").where("id", failedId).update({ state: "生成失败" }); + socket.emit("setFlowData", { + key: "setStoryboardImage", + value: { id: failedId, src: "", state: "生成失败" }, + }); + } + } + thinking.appendText(`第 ${levelIndex + 1}/${levels.length} 层图片生成完成`); + thinking.complete(); + } + thinking.appendText("所有分镜图片生成完成"); + thinking.updateTitle("分镜图片生成完成"); + thinking.complete(); + + return "分镜图片生成完成"; + }, + }), + + //todo 提示词待调 + generate_assets_images: tool({ + description: ` + 生成 资产图片 不区分原资产于衍生资产 + 参数说明: + - images: 图片任务数组 + - assetId: 资产id + - prompt: 图片生成提示词 + 示例: + images:[ + {assetId: 1, prompt: "一张猫的图片"} + ] + `, + inputSchema: z.object({ + images: z.array( + z.object({ + assetId: z.number().describe("衍生资产id"), + prompt: z.string().describe("提示词"), + }), + ), + }), + execute: async ({ images }) => { + console.log("[tools] generate_assets_images", images); + //先获取到前端资产数据 + const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); + const assetsData = flowData["assets"]; + const assetsImage: { assetId: number; prompt: string; id?: number }[] = [...images]; + //获取对应的 原资产id + assetsImage.forEach((item) => { + for (const i of assetsData) { + const findData = i.derive.find((m) => m.id == item.assetId); + if (findData) { + item.id = findData.id; + break; + } + } + }); + //获取所设置模型 + const imageModelData = await u.db("o_project").where("id", resTool.data.projectId).select("imageModel", "imageQuality").first(); + for (const item of assetsImage) { + const [imageId] = await u.db("o_image").insert({ + // 数据库插入图片记录 + assetsId: item.assetId, + model: imageModelData?.imageModel, + state: "生成中", + resolution: imageModelData?.imageQuality, + }); + u.Ai.Image(imageModelData?.imageModel) + .run({ + prompt: item.prompt, + imageBase64: await getAssetsImageBase64(item.id ? [item.id] : []), + size: imageModelData?.imageQuality, + aspectRatio: "16:9", + taskClass: "生成图片", + describe: "资产图片生成", + relatedObjects: "hhhh", + projectId: resTool.data.projectId, + }) + .then(async (imageCls) => { + const savePath = `/${resTool.data.projectId}/assets/${u.uuid()}.jpg`; + await imageCls.save(savePath); + const obj = { + ...item, + id: item.assetId, + src: await u.oss.getFileUrl(savePath), + state: "已完成", + }; + //更新对应数据库 + await u.db("o_assets").where("id", item.assetId).update({ imageId: imageId }); + await u.db("o_image").where({ id: imageId }).update({ state: "已完成", filePath: savePath }); + //通知前端更新 + socket.emit("setFlowData", { key: "setAssetsImage", value: obj }); + }); + //通知前端更新状态 + socket.emit("setFlowData", { key: "setAssetsImage", value: { ...item, id: item.assetId, src: "", state: "生成中" } }); + } + return "资产生成中"; + }, + }), + }; + + return toolsNames ? Object.fromEntries(Object.entries(tools).filter(([n]) => toolsNames.includes(n))) : tools; +}; + +// 获取资产图片base64 +async function getAssetsImageBase64(imageIds: number[]) { + if (imageIds.length === 0) return []; + const imagePaths = await u + .db("o_assets") + .leftJoin("o_image", "o_assets.imageId", "o_image.id") + .whereIn("o_assets.id", imageIds) + .select("o_assets.id", "o_image.filePath"); + if (!imagePaths.length) return []; + const imageUrls = await Promise.all( + imagePaths.map(async (i) => { + if (i.filePath) { + try { + return await urlToBase64(await u.oss.getFileUrl(i.filePath)); + } catch { + return null; + } + } else { + return null; + } + }), + ); + return imageUrls.filter(Boolean) as string[]; +} + +//获取分镜图片base64 +async function getStoryboardImageBase64(imageIds: number[]) { + if (!imageIds.length) return []; + const storayboardData = await u.db("o_storyboard").whereIn("id", imageIds).select("id", "filePath"); + const imageUrls = await Promise.all( + storayboardData.map(async (i) => { + if (i.filePath) { + try { + return await urlToBase64(await u.oss.getFileUrl(i.filePath)); + } catch { + return null; + } + } else { + return null; + } + }), + ); + return imageUrls.filter(Boolean) as string[]; +} diff --git a/src/agents/productionAgent/tools.ts b/src/agents/productionAgent/tools.ts index bb20b61..ada1e8d 100644 --- a/src/agents/productionAgent/tools.ts +++ b/src/agents/productionAgent/tools.ts @@ -3,8 +3,8 @@ import { z } from "zod"; import _ from "lodash"; import ResTool from "@/socket/resTool"; import u from "@/utils"; -import { urlToBase64 } from "@/utils/vm"; -export const deriveAssetSchema = z.object({ + +const deriveAssetSchema = z.object({ id: z.number().describe("衍生资产ID,如果新增则为空"), assetsId: z.number().describe("关联的资产ID"), prompt: z.string().describe("生成提示词"), @@ -14,7 +14,7 @@ export const deriveAssetSchema = z.object({ state: z.enum(["未生成", "生成中", "已完成", "生成失败"]).describe("衍生资产生成状态"), type: z.enum(["role", "tool", "scene", "clip"]).describe("衍生资产类型"), }); -export const assetItemSchema = z.object({ +const assetItemSchema = z.object({ id: z.number().describe("资产唯一标识"), name: z.string().describe("资产名称"), type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"), @@ -22,7 +22,7 @@ export const assetItemSchema = z.object({ desc: z.string().describe("资产描述"), derive: z.array(deriveAssetSchema).describe("衍生资产列表"), }); -export const storyboardSchema = z.object({ +const storyboardSchema = z.object({ id: z.number().describe("分镜ID,必须为真实id"), title: z.string().describe("分镜标题"), description: z.string().describe("分镜描述"), @@ -41,7 +41,7 @@ export const storyboardSchema = z.object({ associateAssetsIds: z.array(z.number()).describe("关联资产ID列表"), src: z.string().nullable().describe("分镜资源路径"), }); -export const workbenchDataSchema = z.object({ +const workbenchDataSchema = z.object({ name: z.string().describe("项目名称"), duration: z.string().describe("视频时长"), resolution: z.string().describe("分辨率"), @@ -49,11 +49,11 @@ export const workbenchDataSchema = z.object({ cover: z.string().optional().describe("封面图片路径"), gradient: z.string().optional().describe("渐变色配置"), }); -export const posterItemSchema = z.object({ +const posterItemSchema = z.object({ id: z.number().describe("海报ID"), image: z.string().describe("海报图片路径"), }); -export const flowDataSchema = z.object({ +const flowDataSchema = z.object({ script: z.string().describe("剧本内容"), scriptPlan: z.string().describe("拍摄计划"), assets: z.array(assetItemSchema).describe("衍生资产"), @@ -93,659 +93,94 @@ export default (toolCpnfig: ToolConfig) => { const thinking = msg.thinking(`正在获取${flowDataKeyLabels[key]}工作区数据...`); console.log("[tools] get_flowData", key); const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res))); - thinking.appendText(`获取到${flowDataKeyLabels[key]}:\n` + flowData[key]); + thinking.appendText(`获取到${flowDataKeyLabels[key]}:\n` + JSON.stringify(flowData[key], null, 2)); thinking.updateTitle(`获取${flowDataKeyLabels[key]}完成`); thinking.complete(); return flowData[key]; }, }), - set_flowData_script: tool({ - description: "保存剧本内容到工作区", - inputSchema: z.object({ value: flowDataSchema.shape.script }), - execute: async ({ value }) => { - console.log("[tools] set_flowData script", value); - const thinking = msg.thinking("正在保存 剧本 数据"); - socket.emit("setFlowData", { key: "script", value }); - thinking.updateTitle("保存 剧本 数据完成"); - thinking.complete(); - return true; - }, - }), - set_flowData_scriptPlan: tool({ - description: "保存拍摄计划到工作区", - inputSchema: z.object({ value: flowDataSchema.shape.scriptPlan }), - execute: async ({ value }) => { - console.log("[tools] set_flowData scriptPlan", value); - const thinking = msg.thinking("正在保存 拍摄计划 数据"); - socket.emit("setFlowData", { key: "scriptPlan", value }); - thinking.updateTitle("保存 拍摄计划 数据完成"); - thinking.complete(); - return true; - }, - }), - add_flowData_assets: tool({ - description: "新增对应衍生资产列表到工作区,严禁包含 不需要新增的数据", - inputSchema: z.object({ value: z.array(deriveAssetSchema.omit({ id: true })).describe("需要新增的衍生资产列表") }), - execute: async ({ value }) => { - console.log("[tools] set_flowData add_flowData_assets", value); - const thinking = msg.thinking("正在保存 衍生资产 数据"); - const setData = [...value] as z.infer[]; + add_deriveAsset: tool({ + description: "新增或更新衍生资产", + inputSchema: z.object({ + assetsId: z.number().describe("关联的资产ID"), + id: z.number().nullable().describe("衍生资产ID,如果新增则为空"), + name: z.string().describe("衍生资产名称"), + desc: z.string().describe("衍生资产描述"), + type: z.enum(["role", "tool", "scene", "clip"]).describe("衍生资产类型"), + }), + execute: async (deriveAsset) => { + const thinking = msg.thinking("正在操作资产..."); const { projectId, scriptId } = resTool.data; const startTime = Date.now(); - - // 并行插入所有 o_assets 记录 - await Promise.all( - setData.map(async (i) => { - const [insertedId] = await u.db("o_assets").insert({ - assetsId: +i.assetsId || null, - projectId, - name: i.name, - type: i.type, - prompt: i.prompt, - describe: i.desc, - startTime, - }); - i.id = insertedId; - }), - ); - - // 批量插入 o_scriptAssets - await u.db("o_scriptAssets").insert(setData.map((i) => ({ scriptId, assetId: i.id }))); - - const watiAddAssetsMap: Record[]> = {}; - setData.forEach((i) => { - if (watiAddAssetsMap[i.assetsId]) { - watiAddAssetsMap[i.assetsId].push(i); - } else { - watiAddAssetsMap[i.assetsId] = [i]; - } - }); - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); - const assetsData = flowData.assets; - assetsData.forEach((i) => { - if (watiAddAssetsMap[i.id]) { - i.derive = [...(i.derive || []), ...watiAddAssetsMap[i.id]]; - } - }); - thinking.updateTitle("保存 衍生资产 数据完成"); - thinking.complete(); - - socket.emit("setFlowData", { key: "assets", value: assetsData }); - return true; - }, - }), - update_flowData_assets: tool({ - description: "更新对应衍生资产列表到工作区", - inputSchema: z.object({ value: z.array(deriveAssetSchema).describe("需要更新的衍生资产列表") }), - execute: async ({ value }) => { - console.log("[tools] update_flowData update_flowData_assets", value); - const thinking = msg.thinking("正在保存 衍生资产 数据"); - for (const i of value) { - await u - .db("o_assets") - .where("id", i.id) - .update({ - assetsId: +i.assetsId || null, - projectId: resTool.data.projectId, - name: i.name, - type: i.type, - prompt: i.prompt, - describe: i.desc, - }); - } - // 按 assetsId 分组,构建更新映射 - const updateAssetsMap: Record[]> = {}; - value.forEach((i) => { - if (updateAssetsMap[i.assetsId]) { - updateAssetsMap[i.assetsId].push(i); - } else { - updateAssetsMap[i.assetsId] = [i]; - } - }); - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); - const assetsData = flowData.assets; - // 将 derive 中已存在的条目替换为更新后的数据 - assetsData.forEach((asset) => { - if (updateAssetsMap[asset.id]) { - const updatedMap = Object.fromEntries(updateAssetsMap[asset.id].map((d) => [d.id, d])); - asset.derive = (asset.derive || []).map((d) => updatedMap[d.id] ?? d); - } - }); - thinking.updateTitle("保存 衍生资产 数据完成"); - thinking.complete(); - socket.emit("setFlowData", { key: "assets", value: assetsData }); - return true; - }, - }), - delete_flowData_assets: tool({ - description: "删除对应衍生资产", - inputSchema: z.object({ ids: z.array(z.number()).describe("需要删除的 衍生资产id ") }), - execute: async ({ ids }) => { - console.log("[tools] delete_flowData delete_flowData_assets", ids); - const thinking = msg.thinking("正在删除指定 衍生资产 数据..."); - await u.db("o_assets").whereIn("id", ids).delete(); - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); - const assetsData = flowData.assets; - assetsData.forEach((i) => { - i.derive = (i.derive || []).filter((d) => !ids.includes(d.id)); - }); - thinking.updateTitle("删除指定 衍生资产 数据完成"); - thinking.complete(); - // 将 derive 中已存在的条目替换为更新后的数据 - socket.emit("setFlowData", { key: "assets", value: assetsData }); - return true; - }, - }), - // set_flowData_assets: tool({ - // description: "保存衍生资产列表到工作区", - // inputSchema: z.object({ value: flowDataSchema.shape.assets }), - // execute: async ({ value }) => { - // console.log("[tools] set_flowData assets", value); - // resTool.systemMessage("正在保存 衍生资产 数据"); - // if (value && Array.isArray(value) && value.length) { - // for (const i of value) { - // if (!i?.id) { - // const [insertedId] = await u.db("o_assets").insert({ - // assetsId: null, - // name: i.name, - // type: i.type, - // prompt: i.prompt, - // describe: i.desc, - // startTime: Date.now(), - // }); - // i.id = insertedId; - // } - // if (i.derive && Array.isArray(i.derive) && i.derive.length) { - // for (const sub of i.derive) { - // if (sub.id) continue; - // const [insertedId] = await u.db("o_assets").insert({ - // assetsId: +i.id || null, - // projectId: resTool.data.projectId, - // name: sub.name, - // type: sub.type, - // prompt: sub.prompt, - // describe: sub.desc, - // startTime: Date.now(), - // }); - // await u.db("o_scriptAssets").insert({ - // scriptId: resTool.data.scriptId, - // assetId: insertedId, - // }); - // sub.id = insertedId; - // } - // } - // } - // } - // socket.emit("setFlowData", { key: "assets", value }); - // return true; - // }, - // }), - set_flowData_storyboardTable: tool({ - description: "保存分镜表到工作区", - inputSchema: z.object({ value: flowDataSchema.shape.storyboardTable }), - execute: async ({ value }) => { - console.log("[tools] set_flowData storyboardTable", value); - const thinking = msg.thinking("正在保存 分镜表 数据..."); - socket.emit("setFlowData", { key: "storyboardTable", value }); - thinking.updateTitle("保存 分镜表 数据完成"); - thinking.complete(); - return true; - }, - }), - add_flowData_storyboard: tool({ - description: "新增分镜面板到工作区", - inputSchema: z.object({ value: z.array(storyboardSchema.omit({ id: true })) }), - execute: async ({ value }) => { - console.log("[tools] add_flowData storyboard", value); - const thinking = msg.thinking("正在保存 分镜面板 数据..."); - const setData = [...value] as z.infer[]; - for (const item of setData) { - item.src = ""; - const [insertedId] = await u.db("o_storyboard").insert({ - title: item.title, - prompt: item.prompt, - description: item.description, - frameMode: item.frameMode, - duration: String(item.duration), - camera: item.camera, - sound: item.sound, - lines: item.lines, - state: "未生成", - scriptId: resTool.data.scriptId, - createTime: Date.now(), - }); - if (item.associateAssetsIds.length) { - await u.db("o_assets2Storyboard").insert(item.associateAssetsIds.map((i) => ({ storyboardId: insertedId, assetId: i }))); - } - item.id = insertedId; - } - //为了防止丢失分镜其他数据,例如:依赖分镜Id、依赖资产idc - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "storyboard" }, (res: any) => resolve(res))); - const storyboardData = flowData["storyboard"].concat([...setData]); - socket.emit("setFlowData", { key: "storyboard", value: storyboardData }); - - thinking.updateTitle("保存 分镜面板 数据完成"); - thinking.complete(); - - return true; - }, - }), - update_flowData_storyboard: tool({ - description: "更新指定分镜面板到工作区", - inputSchema: z.object({ value: flowDataSchema.shape.storyboard }), - execute: async ({ value }) => { - console.log("[tools] update_flowData storyboard", value); - const thinking = msg.thinking("正在保存 分镜面板 数据..."); - for (const item of value) { - await u - .db("o_storyboard") - .where("id", item.id) - .update({ - title: item.title, - prompt: item.prompt, - description: item.description, - frameMode: item.frameMode, - duration: String(item.duration), - camera: item.camera, - sound: item.sound, - lines: item.lines, - }); - } - //直接拉取前端数据,为了防止丢失分镜其他数据,例如:依赖分镜Id、依赖资产idc - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "storyboard" }, (res: any) => resolve(res))); - const storyboardData = flowData["storyboard"].map((existing) => { - const updated = value.find((v) => v.id === existing.id); - if (!updated) return existing; - return { - ...existing, - title: updated.title, - prompt: updated.prompt, - description: updated.description, - frameMode: updated.frameMode, - duration: updated.duration, - camera: updated.camera, - sound: updated.sound, - lines: updated.lines, - }; - }); - socket.emit("setFlowData", { key: "storyboard", value: storyboardData }); - thinking.updateTitle("保存 分镜面板 数据完成"); - thinking.complete(); - return true; - }, - }), - delete_flowData_storyboard: tool({ - description: "删除指定分镜面板并更新工作区", - inputSchema: z.object({ ids: z.array(z.number()).describe("需要删除的 分镜id ") }), - execute: async ({ ids }) => { - console.log("[tools] delete_flowData storyboard", ids); - const thinking = msg.thinking("正在删除指定 分镜面板 数据..."); - await u.db("o_storyboard").whereIn("id", ids).delete(); - await u.db("o_assets2Storyboard").whereIn("storyboardId", ids).delete(); - await u.db("o_storyboardFlow").whereIn("storyboardId", ids).delete(); - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "storyboard" }, (res: any) => resolve(res))); - const storyboardData = flowData["storyboard"].filter((item) => !ids.includes(item.id)); - socket.emit("setFlowData", { key: "storyboard", value: storyboardData }); - thinking.updateTitle("删除指定 分镜面板 数据完成"); - thinking.complete(); - return true; - }, - }), - // set_flowData_storyboard: tool({ - // description: "保存分镜面板到工作区", - // inputSchema: z.object({ value: flowDataSchema.shape.storyboard }), - // execute: async ({ value }) => { - // console.log("[tools] set_flowData storyboard", value); - // resTool.systemMessage("正在保存 分镜面板 数据..."); - // for (const item of value) { - // if (!item.id) { - // const [insertedId] = await u.db("o_storyboard").insert({ - // title: item.title, - // prompt: item.prompt, - // description: item.description, - // filePath: item.src, - // frameMode: item.frameMode, - // duration: String(item.duration), - // camera: item.camera, - // sound: item.sound, - // lines: item.lines, - // state: "未生成", - // scriptId: resTool.data.scriptId, - // }); - // console.log("%c Line:216 🥥 item.associateAssetsIds", "background:#6ec1c2", item.associateAssetsIds); - - // if (item.associateAssetsIds.length) { - // await u.db("o_assets2Storyboard").insert(item.associateAssetsIds.map((i) => ({ storyboardId: insertedId, assetId: i }))); - // } - // item.id = insertedId; - // } - // } - // socket.emit("setFlowData", { key: "storyboard", value }); - // return true; - // }, - // }), - set_flowData_workbench: tool({ - description: "保存工作台配置数据到工作区", - inputSchema: z.object({ value: flowDataSchema.shape.workbench }), - execute: async ({ value }) => { - console.log("[tools] set_flowData workbench", value); - const thinking = msg.thinking("正在保存 工作台配置 数据..."); - socket.emit("setFlowData", { key: "workbench", value }); - thinking.updateTitle("保存 工作台配置 数据完成"); - thinking.complete(); - return true; - }, - }), - set_flowData_poster: tool({ - description: "保存海报配置到工作区", - inputSchema: z.object({ value: flowDataSchema.shape.poster }), - execute: async ({ value }) => { - console.log("[tools] set_flowData poster", value); - const thinking = msg.thinking("正在保存 海报配置 数据..."); - thinking.updateTitle("保存 海报配置 数据完成"); - thinking.complete(); - socket.emit("setFlowData", { key: "poster", value }); - return true; - }, - }), - // todo 提示词待调 - generate_storyboard_images: tool({ - description: `生成一组图片任务,支持图片间的依赖关系(以图生图),基于有向无环图(DAG)拓扑排序执行。 - - 参数说明: - - images: 图片任务数组 - - id: 图片唯一标识符(分镜id) - - prompt: 图片生成提示词 - - referenceIds: 依赖的参考图id数组,无依赖填空数组[] - - assetIds: 参考的资产图id数组(可选) - - 依赖规则: - 1. referenceIds中的id必须存在于images数组中 - 2. 禁止循环依赖(如A依赖B,B依赖A) - 3. 被依赖的图片会先生成,其结果作为参考图传入 - - 示例:生成猫图,再以猫图为参考生成狗图 - images: [ - {id: 1, prompt: "一只橘猫", referenceIds: [], assetIds: []}, - {id: 2, prompt: "风格相同的金毛犬", referenceIds: [1], assetIds: []} - ]`, - inputSchema: z.object({ - images: z.array( - z.object({ - id: z.number().describe("从工作区获取到的分镜id"), - prompt: z.string().describe("图片生成提示词"), - referenceIds: z.array(z.number()).describe("依赖的参考 分镜图id数组,无依赖填空数组[]"), - assetIds: z.array(z.number()).describe("参考的资产图"), - }), - ), - }), - execute: async ({ images }) => { - console.log("[tools] generate_storyboard_images", images); - const thinking = msg.thinking("正在生成 分镜图片 数据..."); - // --- 构建任务id集合 --- - const taskIds = new Set(images.map((item) => item.id)); - const imageMap = new Map(images.map((item) => [item.id, item])); - - // --- 检测循环依赖 (Kahn算法拓扑排序) --- - // 将 referenceIds 分为:本批次内依赖 vs 外部已有依赖 - // 只有本批次内的依赖才参与 DAG 调度,外部依赖直接从数据库获取 - const inDegree = new Map(); - // adjacency: 被依赖者 -> 依赖它的节点列表 - const adjacency = new Map(); - - for (const item of images) { - // 只统计本批次内的依赖作为入度 - const internalDeps = item.referenceIds.filter((refId) => taskIds.has(refId)); - inDegree.set(item.id, internalDeps.length); - for (const depId of internalDeps) { - if (!adjacency.has(depId)) adjacency.set(depId, []); - adjacency.get(depId)!.push(item.id); - } - } - - // 拓扑排序,按层级分组(同层可并行) - const levels: number[][] = []; - let queue = images.filter((item) => (inDegree.get(item.id) ?? 0) === 0).map((item) => item.id); - - const visited = new Set(); - while (queue.length > 0) { - levels.push([...queue]); - const nextQueue: number[] = []; - for (const nodeId of queue) { - visited.add(nodeId); - for (const childId of adjacency.get(nodeId) ?? []) { - inDegree.set(childId, (inDegree.get(childId) ?? 1) - 1); - if (inDegree.get(childId) === 0) { - nextQueue.push(childId); - } - } - } - queue = nextQueue; - } - // 循环依赖检测 - if (visited.size !== images.length) { - const cyclicIds = images.filter((item) => !visited.has(item.id)).map((item) => item.id); - thinking.appendText(`检测到循环依赖,涉及分镜id: ${cyclicIds.join(", ")},请修正后重试`); - thinking.updateTitle("循环依赖错误"); - thinking.error(); - return `错误:检测到循环依赖,涉及分镜id: ${cyclicIds.join(", ")}`; - } - - thinking.appendText(`图片生成调度计划:共 ${levels.length} 层,${images.length} 张图片`); - - // --- 准备公共数据 --- - const projectData = await u.db("o_project").where("id", resTool.data.projectId).select("videoRatio").first(); - const imageModelData = await u.db("o_project").where("id", resTool.data.projectId).select("imageModel", "imageQuality").first(); - - // 生成单张图片的函数 - const generateOneImage = async (item: (typeof images)[0]) => { - const thinking = msg.thinking(`正在生成分镜 id:${item.id} 图片`); - // 更新数据库状态为生成中 - await u.db("o_storyboard").where("id", item.id).update({ state: "生成中" }); - // 更新前端为生成中 - socket.emit("setFlowData", { - key: "setStoryboardImage", - value: { ...item, id: item.id, src: "", state: "生成中", referenceIds: item.referenceIds }, - }); - - // 获取参考图base64(包括资产图和已生成的分镜参考图) - const [assetsBase64, referenceBase64] = await Promise.all([ - getAssetsImageBase64(item.assetIds ?? []), - getStoryboardImageBase64(item.referenceIds), - ]); - - const imageCls = await u.Ai.Image(imageModelData.imageModel).run({ - prompt: item.prompt, - imageBase64: [...assetsBase64, ...referenceBase64], - size: imageModelData.imageQuality, - aspectRatio: (projectData?.videoRatio as `${number}:${number}`) ?? "16:9", - taskClass: "生成图片", - describe: "分镜图片生成", - relatedObjects: "hhhh", - projectId: resTool.data.projectId, - }); - - const savePath = `/${resTool.data.projectId}/storyboard/${u.uuid()}.jpg`; - await imageCls.save(savePath); - - // 更新数据库状态为已完成 - await u.db("o_storyboard").where("id", item.id).update({ state: "已完成", filePath: savePath }); - - const obj = { - ...item, - id: item.id, - src: await u.oss.getFileUrl(savePath), - state: "已完成", - referenceIds: item.referenceIds, - }; - // 前端对话框提示 - thinking.appendText(`分镜 id:${item.id} 图片生成完成`); - thinking.complete(); - // 更新前端界面展示 - socket.emit("setFlowData", { key: "setStoryboardImage", value: obj }); + const data = { + id: deriveAsset.id ?? undefined, + assetsId: deriveAsset.assetsId, + projectId, + name: deriveAsset.name, + type: deriveAsset.type, + describe: deriveAsset.desc, + startTime, }; - - // --- 按层级顺序执行:同层并行,层间串行 --- - for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) { - const levelIds = levels[levelIndex]; - const levelItems = levelIds.map((id) => imageMap.get(id)!); - const thinking = msg.thinking( - `开始生成第 ${levelIndex + 1}/${levels.length} 层,共 ${levelItems.length} 张图片 (ids: ${levelIds.join(", ")})`, - ); - - // 同层内所有图片并行生成,使用 allSettled 确保不会因单张失败中断整层 - const results = await Promise.allSettled(levelItems.map((item) => generateOneImage(item))); - - // 处理失败的任务 - for (let i = 0; i < results.length; i++) { - if (results[i].status === "rejected") { - const failedId = levelIds[i]; - const reason = (results[i] as PromiseRejectedResult).reason; - console.error(`[tools] 分镜 id:${failedId} 图片生成失败`, reason); - thinking.appendText(`分镜 id:${failedId} 图片生成失败: ${reason?.message || reason}`); - await u.db("o_storyboard").where("id", failedId).update({ state: "生成失败" }); - socket.emit("setFlowData", { - key: "setStoryboardImage", - value: { id: failedId, src: "", state: "生成失败" }, - }); - } - } - thinking.appendText(`第 ${levelIndex + 1}/${levels.length} 层图片生成完成`); - thinking.complete(); + if (deriveAsset.id) { + await u.db("o_assets").where("id", deriveAsset.id).update(data); + thinking.appendText(`已更新衍生资产,ID: ${deriveAsset.id}\n`); + } else { + const [insertedId] = await u.db("o_assets").insert(data); + data.id = insertedId; + await u.db("o_scriptAssets").insert({ scriptId, assetId: insertedId }); + thinking.appendText(`已新增衍生资产,ID: ${insertedId}\n`); } - thinking.appendText("所有分镜图片生成完成"); - thinking.updateTitle("分镜图片生成完成"); + const res = await new Promise((resolve) => socket.emit("addDeriveAsset", data, (res: any) => resolve(res))); + thinking.updateTitle("资产操作完成"); thinking.complete(); - - return "分镜图片生成完成"; + return res ?? "操作成功"; }, }), - - //todo 提示词待调 - generate_assets_images: tool({ - description: ` - 生成 资产图片 不区分原资产于衍生资产 - 参数说明: - - images: 图片任务数组 - - assetId: 资产id - - prompt: 图片生成提示词 - 示例: - images:[ - {assetId: 1, prompt: "一张猫的图片"} - ] - `, + del_deriveAsset: tool({ + description: "删除衍生资产", inputSchema: z.object({ - images: z.array( - z.object({ - assetId: z.number().describe("衍生资产id"), - prompt: z.string().describe("提示词"), - }), - ), + assetsId: z.number().describe("关联的资产ID"), + id: z.number().describe("衍生资产ID"), }), - execute: async ({ images }) => { - console.log("[tools] generate_assets_images", images); - //先获取到前端资产数据 - const flowData: FlowData = await new Promise((resolve) => socket.emit("getFlowData", { key: "assets" }, (res: any) => resolve(res))); - const assetsData = flowData["assets"]; - const assetsImage: { assetId: number; prompt: string; id?: number }[] = [...images]; - //获取对应的 原资产id - assetsImage.forEach((item) => { - for (const i of assetsData) { - const findData = i.derive.find((m) => m.id == item.assetId); - if (findData) { - item.id = findData.id; - break; - } - } - }); - //获取所设置模型 - const imageModelData = await u.db("o_project").where("id", resTool.data.projectId).select("imageModel", "imageQuality").first(); - for (const item of assetsImage) { - const [imageId] = await u.db("o_image").insert({ - // 数据库插入图片记录 - assetsId: item.assetId, - model: imageModelData?.imageModel, - state: "生成中", - resolution: imageModelData?.imageQuality, - }); - u.Ai.Image(imageModelData?.imageModel) - .run({ - prompt: item.prompt, - imageBase64: await getAssetsImageBase64(item.id ? [item.id] : []), - size: imageModelData?.imageQuality, - aspectRatio: "16:9", - taskClass: "生成图片", - describe: "资产图片生成", - relatedObjects: "hhhh", - projectId: resTool.data.projectId, - }) - .then(async (imageCls) => { - const savePath = `/${resTool.data.projectId}/assets/${u.uuid()}.jpg`; - await imageCls.save(savePath); - const obj = { - ...item, - id: item.assetId, - src: await u.oss.getFileUrl(savePath), - state: "已完成", - }; - //更新对应数据库 - await u.db("o_assets").where("id", item.assetId).update({ imageId: imageId }); - await u.db("o_image").where({ id: imageId }).update({ state: "已完成", filePath: savePath }); - //通知前端更新 - socket.emit("setFlowData", { key: "setAssetsImage", value: obj }); - }); - //通知前端更新状态 - socket.emit("setFlowData", { key: "setAssetsImage", value: { ...item, id: item.assetId, src: "", state: "生成中" } }); - } - return "资产生成中"; + execute: async ({ assetsId, id }) => { + const thinking = msg.thinking("正在操作资产..."); + const { scriptId } = resTool.data; + await u.db("o_assets").where("id", id).del(); + await u.db("o_scriptAssets").where({ scriptId, assetId: id }).del(); + thinking.appendText(`已删除衍生资产,ID: ${id}\n`); + const res = await new Promise((resolve) => socket.emit("delDeriveAsset", { assetsId, id }, (res: any) => resolve(res))); + thinking.updateTitle("资产操作完成"); + thinking.complete(); + return res ?? "删除成功"; + }, + }), + generate_deriveAsset: tool({ + description: "生成衍生资产", + inputSchema: z.object({ + id: z.number().describe("衍生资产ID"), + }), + execute: async ({ id }) => { + const thinking = msg.thinking("正在生成衍生资产..."); + const res = await new Promise((resolve) => socket.emit("generateDeriveAsset", { id }, (res: any) => resolve(res))); + thinking.appendText(`已生成衍生资产,ID: ${id}\n`); + thinking.updateTitle("衍生资产生成完成"); + thinking.complete(); + return res ?? "生成失败"; + }, + }), + generate_storyboard: tool({ + description: "生成分镜", + inputSchema: z.object({}), + execute: async ({ script }) => { + const thinking = msg.thinking("正在生成分镜..."); + const res = await new Promise((resolve) => socket.emit("generateStoryboard", { script }, (res: any) => resolve(res))); + thinking.appendText("生成的分镜数据:\n" + JSON.stringify(res, null, 2)); + thinking.updateTitle("分镜生成完成"); + thinking.complete(); + return res; }, }), }; return toolsNames ? Object.fromEntries(Object.entries(tools).filter(([n]) => toolsNames.includes(n))) : tools; }; - -// 获取资产图片base64 -async function getAssetsImageBase64(imageIds: number[]) { - if (imageIds.length === 0) return []; - const imagePaths = await u - .db("o_assets") - .leftJoin("o_image", "o_assets.imageId", "o_image.id") - .whereIn("o_assets.id", imageIds) - .select("o_assets.id", "o_image.filePath"); - if (!imagePaths.length) return []; - const imageUrls = await Promise.all( - imagePaths.map(async (i) => { - if (i.filePath) { - try { - return await urlToBase64(await u.oss.getFileUrl(i.filePath)); - } catch { - return null; - } - } else { - return null; - } - }), - ); - return imageUrls.filter(Boolean) as string[]; -} - -//获取分镜图片base64 -async function getStoryboardImageBase64(imageIds: number[]) { - if (!imageIds.length) return []; - const storayboardData = await u.db("o_storyboard").whereIn("id", imageIds).select("id", "filePath"); - const imageUrls = await Promise.all( - storayboardData.map(async (i) => { - if (i.filePath) { - try { - return await urlToBase64(await u.oss.getFileUrl(i.filePath)); - } catch { - return null; - } - } else { - return null; - } - }), - ); - return imageUrls.filter(Boolean) as string[]; -} diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index c09881f..f207906 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -130,7 +130,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => }, { key: "messagesPerSummary", - value: 3, + value: 10, }, { key: "shortTermLimit", diff --git a/src/socket/routes/productionAgent.ts b/src/socket/routes/productionAgent.ts index 611cd5f..04abc35 100644 --- a/src/socket/routes/productionAgent.ts +++ b/src/socket/routes/productionAgent.ts @@ -49,7 +49,7 @@ export default (nsp: Namespace) => { const currentController = abortController; const memory = new Memory("scriptAgent", isolationKey); - const msg = resTool.newMessage("assistant", "统筹"); + const msg = resTool.newMessage("assistant", "视频策划"); const ctx: agent.AgentContext = { socket, isolationKey, @@ -69,7 +69,7 @@ export default (nsp: Namespace) => { const persistCurrentMessage = async () => { if (!currentContent.trim()) return; await memory.add("assistant:decision", currentContent, { - name: "统筹", + name: "视频策划", createTime: new Date(currentMsg.datetime).getTime(), }); currentContent = ""; @@ -103,14 +103,6 @@ export default (nsp: Namespace) => { } }); - socket.on("setModelData", async (data: any) => { - resTool.data.imageModel = data; - }); - socket.on("setKeyScript", async (data: any) => { - isolationKey = data.key; - resTool.data.scriptId = data.scriptId; - }); - socket.on("stop", () => { abortController?.abort(); abortController = null; diff --git a/src/types/database.d.ts b/src/types/database.d.ts index b753c16..99c6f71 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,19 +1,19 @@ -// @db-hash 06e91b1ef334867ed5ea41d5a857d07a +<<<<<<< HEAD +// @db-hash 93b2462070c45c2b449e9a18c4e88763 //该文件由脚本自动生成,请勿手动修改 -export interface _o_project_old_20260328 { - 'artStyle'?: string | null; +======= +// @db-hash f7bc2fdb80756d5536929eb47155578b +//该文件由脚本自动生成,请勿手动修改 + +export interface _o_script_old_20260327 { + 'content'?: string | null; 'createTime'?: number | null; - 'id'?: number | null; - 'imageModel'?: string | null; - 'intro'?: string | null; + 'id'?: number; 'name'?: string | null; - 'projectType'?: string | null; - 'type'?: string | null; - 'userId'?: number | null; - 'videoModel'?: string | null; - 'videoRatio'?: string | null; + 'projectId'?: number | null; } +>>>>>>> 9da2610cdedc1e293b351ed3ab67fbc6fcd989f1 export interface memories { 'content': string; 'createTime': number; @@ -34,7 +34,7 @@ export interface o_agentDeploy { 'model'?: string | null; 'modelName'?: string | null; 'name'?: string | null; - 'vendorId'?: number | null; + 'vendorId'?: string | null; } export interface o_agentWorkData { 'createTime'?: number | null; @@ -60,7 +60,6 @@ export interface o_assets { 'name'?: string | null; 'projectId'?: number | null; 'prompt'?: string | null; - 'promptState'?: string | null; 'remark'?: string | null; 'scriptId'?: number | null; 'startTime'?: number | null; @@ -180,7 +179,7 @@ export interface o_storyboard { 'filePath'?: string | null; 'frameMode'?: string | null; 'id'?: number; - 'index'?: number | null; + 'index'?: string | null; 'lines'?: string | null; 'mode'?: string | null; 'model'?: string | null; @@ -191,7 +190,6 @@ export interface o_storyboard { 'sound'?: string | null; 'state'?: string | null; 'title'?: string | null; - 'videoPrompt'?: string | null; } export interface o_tasks { 'describe'?: string | null; @@ -246,7 +244,10 @@ export interface o_videoConfig { } export interface DB { - "_o_project_old_20260328": _o_project_old_20260328; +<<<<<<< HEAD +======= + "_o_script_old_20260327": _o_script_old_20260327; +>>>>>>> 9da2610cdedc1e293b351ed3ab67fbc6fcd989f1 "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData;