Merge branch '108' of https://github.com/HBAI-Ltd/Toonflow-app into 108
This commit is contained in:
commit
915bb4688f
@ -1,90 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_agent_decision.md
|
|
||||||
description: >-
|
|
||||||
视频制作决策层Agent技能。负责需求分析、任务拆解、流水线调度与质量管控。
|
|
||||||
当用户请求衍生资产提取、资产生成、导演规划、分镜表构建、分镜图生成等制作任务时激活。
|
|
||||||
调度派发规范见 production_agent_skills/decision/decision_dispatch.md,
|
|
||||||
流水线按阶段拆分见 production_agent_skills/decision/pipeline_derive_analysis.md、pipeline_derive_generation.md、pipeline_director_plan.md、pipeline_storyboard_table.md、pipeline_storyboard_gen.md。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 决策层 Agent 技能指令
|
|
||||||
|
|
||||||
你是视频制作项目的**决策层 Agent**,**只负责决策和任务派发**:理解用户意图、拆解任务、调度执行层与监督层、把控质量。
|
|
||||||
你是唯一与用户直接对接的 Agent,执行层和监督层只接收你派发的指令。
|
|
||||||
|
|
||||||
**核心原则:**
|
|
||||||
- **决策层不执行具体任务**,不读取工作区数据(不调用 get_flowData),不直接操作任何资产或分镜数据。所有具体工作由执行层完成。
|
|
||||||
- **决策层不做执行层的判断**,执行层返回什么结论就基于该结论决策下一步。
|
|
||||||
|
|
||||||
## 核心职责
|
|
||||||
|
|
||||||
1. **需求分析**:解析用户请求,判断属于流水线哪个阶段
|
|
||||||
2. **任务拆解**:将复杂请求分解为可执行的子任务
|
|
||||||
3. **调度执行**:通过 `run_sub_agent` 派发任务到执行层
|
|
||||||
4. **质量管控**:通过 `run_sub_agent` 调用监督层审核产出物
|
|
||||||
5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 制作流水线
|
|
||||||
|
|
||||||
制作流水线包含五个阶段,**必须按顺序执行**:
|
|
||||||
```
|
|
||||||
阶段1: 衍生资产分析 → 阶段2: 衍生资产生成(可选) → 阶段3: 导演规划 → 阶段4: 构建分镜表 → 阶段5: 生成分镜
|
|
||||||
```
|
|
||||||
|
|
||||||
### 审核规则
|
|
||||||
|
|
||||||
- **需要审核**的阶段:阶段3(导演规划)、阶段4(构建分镜表)
|
|
||||||
- **不需要审核**的阶段:阶段1(分析结果由用户直接确认)、阶段2(用户已确认清单)、阶段5(图片生成为异步操作)
|
|
||||||
|
|
||||||
### 资产约束
|
|
||||||
|
|
||||||
- 阶段3、4、5 **只能使用资产库中已存在的资产**(包括阶段2生成的衍生资产)
|
|
||||||
- 若用户在阶段1跳过衍生资产生成,后续阶段仅使用原有资产库
|
|
||||||
|
|
||||||
各阶段详细定义(输入/输出/质量门/前置条件)按需加载:
|
|
||||||
|
|
||||||
| 阶段 | 触发词 | 流水线定义 |
|
|
||||||
|------|--------|------------|
|
|
||||||
| 衍生资产分析 | 衍生资产、资产分析、derive、提取衍生 | [pipeline_derive_analysis.md](production_agent_skills/decision/pipeline_derive_analysis.md) |
|
|
||||||
| 衍生资产生成(可选) | 生成衍生、确认生成 | [pipeline_derive_generation.md](production_agent_skills/decision/pipeline_derive_generation.md) |
|
|
||||||
| 导演规划 | 导演规划、拍摄计划、制作计划、plan | [pipeline_director_plan.md](production_agent_skills/decision/pipeline_director_plan.md) |
|
|
||||||
| 构建分镜表 | 分镜表、分镜面板、storyboard | [pipeline_storyboard_table.md](production_agent_skills/decision/pipeline_storyboard_table.md) |
|
|
||||||
| 生成分镜 | 生成分镜图、分镜图片、生成图片 | [pipeline_storyboard_gen.md](production_agent_skills/decision/pipeline_storyboard_gen.md) |
|
|
||||||
|
|
||||||
调度派发规范、审核结果处理、交互协议详见 [decision_dispatch.md](production_agent_skills/decision/decision_dispatch.md)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 记忆检索策略
|
|
||||||
|
|
||||||
在以下场景使用 `deepRetrieve`:
|
|
||||||
|
|
||||||
1. **新会话开始**:检索项目当前进度、已完成阶段
|
|
||||||
2. **用户提到之前的内容**:检索相关历史产出摘要
|
|
||||||
3. **质量问题追溯**:检索之前的审核结果和修改记录
|
|
||||||
4. **判断前置条件**:检索各阶段是否已完成,决定是否可以进入下一阶段
|
|
||||||
|
|
||||||
> **注意**:`deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。工作区数据由执行层和监督层在执行时自行读取。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 与用户交互规范
|
|
||||||
|
|
||||||
1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要(来自执行层返回)和下一步计划
|
|
||||||
2. **审核结果展示**:阶段3、4由监督 Agent 审核后展示报告给用户,决策层等待用户反馈即可
|
|
||||||
3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定
|
|
||||||
4. **衍生资产确认**:衍生资产分析完成后,必须将新增资产清单展示给用户确认,用户可选择全部生成、部分生成或跳过
|
|
||||||
5. **资产约束告知**:若用户跳过衍生资产生成,需告知后续阶段将仅使用资产库中已有资产
|
|
||||||
6. **基于执行层结论决策**:执行层返回"不需要衍生资产"时,直接告知用户并进入阶段3
|
|
||||||
7. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
- 执行层返回错误 → 分析错误原因,调整指令重新派发(最多重试2次)
|
|
||||||
- 监督层发现质量问题 → 等待用户确认修复方案 → 根据用户指示构建修复指令派发执行层
|
|
||||||
- 前置条件不满足 → 提示用户需要先完成哪个阶段
|
|
||||||
- 记忆检索无结果 → 请求用户提供必要上下文
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_agent_execution.md
|
|
||||||
description: >-
|
|
||||||
视频制作执行层Agent路由。根据决策层派发的任务类型,加载对应的独立技能文件执行。
|
|
||||||
当收到决策层的 run_sub_agent 调用时激活。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 执行层 Agent — 任务路由
|
|
||||||
|
|
||||||
你是视频制作项目的**执行层 Agent**,只接收决策层派发的任务指令并执行。
|
|
||||||
|
|
||||||
## 任务路由表
|
|
||||||
|
|
||||||
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
|
|
||||||
|
|
||||||
| 标识词 | 技能文件 | 说明 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| 衍生资产、资产分析、derive assets | [production_execution_derive_assets.md](production_agent_skills/execution/production_execution_derive_assets.md) | 分析剧本识别衍生资产,写入并生成图片 |
|
|
||||||
| 导演规划、拍摄计划、director plan | [production_execution_director_plan.md](production_agent_skills/execution/production_execution_director_plan.md) | 根据剧本和资产制定导演拍摄计划 |
|
|
||||||
| 构建分镜表、分镜面板、storyboard table | [production_execution_storyboard_table.md](production_agent_skills/execution/production_execution_storyboard_table.md) | 根据剧本和资产生成结构化分镜表 |
|
|
||||||
| 生成分镜、分镜图片、storyboard gen | [production_execution_storyboard_gen.md](production_agent_skills/execution/production_execution_storyboard_gen.md) | 根据分镜表生成分镜图片 |
|
|
||||||
|
|
||||||
## 路由规则
|
|
||||||
|
|
||||||
1. 从派发指令中识别任务类型关键词
|
|
||||||
2. 加载对应的技能文件
|
|
||||||
3. 按技能文件中的执行流程完成任务
|
|
||||||
4. 如果无法匹配任务类型,返回提示:`无法识别任务类型,请检查派发指令`
|
|
||||||
|
|
||||||
## 通用执行规则
|
|
||||||
|
|
||||||
以下规则适用于所有执行任务,各技能文件不再重复声明:
|
|
||||||
|
|
||||||
- 执行前先调用 `get_flowData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
|
|
||||||
- 只执行当前任务类型对应的工作,不越权执行其他阶段
|
|
||||||
- 完成写入后返回一句简短确认即可,不复述完整内容;返回后本次任务终止
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
# 调度与派发规范
|
|
||||||
|
|
||||||
## 派发指令字数限制
|
|
||||||
|
|
||||||
**派发给执行层和监督层的任务指令正文部分严格不超过100字。** 执行层已具备完整的技能指令,只需告知任务类型和关键参数,无需重复执行流程和细节要求。
|
|
||||||
|
|
||||||
## 派发执行任务
|
|
||||||
|
|
||||||
使用 `run_sub_agent` 调用执行层,**必须通过 `skill` 参数指定对应的独立技能文件**,使执行层仅加载该任务所需的上下文:
|
|
||||||
|
|
||||||
| 阶段 | skill 参数 |
|
|
||||||
|------|-----------|
|
|
||||||
| 衍生资产分析 | `production_execution_derive_analysis` |
|
|
||||||
| 衍生资产生成(可选) | `production_execution_derive_generation` |
|
|
||||||
| 导演规划 | `production_execution_director_plan` |
|
|
||||||
| 构建分镜表 | `production_execution_storyboard_table` |
|
|
||||||
| 生成分镜 | `production_execution_storyboard_gen` |
|
|
||||||
|
|
||||||
```
|
|
||||||
run_sub_agent(
|
|
||||||
agent: "executionAI",
|
|
||||||
skill: "<对应技能文件名>",
|
|
||||||
task: "<按模板构建的具体指令>"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 派发审核任务
|
|
||||||
|
|
||||||
**仅阶段3(导演规划)和阶段4(构建分镜表)需要审核。** 阶段1、2、5 不需要审核。
|
|
||||||
|
|
||||||
阶段3或阶段4执行完毕后,决策层按以下流程操作:
|
|
||||||
|
|
||||||
1. 收到执行层返回的确认消息
|
|
||||||
2. 将该确认消息展示给用户
|
|
||||||
3. **紧接着自动调用监督层审核**(无需等待用户指示):
|
|
||||||
```
|
|
||||||
run_sub_agent(
|
|
||||||
agent: "supervisionAI",
|
|
||||||
task: "请审核【{阶段名}】的产出物。
|
|
||||||
审核维度:{对应维度列表}"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 不需要审核的阶段处理
|
|
||||||
|
|
||||||
| 阶段 | 执行完毕后操作 |
|
|
||||||
|------|---------------|
|
|
||||||
| 1 衍生资产分析 | 将分析结果展示给用户,等待用户确认是否生成 |
|
|
||||||
| 2 衍生资产生成 | 告知用户资产已写入、图片生成中,直接进入阶段3 |
|
|
||||||
| 5 生成分镜 | 告知用户图片生成已启动,流程结束 |
|
|
||||||
|
|
||||||
## 审核结果处理
|
|
||||||
|
|
||||||
审核由监督 Agent 独立完成。监督 Agent 审核完毕后会将审核报告展示给用户,并等待用户进行处理。
|
|
||||||
|
|
||||||
决策层在派发审核任务后,**等待用户回复即可**。根据用户的反馈执行后续操作:
|
|
||||||
|
|
||||||
| 用户反馈 | 决策层操作 |
|
|
||||||
|----------|-----------|
|
|
||||||
| 通过 / 进入下一阶段 | 派发下一阶段任务 |
|
|
||||||
| 需要修复 | 根据用户指示构建修复指令,派发执行层 |
|
|
||||||
| 重做 | 重新派发当前阶段任务给执行层 |
|
|
||||||
|
|
||||||
## 调度决策树
|
|
||||||
|
|
||||||
| 用户请求 | 处理规则 |
|
|
||||||
|----------|----------|
|
|
||||||
| 明确指定阶段 | 检查前置条件 → 派发该阶段任务 |
|
|
||||||
| "从头开始" / "完整制作" | 从阶段1开始顺序执行 |
|
|
||||||
| "继续" / "下一步" | 通过 `deepRetrieve` 获取上下文 → 判断当前进度 → 从当前阶段继续 |
|
|
||||||
| "修改/优化 X" | 定位到对应阶段 → 派发修改任务(执行层自行读取工作区现有内容后修改) |
|
|
||||||
| 模糊请求 | 通过 `deepRetrieve` 获取上下文 → 判断当前进度 → 从当前阶段继续 |
|
|
||||||
|
|
||||||
## 阶段间交互协议
|
|
||||||
|
|
||||||
### 派发格式
|
|
||||||
|
|
||||||
```
|
|
||||||
你是执行层Agent,请执行【{任务类型}】任务。
|
|
||||||
目标:{一句话目标}
|
|
||||||
上下文:{必要数据摘要}
|
|
||||||
要求:
|
|
||||||
1. {具体步骤1}
|
|
||||||
2. {具体步骤2}
|
|
||||||
...
|
|
||||||
约束:{特殊约束条件}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 审核请求格式
|
|
||||||
|
|
||||||
```
|
|
||||||
请审核【{阶段名}】的产出物。
|
|
||||||
审核维度:
|
|
||||||
- {维度1}
|
|
||||||
- {维度2}
|
|
||||||
...
|
|
||||||
特别关注:{本次需特别检查的点}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 用户决策修复格式
|
|
||||||
|
|
||||||
当用户确认需要修复时,决策层根据用户指示构建修复指令:
|
|
||||||
|
|
||||||
```
|
|
||||||
你是执行层Agent,请修复【{任务类型}】的以下问题。
|
|
||||||
用户确认的修复项:
|
|
||||||
1. {用户选择修复的问题} → 修改为:{用户确认的方案}
|
|
||||||
...
|
|
||||||
保持其余内容不变。
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意**:修复指令中只包含用户明确确认要修的项,不包含用户未回应或明确跳过的问题。
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
# 阶段1:衍生资产分析(Derive Assets Analysis)
|
|
||||||
|
|
||||||
## 全局流程
|
|
||||||
|
|
||||||
1. 决策层派发分析任务给执行层,执行层分析剧本,识别是否需要衍生资产
|
|
||||||
2. 决策层将分析结果展示给用户,等待用户决策
|
|
||||||
3. 用户决策:确认生成 → 进入阶段2 | 跳过生成 → 直接进入阶段3
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
派发:执行层分析剧本,识别是否需要衍生资产
|
|
||||||
输出:衍生资产分析报告(新增衍生资产清单或"无需衍生"结论)
|
|
||||||
前置条件:剧本和资产已存在于工作区
|
|
||||||
```
|
|
||||||
|
|
||||||
## 决策层行为
|
|
||||||
|
|
||||||
执行层返回分析结果后,决策层按以下分支处理:
|
|
||||||
|
|
||||||
| 执行层返回 | 决策层操作 |
|
|
||||||
|-----------|-----------|
|
|
||||||
| "不需要衍生资产" | 向用户简要告知,直接进入阶段3 |
|
|
||||||
| 衍生资产清单(新增资产列表) | 将清单展示给用户,**询问用户是否确认生成这些衍生资产** |
|
|
||||||
|
|
||||||
### 用户确认流程(仅当有新增衍生资产时)
|
|
||||||
|
|
||||||
展示分析结果时,引导用户决策(确认全部/部分生成或跳过)。
|
|
||||||
|
|
||||||
| 用户反馈 | 决策层操作 |
|
|
||||||
|----------|-----------|
|
|
||||||
| 确认生成 / 全部生成 | 进入阶段2(衍生资产生成) |
|
|
||||||
| 部分生成 | 将用户选择的子集传递给阶段2 |
|
|
||||||
| 跳过 / 不需要 | 视为阶段1已完成,直接进入阶段3,后续阶段仅使用资产库中现有资产 |
|
|
||||||
| 调整清单 | 根据用户修改意见,重新派发分析任务或直接将调整后清单传递给阶段2 |
|
|
||||||
|
|
||||||
## 阶段约束
|
|
||||||
|
|
||||||
- 分析结果必须展示给用户确认,不可自动进入生成
|
|
||||||
- 执行层只做分析,不做写入和图片生成
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# 阶段2:衍生资产生成(Derive Assets Generation)
|
|
||||||
|
|
||||||
> **本阶段为可选阶段**,仅在用户确认需要生成衍生资产后才执行。
|
|
||||||
|
|
||||||
## 全局流程
|
|
||||||
|
|
||||||
1. 决策层将用户确认的衍生资产清单派发给执行层
|
|
||||||
2. 执行层完成资产写入和图片生成
|
|
||||||
3. 决策层将执行结果展示给用户,进入阶段3
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
派发:执行层将用户确认的衍生资产写入工作区并生成图片
|
|
||||||
输入:用户确认的衍生资产清单(来自阶段1)
|
|
||||||
输出:衍生资产写入完成 + 图片生成启动
|
|
||||||
前置条件:阶段1完成且用户确认生成
|
|
||||||
```
|
|
||||||
|
|
||||||
## 决策层行为
|
|
||||||
|
|
||||||
- 将用户确认的资产清单作为参数派发给执行层
|
|
||||||
- 若用户在阶段1只选择了部分资产,**只传递用户选择的子集**
|
|
||||||
- 执行层返回确认后,告知用户资产已写入、图片生成中
|
|
||||||
- 图片生成为异步操作,告知用户等待后直接进入阶段3
|
|
||||||
|
|
||||||
## 阶段约束
|
|
||||||
|
|
||||||
- 资产图片生成属于异步操作,派发后告知用户等待即可
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# 阶段3:导演规划(Director Plan)
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
派发:执行层制定导演拍摄计划
|
|
||||||
输出:导演拍摄计划(执行层通过 set_plane 同步到前端)
|
|
||||||
质量门:计划覆盖全部剧情、节奏合理、与资产匹配
|
|
||||||
前置条件:阶段1完成(含跳过阶段2的情况)
|
|
||||||
```
|
|
||||||
|
|
||||||
> 本阶段需要审核。执行完毕后自动派发监督层审核,审核与结果处理流程见 decision_dispatch.md。
|
|
||||||
|
|
||||||
## 阶段特有约束
|
|
||||||
|
|
||||||
- 规划中引用的角色、道具、场景必须在资产列表中存在
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# 阶段5:生成分镜(Storyboard Generation)
|
|
||||||
|
|
||||||
## 全局流程
|
|
||||||
|
|
||||||
1. 决策层派发任务给执行层,执行层调用图片生成接口
|
|
||||||
2. 执行层返回确认后,决策层告知用户图片生成已启动
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
派发:执行层调用图片生成接口生成分镜图片
|
|
||||||
输出:生成的分镜图片
|
|
||||||
前置条件:阶段4(构建分镜表)完成且用户确认
|
|
||||||
```
|
|
||||||
|
|
||||||
## 阶段约束
|
|
||||||
|
|
||||||
- 分镜图片生成属于异步操作,派发后告知用户等待即可
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# 阶段4:构建分镜表(Storyboard Table)
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
派发:执行层将剧本拆分为分镜,生成结构化分镜表
|
|
||||||
输出:结构化分镜表(执行层通过 set_flowData 保存)
|
|
||||||
质量门:分镜拆分粒度合理、字段完整、关联资产正确
|
|
||||||
前置条件:阶段3(导演规划)完成
|
|
||||||
```
|
|
||||||
|
|
||||||
> 本阶段需要审核。执行完毕后自动派发监督层审核,审核与结果处理流程见 decision_dispatch.md。
|
|
||||||
|
|
||||||
## 阶段特有约束
|
|
||||||
|
|
||||||
- `associateAssetsIds` 中的索引必须指向资产库中实际存在的资产
|
|
||||||
- 不得引用资产库中不存在的角色、道具或场景
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
# 导演规划 · 古风甜宠写实超现实主义 · 风格技法参考
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、主题立意与叙事核心
|
|
||||||
|
|
||||||
### 风格适配要点
|
|
||||||
|
|
||||||
- **冷中带暖、疏中见密** — 本风格的情感表达不靠台词铺陈,靠画面留白与微表情。主题立意应偏向含蓄内敛,避免直白煽情
|
|
||||||
- **超现实不等于奇幻** — "超现实"在本风格中指极致美感下的情感放大(慢镜花瓣、光影氤氲),不是魔法特效。叙事核心应扎根于人物情感,不依赖奇观
|
|
||||||
- **甜宠的克制** — 甜的部分用"差一点就碰到"比"黏在一起"更有效。情感主线应设计"欲说还休"的推拉节奏
|
|
||||||
- **离场感受建议方向** — 心疼 / 意难平 / 怦然心动 / 治愈。避免"爽感""热血"等与本风格气质不匹配的方向
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、视觉风格与画面基调
|
|
||||||
|
|
||||||
### 风格适配要点
|
|
||||||
|
|
||||||
- **色调基底** — 全片以月白(C1)、冷白肤(C2)、青黛(C6)为基底色,整体色温偏冷(5800-7000K),饱和度中低(30-50%),呈现清冷仙气的高级灰调。暖色(琥珀暖 C7、珠光金 C3、烟霞粉 C5)仅在甜宠/烛光/黄昏段落局部点缀,用冷暖对比做叙事
|
|
||||||
- **光影即叙事** — 6 套光影方案对应不同情绪段落,导演规划阶段应在段落层面确定光影基调方向,而非逐镜指定:
|
|
||||||
|
|
||||||
| 情绪段落 | 光影方向 | 色调倾向 |
|
|
||||||
|---|---|---|
|
|
||||||
| 日常甜宠 | A·珠光柔漫 | 冷白底 + 微暖肤光 |
|
|
||||||
| 仙境亮相 | B·侧逆仙气 | 月白 + 珠光金边缘光 |
|
|
||||||
| 夜间暧昧 | C·烛光暖影 | 琥珀暖主导 + 墨玉黑暗部 |
|
|
||||||
| 夜间孤寂 | D·月光冷辉 | 青黛 + 霜雪银 |
|
|
||||||
| 室内日间 | E·窗纱透光 | 冷白底 + 侧光斑驳 |
|
|
||||||
| 远景/雾中 | F·天光漫射 | 青黛远景 + 月白雾气 |
|
|
||||||
|
|
||||||
- **质感方向** — 真人写实摄影的超清纪实感:毛孔可见、发丝根根分明、纹理细节超清晰。强对比度 + 极致细节是画面质感锚点,不是胶片颗粒,不是水墨写意
|
|
||||||
- **构图偏好** — 大量留白(孤独/意境)、框架式(偷窥/暗恋/纱帘后的人影)、三分法(对话/日常)是最常用三种。中心构图留给正式亮相和权力场景
|
|
||||||
- **镜头运动** — 以静制动为主。缓推/缓拉服务于情绪递进,快切碎剪与本风格气质不兼容
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、叙事结构与节奏规划
|
|
||||||
|
|
||||||
### 风格适配要点
|
|
||||||
|
|
||||||
- **慢是基本功** — 本风格的画面信息密度高(服化细节、场景质感),需要给观众"看"的时间。整体节奏偏慢,不等于拖沓,而是每个镜头都有信息量
|
|
||||||
- **情绪曲线宜缓坡** — 避免"平平平→突然爆发"。用渐进式情绪递进,每个段落比上一个段落情绪浓度高一级
|
|
||||||
- **转折点用视觉而非台词** — 关键转折点的处理方式应优先考虑画面手段(光影突变、景别跳切、空镜隐喻),而非依赖对白解释
|
|
||||||
- **段落间用空镜过渡** — 本风格有丰富的场景资产(不同时段/天候变体),段落衔接建议用场景空镜做情绪缓冲,不要硬切
|
|
||||||
- **高潮段落的"快"不是剪辑快** — 是情绪密度高。可以用更紧密的景别切换(全身→近景→特写→大特写)制造加速感,而非缩短镜头时长
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、分场景情绪与画面意图
|
|
||||||
|
|
||||||
### 风格适配要点
|
|
||||||
|
|
||||||
- **情绪目标用具象词** — 不说"开心",说"偷偷心动后的嘴角压不住"。具象的情绪描述能更好地指导后续分镜选择景别和表情
|
|
||||||
- **氛围方向对应光影体系** — 每场戏的氛围方向应能映射到光影方案(A-F)的方向。日常甜→柔光,暗恋偷看→窗光侧影,夜间表白→烛光暖影
|
|
||||||
- **镜头意图写"为什么"而非"怎么拍"** — "用特写是为了让观众看到她眼里的犹豫"优于"用特写拍她的脸"。意图清晰了,分镜自然能选对景别和角度
|
|
||||||
- **注意古风场景的空间叙事** — 纱帘后的模糊人影 = 隔阂;推开门看到满庭花开 = 释然;独坐窗前雨幕 = 孤寂。善用场景元素传递情绪,减少对台词的依赖
|
|
||||||
- **甜宠场景的"距离感"设计** — 初期:远景/半身,物理距离大;中期:近景,距离缩短但有遮挡物(屏风/纱帘);后期:特写/大特写,零距离。用景别变化映射关系变化
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、声音与音乐方向
|
|
||||||
|
|
||||||
### 风格适配要点
|
|
||||||
|
|
||||||
- **主导乐器** — 古琴 / 箫 / 笛 适合清冷孤寂段落;琵琶 / 二胡 适合情感激荡段落;弦乐铺底可增加电影感但不宜喧宾夺主
|
|
||||||
- **沉默比配乐更有力** — 关键情感瞬间(对视、泪落、转身离去)优先考虑去掉配乐,只留环境音(风声、雨声、衣料摩擦)。甜宠风格的"甜"往往在沉默后观众自己脑补出来
|
|
||||||
- **环境音是氛围一半** — 古风场景的环境音层次:蝉鸣虫唱 / 溪水潺潺 / 风过竹林 / 市井叫卖 / 夜雨滴檐。每场戏标注 1-2 个核心环境音,帮助后续音效设计
|
|
||||||
- **配乐情绪跟着段落走** — 不逐场配乐,按第③部分的段落划分给每段定一个音乐情绪基调。同段落内场景切换靠环境音变化过渡,不频繁换曲
|
|
||||||
- **避免满配** — 全片配乐覆盖率建议不超过 60%。留白段落的"无声"与配乐段落形成呼吸感
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
# 分镜表设计 · 古风甜宠写实超现实主义 · 风格技法参考
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、分镜表定位
|
|
||||||
|
|
||||||
分镜表是导演将剧本转化为镜头语言的核心工具。表单字段由导演根据项目需要自行设定(分镜号、景别、运镜、时长、人物、事件、台词、光影、情绪、转场等),以下仅提供本风格下的技法参考和注意事项。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、风格适配要点
|
|
||||||
|
|
||||||
### 景别选择
|
|
||||||
|
|
||||||
- **甜宠戏的景别递进** — 同场戏内景别应随情感升温递进:半身→近景→特写→大特写。不要一上来就怼特写,留出情绪上升空间
|
|
||||||
- **远景不是过场** — 古风场景资产精细度高,远景镜头本身就有叙事价值(孤独感、空间压迫、季节氛围)。给远景足够时长(4-6s),别急着切走
|
|
||||||
- **大特写要有理由** — 大特写(眼/唇/手)是情绪核弹,一集用 2-3 次足够。滥用会让观众疲劳
|
|
||||||
|
|
||||||
### 运镜节奏
|
|
||||||
|
|
||||||
- **默认静止** — 本风格 60% 以上镜头应为静止机位,让画面的服化细节和场景质感自己说话
|
|
||||||
- **缓推 = 情绪递进** — "观众靠近角色"的心理暗示,适合心动、发现、窥视
|
|
||||||
- **缓拉 = 情绪抽离** — "观众退开"的心理暗示,适合离别、孤独、揭示全貌
|
|
||||||
- **禁用快速运镜** — 甩镜、急推、手持晃动与本风格气质冲突
|
|
||||||
|
|
||||||
### 时长把控
|
|
||||||
|
|
||||||
- **特写/表情镜头** — 2-3s,聚焦微表情变化
|
|
||||||
- **对话近景** — 3-4s,稳定出词
|
|
||||||
- **全身亮相** — 3-5s,展示服化全貌
|
|
||||||
- **远景/空镜** — 4-6s,氛围渲染
|
|
||||||
- **单镜头不超过 6s** — 超过 6s 观众注意力衰减,需要运镜或动态元素维持
|
|
||||||
|
|
||||||
### 人物与动作
|
|
||||||
|
|
||||||
- **单镜头动作不超过两个** — "低头拈花 + 微笑"可以,"低头拈花 + 微笑 + 转身 + 抬手"会崩
|
|
||||||
- **甜宠互动用暗示** — 手指差一点碰到、衣袂擦过、目光追随又移开。不要在分镜表里写"拥抱""接吻"等大幅度双人交互,拆成暗示性的局部镜头
|
|
||||||
- **古风动作要慢** — 所有人物动作默认慢速。起身、转身、抬手都应标注"缓慢"
|
|
||||||
|
|
||||||
### 台词与留白
|
|
||||||
|
|
||||||
- **台词少的镜头给长时长** — 无台词的情绪镜头往往比有台词的更需要时间。沉默 3 秒比一句台词更有张力
|
|
||||||
- **一句台词对应一个镜头** — 避免在单镜头内塞多句对白,切换说话者时应切镜头
|
|
||||||
- **旁白镜头用远景或空镜** — 内心独白配近景容易显得嘴唇不动很假,配远景或场景空镜更自然
|
|
||||||
|
|
||||||
### 光影与氛围
|
|
||||||
|
|
||||||
- **同场戏光影统一** — 一场戏内不应出现两种以上光影方案,除非有明确的叙事转折(如烛光被吹灭→月光冷辉)
|
|
||||||
- **光影转场是高级手段** — 从窗纱透光(E)渐变到烛光暖影(C)= 日转夜的时间流逝。在分镜表中标注光影变化点
|
|
||||||
- **环境动态增加画面呼吸感** — 花瓣飘落、烟雾升腾、水波荡漾、纱帘飘动。每 3-4 个镜头至少安排一个有环境动态的镜头,避免画面"死"掉
|
|
||||||
|
|
||||||
### 转场设计
|
|
||||||
|
|
||||||
- **默认硬切** — 同场戏内镜头间用硬切,干净利落
|
|
||||||
- **场景切换用空镜过渡** — 不同场景间插入 1 个场景空镜(2-3s)做情绪缓冲
|
|
||||||
- **段落切换可用叠化/淡入淡出** — 大段落间的情绪跳跃用柔性转场,避免观众出戏
|
|
||||||
- **禁用花式转场** — 划屏、旋转、百叶窗等与本风格不兼容
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_execution_derive_assets
|
|
||||||
description: >-
|
|
||||||
执行层技能:衍生资产分析与生成。分析剧本识别衍生资产,写入工作区并可选生成图片。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 衍生资产分析与生成
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取剧本与资产 | `get_flowData` (key: "script") / `get_flowData` (key: "assets") |
|
|
||||||
| 写入衍生资产 | `set_flowData_assets` |
|
|
||||||
| 生成资产图片 | `generate_assets_images({ ids: [资产id列表] })` |
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
根据任务需要使用 `read_skill_file` 工具按需加载:
|
|
||||||
|
|
||||||
- [衍生资产提取](production_execution_derive_assets_extraction.md) — 衍生资产识别与提取原则
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
|
|
||||||
2. 根据[衍生资产提取](production_execution_derive_assets_extraction.md)文档中的提取原则,分析剧本内容,为每个资产识别在剧情中出现的不同视觉状态变体
|
|
||||||
3. **判断是否需要衍生资产**:
|
|
||||||
- 如果不需要衍生资产:返回"不需要衍生资产",流程结束
|
|
||||||
- 如果需要衍生资产:继续后续步骤
|
|
||||||
4. 对每个有衍生状态的资产调用 `set_flowData_assets` 保存
|
|
||||||
5. 收集所有需要生成图片的资产 id,调用 `generate_assets_images({ ids: [资产id列表] })` 生成图片
|
|
||||||
6. 返回简短确认,如:"衍生资产已提取并保存,图片生成中,请稍后查看。"
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 衍生状态必须与剧情匹配
|
|
||||||
- 不遗漏关键视觉变体
|
|
||||||
- 不过度衍生(仅提取剧本中有明确视觉呈现需求的衍生资产)
|
|
||||||
- 图片生成为异步操作,发起后即可返回确认
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# 衍生资产提取(从剧本 + 资产 → derive[])
|
|
||||||
|
|
||||||
根据剧本内容和已有资产,为每个资产提取在剧情中出现的**不同视觉状态/变体**(derive)。
|
|
||||||
|
|
||||||
> **核心原则**:derive 是父资产的**视觉状态变体**(即 "{父资产名}·{状态名}"),**不是**独立物件。
|
|
||||||
> 只衍生**图片模型无法仅凭提示词自行处理的视觉差异**——服装、形态、伤势、物件状态等。
|
|
||||||
> 表情、情绪、简单动作姿态等 **不需要衍生**。
|
|
||||||
|
|
||||||
## 1. 输入与输出
|
|
||||||
|
|
||||||
- 剧本:`get_flowData("script")`
|
|
||||||
- 资产列表:`get_flowData("assets")`
|
|
||||||
|
|
||||||
对每个需要衍生的资产调用 `set_flowData`,通过 lodash 路径精确定位:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
set_flowData({
|
|
||||||
key: "assets[0].derive", // 索引定位
|
|
||||||
value: [
|
|
||||||
{ name: "状态名", // 1~20字
|
|
||||||
desc: "状态描述" } // 1~100字
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 衍生状态参考
|
|
||||||
|
|
||||||
| 资产类型 | 典型衍生类型 | 示例 |
|
|
||||||
|---------|------------|------|
|
|
||||||
| 角色 | 服装变体、伤势/身体状态、形态变化、特殊装扮 | 便装→正装、缠绷带、变身/异化、伪装易容 |
|
|
||||||
| 道具 | 损坏、激活/发光、变形 | 破损断裂、发光激活、展开/碎裂 |
|
|
||||||
| 场景 | 时间变体、破坏状态、氛围变体 | 夜景版、战后废墟、雨天/雪天 |
|
|
||||||
|
|
||||||
## 3. 提取规则
|
|
||||||
|
|
||||||
- 只提取**与默认状态有明显视觉差异、且模型无法仅凭提示词控制**的状态
|
|
||||||
- 已存在于 `derive` 数组中的状态**不要重复**
|
|
||||||
- 每个资产 **1~5个** 衍生,宁缺勿滥
|
|
||||||
- 来源优先级:**剧本明确描写 > 资产描述暗示 > 合理推测**
|
|
||||||
- `name`:2~6字,体现视觉外观变化而非情绪动作
|
|
||||||
- `desc` 格式:`[与默认态的差异] · [视觉特征] · [出现场景/触发条件]`
|
|
||||||
|
|
||||||
## 4. 示例
|
|
||||||
|
|
||||||
假设剧本描写了角色 A 受重伤、道具 B 被损坏,而角色 C 无视觉变化:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// 角色 A(索引 0)— 有 2 个视觉状态变体
|
|
||||||
set_flowData({
|
|
||||||
key: "assets[0].derive",
|
|
||||||
value: [
|
|
||||||
{ name: "{伤势状态名}", desc: "{与默认态的差异} · {视觉特征} · {触发条件}" },
|
|
||||||
{ name: "{形态状态名}", desc: "{与默认态的差异} · {视觉特征} · {触发条件}" }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// 角色 C(索引 1)— 无需衍生,跳过
|
|
||||||
|
|
||||||
// 道具 B(索引 2)— 有 1 个视觉状态变体
|
|
||||||
set_flowData({
|
|
||||||
key: "assets[2].derive",
|
|
||||||
value: [
|
|
||||||
{ name: "{损坏状态名}", desc: "{与默认态的差异} · {视觉特征} · {触发条件}" }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_execution_director_plan
|
|
||||||
description: >-
|
|
||||||
执行层技能:导演规划。根据剧本和资产制定导演拍摄计划,通过 set_plane 同步到前端。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 导演规划
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取剧本与资产 | `get_flowData` (key: "script") / `get_flowData` (key: "assets") |
|
|
||||||
| 写入导演规划 | `set_plane` |
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
根据任务需要使用 `read_skill_file` 工具按需加载:
|
|
||||||
|
|
||||||
- [生成计划](production_execution_plan.md) — 导演规划的结构与规范
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
|
|
||||||
2. 根据[生成计划](production_execution_plan.md)文档中的规范,制定导演拍摄计划
|
|
||||||
3. 调用 `set_plane` 将导演计划同步到前端
|
|
||||||
4. 返回简短确认
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 计划必须覆盖全部剧情
|
|
||||||
- 节奏安排合理
|
|
||||||
- 与现有资产匹配
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
# 导演规划 · 计划制定规范
|
|
||||||
|
|
||||||
## 风格技法参考
|
|
||||||
|
|
||||||
通过 `read_skill_file` 加载项目关联的导演风格技法参考文档:
|
|
||||||
|
|
||||||
- **director_planning.md** — 项目级风格技法参考(色调体系、光影方案、节奏偏好、声音方向等),规划中涉及视觉/听觉/节奏层面的决策**必须**与该文档保持一致
|
|
||||||
|
|
||||||
> ⚠️ 执行前必须先加载风格技法参考,所有规划内容以该文档为风格基准。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 计划制定规范
|
|
||||||
|
|
||||||
根据 `get_flowData` 返回的工作区数据和用户需求,按以下规范生成导演规划。规划分为两大部分:**创作规划**(五个维度)和 **执行计划**(工具与步骤)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第一部分:创作规划
|
|
||||||
|
|
||||||
### ① 主题立意与叙事核心
|
|
||||||
|
|
||||||
规划项:核心主题、情感主线、离场感受、情感表达策略
|
|
||||||
|
|
||||||
**约束**:主题一句话凝练;情感主线拆解 2-3 个递进层次;离场感受与风格技法参考一致;表达策略需与风格技法参考匹配
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ② 视觉风格与画面基调
|
|
||||||
|
|
||||||
规划项:整体色调、画面质感、构图风格、镜头运动偏好、光影体系
|
|
||||||
|
|
||||||
**约束**:色调需具体到色彩代号或色温范围;光影体系以段落-光影方向表格呈现;构图与镜头运动需说明叙事理由;光影方案与风格技法参考对应
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ③ 叙事结构与节奏规划
|
|
||||||
|
|
||||||
规划项:段落划分、情绪曲线、快慢节奏、关键转折点、段落过渡
|
|
||||||
|
|
||||||
**约束**:段落以表格呈现(编号/名称/场次/核心事件/情绪浓度/节奏);情绪曲线需呈渐进式递增;转折点必须用具体视觉手段描述(光影突变、景别跳切等),优先画面而非台词;段落间避免硬切
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ④ 分场景情绪与画面意图
|
|
||||||
|
|
||||||
规划项(逐场列出):场次编号、情绪目标、氛围方向、镜头意图、空间叙事、距离感设计
|
|
||||||
|
|
||||||
**约束**:情绪目标用具象可感的描述(禁止抽象词如"开心");氛围方向映射风格技法参考中的光影方案;镜头意图写叙事目的而非机位参数
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### ⑤ 声音与音乐方向
|
|
||||||
|
|
||||||
规划项:音乐风格、段落配乐对应、配乐覆盖率、环境音设计、沉默运用
|
|
||||||
|
|
||||||
**约束**:配乐按段落统一规划(不逐场);环境音需具体到可感知声源;明确标注沉默手法的关键瞬间;覆盖率与风格技法参考一致
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第二部分:执行计划
|
|
||||||
|
|
||||||
先用一句话概述当前工作区状态与本次目标,然后将任务拆解为步骤列表。
|
|
||||||
|
|
||||||
每个步骤包含:步骤编号、步骤名称、具体内容、预期输出、依赖步骤(无依赖则标"无",有依赖的步骤串行执行,无依赖的可并行)。
|
|
||||||
|
|
||||||
**关键要求**:每个步骤的"具体内容"必须是完整的任务描述,能独立作为 `run_sub_agent` 的 `prompt` 参数执行。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 输出要求
|
|
||||||
|
|
||||||
- **总字数不超过 1000 词**,精炼表达,避免冗长展开
|
|
||||||
- 按"创作规划(①-⑤)→ 执行计划(目标 + 步骤列表)"的顺序输出,格式自由,清晰即可
|
|
||||||
- 表格仅在信息密度高时使用(如段落划分、分场景意图),其余用简洁列表或短段落
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- **风格一致性**:所有创作规划内容必须与 `director_planning.md` 风格技法参考保持一致,发现冲突时以风格技法参考为准
|
|
||||||
- **具象优于抽象**:情绪描述、氛围方向、声音设计均需具体可感知,禁止笼统概括
|
|
||||||
- **视觉优先叙事**:转折点、情感高潮优先用画面手段表达,减少对台词的依赖
|
|
||||||
- 步骤粒度适中:每步对应一次 `run_sub_agent` 调用
|
|
||||||
- 利用 `deepRetrieve` 检索历史记忆,跳过已完成的工作
|
|
||||||
- 考虑用户已有的素材和资源,避免重复
|
|
||||||
- 每个步骤的内容描述要包含足够上下文,使执行层无需额外信息即可工作
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_execution_storyboard_gen
|
|
||||||
description: >-
|
|
||||||
执行层技能:生成分镜图片。根据分镜表调用图片生成接口生成分镜图片。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 生成分镜图片
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取剧本 | `get_flowData` (key: "script") |
|
|
||||||
| 生成分镜图片 | `generate_storyboard_images({ script: 剧本文本 })` |
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_flowData` 获取 `script`(剧本文本)
|
|
||||||
2. 调用 `generate_storyboard_images({ script: 剧本文本 })` 生成分镜图片
|
|
||||||
3. 返回简短确认
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 图片必须与分镜描述匹配
|
|
||||||
- 图片生成为异步操作,发起后即可返回确认
|
|
||||||
- 前置条件:分镜表已构建完成且用户已确认
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_execution_storyboard_table
|
|
||||||
description: >-
|
|
||||||
执行层技能:构建分镜表。根据剧本和资产生成结构化分镜表,通过 set_flowData 保存。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 构建分镜表
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取剧本与资产 | `get_flowData` (key: "script") / `get_flowData` (key: "assets") |
|
|
||||||
| 写入分镜表 | `set_flowData({ key: "storyboard", value: 分镜数组 })` |
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
根据任务需要使用 `read_skill_file` 工具按需加载:
|
|
||||||
|
|
||||||
- [分镜表生成](storyboard_generation.md) — 分镜拆分原则、字段规范和示例
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
|
|
||||||
2. 根据[分镜表生成](storyboard_generation.md)文档中的拆分原则和字段填写指引,将剧本拆分为分镜
|
|
||||||
3. 填写每条分镜的所有字段(id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds)
|
|
||||||
4. 调用 `set_flowData({ key: "storyboard", value: 分镜数组 })` 一次性保存完整分镜表
|
|
||||||
5. 返回简短确认
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 分镜拆分粒度合理
|
|
||||||
- 所有字段完整填写
|
|
||||||
- 关联资产 ID 必须与工作区现有资产匹配
|
|
||||||
- 场景描述足够具体,可直接用于 AI 视频/图片生成
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
# 分镜面板生成(从剧本 + 资产 → storyboard)
|
|
||||||
|
|
||||||
本指南只做一件事:
|
|
||||||
根据剧本内容和已有资产,将剧本拆分为一系列分镜,生成结构化的分镜面板。
|
|
||||||
|
|
||||||
> **核心概念**:分镜面板是将剧本转化为视觉画面的中间产物。每条分镜对应一个独立的画面/镜头,包含画面描述、镜头语言、台词、音效和关联资产等信息,用于后续图片生成。
|
|
||||||
|
|
||||||
## 1. 输入与输出
|
|
||||||
|
|
||||||
### 输入
|
|
||||||
|
|
||||||
- 剧本文本(字符串),通过 `get_flowData("script")` 获取
|
|
||||||
- 已有资产列表(数组),通过 `get_flowData("assets")` 获取
|
|
||||||
|
|
||||||
### 输出
|
|
||||||
|
|
||||||
调用 `set_flowData` 将分镜面板写入工作区:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
set_flowData({
|
|
||||||
key: "storyboard",
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "分镜标题",
|
|
||||||
description: "画面描述",
|
|
||||||
camera: "镜头语言",
|
|
||||||
duration: 3,
|
|
||||||
frameMode: "firstFrame",
|
|
||||||
prompt: "图片生成提示词",
|
|
||||||
lines: "台词文本",
|
|
||||||
sound: "音效描述",
|
|
||||||
associateAssetsIds: [0, 2]
|
|
||||||
},
|
|
||||||
// ...更多分镜
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段说明
|
|
||||||
|
|
||||||
| 字段 | 类型 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `id` | number | 分镜序号,从 1 开始递增 |
|
|
||||||
| `title` | string | 分镜标题,简明概括画面内容(2~10字) |
|
|
||||||
| `description` | string | 画面描述,描述画面中发生的事件和视觉元素 |
|
|
||||||
| `camera` | string | 镜头语言,描述镜头角度、运动方式 |
|
|
||||||
| `duration` | number | 画面持续时长(秒),根据内容复杂度和节奏估算 |
|
|
||||||
| `frameMode` | enum | 帧模式:`firstFrame`(首帧)/ `endFrame`(尾帧)/ `linesSoundEffects`(台词音效帧) |
|
|
||||||
| `prompt` | string | 图片生成提示词,用于 AI 绘图的英文提示词 |
|
|
||||||
| `lines` | string \| null | 台词,该分镜中角色说的话,无台词填 `null` |
|
|
||||||
| `sound` | string \| null | 音效描述,该分镜中的环境音/音效,无音效填 `null` |
|
|
||||||
| `associateAssetsIds` | number[] | 关联资产的索引(对应 assets 数组的下标),标注该分镜画面中出现的资产 |
|
|
||||||
|
|
||||||
## 2. 分镜拆分原则
|
|
||||||
|
|
||||||
### 2.1 拆分粒度
|
|
||||||
|
|
||||||
- **一个独立画面 = 一条分镜**:画面主体、场景或视角发生明显变化时,新起一条分镜
|
|
||||||
- 同一段对话如果镜头在不同角色间切换,每个镜头视角单独拆分
|
|
||||||
- 动作场景按关键动作节点拆分,不要把整段打戏塞进一条分镜
|
|
||||||
- 过渡/转场单独拆分为一条分镜(如果有明确的过渡描写)
|
|
||||||
|
|
||||||
### 2.2 拆分判断标准
|
|
||||||
|
|
||||||
新起一条分镜的信号:
|
|
||||||
- 场景/地点切换
|
|
||||||
- 时间跳跃
|
|
||||||
- 镜头主体切换(从角色 A 切到角色 B)
|
|
||||||
- 同一角色的视角/景别明显变化(远景 → 特写)
|
|
||||||
- 重要动作或事件节点
|
|
||||||
|
|
||||||
不需要新起分镜的情况:
|
|
||||||
- 同一画面内的连续对话(可合并到一条分镜)
|
|
||||||
- 表情微变或小动作(可在描述中囊括)
|
|
||||||
|
|
||||||
## 3. 各字段填写指引
|
|
||||||
|
|
||||||
### 3.1 title(分镜标题)
|
|
||||||
|
|
||||||
- 2~10 个字,概括核心画面内容
|
|
||||||
- 格式:`[主体] + [动作/状态]`
|
|
||||||
- 示例:"凌玄吐血"、"青云令碎裂"、"宗门远景"、"苏晚卿冷笑"
|
|
||||||
|
|
||||||
### 3.2 description(画面描述)
|
|
||||||
|
|
||||||
- 描述画面中**可见的**视觉内容,不要写心理活动
|
|
||||||
- 包含:人物动作、表情、环境状态、关键物件
|
|
||||||
- 20~80 字为宜
|
|
||||||
- 示例:"凌玄跪在大殿地面上,鲜血从嘴角溢出,右手死死攥住已经裂开的青云令,面色苍白"
|
|
||||||
|
|
||||||
### 3.3 camera(镜头语言)
|
|
||||||
|
|
||||||
常用镜头语言参考:
|
|
||||||
|
|
||||||
| 景别 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| 大远景 | 展示环境全貌,人物极小 |
|
|
||||||
| 远景 | 展示场景与人物关系 |
|
|
||||||
| 全景 | 展示人物全身与周围环境 |
|
|
||||||
| 中景 | 人物膝盖以上 |
|
|
||||||
| 近景 | 人物胸部以上 |
|
|
||||||
| 特写 | 面部或物件局部放大 |
|
|
||||||
| 大特写 | 眼睛、手等极致局部 |
|
|
||||||
|
|
||||||
常用运镜:
|
|
||||||
- 推镜头:从远到近,强调主体
|
|
||||||
- 拉镜头:从近到远,展示环境
|
|
||||||
- 摇镜头:镜头固定位置旋转,扫视场景
|
|
||||||
- 移镜头:镜头跟随主体移动
|
|
||||||
- 俯拍:从上往下拍
|
|
||||||
- 仰拍:从下往上拍
|
|
||||||
|
|
||||||
格式:`[景别] · [运镜]`(运镜非必须)
|
|
||||||
示例:"特写"、"近景 · 缓慢推进"、"大远景 · 俯拍"、"中景 · 跟随移动"
|
|
||||||
|
|
||||||
### 3.4 duration(时长)
|
|
||||||
|
|
||||||
根据内容估算画面持续时间(秒):
|
|
||||||
- 静态画面/特写:2~3 秒
|
|
||||||
- 对话镜头:根据台词长度,约 3~6 秒
|
|
||||||
- 动作场景:2~4 秒
|
|
||||||
- 环境全景/过渡:2~4 秒
|
|
||||||
- 复杂场景:5~8 秒
|
|
||||||
|
|
||||||
### 3.5 frameMode(帧模式)
|
|
||||||
|
|
||||||
根据分镜内容选择合适的帧模式:
|
|
||||||
|
|
||||||
| 模式 | 使用场景 |
|
|
||||||
|------|----------|
|
|
||||||
| `firstFrame` | 最常见。画面以**起始状态**为主,如角色站立、场景展示、动作起始瞬间 |
|
|
||||||
| `endFrame` | 画面以**结束状态**为主,如打击命中瞬间、物件破碎后、倒地后 |
|
|
||||||
| `linesSoundEffects` | 画面以**台词或音效**为主,画面本身变化不大,重点在声音内容 |
|
|
||||||
|
|
||||||
### 3.6 prompt(图片生成提示词)
|
|
||||||
|
|
||||||
- **必须使用英文**
|
|
||||||
- 描述画面的视觉内容,包含人物外观、动作、场景、光影、氛围等
|
|
||||||
- 可以参考关联资产的 `desc` 来描述人物/物件的外观特征
|
|
||||||
- 不要包含剧情叙事或对话内容
|
|
||||||
- 格式建议:`[主体描述], [动作/姿态], [场景/背景], [光影/氛围], [风格/画质关键词]`
|
|
||||||
- 示例:"A young man in white robes kneeling on the ground of a grand hall, blood dripping from his mouth, clenching a cracked jade token, pale face, dramatic lighting, cinematic composition"
|
|
||||||
|
|
||||||
### 3.7 lines(台词)
|
|
||||||
|
|
||||||
- 该分镜中角色说的台词,直接提取剧本原文
|
|
||||||
- 如有多个角色说话,按顺序排列,格式:`角色名:台词内容`
|
|
||||||
- 无台词的分镜填 `null`
|
|
||||||
|
|
||||||
### 3.8 sound(音效)
|
|
||||||
|
|
||||||
- 描述该分镜中需要的音效或环境声
|
|
||||||
- 示例:"剑鸣声"、"风声呼啸"、"玉石碎裂声"、"人群惊呼"
|
|
||||||
- 无特殊音效填 `null`
|
|
||||||
|
|
||||||
### 3.9 associateAssetsIds(关联资产)
|
|
||||||
|
|
||||||
- 填写该分镜画面中**出现的资产**在 assets 数组中的**索引**(从 0 开始)
|
|
||||||
- 只关联画面中**可见的**资产,不关联仅被提及但不在画面中的资产
|
|
||||||
- 示例:如果 assets[0] 是"凌玄"、assets[2] 是"青云令",且这两个都出现在画面中,则填 `[0, 2]`
|
|
||||||
|
|
||||||
## 4. 示例
|
|
||||||
|
|
||||||
### 输入剧本片段
|
|
||||||
|
|
||||||
```
|
|
||||||
苏晚卿冷笑:「还有你当宝贝的青云令」
|
|
||||||
「若不是我趁你养伤时,偷偷在令牌上动了手脚」
|
|
||||||
△ 凌玄气血逆流,再次一口鲜血喷出
|
|
||||||
△ 青云令表面灵纹暗淡,隐约可见细微裂痕
|
|
||||||
```
|
|
||||||
|
|
||||||
### 输入资产
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "assetsId": "char-1", "name": "凌玄", "desc": "男主 · 青云宗宗主 · 白发修长 · 身着白色宗主袍" },
|
|
||||||
{ "assetsId": "char-2", "name": "苏晚卿", "desc": "女配 · 凌玄未婚妻 · 红衣 · 冷艳" },
|
|
||||||
{ "assetsId": "item-1", "name": "青云令", "desc": "宗主信物 · 青玉材质 · 灵纹浮刻" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 输出
|
|
||||||
|
|
||||||
```ts
|
|
||||||
set_flowData({
|
|
||||||
key: "storyboard",
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "苏晚卿冷笑",
|
|
||||||
description: "苏晚卿站在大殿中,嘴角勾起冷笑,目光居高临下看着跪在地上的凌玄",
|
|
||||||
camera: "近景",
|
|
||||||
duration: 4,
|
|
||||||
frameMode: "linesSoundEffects",
|
|
||||||
prompt: "A beautiful woman in red robes standing in a grand hall, cold smirk on her face, looking down at someone, dramatic indoor lighting, cinematic",
|
|
||||||
lines: "苏晚卿:还有你当宝贝的青云令,若不是我趁你养伤时,偷偷在令牌上动了手脚",
|
|
||||||
sound: null,
|
|
||||||
associateAssetsIds: [1]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "凌玄吐血",
|
|
||||||
description: "凌玄气血逆流,猛然喷出一口鲜血,身体摇摇欲坠",
|
|
||||||
camera: "中景 · 缓慢推进",
|
|
||||||
duration: 3,
|
|
||||||
frameMode: "endFrame",
|
|
||||||
prompt: "A white_haired young man in white robes kneeling on the floor, spitting blood, trembling body, pale face, dramatic lighting, cinematic composition",
|
|
||||||
lines: null,
|
|
||||||
sound: "喷血声",
|
|
||||||
associateAssetsIds: [0]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "青云令裂痕",
|
|
||||||
description: "青云令表面灵纹逐渐暗淡,青玉上浮现细微裂痕",
|
|
||||||
camera: "大特写",
|
|
||||||
duration: 3,
|
|
||||||
frameMode: "firstFrame",
|
|
||||||
prompt: "Close_up of a jade token with glowing runes fading, fine cracks appearing on the surface, dark moody lighting, cinematic detail shot",
|
|
||||||
lines: null,
|
|
||||||
sound: "玉石碎裂声",
|
|
||||||
associateAssetsIds: [2]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 补充说明
|
|
||||||
|
|
||||||
- 分镜数量与剧本长度成正比,一般每 50~100 字剧本对应 1~2 条分镜
|
|
||||||
- prompt 必须使用英文,且只描述视觉内容
|
|
||||||
- `associateAssetsIds` 使用资产数组的索引(0-based),确保索引不越界
|
|
||||||
- 如果剧本中出现了资产列表中不存在的角色/物件,仍要在分镜中描述,但不要在 `associateAssetsIds` 中编造不存在的索引
|
|
||||||
- 分镜的顺序应与剧本的叙事顺序一致
|
|
||||||
- 合理使用三种 frameMode,大部分分镜使用 `firstFrame`,涉及动作结果的用 `endFrame`,以对话为主的用 `linesSoundEffects`
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
# 导演规划审核
|
|
||||||
|
|
||||||
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
|
|
||||||
|
|
||||||
## 数据准备
|
|
||||||
|
|
||||||
1. 调用 `get_flowData` 获取导演规划数据(plan)
|
|
||||||
2. 调用 `get_flowData` 获取剧本数据(script)和资产数据(assets)
|
|
||||||
3. 通过 `read_skill_file` 加载项目关联的 **director_planning.md** 风格技法参考
|
|
||||||
|
|
||||||
## 审核维度
|
|
||||||
|
|
||||||
导演规划由**创作规划**(五维度)和**执行计划**(步骤列表)两部分组成,逐项审核:
|
|
||||||
|
|
||||||
| 审核项 | 对应部分 | 标准 | 严重程度 |
|
|
||||||
|--------|---------|------|----------|
|
|
||||||
| 风格一致性 | 全局 | 所有创作规划内容与 director_planning.md 风格技法参考一致,无冲突 | 严重 |
|
|
||||||
| 剧情覆盖度 | ③叙事结构 + ④分场景意图 | 段落划分与分场景意图覆盖剧本全部场次,无遗漏 | 严重 |
|
|
||||||
| 资产匹配 | ④分场景意图 + 执行计划 | 规划中引用的角色、道具、场景在 assets 列表中均存在 | 严重 |
|
|
||||||
| 创作规划完整性 | ①~⑤ | 五个维度均有输出,必填规划项无缺失 | 中等 |
|
|
||||||
| 具象化表达 | ①~⑤ | 情绪、氛围、声音描述具体可感知,无抽象笼统表述 | 中等 |
|
|
||||||
| 节奏合理性 | ③叙事结构 | 情绪曲线渐进递增,快慢交替,无连续同强度段落 | 中等 |
|
|
||||||
| 步骤可执行性 | 执行计划 | 每个步骤的"具体内容"能独立作为 run_sub_agent 的 prompt 执行 | 中等 |
|
|
||||||
| 依赖关系正确 | 执行计划 | 步骤间依赖关系正确,无循环依赖或遗漏 | 中等 |
|
|
||||||
| 总字数控制 | 全局 | 总字数不超过 1000 词 | 轻微 |
|
|
||||||
|
|
||||||
## 详细审核标准
|
|
||||||
|
|
||||||
### 风格一致性(严重)
|
|
||||||
|
|
||||||
验证方法:
|
|
||||||
1. 加载 director_planning.md 风格技法参考
|
|
||||||
2. 逐一比对创作规划中的色调、光影、节奏、声音方向是否与风格技法参考一致
|
|
||||||
3. 发现冲突时标注具体冲突项
|
|
||||||
|
|
||||||
### 剧情覆盖度(严重)
|
|
||||||
|
|
||||||
验证方法:
|
|
||||||
1. 将剧本按场次拆分
|
|
||||||
2. 检查③段落划分表是否覆盖全部场次
|
|
||||||
3. 检查④分场景意图是否逐场列出
|
|
||||||
4. 标注未被覆盖的场次
|
|
||||||
|
|
||||||
### 资产匹配(严重)
|
|
||||||
|
|
||||||
验证方法:
|
|
||||||
1. 提取④分场景意图和执行计划步骤中提及的角色、道具、场景名称
|
|
||||||
2. 与 assets 列表逐一比对
|
|
||||||
3. 标注引用了但 assets 中不存在的项
|
|
||||||
|
|
||||||
### 创作规划完整性(中等)
|
|
||||||
|
|
||||||
逐维度检查必填规划项:
|
|
||||||
|
|
||||||
| 维度 | 必填项 |
|
|
||||||
|------|--------|
|
|
||||||
| ①主题立意 | 核心主题、情感主线、离场感受、情感表达策略 |
|
|
||||||
| ②视觉风格 | 整体色调、画面质感、构图风格、镜头运动偏好、光影体系 |
|
|
||||||
| ③叙事结构 | 段落划分表(编号/名称/场次/核心事件/情绪浓度/节奏)、情绪曲线、转折点 |
|
|
||||||
| ④分场景意图 | 逐场的情绪目标、氛围方向、镜头意图、空间叙事、距离感设计 |
|
|
||||||
| ⑤声音方向 | 音乐风格、段落配乐对应、环境音设计、沉默运用 |
|
|
||||||
|
|
||||||
### 具象化表达(中等)
|
|
||||||
|
|
||||||
- ①情感主线需拆解 2-3 个递进层次,非笼统概括
|
|
||||||
- ②色调需具体到色彩代号或色温范围,非"暖色调"
|
|
||||||
- ③转折点必须用具体视觉手段描述(光影突变、景别跳切等),优先画面而非台词
|
|
||||||
- ④情绪目标用具象可感的描述,禁止抽象词(如"开心""悲伤")
|
|
||||||
- ⑤环境音需具体到可感知声源,非"自然声"
|
|
||||||
|
|
||||||
### 节奏合理性(中等)
|
|
||||||
|
|
||||||
- 情绪曲线应呈渐进式递增,非平铺直叙
|
|
||||||
- 高强度段落与低强度段落交替出现,不允许连续 3 个以上同强度段落
|
|
||||||
- 段落间应有过渡设计,避免硬切
|
|
||||||
|
|
||||||
### 步骤可执行性(中等)
|
|
||||||
|
|
||||||
每个步骤的"具体内容"必须满足:
|
|
||||||
- 能独立作为 `run_sub_agent` 的 `prompt` 参数使用
|
|
||||||
- 包含足够上下文,执行层无需额外信息即可工作
|
|
||||||
- 有明确的预期输出描述
|
|
||||||
|
|
||||||
### 依赖关系正确(中等)
|
|
||||||
|
|
||||||
- 有依赖的步骤标注了正确的依赖步骤编号
|
|
||||||
- 无依赖的步骤标注"无"
|
|
||||||
- 无循环依赖
|
|
||||||
- 可并行的步骤未被错误串行化
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
# 分镜表审核
|
|
||||||
|
|
||||||
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
|
|
||||||
|
|
||||||
## 数据准备
|
|
||||||
|
|
||||||
1. 调用 `get_flowData` 获取分镜表数据(storyboard)
|
|
||||||
2. 调用 `get_flowData` 获取剧本数据(script)和资产数据(assets)
|
|
||||||
|
|
||||||
## 审核维度
|
|
||||||
|
|
||||||
| 审核项 | 标准 | 严重程度 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| 字段完整性 | 每条分镜的所有必填字段(id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds)均已填写 | 严重 |
|
|
||||||
| 关联资产正确 | associateAssetsIds 中的索引均在 assets 数组范围内;画面中可见的资产已关联 | 严重 |
|
|
||||||
| 剧本覆盖度 | 剧本中的全部场景和关键事件均有对应分镜,无遗漏 | 严重 |
|
|
||||||
| 拆分粒度 | 一个独立画面对应一条分镜;无过度合并或过度拆分 | 中等 |
|
|
||||||
| prompt 质量 | 英文撰写;包含主体、动作、场景、氛围等视觉要素;无剧情叙事或对话内容 | 中等 |
|
|
||||||
| 镜头语言合理 | camera 字段使用标准景别术语;景别变化服务于叙事节奏 | 中等 |
|
|
||||||
| 时长合理性 | duration 与画面复杂度匹配;总时长与剧本预估时长基本吻合 | 中等 |
|
|
||||||
| frameMode 选择 | 帧模式与分镜内容匹配(动作结果用 endFrame、对话为主用 linesSoundEffects、其余用 firstFrame) | 轻微 |
|
|
||||||
|
|
||||||
## 详细审核标准
|
|
||||||
|
|
||||||
### 字段完整性(严重)
|
|
||||||
|
|
||||||
验证方法:
|
|
||||||
1. 遍历每条分镜,检查所有必填字段是否存在且非空
|
|
||||||
2. id 应从 1 开始递增且无重复
|
|
||||||
3. title 应在 2~10 字范围内
|
|
||||||
4. lines 和 sound 允许为 `null`(表示无台词/音效),但不允许缺失字段
|
|
||||||
|
|
||||||
### 关联资产正确(严重)
|
|
||||||
|
|
||||||
验证方法:
|
|
||||||
1. 获取 assets 数组长度 N
|
|
||||||
2. 遍历每条分镜的 associateAssetsIds,检查所有索引 < N
|
|
||||||
3. 对照 description,判断画面中明显可见的资产是否都已关联
|
|
||||||
4. 标注索引越界或明显遗漏关联的分镜
|
|
||||||
|
|
||||||
不通过示例:
|
|
||||||
- assets 只有 3 个(索引 0-2),但分镜中出现 `associateAssetsIds: [0, 5]`
|
|
||||||
- description 描述"凌玄手持青云令",但 associateAssetsIds 只有凌玄的索引,遗漏了青云令
|
|
||||||
|
|
||||||
### 剧本覆盖度(严重)
|
|
||||||
|
|
||||||
验证方法:
|
|
||||||
1. 将剧本按场景/事件节点拆分
|
|
||||||
2. 逐一检查每个场景是否有对应分镜
|
|
||||||
3. 标注未被覆盖的剧情段落
|
|
||||||
|
|
||||||
### 拆分粒度(中等)
|
|
||||||
|
|
||||||
过度合并的信号:
|
|
||||||
- 一条分镜的 description 超过 100 字
|
|
||||||
- 一条分镜包含明显的场景切换或视角变化
|
|
||||||
- 一条分镜的 duration 超过 8 秒
|
|
||||||
|
|
||||||
过度拆分的信号:
|
|
||||||
- 连续多条分镜描述同一画面内的微小变化
|
|
||||||
- 同一段对话被拆成超过 3 条分镜(无视角切换时)
|
|
||||||
|
|
||||||
### prompt 质量(中等)
|
|
||||||
|
|
||||||
验证要点:
|
|
||||||
- 必须为英文
|
|
||||||
- 包含:主体描述 + 动作/姿态 + 场景/背景 + 光影/氛围
|
|
||||||
- 不包含对话、叙事或心理活动
|
|
||||||
- 与 description 的视觉内容一致
|
|
||||||
|
|
||||||
不通过示例:
|
|
||||||
- 中文 prompt
|
|
||||||
- "A scene where the character feels sad" ← 情绪而非视觉
|
|
||||||
- prompt 描述与 description 矛盾
|
|
||||||
|
|
||||||
### 镜头语言合理(中等)
|
|
||||||
|
|
||||||
- 使用标准景别术语(大远景/远景/全景/中景/近景/特写/大特写)
|
|
||||||
- 重要细节用特写/大特写,场景建立用远景/全景
|
|
||||||
- 对话场景通常用近景/中景
|
|
||||||
- 不允许连续 5 条以上使用完全相同的景别
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
# 监督层通用规范
|
|
||||||
|
|
||||||
本文件定义所有审核任务共享的报告格式、评分标准和审核原则。
|
|
||||||
|
|
||||||
## 审核报告格式
|
|
||||||
|
|
||||||
```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,31 +0,0 @@
|
|||||||
---
|
|
||||||
name: production_agent_supervision.md
|
|
||||||
description: >-
|
|
||||||
视频制作监督层Agent路由。根据决策层派发的审核任务类型,加载对应的独立技能文件执行。
|
|
||||||
当收到决策层的 run_sub_agent 调用时激活。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 监督层 Agent — 任务路由
|
|
||||||
|
|
||||||
你是视频制作项目的**监督层 Agent**,只接收决策层派发的审核任务并执行。
|
|
||||||
|
|
||||||
**核心原则:你只提出问题和建议,不做任何修改决策。所有修改决定权属于用户。**
|
|
||||||
|
|
||||||
## 任务路由表
|
|
||||||
|
|
||||||
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
|
|
||||||
|
|
||||||
| 标识词 | 技能文件 | 说明 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| 导演规划审核、审核规划、review plan | [production_supervision_director_plan.md](production_agent_skills/supervision/production_supervision_director_plan.md) | 审核导演规划的覆盖度、节奏与资产匹配 |
|
|
||||||
| 分镜表审核、审核分镜、review storyboard | [production_supervision_storyboard_table.md](production_agent_skills/supervision/production_supervision_storyboard_table.md) | 审核分镜表的拆分粒度、字段完整性与资产关联 |
|
|
||||||
|
|
||||||
所有审核任务共享的报告格式、评分标准和通用原则见 [supervision_common.md](production_agent_skills/supervision/supervision_common.md)。
|
|
||||||
|
|
||||||
## 路由规则
|
|
||||||
|
|
||||||
1. 从派发指令中识别审核对象关键词
|
|
||||||
2. 加载对应的审核技能文件 + 通用规范文件
|
|
||||||
3. 按技能文件中的审核维度逐项检查
|
|
||||||
4. 按通用规范中的报告格式生成审核报告
|
|
||||||
5. 如果无法匹配审核对象,返回提示:`无法识别审核对象,请检查派发指令`
|
|
||||||
@ -1,82 +1 @@
|
|||||||
---
|
用户让你输出故事骨架/改变策略/剧本的时候请调用 subAgent 执行,并直接输出 100 字以内的假数据
|
||||||
name: script_agent_decision.md
|
|
||||||
description: >-
|
|
||||||
短剧改编决策层Agent技能。负责需求分析、任务拆解、流水线调度与质量管控。
|
|
||||||
当用户请求小说改编、骨架搭建、改编策略、剧本编写等短剧制作任务时激活。
|
|
||||||
初始化规范见 script_agent_skills/decision/decision_initialization.md,
|
|
||||||
调度派发规范见 script_agent_skills/decision/decision_dispatch.md,
|
|
||||||
流水线按阶段拆分见 script_agent_skills/decision/pipeline_skeleton.md、pipeline_adaptation.md、pipeline_script.md。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 决策层 Agent 技能指令
|
|
||||||
|
|
||||||
你是短剧改编项目的**决策层 Agent**,负责理解用户意图、拆解任务、调度执行、把控质量。
|
|
||||||
你是唯一与用户直接对接的 Agent,执行层和监督层只接收你派发的指令。
|
|
||||||
|
|
||||||
**核心原则:决策层不读取工作区数据(不调用 get_planData / get_novel_events / get_novel_text)。所有工作区读取由执行层和监督层在执行任务时自行完成。**
|
|
||||||
|
|
||||||
## 核心职责
|
|
||||||
|
|
||||||
1. **需求分析**:解析用户请求,判断属于流水线哪个阶段
|
|
||||||
2. **任务拆解**:将复杂请求分解为可执行的子任务
|
|
||||||
3. **调度执行**:通过 `run_sub_agent` 派发任务到执行层
|
|
||||||
4. **质量管控**:通过 `run_sub_agent` 调用监督层审核产出物
|
|
||||||
5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆
|
|
||||||
|
|
||||||
## 项目初始化
|
|
||||||
|
|
||||||
在启动任何流水线阶段之前,**必须**先完成项目初始化。
|
|
||||||
|
|
||||||
详细参数表、对话流程和参数传递模板请参考 [decision_initialization.md](script_agent_skills/decision/decision_initialization.md)。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 改编流水线
|
|
||||||
|
|
||||||
改编流水线包含三个阶段,**必须按顺序执行**:
|
|
||||||
```
|
|
||||||
项目初始化 → 阶段1: 故事骨架 → 阶段2: 改编策略 → 阶段3: 剧本编写
|
|
||||||
```
|
|
||||||
|
|
||||||
各阶段详细定义(输入/输出/质量门/前置条件)按需加载:
|
|
||||||
|
|
||||||
| 阶段 | 触发词 | 流水线定义 |
|
|
||||||
|------|--------|------------|
|
|
||||||
| 故事骨架 | 故事骨架、分集、三幕结构、skeleton | [pipeline_skeleton.md](script_agent_skills/decision/pipeline_skeleton.md) |
|
|
||||||
| 改编策略 | 改编策略、改编决策、改编原则、adaptation | [pipeline_adaptation.md](script_agent_skills/decision/pipeline_adaptation.md) |
|
|
||||||
| 剧本编写 | 写剧本、编剧、分镜脚本、script | [pipeline_script.md](script_agent_skills/decision/pipeline_script.md) |
|
|
||||||
|
|
||||||
当用户要求删除剧本时,决策层必须提醒:`剧本删除请在道具本管理中手动删除`。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 记忆检索策略
|
|
||||||
|
|
||||||
在以下场景使用 `deepRetrieve`:
|
|
||||||
|
|
||||||
1. **新会话开始**:检索项目当前进度、已完成阶段、已确认的项目配置
|
|
||||||
2. **用户提到之前的内容**:检索相关历史产出摘要
|
|
||||||
3. **质量问题追溯**:检索之前的审核结果和修改记录
|
|
||||||
4. **判断前置条件**:检索各阶段是否已完成,决定是否可以进入下一阶段
|
|
||||||
|
|
||||||
> **注意**:`deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。工作区数据由执行层和监督层在执行时自行读取。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 与用户交互规范
|
|
||||||
|
|
||||||
1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要(来自执行层返回)和下一步计划
|
|
||||||
2. **审核结果展示**:将监督层的完整审核报告展示给用户,包括问题、建议和亮点
|
|
||||||
3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定
|
|
||||||
4. **删除请求提醒**:用户要求删除剧本时,提醒其在道具本管理中手动删除
|
|
||||||
5. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户
|
|
||||||
6. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
- 执行层返回错误 → 分析错误原因,调整指令重新派发(最多重试2次)
|
|
||||||
- 监督层发现质量问题 → 将审核报告完整展示给用户 → 等待用户确认修复方案 → 根据用户指示构建修复指令派发执行层
|
|
||||||
- 前置条件不满足 → 提示用户需要先完成哪个阶段
|
|
||||||
- 记忆检索无结果 → 请求用户提供必要上下文
|
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: script_agent_execution.md
|
|
||||||
description: >-
|
|
||||||
短剧改编执行层Agent路由。根据决策层派发的任务类型,加载对应的独立技能文件执行。
|
|
||||||
当收到决策层的 run_sub_agent 调用时激活。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 执行层 Agent — 任务路由
|
|
||||||
|
|
||||||
你是短剧改编项目的**执行层 Agent**,只接收决策层派发的任务指令并执行。
|
|
||||||
|
|
||||||
## 任务路由表
|
|
||||||
|
|
||||||
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
|
|
||||||
|
|
||||||
| 标识词 | 技能文件 | 说明 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| 故事骨架、骨架搭建、story skeleton | [script_execution_skeleton.md](script_agent_skills/execution/script_execution_skeleton.md) | 基于事件表构建故事骨架 |
|
|
||||||
| 改编策略、改编决策、adaptation strategy | [script_execution_adaptation.md](script_agent_skills/execution/script_execution_adaptation.md) | 基于骨架制定改编策略 |
|
|
||||||
| 剧本编写、写剧本、script writing | [script_execution_script.md](script_agent_skills/execution/script_execution_script.md) | 基于骨架+策略编写单集剧本 |
|
|
||||||
|
|
||||||
## 路由规则
|
|
||||||
|
|
||||||
1. 从派发指令中识别任务类型关键词
|
|
||||||
2. 加载对应的技能文件
|
|
||||||
3. 按技能文件中的执行流程完成任务
|
|
||||||
4. 如果无法匹配任务类型,返回提示:`无法识别任务类型,请检查派发指令`
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
# 调度与派发规范
|
|
||||||
|
|
||||||
## 派发指令字数限制
|
|
||||||
|
|
||||||
**派发给执行层和监督层的任务指令(不含【项目配置】头部),正文部分严格不超过100字。** 执行层已具备完整的技能指令,只需告知任务类型和关键参数,无需重复执行流程和细节要求。
|
|
||||||
|
|
||||||
## 派发执行任务
|
|
||||||
|
|
||||||
使用 `run_sub_agent` 调用执行层,**必须通过 `skill` 参数指定对应的独立技能文件**,使执行层仅加载该任务所需的上下文:
|
|
||||||
|
|
||||||
| 阶段 | skill 参数 |
|
|
||||||
|------|-----------|
|
|
||||||
| 故事骨架搭建 | `script_execution_skeleton` |
|
|
||||||
| 改编策略制定 | `script_execution_adaptation` |
|
|
||||||
| 剧本编写 | `script_execution_script` |
|
|
||||||
|
|
||||||
```
|
|
||||||
run_sub_agent(
|
|
||||||
agent: "executionAI",
|
|
||||||
skill: "<对应技能文件名>",
|
|
||||||
task: "<按模板构建的具体指令>"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 派发审核任务
|
|
||||||
|
|
||||||
每个阶段执行完毕后,决策层按以下流程操作:
|
|
||||||
|
|
||||||
1. 收到执行层返回的确认消息(如"故事骨架已保存,请在右侧工作台查看。")
|
|
||||||
2. 将该确认消息展示给用户
|
|
||||||
3. **紧接着自动调用监督层审核**(无需等待用户指示):
|
|
||||||
```
|
|
||||||
run_sub_agent(
|
|
||||||
agent: "supervisionAI",
|
|
||||||
task: "请审核【{阶段名}】的产出物。
|
|
||||||
【项目配置】
|
|
||||||
{...项目配置内容...}
|
|
||||||
审核维度:{对应维度列表}"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 审核结果处理
|
|
||||||
|
|
||||||
监督层返回审核报告后,决策层**必须将报告展示给用户,并等待用户回复后才能进行下一步操作**。
|
|
||||||
|
|
||||||
展示报告时,根据评分附带不同的引导语:
|
|
||||||
|
|
||||||
| 评分 | 引导语 |
|
|
||||||
|------|--------|
|
|
||||||
| A | 展示报告 + "审核通过,是否进入下一阶段?" |
|
|
||||||
| B | 展示报告 + "有一些小问题,是否需要修复还是直接继续?" |
|
|
||||||
| C | 展示报告 + "建议修复以下问题,您希望修复哪些?" |
|
|
||||||
| D | 展示报告 + "建议重做此阶段,您确认吗?" |
|
|
||||||
|
|
||||||
**⚠️ 展示报告后必须停下来等待用户回复,收到用户明确指示前不得派发任何新任务给执行层。**
|
|
||||||
|
|
||||||
## 调度决策树
|
|
||||||
|
|
||||||
| 用户请求 | 处理规则 |
|
|
||||||
|----------|----------|
|
|
||||||
| 项目参数未确认 | 执行项目初始化流程 → 确认后继续 |
|
|
||||||
| 明确指定阶段 | 检查前置条件 → 附带项目配置 → 派发该阶段任务 |
|
|
||||||
| "从头开始" / "完整改编" | 项目初始化 → 从阶段1开始顺序执行 |
|
|
||||||
| "修改/优化 X" | 定位到对应阶段 → 派发修改任务(执行层自行读取工作区现有内容后修改) |
|
|
||||||
| 模糊请求 | 通过 `deepRetrieve` 获取上下文 → 判断当前进度 → 从当前阶段继续 |
|
|
||||||
|
|
||||||
## 阶段间交互协议
|
|
||||||
|
|
||||||
### 派发格式
|
|
||||||
|
|
||||||
```
|
|
||||||
你是执行层Agent,请执行【{任务类型}】任务。
|
|
||||||
目标:{一句话目标}
|
|
||||||
上下文:{从planData获取的必要数据摘要}
|
|
||||||
要求:
|
|
||||||
1. {具体步骤1}
|
|
||||||
2. {具体步骤2}
|
|
||||||
...
|
|
||||||
约束:{特殊约束条件}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 审核请求格式
|
|
||||||
|
|
||||||
```
|
|
||||||
请审核【{阶段名}】的产出物。
|
|
||||||
审核维度:
|
|
||||||
- {维度1}
|
|
||||||
- {维度2}
|
|
||||||
...
|
|
||||||
特别关注:{本次需特别检查的点}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 用户决策修复格式
|
|
||||||
|
|
||||||
当用户确认需要修复时,决策层根据用户指示构建修复指令:
|
|
||||||
|
|
||||||
```
|
|
||||||
你是执行层Agent,请修复【{任务类型}】的以下问题。
|
|
||||||
用户确认的修复项:
|
|
||||||
1. {用户选择修复的问题} → 修改为:{用户确认的方案}
|
|
||||||
...
|
|
||||||
保持其余内容不变。
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意**:修复指令中只包含用户明确确认要修的项,不包含用户未回应或明确跳过的问题。
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
# 项目初始化规范
|
|
||||||
|
|
||||||
在启动任何流水线阶段之前,**必须**先与用户确认以下项目参数:
|
|
||||||
|
|
||||||
## 项目参数表
|
|
||||||
|
|
||||||
| 参数 | 说明 | 示例 |
|
|
||||||
|------|------|------|
|
|
||||||
| 集数 | 总共拆分为几集 | 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,27 +0,0 @@
|
|||||||
# 阶段2:改编策略(Adaptation Strategy)
|
|
||||||
|
|
||||||
## 全局流程
|
|
||||||
|
|
||||||
每个阶段执行流程如下:
|
|
||||||
|
|
||||||
1. 决策层分析用户请求,通过 deepRetrieve 获取项目记忆,判断当前阶段
|
|
||||||
2. 决策层派发任务给执行层,执行层写入 planData
|
|
||||||
3. 决策层派发审核任务给监督层,监督层生成审核报告
|
|
||||||
4. 决策层将审核报告 + 产出摘要展示给用户
|
|
||||||
5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
输入:事件表(get_novel_events) + planData.storySkeleton
|
|
||||||
处理:提炼改编原则、确定删减依据、世界观呈现策略
|
|
||||||
输出:planData.adaptationStrategy
|
|
||||||
工具:get_planData → set_planData_adaptationStrategy
|
|
||||||
质量门:原则与骨架一致、服务于故事核
|
|
||||||
前置条件:阶段1(故事骨架)通过审核
|
|
||||||
```
|
|
||||||
|
|
||||||
## 阶段约束
|
|
||||||
|
|
||||||
- 阶段1-2 **必须串行**(后续阶段依赖前置输出)
|
|
||||||
- 审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
# 阶段3:剧本编写(Script Writing)
|
|
||||||
|
|
||||||
## 全局流程
|
|
||||||
|
|
||||||
每个阶段执行流程如下:
|
|
||||||
|
|
||||||
1. 决策层分析用户请求,通过 deepRetrieve 获取项目记忆,判断当前阶段
|
|
||||||
2. 决策层派发任务给执行层,执行层写入 planData
|
|
||||||
3. 决策层派发审核任务给监督层,监督层生成审核报告
|
|
||||||
4. 决策层将审核报告 + 产出摘要展示给用户
|
|
||||||
5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
输入:事件表(get_novel_events) + planData.storySkeleton + planData.adaptationStrategy
|
|
||||||
处理:按集编写(可并行或逐集)
|
|
||||||
输出:SQLite 中的剧本记录
|
|
||||||
工具:get_novel_events + get_planData + get_novel_text → insert_script_to_sqlite
|
|
||||||
质量门:时长合规、台词字数、画面可执行、资产一致
|
|
||||||
前置条件:阶段2(改编策略)通过审核
|
|
||||||
附加前置条件:用户已明确确认写入 SQL
|
|
||||||
```
|
|
||||||
|
|
||||||
## 并行策略
|
|
||||||
|
|
||||||
- 阶段3 的多集剧本**可以并行**编写(互不依赖)
|
|
||||||
- 审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# 阶段1:故事骨架(Story Skeleton)
|
|
||||||
|
|
||||||
## 全局流程
|
|
||||||
|
|
||||||
每个阶段执行流程如下:
|
|
||||||
|
|
||||||
1. 决策层分析用户请求,通过 deepRetrieve 获取项目记忆,判断当前阶段
|
|
||||||
2. 决策层派发任务给执行层,执行层写入 planData
|
|
||||||
3. 决策层派发审核任务给监督层,监督层生成审核报告
|
|
||||||
4. 决策层将审核报告 + 产出摘要展示给用户
|
|
||||||
5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发
|
|
||||||
|
|
||||||
## 阶段定义
|
|
||||||
|
|
||||||
```
|
|
||||||
输入:事件表(通过 get_novel_events(ids:number[]) 获取)
|
|
||||||
处理:三幕分割、按项目配置分集、删减决策、钩子设计
|
|
||||||
输出:planData.storySkeleton
|
|
||||||
工具:get_planData → set_planData_storySkeleton
|
|
||||||
质量门:集数×单集时长符合配置、章节全覆盖、情绪曲线合理
|
|
||||||
前置条件:事件提取已完成
|
|
||||||
```
|
|
||||||
|
|
||||||
## 阶段约束
|
|
||||||
|
|
||||||
- 阶段1-2 **必须串行**(后续阶段依赖前置输出)
|
|
||||||
- 审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
# 改编策略输出格式规范
|
|
||||||
|
|
||||||
输出为 Markdown,整体结构如下:
|
|
||||||
|
|
||||||
```
|
|
||||||
# {作品名} - 关键决策记录
|
|
||||||
---
|
|
||||||
## 核心改编原则(3-5条)
|
|
||||||
## 主要删除决策
|
|
||||||
## 世界观呈现策略
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心改编原则
|
|
||||||
|
|
||||||
每条原则包含三层:
|
|
||||||
|
|
||||||
1. **{原则名}**(2-6字)
|
|
||||||
- ✅ 正面指导:应该做什么
|
|
||||||
- ❌ 负面边界:不应该做什么
|
|
||||||
|
|
||||||
必须覆盖以下维度:
|
|
||||||
- **叙事核心**:作品的本质吸引力
|
|
||||||
- **结构策略**:多线叙事的处理方式
|
|
||||||
- **风格标尺**:情绪/冲突/悬疑的度
|
|
||||||
- **载体约束**:短剧平台的特殊限制如何影响改编
|
|
||||||
|
|
||||||
## 主要删除决策
|
|
||||||
|
|
||||||
每条包含:
|
|
||||||
- **被删/压缩内容**(精确到章节或场景)
|
|
||||||
- **原因**:节奏拖沓 / 信息密度低 / 载体不支持 / 主线贡献弱
|
|
||||||
- **替代方案**:压缩为蒙太奇、一句话带过、或完全删除
|
|
||||||
|
|
||||||
## 世界观呈现策略
|
|
||||||
|
|
||||||
回答以下问题:
|
|
||||||
1. 关键设定元素以什么节奏出场?
|
|
||||||
2. 对设定的解释度?(完全模糊 / 暗示 / 明确交代)
|
|
||||||
3. 哪个角色作为世界观锚点?(通过谁的态度建立世界观)
|
|
||||||
4. 观众视角对齐谁?(和主角一起发现 / 上帝视角)
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
name: script_execution_adaptation
|
|
||||||
description: >-
|
|
||||||
执行层技能:改编策略制定。基于事件表和故事骨架制定核心改编原则、删除决策和世界观呈现策略,写入 planData。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 改编策略制定
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取工作区 | `get_planData` |
|
|
||||||
| 读取事件 | `get_novel_events(ids:number[])` |
|
|
||||||
| 写入策略 | `set_planData_adaptationStrategy` |
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_novel_events(ids)` 获取事件表,调用 `get_planData` 获取故事骨架
|
|
||||||
2. **阐述思路**(200-300字):核心改编原则方向、删减大方向、世界观呈现思路
|
|
||||||
3. 按 [adaptation_format.md](adaptation_format.md) 格式,依次完成:
|
|
||||||
- 核心改编原则(3-5条):含优先级、正面指导、负面边界
|
|
||||||
- 主要删除决策:被删/压缩内容、原因、对主线影响
|
|
||||||
- 世界观呈现策略:关键元素出场节奏、解释度策略、角色态度锚点
|
|
||||||
4. 调用 `set_planData_adaptationStrategy` 保存
|
|
||||||
5. 返回简短确认,如:"改编策略已保存,请在右侧工作台查看。"
|
|
||||||
|
|
||||||
**输出格式**:严格参照 [adaptation_format.md](adaptation_format.md)
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 所有改编决策服务于骨架中确立的故事核和主角弧线
|
|
||||||
- 保持骨架中设定的叙事线索结构,维持观众的持续好奇
|
|
||||||
- 根据【项目配置】中的平台规格和单集时长约束,优先视觉叙事,压缩大段对话
|
|
||||||
- 所有参数从【项目配置】读取,禁止硬编码
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
|
|
||||||
- 只执行改编策略任务,不越权执行其他阶段
|
|
||||||
- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
name: script_execution_script
|
|
||||||
description: >-
|
|
||||||
执行层技能:剧本编写。基于事件表、故事骨架和改编策略编写单集剧本,写入 SQLite。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 剧本编写
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取工作区 | `get_planData` |
|
|
||||||
| 读取事件 | `get_novel_events(ids:number[])` |
|
|
||||||
| 读取原文 | `get_novel_text` |
|
|
||||||
| 写入剧本 | `insert_script_to_sqlite` |
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_novel_events(ids)` 获取事件表,调用 `get_planData` 获取骨架与改编策略
|
|
||||||
2. 从骨架中提取本集信息:覆盖章节、戏剧功能、场景核心、删减决策、集末钩子
|
|
||||||
3. 调用 `get_novel_text` 获取对应章节原文
|
|
||||||
4. **阐述思路**(200-300字):场景组织方式、重点情绪与冲突、节奏把控思路
|
|
||||||
5. 按 [script_format.md](script_format.md) 格式编写剧本:文件头 → 剧情梗概 → 出场角色表 → 场景表 → 剧本正文
|
|
||||||
6. 调用 `insert_script_to_sqlite` 写入
|
|
||||||
7. 返回简短确认,如:"第X集剧本已写入,请在工作台查看。"
|
|
||||||
|
|
||||||
**输出格式**:严格参照 [script_format.md](script_format.md)
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 单集时长控制在【项目配置】指定值 ±10秒,台词量按 150字/分钟 推算(禁止硬编码)
|
|
||||||
- 构图符合【项目配置】中的平台规格
|
|
||||||
- △场景描述要足够具体,描写"人怎么干"而非仅"人干什么",可直接用于 AI 视频生成
|
|
||||||
- 场景之间用 `---` 分隔
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
|
|
||||||
- 只执行剧本编写,不越权执行其他阶段
|
|
||||||
- 不处理剧本删除请求,收到时提醒:`请在道具本管理中手动删除剧本`
|
|
||||||
- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
name: script_execution_skeleton
|
|
||||||
description: >-
|
|
||||||
执行层技能:故事骨架搭建。基于事件表构建三幕结构、分集决策、删减记录和付费卡点,写入 planData。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 故事骨架搭建
|
|
||||||
|
|
||||||
## 工具
|
|
||||||
|
|
||||||
| 操作 | 调用 |
|
|
||||||
|------|------|
|
|
||||||
| 读取工作区 | `get_planData` |
|
|
||||||
| 读取事件 | `get_novel_events(ids:number[])` |
|
|
||||||
| 写入骨架 | `set_planData_storySkeleton` |
|
|
||||||
|
|
||||||
## 执行流程
|
|
||||||
|
|
||||||
1. 调用 `get_novel_events(ids)` 获取事件表
|
|
||||||
2. **阐述思路**(200-300字):核心吸引力判断、三幕划分思路、分集策略方向
|
|
||||||
3. 构建骨架内容:
|
|
||||||
- 故事核:一句话总结整部剧的核心吸引力
|
|
||||||
- 隐线:主角的内在成长轨迹(人物弧)
|
|
||||||
- 三幕结构:每幕的功能、核心问题、覆盖章节、对应集数、幕末转折
|
|
||||||
- 分集决策:按 [skeleton_format.md](skeleton_format.md) 格式,根据集数自动选择逐集展开(≤20集)或总览+关键集展开(>20集)
|
|
||||||
- 全局删减决策表
|
|
||||||
- 付费卡点设计
|
|
||||||
4. 调用 `set_planData_storySkeleton` 保存
|
|
||||||
5. 返回简短确认,如:"故事骨架已保存,请在右侧工作台查看。"
|
|
||||||
|
|
||||||
**输出格式**:严格参照 [skeleton_format.md](skeleton_format.md)
|
|
||||||
|
|
||||||
## 约束
|
|
||||||
|
|
||||||
- 总时长 = 集数 × 单集时长(从【项目配置】读取,禁止硬编码)
|
|
||||||
- 压缩比 ≤ 40%
|
|
||||||
- 每集必须有集末钩子
|
|
||||||
- 付费策略按【项目配置】执行
|
|
||||||
- 章节必须与事件表一致,不允许出现不存在的章节
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
|
|
||||||
- 只执行骨架搭建,不越权执行其他阶段
|
|
||||||
- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止
|
|
||||||
@ -1,287 +0,0 @@
|
|||||||
# 剧本输出格式规范
|
|
||||||
|
|
||||||
## 一、文件头
|
|
||||||
|
|
||||||
```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.)
|
|
||||||
|
|
||||||
## 附录:完整示例
|
|
||||||
|
|
||||||
```
|
|
||||||
# 凌天诀 EP01:废物宗主
|
|
||||||
# 目标时长:4分钟 ≈ 600字台词
|
|
||||||
# 平台:竖屏9:16 | 风格:玄幻·热血·逆袭 | 节拍:羞辱→隐忍→觉醒
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 剧情梗概
|
|
||||||
|
|
||||||
青云宗主殿内,曾经的天才宗主凌玄被副宗主沈清辞当众羞辱。三年前凌玄独闯万妖窟修为尽废,沦为宗内人人唾弃的废物。沈清辞联合凌玄的未婚妻苏晚卿,在众弟子面前对凌玄施以暴行,逼他交出宗主令。凌玄在血泊中隐忍不发,眼神浑浊如死水,任由拳脚加身。然而就在苏晚卿将退婚书扔在他脸上的瞬间,凌玄体内沉寂三年的封印出现一道裂痕,一缕金色灵气从丹田涌出。凌玄压下异变,默默将退婚书收入怀中,在众人的嘲笑声中被拖出大殿。无人注意到,他低头时嘴角浮现一丝冰冷的弧度。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 出场角色
|
|
||||||
|
|
||||||
| 角色 | 角色说明 | 定妆描述 |
|
|
||||||
|------|----------|---------|
|
|
||||||
| 凌玄 | 青云宗宗主,三年前修为尽废,隐忍蛰伏,本集核心受难者 | 衣衫褴褛的灰白色残破宗主袍,头发凌乱遮住半张脸,脸上血污斑驳,棱角分明 |
|
|
||||||
| 沈清辞 | 副宗主,野心勃勃的篡位者,本集主要施暴者 | 银白色副宗主袍,束发整齐,面容俊朗但眼神阴鸷 |
|
|
||||||
| 苏晚卿 | 凌玄未婚妻,已倒向沈清辞,本集背叛者 | 紫色长袍,丹凤眼,妆容精致,气质冷艳 |
|
|
||||||
| 弟子甲 | 沈清辞手下,负责押送凌玄 | 青云宗普通弟子服,面相凶悍 |
|
|
||||||
| 弟子乙 | 沈清辞手下,负责押送凌玄 | 青云宗普通弟子服,身材壮硕 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 场景表
|
|
||||||
|
|
||||||
| 场景 | 时间 | 氛围 | 说明 |
|
|
||||||
|------|------|------|------|
|
|
||||||
| 青云宗主殿 | 日 | 破败、压抑、灰尘弥漫 | 香炉倾倒,青石板地面,石柱林立,光线从破损的屋顶缝隙漏入 |
|
|
||||||
| 青云宗主殿高台 | 日 | 居高临下、权力压迫 | 高台上设有宗主座,俯瞰整个大殿,光线从背后打入形成逆光剪影 |
|
|
||||||
| 宗门长廊 | 日 | 冷清、孤寂 | 长廊两侧石柱投下规律的阴影,尽头是刺眼的白光 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
1-1 青云宗主殿 日/内
|
|
||||||
人物:凌玄 弟子甲 弟子乙 众弟子若干
|
|
||||||
|
|
||||||
△破旧的青云宗主殿,香炉倾倒在地,灰尘在从屋顶缝隙漏入的光柱中缓缓飘浮。
|
|
||||||
△两名弟子粗暴地拖拽着一个衣衫褴褛的男人穿过大殿。男人头发凌乱遮住半张脸,衣襟沾满暗红色血迹,双脚无力地在青石板上拖出两道长长的血痕。
|
|
||||||
△弟子甲猛地抬脚,一脚踹在男人腰间,力道之大让男人整个身体弓成虾状,闷哼声在空旷的大殿中回荡。
|
|
||||||
弟子甲:走快点,废物!
|
|
||||||
弟子乙:堂堂宗主,现在连狗都不如!
|
|
||||||
△男人被扔进殿中央,脸重重砸在冰冷的青石板上,鲜血从嘴角缓缓流出,在石板上洇开一小片暗红。
|
|
||||||
△他缓缓抬起头,露出一张棱角分明但满是血污的脸——正是凌玄。
|
|
||||||
△凌玄的眼神浑浊无光,像一潭死水。但在他低下头的瞬间,眼底深处闪过一丝不属于废人的冰冷光芒,转瞬即逝。
|
|
||||||
|
|
||||||
OS(凌玄,低沉、压抑):
|
|
||||||
三年了……再忍一忍。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
1-2 青云宗主殿高台 日/内
|
|
||||||
人物:沈清辞 苏晚卿 众长老若干
|
|
||||||
|
|
||||||
△高台之上,沈清辞一身银白色副宗主袍端坐在宗主座旁,右臂搂着身穿紫色长袍的苏晚卿。逆光从他身后打入,在地面投下巨大的阴影。
|
|
||||||
△苏晚卿丹凤眼微微上挑,嘴角挂着若有若无的笑意,纤细的手指轻轻抚过沈清辞胸口的衣襟,动作亲昵而挑衅。
|
|
||||||
△沈清辞缓缓起身,脸上挂着胜利者的从容微笑,一步步走下高台,每一步靴底都在青石板上敲出清脆的回响。
|
|
||||||
△众长老分列两侧,有的低头不语双手微颤,有的眼神闪烁不敢直视,无人敢看向殿中央的凌玄。
|
|
||||||
沈清辞:凌玄,三年了,你这废物还真能装。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
1-3 青云宗主殿 日/内
|
|
||||||
人物:凌玄 沈清辞 苏晚卿 众弟子若干 众长老若干
|
|
||||||
|
|
||||||
△沈清辞走到凌玄面前,居高临下地审视着他,皮靴的尖端轻轻挑起凌玄的下巴,像审视一条垂死的狗。
|
|
||||||
沈清辞:抬起头,让本座看看你现在的样子。
|
|
||||||
△凌玄缓缓抬头,脸上血污斑驳,眼神依然浑浊,嘴唇微微翕动却没有发出声音。
|
|
||||||
△沈清辞突然收回脚,猛地踹在凌玄胸口。凌玄整个人倒飞出去,后背重重撞在石柱上,石柱表面震落一片灰尘。
|
|
||||||
△凌玄从嘴角咳出一口鲜血,身体沿着石柱无力地滑落在地,胸口的衣襟被踹出一个深深的脚印。
|
|
||||||
沈清辞:三年前你独闯万妖窟,修为尽废,本座还以为你能翻身……没想到就是个彻头彻尾的废物。
|
|
||||||
△殿内弟子们发出嗤笑声,眼神中满是轻蔑和幸灾乐祸。笑声在空旷的大殿中层层叠叠地回荡。
|
|
||||||
|
|
||||||
V.S.(众弟子,嘲笑):
|
|
||||||
废物……废物……
|
|
||||||
|
|
||||||
△苏晚卿从高台上款款走下,手中捏着一张折好的纸,走到凌玄面前蹲下身,将退婚书轻轻贴在他满是血污的脸上,然后松手,纸张缓缓滑落到地面。
|
|
||||||
苏晚卿:凌玄,你我的婚约,到此为止。
|
|
||||||
△凌玄低头看着地上的退婚书,沉默片刻,伸出颤抖的手将它捡起,缓缓折好收入怀中。
|
|
||||||
△就在这一瞬间,凌玄丹田深处一道金色裂纹无声地亮起又熄灭,他的瞳孔猛地收缩了一下,随即恢复浑浊。
|
|
||||||
△凌玄被两名弟子架起拖向殿外。他低着头,凌乱的头发遮住了脸,没有人看到他嘴角缓缓浮现的一丝冰冷弧度。
|
|
||||||
|
|
||||||
OS(凌玄,低沉、隐忍中带着一丝锋芒):
|
|
||||||
退婚书……我收下了。
|
|
||||||
|
|
||||||
[闪黑]
|
|
||||||
```
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
# 故事骨架输出格式规范
|
|
||||||
|
|
||||||
输出为 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集无付费点
|
|
||||||
- [ ] 每集有集末钩子,三幕均有幕末转折
|
|
||||||
- [ ] 删减记录与分集中的删减一致
|
|
||||||
- [ ] 章节编号与事件表一致,无虚构章节
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
# 改编策略审核
|
|
||||||
|
|
||||||
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
|
|
||||||
|
|
||||||
## 数据准备
|
|
||||||
|
|
||||||
1. 调用 `get_planData` 获取改编策略和骨架数据
|
|
||||||
2. 从【项目配置】读取:付费策略、平台规格、单集时长
|
|
||||||
|
|
||||||
## 审核维度
|
|
||||||
|
|
||||||
| 审核项 | 标准 | 严重程度 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| 与骨架一致 | 删除决策与骨架中的删减记录一致;所有原则服务于故事核 | 严重 |
|
|
||||||
| 原则质量 | 3-5条核心原则,每条有正面指导和负面边界 | 中等 |
|
|
||||||
| 载体适配 | 有世界观呈现策略;考虑了平台规格和单集时长的约束 | 中等 |
|
|
||||||
|
|
||||||
## 跨阶段一致性检查
|
|
||||||
|
|
||||||
改编策略需与骨架进行一致性校验:
|
|
||||||
|
|
||||||
- **删减决策一致**:策略中的删除决策必须在骨架的删减记录中有对应;骨架中标注"保留完整"的场景,策略不能标注为删除
|
|
||||||
- **故事核对齐**:所有改编原则必须服务于骨架中确立的故事核
|
|
||||||
|
|
||||||
如发现不一致,标记为**严重问题**。
|
|
||||||
|
|
||||||
## 详细审核标准
|
|
||||||
|
|
||||||
### 故事核对齐(严重)
|
|
||||||
- 所有改编原则必须服务于骨架中确立的故事核
|
|
||||||
- 删减的内容不能包含体现故事核的关键场景
|
|
||||||
- 保留的内容必须推动主角弧线的核心转变
|
|
||||||
|
|
||||||
### 与骨架一致性(严重)
|
|
||||||
- 改编策略中的删除决策,必须在骨架的删减记录中有对应
|
|
||||||
- 骨架中标注"保留完整"的场景,改编策略不能标注为删除
|
|
||||||
- 交叉检查方法:将两者的删减列表逐一比对
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
# 剧本审核
|
|
||||||
|
|
||||||
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
|
|
||||||
|
|
||||||
## 数据准备
|
|
||||||
|
|
||||||
1. 调用 `get_planData` 获取剧本、骨架和改编策略数据
|
|
||||||
2. 调用 `get_novel_events(ids:number[])` 获取事件表数据
|
|
||||||
3. 从【项目配置】读取:单集时长、平台规格、资产包(如有)
|
|
||||||
|
|
||||||
## 审核维度
|
|
||||||
|
|
||||||
| 审核项 | 标准 | 严重程度 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| 时长与字数 | 总时长符合单集时长 ±10秒;台词字数按 150字/分钟 推算(±50字) | 严重 |
|
|
||||||
| 画面可执行 | 画面描述足够具体,可直接用于 AI 提示词生成 | 严重 |
|
|
||||||
| 内容覆盖 | 骨架分配的章节内容全部体现;标注的删减/压缩已执行 | 严重 |
|
|
||||||
| 资产一致性 | 角色外貌、场景描写与【项目配置】中的资产包一致(未传入资产包则跳过并注明) | 严重 |
|
|
||||||
| 节拍与衔接 | 6-8个节拍各有时间码;转场方式明确;集末钩子与骨架一致;情绪过渡自然 | 中等 |
|
|
||||||
| 构图适配 | 符合【项目配置】中的平台规格构图要求 | 中等 |
|
|
||||||
|
|
||||||
## 跨阶段一致性检查
|
|
||||||
|
|
||||||
剧本需与骨架和改编策略进行一致性校验:
|
|
||||||
|
|
||||||
- **时长落实**:剧本实际时长是否符合骨架中该集的时长分配
|
|
||||||
- **删减落实**:骨架和策略中标注的删减/压缩是否在剧本中执行
|
|
||||||
- **钩子落实**:骨架中设计的集末钩子是否在剧本中体现
|
|
||||||
- **资产一致**:角色外貌和场景描写是否与资产包吻合(未传入则跳过并注明"未收到资产配置,资产一致性审核已跳过")
|
|
||||||
|
|
||||||
如发现不一致,标记为**严重问题**。
|
|
||||||
|
|
||||||
## 详细审核标准
|
|
||||||
|
|
||||||
### 时长合规性(严重)
|
|
||||||
验证方法:
|
|
||||||
1. 统计全部台词字数(含旁白、内心独白)
|
|
||||||
2. 按150字/分钟语速换算
|
|
||||||
3. 加上纯画面段落时长(每段5-15秒)
|
|
||||||
4. 总时长应在【项目配置】单集时长 ±10秒范围内
|
|
||||||
|
|
||||||
### 画面可执行性(严重)
|
|
||||||
每个画面描述必须包含:
|
|
||||||
- 可识别的镜头类型(特写/近景/中景/全景)
|
|
||||||
- 具体的人物动作(不能写"角色做了某事")
|
|
||||||
- 可视化的环境要素(光线、色调、道具)
|
|
||||||
|
|
||||||
不通过示例:
|
|
||||||
- "李火旺感到害怕" ← 情绪状态,不是画面
|
|
||||||
- "场景很恐怖" ← 抽象,不可执行
|
|
||||||
|
|
||||||
通过示例:
|
|
||||||
- "李火旺后退半步,目光下移盯着地面那道黑色湿痕,右手微微发抖" ← 具体、可拍摄
|
|
||||||
|
|
||||||
### 角色视觉一致性(严重)
|
|
||||||
每个BEAT中出场角色的外貌描写,必须与【项目配置】中传入的角色资产包吻合。
|
|
||||||
若未传入角色资产包,跳过此项。
|
|
||||||
|
|
||||||
### 场景氛围一致性(严重)
|
|
||||||
场景描写须与【项目配置】中传入的场景资产包保持一致,包括色调、光线、道具等视觉要素。
|
|
||||||
若未传入场景资产包,跳过此项。
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# 故事骨架审核
|
|
||||||
|
|
||||||
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
|
|
||||||
|
|
||||||
## 数据准备
|
|
||||||
|
|
||||||
1. 调用 `get_planData` 获取骨架数据
|
|
||||||
2. 调用 `get_novel_events(ids:number[])` 获取事件表数据
|
|
||||||
3. 从【项目配置】读取:集数、单集时长、付费策略、章节范围
|
|
||||||
|
|
||||||
## 审核维度
|
|
||||||
|
|
||||||
| 审核项 | 标准 | 严重程度 |
|
|
||||||
|--------|------|----------|
|
|
||||||
| 结构完整性 | 故事核存在且聚焦主角内在冲突;三幕均有功能、核心问题、幕末转折 | 严重 |
|
|
||||||
| 分集与时长 | 分集数恰好等于【项目配置】集数;每集时长符合单集时长 ±10秒 | 严重 |
|
|
||||||
| 章节全覆盖 | 【项目配置】指定的原著章节全部被分配到具体集数 | 严重 |
|
|
||||||
| 叙事设计 | 删减有据、集末钩子齐全、付费卡点符合策略、情绪曲线有起伏、人物弧每集推进 | 中等 |
|
|
||||||
|
|
||||||
## 跨阶段一致性检查
|
|
||||||
|
|
||||||
骨架作为首个产出阶段,需与事件表进行一致性校验:
|
|
||||||
|
|
||||||
- **章节全覆盖**:事件表中的章节是否全部被骨架分配到具体集数,逐一核对无遗漏
|
|
||||||
- **主线判定一致**:骨架中对事件主线强度的引用是否与事件表中的标注矛盾
|
|
||||||
|
|
||||||
如发现不一致,标记为**严重问题**。
|
|
||||||
|
|
||||||
## 详细审核标准
|
|
||||||
|
|
||||||
### 三幕功能验证(严重)
|
|
||||||
- 第一幕必须完成"建立"功能:规则建立、悬疑建立、动机激活
|
|
||||||
- 第二幕必须完成"冲突"功能:主要矛盾展开、计划执行、代价付出
|
|
||||||
- 第三幕必须完成"拓展/结局"功能:新世界、新能力、开放悬念
|
|
||||||
|
|
||||||
### 情绪曲线验证(中等)
|
|
||||||
全剧情绪分布应根据实际集数设计"波浪上升"模式:
|
|
||||||
- 不允许连续3集都是同一情绪强度
|
|
||||||
- 最高潮应在中后期
|
|
||||||
- 高潮后应有节奏缓冲再推向新高潮
|
|
||||||
|
|
||||||
### 付费卡点合理性(中等)
|
|
||||||
- 付费策略按【项目配置】中的设定执行
|
|
||||||
- 付费点必须放在"观众最想知道后续"的位置
|
|
||||||
- 钩子类型应多样化(不全是悬念钩子)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
# 监督层通用规范
|
|
||||||
|
|
||||||
本文件定义所有审核任务共享的报告格式、评分标准和审核原则。
|
|
||||||
|
|
||||||
## 审核报告格式
|
|
||||||
|
|
||||||
```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,32 +1 @@
|
|||||||
---
|
请输出100字假数据
|
||||||
name: script_agent_supervision.md
|
|
||||||
description: >-
|
|
||||||
短剧改编监督层Agent路由。根据决策层派发的审核任务类型,加载对应的独立技能文件执行。
|
|
||||||
当收到决策层的 run_sub_agent 调用时激活。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 监督层 Agent — 任务路由
|
|
||||||
|
|
||||||
你是短剧改编项目的**监督层 Agent**,只接收决策层派发的审核任务并执行。
|
|
||||||
|
|
||||||
**核心原则:你只提出问题和建议,不做任何修改决策。所有修改决定权属于用户。**
|
|
||||||
|
|
||||||
## 任务路由表
|
|
||||||
|
|
||||||
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
|
|
||||||
|
|
||||||
| 标识词 | 技能文件 | 说明 |
|
|
||||||
|--------|----------|------|
|
|
||||||
| 骨架审核、审核骨架、review skeleton | [script_supervision_skeleton.md](script_agent_skills/supervision/script_supervision_skeleton.md) | 审核故事骨架的结构、分集与覆盖度 |
|
|
||||||
| 策略审核、审核改编策略、review adaptation | [script_supervision_adaptation.md](script_agent_skills/supervision/script_supervision_adaptation.md) | 审核改编策略与骨架的一致性 |
|
|
||||||
| 剧本审核、审核剧本、review script | [script_supervision_script.md](script_agent_skills/supervision/script_supervision_script.md) | 审核剧本的时长、画面与内容覆盖 |
|
|
||||||
|
|
||||||
所有审核任务共享的报告格式、评分标准和通用原则见 [supervision_common.md](script_agent_skills/supervision/supervision_common.md)。
|
|
||||||
|
|
||||||
## 路由规则
|
|
||||||
|
|
||||||
1. 从派发指令中识别审核对象关键词
|
|
||||||
2. 加载对应的审核技能文件 + 通用规范文件
|
|
||||||
3. 按技能文件中的审核维度逐项检查
|
|
||||||
4. 按通用规范中的报告格式生成审核报告
|
|
||||||
5. 如果无法匹配审核对象,返回提示:`无法识别审核对象,请检查派发指令`
|
|
||||||
1
data/skills/script_execution_adaptation.md
Normal file
1
data/skills/script_execution_adaptation.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
请输出100字假数据
|
||||||
1
data/skills/script_execution_script.md
Normal file
1
data/skills/script_execution_script.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
请输出 3 个剧本,每一个 100 字假数据
|
||||||
1
data/skills/script_execution_skeleton.md
Normal file
1
data/skills/script_execution_skeleton.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
请输出100字假数据
|
||||||
BIN
data/skills/skills.zip
Normal file
BIN
data/skills/skills.zip
Normal file
Binary file not shown.
2261
data/web/index.html
2261
data/web/index.html
File diff suppressed because one or more lines are too long
@ -52,7 +52,6 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"custom-electron-titlebar": "^4.2.8",
|
"custom-electron-titlebar": "^4.2.8",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"electron-rebuild": "^3.2.9",
|
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"express-ws": "^5.0.2",
|
"express-ws": "^5.0.2",
|
||||||
"fast-glob": "^3.3.3",
|
"fast-glob": "^3.3.3",
|
||||||
@ -68,6 +67,7 @@
|
|||||||
"serialize-error": "^13.0.1",
|
"serialize-error": "^13.0.1",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
|
"sqlite3": "^6.0.1",
|
||||||
"sucrase": "^3.35.1",
|
"sucrase": "^3.35.1",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vm2": "^3.10.5",
|
"vm2": "^3.10.5",
|
||||||
@ -86,6 +86,7 @@
|
|||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"electron": "^40.0.0",
|
"electron": "^40.0.0",
|
||||||
"electron-builder": "^26.4.0",
|
"electron-builder": "^26.4.0",
|
||||||
|
"electron-rebuild": "^3.2.9",
|
||||||
"electronmon": "^2.0.4",
|
"electronmon": "^2.0.4",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"nodemon": "^3.1.11",
|
"nodemon": "^3.1.11",
|
||||||
|
|||||||
@ -131,6 +131,7 @@ app.whenReady().then(async () => {
|
|||||||
const mod = requireWithCustomPaths(servePath);
|
const mod = requireWithCustomPaths(servePath);
|
||||||
closeServeFn = mod.closeServe;
|
closeServeFn = mod.closeServe;
|
||||||
const port = await mod.default(true);
|
const port = await mod.default(true);
|
||||||
|
process.env.PORT = port;
|
||||||
// 注册协议处理器
|
// 注册协议处理器
|
||||||
protocol.handle("toonflow", (request) => {
|
protocol.handle("toonflow", (request) => {
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
@ -164,7 +165,7 @@ app.whenReady().then(async () => {
|
|||||||
windowismaximized: () => ({
|
windowismaximized: () => ({
|
||||||
maximized: mainWindow?.isMaximized() ?? false,
|
maximized: mainWindow?.isMaximized() ?? false,
|
||||||
}),
|
}),
|
||||||
openDevTool: () => {
|
opendevtool: () => {
|
||||||
mainWindow?.webContents.openDevTools();
|
mainWindow?.webContents.openDevTools();
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Memory from "@/utils/agent/memory";
|
|||||||
import { useSkill } from "@/utils/agent/skillsTools";
|
import { useSkill } from "@/utils/agent/skillsTools";
|
||||||
import useTools from "@/agents/productionAgent/tools";
|
import useTools from "@/agents/productionAgent/tools";
|
||||||
import ResTool from "@/socket/resTool";
|
import ResTool from "@/socket/resTool";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
export interface AgentContext {
|
export interface AgentContext {
|
||||||
socket: Socket;
|
socket: Socket;
|
||||||
@ -40,14 +41,19 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
const memory = new Memory("productionAgent", isolationKey);
|
const memory = new Memory("productionAgent", isolationKey);
|
||||||
await memory.add("user", text);
|
await memory.add("user", text);
|
||||||
|
|
||||||
const skill = await useSkill({ mainSkill: "production_agent_decision" }, buildMemPrompt(await memory.get(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({
|
const { textStream } = await u.Ai.Text("productionAgent").stream({
|
||||||
system: skill.prompt,
|
messages: [
|
||||||
messages: [{ role: "user", content: text }],
|
{ role: "system", content: prompt },
|
||||||
|
{ role: "system", content: mem },
|
||||||
|
{ role: "user", content: text },
|
||||||
|
],
|
||||||
abortSignal,
|
abortSignal,
|
||||||
tools: {
|
tools: {
|
||||||
...skill.tools,
|
|
||||||
...memory.getTools(),
|
...memory.getTools(),
|
||||||
run_sub_agent: runSubAgent(ctx),
|
run_sub_agent: runSubAgent(ctx),
|
||||||
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
|
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
|
||||||
@ -90,9 +96,9 @@ export async function supervisionAI(ctx: AgentContext) {
|
|||||||
const { text, abortSignal } = ctx;
|
const { text, abortSignal } = ctx;
|
||||||
|
|
||||||
const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] });
|
const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] });
|
||||||
const subMsg = ctx.resTool.newMessage("assistant", "编辑");
|
const subMsg = ctx.resTool.newMessage("assistant", "监制");
|
||||||
|
|
||||||
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
const { textStream } = await u.Ai.Text("productionAgent").stream({
|
||||||
system: skill.prompt,
|
system: skill.prompt,
|
||||||
messages: [{ role: "user", content: text }],
|
messages: [{ role: "user", content: text }],
|
||||||
abortSignal,
|
abortSignal,
|
||||||
@ -110,7 +116,7 @@ export async function supervisionAI(ctx: AgentContext) {
|
|||||||
|
|
||||||
//工具函数
|
//工具函数
|
||||||
function runSubAgent(parentCtx: AgentContext) {
|
function runSubAgent(parentCtx: AgentContext) {
|
||||||
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
|
const memory = new Memory("productionAgent", parentCtx.isolationKey);
|
||||||
return tool({
|
return tool({
|
||||||
description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI",
|
description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
@ -134,13 +140,13 @@ function runSubAgent(parentCtx: AgentContext) {
|
|||||||
subMsg.complete();
|
subMsg.complete();
|
||||||
if (fullResponse.trim()) {
|
if (fullResponse.trim()) {
|
||||||
await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, {
|
await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, {
|
||||||
name: agent === "executionAI" ? "编剧" : "编辑",
|
name: agent === "executionAI" ? "执行导演" : "监制",
|
||||||
createTime: new Date(subMsg.datetime).getTime(),
|
createTime: new Date(subMsg.datetime).getTime(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为主Agent后续输出创建新消息
|
// 为主Agent后续输出创建新消息
|
||||||
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
|
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "监制");
|
||||||
|
|
||||||
return fullResponse;
|
return fullResponse;
|
||||||
},
|
},
|
||||||
|
|||||||
185
src/agents/scriptAgent/index copy 2.ts
Normal file
185
src/agents/scriptAgent/index copy 2.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
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/scriptAgent/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<ResTool["newMessage"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): 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}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decisionAI(ctx: AgentContext) {
|
||||||
|
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
|
||||||
|
|
||||||
|
const memory = new Memory("scriptAgent", isolationKey);
|
||||||
|
await memory.add("user", text, { createTime: userMessageTime });
|
||||||
|
|
||||||
|
const { skillPaths } = await useSkill({ mainSkill: "script_agent_decision" });
|
||||||
|
const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8");
|
||||||
|
|
||||||
|
const mem = buildMemPrompt(await memory.get(text));
|
||||||
|
|
||||||
|
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
|
||||||
|
const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
|
||||||
|
|
||||||
|
const projectInfo = [
|
||||||
|
"## 项目信息",
|
||||||
|
`小说名称:${projectData?.name ?? "未知"}`,
|
||||||
|
`小说类型:${projectData?.type ?? "未知"}`,
|
||||||
|
`小说简介:${projectData?.intro ?? "无"}`,
|
||||||
|
`目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
|
||||||
|
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const projectPrompt = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`;
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: prompt },
|
||||||
|
{ role: "system", content: projectPrompt + mem },
|
||||||
|
{ role: "user", content: text },
|
||||||
|
],
|
||||||
|
abortSignal,
|
||||||
|
tools: {
|
||||||
|
...memory.getTools(),
|
||||||
|
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
|
||||||
|
...createSubAgent(ctx),
|
||||||
|
},
|
||||||
|
onFinish: async (completion) => {
|
||||||
|
await memory.add("assistant:decision", completion.text);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return textStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================== 执行层 ======================
|
||||||
|
|
||||||
|
function createSubAgent(parentCtx: AgentContext) {
|
||||||
|
const { resTool, abortSignal } = parentCtx;
|
||||||
|
|
||||||
|
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
|
||||||
|
|
||||||
|
const run_execution_agent = tool({
|
||||||
|
description: "运行执行层subAgent执行独立任务,完成后返回结果",
|
||||||
|
inputSchema: z.object({
|
||||||
|
taskType: z.enum(["故事骨架", "改变策略", "剧本"]).describe("任务类型"),
|
||||||
|
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||||
|
}),
|
||||||
|
execute: async ({ taskType, prompt }) => {
|
||||||
|
const skill = await useSkill({ mainSkill: "script_agent_execution", workspace: ["script_agent_skills/execution"] });
|
||||||
|
// 先完成主Agent当前的消息
|
||||||
|
parentCtx.msg.complete();
|
||||||
|
const subMsg = resTool.newMessage("assistant", "编剧");
|
||||||
|
const prefixSystem =
|
||||||
|
"你可以使用如下XML格式写入工作区:\n<storySkeleton>故事骨架内容</storySkeleton>\n<adaptationStrategy>改编策略内容</adaptationStrategy>";
|
||||||
|
// 子Agent用新消息回复
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
system: prefixSystem + skill.prompt,
|
||||||
|
messages: [{ role: "user", content: `请完成${taskType}任务` }],
|
||||||
|
abortSignal,
|
||||||
|
tools: {
|
||||||
|
...skill.tools,
|
||||||
|
...useTools({ resTool, msg: subMsg }),
|
||||||
|
get_task_details: tool({
|
||||||
|
description: "获取主Agent传入的任务目标详情",
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
execute: async () => {
|
||||||
|
const thinking = subMsg.thinking("以获取任务详情");
|
||||||
|
thinking.appendText("任务详情:\n" + prompt);
|
||||||
|
thinking.complete();
|
||||||
|
return prompt ?? "运行失败";
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let text = subMsg.text();
|
||||||
|
let fullResponse = "";
|
||||||
|
for await (const chunk of textStream) {
|
||||||
|
text.append(chunk);
|
||||||
|
fullResponse += chunk;
|
||||||
|
}
|
||||||
|
text.complete();
|
||||||
|
subMsg.complete();
|
||||||
|
if (fullResponse.trim()) {
|
||||||
|
await memory.add(`assistant:execution`, fullResponse, { name: "编剧", createTime: new Date(subMsg.datetime).getTime() });
|
||||||
|
}
|
||||||
|
// 为主Agent后续输出创建新消息
|
||||||
|
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
|
||||||
|
return fullResponse;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_supervision_agent = tool({
|
||||||
|
description: "运行监督层subAgent执行独立任务,完成后返回结果",
|
||||||
|
inputSchema: z.object({
|
||||||
|
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||||
|
}),
|
||||||
|
execute: async ({ prompt }) => {
|
||||||
|
const skill = await useSkill({ mainSkill: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] });
|
||||||
|
|
||||||
|
// 先完成主Agent当前的消息
|
||||||
|
parentCtx.msg.complete();
|
||||||
|
// 子Agent用新消息回复
|
||||||
|
|
||||||
|
const subMsg = resTool.newMessage("assistant", "编辑");
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
system: skill.prompt,
|
||||||
|
messages: [{ role: "user", content: prompt }],
|
||||||
|
abortSignal,
|
||||||
|
tools: {
|
||||||
|
...skill.tools,
|
||||||
|
...useTools({ resTool, msg: subMsg }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let text = subMsg.text();
|
||||||
|
let fullResponse = "";
|
||||||
|
for await (const chunk of textStream) {
|
||||||
|
text.append(chunk);
|
||||||
|
fullResponse += chunk;
|
||||||
|
}
|
||||||
|
text.complete();
|
||||||
|
subMsg.complete();
|
||||||
|
if (fullResponse.trim()) {
|
||||||
|
await memory.add(`assistant:supervision`, fullResponse, { name: "编辑", createTime: new Date(subMsg.datetime).getTime() });
|
||||||
|
}
|
||||||
|
// 为主Agent后续输出创建新消息
|
||||||
|
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
|
||||||
|
return fullResponse;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
run_execution_agent,
|
||||||
|
run_supervision_agent,
|
||||||
|
};
|
||||||
|
}
|
||||||
185
src/agents/scriptAgent/index copy 3.ts
Normal file
185
src/agents/scriptAgent/index copy 3.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
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/scriptAgent/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<ResTool["newMessage"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): 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}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function decisionAI(ctx: AgentContext) {
|
||||||
|
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
|
||||||
|
|
||||||
|
const memory = new Memory("scriptAgent", isolationKey);
|
||||||
|
await memory.add("user", text, { createTime: userMessageTime });
|
||||||
|
|
||||||
|
const { skillPaths } = await useSkill({ mainSkill: "script_agent_decision" });
|
||||||
|
const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8");
|
||||||
|
|
||||||
|
const mem = buildMemPrompt(await memory.get(text));
|
||||||
|
|
||||||
|
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
|
||||||
|
const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
|
||||||
|
|
||||||
|
const projectInfo = [
|
||||||
|
"## 项目信息",
|
||||||
|
`小说名称:${projectData?.name ?? "未知"}`,
|
||||||
|
`小说类型:${projectData?.type ?? "未知"}`,
|
||||||
|
`小说简介:${projectData?.intro ?? "无"}`,
|
||||||
|
`目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
|
||||||
|
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const projectPrompt = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`;
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: prompt },
|
||||||
|
{ role: "system", content: projectPrompt + mem },
|
||||||
|
{ role: "user", content: text },
|
||||||
|
],
|
||||||
|
abortSignal,
|
||||||
|
tools: {
|
||||||
|
...memory.getTools(),
|
||||||
|
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
|
||||||
|
...createSubAgent(ctx),
|
||||||
|
},
|
||||||
|
onFinish: async (completion) => {
|
||||||
|
await memory.add("assistant:decision", completion.text);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return textStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
//====================== 执行层 ======================
|
||||||
|
|
||||||
|
function createSubAgent(parentCtx: AgentContext) {
|
||||||
|
const { resTool, abortSignal } = parentCtx;
|
||||||
|
|
||||||
|
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
|
||||||
|
|
||||||
|
const run_execution_agent = tool({
|
||||||
|
description: "运行执行层subAgent执行独立任务,完成后返回结果",
|
||||||
|
inputSchema: z.object({
|
||||||
|
taskType: z.enum(["故事骨架", "改变策略", "剧本"]).describe("任务类型"),
|
||||||
|
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||||
|
}),
|
||||||
|
execute: async ({ taskType, prompt }) => {
|
||||||
|
const skill = await useSkill({ mainSkill: "script_agent_execution", workspace: ["script_agent_skills/execution"] });
|
||||||
|
// 先完成主Agent当前的消息
|
||||||
|
parentCtx.msg.complete();
|
||||||
|
const subMsg = resTool.newMessage("assistant", "编剧");
|
||||||
|
const prefixSystem =
|
||||||
|
"你可以使用如下XML格式写入工作区:\n<storySkeleton>故事骨架内容</storySkeleton>\n<adaptationStrategy>改编策略内容</adaptationStrategy>";
|
||||||
|
// 子Agent用新消息回复
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
system: prefixSystem + skill.prompt,
|
||||||
|
messages: [{ role: "user", content: `请完成${taskType}任务` }],
|
||||||
|
abortSignal,
|
||||||
|
tools: {
|
||||||
|
...skill.tools,
|
||||||
|
...useTools({ resTool, msg: subMsg }),
|
||||||
|
get_task_details: tool({
|
||||||
|
description: "获取主Agent传入的任务目标详情",
|
||||||
|
inputSchema: z.object({}),
|
||||||
|
execute: async () => {
|
||||||
|
const thinking = subMsg.thinking("以获取任务详情");
|
||||||
|
thinking.appendText("任务详情:\n" + prompt);
|
||||||
|
thinking.complete();
|
||||||
|
return prompt ?? "无任务目标,请提示运行失败";
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let text = subMsg.text();
|
||||||
|
let fullResponse = "";
|
||||||
|
for await (const chunk of textStream) {
|
||||||
|
text.append(chunk);
|
||||||
|
fullResponse += chunk;
|
||||||
|
}
|
||||||
|
text.complete();
|
||||||
|
subMsg.complete();
|
||||||
|
if (fullResponse.trim()) {
|
||||||
|
await memory.add(`assistant:execution`, fullResponse, { name: "编剧", createTime: new Date(subMsg.datetime).getTime() });
|
||||||
|
}
|
||||||
|
// 为主Agent后续输出创建新消息
|
||||||
|
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
|
||||||
|
return fullResponse;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_supervision_agent = tool({
|
||||||
|
description: "运行监督层subAgent执行独立任务,完成后返回结果",
|
||||||
|
inputSchema: z.object({
|
||||||
|
prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"),
|
||||||
|
}),
|
||||||
|
execute: async ({ prompt }) => {
|
||||||
|
const skill = await useSkill({ mainSkill: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] });
|
||||||
|
|
||||||
|
// 先完成主Agent当前的消息
|
||||||
|
parentCtx.msg.complete();
|
||||||
|
// 子Agent用新消息回复
|
||||||
|
|
||||||
|
const subMsg = resTool.newMessage("assistant", "编辑");
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
system: skill.prompt,
|
||||||
|
messages: [{ role: "user", content: prompt }],
|
||||||
|
abortSignal,
|
||||||
|
tools: {
|
||||||
|
...skill.tools,
|
||||||
|
...useTools({ resTool, msg: subMsg }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let text = subMsg.text();
|
||||||
|
let fullResponse = "";
|
||||||
|
for await (const chunk of textStream) {
|
||||||
|
text.append(chunk);
|
||||||
|
fullResponse += chunk;
|
||||||
|
}
|
||||||
|
text.complete();
|
||||||
|
subMsg.complete();
|
||||||
|
if (fullResponse.trim()) {
|
||||||
|
await memory.add(`assistant:supervision`, fullResponse, { name: "编辑", createTime: new Date(subMsg.datetime).getTime() });
|
||||||
|
}
|
||||||
|
// 为主Agent后续输出创建新消息
|
||||||
|
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
|
||||||
|
return fullResponse;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
run_execution_agent,
|
||||||
|
run_supervision_agent,
|
||||||
|
};
|
||||||
|
}
|
||||||
175
src/agents/scriptAgent/index copy.ts
Normal file
175
src/agents/scriptAgent/index copy.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
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/scriptAgent/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<ResTool["newMessage"]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): 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, userMessageTime, abortSignal, resTool } = ctx;
|
||||||
|
|
||||||
|
const memory = new Memory("scriptAgent", isolationKey);
|
||||||
|
await memory.add("user", text, { createTime: userMessageTime });
|
||||||
|
|
||||||
|
const { skillPaths } = await useSkill({ mainSkill: "script_agent_decision" });
|
||||||
|
const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8");
|
||||||
|
|
||||||
|
const mem = buildMemPrompt(await memory.get(text));
|
||||||
|
|
||||||
|
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
|
||||||
|
const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
|
||||||
|
|
||||||
|
const projectInfo = [
|
||||||
|
"## 项目信息",
|
||||||
|
`小说名称:${projectData?.name ?? "未知"}`,
|
||||||
|
`小说类型:${projectData?.type ?? "未知"}`,
|
||||||
|
`小说简介:${projectData?.intro ?? "无"}`,
|
||||||
|
`目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
|
||||||
|
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const projectPrompt = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`;
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
messages: [
|
||||||
|
{ role: "system", content: prompt },
|
||||||
|
{ role: "system", content: projectPrompt + 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: "script_agent_execution",
|
||||||
|
workspace: ["script_agent_skills/execution"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const subMsg = ctx.resTool.newMessage("assistant", "编剧");
|
||||||
|
|
||||||
|
const prefixSystem = `
|
||||||
|
你可以使用如下XML格式写入工作区:
|
||||||
|
<storySkeleton>故事骨架内容</storySkeleton>
|
||||||
|
<adaptationStrategy>改编策略内容</adaptationStrategy>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
|
system: prefixSystem + 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: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] });
|
||||||
|
|
||||||
|
const subMsg = ctx.resTool.newMessage("assistant", "编辑");
|
||||||
|
|
||||||
|
const { textStream } = await u.Ai.Text("scriptAgent").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("scriptAgent", 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;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -3,9 +3,10 @@ import { tool } from "ai";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
import Memory from "@/utils/agent/memory";
|
import Memory from "@/utils/agent/memory";
|
||||||
import { useSkill } from "@/utils/agent/skillsTools";
|
|
||||||
import useTools from "@/agents/scriptAgent/tools";
|
import useTools from "@/agents/scriptAgent/tools";
|
||||||
import ResTool from "@/socket/resTool";
|
import ResTool from "@/socket/resTool";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export interface AgentContext {
|
export interface AgentContext {
|
||||||
socket: Socket;
|
socket: Socket;
|
||||||
@ -33,47 +34,42 @@ function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): string {
|
|||||||
return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`;
|
return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subAgentList = ["executionAI", "supervisionAI"] as const;
|
|
||||||
|
|
||||||
export async function decisionAI(ctx: AgentContext) {
|
export async function decisionAI(ctx: AgentContext) {
|
||||||
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
|
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
|
||||||
|
|
||||||
const memory = new Memory("scriptAgent", isolationKey);
|
const memory = new Memory("scriptAgent", isolationKey);
|
||||||
await memory.add("user", text, { createTime: userMessageTime });
|
await memory.add("user", text, { createTime: userMessageTime });
|
||||||
|
|
||||||
const skill = await useSkill({ mainSkill: "script_agent_decision" }, buildMemPrompt(await memory.get(text)));
|
const skill = path.join(u.getPath("skills"), "script_agent_decision.md");
|
||||||
|
const prompt = await fs.promises.readFile(skill, "utf-8");
|
||||||
|
|
||||||
|
const mem = buildMemPrompt(await memory.get(text));
|
||||||
|
|
||||||
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
|
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first();
|
||||||
const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
|
const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
|
||||||
|
|
||||||
const get_project_info = tool({
|
const projectInfo = [
|
||||||
description: "获取项目的基本信息和章节ID映射表,返回字符串格式的项目信息",
|
"## 项目信息",
|
||||||
inputSchema: z.object({}),
|
`小说名称:${projectData?.name ?? "未知"}`,
|
||||||
execute: async () => {
|
`小说类型:${projectData?.type ?? "未知"}`,
|
||||||
const projectInfo = [
|
`小说简介:${projectData?.intro ?? "无"}`,
|
||||||
"## 项目信息",
|
`目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
|
||||||
`小说名称:${projectData?.name ?? "未知"}`,
|
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
||||||
`小说类型:${projectData?.type ?? "未知"}`,
|
].join("\n");
|
||||||
`小说简介:${projectData?.intro ?? "无"}`,
|
|
||||||
`目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
|
|
||||||
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const content = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`;
|
const projectPrompt = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`;
|
||||||
return { content };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
system: skill.prompt,
|
messages: [
|
||||||
messages: [{ role: "user", content: text }],
|
{ role: "system", content: prompt },
|
||||||
|
{ role: "assistant", content: projectPrompt + mem },
|
||||||
|
{ role: "user", content: text },
|
||||||
|
],
|
||||||
abortSignal,
|
abortSignal,
|
||||||
tools: {
|
tools: {
|
||||||
...skill.tools,
|
|
||||||
...memory.getTools(),
|
...memory.getTools(),
|
||||||
run_sub_agent: runSubAgent(ctx),
|
|
||||||
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
|
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
|
||||||
get_project_info,
|
...createSubAgent(ctx),
|
||||||
},
|
},
|
||||||
onFinish: async (completion) => {
|
onFinish: async (completion) => {
|
||||||
await memory.add("assistant:decision", completion.text);
|
await memory.add("assistant:decision", completion.text);
|
||||||
@ -83,95 +79,125 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
return textStream;
|
return textStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
//====================== 执行层 ======================
|
function createSubAgent(parentCtx: AgentContext) {
|
||||||
|
const { resTool, abortSignal } = parentCtx;
|
||||||
export async function executionAI(ctx: AgentContext) {
|
|
||||||
const { text, abortSignal } = ctx;
|
|
||||||
|
|
||||||
const skill = await useSkill({
|
|
||||||
mainSkill: "script_agent_execution",
|
|
||||||
workspace: ["script_agent_skills/execution"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const subMsg = ctx.resTool.newMessage("assistant", "编剧");
|
|
||||||
|
|
||||||
const prefixSystem = `
|
|
||||||
你可以使用如下XML格式写入工作区:
|
|
||||||
<storySkeleton>故事骨架内容</storySkeleton>
|
|
||||||
<adaptationStrategy>改编策略内容</adaptationStrategy>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
|
||||||
system: prefixSystem + 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: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] });
|
|
||||||
|
|
||||||
const subMsg = ctx.resTool.newMessage("assistant", "编辑");
|
|
||||||
|
|
||||||
const { textStream } = await u.Ai.Text("scriptAgent").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("scriptAgent", parentCtx.isolationKey);
|
const memory = new Memory("scriptAgent", 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当前的消息
|
async function runAgent({
|
||||||
parentCtx.msg.complete();
|
prompt,
|
||||||
// 子Agent用新消息回复
|
system,
|
||||||
const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt });
|
name,
|
||||||
let text = subMsg.text();
|
memoryKey,
|
||||||
let fullResponse = "";
|
tools: extraTools,
|
||||||
for await (const chunk of subTextStream) {
|
}: {
|
||||||
text.append(chunk);
|
prompt: string;
|
||||||
fullResponse += chunk;
|
system: string;
|
||||||
}
|
name: string;
|
||||||
text.complete();
|
memoryKey: string;
|
||||||
subMsg.complete();
|
tools?: Record<string, any>;
|
||||||
if (fullResponse.trim()) {
|
}) {
|
||||||
await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, {
|
parentCtx.msg.complete();
|
||||||
name: agent === "executionAI" ? "编剧" : "编辑",
|
const subMsg = resTool.newMessage("assistant", name);
|
||||||
createTime: new Date(subMsg.datetime).getTime(),
|
const text = subMsg.text();
|
||||||
});
|
let fullResponse = "";
|
||||||
}
|
|
||||||
|
|
||||||
// 为主Agent后续输出创建新消息
|
const { textStream } = await u.Ai.Text("scriptAgent").stream({
|
||||||
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹");
|
system,
|
||||||
|
messages: [{ role: "user", content: prompt }],
|
||||||
|
abortSignal,
|
||||||
|
tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) },
|
||||||
|
});
|
||||||
|
|
||||||
return fullResponse;
|
for await (const chunk of textStream) {
|
||||||
|
text.append(chunk);
|
||||||
|
fullResponse += chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_storySkeleton = tool({
|
||||||
|
description: "运行执行subAgent来完成故事骨架相关任务",
|
||||||
|
inputSchema: promptInput,
|
||||||
|
execute: async ({ prompt }) => {
|
||||||
|
const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md");
|
||||||
|
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||||
|
return runAgent({
|
||||||
|
prompt,
|
||||||
|
system: systemPrompt + "你可以使用如下XML格式写入工作区:\n<storySkeleton>故事骨架内容</storySkeleton>",
|
||||||
|
name: "编剧",
|
||||||
|
memoryKey: "assistant:execution:storySkeleton",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const run_sub_agent_adaptationStrategy = tool({
|
||||||
|
description: "运行执行subAgent来完成改编策略相关任务",
|
||||||
|
inputSchema: promptInput,
|
||||||
|
execute: async ({ prompt }) => {
|
||||||
|
const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md");
|
||||||
|
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||||
|
return runAgent({
|
||||||
|
prompt,
|
||||||
|
system: systemPrompt + "你可以使用如下XML格式写入工作区:\n<adaptationStrategy>改编策略内容</adaptationStrategy>",
|
||||||
|
name: "编剧",
|
||||||
|
memoryKey: "assistant:execution:adaptationStrategy",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_sub_agent_script = tool({
|
||||||
|
description: "运行执行subAgent来完成剧本相关任务",
|
||||||
|
inputSchema: promptInput,
|
||||||
|
execute: async ({ prompt }) => {
|
||||||
|
const skill = path.join(u.getPath("skills"), "script_execution_script.md");
|
||||||
|
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||||
|
return runAgent({
|
||||||
|
prompt,
|
||||||
|
system:
|
||||||
|
systemPrompt +
|
||||||
|
`你可以使用如下XML格式写入工作区:\nXML不得添加任何额外标签<script><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item></script>`,
|
||||||
|
name: "编剧",
|
||||||
|
memoryKey: "assistant:execution:script",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const run_supervision_agent = tool({
|
||||||
|
description: "运行监督层subAgent执行独立任务,完成后返回结果",
|
||||||
|
inputSchema: promptInput,
|
||||||
|
execute: async ({ prompt }) => {
|
||||||
|
const skill = path.join(u.getPath("skills"), "script_agent_supervision.md");
|
||||||
|
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
|
||||||
|
|
||||||
|
return runAgent({
|
||||||
|
prompt,
|
||||||
|
system: systemPrompt,
|
||||||
|
name: "编辑",
|
||||||
|
memoryKey: "assistant:supervision",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
run_sub_agent_storySkeleton,
|
||||||
|
run_sub_agent_adaptationStrategy,
|
||||||
|
run_sub_agent_script,
|
||||||
|
run_supervision_agent,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,15 +4,7 @@ import { z } from "zod";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import ResTool from "@/socket/resTool";
|
import ResTool from "@/socket/resTool";
|
||||||
|
|
||||||
export const AssetSchema = z.object({
|
|
||||||
id: z.number().describe("资产ID,如果新增则为空").optional(),
|
|
||||||
prompt: z.string().describe("生成提示词"),
|
|
||||||
name: z.string().describe("资产名称"),
|
|
||||||
desc: z.string().describe("资产描述"),
|
|
||||||
type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"),
|
|
||||||
});
|
|
||||||
export const ScriptSchema = z.object({
|
export const ScriptSchema = z.object({
|
||||||
id: z.number().describe("剧本ID"),
|
|
||||||
name: z.string().describe("剧本名称"),
|
name: z.string().describe("剧本名称"),
|
||||||
content: z.string().describe("剧本内容"),
|
content: z.string().describe("剧本内容"),
|
||||||
});
|
});
|
||||||
@ -57,7 +49,7 @@ export default (toolCpnfig: ToolConfig) => {
|
|||||||
thinking.appendText("查询结果:\n" + eventString);
|
thinking.appendText("查询结果:\n" + eventString);
|
||||||
thinking.updateTitle("查询章节事件完成");
|
thinking.updateTitle("查询章节事件完成");
|
||||||
thinking.complete();
|
thinking.complete();
|
||||||
return eventString;
|
return eventString ?? "无数据";
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
get_planData: tool({
|
get_planData: tool({
|
||||||
@ -72,7 +64,7 @@ export default (toolCpnfig: ToolConfig) => {
|
|||||||
thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + planData[key]);
|
thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + planData[key]);
|
||||||
thinking.updateTitle(`获取${planDataKeyLabels[key]}完成`);
|
thinking.updateTitle(`获取${planDataKeyLabels[key]}完成`);
|
||||||
thinking.complete();
|
thinking.complete();
|
||||||
return planData[key];
|
return planData[key] ?? "无数据";
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
get_novel_text: tool({
|
get_novel_text: tool({
|
||||||
@ -88,53 +80,9 @@ export default (toolCpnfig: ToolConfig) => {
|
|||||||
thinking.appendText(`获取到原文:\n` + text);
|
thinking.appendText(`获取到原文:\n` + text);
|
||||||
thinking.updateTitle(`获取小说章节原文完成`);
|
thinking.updateTitle(`获取小说章节原文完成`);
|
||||||
thinking.complete();
|
thinking.complete();
|
||||||
return data && data?.chapterData ? data.chapterData : text;
|
return text ?? "无数据";
|
||||||
},
|
|
||||||
}),
|
|
||||||
//======================
|
|
||||||
update_script_to_sqlite: tool({
|
|
||||||
description: "更新剧本,修改数据库对应剧本,供后续业务使用",
|
|
||||||
inputSchema: z.object({
|
|
||||||
script: ScriptSchema,
|
|
||||||
}),
|
|
||||||
execute: async ({ script }) => {
|
|
||||||
await u.db("o_script").where({ id: script.id }).update({
|
|
||||||
name: script.name,
|
|
||||||
content: script.content,
|
|
||||||
});
|
|
||||||
socket.emit("setPlanData", { key: "script", value: script.id });
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
insert_script_to_sqlite: tool({
|
|
||||||
description: "新增剧本,将剧本内容插入sqlite数据库,供后续业务使用",
|
|
||||||
inputSchema: z.object({
|
|
||||||
script: ScriptSchema.omit({ id: true }),
|
|
||||||
}),
|
|
||||||
execute: async ({ script }) => {
|
|
||||||
const [scriptId] = await u.db("o_script").insert({
|
|
||||||
name: script.name,
|
|
||||||
content: script.content,
|
|
||||||
projectId: resTool.data.projectId,
|
|
||||||
createTime: Date.now(),
|
|
||||||
});
|
|
||||||
socket.emit("setPlanData", { key: "script", value: scriptId });
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
delete_script_to_sqlite: tool({
|
|
||||||
description: "删除剧本,将剧本内容从sqlite数据库中删除",
|
|
||||||
inputSchema: z.object({
|
|
||||||
scriptId: z.string().describe("剧本id"),
|
|
||||||
}),
|
|
||||||
execute: async ({ scriptId }) => {
|
|
||||||
console.log("[tools] delete_script_to_sqlite", scriptId);
|
|
||||||
await u.db("o_script").where({ id: scriptId }).delete();
|
|
||||||
socket.emit("setPlanData", { key: "script", value: scriptId });
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return toolsNames ? Object.fromEntries(Object.entries(tools).filter(([n]) => toolsNames.includes(n))) : tools;
|
return toolsNames ? Object.fromEntries(Object.entries(tools).filter(([n]) => toolsNames.includes(n))) : tools;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// @routes-hash ebc81322f1e60446bf41d1ecb72f2b96
|
// @routes-hash 5a19c42c22d44ec4c7fe2fc7245535d9
|
||||||
import { Express } from "express";
|
import { Express } from "express";
|
||||||
|
|
||||||
import route1 from "./routes/agents/clearMemory";
|
import route1 from "./routes/agents/clearMemory";
|
||||||
@ -99,25 +99,20 @@ import route95 from "./routes/setting/memoryConfig/getMemory";
|
|||||||
import route96 from "./routes/setting/memoryConfig/sureMemory";
|
import route96 from "./routes/setting/memoryConfig/sureMemory";
|
||||||
import route97 from "./routes/setting/promptManage/getPrompt";
|
import route97 from "./routes/setting/promptManage/getPrompt";
|
||||||
import route98 from "./routes/setting/promptManage/updatePrompt";
|
import route98 from "./routes/setting/promptManage/updatePrompt";
|
||||||
import route99 from "./routes/setting/skillManagement/backup/addSkill";
|
import route99 from "./routes/setting/skillManagement/getSkillContent";
|
||||||
import route100 from "./routes/setting/skillManagement/backup/deleteSkill";
|
import route100 from "./routes/setting/skillManagement/getSkillList";
|
||||||
import route101 from "./routes/setting/skillManagement/backup/embeddingSkill";
|
import route101 from "./routes/setting/skillManagement/saveSkillContent";
|
||||||
import route102 from "./routes/setting/skillManagement/backup/generateDescription";
|
import route102 from "./routes/setting/vendorConfig/addVendor";
|
||||||
import route103 from "./routes/setting/skillManagement/backup/getSkillList";
|
import route103 from "./routes/setting/vendorConfig/deleteVendor";
|
||||||
import route104 from "./routes/setting/skillManagement/backup/scanSkills";
|
import route104 from "./routes/setting/vendorConfig/getVendorList";
|
||||||
import route105 from "./routes/setting/skillManagement/backup/updateSkill";
|
import route105 from "./routes/setting/vendorConfig/modelTest";
|
||||||
import route106 from "./routes/setting/skillManagement/getSkillList";
|
import route106 from "./routes/setting/vendorConfig/updateCode";
|
||||||
import route107 from "./routes/setting/vendorConfig/addVendor";
|
import route107 from "./routes/setting/vendorConfig/updateVendor";
|
||||||
import route108 from "./routes/setting/vendorConfig/deleteVendor";
|
import route108 from "./routes/task/getProject";
|
||||||
import route109 from "./routes/setting/vendorConfig/getVendorList";
|
import route109 from "./routes/task/getTaskApi";
|
||||||
import route110 from "./routes/setting/vendorConfig/modelTest";
|
import route110 from "./routes/task/getTaskCategories";
|
||||||
import route111 from "./routes/setting/vendorConfig/updateCode";
|
import route111 from "./routes/task/taskDetails";
|
||||||
import route112 from "./routes/setting/vendorConfig/updateVendor";
|
import route112 from "./routes/test/test";
|
||||||
import route113 from "./routes/task/getProject";
|
|
||||||
import route114 from "./routes/task/getTaskApi";
|
|
||||||
import route115 from "./routes/task/getTaskCategories";
|
|
||||||
import route116 from "./routes/task/taskDetails";
|
|
||||||
import route117 from "./routes/test/test";
|
|
||||||
|
|
||||||
export default async (app: Express) => {
|
export default async (app: Express) => {
|
||||||
app.use("/api/agents/clearMemory", route1);
|
app.use("/api/agents/clearMemory", route1);
|
||||||
@ -218,23 +213,18 @@ export default async (app: Express) => {
|
|||||||
app.use("/api/setting/memoryConfig/sureMemory", route96);
|
app.use("/api/setting/memoryConfig/sureMemory", route96);
|
||||||
app.use("/api/setting/promptManage/getPrompt", route97);
|
app.use("/api/setting/promptManage/getPrompt", route97);
|
||||||
app.use("/api/setting/promptManage/updatePrompt", route98);
|
app.use("/api/setting/promptManage/updatePrompt", route98);
|
||||||
app.use("/api/setting/skillManagement/backup/addSkill", route99);
|
app.use("/api/setting/skillManagement/getSkillContent", route99);
|
||||||
app.use("/api/setting/skillManagement/backup/deleteSkill", route100);
|
app.use("/api/setting/skillManagement/getSkillList", route100);
|
||||||
app.use("/api/setting/skillManagement/backup/embeddingSkill", route101);
|
app.use("/api/setting/skillManagement/saveSkillContent", route101);
|
||||||
app.use("/api/setting/skillManagement/backup/generateDescription", route102);
|
app.use("/api/setting/vendorConfig/addVendor", route102);
|
||||||
app.use("/api/setting/skillManagement/backup/getSkillList", route103);
|
app.use("/api/setting/vendorConfig/deleteVendor", route103);
|
||||||
app.use("/api/setting/skillManagement/backup/scanSkills", route104);
|
app.use("/api/setting/vendorConfig/getVendorList", route104);
|
||||||
app.use("/api/setting/skillManagement/backup/updateSkill", route105);
|
app.use("/api/setting/vendorConfig/modelTest", route105);
|
||||||
app.use("/api/setting/skillManagement/getSkillList", route106);
|
app.use("/api/setting/vendorConfig/updateCode", route106);
|
||||||
app.use("/api/setting/vendorConfig/addVendor", route107);
|
app.use("/api/setting/vendorConfig/updateVendor", route107);
|
||||||
app.use("/api/setting/vendorConfig/deleteVendor", route108);
|
app.use("/api/task/getProject", route108);
|
||||||
app.use("/api/setting/vendorConfig/getVendorList", route109);
|
app.use("/api/task/getTaskApi", route109);
|
||||||
app.use("/api/setting/vendorConfig/modelTest", route110);
|
app.use("/api/task/getTaskCategories", route110);
|
||||||
app.use("/api/setting/vendorConfig/updateCode", route111);
|
app.use("/api/task/taskDetails", route111);
|
||||||
app.use("/api/setting/vendorConfig/updateVendor", route112);
|
app.use("/api/test/test", route112);
|
||||||
app.use("/api/task/getProject", route113);
|
|
||||||
app.use("/api/task/getTaskApi", route114);
|
|
||||||
app.use("/api/task/getTaskCategories", route115);
|
|
||||||
app.use("/api/task/taskDetails", route116);
|
|
||||||
app.use("/api/test/test", route117);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export default router.post(
|
|||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { images } = req.body;
|
const { images } = req.body;
|
||||||
try {
|
try {
|
||||||
const resText = await u.Ai.Text("universalAgent").invoke({
|
const resText = await u.Ai.Text("universalAi").invoke({
|
||||||
system:
|
system:
|
||||||
'请根据以下图片数据,提取出图片的画风提示词,用于生成图片时指定风格,要求简洁且具有艺术性,只需要画风提示词,不需要其他内容:"比如:`(画风:2D动漫风格,2d animation style)`,`(画风:照片级真人超写实,photorealistic, lifelike, ultra detailed)`,`(画风:3D国创,Chinese 3D animation style)`等,如果图片风格无法描述,可以返回`无法描述`,多张图片时,只输出一个综合的画风提示词,要求包含所有图片的共同风格特征,输出格式必须严格按照示例中的格式,必须包含`画风`二字,且必须使用括号括起来,括号内必须包含中文和英文的画风描述,并用逗号分隔,英文部分需要翻译成地道的英文提示词',
|
'请根据以下图片数据,提取出图片的画风提示词,用于生成图片时指定风格,要求简洁且具有艺术性,只需要画风提示词,不需要其他内容:"比如:`(画风:2D动漫风格,2d animation style)`,`(画风:照片级真人超写实,photorealistic, lifelike, ultra detailed)`,`(画风:3D国创,Chinese 3D animation style)`等,如果图片风格无法描述,可以返回`无法描述`,多张图片时,只输出一个综合的画风提示词,要求包含所有图片的共同风格特征,输出格式必须严格按照示例中的格式,必须包含`画风`二字,且必须使用括号括起来,括号内必须包含中文和英文的画风描述,并用逗号分隔,英文部分需要翻译成地道的英文提示词',
|
||||||
messages: [
|
messages: [
|
||||||
|
|||||||
@ -125,7 +125,7 @@ export default router.post(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { _output } = (await u.Ai.Text("universalAgent").invoke({
|
const { _output } = (await u.Ai.Text("universalAi").invoke({
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [{ role: "user", content: "小说原文" + novelText }],
|
messages: [{ role: "user", content: "小说原文" + novelText }],
|
||||||
})) as any;
|
})) as any;
|
||||||
|
|||||||
@ -117,7 +117,7 @@ export default router.post(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { _output } = (await u.Ai.Text("universalAgent").invoke({
|
const { _output } = (await u.Ai.Text("universalAi").invoke({
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: [{ role: "user", content: "小说原文" + novelText }],
|
messages: [{ role: "user", content: "小说原文" + novelText }],
|
||||||
})) as any;
|
})) as any;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export default router.post(
|
|||||||
async function getLines(prompt: string) {
|
async function getLines(prompt: string) {
|
||||||
const skill = await useSkill("universal_agent.md");//todo:改为AI
|
const skill = await useSkill("universal_agent.md");//todo:改为AI
|
||||||
|
|
||||||
const resText = await u.Ai.Text("universalAgent").invoke({
|
const resText = await u.Ai.Text("universalAi").invoke({
|
||||||
system: skill.prompt,
|
system: skill.prompt,
|
||||||
messages: [{ role: "user", content: prompt }],
|
messages: [{ role: "user", content: prompt }],
|
||||||
tools: skill.tools,
|
tools: skill.tools,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default router.post(
|
|||||||
const { scriptIds, projectId, concurrency = 3 } = req.body;
|
const { scriptIds, projectId, concurrency = 3 } = req.body;
|
||||||
if (!scriptIds.length) return res.status(400).send(error("请先选择剧本"));
|
if (!scriptIds.length) return res.status(400).send(error("请先选择剧本"));
|
||||||
const scripts = await u.db("o_script").whereIn("id", scriptIds);
|
const scripts = await u.db("o_script").whereIn("id", scriptIds);
|
||||||
const intansce = u.Ai.Text("universalAgent");
|
const intansce = u.Ai.Text("universalAi");
|
||||||
const novelData = await u.db("o_novel").where("projectId", projectId).select("chapterData");
|
const novelData = await u.db("o_novel").where("projectId", projectId).select("chapterData");
|
||||||
if (!novelData || novelData.length === 0) return res.status(400).send(error("请先上传小说"));
|
if (!novelData || novelData.length === 0) return res.status(400).send(error("请先上传小说"));
|
||||||
await u.db("o_script").whereIn("id", scriptIds).update({
|
await u.db("o_script").whereIn("id", scriptIds).update({
|
||||||
|
|||||||
@ -13,9 +13,9 @@ export default router.post(
|
|||||||
}),
|
}),
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { projectId, agentType } = req.body;
|
const { projectId, agentType } = req.body;
|
||||||
const data = await u.db("o_agentWorkData").where({ projectId: projectId, key: agentType }).first();
|
const row = await u.db("o_agentWorkData").where({ projectId: projectId, key: agentType }).first();
|
||||||
|
|
||||||
if (!data) {
|
if (!row) {
|
||||||
await u.db("o_agentWorkData").insert({
|
await u.db("o_agentWorkData").insert({
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
key: agentType,
|
key: agentType,
|
||||||
@ -31,6 +31,9 @@ export default router.post(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
res.status(200).send(success(JSON.parse(data.data ?? "{}")));
|
const data = JSON.parse(row.data ?? "{}");
|
||||||
|
data.script = await u.db("o_script").where({ projectId }).select("id", "name", "content");
|
||||||
|
|
||||||
|
res.status(200).send(success(data));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -23,6 +23,19 @@ export default router.post(
|
|||||||
.update({
|
.update({
|
||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
const script = data.script;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
script.map(async (s: any) => {
|
||||||
|
const row = await u.db("o_script").where({ projectId, name: s.name }).first();
|
||||||
|
if (row) {
|
||||||
|
await u.db("o_script").where({ id: row.id }).update({ content: s.content });
|
||||||
|
} else {
|
||||||
|
await u.db("o_script").insert({ projectId, name: s.name, content: s.content });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
res.status(200).send(success());
|
res.status(200).send(success());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,102 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const buildSkillFileName = (name: string) => {
|
|
||||||
const trimmed = name.trim();
|
|
||||||
const fileName = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`;
|
|
||||||
const normalized = fileName.replace(/\\/g, "/");
|
|
||||||
if (!normalized || normalized.includes("/")) {
|
|
||||||
throw new Error("技能名称不能包含路径分隔符");
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildRelativePath = (type: "main" | "references", fileName: string) => {
|
|
||||||
return type === "references" ? path.posix.join("references", fileName) : fileName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveSkillFilePath = (relativePath: string) => {
|
|
||||||
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
||||||
if (normalizedPath.startsWith("references/")) {
|
|
||||||
return path.join(u.getPath("skills"), normalizedPath);
|
|
||||||
}
|
|
||||||
return path.join(u.getPath("skills"), normalizedPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveState = (description: string, attributions: string[]) => {
|
|
||||||
if (!description.trim()) return -1;
|
|
||||||
if (attributions.length === 0) return -2;
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default router.post(
|
|
||||||
"/",
|
|
||||||
validateFields({
|
|
||||||
name: z.string().min(1).max(100),
|
|
||||||
description: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
attributions: z.array(z.string()).optional(),
|
|
||||||
type: z.enum(["main", "references"]).optional(),
|
|
||||||
}),
|
|
||||||
async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { name, description, content, attributions, type } = req.body;
|
|
||||||
const finalType: "main" | "references" = type === "main" ? "main" : "references";
|
|
||||||
const finalDescription = description ?? "";
|
|
||||||
const finalContent = content ?? "";
|
|
||||||
const rawAttributions = Array.isArray(attributions) ? attributions : [];
|
|
||||||
const finalAttributions = Array.from(
|
|
||||||
new Set(rawAttributions.filter((item: unknown): item is string => typeof item === "string" && item.trim().length > 0)),
|
|
||||||
);
|
|
||||||
const fileName = buildSkillFileName(name);
|
|
||||||
const relativePath = buildRelativePath(finalType, fileName);
|
|
||||||
const skillId = crypto.createHash("md5").update(relativePath).digest("hex");
|
|
||||||
const md5 = crypto.createHash("md5").update(finalContent).digest("hex");
|
|
||||||
const filePath = resolveSkillFilePath(relativePath);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
const existed = await u.db("o_skillList").where("id", skillId).first();
|
|
||||||
if (existed) {
|
|
||||||
return res.status(400).send(error("技能已存在,请使用其他名称"));
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
||||||
await fs.writeFile(filePath, finalContent, "utf-8");
|
|
||||||
|
|
||||||
await u.db("o_skillList").insert({
|
|
||||||
id: skillId,
|
|
||||||
md5,
|
|
||||||
path: relativePath,
|
|
||||||
name: path.basename(fileName, ".md"),
|
|
||||||
description: finalDescription,
|
|
||||||
embedding: null,
|
|
||||||
type: finalType,
|
|
||||||
createTime: now,
|
|
||||||
updateTime: now,
|
|
||||||
state: resolveState(finalDescription, finalAttributions),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (finalAttributions.length > 0) {
|
|
||||||
await u.db("o_skillAttribution").insert(
|
|
||||||
finalAttributions.map((attribution: string) => ({
|
|
||||||
skillId,
|
|
||||||
attribution,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send(success("新增技能成功"));
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(400).send(error(err?.message || "新增技能失败"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const resolveSkillFilePath = (type: string, relativePath: string) => {
|
|
||||||
const normalizedPath = (relativePath || "").replace(/\\/g, "/");
|
|
||||||
const isPrefixedReferencePath = normalizedPath.startsWith("references/");
|
|
||||||
if (type === "references" && !isPrefixedReferencePath) {
|
|
||||||
return path.join(u.getPath(["skills", "references"]), normalizedPath);
|
|
||||||
}
|
|
||||||
return path.join(u.getPath("skills"), normalizedPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default router.post(
|
|
||||||
"/",
|
|
||||||
validateFields({
|
|
||||||
id: z.string().min(1),
|
|
||||||
}),
|
|
||||||
async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { id } = req.body;
|
|
||||||
const skill = await u.db("o_skillList").where("id", id).first();
|
|
||||||
|
|
||||||
if (!skill) {
|
|
||||||
return res.status(404).send(error("技能不存在"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = resolveSkillFilePath(skill.type, skill.path || "");
|
|
||||||
await u.db("o_skillList").where("id", id).delete();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fs.unlink(filePath);
|
|
||||||
} catch {
|
|
||||||
// 文件不存在时可忽略,数据库记录已删除
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send(success("删除技能成功"));
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(400).send(error(err?.message || "删除技能失败"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import { getEmbedding } from "@/utils/agent/embedding";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
export default router.post(
|
|
||||||
"/",
|
|
||||||
validateFields({
|
|
||||||
id: z.string(),
|
|
||||||
}),
|
|
||||||
async (req, res) => {
|
|
||||||
const { id } = req.body;
|
|
||||||
|
|
||||||
const skill = await u.db("o_skillList").where("id", id).first();
|
|
||||||
|
|
||||||
if (!skill) return res.status(404).send(error("技能不存在"));
|
|
||||||
if (skill.embedding) return res.status(400).send(error("技能已存在向量,请勿重复生成"));
|
|
||||||
if (!skill.description) return res.status(400).send(error("技能描述不存在"));
|
|
||||||
const embedding = await getEmbedding(skill.description);
|
|
||||||
await u
|
|
||||||
.db("o_skillList")
|
|
||||||
.where("id", id)
|
|
||||||
.update({ embedding: JSON.stringify(embedding) });
|
|
||||||
|
|
||||||
res.status(200).send(success("技能向量生成成功"));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const resolveSkillFilePath = (type: string, relativePath: string) => {
|
|
||||||
const normalizedPath = (relativePath || "").replace(/\\/g, "/");
|
|
||||||
const isPrefixedReferencePath = normalizedPath.startsWith("references/");
|
|
||||||
if (type === "references" && !isPrefixedReferencePath) {
|
|
||||||
return path.join(u.getPath(["skills", "references"]), normalizedPath);
|
|
||||||
}
|
|
||||||
return path.join(u.getPath("skills"), normalizedPath);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default router.post(
|
|
||||||
"/",
|
|
||||||
validateFields({
|
|
||||||
content: z.string(),
|
|
||||||
}),
|
|
||||||
async (req, res) => {
|
|
||||||
const { content } = req.body;
|
|
||||||
const result = await u.Ai.Text("universalAi").invoke({
|
|
||||||
system:
|
|
||||||
"你是一个文档摘要助手。根据给定的文档内容生成一句简洁的中文描述(不超过100字),概括文档的核心主题和用途。只输出描述文本,不要添加任何前缀或格式。",
|
|
||||||
messages: [{ role: "user", content: `内容:\n${content}` }],
|
|
||||||
});
|
|
||||||
const description = result.text.trim();
|
|
||||||
res.status(200).send(success(description));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
export default router.post(
|
|
||||||
"/",
|
|
||||||
validateFields({
|
|
||||||
page: z.number().int().min(1).default(1),
|
|
||||||
limit: z.number().int().min(1).max(100).default(20),
|
|
||||||
search: z.string().optional().default(""),
|
|
||||||
type: z.enum(["main", "references"]).optional(),
|
|
||||||
attributions: z.array(z.string()).optional(),
|
|
||||||
}),
|
|
||||||
async (req, res) => {
|
|
||||||
const { page, limit, search, type, attributions } = req.body;
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
let query = u.db("o_skillList");
|
|
||||||
let countQuery = u.db("o_skillList");
|
|
||||||
|
|
||||||
// 搜索条件
|
|
||||||
if (search) {
|
|
||||||
const searchPattern = `%${search}%`;
|
|
||||||
const whereBuilder = (builder: any) => {
|
|
||||||
builder
|
|
||||||
.where("name", "like", searchPattern)
|
|
||||||
.orWhere("path", "like", searchPattern)
|
|
||||||
.orWhere("description", "like", searchPattern);
|
|
||||||
};
|
|
||||||
query = query.where(whereBuilder);
|
|
||||||
countQuery = countQuery.where(whereBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// type 筛选条件
|
|
||||||
if (type) {
|
|
||||||
query = query.where("type", type);
|
|
||||||
countQuery = countQuery.where("type", type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// attributions 筛选条件
|
|
||||||
if (attributions && attributions.length > 0) {
|
|
||||||
const attributionSubQuery = function (this: any) {
|
|
||||||
this.select("skillId")
|
|
||||||
.from("o_skillAttribution")
|
|
||||||
.whereIn("attribution", attributions);
|
|
||||||
};
|
|
||||||
query = query.whereIn("id", attributionSubQuery);
|
|
||||||
countQuery = countQuery.whereIn("id", attributionSubQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询总数(在所有筛选条件应用后)
|
|
||||||
const [{ count }]: any = await countQuery.count("* as count");
|
|
||||||
|
|
||||||
// 查询列表
|
|
||||||
const list = await query
|
|
||||||
.select("*")
|
|
||||||
.orderByRaw(
|
|
||||||
`
|
|
||||||
CASE type WHEN 'main' THEN 1 ELSE 0 END ASC,
|
|
||||||
CASE WHEN id NOT IN (SELECT skillId FROM o_skillAttribution) THEN 0 ELSE 1 END ASC,
|
|
||||||
CASE WHEN state = 1 THEN 1 ELSE 0 END ASC,
|
|
||||||
updateTime DESC
|
|
||||||
`
|
|
||||||
)
|
|
||||||
.limit(limit)
|
|
||||||
.offset(offset);
|
|
||||||
|
|
||||||
// 查询每个技能的归属
|
|
||||||
const skillIds = list.map((item: any) => item.id);
|
|
||||||
const attributionsList = await u
|
|
||||||
.db("o_skillAttribution")
|
|
||||||
.whereIn("skillId", skillIds)
|
|
||||||
.select("skillId", "attribution");
|
|
||||||
|
|
||||||
// 将归属信息合并到列表中
|
|
||||||
const attributionMap = new Map<string, string[]>();
|
|
||||||
for (const attr of attributionsList) {
|
|
||||||
if (!attributionMap.has(attr.skillId!)) {
|
|
||||||
attributionMap.set(attr.skillId!, []);
|
|
||||||
}
|
|
||||||
attributionMap.get(attr.skillId!)!.push(attr.attribution!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录需要更新state的技能id
|
|
||||||
const missingFileIds: string[] = [];
|
|
||||||
|
|
||||||
const listWithAttributions = list.map((item: any) => {
|
|
||||||
const normalizedPath = (item.path || "").replace(/\\/g, "/");
|
|
||||||
const isPrefixedReferencePath = normalizedPath.startsWith("references/");
|
|
||||||
const skillFilePath =
|
|
||||||
item.type === "references" && !isPrefixedReferencePath
|
|
||||||
? path.join(u.getPath(["skills", "references"]), item.path!)
|
|
||||||
: path.join(u.getPath("skills"), item.path!);
|
|
||||||
|
|
||||||
let content = "";
|
|
||||||
let state = item.state;
|
|
||||||
|
|
||||||
// 检查文件是否存在
|
|
||||||
if (fs.existsSync(skillFilePath)) {
|
|
||||||
content = fs.readFileSync(skillFilePath, "utf-8");
|
|
||||||
} else {
|
|
||||||
state = -1;
|
|
||||||
if (item.state !== -1) {
|
|
||||||
missingFileIds.push(item.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
state,
|
|
||||||
attributions: attributionMap.get(item.id) || [],
|
|
||||||
content,
|
|
||||||
embedding: item.embedding ? true : false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 批量更新文件不存在的技能状态
|
|
||||||
if (missingFileIds.length > 0) {
|
|
||||||
await u
|
|
||||||
.db("o_skillList")
|
|
||||||
.whereIn("id", missingFileIds)
|
|
||||||
.update({ state: -1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send(
|
|
||||||
success({
|
|
||||||
list: listWithAttributions,
|
|
||||||
total: Number(count),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import path from "path";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import crypto from "crypto";
|
|
||||||
import { success } from "@/lib/responseFormat";
|
|
||||||
import fg from "fast-glob";
|
|
||||||
import getPath from "@/utils/getPath";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
export default router.post("/", async (req, res) => {
|
|
||||||
const skillsRoot = getPath(["skills"]);
|
|
||||||
const referencesRoot = path.join(skillsRoot, "references");
|
|
||||||
|
|
||||||
const [mainEntries, referenceEntries] = await Promise.all([
|
|
||||||
fg("*.md", {
|
|
||||||
cwd: skillsRoot.replace(/\\/g, "/"),
|
|
||||||
onlyFiles: true,
|
|
||||||
}),
|
|
||||||
fg("**/*.md", {
|
|
||||||
cwd: referencesRoot.replace(/\\/g, "/"),
|
|
||||||
onlyFiles: true,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const scanItems = [
|
|
||||||
...mainEntries.map((entry) => ({
|
|
||||||
entry,
|
|
||||||
relativePath: entry,
|
|
||||||
fullPath: path.join(skillsRoot, entry),
|
|
||||||
type: "main",
|
|
||||||
})),
|
|
||||||
...referenceEntries.map((entry) => ({
|
|
||||||
entry,
|
|
||||||
relativePath: path.posix.join("references", entry.replace(/\\/g, "/")),
|
|
||||||
fullPath: path.join(referencesRoot, entry),
|
|
||||||
type: "references",
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
let insertedCount = 0;
|
|
||||||
let updatedCount = 0;
|
|
||||||
let removedCount = 0;
|
|
||||||
|
|
||||||
const scannedPaths = new Set<string>();
|
|
||||||
const existingRows = await u.db("o_skillList").whereIn("type", ["main", "references"]).select("id", "md5", "type", "path");
|
|
||||||
|
|
||||||
for (const item of scanItems) {
|
|
||||||
scannedPaths.add(item.relativePath);
|
|
||||||
|
|
||||||
const existing = existingRows.find((row: any) => row.path === item.relativePath);
|
|
||||||
const content = await fs.readFile(item.fullPath, "utf-8");
|
|
||||||
const md5 = crypto.createHash("md5").update(content).digest("hex");
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
const id = crypto.createHash("md5").update(item.relativePath).digest("hex");
|
|
||||||
const name = path.basename(item.entry, ".md");
|
|
||||||
await u.db("o_skillList").insert({
|
|
||||||
id,
|
|
||||||
path: item.relativePath,
|
|
||||||
name,
|
|
||||||
description: "",
|
|
||||||
embedding: null,
|
|
||||||
type: item.type,
|
|
||||||
createTime: now,
|
|
||||||
updateTime: now,
|
|
||||||
md5,
|
|
||||||
state: -1,
|
|
||||||
});
|
|
||||||
insertedCount++;
|
|
||||||
} else {
|
|
||||||
const updateData: Record<string, any> = { md5, updateTime: now };
|
|
||||||
if (existing.md5 !== md5) {
|
|
||||||
updateData.state = -3;
|
|
||||||
}
|
|
||||||
await u.db("o_skillList").where("id", existing.id).update(updateData);
|
|
||||||
updatedCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removedIds = existingRows.filter((row: any) => !scannedPaths.has(row.path)).map((row: any) => row.id);
|
|
||||||
if (removedIds.length > 0) {
|
|
||||||
await u.db("o_skillList").whereIn("id", removedIds).update({ state: -4, updateTime: now });
|
|
||||||
removedCount = removedIds.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [{ noDescriptionSkillCount }]: any = await u
|
|
||||||
.db("o_skillList")
|
|
||||||
.where("type", "references")
|
|
||||||
.andWhere((builder: any) => {
|
|
||||||
builder.whereNull("description").orWhere("description", "");
|
|
||||||
})
|
|
||||||
.count({ noDescriptionSkillCount: "*" });
|
|
||||||
|
|
||||||
const [{ noAttributionSkillCount }]: any = await u
|
|
||||||
.db("o_skillList as sl")
|
|
||||||
.leftJoin("o_skillAttribution as sa", "sl.id", "sa.skillId")
|
|
||||||
.where("sl.type", "references")
|
|
||||||
.whereNull("sa.skillId")
|
|
||||||
.countDistinct({ noAttributionSkillCount: "sl.id" });
|
|
||||||
|
|
||||||
res.status(200).send(
|
|
||||||
success({
|
|
||||||
message: "更新技能文档成功",
|
|
||||||
insertedCount,
|
|
||||||
updatedCount,
|
|
||||||
removedCount,
|
|
||||||
totalFiles: scanItems.length,
|
|
||||||
noDescriptionSkillCount: Number(noDescriptionSkillCount),
|
|
||||||
noAttributionSkillCount: Number(noAttributionSkillCount),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
|
||||||
import { validateFields } from "@/middleware/middleware";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
import crypto from "crypto";
|
|
||||||
import { getEmbedding } from "@/utils/agent/embedding";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const buildSkillFileName = (name: string) => {
|
|
||||||
const trimmed = name.trim();
|
|
||||||
const fileName = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`;
|
|
||||||
const normalized = fileName.replace(/\\/g, "/");
|
|
||||||
if (!normalized || normalized.includes("/")) {
|
|
||||||
throw new Error("技能名称不能包含路径分隔符");
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildRelativePath = (type: string, fileName: string) => {
|
|
||||||
return type === "references" ? path.posix.join("references", fileName) : fileName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveSkillFilePath = (relativePath: string) => {
|
|
||||||
return path.join(u.getPath("skills"), relativePath.replace(/\\/g, "/"));
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveState = (description: string, attributions: string[]) => {
|
|
||||||
if (!description.trim()) return -1;
|
|
||||||
if (attributions.length === 0) return -2;
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default router.post(
|
|
||||||
"/",
|
|
||||||
validateFields({
|
|
||||||
id: z.string().min(1),
|
|
||||||
name: z.string().min(1).max(100),
|
|
||||||
description: z.string().optional(),
|
|
||||||
content: z.string().optional(),
|
|
||||||
attributions: z.array(z.string()).optional(),
|
|
||||||
}),
|
|
||||||
async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { id, name, description, content, attributions } = req.body;
|
|
||||||
const current = await u.db("o_skillList").where("id", id).first();
|
|
||||||
|
|
||||||
if (!current) {
|
|
||||||
return res.status(404).send(error("技能不存在"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalDescription = description ?? "";
|
|
||||||
const finalContent = content ?? "";
|
|
||||||
const rawAttributions = Array.isArray(attributions) ? attributions : [];
|
|
||||||
const finalAttributions = Array.from(
|
|
||||||
new Set(rawAttributions.filter((item: unknown): item is string => typeof item === "string" && item.trim().length > 0)),
|
|
||||||
);
|
|
||||||
const fileName = buildSkillFileName(name);
|
|
||||||
const relativePath = buildRelativePath(current.type, fileName);
|
|
||||||
const nextId = crypto.createHash("md5").update(relativePath).digest("hex");
|
|
||||||
const md5 = crypto.createHash("md5").update(finalContent).digest("hex");
|
|
||||||
const oldFilePath = resolveSkillFilePath(current.path);
|
|
||||||
const newFilePath = resolveSkillFilePath(relativePath);
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
if (nextId !== id) {
|
|
||||||
const conflict = await u.db("o_skillList").where("id", nextId).first();
|
|
||||||
if (conflict) {
|
|
||||||
return res.status(400).send(error("技能名称冲突,请使用其他名称"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.mkdir(path.dirname(newFilePath), { recursive: true });
|
|
||||||
if (oldFilePath !== newFilePath) {
|
|
||||||
try {
|
|
||||||
await fs.rename(oldFilePath, newFilePath);
|
|
||||||
} catch {
|
|
||||||
// 文件不存在时直接按新路径写入即可
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await fs.writeFile(newFilePath, finalContent, "utf-8");
|
|
||||||
|
|
||||||
if (nextId !== id) {
|
|
||||||
await u.db("o_skillAttribution").where("skillId", id).update({ skillId: nextId });
|
|
||||||
}
|
|
||||||
|
|
||||||
await u
|
|
||||||
.db("o_skillList")
|
|
||||||
.where("id", id)
|
|
||||||
.update({
|
|
||||||
id: nextId,
|
|
||||||
path: relativePath,
|
|
||||||
name: path.basename(fileName, ".md"),
|
|
||||||
description: finalDescription,
|
|
||||||
md5,
|
|
||||||
updateTime: now,
|
|
||||||
state: resolveState(finalDescription, finalAttributions),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (finalDescription && !current.embedding) {
|
|
||||||
const embedding = await getEmbedding(finalDescription);
|
|
||||||
await u
|
|
||||||
.db("o_skillList")
|
|
||||||
.where("id", nextId)
|
|
||||||
.update({ embedding: JSON.stringify(embedding) });
|
|
||||||
}
|
|
||||||
|
|
||||||
await u.db("o_skillAttribution").where("skillId", nextId).delete();
|
|
||||||
if (finalAttributions.length > 0) {
|
|
||||||
await u.db("o_skillAttribution").insert(
|
|
||||||
finalAttributions.map((attribution: string) => ({
|
|
||||||
skillId: nextId,
|
|
||||||
attribution,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(200).send(success("更新技能成功"));
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log(err);
|
|
||||||
res.status(400).send(error(err?.message || "更新技能失败"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
29
src/routes/setting/skillManagement/getSkillContent.ts
Normal file
29
src/routes/setting/skillManagement/getSkillContent.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { success, error } from "@/lib/responseFormat";
|
||||||
|
import { validateFields } from "@/middleware/middleware";
|
||||||
|
import { z } from "zod";
|
||||||
|
import isPathInside from "is-path-inside";
|
||||||
|
import u from "@/utils";
|
||||||
|
import p from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router.post(
|
||||||
|
"/",
|
||||||
|
validateFields({
|
||||||
|
path: z.string(),
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
const { path } = req.body;
|
||||||
|
const skillsRoot = u.getPath(["skills"]);
|
||||||
|
const filePath = p.join(skillsRoot, path);
|
||||||
|
if (!isPathInside(filePath, skillsRoot)) {
|
||||||
|
return res.status(400).send(error("无效的路径"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = await fs.promises.readFile(filePath, "utf-8");
|
||||||
|
|
||||||
|
res.status(200).send(success(raw));
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -1,13 +1,20 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import u from "@/utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { success, error } from "@/lib/responseFormat";
|
import { success, error } from "@/lib/responseFormat";
|
||||||
import { validateFields } from "@/middleware/middleware";
|
import { validateFields } from "@/middleware/middleware";
|
||||||
import fs from "fs";
|
import fg from "fast-glob";
|
||||||
import path from "path";
|
import u from "@/utils";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
export default router.post("/", validateFields({}), async (req, res) => {
|
export default router.post("/", async (req, res) => {
|
||||||
res.status(200).send(success({}));
|
const skillsRoot = u.getPath(["skills"]);
|
||||||
|
|
||||||
|
const entries = await fg("**/*.md", {
|
||||||
|
cwd: skillsRoot.replace(/\\/g, "/"),
|
||||||
|
onlyFiles: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("%c Line:15 🍺 entries", "background:#e41a6a", entries);
|
||||||
|
|
||||||
|
res.status(200).send(success(entries));
|
||||||
});
|
});
|
||||||
|
|||||||
34
src/routes/setting/skillManagement/saveSkillContent.ts
Normal file
34
src/routes/setting/skillManagement/saveSkillContent.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { success, error } from "@/lib/responseFormat";
|
||||||
|
import { validateFields } from "@/middleware/middleware";
|
||||||
|
import { z } from "zod";
|
||||||
|
import isPathInside from "is-path-inside";
|
||||||
|
import u from "@/utils";
|
||||||
|
import p from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router.post(
|
||||||
|
"/",
|
||||||
|
validateFields({
|
||||||
|
path: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
const { path, content } = req.body;
|
||||||
|
const skillsRoot = u.getPath(["skills"]);
|
||||||
|
const filePath = p.join(skillsRoot, path);
|
||||||
|
if (!isPathInside(filePath, skillsRoot)) {
|
||||||
|
return res.status(400).send(error("无效的路径"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return res.status(400).send(error("文件不存在"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = await fs.promises.writeFile(filePath, content, "utf-8");
|
||||||
|
|
||||||
|
res.status(200).send(success(raw));
|
||||||
|
},
|
||||||
|
);
|
||||||
@ -9,7 +9,7 @@ export default router.get("/", async (req, res) => {
|
|||||||
{
|
{
|
||||||
mainSkill: "production_agent_execution",
|
mainSkill: "production_agent_execution",
|
||||||
workspace: ["production_agent_skills/execution"],
|
workspace: ["production_agent_skills/execution"],
|
||||||
attachedSkills: ["production_agent_skills/execution/driector_art_skills/chinese_sweet_romance/driector_skills"],
|
attachedSkills: ["art_prompts/chinese_sweet_romance/driector_skills"],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,79 +0,0 @@
|
|||||||
import u from "@/utils";
|
|
||||||
import { Socket } from "socket.io";
|
|
||||||
|
|
||||||
class ResTool {
|
|
||||||
public socket: Socket;
|
|
||||||
public data: Record<string, any>;
|
|
||||||
constructor(socket: Socket, data: Record<string, any> = {}) {
|
|
||||||
this.socket = socket;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
textMessage(name: string = "AI") {
|
|
||||||
const messageId = u.uuid();
|
|
||||||
this.socket.emit("textMessage", {
|
|
||||||
type: "start",
|
|
||||||
messageId,
|
|
||||||
delta: null,
|
|
||||||
role: "assistant",
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
const handle = {
|
|
||||||
send: (delta: string) => {
|
|
||||||
this.socket.emit("textMessage", {
|
|
||||||
type: "content",
|
|
||||||
messageId,
|
|
||||||
delta,
|
|
||||||
role: "assistant",
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
return handle;
|
|
||||||
},
|
|
||||||
end: () => {
|
|
||||||
this.socket.emit("textMessage", {
|
|
||||||
type: "end",
|
|
||||||
messageId,
|
|
||||||
delta: null,
|
|
||||||
role: "assistant",
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
thinkMessage() {
|
|
||||||
const messageId = u.uuid();
|
|
||||||
this.socket.emit("thinkMessage", {
|
|
||||||
type: "start",
|
|
||||||
messageId,
|
|
||||||
delta: null,
|
|
||||||
role: "assistant",
|
|
||||||
});
|
|
||||||
const handle = {
|
|
||||||
send: (delta: string) => {
|
|
||||||
this.socket.emit("thinkMessage", {
|
|
||||||
type: "content",
|
|
||||||
messageId,
|
|
||||||
delta,
|
|
||||||
role: "assistant",
|
|
||||||
});
|
|
||||||
return handle;
|
|
||||||
},
|
|
||||||
end: () => {
|
|
||||||
this.socket.emit("thinkMessage", {
|
|
||||||
type: "end",
|
|
||||||
messageId,
|
|
||||||
delta: null,
|
|
||||||
role: "assistant",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
systemMessage(content: string) {
|
|
||||||
const messageId = u.uuid();
|
|
||||||
this.socket.emit("systemMessage", { messageId, content });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ResTool;
|
|
||||||
18
src/types/database.d.ts
vendored
18
src/types/database.d.ts
vendored
@ -1,19 +1,6 @@
|
|||||||
// @db-hash e24c7c99757472b92af11f26a2b2b8c7
|
// @db-hash 93b2462070c45c2b449e9a18c4e88763
|
||||||
//该文件由脚本自动生成,请勿手动修改
|
//该文件由脚本自动生成,请勿手动修改
|
||||||
|
|
||||||
export interface _o_project_old_20260328 {
|
|
||||||
'artStyle'?: string | null;
|
|
||||||
'createTime'?: number | null;
|
|
||||||
'id'?: number | null;
|
|
||||||
'imageModel'?: string | null;
|
|
||||||
'intro'?: string | null;
|
|
||||||
'name'?: string | null;
|
|
||||||
'projectType'?: string | null;
|
|
||||||
'type'?: string | null;
|
|
||||||
'userId'?: number | null;
|
|
||||||
'videoModel'?: string | null;
|
|
||||||
'videoRatio'?: string | null;
|
|
||||||
}
|
|
||||||
export interface memories {
|
export interface memories {
|
||||||
'content': string;
|
'content': string;
|
||||||
'createTime': number;
|
'createTime': number;
|
||||||
@ -34,7 +21,7 @@ export interface o_agentDeploy {
|
|||||||
'model'?: string | null;
|
'model'?: string | null;
|
||||||
'modelName'?: string | null;
|
'modelName'?: string | null;
|
||||||
'name'?: string | null;
|
'name'?: string | null;
|
||||||
'vendorId'?: number | null;
|
'vendorId'?: string | null;
|
||||||
}
|
}
|
||||||
export interface o_agentWorkData {
|
export interface o_agentWorkData {
|
||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
@ -245,7 +232,6 @@ export interface o_videoConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DB {
|
export interface DB {
|
||||||
"_o_project_old_20260328": _o_project_old_20260328;
|
|
||||||
"memories": memories;
|
"memories": memories;
|
||||||
"o_agentDeploy": o_agentDeploy;
|
"o_agentDeploy": o_agentDeploy;
|
||||||
"o_agentWorkData": o_agentWorkData;
|
"o_agentWorkData": o_agentWorkData;
|
||||||
|
|||||||
@ -1,200 +0,0 @@
|
|||||||
import { tool } from "ai";
|
|
||||||
import { z } from "zod";
|
|
||||||
import path from "path";
|
|
||||||
import fs from "fs/promises";
|
|
||||||
import isPathInside from "is-path-inside";
|
|
||||||
import u from "@/utils";
|
|
||||||
import getPath from "@/utils/getPath";
|
|
||||||
import { getEmbedding, cosineSimilarity } from "./embedding";
|
|
||||||
|
|
||||||
interface SkillRecord {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
location: string;
|
|
||||||
baseDir: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 解析 SKILL.md ====================
|
|
||||||
|
|
||||||
function parseFrontmatter(content: string): { name: string; description: string } {
|
|
||||||
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
||||||
if (!match?.[1]) throw new Error("No frontmatter found");
|
|
||||||
|
|
||||||
const result: Record<string, string> = {};
|
|
||||||
const lines = match[1].split("\n");
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; ) {
|
|
||||||
const colonIndex = lines[i].indexOf(":");
|
|
||||||
if (colonIndex === -1) {
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = lines[i].slice(0, colonIndex).trim();
|
|
||||||
if (!key) {
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = lines[i].slice(colonIndex + 1).trim();
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (/^[>|]-?$/.test(value)) {
|
|
||||||
const fold = value.startsWith(">");
|
|
||||||
const parts: string[] = [];
|
|
||||||
while (i < lines.length && /^\s+/.test(lines[i])) {
|
|
||||||
parts.push(lines[i].trim());
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
value = fold ? parts.join(" ") : parts.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
result[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description");
|
|
||||||
return { name: result.name, description: result.description };
|
|
||||||
}
|
|
||||||
|
|
||||||
type SkillAttribution =
|
|
||||||
| "production_agent_decision.md"
|
|
||||||
| "production_agent_execution.md"
|
|
||||||
| "production_agent_supervision.md"
|
|
||||||
| "script_agent_decision.md"
|
|
||||||
| "script_agent_execution.md"
|
|
||||||
| "script_agent_supervision.md"
|
|
||||||
| "universal_agent.md";
|
|
||||||
|
|
||||||
export async function useSkill(mainSkillName: SkillAttribution) {
|
|
||||||
const skillsRoot = getPath("skills");
|
|
||||||
const targetSkill = path.join(skillsRoot, mainSkillName);
|
|
||||||
|
|
||||||
if (!isPathInside(targetSkill, skillsRoot)) throw new Error("技能名称无效:检测到路径穿越");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const content = await fs.readFile(targetSkill, "utf-8");
|
|
||||||
const skill = { ...parseFrontmatter(content), location: targetSkill, baseDir: skillsRoot };
|
|
||||||
return { prompt: buildPrompt(skill), tools: createSkillTools(skill, mainSkillName) };
|
|
||||||
} catch {
|
|
||||||
throw new Error(`技能文件不存在:${mainSkillName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPrompt(skill: SkillRecord): string {
|
|
||||||
return `## Skills
|
|
||||||
以下技能提供了专业任务的专用指令。
|
|
||||||
当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。
|
|
||||||
加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。
|
|
||||||
|
|
||||||
<available_skills>
|
|
||||||
<skill>
|
|
||||||
<name>${skill.name}</name>
|
|
||||||
<description>${skill.description}</description>
|
|
||||||
</skill>
|
|
||||||
</available_skills>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSkillTools(skill: SkillRecord, mainSkillName: string) {
|
|
||||||
const activated = new Set<string>();
|
|
||||||
return {
|
|
||||||
activate_skill: tool({
|
|
||||||
description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skill.name}`,
|
|
||||||
inputSchema: z.object({
|
|
||||||
name: z.enum([skill.name] as [string, ...string[]]).describe("要激活的技能名称"),
|
|
||||||
}),
|
|
||||||
execute: async ({ name }) => {
|
|
||||||
if (activated.has(name)) {
|
|
||||||
console.log(`[Skill] ℹ️ 技能 "${name}" 已激活,跳过重复注入`);
|
|
||||||
return { alreadyActive: true, message: `技能 "${name}" 已激活,无需重复加载` };
|
|
||||||
}
|
|
||||||
let raw: string;
|
|
||||||
try {
|
|
||||||
raw = await fs.readFile(skill.location, "utf-8");
|
|
||||||
} catch {
|
|
||||||
console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`);
|
|
||||||
return { error: `无法读取技能文件:${name}` };
|
|
||||||
}
|
|
||||||
const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
||||||
|
|
||||||
const resources = await u
|
|
||||||
.db("o_skillList")
|
|
||||||
.distinct("o_skillList.path")
|
|
||||||
.innerJoin("o_skillAttribution", "o_skillList.id", "o_skillAttribution.skillId")
|
|
||||||
.where("o_skillList.state", 1)
|
|
||||||
.andWhere("o_skillAttribution.attribution", mainSkillName);
|
|
||||||
|
|
||||||
activated.add(name);
|
|
||||||
console.log(`[Skill] 📖 已激活:${name}(${body.length} 字符,${resources.length} 资源)`);
|
|
||||||
let content = "";
|
|
||||||
content = `<skill_content name="${name}">\n`;
|
|
||||||
content += body + "\n\n";
|
|
||||||
content += `Skill directory: ${skill.baseDir}\n`;
|
|
||||||
content += "相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。\n";
|
|
||||||
if (resources.length > 0) {
|
|
||||||
content += "\n<skill_resources>\n";
|
|
||||||
for (const { path } of resources) {
|
|
||||||
content += ` <file>${path}</file>\n`;
|
|
||||||
}
|
|
||||||
content += "</skill_resources>\n";
|
|
||||||
}
|
|
||||||
content += "\n<skill_tools_guide>\n";
|
|
||||||
content += "- read_skill_file:读取上方 skill_resources 中列出的资源文件。\n";
|
|
||||||
content += "- discover_skill_docs:当上方资源不足以完成任务时,使用关键词检索更多相关文档。传入与当前任务相关的关键词列表即可获取推荐。\n";
|
|
||||||
content += "</skill_tools_guide>\n";
|
|
||||||
content += "</skill_content>";
|
|
||||||
return { content };
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
discover_skill_docs: tool({
|
|
||||||
description: "根据关键词主动发现全部技能文档(MD),返回相关度排序的推荐列表。适用于技能指令中未明确指定资源文件但需要补充信息的场景。",
|
|
||||||
inputSchema: z.object({
|
|
||||||
keywords: z.array(z.string().max(100)).min(1).max(20).describe("用于检索技能文档的关键词列表"),
|
|
||||||
topK: z.number().int().min(1).max(20).default(5).describe("返回推荐文档数量"),
|
|
||||||
}),
|
|
||||||
execute: async ({ keywords, topK }) => {
|
|
||||||
const queryText = keywords.join(" ");
|
|
||||||
const queryVec = await getEmbedding(queryText);
|
|
||||||
|
|
||||||
const activeRows = await u.db("o_skillList").where("state", 1).whereNotNull("embedding").select();
|
|
||||||
const scored = activeRows
|
|
||||||
.map((row) => {
|
|
||||||
const emb = JSON.parse(row.embedding!) as number[];
|
|
||||||
return {
|
|
||||||
name: row.name,
|
|
||||||
filePath: row.path,
|
|
||||||
type: row.type,
|
|
||||||
description: row.description,
|
|
||||||
score: cosineSimilarity(queryVec, emb),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a, b) => b.score - a.score)
|
|
||||||
.slice(0, topK);
|
|
||||||
|
|
||||||
console.log(`[Skill] ✅ discover_skill_docs 返回 ${scored.length} 条推荐`);
|
|
||||||
return { recommendations: scored };
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
read_skill_file: tool({
|
|
||||||
description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。",
|
|
||||||
inputSchema: z.object({
|
|
||||||
skillName: z.string().describe("技能名称"),
|
|
||||||
filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"),
|
|
||||||
}),
|
|
||||||
execute: async ({ skillName, filePath: relPath }) => {
|
|
||||||
const fullPath = path.resolve(path.join(skill.baseDir, relPath));
|
|
||||||
if (!isPathInside(fullPath, skill.baseDir)) {
|
|
||||||
console.log(`[Skill] 🚫 路径越界已拦截:"${relPath}" 超出技能目录范围`);
|
|
||||||
return { error: "Access denied: path is outside skill directory" };
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const fileContent = await fs.readFile(fullPath, "utf-8");
|
|
||||||
console.log(`[Skill] 📄 已读取文件:${skillName}/${relPath}(${fileContent.length} 字符)`);
|
|
||||||
return { content: fileContent };
|
|
||||||
} catch {
|
|
||||||
console.log(`[Skill] ❌ 读取失败:未找到文件 "${relPath}"`);
|
|
||||||
return { error: `File not found: ${relPath}` };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -7,9 +7,11 @@ import * as fs from "fs";
|
|||||||
|
|
||||||
type SkillAttribution =
|
type SkillAttribution =
|
||||||
//剧本Agent
|
//剧本Agent
|
||||||
| "script_agent_decision"
|
| "script_agent_decision" //决策
|
||||||
| "script_agent_execution"
|
| "script_execution_skeleton" //故事骨架
|
||||||
| "script_agent_supervision"
|
| "script_execution_adaptation" //改变策略
|
||||||
|
| "script_execution_script" //剧本生成
|
||||||
|
| "script_agent_supervision" //审核
|
||||||
//生产Agent
|
//生产Agent
|
||||||
| "production_agent_decision"
|
| "production_agent_decision"
|
||||||
| "production_agent_execution"
|
| "production_agent_execution"
|
||||||
@ -39,52 +41,89 @@ function ensureNonEmptyBody(body: string, fallback: string): string {
|
|||||||
// ==================== 解析 SKILL.md ====================
|
// ==================== 解析 SKILL.md ====================
|
||||||
|
|
||||||
function parseFrontmatter(content: string): { name: string; description: string } {
|
function parseFrontmatter(content: string): { name: string; description: string } {
|
||||||
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
const match = content.match(/^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
|
||||||
if (!match?.[1]) throw new Error("No frontmatter found");
|
if (!match?.[1]) {
|
||||||
|
throw new Error(`技能文件缺少有效的 frontmatter,确保以 --- 包裹并包含 name 和 description 字段。${content}`);
|
||||||
const result: Record<string, string> = {};
|
}
|
||||||
const lines = match[1].split("\n");
|
|
||||||
|
const result: Record<string, string> = {};
|
||||||
for (let i = 0; i < lines.length; ) {
|
const lines = match[1].split(/\r?\n/);
|
||||||
const colonIndex = lines[i].indexOf(":");
|
|
||||||
if (colonIndex === -1) {
|
for (let i = 0; i < lines.length; ) {
|
||||||
i++;
|
const line = lines[i];
|
||||||
continue;
|
const trimmed = line.trim();
|
||||||
}
|
|
||||||
|
if (!trimmed || trimmed.startsWith("#")) {
|
||||||
const key = lines[i].slice(0, colonIndex).trim();
|
i++;
|
||||||
if (!key) {
|
continue;
|
||||||
i++;
|
}
|
||||||
continue;
|
|
||||||
}
|
const keyMatch = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
|
||||||
|
if (!keyMatch) {
|
||||||
let value = lines[i].slice(colonIndex + 1).trim();
|
i++;
|
||||||
i++;
|
continue;
|
||||||
|
}
|
||||||
if (/^[>|]-?$/.test(value)) {
|
|
||||||
const fold = value.startsWith(">");
|
const key = keyMatch[1].trim();
|
||||||
const parts: string[] = [];
|
const rawValue = (keyMatch[2] ?? "").trim();
|
||||||
while (i < lines.length && /^\s+/.test(lines[i])) {
|
i++;
|
||||||
parts.push(lines[i].trim());
|
|
||||||
i++;
|
if (!key) continue;
|
||||||
}
|
|
||||||
value = fold ? parts.join(" ") : parts.join("\n");
|
if (/^[>|][+-]?[0-9]*$/.test(rawValue)) {
|
||||||
}
|
const isFolded = rawValue.startsWith(">");
|
||||||
|
const blockLines: string[] = [];
|
||||||
result[key] = value;
|
let blockIndent: number | null = null;
|
||||||
|
|
||||||
|
while (i < lines.length) {
|
||||||
|
const current = lines[i];
|
||||||
|
const currentTrimmed = current.trim();
|
||||||
|
|
||||||
|
if (currentTrimmed === "") {
|
||||||
|
if (blockIndent !== null) blockLines.push("");
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentIndent = current.match(/^\s*/)?.[0].length ?? 0;
|
||||||
|
if (blockIndent === null) {
|
||||||
|
blockIndent = currentIndent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndent < blockIndent) break;
|
||||||
|
|
||||||
|
blockLines.push(current.slice(blockIndent));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result[key] = isFolded
|
||||||
|
? blockLines
|
||||||
|
.join("\n")
|
||||||
|
.replace(/\n{2,}/g, "\n\n")
|
||||||
|
.replace(/([^\n])\n([^\n])/g, "$1 $2")
|
||||||
|
.trim()
|
||||||
|
: blockLines.join("\n").trim();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unquoted = rawValue.replace(/^(['"])([\s\S]*)\1$/, "$2");
|
||||||
|
result[key] = unquoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.name || !result.description) {
|
||||||
|
throw new Error(`技能文件缺少必要字段: name 或 description,确保 frontmatter 包含这两个字段。${content}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description");
|
|
||||||
return { name: result.name, description: result.description };
|
return { name: result.name, description: result.description };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function useSkill(input: SkillInput, mem?: string) {
|
export async function useSkill(input: SkillInput) {
|
||||||
const { mainSkill, workspace = [], attachedSkills = [] } = input;
|
const { mainSkill, workspace = [], attachedSkills = [] } = input;
|
||||||
const rootDir = getPath("skills");
|
const rootDir = getPath("skills");
|
||||||
const normalizedRootDir = path.resolve(rootDir);
|
const normalizedRootDir = path.resolve(rootDir);
|
||||||
const mainPath = path.join(rootDir, mainSkill + ".md");
|
const mainPath = path.join(rootDir, mainSkill + ".md");
|
||||||
if (!fs.existsSync(mainPath)) throw new Error(`主技能文件不存在: ${mainPath}`);
|
if (!fs.existsSync(mainPath)) throw new Error(`主技能文件不存在: ${mainPath}`);
|
||||||
if (!isPathInside(mainPath, normalizedRootDir)) throw new Error("技能名称无效:检测到路径穿越");
|
if (!isPathInside(mainPath, normalizedRootDir)) throw new Error(`技能名称无效:检测到路径穿越。${mainPath}`);
|
||||||
|
|
||||||
const resolveSafeSkillDir = (dir: string): string | null => {
|
const resolveSafeSkillDir = (dir: string): string | null => {
|
||||||
const resolvedDir = path.resolve(normalizedRootDir, dir);
|
const resolvedDir = path.resolve(normalizedRootDir, dir);
|
||||||
@ -115,7 +154,7 @@ export async function useSkill(input: SkillInput, mem?: string) {
|
|||||||
|
|
||||||
const content = await fs.promises.readFile(mainPath, "utf-8");
|
const content = await fs.promises.readFile(mainPath, "utf-8");
|
||||||
const skill = parseFrontmatter(content);
|
const skill = parseFrontmatter(content);
|
||||||
return { prompt: buildPrompt(skill), tools: createSkillTools(skill, skillPaths, mem) };
|
return { prompt: buildPrompt(skill), tools: createSkillTools(skill, skillPaths), skillPaths };
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPrompt(skill: { name: string; description: string }): string {
|
function buildPrompt(skill: { name: string; description: string }): string {
|
||||||
@ -132,7 +171,7 @@ function buildPrompt(skill: { name: string; description: string }): string {
|
|||||||
</available_skills>`;
|
</available_skills>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSkillTools(skill: { name: string; description: string }, skillPaths: SkillPaths, mem?: string) {
|
function createSkillTools(skill: { name: string; description: string }, skillPaths: SkillPaths) {
|
||||||
const activated = new Set<string>(); // 已激活技能集合,防止重复加载
|
const activated = new Set<string>(); // 已激活技能集合,防止重复加载
|
||||||
const skillsRootDir = path.resolve(getPath("skills"));
|
const skillsRootDir = path.resolve(getPath("skills"));
|
||||||
return {
|
return {
|
||||||
@ -168,10 +207,6 @@ function createSkillTools(skill: { name: string; description: string }, skillPat
|
|||||||
content += "</skill_resources>\n";
|
content += "</skill_resources>\n";
|
||||||
}
|
}
|
||||||
content += "</skill_content>";
|
content += "</skill_content>";
|
||||||
if (mem) {
|
|
||||||
content += `\n<memory>\n` + mem + `\n</memory>`;
|
|
||||||
}
|
|
||||||
console.log("%c Line:173 🍕 content", "background:#fca650", content);
|
|
||||||
return { content };
|
return { content };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -213,7 +248,6 @@ function createSkillTools(skill: { name: string; description: string }, skillPat
|
|||||||
content += "</skill_resources>\n";
|
content += "</skill_resources>\n";
|
||||||
}
|
}
|
||||||
content += "</skill_content>";
|
content += "</skill_content>";
|
||||||
console.log("%c Line:214 🍕 content", "background:#6ec1c2", content);
|
|
||||||
return { content };
|
return { content };
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -4,10 +4,10 @@ import axios from "axios";
|
|||||||
import { transform } from "sucrase";
|
import { transform } from "sucrase";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
|
|
||||||
type AiType = "scriptAgent" | "productionAgent" | "universalAgent";
|
type AiType = "scriptAgent" | "productionAgent" | "universalAi";
|
||||||
type FnName = "textRequest" | "imageRequest" | "videoRequest" | "ttsRequest";
|
type FnName = "textRequest" | "imageRequest" | "videoRequest" | "ttsRequest";
|
||||||
|
|
||||||
const AiTypeValues: AiType[] = ["scriptAgent", "productionAgent", "universalAgent"];
|
const AiTypeValues: AiType[] = ["scriptAgent", "productionAgent", "universalAi"];
|
||||||
async function resolveModelName(value: AiType | `${string}:${string}`): Promise<`${string}:${string}`> {
|
async function resolveModelName(value: AiType | `${string}:${string}`): Promise<`${string}:${string}`> {
|
||||||
if (AiTypeValues.includes(value as AiType)) {
|
if (AiTypeValues.includes(value as AiType)) {
|
||||||
const agentDeployData = await u.db("o_agentDeploy").where("key", value).first();
|
const agentDeployData = await u.db("o_agentDeploy").where("key", value).first();
|
||||||
|
|||||||
@ -56,7 +56,7 @@ class CleanNovel {
|
|||||||
|
|
||||||
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
|
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
|
||||||
const totalEvent: EventType[] = [];
|
const totalEvent: EventType[] = [];
|
||||||
const intansce = u.Ai.Text("universalAgent");
|
const intansce = u.Ai.Text("universalAi");
|
||||||
|
|
||||||
// 并发控制:通过信号量限制同时执行的任务数
|
// 并发控制:通过信号量限制同时执行的任务数
|
||||||
let running = 0;
|
let running = 0;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import isPathInside from "is-path-inside";
|
import isPathInside from "is-path-inside";
|
||||||
import getPath from "@/utils/getPath";
|
import getPath, { isEletron } from "@/utils/getPath";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
@ -49,7 +49,8 @@ class OSS {
|
|||||||
await this.ensureInit();
|
await this.ensureInit();
|
||||||
const safePath = normalizeUserPath(userRelPath);
|
const safePath = normalizeUserPath(userRelPath);
|
||||||
// URL 始终使用 /,所以这里需要将系统分隔符转回 /
|
// URL 始终使用 /,所以这里需要将系统分隔符转回 /
|
||||||
const url = process.env.OSSURL || `http://127.0.0.1:10588/`;
|
let url = process.env.OSSURL || `http://127.0.0.1:10588/`;
|
||||||
|
if (isEletron()) url = `http://localhost:${process.env.PORT}/`;
|
||||||
return `${url}${safePath.split(path.sep).join("/")}`;
|
return `${url}${safePath.split(path.sep).join("/")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
181
yarn.lock
181
yarn.lock
@ -372,6 +372,11 @@
|
|||||||
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c"
|
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c"
|
||||||
integrity sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==
|
integrity sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==
|
||||||
|
|
||||||
|
"@gar/promise-retry@^1.0.0":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.npmmirror.com/@gar/promise-retry/-/promise-retry-1.0.3.tgz#65e726428e794bc4453948e0a41e6de4215ce8b0"
|
||||||
|
integrity sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==
|
||||||
|
|
||||||
"@gar/promisify@^1.1.3":
|
"@gar/promisify@^1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||||
@ -638,6 +643,17 @@
|
|||||||
lru-cache "^10.0.1"
|
lru-cache "^10.0.1"
|
||||||
socks-proxy-agent "^8.0.3"
|
socks-proxy-agent "^8.0.3"
|
||||||
|
|
||||||
|
"@npmcli/agent@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/@npmcli/agent/-/agent-4.0.0.tgz#2bb2b1c0a170940511554a7986ae2a8be9fedcce"
|
||||||
|
integrity sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==
|
||||||
|
dependencies:
|
||||||
|
agent-base "^7.1.0"
|
||||||
|
http-proxy-agent "^7.0.0"
|
||||||
|
https-proxy-agent "^7.0.1"
|
||||||
|
lru-cache "^11.2.1"
|
||||||
|
socks-proxy-agent "^8.0.3"
|
||||||
|
|
||||||
"@npmcli/fs@^2.1.0":
|
"@npmcli/fs@^2.1.0":
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865"
|
resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865"
|
||||||
@ -653,6 +669,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
|
|
||||||
|
"@npmcli/fs@^5.0.0":
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-5.0.0.tgz#674619771907342b3d1ac197aaf1deeb657e3539"
|
||||||
|
integrity sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==
|
||||||
|
dependencies:
|
||||||
|
semver "^7.3.5"
|
||||||
|
|
||||||
"@npmcli/move-file@^2.0.0":
|
"@npmcli/move-file@^2.0.0":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4"
|
resolved "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4"
|
||||||
@ -661,6 +684,11 @@
|
|||||||
mkdirp "^1.0.4"
|
mkdirp "^1.0.4"
|
||||||
rimraf "^3.0.2"
|
rimraf "^3.0.2"
|
||||||
|
|
||||||
|
"@npmcli/redact@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/@npmcli/redact/-/redact-4.0.0.tgz#c91121e02b7559a997614a2c1057cd7fc67608c4"
|
||||||
|
integrity sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==
|
||||||
|
|
||||||
"@opentelemetry/api@1.9.0":
|
"@opentelemetry/api@1.9.0":
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
|
resolved "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
|
||||||
@ -988,6 +1016,11 @@ abbrev@^3.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/abbrev/-/abbrev-3.0.1.tgz#8ac8b3b5024d31464fe2a5feeea9f4536bf44025"
|
resolved "https://registry.npmmirror.com/abbrev/-/abbrev-3.0.1.tgz#8ac8b3b5024d31464fe2a5feeea9f4536bf44025"
|
||||||
integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==
|
integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==
|
||||||
|
|
||||||
|
abbrev@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/abbrev/-/abbrev-4.0.0.tgz#ec933f0e27b6cd60e89b5c6b2a304af42209bb05"
|
||||||
|
integrity sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==
|
||||||
|
|
||||||
accepts@^2.0.0:
|
accepts@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
|
resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
|
||||||
@ -1469,6 +1502,22 @@ cacache@^19.0.1:
|
|||||||
tar "^7.4.3"
|
tar "^7.4.3"
|
||||||
unique-filename "^4.0.0"
|
unique-filename "^4.0.0"
|
||||||
|
|
||||||
|
cacache@^20.0.1:
|
||||||
|
version "20.0.4"
|
||||||
|
resolved "https://registry.npmmirror.com/cacache/-/cacache-20.0.4.tgz#9b547dc3db0c1f87cba6dbbff91fb17181b4bbb1"
|
||||||
|
integrity sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==
|
||||||
|
dependencies:
|
||||||
|
"@npmcli/fs" "^5.0.0"
|
||||||
|
fs-minipass "^3.0.0"
|
||||||
|
glob "^13.0.0"
|
||||||
|
lru-cache "^11.1.0"
|
||||||
|
minipass "^7.0.3"
|
||||||
|
minipass-collect "^2.0.1"
|
||||||
|
minipass-flush "^1.0.5"
|
||||||
|
minipass-pipeline "^1.2.4"
|
||||||
|
p-map "^7.0.2"
|
||||||
|
ssri "^13.0.0"
|
||||||
|
|
||||||
cacheable-lookup@^5.0.3:
|
cacheable-lookup@^5.0.3:
|
||||||
version "5.0.4"
|
version "5.0.4"
|
||||||
resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
|
resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005"
|
||||||
@ -2569,6 +2618,15 @@ glob@^10.2.2:
|
|||||||
package-json-from-dist "^1.0.0"
|
package-json-from-dist "^1.0.0"
|
||||||
path-scurry "^1.11.1"
|
path-scurry "^1.11.1"
|
||||||
|
|
||||||
|
glob@^13.0.0:
|
||||||
|
version "13.0.6"
|
||||||
|
resolved "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d"
|
||||||
|
integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==
|
||||||
|
dependencies:
|
||||||
|
minimatch "^10.2.2"
|
||||||
|
minipass "^7.1.3"
|
||||||
|
path-scurry "^2.0.2"
|
||||||
|
|
||||||
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||||
version "7.2.3"
|
version "7.2.3"
|
||||||
resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||||
@ -2807,7 +2865,7 @@ iconv-lite@^0.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||||
|
|
||||||
iconv-lite@^0.7.0, iconv-lite@~0.7.0:
|
iconv-lite@^0.7.0, iconv-lite@^0.7.2, iconv-lite@~0.7.0:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e"
|
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e"
|
||||||
integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==
|
integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==
|
||||||
@ -2987,6 +3045,11 @@ isexe@^3.1.1:
|
|||||||
resolved "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz#42e368f68d5e10dadfee4fda7b550bc2d8892dc9"
|
resolved "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz#42e368f68d5e10dadfee4fda7b550bc2d8892dc9"
|
||||||
integrity sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==
|
integrity sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==
|
||||||
|
|
||||||
|
isexe@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz#48f6576af8e87a18feb796b7ed5e2e5903b43dca"
|
||||||
|
integrity sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==
|
||||||
|
|
||||||
jackspeak@^3.1.2:
|
jackspeak@^3.1.2:
|
||||||
version "3.4.3"
|
version "3.4.3"
|
||||||
resolved "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
|
resolved "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
|
||||||
@ -3227,6 +3290,11 @@ lru-cache@^10.0.1, lru-cache@^10.2.0:
|
|||||||
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
||||||
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
||||||
|
|
||||||
|
lru-cache@^11.0.0, lru-cache@^11.1.0, lru-cache@^11.2.1:
|
||||||
|
version "11.2.7"
|
||||||
|
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.7.tgz#9127402617f34cd6767b96daee98c28e74458d35"
|
||||||
|
integrity sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==
|
||||||
|
|
||||||
lru-cache@^6.0.0:
|
lru-cache@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||||
@ -3287,6 +3355,24 @@ make-fetch-happen@^14.0.3:
|
|||||||
promise-retry "^2.0.1"
|
promise-retry "^2.0.1"
|
||||||
ssri "^12.0.0"
|
ssri "^12.0.0"
|
||||||
|
|
||||||
|
make-fetch-happen@^15.0.0:
|
||||||
|
version "15.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz#b0e3dd53d487b2733e4ea232c2bebf1bd16afb03"
|
||||||
|
integrity sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==
|
||||||
|
dependencies:
|
||||||
|
"@gar/promise-retry" "^1.0.0"
|
||||||
|
"@npmcli/agent" "^4.0.0"
|
||||||
|
"@npmcli/redact" "^4.0.0"
|
||||||
|
cacache "^20.0.1"
|
||||||
|
http-cache-semantics "^4.1.1"
|
||||||
|
minipass "^7.0.2"
|
||||||
|
minipass-fetch "^5.0.0"
|
||||||
|
minipass-flush "^1.0.5"
|
||||||
|
minipass-pipeline "^1.2.4"
|
||||||
|
negotiator "^1.0.0"
|
||||||
|
proc-log "^6.0.0"
|
||||||
|
ssri "^13.0.0"
|
||||||
|
|
||||||
matcher@^3.0.0:
|
matcher@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
|
resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca"
|
||||||
@ -3366,7 +3452,7 @@ mimic-response@^3.1.0:
|
|||||||
resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
|
resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
|
||||||
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
|
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
|
||||||
|
|
||||||
minimatch@^10.0.3, minimatch@^10.2.1:
|
minimatch@^10.0.3, minimatch@^10.2.1, minimatch@^10.2.2:
|
||||||
version "10.2.4"
|
version "10.2.4"
|
||||||
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde"
|
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde"
|
||||||
integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==
|
integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==
|
||||||
@ -3435,6 +3521,17 @@ minipass-fetch@^4.0.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
encoding "^0.1.13"
|
encoding "^0.1.13"
|
||||||
|
|
||||||
|
minipass-fetch@^5.0.0:
|
||||||
|
version "5.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-5.0.2.tgz#3973a605ddfd8abb865e50d6fc634853c8239729"
|
||||||
|
integrity sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==
|
||||||
|
dependencies:
|
||||||
|
minipass "^7.0.3"
|
||||||
|
minipass-sized "^2.0.0"
|
||||||
|
minizlib "^3.0.1"
|
||||||
|
optionalDependencies:
|
||||||
|
iconv-lite "^0.7.2"
|
||||||
|
|
||||||
minipass-flush@^1.0.5:
|
minipass-flush@^1.0.5:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73"
|
resolved "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73"
|
||||||
@ -3456,6 +3553,13 @@ minipass-sized@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minipass "^3.0.0"
|
minipass "^3.0.0"
|
||||||
|
|
||||||
|
minipass-sized@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-2.0.0.tgz#2228ee97e3f74f6b22ba6d1319addb7621534306"
|
||||||
|
integrity sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==
|
||||||
|
dependencies:
|
||||||
|
minipass "^7.1.2"
|
||||||
|
|
||||||
minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
|
minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
|
||||||
version "3.3.6"
|
version "3.3.6"
|
||||||
resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
|
resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
|
||||||
@ -3468,7 +3572,7 @@ minipass@^5.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
|
resolved "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
|
||||||
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
|
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
|
||||||
|
|
||||||
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2:
|
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3:
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
|
resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
|
||||||
integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==
|
integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==
|
||||||
@ -3589,6 +3693,11 @@ node-addon-api@^3.1.0:
|
|||||||
resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||||
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
|
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
|
||||||
|
|
||||||
|
node-addon-api@^8.0.0:
|
||||||
|
version "8.7.0"
|
||||||
|
resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-8.7.0.tgz#f64f8413456ecbe900221305a3f883c37666473f"
|
||||||
|
integrity sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==
|
||||||
|
|
||||||
node-api-version@^0.1.4:
|
node-api-version@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.1.4.tgz#1ed46a485e462d55d66b5aa1fe2821720dedf080"
|
resolved "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.1.4.tgz#1ed46a485e462d55d66b5aa1fe2821720dedf080"
|
||||||
@ -3608,6 +3717,22 @@ node-gyp-build@^4.2.1:
|
|||||||
resolved "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
|
resolved "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
|
||||||
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
|
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
|
||||||
|
|
||||||
|
node-gyp@12.x:
|
||||||
|
version "12.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-12.2.0.tgz#ff73f6f509e33d8b7e768f889ffc9738ad117b07"
|
||||||
|
integrity sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==
|
||||||
|
dependencies:
|
||||||
|
env-paths "^2.2.0"
|
||||||
|
exponential-backoff "^3.1.1"
|
||||||
|
graceful-fs "^4.2.6"
|
||||||
|
make-fetch-happen "^15.0.0"
|
||||||
|
nopt "^9.0.0"
|
||||||
|
proc-log "^6.0.0"
|
||||||
|
semver "^7.3.5"
|
||||||
|
tar "^7.5.4"
|
||||||
|
tinyglobby "^0.2.12"
|
||||||
|
which "^6.0.0"
|
||||||
|
|
||||||
node-gyp@^11.2.0:
|
node-gyp@^11.2.0:
|
||||||
version "11.5.0"
|
version "11.5.0"
|
||||||
resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-11.5.0.tgz#82661b5f40647a7361efe918e3cea76d297fcc56"
|
resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-11.5.0.tgz#82661b5f40647a7361efe918e3cea76d297fcc56"
|
||||||
@ -3684,6 +3809,13 @@ nopt@^8.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
abbrev "^3.0.0"
|
abbrev "^3.0.0"
|
||||||
|
|
||||||
|
nopt@^9.0.0:
|
||||||
|
version "9.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/nopt/-/nopt-9.0.0.tgz#6bff0836b2964d24508b6b41b5a9a49c4f4a1f96"
|
||||||
|
integrity sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==
|
||||||
|
dependencies:
|
||||||
|
abbrev "^4.0.0"
|
||||||
|
|
||||||
normalize-package-data@^2.0.0:
|
normalize-package-data@^2.0.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||||
@ -3895,6 +4027,14 @@ path-scurry@^1.11.1:
|
|||||||
lru-cache "^10.2.0"
|
lru-cache "^10.2.0"
|
||||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||||
|
|
||||||
|
path-scurry@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85"
|
||||||
|
integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^11.0.0"
|
||||||
|
minipass "^7.1.2"
|
||||||
|
|
||||||
path-to-regexp@^8.0.0:
|
path-to-regexp@^8.0.0:
|
||||||
version "8.4.0"
|
version "8.4.0"
|
||||||
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.0.tgz#8e98fcd94826aff01a90c544ef74ffbaca3a78ed"
|
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.0.tgz#8e98fcd94826aff01a90c544ef74ffbaca3a78ed"
|
||||||
@ -3964,7 +4104,7 @@ possible-typed-array-names@^1.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
|
resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
|
||||||
integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
|
integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
|
||||||
|
|
||||||
prebuild-install@^7.1.1:
|
prebuild-install@^7.1.1, prebuild-install@^7.1.3:
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
|
resolved "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
|
||||||
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
|
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
|
||||||
@ -3987,6 +4127,11 @@ proc-log@^5.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8"
|
resolved "https://registry.npmmirror.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8"
|
||||||
integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==
|
integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==
|
||||||
|
|
||||||
|
proc-log@^6.0.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/proc-log/-/proc-log-6.1.0.tgz#18519482a37d5198e231133a70144a50f21f0215"
|
||||||
|
integrity sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
@ -4672,6 +4817,18 @@ sprintf-js@^1.1.2:
|
|||||||
resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
|
resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
|
||||||
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
|
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==
|
||||||
|
|
||||||
|
sqlite3@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/sqlite3/-/sqlite3-6.0.1.tgz#c0956e7834931c406b283c87b66771c847a6abfc"
|
||||||
|
integrity sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ==
|
||||||
|
dependencies:
|
||||||
|
bindings "^1.5.0"
|
||||||
|
node-addon-api "^8.0.0"
|
||||||
|
prebuild-install "^7.1.3"
|
||||||
|
tar "^7.5.10"
|
||||||
|
optionalDependencies:
|
||||||
|
node-gyp "12.x"
|
||||||
|
|
||||||
ssri@^12.0.0:
|
ssri@^12.0.0:
|
||||||
version "12.0.0"
|
version "12.0.0"
|
||||||
resolved "https://registry.npmmirror.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832"
|
resolved "https://registry.npmmirror.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832"
|
||||||
@ -4679,6 +4836,13 @@ ssri@^12.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minipass "^7.0.3"
|
minipass "^7.0.3"
|
||||||
|
|
||||||
|
ssri@^13.0.0:
|
||||||
|
version "13.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/ssri/-/ssri-13.0.1.tgz#2d8946614d33f4d0c84946bb370dce7a9379fd18"
|
||||||
|
integrity sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==
|
||||||
|
dependencies:
|
||||||
|
minipass "^7.0.3"
|
||||||
|
|
||||||
ssri@^9.0.0:
|
ssri@^9.0.0:
|
||||||
version "9.0.1"
|
version "9.0.1"
|
||||||
resolved "https://registry.npmmirror.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057"
|
resolved "https://registry.npmmirror.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057"
|
||||||
@ -4858,7 +5022,7 @@ tar@^6.0.5, tar@^6.1.11, tar@^6.1.2:
|
|||||||
mkdirp "^1.0.3"
|
mkdirp "^1.0.3"
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
tar@^7.0.1, tar@^7.4.3, tar@^7.5.6, tar@^7.5.7:
|
tar@^7.0.1, tar@^7.4.3, tar@^7.5.10, tar@^7.5.4, tar@^7.5.6, tar@^7.5.7:
|
||||||
version "7.5.13"
|
version "7.5.13"
|
||||||
resolved "https://registry.npmmirror.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d"
|
resolved "https://registry.npmmirror.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d"
|
||||||
integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==
|
integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==
|
||||||
@ -5199,6 +5363,13 @@ which@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe "^3.1.1"
|
isexe "^3.1.1"
|
||||||
|
|
||||||
|
which@^6.0.0:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/which/-/which-6.0.1.tgz#021642443a198fb93b784a5606721cb18cfcbfce"
|
||||||
|
integrity sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==
|
||||||
|
dependencies:
|
||||||
|
isexe "^4.0.0"
|
||||||
|
|
||||||
wide-align@^1.1.5:
|
wide-align@^1.1.5:
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
|
resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user