修改端口为10588

This commit is contained in:
ACT丶流星雨 2026-03-23 10:59:32 +08:00
parent c3dd7ade64
commit cd791ce92b
21 changed files with 389 additions and 104 deletions

View File

@ -174,7 +174,7 @@ docker-compose -f docker/docker-compose.local.yml up -d --build
| 端口 | 用途 | 在线部署映射 | 本地构建映射 |
| ------- | -------------- | ------------- | ------------- |
| `80` | Nginx 前端页面 | 随机端口 | `8080:80` |
| `60000` | 后端 API 服务 | `60000:60000` | `60000:60000` |
| `10588` | 后端 API 服务 | `10588:10588` | `10588:10588` |
### 数据持久化
@ -265,8 +265,8 @@ yarn build
"exec_mode": "cluster",
"env": {
"NODE_ENV": "prod",
"PORT": 60000,
"OSSURL": "http://127.0.0.1:60000/"
"PORT": 10588,
"OSSURL": "http://127.0.0.1:10588/"
}
}
```
@ -361,7 +361,7 @@ pm2 monit # 监控面板
yarn dev
```
> ⚠️ 此命令仅启动后端 API 服务(端口 60000**不包含前端页面**。直接访问 `http://localhost:60000` 只能调用 API 接口,无法看到完整的网页界面。如需同时使用前端页面,请配合前端项目单独启动,或使用下方的 GUI 模式。
> ⚠️ 此命令仅启动后端 API 服务(端口 10588**不包含前端页面**。直接访问 `http://localhost:10588` 只能调用 API 接口,无法看到完整的网页界面。如需同时使用前端页面,请配合前端项目单独启动,或使用下方的 GUI 模式。
- **方式二:启动 Electron 桌面客户端(推荐完整体验)**
@ -375,7 +375,7 @@ pm2 monit # 监控面板
| 命令 | 启动内容 | 前端页面 | 适用场景 |
| -------------- | ------------------------ | -------- | -------------------------------- |
| `yarn dev` | 仅后端 API端口 60000 | ❌ 无 | 后端开发调试、配合前端项目联调 |
| `yarn dev` | 仅后端 API端口 10588 | ❌ 无 | 后端开发调试、配合前端项目联调 |
| `yarn dev:gui` | 后端 + Electron 桌面端 | ✅ 内置 | 完整功能体验、桌面客户端开发调试 |
4. **项目打包**

View File

@ -5,17 +5,17 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
# Decision Agent
短剧漫剧制作的指挥层,负责整体决策和协调。接收用户需求后,先制定计划、获得用户确认,再将任务逐步交给执行层完成
短剧漫剧制作的指挥层,负责整体决策和协调。始终以用户当前指令为最终目标推进:默认直接协调执行,只有用户明确提出需要新增或修改拍摄计划时,才进入计划编辑与确认流程
## 可用工具
| 工具 | 说明 |
|------|------|
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `activate_skill` | 激活技能,加载完整指令和资源列表到上下文 |
| `read_skill_file` | 读取已激活技能目录下的参考资料文件 |
| `deepRetrieve` | 深度检索记忆,通过关键词回忆历史对话详情 |
| `run_sub_agent` | 启动子Agent执行任务可用`executionAI``supervisionAI` |
| `get_flowData` | 获取工作区数据key: `script` 剧本 / `assets` 资产列表) |
| `run_sub_agent` | 启动子 Agent 执行任务(可用:`executionAI``supervisionAI` |
| `get_flowData` | 获取工作区数据key: `script` 剧本 / `scriptPlan` 拍摄计划 / `assets` 资产列表 / `storyboardTable` 分镜表) |
| `get_flowData_schema` | 获取工作区数据的类型结构 |
| `set_flowData` | 保存数据到工作区lodash 路径) |
| `generate_assets_images` | 生成衍生资产图片(传入资产 id 列表) |
@ -27,30 +27,37 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
收到用户消息时,**先判断当前处于哪个阶段**,再决定下一步动作:
- **用户发起新的制作任务**(如"开始制作第4集"、"帮我拆分剧本"等明确的新需求) → 进入阶段一
- **用户确认计划**(如"可以"、"确认"、"开始吧"、"没问题"等) → 直接进入阶段三执行,**不要重新制定计划**
- **用户要求修改计划**(如"第2步改一下"、"加一个步骤"等) → 留在阶段二,修改后重新回复计划
- **用户发起执行类需求**(如"开始制作第 4 集"、"继续生成分镜"、"提取角色资产" → 直接进入阶段三,按用户目标执行
- **用户明确要求新增/修改拍摄计划**(如"给我出一版拍摄计划"、"第 2 步改一下"、"加一个镜头" → 进入阶段二,更新 `scriptPlan` 并与用户确认
- **用户确认拍摄计划**(如"可以"、"确认"、"开始吧"、"没问题" → 在不重做计划的前提下进入阶段三执行
**禁止**把用户的确认或简短回复当作新任务重新走阶段一
**禁止**在用户未提出计划诉求时,主动生成或反复重生成拍摄计划
### 阶段一:收集信息(仅新任务触发)
### 阶段一:收集信息(仅首次进入会话或上下文不足时触发)
1. 调用 `get_flowData` 获取当前工作区的剧本和资产数据,了解项目现状
2. 调用 `deepRetrieve` 检索相关历史记忆,了解已完成的工作进度
3. 使用 `read_skill_file` 加载 `references/plan.md` 获取计划制定规范
1. 调用 `get_flowData`key: `script`)获取当前剧本内容
2. 调用 `get_flowData`key: `scriptPlan`)获取已有拍摄计划(可能为空)
3. 调用 `get_flowData`key: `assets`)获取资产数据,了解项目现状
4. 调用 `deepRetrieve` 检索相关历史记忆,了解已完成的工作进度
5. 使用 `read_skill_file` 加载 `references/plan.md` 获取计划制定规范
### 阶段二:制定计划并确认
### 阶段二:编辑拍摄计划并对话确认(仅用户明确提出时触发)
1. 结合工作区数据、历史记忆和用户需求,按照 `plan.md` 的规范生成**结构化执行计划**
2. **将计划回复给用户**,请求确认
3. 如果用户要求调整,修改计划后重新回复,直到用户确认
4. 输出计划后**停止并等待用户回复**,不要自行继续
1. 根据剧本内容、工作区数据、历史记忆和用户需求,新增或修改**拍摄计划**scriptPlan
2. 调用 `set_flowData`key: `scriptPlan`, value: 最新拍摄计划文本)将拍摄计划同步到前端工作区
3. **将拍摄计划回复给用户**,请求确认
4. 输出拍摄计划后**停止并等待用户回复**,不要自行继续
5. 如果用户要求调整:
- 根据用户反馈修改拍摄计划
- 再次调用 `set_flowData`key: `scriptPlan`, value: 修改后的拍摄计划)同步到前端
- 重新回复修改后的拍摄计划,继续等待确认
- **循环此过程**,直到用户明确确认
### 阶段三:按计划执行(仅用户确认后触发)
### 阶段三:按用户目标执行(默认阶段
用户确认后,按步骤顺序逐步调用 `run_sub_agent` 工具:
以用户当前指令为目标,优先执行用户要求;若 `scriptPlan` 已存在则按其作为参考,不存在时也可直接执行当前任务。需要分步时再拆解为执行步骤,并按顺序调用 `run_sub_agent` 工具:
1. 每次调用 `run_sub_agent` 时,选择 `executionAI` 作为子Agent将当前步骤的任务描述作为 `prompt` 参数传入
1. 每次调用 `run_sub_agent` 时,选择 `executionAI` 作为子 Agent将当前步骤的任务描述作为 `prompt` 参数传入
2. 检查返回结果是否符合预期,不符合则调整指令重试
3. 将上一步的输出作为上下文传入下一步(如有依赖)
4. 全部步骤完成后,向用户汇报整体结果
@ -61,7 +68,10 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
- 复杂任务拆分为可独立执行的小步骤
- 关注步骤间的依赖关系,确保顺序合理
- 利用 `deepRetrieve` 检索历史记忆,避免重复已完成的工作
- **用户目标优先**:默认直接响应并推进用户当前任务,不要为了流程完整性而强制先生成计划
- **计划按需维护**:仅当用户明确要求新增/修改拍摄计划时,才更新 `scriptPlan`,且每次改动都调用 `set_flowData` 同步到前端
- **提取衍生资产后**:计划中必须包含"询问用户是否生成资产图片"步骤。若用户确认,执行层将调用 `generate_assets_images` 工具批量生成衍生资产图片
- **生成分镜表后**:计划中必须包含"询问用户是否生成分镜图片"步骤。若用户确认,执行层将调用 `generate_storyboard_images` 工具生成分镜图
## 参考资料

