修改端口为10588
This commit is contained in:
parent
c3dd7ade64
commit
cd791ce92b
10
README.md
10
README.md
@ -174,7 +174,7 @@ docker-compose -f docker/docker-compose.local.yml up -d --build
|
|||||||
| 端口 | 用途 | 在线部署映射 | 本地构建映射 |
|
| 端口 | 用途 | 在线部署映射 | 本地构建映射 |
|
||||||
| ------- | -------------- | ------------- | ------------- |
|
| ------- | -------------- | ------------- | ------------- |
|
||||||
| `80` | Nginx 前端页面 | 随机端口 | `8080:80` |
|
| `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",
|
"exec_mode": "cluster",
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "prod",
|
"NODE_ENV": "prod",
|
||||||
"PORT": 60000,
|
"PORT": 10588,
|
||||||
"OSSURL": "http://127.0.0.1:60000/"
|
"OSSURL": "http://127.0.0.1:10588/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -361,7 +361,7 @@ pm2 monit # 监控面板
|
|||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
> ⚠️ 此命令仅启动后端 API 服务(端口 60000),**不包含前端页面**。直接访问 `http://localhost:60000` 只能调用 API 接口,无法看到完整的网页界面。如需同时使用前端页面,请配合前端项目单独启动,或使用下方的 GUI 模式。
|
> ⚠️ 此命令仅启动后端 API 服务(端口 10588),**不包含前端页面**。直接访问 `http://localhost:10588` 只能调用 API 接口,无法看到完整的网页界面。如需同时使用前端页面,请配合前端项目单独启动,或使用下方的 GUI 模式。
|
||||||
|
|
||||||
- **方式二:启动 Electron 桌面客户端(推荐完整体验)**
|
- **方式二:启动 Electron 桌面客户端(推荐完整体验)**
|
||||||
|
|
||||||
@ -375,7 +375,7 @@ pm2 monit # 监控面板
|
|||||||
|
|
||||||
| 命令 | 启动内容 | 前端页面 | 适用场景 |
|
| 命令 | 启动内容 | 前端页面 | 适用场景 |
|
||||||
| -------------- | ------------------------ | -------- | -------------------------------- |
|
| -------------- | ------------------------ | -------- | -------------------------------- |
|
||||||
| `yarn dev` | 仅后端 API(端口 60000) | ❌ 无 | 后端开发调试、配合前端项目联调 |
|
| `yarn dev` | 仅后端 API(端口 10588) | ❌ 无 | 后端开发调试、配合前端项目联调 |
|
||||||
| `yarn dev:gui` | 后端 + Electron 桌面端 | ✅ 内置 | 完整功能体验、桌面客户端开发调试 |
|
| `yarn dev:gui` | 后端 + Electron 桌面端 | ✅ 内置 | 完整功能体验、桌面客户端开发调试 |
|
||||||
|
|
||||||
4. **项目打包**
|
4. **项目打包**
|
||||||
|
|||||||
@ -5,21 +5,21 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
|
|||||||
|
|
||||||
# Decision Agent
|
# Decision Agent
|
||||||
|
|
||||||
短剧漫剧制作的指挥层,负责整体决策和协调。接收用户需求后,先制定计划、获得用户确认,再将任务逐步交给执行层完成。
|
短剧漫剧制作的指挥层,负责整体决策和协调。始终以用户当前指令为最终目标推进:默认直接协调执行,只有用户明确提出需要新增或修改拍摄计划时,才进入计划编辑与确认流程。
|
||||||
|
|
||||||
## 可用工具
|
## 可用工具
|
||||||
|
|
||||||
| 工具 | 说明 |
|
| 工具 | 说明 |
|
||||||
|------|------|
|
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||||||
| `activate_skill` | 激活技能,加载完整指令和资源列表到上下文 |
|
| `activate_skill` | 激活技能,加载完整指令和资源列表到上下文 |
|
||||||
| `read_skill_file` | 读取已激活技能目录下的参考资料文件 |
|
| `read_skill_file` | 读取已激活技能目录下的参考资料文件 |
|
||||||
| `deepRetrieve` | 深度检索记忆,通过关键词回忆历史对话详情 |
|
| `deepRetrieve` | 深度检索记忆,通过关键词回忆历史对话详情 |
|
||||||
| `run_sub_agent` | 启动子Agent执行任务(可用:`executionAI`、`supervisionAI`) |
|
| `run_sub_agent` | 启动子 Agent 执行任务(可用:`executionAI`、`supervisionAI`) |
|
||||||
| `get_flowData` | 获取工作区数据(key: `script` 剧本 / `assets` 资产列表) |
|
| `get_flowData` | 获取工作区数据(key: `script` 剧本 / `scriptPlan` 拍摄计划 / `assets` 资产列表 / `storyboardTable` 分镜表) |
|
||||||
| `get_flowData_schema` | 获取工作区数据的类型结构 |
|
| `get_flowData_schema` | 获取工作区数据的类型结构 |
|
||||||
| `set_flowData` | 保存数据到工作区(lodash 路径) |
|
| `set_flowData` | 保存数据到工作区(lodash 路径) |
|
||||||
| `generate_assets_images` | 生成衍生资产图片(传入资产 id 列表) |
|
| `generate_assets_images` | 生成衍生资产图片(传入资产 id 列表) |
|
||||||
| `generate_storyboard_images` | 生成分镜图(传入剧本文本) |
|
| `generate_storyboard_images` | 生成分镜图(传入剧本文本) |
|
||||||
|
|
||||||
## 核心工作流程(必须严格遵循)
|
## 核心工作流程(必须严格遵循)
|
||||||
|
|
||||||
@ -27,30 +27,37 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
|
|||||||
|
|
||||||
收到用户消息时,**先判断当前处于哪个阶段**,再决定下一步动作:
|
收到用户消息时,**先判断当前处于哪个阶段**,再决定下一步动作:
|
||||||
|
|
||||||
- **用户发起新的制作任务**(如"开始制作第4集"、"帮我拆分剧本"等明确的新需求) → 进入阶段一
|
- **用户发起执行类需求**(如"开始制作第 4 集"、"继续生成分镜"、"提取角色资产") → 直接进入阶段三,按用户目标执行
|
||||||
- **用户确认计划**(如"可以"、"确认"、"开始吧"、"没问题"等) → 直接进入阶段三执行,**不要重新制定计划**
|
- **用户明确要求新增/修改拍摄计划**(如"给我出一版拍摄计划"、"第 2 步改一下"、"加一个镜头") → 进入阶段二,更新 `scriptPlan` 并与用户确认
|
||||||
- **用户要求修改计划**(如"第2步改一下"、"加一个步骤"等) → 留在阶段二,修改后重新回复计划
|
- **用户确认拍摄计划**(如"可以"、"确认"、"开始吧"、"没问题") → 在不重做计划的前提下进入阶段三执行
|
||||||
|
|
||||||
**禁止**:把用户的确认或简短回复当作新任务重新走阶段一。
|
**禁止**:在用户未提出计划诉求时,主动生成或反复重生成拍摄计划。
|
||||||
|
|
||||||
### 阶段一:收集信息(仅新任务触发)
|
### 阶段一:收集信息(仅首次进入会话或上下文不足时触发)
|
||||||
|
|
||||||
1. 调用 `get_flowData` 获取当前工作区的剧本和资产数据,了解项目现状
|
1. 调用 `get_flowData`(key: `script`)获取当前剧本内容
|
||||||
2. 调用 `deepRetrieve` 检索相关历史记忆,了解已完成的工作进度
|
2. 调用 `get_flowData`(key: `scriptPlan`)获取已有拍摄计划(可能为空)
|
||||||
3. 使用 `read_skill_file` 加载 `references/plan.md` 获取计划制定规范
|
3. 调用 `get_flowData`(key: `assets`)获取资产数据,了解项目现状
|
||||||
|
4. 调用 `deepRetrieve` 检索相关历史记忆,了解已完成的工作进度
|
||||||
|
5. 使用 `read_skill_file` 加载 `references/plan.md` 获取计划制定规范
|
||||||
|
|
||||||
### 阶段二:制定计划并确认
|
### 阶段二:编辑拍摄计划并对话确认(仅用户明确提出时触发)
|
||||||
|
|
||||||
1. 结合工作区数据、历史记忆和用户需求,按照 `plan.md` 的规范生成**结构化执行计划**
|
1. 根据剧本内容、工作区数据、历史记忆和用户需求,新增或修改**拍摄计划**(scriptPlan)
|
||||||
2. **将计划回复给用户**,请求确认
|
2. 调用 `set_flowData`(key: `scriptPlan`, value: 最新拍摄计划文本)将拍摄计划同步到前端工作区
|
||||||
3. 如果用户要求调整,修改计划后重新回复,直到用户确认
|
3. **将拍摄计划回复给用户**,请求确认
|
||||||
4. 输出计划后**停止并等待用户回复**,不要自行继续
|
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. 检查返回结果是否符合预期,不符合则调整指令重试
|
2. 检查返回结果是否符合预期,不符合则调整指令重试
|
||||||
3. 将上一步的输出作为上下文传入下一步(如有依赖)
|
3. 将上一步的输出作为上下文传入下一步(如有依赖)
|
||||||
4. 全部步骤完成后,向用户汇报整体结果
|
4. 全部步骤完成后,向用户汇报整体结果
|
||||||
@ -61,7 +68,10 @@ description: 短剧漫剧制作决策层。负责分析用户需求、制定执
|
|||||||
- 复杂任务拆分为可独立执行的小步骤
|
- 复杂任务拆分为可独立执行的小步骤
|
||||||
- 关注步骤间的依赖关系,确保顺序合理
|
- 关注步骤间的依赖关系,确保顺序合理
|
||||||
- 利用 `deepRetrieve` 检索历史记忆,避免重复已完成的工作
|
- 利用 `deepRetrieve` 检索历史记忆,避免重复已完成的工作
|
||||||
|
- **用户目标优先**:默认直接响应并推进用户当前任务,不要为了流程完整性而强制先生成计划
|
||||||
|
- **计划按需维护**:仅当用户明确要求新增/修改拍摄计划时,才更新 `scriptPlan`,且每次改动都调用 `set_flowData` 同步到前端
|
||||||
- **提取衍生资产后**:计划中必须包含"询问用户是否生成资产图片"步骤。若用户确认,执行层将调用 `generate_assets_images` 工具批量生成衍生资产图片
|
- **提取衍生资产后**:计划中必须包含"询问用户是否生成资产图片"步骤。若用户确认,执行层将调用 `generate_assets_images` 工具批量生成衍生资产图片
|
||||||
|
- **生成分镜表后**:计划中必须包含"询问用户是否生成分镜图片"步骤。若用户确认,执行层将调用 `generate_storyboard_images` 工具生成分镜图
|
||||||
|
|
||||||
## 参考资料
|
## 参考资料
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: execution
|
name: execution
|
||||||
description: >
|
description: >
|
||||||
用户需要拆分剧本或提取衍生资产时可以看此skill的参考资料,了解拆分原则、衍生资产提取原则和示例
|
用户需要拆分剧本、提取衍生资产或生成分镜表时可以看此skill的参考资料,了解拆分原则、衍生资产提取原则、分镜表生成规范和示例
|
||||||
---
|
---
|
||||||
|
|
||||||
# execution Agent
|
# 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` 工具按需加载:
|
本技能附带以下参考资料,根据任务需要使用 `read_skill_file` 工具按需加载:
|
||||||
|
|
||||||
- [衍生资产提取](references/derive-assets-extraction.md) — 从剧本和角色资产中提取衍生资产的原则和示例
|
- [衍生资产提取](references/derive-assets-extraction.md) — 从剧本和角色资产中提取衍生资产的原则和示例
|
||||||
|
- [分镜表生成](references/storyboard-generation.md) — 从剧本和资产生成分镜表的拆分原则、字段规范和示例
|
||||||
|
|
||||||
**注意**:根据用户当前任务选择性加载对应参考资料,不要一次性全部加载。
|
**注意**:根据用户当前任务选择性加载对应参考资料,不要一次性全部加载。
|
||||||
|
|||||||
@ -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`
|
||||||
@ -114,7 +114,7 @@ EOF
|
|||||||
ENV NODE_ENV=prod
|
ENV NODE_ENV=prod
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 60000
|
EXPOSE 10588
|
||||||
|
|
||||||
# 启动时创建必要目录(防止 volume 挂载覆盖)
|
# 启动时创建必要目录(防止 volume 挂载覆盖)
|
||||||
CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf"
|
CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf"
|
||||||
|
|||||||
@ -88,7 +88,7 @@ EOF
|
|||||||
ENV NODE_ENV=prod
|
ENV NODE_ENV=prod
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 60000
|
EXPOSE 10588
|
||||||
|
|
||||||
# 启动时创建必要目录(防止 volume 挂载覆盖)
|
# 启动时创建必要目录(防止 volume 挂载覆盖)
|
||||||
CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf"
|
CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf"
|
||||||
|
|||||||
@ -11,7 +11,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
- "60000:60000"
|
- "10588:10588"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=prod
|
- NODE_ENV=prod
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@ -12,7 +12,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "80"
|
- "80"
|
||||||
- "60000:60000"
|
- "10588:10588"
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=prod
|
- NODE_ENV=prod
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@ -175,8 +175,8 @@ Create a `pm2.json` file:
|
|||||||
"exec_mode": "cluster",
|
"exec_mode": "cluster",
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "prod",
|
"NODE_ENV": "prod",
|
||||||
"PORT": 60000,
|
"PORT": 10588,
|
||||||
"OSSURL": "http://127.0.0.1:60000/"
|
"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:
|
- Launch dev server with Node.js:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn dev #port 60000
|
yarn dev #port 10588
|
||||||
```
|
```
|
||||||
|
|
||||||
- Use Bun to quickly start dev server:
|
- Use Bun to quickly start dev server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn bun:dev #port 60000
|
yarn bun:dev #port 10588
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Build the Project**
|
4. **Build the Project**
|
||||||
|
|||||||
4
env/.env.dev
vendored
4
env/.env.dev
vendored
@ -1,4 +1,4 @@
|
|||||||
NODE_ENV=dev
|
NODE_ENV=dev
|
||||||
PORT=60000
|
PORT=10588
|
||||||
OSSURL=http://127.0.0.1:60000/
|
OSSURL=http://127.0.0.1:10588/
|
||||||
|
|
||||||
|
|||||||
4
env/.env.prod
vendored
4
env/.env.prod
vendored
@ -1,4 +1,4 @@
|
|||||||
NODE_ENV=prod
|
NODE_ENV=prod
|
||||||
PORT=60000
|
PORT=10588
|
||||||
OSSURL=http://127.0.0.1:60000/
|
OSSURL=http://127.0.0.1:10588/
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ if (!fs.existsSync(envDir)) {
|
|||||||
fs.mkdirSync(envDir, { recursive: true });
|
fs.mkdirSync(envDir, { recursive: true });
|
||||||
}
|
}
|
||||||
if (!fs.existsSync(envFile)) {
|
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");
|
fs.writeFileSync(envFile, defaultEnv, "utf8");
|
||||||
console.log(`📄 已自动创建环境变量文件: ${envFile}`);
|
console.log(`📄 已自动创建环境变量文件: ${envFile}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import startServe, { closeServe } from "src/app";
|
|||||||
import { number } from "zod";
|
import { number } from "zod";
|
||||||
|
|
||||||
// 默认端口配置
|
// 默认端口配置
|
||||||
const defaultPort = 60000;
|
const defaultPort = 10588;
|
||||||
|
|
||||||
function createMainWindow(port: any): void {
|
function createMainWindow(port: any): void {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
@ -35,7 +35,7 @@ function createMainWindow(port: any): void {
|
|||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
try {
|
try {
|
||||||
const port = await startServe(false);
|
const port = await startServe(false);
|
||||||
createMainWindow(60000);
|
createMainWindow(10588);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[服务启动失败]:", err);
|
console.error("[服务启动失败]:", err);
|
||||||
// 如果服务启动失败,使用默认端口创建窗口
|
// 如果服务启动失败,使用默认端口创建窗口
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -42,7 +42,7 @@ export async function decisionAI(ctx: AgentContext) {
|
|||||||
|
|
||||||
const systemPrompt = buildSystemPrompt(skill.prompt, mem);
|
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({
|
const { textStream } = await u.Ai.Text("productionAgent").stream({
|
||||||
system: prefixSystem + systemPrompt,
|
system: prefixSystem + systemPrompt,
|
||||||
|
|||||||
@ -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 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 assetSchema = z.object({ assetsId: z.string(), name: z.string(), desc: z.string(), src: z.string(), derive: z.array(deriveSchema).optional() });
|
||||||
const storyboardTableSchema = z.array(
|
const storyboardTableSchema = z.string().describe("分镜表的markdown文本");
|
||||||
z.object({
|
const flowDataSchema = z.object({ script: z.string(), scriptPlan: z.string(), assets: z.array(assetSchema), storyboardTable: storyboardTableSchema });
|
||||||
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 });
|
|
||||||
|
|
||||||
type FlowData = z.infer<typeof flowDataSchema>;
|
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
|
const valueSchema = z
|
||||||
.union([z.string(), z.array(assetSchema), assetSchema, z.array(deriveSchema), z.array(storyboardTableSchema)])
|
.union([z.string(), z.array(assetSchema), assetSchema, z.array(deriveSchema), z.array(storyboardTableSchema)])
|
||||||
.describe("路径对应的值");
|
.describe("路径对应的值");
|
||||||
@ -66,20 +58,48 @@ export default (socket: Socket, toolsNames?: string[]) => {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
generate_assets_images: tool({
|
|
||||||
description: "生成衍生资产的图片",
|
generate_storyboard_images: tool({
|
||||||
inputSchema: z.object({ ids: z.array(z.string()).describe("需要生成的资产id列表") }),
|
description: `生成一组图片任务,支持图片间的依赖关系(以图生图)。
|
||||||
execute: async ({ ids }) => {
|
|
||||||
console.log("[tools] generated_assets", ids);
|
参数说明:
|
||||||
return new Promise((resolve) => socket.emit("generatedAssets", { ids }, (res: any) => resolve(res)));
|
- images: 图片任务数组
|
||||||
|
- id: 图片唯一标识符
|
||||||
|
- prompt: 图片生成提示词
|
||||||
|
- referenceIds: 依赖的参考图id数组,无依赖填空数组[]
|
||||||
|
- assetIds: 参考的资产图id数组(可选)
|
||||||
|
|
||||||
|
依赖规则:
|
||||||
|
1. referenceIds中的id必须存在于images数组中
|
||||||
|
2. 禁止循环依赖(如A依赖B,B依赖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: "生成分镜图",
|
description: "生成分镜图",
|
||||||
inputSchema: z.object({ script: z.string().describe("剧本文本") }),
|
inputSchema: z.object({ images: z.array(z.object({ assetId: z.number(), prompt: z.string() })) }),
|
||||||
execute: async ({ script }) => {
|
execute: async ({ images }) => {
|
||||||
console.log("[tools] generate_storyboard_images", script);
|
console.log("[tools] generate_assets_images", images);
|
||||||
return new Promise((resolve) => socket.emit("generateStoryboardImages", { script }, (res: any) => resolve(res)));
|
return new Promise((resolve) => socket.emit("generateAssetsImages", { images }, (res: any) => resolve(res)));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import "./logger";
|
// import "./logger";
|
||||||
import "./err";
|
import "./err";
|
||||||
import "./env";
|
import "./env";
|
||||||
import express, { Request, Response, NextFunction } from "express";
|
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);
|
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) => {
|
return await new Promise((resolve) => {
|
||||||
server.listen(port, async () => {
|
server.listen(port, async () => {
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import path from "path";
|
|||||||
|
|
||||||
// 默认环境变量(当 env 文件不存在时自动创建)
|
// 默认环境变量(当 env 文件不存在时自动创建)
|
||||||
const defaultEnvValues: Record<string, string> = {
|
const defaultEnvValues: Record<string, string> = {
|
||||||
dev: `NODE_ENV=dev\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=60000\nOSSURL=http://127.0.0.1:60000/`,
|
prod: `NODE_ENV=prod\nPORT=10588\nOSSURL=http://127.0.0.1:10588/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 判断是否为打包后的 Electron 环境
|
// 判断是否为打包后的 Electron 环境
|
||||||
|
|||||||
@ -13,7 +13,6 @@ export default router.post(
|
|||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { projectId } = req.body;
|
const { projectId } = req.body;
|
||||||
const storyboardData = await u.db("o_storyboard");
|
const storyboardData = await u.db("o_storyboard");
|
||||||
console.log("%c Line:16 🍖 storyboardData", "background:#ed9ec7", storyboardData);
|
|
||||||
const data = await Promise.all(
|
const data = await Promise.all(
|
||||||
storyboardData.map(async (i) => {
|
storyboardData.map(async (i) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
7
src/types/database.d.ts
vendored
7
src/types/database.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
// @db-hash 8af8e41e3ca0cb5ee554944515d72ba8
|
// @db-hash bd46e7c381481a74efedc662a4f9049f
|
||||||
//该文件由脚本自动生成,请勿手动修改
|
//该文件由脚本自动生成,请勿手动修改
|
||||||
|
|
||||||
export interface memories {
|
export interface memories {
|
||||||
@ -163,13 +163,8 @@ export interface o_skills {
|
|||||||
}
|
}
|
||||||
export interface o_storyboard {
|
export interface o_storyboard {
|
||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
'detail'?: string | null;
|
|
||||||
'filePath'?: string | null;
|
|
||||||
'frameType'?: string | null;
|
|
||||||
'id'?: number;
|
'id'?: number;
|
||||||
'name'?: string | null;
|
'name'?: string | null;
|
||||||
'prompt'?: string | null;
|
|
||||||
'seconds'?: string | null;
|
|
||||||
}
|
}
|
||||||
export interface o_storyboardFlow {
|
export interface o_storyboardFlow {
|
||||||
'flowData': string;
|
'flowData': string;
|
||||||
|
|||||||
@ -49,7 +49,7 @@ 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:60000/`;
|
const url = process.env.OSSURL || `http://127.0.0.1:10588/`;
|
||||||
return `${url}${safePath.split(path.sep).join("/")}`;
|
return `${url}${safePath.split(path.sep).join("/")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user