View File

@ -1,7 +1,7 @@
---
name: execution
description: >
用户需要拆分剧本或提取衍生资产时可以看此skill的参考资料了解拆分原则、衍生资产提取原则和示例
用户需要拆分剧本、提取衍生资产或生成分镜表时可以看此skill的参考资料了解拆分原则、衍生资产提取原则、分镜表生成规范和示例
---
# execution Agent
@ -14,6 +14,7 @@ description: >
- 拆分剧本
- 提取衍生资产(从剧本和已有角色资产中提取关联道具、场景物件等衍生资产)
- 生成分镜表(根据剧本和资产生成结构化的分镜表)
## 工作指引
@ -28,10 +29,21 @@ description: >
- 如果用户拒绝,跳过此步骤,流程结束
- 生成图片为异步操作,可以先回复用户"正在生成图片,稍后会自动更新",等图片生成完成后再通知用户查看
### 生成分镜表流程
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
2. 根据[分镜表生成](references/storyboard-generation.md)文档中的拆分原则和字段填写指引将剧本拆分为分镜填写每条分镜的所有字段id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds
3. 调用 `set_flowData({ key: "storyboardTable", value: 分镜数组 })` 一次性保存完整分镜表
4. 告知用户分镜表生成完成,列出分镜概要(总条数、主要场景)
5. **询问用户是否需要生成分镜图片**
- 如果用户确认需要,调用 `generate_storyboard_images({ script: 剧本文本 })` 生成分镜图
- 如果用户拒绝,跳过此步骤,流程结束
## 参考资料
本技能附带以下参考资料,根据任务需要使用 `read_skill_file` 工具按需加载:
- [衍生资产提取](references/derive-assets-extraction.md) — 从剧本和角色资产中提取衍生资产的原则和示例
- [分镜表生成](references/storyboard-generation.md) — 从剧本和资产生成分镜表的拆分原则、字段规范和示例
**注意**:根据用户当前任务选择性加载对应参考资料,不要一次性全部加载。

View File

@ -0,0 +1,248 @@
# 分镜表生成(从剧本 + 资产 → storyboardTable
本指南只做一件事:
根据剧本内容和已有资产,将剧本拆分为一系列分镜,生成结构化的分镜表。
> **核心概念**:分镜表是将剧本转化为视觉画面的中间产物。每条分镜对应一个独立的画面/镜头,包含画面描述、镜头语言、台词、音效和关联资产等信息,用于后续图片生成。
## 1. 输入与输出
### 输入
- 剧本文本(字符串),通过 `get_flowData("script")` 获取
- 已有资产列表(数组),通过 `get_flowData("assets")` 获取
### 输出
调用 `set_flowData` 将分镜表写入工作区:
```ts
set_flowData({
key: "storyboardTable",
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: "storyboardTable",
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. 工具调用顺序
1. `get_flowData("script")` — 获取剧本内容
2. `get_flowData("assets")` — 获取已有资产列表
3. 分析剧本,按照拆分原则划分分镜,并为每条分镜填写所有字段
4. 调用 `set_flowData({ key: "storyboardTable", value: 分镜数组 })` 一次性保存完整分镜表
5. 向用户汇报分镜表概要(总共多少条分镜,覆盖的场景概括)
6. **询问用户是否需要生成分镜图片**
- 如果用户确认,调用 `generate_storyboard_images({ script: 剧本文本 })` 生成分镜图
- 如果用户拒绝,跳过此步骤,流程结束
## 6. 注意事项
- 分镜数量与剧本长度成正比,一般每 50~100 字剧本对应 1~2 条分镜
- prompt 必须使用英文,且只描述视觉内容
- `associateAssetsIds` 使用资产数组的索引0-based确保索引不越界
- 如果剧本中出现了资产列表中不存在的角色/物件,仍要在分镜中描述,但不要在 `associateAssetsIds` 中编造不存在的索引
- 分镜的顺序应与剧本的叙事顺序一致
- 合理使用三种 frameMode大部分分镜使用 `firstFrame`,涉及动作结果的用 `endFrame`,以对话为主的用 `linesSoundEffects`

View File

@ -114,7 +114,7 @@ EOF
ENV NODE_ENV=prod
EXPOSE 80
EXPOSE 60000
EXPOSE 10588
# 启动时创建必要目录(防止 volume 挂载覆盖)
CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf"

View File

@ -88,7 +88,7 @@ EOF
ENV NODE_ENV=prod
EXPOSE 80
EXPOSE 60000
EXPOSE 10588
# 启动时创建必要目录(防止 volume 挂载覆盖)
CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf"

View File

@ -11,7 +11,7 @@ services:
restart: unless-stopped
ports:
- "8080:80"
- "60000:60000"
- "10588:10588"
environment:
- NODE_ENV=prod
volumes:

View File

@ -12,7 +12,7 @@ services:
restart: unless-stopped
ports:
- "80"
- "60000:60000"
- "10588:10588"
environment:
- NODE_ENV=prod
volumes:

View File

@ -175,8 +175,8 @@ Create a `pm2.json` file:
"exec_mode": "cluster",
"env": {
"NODE_ENV": "prod",
"PORT": 60000,
"OSSURL": "http://127.0.0.1:60000/"
"PORT": 10588,
"OSSURL": "http://127.0.0.1:10588/"
}
}
```
@ -257,13 +257,13 @@ To deploy or customize the frontend separately, refer to the frontend repo:
- Launch dev server with Node.js:
```bash
yarn dev #port 60000
yarn dev #port 10588
```
- Use Bun to quickly start dev server:
```bash
yarn bun:dev #port 60000
yarn bun:dev #port 10588
```
4. **Build the Project**

4
env/.env.dev vendored
View File

@ -1,4 +1,4 @@
NODE_ENV=dev
PORT=60000
OSSURL=http://127.0.0.1:60000/
PORT=10588
OSSURL=http://127.0.0.1:10588/

4
env/.env.prod vendored
View File

@ -1,4 +1,4 @@
NODE_ENV=prod
PORT=60000
OSSURL=http://127.0.0.1:60000/
PORT=10588
OSSURL=http://127.0.0.1:10588/

View File

@ -14,7 +14,7 @@ if (!fs.existsSync(envDir)) {
fs.mkdirSync(envDir, { recursive: true });
}
if (!fs.existsSync(envFile)) {
const defaultEnv = `NODE_ENV=${process.env.NODE_ENV}\nPORT=60000\nOSSURL=http://127.0.0.1:60000/\n`;
const defaultEnv = `NODE_ENV=${process.env.NODE_ENV}\nPORT=10588\nOSSURL=http://127.0.0.1:10588/\n`;
fs.writeFileSync(envFile, defaultEnv, "utf8");
console.log(`📄 已自动创建环境变量文件: ${envFile}`);
}

View File

@ -4,7 +4,7 @@ import startServe, { closeServe } from "src/app";
import { number } from "zod";
// 默认端口配置
const defaultPort = 60000;
const defaultPort = 10588;
function createMainWindow(port: any): void {
const win = new BrowserWindow({
@ -35,7 +35,7 @@ function createMainWindow(port: any): void {
app.whenReady().then(async () => {
try {
const port = await startServe(false);
createMainWindow(60000);
createMainWindow(10588);
} catch (err) {
console.error("[服务启动失败]:", err);
// 如果服务启动失败,使用默认端口创建窗口

File diff suppressed because one or more lines are too long

View File

@ -42,7 +42,7 @@ export async function decisionAI(ctx: AgentContext) {
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
const prefixSystem = `请回复用户收到以后直接调用run_sub_agent运行**executionAI**执行用户的任务`;
const prefixSystem = `以用户当前指令为最终目标。默认直接推进执行仅当用户明确要求新增或修改拍摄计划时才调用set_flowData更新scriptPlan并与用户确认。需要执行任务时调用run_sub_agent运行**executionAI**。`;
const { textStream } = await u.Ai.Text("productionAgent").stream({
system: prefixSystem + systemPrompt,

View File

@ -5,24 +5,16 @@ import { Socket } from "socket.io";
const deriveSchema = z.object({ name: z.string().min(1).max(20), desc: z.string().min(1).max(100) });
const assetSchema = z.object({ assetsId: z.string(), name: z.string(), desc: z.string(), src: z.string(), derive: z.array(deriveSchema).optional() });
const storyboardTableSchema = z.array(
z.object({
id: z.number(),
title: z.string(),
description: z.string(),
camera: z.string(),
duration: z.number(),
frameMode: z.enum(["firstFrame", "endFrame", "linesSoundEffects"]),
lines: z.string().nullable(),
sound: z.string().nullable(),
associateAssetsIds: z.array(z.number()),
}),
);
const flowDataSchema = z.object({ script: z.string(), assets: z.array(assetSchema), storyboardTable: storyboardTableSchema });
const storyboardTableSchema = z.string().describe("分镜表的markdown文本");
const flowDataSchema = z.object({ script: z.string(), scriptPlan: z.string(), assets: z.array(assetSchema), storyboardTable: storyboardTableSchema });
type FlowData = z.infer<typeof flowDataSchema>;
const keySchema = z.object({ key: z.enum(["script", "assets", "storyboardTable"]).describe("script=剧本,assets=资产列表,storyboardTable=分镜表") });
const keySchema = z.object({
key: z
.enum(["script", "scriptPlan", "assets", "storyboardTable"])
.describe("script=剧本,scriptPlan=拍摄计划,assets=资产列表,storyboardTable=分镜表"),
});
const valueSchema = z
.union([z.string(), z.array(assetSchema), assetSchema, z.array(deriveSchema), z.array(storyboardTableSchema)])
.describe("路径对应的值");
@ -66,20 +58,48 @@ export default (socket: Socket, toolsNames?: string[]) => {
return true;
},
}),
generate_assets_images: tool({
description: "生成衍生资产的图片",
inputSchema: z.object({ ids: z.array(z.string()).describe("需要生成的资产id列表") }),
execute: async ({ ids }) => {
console.log("[tools] generated_assets", ids);
return new Promise((resolve) => socket.emit("generatedAssets", { ids }, (res: any) => resolve(res)));
generate_storyboard_images: tool({
description: `生成一组图片任务,支持图片间的依赖关系(以图生图)。
- images: 图片任务数组
- id: 图片唯一标识符
- prompt: 图片生成提示词
- referenceIds: 依赖的参考图id数组[]
- assetIds: 参考的资产图id数组
1. referenceIds中的id必须存在于images数组中
2. A依赖BB依赖A
3.
images: [
{id: "cat", prompt: "一只橘猫", referenceIds: [], assetIds: []},
{id: "dog", prompt: "风格相同的金毛犬", referenceIds: ["cat"], assetIds: []}
]`,
inputSchema: z.object({
images: z.array(
z.object({
id: z.string().describe("图片唯一标识符"),
prompt: z.string().describe("图片生成提示词"),
referenceIds: z.array(z.string()).describe("依赖的参考图id数组无依赖填空数组[]"),
assetIds: z.array(z.number()).optional().describe("参考的资产图"),
}),
),
}),
execute: async ({ images }) => {
console.log("[tools] generated_assets", images);
return new Promise((resolve) => socket.emit("generatedAssets", { images }, (res: any) => resolve(res)));
},
}),
generate_storyboard_images: tool({
generate_assets_images: tool({
description: "生成分镜图",
inputSchema: z.object({ script: z.string().describe("剧本文本") }),
execute: async ({ script }) => {
console.log("[tools] generate_storyboard_images", script);
return new Promise((resolve) => socket.emit("generateStoryboardImages", { script }, (res: any) => resolve(res)));
inputSchema: z.object({ images: z.array(z.object({ assetId: z.number(), prompt: z.string() })) }),
execute: async ({ images }) => {
console.log("[tools] generate_assets_images", images);
return new Promise((resolve) => socket.emit("generateAssetsImages", { images }, (res: any) => resolve(res)));
},
}),
};

View File

@ -1,4 +1,4 @@
import "./logger";
// import "./logger";
import "./err";
import "./env";
import express, { Request, Response, NextFunction } from "express";
@ -75,7 +75,7 @@ export default async function startServe(randomPort: Boolean = false) {
res.status(err.status || 500).send(err);
});
const port = randomPort ? 0 : parseInt(process.env.PORT || "60000");
const port = randomPort ? 0 : parseInt(process.env.PORT || "10588");
return await new Promise((resolve) => {
server.listen(port, async () => {
const address = server.address();

View File

@ -3,8 +3,8 @@ import path from "path";
// 默认环境变量(当 env 文件不存在时自动创建)
const defaultEnvValues: Record<string, string> = {
dev: `NODE_ENV=dev\nPORT=60000\nOSSURL=http://127.0.0.1:60000/`,
prod: `NODE_ENV=prod\nPORT=60000\nOSSURL=http://127.0.0.1:60000/`,
dev: `NODE_ENV=dev\nPORT=10588\nOSSURL=http://127.0.0.1:10588/`,
prod: `NODE_ENV=prod\nPORT=10588\nOSSURL=http://127.0.0.1:10588/`,
};
// 判断是否为打包后的 Electron 环境

View File

@ -13,7 +13,6 @@ export default router.post(
async (req, res) => {
const { projectId } = req.body;
const storyboardData = await u.db("o_storyboard");
console.log("%c Line:16 🍖 storyboardData", "background:#ed9ec7", storyboardData);
const data = await Promise.all(
storyboardData.map(async (i) => {
return {

View File

@ -1,4 +1,4 @@
// @db-hash 8af8e41e3ca0cb5ee554944515d72ba8
// @db-hash bd46e7c381481a74efedc662a4f9049f
//该文件由脚本自动生成,请勿手动修改
export interface memories {
@ -163,13 +163,8 @@ export interface o_skills {
}
export interface o_storyboard {
'createTime'?: number | null;
'detail'?: string | null;
'filePath'?: string | null;
'frameType'?: string | null;
'id'?: number;
'name'?: string | null;
'prompt'?: string | null;
'seconds'?: string | null;
}
export interface o_storyboardFlow {
'flowData': string;

View File

@ -49,7 +49,7 @@ class OSS {
await this.ensureInit();
const safePath = normalizeUserPath(userRelPath);
// URL 始终使用 /,所以这里需要将系统分隔符转回 /
const url = process.env.OSSURL || `http://127.0.0.1:60000/`;
const url = process.env.OSSURL || `http://127.0.0.1:10588/`;
return `${url}${safePath.split(path.sep).join("/")}`;
}