This commit is contained in:
2026-06-30 15:02:20 +08:00
commit 3948b5a48a
306 changed files with 77275 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# 版本历史 / Changelog
**导航:[项目主页](../README.md) | [English](en.md)**
> 版本历史已统一维护于项目根目录,请查阅 → **[CHANGELOG.md](../CHANGELOG.md)**
---
[← 返回项目主页](../README.md)
+28
View File
@@ -0,0 +1,28 @@
文件 欧先生提供@全力以赴提供
自动注入原理
代理在收到请求后会加载对应的工作流 JSON 文件,并遍历所有节点,基于节点类型和标题识别注入点:
CLIPTextEncode:标题中含有 "Positive" 的节点注入正向提示词,含有 "Negative" 的注入负向提示词。
EmptyLatentImage / EmptySD3LatentImage / EmptyFlux2LatentImage:注入 width、height、batch_size。
LoadImage:按标题中的 "Reference Image 1"、"Reference Image 2" 等序号排序后,依次注入参考图的文件名(参考图已被代理上传至 ComfyUI)。
PrimitiveInt / INTConstant / PrimitiveFloat / FloatSlider:标题为 "Width"、"Height"、"Duration"、"FPS" 等会被注入相应的数值。
RandomNoise / KSampler:统一注入生成用的随机种子。
PromptRelayEncode:自动构建 local_prompts 和 segment_lengths(用于多镜头视频)。
LTXVAddGuideMulti:根据参考图数量动态计算并注入引导帧索引。
创建自定义工作流
在 ComfyUI 中设计工作流,并为关键节点设置具有语义的标题(title):
正向提示词 CLIPTextEncode → 标题包含 "Positive"
负向提示词 CLIPTextEncode → 标题包含 "Negative"
宽度整数/浮点节点 → "Width"
高度整数/浮点节点 → "Height"
帧率节点 → "FPS"
时长节点 → "Duration"
参考图 LoadImage 节点 → "Reference Image 1"、"Reference Image 2" …(序号将决定注入顺序)
保存工作流为 JSON 文件(文件名将作为 model 参数的值)。
将 JSON 文件放入配置的 workflows_folder 目录。
启动代理,即可通过指定 model 参数调用该工作流。
https://github.com/553556705-tech/ComfyUI-OpenAI-API-Refactored/blob/main/apps/rust/comfyui-openai-api/README.md
+389
View File
@@ -0,0 +1,389 @@
```markdown
文件 欧先生提供@全力以赴提供
# ComfyUI OpenAI API Proxy
基于 Rust 构建的高性能反向代理,将标准 OpenAI 图像/视频生成 API 调用无缝转换为 ComfyUI 后端请求。支持多后端健康检查、智能负载均衡、WebSocket 双通道、指数退避完全抖动、令牌桶限流、幂等键缓存、请求级响应缓存以及 OpenTelemetry 可观测性,为生成服务提供生产级可靠性。
## 概述
`comfyui-openai-api` 是 OpenAI API 兼容客户端与 ComfyUI 工作流引擎之间的桥梁,核心职责:
- **接收** 标准 OpenAI 格式的图像/视频生成请求
- **转换** 请求参数为 ComfyUI 工作流注入格式
- **路由** 根据配置策略将请求分发至健康的 ComfyUI 后端
- **管理** 异步任务生命周期,支持状态持久化与查询
- **交付** 符合 OpenAI API 规范的响应(Base64 编码图像/视频)
**LocalMiniDrama**(本地 AI 短剧全流程创作工具)等项目生态中,本代理承载着底层生成引擎的统一 API 基座角色,为从剧本到成片的完整链路提供稳定、可扩展的推理调度能力。
## 核心特性
### API 兼容性
- **OpenAI 图像生成** —— `POST /v1/images/generations`,同步返回 Base64 图像
- **视频生成扩展** —— `POST /v1/videos/generations`,异步返回 `task_id`,通过 `GET /v1/tasks/{task_id}` 查询结果
- **任务生命周期管理** —— 查询、列出、删除任务(`GET /v1/tasks``GET /v1/tasks/{task_id}``DELETE /v1/tasks/{task_id}`
- **模型列表** —— `GET /v1/models` 返回所有可用工作流(模型)
- **后端状态查询** —— `GET /v1/backends` 查看各后端健康状态
- **健康检查** —— `GET /v1/health` 存活探针
- **视频子系统状态** —— `GET /v1/videos/health`
- **Prometheus 指标** —— `GET /v1/metrics`
- **API 帮助文档** —— `GET /v1/help`
### 多后端管理
- 支持配置多个 ComfyUI 后端节点,按名称 (`?backend=xxx`) 显式指定或自动选择
- **定期健康检查**:通过请求每个后端的 `/system_stats`,连续失败达阈值自动摘除,恢复后自动加入
- **负载均衡策略**:轮询(Round Robin)、最少连接数(Least Connections)、随机(Random),可在配置文件中切换
### 前置条件
- Rust 1.70+
- ComfyUI 后端(需启用 `--api` 模式)
### 运行
./target/release/comfyui-openai-api
## API 端点详解
所有端点均以 `/v1` 为前缀。以下是完整列表:
| 端点 | 方法 | 说明 |
|------|------|------|
| `/v1/models` | GET | 列出所有可用模型(工作流文件名) |
| `/v1/health` | GET | 简单存活检查 |
| `/v1/backends` | GET | 查看所有后端健康状态 |
| `/v1/images/generations` | POST | 图像生成,同步返回 |
| `/v1/videos/generations` | POST | 视频生成,异步返回 `task_id` |
| `/v1/tasks` | GET | 列出所有任务状态 |
| `/v1/tasks/{task_id}` | GET / DELETE | 查询或删除单个任务 |
| `/v1/videos/health` | GET | 视频生成子系统状态 |
| `/v1/metrics` | GET | Prometheus 指标导出 |
| `/v1/help` | GET | API 帮助文档(JSON |
### 1. 图像生成 `POST /v1/images/generations`
**请求示例**
```bash
curl -X POST 'http://localhost:8080/v1/images/generations?backend=backend-a' \
-H 'Content-Type: application/json' \
-d '{
"model": "sdxl-workflow",
"prompt": "a cat wearing a hat, masterpiece",
"negative_prompt": "low quality, blurry",
"size": "1024x1024",
"n": 1,
"seed": 42,
"reference_images": [
{"name": "ref1", "data": "data:image/png;base64,iVBOR..."}
]
}'
```
**请求参数(Body**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `model` | string | 是 | 工作流文件名(不含 `.json` |
| `prompt` | string | 否 | 正向提示词 |
| `negative_prompt` | string | 否 | 负向提示词 |
| `size` | string | 否 | 尺寸,如 `"1024x1024"`(可被配置文件覆盖) |
| `seed` | integer | 否 | 随机种子 |
| `n` | integer | 否 | 生成数量(批次大小) |
| `reference_images` | array | 否 | 参考图数组 `[{name, data}]` |
| `image` | array | 否 | Base64 图片字符串数组(等效于 `reference_images` |
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `backend` | string | 否 | 指定后端名称,未指定时自动使用负载均衡策略 |
**响应格式**
```json
{
"created": 1704067200,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUg..."
}
]
}
```
### 2. 视频生成 `POST /v1/videos/generations`
**请求示例**
```bash
curl -X POST 'http://localhost:8080/v1/videos/generations?backend=backend-b' \
-H 'Content-Type: application/json' \
-d '{
"model": "video-workflow",
"content": [
{"type": "text", "text": "a dog running in the park"},
{"type": "image_url", "image_url": {"url": "https://example.com/ref.png"}, "role": "reference_image"}
],
"duration": 5,
"resolution": "720p"
}'
```
**请求参数(Body**
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `model` | string | 是 | 视频工作流文件名 |
| `content` | array | 是 | 内容数组,每项可为 `{"type":"text", "text":"..."}``{"type":"image_url", "image_url":{"url":"..."}, "role":"reference_image"}` |
| `duration` | integer | 否 | 时长(秒),默认 5 |
| `resolution` | string | 否 | `"720p"``"1080p"` |
| `ratio` | string | 否 | 宽高比,如 `"16:9"` |
| `local_prompts` | string | 否 | 多镜头提示词,格式 `[起始-结束]\n描述` |
| `global_prompt` | string | 否 | 全局提示词 |
| `guide_strengths` | array | 否 | 引导强度数组 |
**响应格式**
```json
{
"task_id": "vid-1715432100123-789"
}
```
### 3. 任务查询 `GET /v1/tasks/{task_id}`
```bash
curl http://localhost:8080/v1/tasks/vid-1715432100123-789
```
响应示例(处理中):
```json
{
"status": "processing"
}
```
响应示例(已完成):
```json
{
"status": "completed",
"video_url": "http://backend-b:8000/view?filename=video.mp4&subfolder=&type=output",
"b64_json": "..."
}
```
响应示例(失败):
```json
{
"status": "failed",
"error": "ComfyUI node error: ..."
}
```
### 4. 列出所有任务 `GET /v1/tasks`
```bash
curl http://localhost:8080/v1/tasks
```
返回:
```json
{
"tasks": [
{
"task_id": "vid-1715432100123-789",
"status": "completed"
},
{
"task_id": "img-1715432100456-123",
"status": "processing"
}
]
}
```
### 5. 删除任务 `DELETE /v1/tasks/{task_id}`
```bash
curl -X DELETE http://localhost:8080/v1/tasks/img-1715432100456-123
```
成功时返回 HTTP `204 No Content`
### 6. 其他端点
- **列出模型** `GET /v1/models`
```bash
curl http://localhost:8080/v1/models
```
响应:
```json
{
"object": "list",
"data": [
{ "id": "sdxl-workflow", "object": "model", "owned_by": "comfyui-openai-api" },
{ "id": "video-workflow", "object": "model", "owned_by": "comfyui-openai-api" }
]
}
```
- **后端健康状态** `GET /v1/backends`
```json
{
"backends": [
{ "name": "backend-a", "healthy": true },
{ "name": "backend-b", "healthy": false }
]
}
```
- **存活探针** `GET /v1/health`:返回 `OK`
- **Prometheus 指标** `GET /v1/metrics`:返回标准 Prometheus 文本格式指标。
### 完整配置项详解
```yaml
# 日志级别:trace, debug, info, warn, error
log_level: "info"
# 代理服务绑定地址和端口
server:
host: "0.0.0.0"
port: 8080
# 多后端列表,至少需要一个后端
comfyui_backends:
- name: "backend-a" # 唯一名称,用于通过 ?backend= 选择
host: "127.0.0.1" # ComfyUI 地址
port: 8000 # ComfyUI 端口
default: true # 是否默认后端(用于 WebSocket 连接)
- name: "backend-b"
host: "192.168.1.100"
port: 8188
default: false
# 代理内部设置
comfyui_backend:
client_id: "comfyui-api" # WebSocket 客户端 ID
workflows_folder: "./workflows" # 工作流 JSON 存放目录
use_ws: true # 是否启用 WebSocket 连接默认后端
input_dir: "./cache" # 图片缓存目录,保存上传的参考图
# 路由与运行时配置
routing:
timeout_seconds: 3600 # ComfyUI 任务总超时(秒)
max_payload_size_mb: 500 # 请求体最大大小(MB
Image_Width: 1280 # 图像默认宽度(可被请求中的 size 覆盖)
Image_Height: 704 # 图像默认高度
video_Width: 1024 # 视频默认宽度
video_Height: 576 # 视频默认高度
fps: 24 # 默认帧率
free_model_before_video: true # 生成视频前是否调用 ComfyUI /free 释放显存
# 负载均衡策略,可选值:RoundRobin, LeastConnections, Random
lb_strategy: "RoundRobin"
# 令牌桶限流(可选,注释或删除则限流不生效)
rate_limit:
max_tokens: 60 # 桶容量
refill_rate: 1.0 # 每秒补充令牌数
# 请求级响应缓存(可选,注释或删除则缓存不生效)
response_cache:
ttl_secs: 600 # 缓存有效期(秒)
max_entries: 500 # LRU 最大条目数
# 是否启用幂等键检查(Idempotency-Key 头)
enable_idempotency: true
# 优雅关闭最长等待时间(秒),超时后强制退出
graceful_shutdown_timeout_secs: 30
# 健康检查间隔(秒)与连续失败阈值
health_check_interval_secs: 15
health_check_fail_threshold: 3
```
## 架构与请求流程
### 架构概览
```
┌──────────────────────────────────────┐
│ OpenAI 兼容客户端 │
│ (Python, JS, curl, LocalMiniDrama) │
└────────────────┬─────────────────────┘
│ HTTP POST /v1/images/generations
┌──────────────────────────────────────┐
│ comfyui-openai-api │
│ ┌──────────┐ ┌────────────────┐ │
│ │ 限流器 │ │ 幂等键检查 │ │
│ └──────────┘ └────────────────┘ │
│ ┌──────────┐ ┌────────────────┐ │
│ │ 工作流 │ │ 后端池 & LB │ │
│ │ 注入器 │ │ + 健康检查 │ │
│ └──────────┘ └────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ WebSocket / HTTP 轮询 │ │
│ │ (全抖动退避) │ │
│ └──────────────────────────────┘ │
└────────────────┬─────────────────────┘
┌──────────────────────────────────────┐
│ ComfyUI 后端 A (健康) │
│ ComfyUI 后端 B (健康) │
│ ComfyUI 后端 C (不健康,已摘除) │
└──────────────────────────────────────┘
```
## 与 LocalMiniDrama 的生态协同
- **统一生成接口**`LocalMiniDrama` 通过标准 OpenAI API 调用本代理,无需关心底层 ComfyUI 工作流细节。
- **批量分镜生成**:逐镜生成流程可借助多后端负载均衡实现并行加速。
- **角色一致性**:通过 `X-Consistent-Role` 头与种子追踪器联动,保持同一角色多分镜外貌一致。
- **视频模型支持**:内置豆包 Seedance、通义万相、Vidu 等工作流注入兼容,覆盖短剧制作的多模型需求。
## 故障排查
| 现象 | 可能原因 | 排查方法 |
|------|---------|---------|
| 502 Bad Gateway | ComfyUI 后端不可达 | 检查 `comfyui_backends` 配置中 host/port 是否正确,确认 ComfyUI 已启动 `--api` |
| 404 Workflow not found | `model` 参数对应的工作流文件不存在 | 确认 `workflows_folder` 目录下存在 `{model}.json` 文件 |
| 400 Invalid request | 请求体格式错误或 Base64 解码失败 | 检查 JSON 格式,验证 Base64 编码有效性 |
| 504 Timeout | 生成时间超过 `timeout_seconds` | 增大超时值或检查 ComfyUI 日志中的节点错误信息 |
| 429 Too Many Requests | 触发令牌桶限流 | 调整 `rate_limit` 配置或降低请求频率 |
| 后端被摘除 | 健康检查连续失败 | 检查 ComfyUI `/system_stats` 是否正常返回,网络连通性 |
## 版本历史
### v0.3.0
- 🏗️ 模块化架构重构,拆分 handlers/backend/transport/middleware/cache/workflows
- 🔄 多后端健康检查与负载均衡(RoundRobin / LeastConnections / Random
- 🔒 令牌桶限流中间件
- 🆔 幂等键支持
- 💾 请求级 LRU 响应缓存
- 📡 OpenTelemetry 分布式追踪
- 🧹 优雅关闭与任务排水
- 🌱 角色种子稳定性追踪器
- 📋 新增 `/v1/models``/v1/backends``/v1/tasks` 列表与删除等端点
### v0.2.0
- 视频生成支持
- 多后端手动路由
- 任务持久化(tasks.json
- PromptRelayEncode / LTXVAddGuideMulti 节点注入
### v0.1.0
- 初始版本,OpenAI 图像生成兼容
## 贡献指南
欢迎通过 Issue 和 Pull Request 参与贡献。请遵循以下准则:
- 为新增的公共函数和模块添加文档注释
- 面向用户的功能改动需同步更新配置示例和 API 文档(即本 README 与 /v1/help 端点)
- 针对多种工作流配置进行测试
- 遵循现有代码风格和模块组织方式
+17
View File
@@ -0,0 +1,17 @@
# 使用系统自带的PowerShell执行
# 1.将ComfyUI包装成标准的OpenAI API接口
CD C:\ComfyUI
git clone https://github.com/pnyxai/comfyui-openai-api.git
# 2.进入目录
CD C:\ComfyUI\comfyui-openai-api\apps\rust\comfyui-openai-api
# 3.安装ComFyUI OpenAI API代理环境支持-Rust
https://rust-lang.org/zh-CN/tools/install/
#4.编译ComFyUI OpenAI API代理程序代码-Rust
cargo clean
cargo build --release
# 5.启动组件 终端显示 Proxy server listening on 0.0.0.0:8080为成功。
./target/release/comfyui-openai-api
# 生成的OpenAI API接口地址http://127.0.0.1:8080/v1/images/generations
感谢群友 欧先生@全力以赴 整理的教程
+236
View File
@@ -0,0 +1,236 @@
# AI 配置指南
**导航:[项目主页](../README.md) | [快速开始](quickstart.md) | [English](en.md)**
---
## 目录
- [配置入口](#配置入口)
- [三类模型配置](#三类模型配置)
- [阿里云 DashScope(通义)](#阿里云-dashscope通义)
- [申请 API Key](#申请-api-key)
- [可用模型](#可用模型)
- [配置示例](#配置示例)
- [火山引擎 Volcengine(豆包)](#火山引擎-volcengine豆包)
- [申请 API Key](#申请-api-key-1)
- [可用模型](#可用模型-1)
- [配置示例](#配置示例-1)
- [本地部署模型(Ollama 等)](#本地部署模型ollama-等)
- [其他 OpenAI 兼容接口](#其他-openai-兼容接口)
- [一键配置功能](#一键配置功能)
- [连接测试](#连接测试)
- [常见问题](#常见问题)
---
## 配置入口
点击软件右上角 **「AI 配置」** 按钮,进入 AI 服务管理页面。
页面分为三个 Tab
- **文本生成** — 用于生成剧本、分镜脚本、提示词等
- **图片生成** — 用于生成角色图、场景图、分镜图
- **视频生成** — 用于生成分镜视频片段
每类模型可独立配置不同的服务商和模型,互不影响。
---
## 三类模型配置
| 类型 | 用途 | 推荐服务商 |
|------|------|----------|
| 文本生成 | 剧本生成、角色提取、分镜脚本、提示词优化 | 通义 Qwen、豆包 Pro |
| 图片生成 | 角色形象图、场景背景图、分镜静帧图 | 通义万象、豆包图片 |
| 视频生成 | 分镜视频片段 | 豆包 Seedance(经典单链路或 **Seedance 2.0 多图 / 全能模式** |
---
## 阿里云 DashScope(通义)
### 申请 API Key
1. 访问 [阿里云百炼控制台](https://bailian.console.aliyun.com/)
2. 注册/登录阿里云账号
3. 进入「模型广场」,开通你需要的模型(文本类、图片类等)
4. 左侧菜单点击「API-KEY 管理」,创建新的 API Key
5. 复制 API Key(以 `sk-` 开头)
> 新用户通常有免费额度,建议先用免费额度测试。
### 可用模型
**文本生成:**
| 模型名 | 说明 |
|--------|------|
| `qwen-turbo` | 速度快、成本低,适合批量生成 |
| `qwen-plus` | 性能均衡,推荐日常使用 |
| `qwen-max` | 最强文本能力,适合剧本生成 |
| `qwen-long` | 超长上下文,适合长剧本 |
**图片生成:**
| 模型名 | 说明 |
|--------|------|
| `wanx2.1-t2i-turbo` | 速度快,通用图片生成 |
| `wanx2.1-t2i-plus` | 更高质量 |
| `wanx-v1` | 经典版本 |
**视频生成:**
| 模型名 | 说明 |
|--------|------|
| `wan2.1-t2v-turbo` | 文字转视频,速度较快 |
| `wan2.1-t2v-plus` | 更高质量 |
### 配置示例
在「AI 配置」页面新增配置:
```
服务商:DashScope
Base URLhttps://dashscope.aliyuncs.com/compatible-mode/v1
API Keysk-xxxxxxxxxxxxxxxx
模型:qwen-plus(文本)/ wanx2.1-t2i-turbo(图片)/ wan2.1-t2v-turbo(视频)
```
---
## 火山引擎 Volcengine(豆包)
### 申请 API Key
1. 访问 [火山方舟控制台](https://console.volcengine.com/ark)
2. 注册/登录火山引擎账号
3. 进入「模型广场」,开通所需模型(文本/图片/视频)
4. 左侧点击「API Key 管理」,创建 API Key
5. 复制 API Key
> 💡 视频生成(Seedance)需要单独开通,且按生成时长计费,注意控制用量。
### 可用模型
**文本生成:**
| 模型名 | API 端点 ID | 说明 |
|--------|------------|------|
| `Doubao-pro-32k` | `doubao-pro-32k-241215` | 通用高性能模型 |
| `Doubao-lite-32k` | `doubao-lite-32k-241215` | 低成本模型 |
| `Doubao-pro-128k` | `doubao-pro-128k-241215` | 超长上下文 |
**图片生成:**
| 模型名 | API 端点 ID | 说明 |
|--------|------------|------|
| `Doubao-seedream-4.5` | `doubao-seedream-4-5-251128` | 高质量图片生成 |
**视频生成:**
| 模型名 | API 端点 ID | 说明 |
|--------|------------|------|
| `Doubao-Seedance-1.0-pro-fast` | `doubao-seedance-1-0-pro-250528` | 较快速度 |
| `Doubao-Seedance-1.5-pro` | `doubao-seedance-1-5-pro-251215` | 高质量版 |
| `Doubao-Seedance-2.0-pro` | `doubao-seedance-2-0-260128` | **Seedance 2.0**,方舟多参考图;配合接口规范 **`volcengine_omni`** 与分镜**全能模式** |
| `Doubao-Seedance-2.0-fast` | `doubao-seedance-2-0-fast-260128` | Seedance 2.0 快速版 |
> ⚠️ 配置中填写模型名时,系统会自动映射到正确的 API 端点 ID,两种写法均可。
**分镜「全能模式」与接口规范(v1.2.5+,v1.2.7 增强校验):**
- 制作页单个分镜可切换为 **「全能模式」**:中间编辑区为**片段描述**,可用 **`@图片1``@图片2`…** 对应参考图顺序(一般为场景 → 角色 → 物品;不含经典分镜中间主图;`@图片N` 后建议加**半角空格**)。若该框有内容,生视频时**只发送这段文本**,不会拼接下方结构化「视频提示词」。
-**AI 配置 → 视频生成** 中,将 **接口规范** 选为 **`volcengine_omni`**(火山即梦 Seedance 2.0 等多图参考)或 **`kling_omni`**(可灵 Omni)。Seedance **2.x** 单段时长由后端吸附到 **415 秒**;方舟多图侧最多 **9** 张参考图。
- **v1.2.7**:单条生视频前会检测配置是否匹配(`kling_omni`,或 `volcengine_omni` + Seedance 2.x 模型名);不匹配时弹窗说明,可选强制继续(降级为场景图 / 分镜主图参考)。**经典模式**无分镜参考图时会提示先生成分镜图,不提供纯文案强行生成。
- 亦可使用 **可灵 Omni** 走同一套全能分镜工作流,详见 AI 配置页内嵌说明。
### 配置示例
```
服务商:Volcengine
Base URLhttps://ark.cn-beijing.volces.com/api/v3
API Keyxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
模型:Doubao-pro-32k(文本)/ Doubao-seedream-4.5(图片)/ Doubao-Seedance-1.0-pro-fast(视频)
```
**视频生成参数(可选):**
| 参数 | 说明 | 默认值 |
|------|------|--------|
| 分辨率 | `720p` / `1080p` / `480p` | `720p` |
| 视频时长 | 每段分镜的视频秒数(4 / 5 / 8 / 10s | `5` |
| seed | 随机种子,固定可复现结果 | 随机 |
| camera_fixed | 是否固定摄像机 | `false` |
| watermark | 是否添加水印 | `false` |
---
## 本地部署模型(Ollama 等)
如果你在本机或内网部署了兼容 OpenAI 接口的模型服务(如 Ollama、LM Studio、vLLM 等):
```
服务商:自定义 / OpenAI 兼容
Base URLhttp://localhost:11434/v1 Ollama 示例)
API Keyollama (或任意字符串,本地服务通常不验证)
模型:qwen2.5:7b (你下载的模型名)
```
> ⚠️ 本地模型仅适用于**文本生成**,图片和视频生成通常需要专用的云端 API。
---
## 其他 OpenAI 兼容接口
任何支持 OpenAI Chat Completions 协议的接口均可接入:
```
Base URLhttps://your-api-endpoint/v1
API Keyyour-api-key
模型:your-model-name
```
常见兼容服务商:DeepSeek、硅基流动(SiliconFlow)、Groq、OpenRouter 等。
---
## 一键配置功能
在「AI 配置」页面,点击顶部的:
- **「一键配置通义」** — 自动创建阿里云 DashScope 的文本/图片/视频三套配置模板
- **「一键配置火山」** — 自动创建火山引擎的文本/图片/视频三套配置模板
一键配置后,只需填入你的 API Key,其他参数已预填好,点击「保存」即可使用。
---
## 连接测试
每条 AI 配置记录右侧有「测试」按钮,点击后会发送一条简短请求验证连接是否正常。
测试成功显示绿色提示,失败会显示具体错误信息(如认证失败、模型不存在等)。
---
## 常见问题
### Q: API Key 填错了或过期了怎么办?
在「AI 配置」页面找到对应记录,点击编辑,修改 API Key 后保存即可立即生效。
---
### Q: 生成图片时提示「image size must be at least 3686400 pixels」
这是火山引擎图片生成 API 的最低像素要求。本系统会自动根据项目设定的画面比例计算合适的分辨率(最低 2560×1440),通常无需手动处理。如果仍然报错,请检查是否配置了自定义的 size 参数。
---
### Q: 视频生成提示「model does not exist」
火山引擎视频模型的 API 端点 ID 与展示名称不同。请确认你已在火山方舟控制台开通了该模型,并使用正确的模型名称。系统内置了常见模型名称的映射,两种写法(展示名 / 端点 ID)均支持。
---
### Q: 生成速度很慢怎么办?
- 图片生成通常需要 1560 秒
- 视频生成通常需要 1–5 分钟(取决于时长和分辨率)
- 建议使用 `turbo``fast` 后缀的模型加快速度
- 如频繁遇到 429 限流,系统会自动重试,无需手动干预
---
[← 返回项目主页](../README.md)
+282
View File
@@ -0,0 +1,282 @@
<div align="center">
# 🎬 LocalMiniDrama
**A locally-running AI short drama & comic generator — download and run, no cloud required, fully open source**
[![version](https://img.shields.io/badge/version-1.2.7-blue?style=flat-square)](../../releases)
[![license](https://img.shields.io/badge/license-MIT-green?style=flat-square)](../LICENSE)
[![platform](https://img.shields.io/badge/platform-Windows-lightgrey?style=flat-square)](#)
[![stack](https://img.shields.io/badge/Vue3%20%2B%20Node.js%20%2B%20Electron-informational?style=flat-square)](#)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](../../pulls)
**[中文](../README.md) | English | [Author's Story](story.md)**
</div>
---
There are plenty of AI short-drama tools out there, but almost none that truly run **offline locally, work out of the box, and keep your assets private**.
This project is built entirely in JavaScript from scratch. Connect your own AI API and start generating your own AI short drama immediately.
> ✅ No subscription · ✅ Data stays local · ✅ Multiple AI providers · ✅ Fully open source
---
## 📸 Screenshots
<table>
<tr>
<td align="center"><img src="../项目截图/武侠.png" alt="Project list" width="480"/><br/><sub>Project list · Export/Import projects</sub></td>
<td align="center"><img src="../项目截图/武侠分镜.png" alt="Storyboard editor" width="480"/><br/><sub>Storyboard editor · One-click image + video generation</sub></td>
</tr>
</table>
---
## ✨ Features
### 🔄 Full Creation Workflow
| Step | Feature | Description |
|:----:|---------|-------------|
| 1 | **Story Generation** | Enter a synopsis + style; AI generates a full multi-episode script |
| 2 | **Script Editing** | Manage episodes and freely edit script text |
| 3 | **Character Generation** | AI extracts characters; generate a portrait image for each |
| 4 | **Scene Generation** | Auto-extract scenes from script; generate scene background images |
| 5 | **Prop Generation** | Extract / manually add props; generate prop images |
| 6 | **Storyboard Generation** | Auto-generate storyboard per episode (shot type, camera, dialogue…) |
| 7 | **Image / Video Generation** | Generate still image and video clip for each shot |
| 8 | **Video Synthesis** | Automatically merge all shot videos into a complete episode |
### ⚡ One-Click Pipeline
- **Generate All**: Characters → Scenes → Storyboard → Images → Videos → Synthesis — fully automated
- **Fill & Generate**: Intelligently skips already-generated content; only fills what's missing
- **Auto Retry**: Up to 3 retries per step (handles 429 rate limits etc.); errors are logged and the pipeline continues
- **Live Progress**: Shows the current step and full error log in real time
### 🗂 Project & Asset Management
- **Project Export / Import**: Pack the full project as a ZIP (images, videos, text, configs); share or migrate with one file
- **Material Library**: Global character / scene / prop library reusable across projects; per-project and global libraries are strictly isolated
- **Aspect Ratio**: Set the ratio (16:9 / 9:16 / 1:1 …) when creating a project; all generated images and videos adapt automatically
- **Episode Management**: Add / delete episodes; script preview
### ✏️ Storyboard Fine Editing
- **Classic vs Universal mode**: Toggle per storyboard. **Classic** shows the main reference image in the center (video is blocked with a prompt if no reference image); **Universal mode** uses a **segment prompt** field (`universal_segment_text`) for omni video APIs — pair with **`volcengine_omni`** (Volcengine Ark Seedance 2.0 multi-image) or **`kling_omni`** (Kling Omni), with a pre-submit config check. Classic fields remain; switch back anytime
- **`@Image1` … slot references**: In the segment prompt, use **`@图片1` / `@图片2` …** to align with the reference order (scene → characters → props; excludes the classic center panel image); “Generate from storyboard” can fill camera/movement hints. If the segment prompt is non-empty, **only that text** is sent for video (structured video fields are not concatenated)
- **Tail-frame link** (v1.2.7): Extract the last frame from the current shots completed video and set it as the next shots first frame
- **Export storyboard sheet** (v1.2.7): Export the current episode to an HTML table for review and collaboration
- **Image Prompt**: View and edit the image-generation prompt for each shot; regenerate after changes
- **Video Prompt**: Edit the full prompt text, or expand the composition panel to edit individual fields (scene / duration / action / mood / camera / shot type) — auto-reassembled on save
- **Image Management**: AI generation, manual upload, drag-and-drop; replace at any time
### 🤖 AI Configuration
- Three independent model slots: **image generation**, **video generation**, **text generation**
- Compatible with **Alibaba DashScope**, **Volcengine (Doubao)**, **locally-deployed models** and any OpenAI-compatible API
- Visual config panel; changes take effect immediately; **connection test** supported
- Built-in quick-setup wizards for DashScope and Volcengine, with step-by-step API key instructions
### 🌓 UI / Theme
- **Dark mode** (default) and **Light mode** toggle, preference persisted
- Theme toggle available on every page
---
## 🚀 Quick Start
### Option A — Download exe (recommended)
Go to **[Releases](../../releases)** and download the latest:
- `LocalMiniDrama Setup x.x.x.exe` — NSIS installer
- `LocalMiniDrama x.x.x.exe` — portable, no install needed
Double-click → open **AI Config** → enter your API key → start creating.
> On first launch a config file is created at:
> `%APPDATA%\LocalMiniDrama\backend\configs\config.yaml`
### Option B — Development Mode
> Requires Node.js >= 18
```bash
# 1. Clone
git clone https://github.com/your-username/LocalMiniDrama.git
cd LocalMiniDrama
# 2. Backend (port 5679)
cd backend-node
npm install
cp configs/config.example.yaml configs/config.yaml
# Edit config.yaml — set your AI API endpoint and key
npm run migrate # first run: initialise DB
npm start
# 3. Frontend (new terminal, port 3013)
cd frontweb
npm install
npm run dev
```
Open `http://localhost:3013` in your browser.
You can also double-click `run_dev.bat` at the project root to **start both servers at once**.
📖 Full developer guide, packaging, and FAQ → **[Quickstart Guide](quickstart.md)**
---
## 🤖 AI Provider Support
| Provider | Text | Image | Video |
|----------|:----:|:-----:|:-----:|
| Alibaba DashScope (Qwen) | ✅ | ✅ | ✅ |
| Volcengine / Doubao | ✅ | ✅ | ✅ |
| Local (Ollama, OpenAI-compat.) | ✅ | — | — |
| Other OpenAI-compatible APIs | ✅ | ✅ | — |
📖 API key registration and configuration → **[Configuration Guide](configuration.md)**
---
## 🏗 Architecture
```
LocalMiniDrama/
├── backend-node/ # Node.js backend (Express + SQLite)
│ ├── src/
│ │ ├── config/ # YAML config loader
│ │ ├── db/ # SQLite connection & migrations
│ │ ├── services/ # Business logic (generation, export/import…)
│ │ └── routes/ # REST API routes
│ └── configs/ # config.yaml lives here
├── frontweb/ # Vue 3 frontend (Vite + Element Plus)
│ └── src/
│ ├── views/
│ │ ├── FilmList.vue # Home: project list & material library
│ │ ├── DramaDetail.vue # Drama: info / episodes / resource library
│ │ └── FilmCreate.vue # Studio: script / characters / storyboard
│ ├── api/ # Backend API wrappers
│ ├── stores/ # Pinia state management
│ └── styles/ # Global styles & theme variables
├── desktop/ # Electron shell (builds the exe)
├── docs/ # Documentation
└── README.md
```
**Tech Stack:**
| Layer | Technology |
|-------|-----------|
| Frontend | Vue 3 + Vite + Element Plus + Pinia + Axios |
| Backend | Node.js + Express + SQLite (better-sqlite3) |
| Desktop | Electron 28 + electron-builder |
| Language | Plain JavaScript (no TypeScript) |
---
## 📋 Changelog
Full version history → **[CHANGELOG](changelog.md)**
**Latest v1.2.7 highlights:**
- 🆕 **Tail-frame link** — one-click extract the last frame of the current shots video (server-side ffmpeg) and set it as the **next shots first frame**
- 🆕 **Export storyboard sheet** — export the current episodes shots to an **HTML table** (dialogue, narration, universal segment, prompts, etc.)
- 🆕 **Unified generation task progress** — shared Pinia store for character/scene/prop/storyboard image & video async jobs, with recovery after page refresh
- 🔧 **Video mode guards** — Universal mode checks **`kling_omni`** or **`volcengine_omni` + Seedance 2.x** before Omni multi-ref submit; Classic mode blocks video when no storyboard reference image
- 🔧 **Separate first/last frame binding** — last frame no longer overwrites the main panel; Seedance 2.0 certified assets marked stale when the character main image changes
**v1.2.6 / v1.2.5 highlights:**
- 🆕 **Seedance 2.0 + Universal storyboard mode**`volcengine_omni` / `kling_omni`, multi-ref **`@图片N`**, `universal_segment_text` (see [CHANGELOG](../CHANGELOG.md))
**v1.2.3 highlights:**
- 🆕 **Storyboard narrator (narration)** — optional per-shot voice-over text separate from character `dialogue`, for TTS and editing
- 🆕 **Export narration SRT** — build subtitle cues from shot order and durations
- 🔧 **First-shot empty narration fix** — incrementally saved rows are merged from the final parsed JSON so stream-early inserts are not stuck without `narration`
- 🔧 **Stricter narration prompts** — system/user instructions require opening VO and non-empty lines when the mode is enabled
- 🎨 **Narration UI** — textarea/button contrast in light & dark themes; high-contrast “Export SRT” button
**Earlier releases:** see **[CHANGELOG.md](../CHANGELOG.md)** for v1.2.2 (coherent frames, novel import, ffmpeg) and full history.
---
## 🎯 Who Is This For
| User | Scenario |
|------|----------|
| 📹 Content creators | Batch-produce AI short dramas / comics |
| 🔒 Privacy-conscious users | Keep all assets local, no cloud uploads |
| 🛠 Developers | Extend AI providers or customise the pipeline |
| 🌱 Beginners | Explore the AI video space at zero cost |
---
## 🔗 Similar Tools
| Tool | Notes |
|------|-------|
| **Kino 视界** | Active Chinese AI short-drama platform; cloud-based, closed source |
| **Filmaction AI** | AI-driven plot / storyboard / voice; SaaS / web, partly paid |
| **oiioii** | Open source, lightweight AI visual creation, flexible deployment |
| **ChatFire** | AI dialogue-based short drama; inspired this project's backend design |
This project focuses on **local offline use, a friendly UI, and easy customisation**. Feel free to open an [Issue](../../issues) to recommend other tools.
---
## 🤝 Contributing
All contributions are welcome!
- 🐛 **Report a bug** → [New Issue](../../issues/new)
- 💡 **Suggest a feature** → [New Issue](../../issues/new)
- 🔧 **Submit code** → Fork → Edit → Pull Request
-**Star the project** → Help others discover it
---
## ☕ Buy the Author a Coffee
LocalMiniDrama is **free, open source, and runs locally** — maintained in spare time. If it saved you hours or helped ship a short drama, optional tips are warmly appreciated (any amount; totally voluntary).
> Tips do **not** affect features, issues, or PRs. A ⭐ Star or sharing the repo helps just as much.
<table>
<tr>
<td align="center">
<img src="../项目截图/weixinpay.jpg" alt="WeChat Pay tip QR" width="200"/><br/>
<sub><b>WeChat Pay</b></sub>
</td>
<td align="center">
<img src="../项目截图/ali.jpg" alt="Alipay tip QR" width="200"/><br/>
<sub><b>Alipay</b></sub>
</td>
</tr>
</table>
---
## 💬 About the Author
Just an ordinary game developer who got excited about the AI short-drama trend and built this open-source tool in JavaScript. Ship first, figure out the rest later.
Full story, inspirations, and acknowledgements → [Author's Story](story.md)
---
## 📄 License
[MIT](../LICENSE)
---
<div align="center">
**If this project helps you, a ⭐ Star is the best encouragement for the author!**
</div>
@@ -0,0 +1,341 @@
# 分镜图相机角度视角 + 四宫格序列图 Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 修复分镜图生成时背景角度固定的问题(让相机 angle 字段驱动背景透视),并新增四宫格序列图生成模式(通过特殊提示词一次生成含4个画面的分镜参考图)。
**Architecture:**
- 功能一:在 `framePromptService.js``buildStoryboardContext()` 中新增 `expandAngleDescription()` 辅助函数,将原始 angle 值扩展为含透视含义的详细描述,注入 AI 上下文,让 AI 生成帧提示词时考虑相机视角。
- 功能二:利用 `image_generations.frame_type` 已有字段存储 `'quad_grid'` 标志,在 `imageService.processImageGeneration()` 中检测并走四宫格分支:先串行生成 4 个帧提示词,再拼成四宫格格式提示词,最后生成一张图。前端新增全局开关控制是否启用四宫格模式。
**Tech Stack:** Node.js (Express), better-sqlite3, Vue 3, Element Plus
---
## Task 1expandAngleDescription — 角度扩展注入
**Files:**
- Modify: `backend-node/src/services/framePromptService.js`(在 `buildStoryboardContext` 前添加辅助函数并调用)
**Step 1: 在 `buildStoryboardContext` 之前添加 `expandAngleDescription` 函数**
在文件第 45 行(`function buildStoryboardContext` 前)插入:
```javascript
function expandAngleDescription(angle) {
if (!angle) return null;
const a = angle.toString().trim().toLowerCase();
// 中文 angle 值(来自分镜生成)
if (a === '平视' || a === 'eye-level' || a === 'eye level') {
return '平视视角(eye-level shot):水平视角,正常透视,背景与人物同高度展开';
}
if (a === '仰视' || a === 'low-angle' || a === 'low angle') {
return '仰视视角(low-angle shot):从下往上仰拍,背景呈现天空/天花板/建筑顶部的仰视透视,地平线偏低';
}
if (a === '俯视' || a === 'high-angle' || a === 'high angle') {
return '俯视视角(high-angle shot):从上往下俯拍,背景呈现地面/场景的鸟瞰俯视透视,地平线偏高';
}
if (a === '侧面' || a === 'side') {
return '侧面视角(side angle shot):从侧面拍摄,背景呈侧向延伸的构图';
}
if (a === '背面' || a === 'back') {
return '背面视角(rear/back shot):从角色背后拍摄,角色背对镜头,背景场景在角色前方延伸展开';
}
// 未匹配到预设值时原样保留
return `相机角度:${angle}`;
}
```
**Step 2: 在 `buildStoryboardContext` 中调用 `expandAngleDescription`**
找到当前的 angle 处理部分(约第 73-75 行):
```javascript
if (sb.angle) {
parts.push(promptI18n.formatUserPrompt(cfg, 'angle_label', sb.angle));
}
```
替换为:
```javascript
if (sb.angle) {
const angleDesc = expandAngleDescription(sb.angle);
if (angleDesc) {
parts.push(promptI18n.formatUserPrompt(cfg, 'angle_label', angleDesc));
} else {
parts.push(promptI18n.formatUserPrompt(cfg, 'angle_label', sb.angle));
}
}
```
**Step 3: 手动验证(无自动化测试框架,目视检查)**
启动后端,生成一个 angle='仰视' 的分镜的帧提示词,检查日志中传给 AI 的 userPrompt 是否包含 "低角度仰拍" 等扩展描述。
**Step 4: Commit**
```bash
git add backend-node/src/services/framePromptService.js
git commit -m "feat: expand camera angle to perspective description in frame prompt context"
```
---
## Task 2processImageGeneration 四宫格分支
**Files:**
- Modify: `backend-node/src/services/imageService.js`(在 `processImageGeneration` 中新增 quad_grid 分支)
- Modify: `backend-node/src/services/imageService.js`(新增 `buildQuadGridPrompt` 辅助函数)
**Step 1: 在 `imageService.js` 中引入 framePromptService**
在文件顶部(约第 56-59 行,现有 require 后)追加:
```javascript
const framePromptService = require('./framePromptService');
const loadConfig = require('../config').loadConfig;
```
注意:`loadConfig``processImageGeneration` 内部已有局部 require,改为顶部引入(删除函数内的重复 require)。
**Step 2: 新增 `buildQuadGridPrompt` 辅助函数**
`processImageGeneration` 函数之前添加:
```javascript
/**
* 四宫格模式:为分镜生成 4 帧提示词,拼成 2×2 grid 格式的单张图生成提示词
*/
async function buildQuadGridPrompt(db, log, storyboardId, model) {
let cfg = loadConfig();
// 复用 framePromptService 内的辅助函数
const sb = framePromptService.loadStoryboard(db, storyboardId);
if (!sb) throw new Error('分镜不存在');
// 读取 drama style
try {
const epRow = db.prepare(
'SELECT drama_id FROM episodes WHERE id = (SELECT episode_id FROM storyboards WHERE id = ? AND deleted_at IS NULL) AND deleted_at IS NULL'
).get(Number(storyboardId));
if (epRow && epRow.drama_id) {
const dramaRow = db.prepare('SELECT style, metadata FROM dramas WHERE id = ? AND deleted_at IS NULL').get(epRow.drama_id);
if (dramaRow) {
const styleOverrides = {};
if (dramaRow.style && String(dramaRow.style).trim()) {
styleOverrides.default_style = String(dramaRow.style).trim();
}
if (dramaRow.metadata) {
try {
const meta = typeof dramaRow.metadata === 'string' ? JSON.parse(dramaRow.metadata) : dramaRow.metadata;
if (meta && meta.aspect_ratio) {
styleOverrides.default_image_ratio = meta.aspect_ratio;
}
} catch (_) {}
}
if (Object.keys(styleOverrides).length > 0) {
cfg = { ...cfg, style: { ...(cfg?.style || {}), ...styleOverrides } };
}
}
}
} catch (_) {}
const scene = framePromptService.loadScene(db, sb.scene_id);
const characterNames = framePromptService.loadStoryboardCharacterNames(db, storyboardId);
log.info('[四宫格] 开始生成4帧提示词', { storyboard_id: storyboardId });
const [first, key1, key2, last] = await Promise.all([
framePromptService.generateSingleFrameForQuadGrid(db, log, cfg, sb, scene, characterNames, model, 'first'),
framePromptService.generateSingleFrameForQuadGrid(db, log, cfg, sb, scene, characterNames, model, 'key'),
framePromptService.generateSingleFrameForQuadGrid(db, log, cfg, sb, scene, characterNames, model, 'key'),
framePromptService.generateSingleFrameForQuadGrid(db, log, cfg, sb, scene, characterNames, model, 'last'),
]);
log.info('[四宫格] 4帧提示词生成完成', { storyboard_id: storyboardId });
const style = cfg?.style?.default_style || '';
const styleHint = style ? `, art style: ${style}` : '';
const quadPrompt =
`Generate a 2x2 storyboard grid image (four panels showing action sequence progression${styleHint}). ` +
`Each panel is clearly separated by a thin border. ` +
`Panel 1 (top-left, initial state): ${first.prompt}. ` +
`Panel 2 (top-right, action begins): ${key1.prompt}. ` +
`Panel 3 (bottom-left, action climax): ${key2.prompt}. ` +
`Panel 4 (bottom-right, final state): ${last.prompt}. ` +
`Consistent character appearance and scene across all panels. Cinematic quality.`;
return quadPrompt;
}
```
**Step 3: 在 `framePromptService.js` 中导出 `generateSingleFrame`**
`generateSingleFrame` 当前是 `framePromptService.js` 内的私有函数,需要将其导出(或新增一个包装导出)。
`framePromptService.js` 末尾 `module.exports` 中追加:
```javascript
module.exports = {
generateFramePrompt,
loadStoryboard,
loadStoryboardCharacterNames,
loadScene,
getFramePrompts: (db, storyboardId) => storyboardService.getFramePrompts(db, storyboardId),
// 供 imageService 的四宫格模式调用
generateSingleFrameForQuadGrid: generateSingleFrame,
};
```
**Step 4: 在 `processImageGeneration` 中插入四宫格分支**
`processImageGeneration` 函数中,找到 Step 4(调用图生 API)之前的 Step 3(计算尺寸)后面,找到:
```javascript
// ── Step 4: 调用图生 API ─────────────────────────────────────────
log.info('[图生] Step4 调用图生 API →', { id: imageGenId, elapsed: elapsed() });
const tApi = Date.now();
const result = await imageClient.callImageApi(db, log, {
prompt: row.prompt,
```
在 Step 4 开始前插入四宫格提示词覆盖逻辑(注意:使用 `let` 声明覆盖变量,需在 `try` 块内,位于 Step 3 结束后):
```javascript
// ── Step 3.5: 四宫格模式 — 用 AI 生成的4帧内容替换 prompt ────────
let finalPrompt = row.prompt;
if (row.frame_type === 'quad_grid' && row.storyboard_id) {
log.info('[图生] Step3.5 四宫格模式,生成组合提示词', { id: imageGenId });
try {
finalPrompt = await buildQuadGridPrompt(db, log, row.storyboard_id, row.model);
log.info('[图生] Step3.5 四宫格提示词生成完成', {
id: imageGenId,
prompt_preview: finalPrompt.slice(0, 120),
});
} catch (qErr) {
log.warn('[图生] Step3.5 四宫格提示词生成失败,回退到原始 prompt', { error: qErr.message });
// 回退到原始 prompt,不中断流程
}
}
```
然后在 Step 4 的 `callImageApi` 调用中将 `prompt: row.prompt` 改为 `prompt: finalPrompt`
```javascript
const result = await imageClient.callImageApi(db, log, {
prompt: finalPrompt,
```
**Step 5: Commit**
```bash
git add backend-node/src/services/imageService.js
git add backend-node/src/services/framePromptService.js
git commit -m "feat: add quad-grid storyboard image generation via combined frame prompts"
```
---
## Task 3:前端四宫格全局开关 UI
**Files:**
- Modify: `frontweb/src/views/FilmCreate.vue`
**Step 1: 添加 `quadGridMode` 响应式变量**
`FilmCreate.vue``<script setup>` 区域,找到现有的 `storyboardCount``videoDuration` ref 声明处(约第 1432 行附近),追加:
```javascript
const quadGridMode = ref(false)
```
**Step 2: 在分镜生成配置区添加开关 UI**
找到 `sb-config-row` 区域(约第 511-523 行),在最后一个 `<label>` 配置项之后,在 `</div>` 关闭前添加:
```html
<span class="sb-config-divider"></span>
<label class="sb-config-item">
<span class="sb-config-label">四宫格序列图</span>
<el-switch v-model="quadGridMode" />
<span class="sb-config-hint">开启后生成含4帧画面的序列参考图</span>
</label>
```
**Step 3: 修改 `onGenerateSbImage` 传参**
找到 `onGenerateSbImage` 函数(约第 1744 行),将:
```javascript
const res = await imagesAPI.create({
storyboard_id: sb.id,
drama_id: dramaId.value,
prompt: sb.image_prompt || sb.description || '',
model: undefined,
style: getSelectedStyle()
})
```
改为:
```javascript
const res = await imagesAPI.create({
storyboard_id: sb.id,
drama_id: dramaId.value,
prompt: sb.image_prompt || sb.description || '',
model: undefined,
style: getSelectedStyle(),
frame_type: quadGridMode.value ? 'quad_grid' : undefined
})
```
**Step 4: 修改批量生成分镜图逻辑(`startBatchImageGeneration`**
找到 `startBatchImageGeneration` 函数(约第 1527 行),在其内部调用 `onGenerateSbImage``imagesAPI.create` 的地方确认是否已封装调用 `onGenerateSbImage`
查找批量图片生成的实际循环逻辑,找到类似:
```javascript
await imagesAPI.create({ storyboard_id: sb.id, ... })
```
或间接调用 `onGenerateSbImage`
如果批量生成直接复用了 `onGenerateSbImage`,则无需修改(全局 `quadGridMode` 会自动传递)。
如果批量生成有独立的 `imagesAPI.create` 调用,同样追加 `frame_type: quadGridMode.value ? 'quad_grid' : undefined`
**Step 5: 验证 UI 显示**
启动前端,在分镜生成区看到"四宫格序列图"开关,切换后开关状态正常。
**Step 6: Commit**
```bash
git add frontweb/src/views/FilmCreate.vue
git commit -m "feat: add quad-grid mode global toggle in storyboard section UI"
```
---
## Task 4:端到端验证
**验证步骤:**
1. **验证功能一(角度视角):**
- 创建一个 angle='仰视' 的分镜,触发生成帧提示词
- 查看后端日志,确认传给 AI 的 userPrompt 包含 "低角度仰拍" 等扩展描述
- 生成的 image_prompt 应包含仰视视角相关描述
2. **验证功能二(四宫格):**
- 开启前端"四宫格序列图"开关
- 点击某个分镜的"生成分镜"按钮
- 查看后端日志,确认出现 `[四宫格]` 日志行
- 最终生成的图片为 2×2 四格图
3. **回归验证(确保原有流程不受影响):**
- 关闭四宫格开关,正常生成分镜图,确保行为与修改前一致
- angle='平视' 的分镜,生成的提示词包含平视描述
**Step: Final Commit(如有遗漏修改)**
```bash
git add -A
git commit -m "feat: storyboard angle perspective + quad-grid sequence image generation"
```
@@ -0,0 +1,237 @@
# 分镜角度视角 + 四宫格序列图 实现计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 修复分镜图背景角度固定问题,并支持四宫格序列图生成模式。
**Architecture:**
- 功能一:在 `framePromptService.js``buildStoryboardContext()` 中,将 `angle` 字段翻译为带透视含义的完整描述注入上下文,让 AI 根据相机角度生成正确视角的背景。
- 功能二:前端增加全局"四宫格序列图"开关;开启后图片生成时传 `frame_type: 'quad_grid'`;后端 `imageService.js` 检测到该标记后先用 AI 生成 4 个帧提示词,拼成四宫格格式提示词,再调用图片生成 API 生成一张四宫格图。
- 利用现有 `frame_type` 字段存储标记,无需 DB migration。
**Tech Stack:** Node.js, better-sqlite3, Vue 3, Element Plus
---
## Task 1:角度描述扩展(功能一)
**Files:**
- Modify: `backend-node/src/services/framePromptService.js`
**Step 1: 在 `buildStoryboardContext()` 前新增角度映射辅助函数**
在文件顶部(`const loadConfig` 之后)新增:
```js
function expandAngleDescription(angle, isEnglish) {
if (!angle) return null;
const a = String(angle).trim().toLowerCase();
if (isEnglish) {
if (a.includes('low') || a === '仰视') return 'camera angle: low-angle upward shot, scene background shows sky/ceiling/treetops from below, strong upward perspective';
if (a.includes('high') || a === '俯视') return 'camera angle: high-angle downward shot, bird\'s eye view perspective, background shows ground/floor/scene from above';
if (a.includes('side') || a === '侧面') return 'camera angle: side-angle shot, profile composition, background extends laterally';
if (a.includes('back') || a === '背面') return 'camera angle: rear shot, character\'s back to camera, background scene stretches ahead';
return `camera angle: eye-level horizontal shot, normal perspective`;
} else {
if (a.includes('仰') || a.includes('low')) return '相机角度:低角度仰拍,背景呈现天空/天花板/树冠的仰视透视效果,视角向上倾斜';
if (a.includes('俯') || a.includes('high')) return '相机角度:高角度俯拍,鸟瞰视角,背景呈现地面/场景从上方向下看的俯视透视效果';
if (a.includes('侧') || a.includes('side')) return '相机角度:侧面视角,侧向构图,背景向两侧延展';
if (a.includes('背') || a.includes('back')) return '相机角度:从角色背后拍摄,角色背对镜头,背景场景在角色前方延伸';
return '相机角度:平视水平拍摄,正常透视构图';
}
}
```
**Step 2: 修改 `buildStoryboardContext()` 中 angle 处理部分**
找到原来的:
```js
if (sb.angle) {
parts.push(promptI18n.formatUserPrompt(cfg, 'angle_label', sb.angle));
}
```
替换为:
```js
if (sb.angle) {
const isEn = promptI18n.isEnglish(cfg);
const angleDesc = expandAngleDescription(sb.angle, isEn);
if (angleDesc) parts.push(angleDesc);
}
```
**Step 3: 同样修改 `episodeStoryboardService.js` 的 `generateImagePrompt()`**
`episodeStoryboardService.js` 中的 `generateImagePrompt()` 是生成初始 image_prompt 的函数,也需要加入角度信息。找到:
```js
function generateImagePrompt(sb, style) {
const parts = [];
if (sb.location) {
let locationDesc = sb.location;
if (sb.time) locationDesc += ', ' + sb.time;
parts.push(locationDesc);
}
...
const styleText = style && String(style).trim();
if (styleText) parts.push(styleText + ', first frame');
else parts.push('first frame');
return parts.length ? parts.join(', ') : (styleText ? styleText + ', first frame' : 'first frame');
}
```
`parts.push(locationDesc)` 之后,加入角度信息:
```js
if (sb.angle) {
const a = String(sb.angle).trim().toLowerCase();
if (a.includes('仰') || a.includes('low')) parts.push('low-angle upward shot');
else if (a.includes('俯') || a.includes('high')) parts.push('high-angle downward shot, bird\'s eye view');
else if (a.includes('侧') || a.includes('side')) parts.push('side-angle shot');
else if (a.includes('背') || a.includes('back')) parts.push('rear shot from behind character');
else parts.push('eye-level shot');
}
```
**Step 4: 提交**
```bash
git add backend-node/src/services/framePromptService.js backend-node/src/services/episodeStoryboardService.js
git commit -m "feat: expand camera angle to perspective description in storyboard image prompts"
```
---
## Task 2:四宫格图片生成(功能二后端)
**Files:**
- Modify: `backend-node/src/services/imageService.js`
**Step 1: 在 `imageService.js` 中新增 `buildQuadGridPrompt()` 函数**
在文件顶部 `const path = require('path')` 之后,引入 framePromptService(注意避免循环依赖,在函数内部 require)。
`processImageGeneration` 函数之前添加:
```js
async function buildQuadGridPrompt(db, log, cfg, storyboardId, model) {
const framePromptService = require('./framePromptService');
const sb = framePromptService.loadStoryboard(db, storyboardId);
if (!sb) return null;
const scene = framePromptService.loadScene(db, sb.scene_id);
const characterNames = framePromptService.loadStoryboardCharacterNames(db, storyboardId);
const [first, key1, key2, last] = await Promise.all([
framePromptService.generateSingleFrameExported(db, log, cfg, sb, scene, characterNames, model, 'first'),
framePromptService.generateSingleFrameExported(db, log, cfg, sb, scene, characterNames, model, 'key'),
framePromptService.generateSingleFrameExported(db, log, cfg, sb, scene, characterNames, model, 'key'),
framePromptService.generateSingleFrameExported(db, log, cfg, sb, scene, characterNames, model, 'last'),
]);
const style = cfg?.style?.default_style || '';
const styleNote = style ? `, consistent style: ${style}` : '';
return `Generate a 2x2 four-panel storyboard grid image (comic strip layout, clear panel borders). Each panel shows a different moment in sequence${styleNote}:
Panel 1 (top-left, first frame): ${first.prompt}
Panel 2 (top-right, key moment): ${key1.prompt}
Panel 3 (bottom-left, key moment): ${key2.prompt}
Panel 4 (bottom-right, last frame): ${last.prompt}
All panels have consistent character appearance and scene. Clear visible borders between panels. Sequential action progression.`;
}
```
**Step 2: 在 `framePromptService.js` 中导出 `generateSingleFrame`**
`framePromptService.js``module.exports` 中添加:
```js
generateSingleFrameExported: generateSingleFrame,
```
**Step 3: 在 `processImageGeneration()` 中添加四宫格分支**
`processImageGeneration` 中,找到 `// ── Step 1: 获取 AI 配置 ──` 之前,添加四宫格处理:
```js
// ── 四宫格模式:先生成4帧提示词,再拼装组合提示词 ──────────────────
if (row.frame_type === 'quad_grid' && row.storyboard_id) {
try {
const quadPrompt = await buildQuadGridPrompt(db, log, cfg, row.storyboard_id, row.model);
if (quadPrompt) {
db.prepare('UPDATE image_generations SET prompt = ?, updated_at = ? WHERE id = ?')
.run(quadPrompt, new Date().toISOString(), imageGenId);
row.prompt = quadPrompt;
}
} catch (quadErr) {
log.warn('[图生] 四宫格提示词生成失败,使用原始提示词', { error: quadErr.message });
}
}
```
这段代码放在 `// ── Step 1: 获取 AI 配置 ──` 注释之前。
**Step 4: 提交**
```bash
git add backend-node/src/services/imageService.js backend-node/src/services/framePromptService.js
git commit -m "feat: add quad-grid storyboard image generation support"
```
---
## Task 3:四宫格前端 UI(功能二前端)
**Files:**
- Modify: `frontweb/src/views/FilmCreate.vue`
**Step 1: 添加 `quadGridMode` 响应式变量**
在文件中找到 `const resourcePanelCollapsed = ref(false)` 附近(约第 1432 行),添加:
```js
const quadGridMode = ref(false)
```
**Step 2: 在分镜配置行添加四宫格开关 UI**
找到分镜配置区(约 511 行的 `<div class="sb-config-row">`),在最后一个 `</label>` 之后、`</div>` 之前添加:
```html
<span class="sb-config-divider"></span>
<label class="sb-config-item">
<span class="sb-config-label">四宫格序列图</span>
<el-switch v-model="quadGridMode" />
<span class="sb-config-hint">生成含4帧的序列参考图</span>
</label>
```
**Step 3: 修改 `onGenerateSbImage()` 传参**
找到 `onGenerateSbImage` 函数(约 1744 行),修改 `imagesAPI.create()` 调用:
```js
const res = await imagesAPI.create({
storyboard_id: sb.id,
drama_id: dramaId.value,
prompt: sb.image_prompt || sb.description || '',
model: undefined,
style: getSelectedStyle(),
frame_type: quadGridMode.value ? 'quad_grid' : undefined,
})
```
**Step 4: 修改批量生成分镜图也透传 quad_grid**
找到 `startBatchImageGeneration` 函数(约 1527 行),找到其中调用 `imagesAPI.create` 的地方,同样加上:
```js
frame_type: quadGridMode.value ? 'quad_grid' : undefined,
```
**Step 5: 提交**
```bash
git add frontweb/src/views/FilmCreate.vue
git commit -m "feat: add quad-grid mode switch to storyboard UI"
```
---
## 验证步骤
1. 重启后端服务
2. 打开一个剧集的分镜页
3. **验证角度**:检查一个带有"俯视"角度的分镜,点击生成分镜图,观察 AI 生成的图片背景是否呈现俯视透视
4. **验证四宫格**:打开四宫格开关,点击某个分镜的"生成分镜"按钮,等待后确认生成的图片是 2×2 四格布局
5. **验证批量**:开启四宫格模式后点击"批量生成分镜图",确认所有分镜均生成四宫格图
@@ -0,0 +1,136 @@
# 分镜图生成质量提升计划
**目标:** 解决分镜图生成时出现宫格布局、角色外貌不一致、背景不一致等问题,并规划后续质量提升路径。
**技术栈:** Node.js (Express), better-sqlite3, Vue 3, Element Plus, Gemini / NanoBanana 图片 API
---
## 已完成(2026-03-14
### 问题一:场景四视图生成后不显示
**原因:** `imageClient.createAndGenerateImage` 是为角色图片设计的函数,缺少 `scene_id` 参数支持,导致:
- 图片存储路径 hardcode 为 `characters/`(应为 `scenes/`
- `image_generations` 表不写入 `scene_id`
- 生成完成后不回写 `scenes` 表的 `image_url` / `local_path`
**修复:**
- `imageClient.js``createAndGenerateImage` 新增 `scene_id` 参数,动态判断存储目录(`scenes` / `characters` / `images`),生成完成后回写 `scenes`
- `sceneService.js`:调用时传入 `scene_id: sceneId`
---
### 问题二:分镜图生成结果仍为宫格布局
**原因:** 传入的场景/角色参考图是 2×2 四视图合图,图片 AI 看到宫格参考图后会模仿其布局格式输出宫格图,且提示词中没有明确的"单张输出"约束。
**修复:**
- `imageService.js`:单张分镜生成时,在 Step 2.5 记录参考图映射,通过 Gemini parts 结构传递说明
- `imageClient.js`:重构 Gemini 多模态输入结构为正确顺序:
```
[总说明文字] → [参考1说明] → [参考图1] → [参考2说明] → [参考图2] → [生成指令+主提示词]
```
而非原来的"prompt 在前,图片附后"的错误顺序
---
### 问题三:角色外貌不一致(参考图格式干扰)
**原因:** 作为参考图传入的是 2×2 四视图合图(含头像/正面/侧面/背面 4 格),图片 AI 难以从合图中准确提取角色外貌,且四格合图的视觉格式会干扰输出布局。
**修复:**
- `imageService.js``splitQuadGridToImages`):INSERT 时增加 `character_id` 字段,让角色拆分面板可按 ID 查询
- `imageService.js`(Step 2 参考图解析):优先查询拆分后的单张面板作为参考:
- 场景:`quad_panel_0`(左上格 = 建立远景,最能代表环境)
- 角色:`quad_panel_1`(右上格 = 正面全身图,最能代表角色外貌)
- 无拆分面板时 fallback 到四视图合图
- 角色最多取 3 个(避免超出模型参考图限制),Gemini 参考图上限从 3 改为 4(场景1 + 角色3)
---
### 问题四:参考图标签与实际传图数量不对齐
**原因:** 当角色有 `extra_images` 且无主图时,`extra_images` 仍会被推入 refs 但无对应主标签,导致后续角色编号错乱;`refLabels` 数量可能超过 Gemini 实际接收的图片数,让模型产生困惑。
**修复:**
- `extra_images` 推入逻辑移入 `if (primaryRef)` 块内,确保无主图时不产生孤立的 extra 标签
- `refLabels.slice(0, refs.length)` 强制对齐条数
- Gemini parts 构建时,按实际传入图片数裁剪标签
---
## 待完成(优先级排序)
### P1:分镜 prompt 二次优化
**背景:** 当前分镜描述(由文本 AI 生成的首帧/关键帧/尾帧提示词)是针对"分镜内容描述"设计的,直接发给图片 AI 效果打折。
**方案:** 在 `imageService.js` Step 3 之前增加一个文本 AI 优化步骤:
- 输入:原始分镜提示词 + 风格 + 宽高比 + 参考资产名称列表
- 输出:针对图片生成模型优化的提示词(增强细节描述、统一风格词汇、加入参考资产名称映射)
- 模型:使用轻量文本模型(如 deepseek-v3),成本低
- 缓存:优化后的 prompt 回写 `image_generations.prompt`,同一分镜重新生成时可复用
**预期收益:** 图片细节更丰富,风格更一致,角色名称与参考图映射更清晰
---
### P2:批量宫格生成(同一分镜多帧)
**背景:** 当前每一帧独立调用图片 API,导致同一分镜的首帧/关键帧/尾帧在风格、光线、色调上可能不一致。
**方案:** 复用现有 `quad_grid` / `nine_grid` 基础设施:
- 在分镜图生成时,将同一分镜的多帧组合为宫格提示词,一次生成宫格图
- 生成完成后调用 `splitQuadGridToImages` 自动拆分为独立帧
- API 调用次数从 N 次降为 1 次
**预期收益:** 同一分镜内视觉一致性大幅提升,API 成本降低
---
### P3:参考图智能过滤
**背景:** 当前把分镜绑定的所有角色/场景参考图都传入,部分角色/场景可能在当前帧中根本未出现,增加无效噪声。
**方案:** 在 Step 2 解析参考图后,调用文本 AI 判断:
- 输入:分镜描述 + 候选资产列表(名称 + 简介)
- 输出:与本帧内容直接相关的资产列表
- 只传相关资产的参考图
**预期收益:** 减少无关参考图干扰,提高角色/场景还原度
---
### P4:参考图压缩
**背景:** 传参考图前无压缩处理,可能触发 API 请求大小限制。
**方案:** 在 `callGeminiImageApi` 中对参考图 buffer 做压缩:
- 单张目标:≤ 3MBJPEG quality 递减压缩)
- 总大小:≤ 10MB(按大小降序优先压缩大图)
- 使用 `sharp` 处理(项目已依赖)
---
### P5:多 Agent 大纲生成
**背景:** 当前大纲/剧本生成为单次 AI 调用,无法做到自动审核、迭代修改。
**方案:** 基于 Vercel AI SDK 的 Tool Use,实现三 Agent 协作:
- **故事师**:分析输入主题,生成故事线(存 `storylines` 表)
- **大纲师**:根据故事线生成各集大纲(存 `outlines` 表)
- **导演**:审核大纲与故事线一致性,提出修改建议并执行
工具调用链:`主Agent → 故事师 → saveStoryline → 大纲师 → saveOutline → 导演 → updateOutline`
---
### P6:分镜自动生成 AgentshotAgent
**背景:** 当前分镜需要用户手动拆分,或依赖单次 AI 调用,缺少自动化流程。
**方案:**
- **segmentAgent**:从剧本中自动拆分场景片段(含情绪、动作描述)
- **shotAgent**:根据片段生成分镜提示词(含景别、角度、运镜、角色、对话)
- 前端通过 WebSocket 接收流式进度更新
@@ -0,0 +1,278 @@
# 漫剧画布工作流(LibTV 式)实施计划
> 目标:在 LocalMiniDrama 现有 `project.json` / SQLite 数据之上,增加 LibTV 风格的无限画布视图;列表模式(FilmCreate)与画布模式双视图、单数据源。
**最后更新**2026-06-15(阶段 D:交互增强、媒体对齐、全能模式)
## 设计原则
1. **真源不变**:角色、分镜、图片、视频仍存现有表与 `project.json` 结构。
2. **画布是视图层**:额外持久化 `drama.metadata.canvas_layout`(坐标、视口)与 `workflow_groups`(工作流组)。
3. **旧 JSON 兼容**:无 `canvas_layout` 时自动布局;导入/导出忽略未知 metadata 字段不影响旧版。
4. **列表模式对齐**:画布读取分镜图/视频/首尾帧/全能词的逻辑与 `FilmCreate.vue` 一致(通过 `/images``/videos` API + `storyboardMedia.js`)。
5. **技术栈**Vue 3 + `@vue-flow/core` ^1.48(与 Element Plus / Pinia 一致)。
---
## 阶段 A:只读画布 MVP(已完成)
### 交付物
- [x] 路由 `/film/:id/canvas`
- [x] `FilmCreate` / `DramaDetail` 顶部「画布模式」入口
- [x] `dramaCanvasAdapter.js``drama` API 数据 → nodes/edges 自动布局
- [x] `DramaCanvas.vue`:平移缩放、小地图、集数筛选
- [x] 双击分镜节点 → 跳转列表模式并定位集数
---
## 阶段 B:布局持久化 + 素材侧栏交互(已完成)
### 交付物
- [x] 节点可拖动,debounce 保存 `metadata.canvas_layout`PUT `/dramas/:id/canvas-layout`
- [x] 导出 ZIP 时 `canvas_layout` 写入 `drama.metadata`
- [x] 左侧素材库点击高亮关联分镜与连线
- [x] 画布 ↔ 列表模式双向入口,保留集数筛选 query
- [x] 分镜节点展示生成状态,生成中自动轮询刷新
---
## 阶段 C:工作流编排(整组重跑)(已完成)
### 交付物
- [x] 框选 / Ctrl 多选分镜 →「创建工作流」
- [x] `metadata.workflow_groups` 持久化(随项目导出)
- [x] 流水线配置:生图 → 生视频 → 配音(可勾选)
- [x] 「整组重跑」按组内分镜顺序依次执行
- [x] 分镜节点显示所属工作流标签
### 数据结构(实际实现)
```json
{
"canvas_layout": {
"version": 1,
"viewport": { "x": 0, "y": 0, "zoom": 0.75 },
"nodes": { "sb:12": { "x": 360, "y": 144 } },
"updated_at": "2026-06-15T12:00:00.000Z"
},
"workflow_groups": [
{
"id": "wg-1700000000-abc123",
"title": "第一场批量",
"storyboard_ids": [12, 13, 14],
"pipeline": ["image", "video", "audio"],
"created_at": "2026-06-15T12:00:00.000Z"
}
]
}
```
> 注:早期草案中的 `node_refs` 已改为 `storyboard_ids`(仅存分镜 ID,媒体节点由 adapter 动态生成)。
---
## 阶段 D:交互与媒体对齐(2026-06-15,已完成)
### 交付物
#### 1. 连线与布局
- [x] 连线改为 Vue Flow 贝塞尔曲线(`type: default``curvature: 0.62`
- [x] **默认布局改为竖排**:每个分镜占一行,自上而下;单行内仍为「分镜 → 媒体链」横向展开
- [x] 分镜顺序链改为上下连接(`chain-out` / `chain-in` 锚点)
#### 2. 节点内操作面板(无需切列表模式)
- [x] 单击分镜 / 媒体 / 素材节点,下方展开操作面板;单击空白画布关闭
- [x] 分镜面板:编辑动作、对白、提示词;保存 / 润色 / 生图 / 生视频 / 配音
- [x] 媒体面板:预览 + 对应步骤重跑
- [x] 素材面板:信息展示 + 生成参考图 + 高亮关联分镜
- [x] 面板区域 `nodrag nopan`,避免与画布拖拽冲突
#### 3. 媒体读取与列表模式对齐
- [x] `useCanvasStoryboardMedia.js`:进入画布时按集批量拉取 `/images``/videos`
- [x] `storyboardMedia.js`:首帧 / 尾帧 / 主图 / 视频解析(对齐 `FilmCreate.getSbFirstImage` 等)
- [x] 后端 `dramaService.rowToStoryboard` 补全 `first_frame_*``last_frame_*` 字段
- [x] 首尾帧模式:画布展示 **首帧** + **尾帧** 两个图节点(`sbimg-first` / `sbimg-last`
- [x] 画布内生成视频时传递 `first_frame_url` / `last_frame_url`
#### 4. 全能模式(`creation_mode === 'universal'`
- [x] 不展示空的分镜图节点
- [x] 流水线:`分镜 → 全能分镜词 → 视频`(节点 `sbuni:{id}`kind `universal`
- [x] 分镜卡片显示「全能」徽章;操作面板编辑 `universal_segment_text`,隐藏生图入口
#### 5. 框选与工作流交互修复
- [x] Vue Flow 1.48 移除 `selection-on-drag`,改为 `:selection-key-code="true"` 实现左键框选
- [x] `:pan-on-drag="[1, 2]"`:左键框选,中键/右键平移画布
---
## 默认布局规则(2026-06-15
```
┌─────────────────────────────────────────────────────────────────┐
│ 顶栏:列表模式 | 集数 | 工作流条(创建/选择/整组重跑/删除) │
├──────────────┬──────────────────────────────────────────────────┤
│ 素材库 │ 第1集 │
│ 👤 角色 │ [SB#1] ─→ [文本] ─→ [首帧] ─→ [尾帧] ─→ [视频] │
│ 🏞 场景 │ [SB#2] ─→ ... (竖排,每镜一行) │
│ 🎭 道具 │ [SB#3] ─→ [全能分镜词] ─→ [视频] (全能模式) │
│ 工作流列表 │ │
└──────────────┴──────────────────────────────────────────────────┘
```
- 左栏(x≈48):角色 / 场景 / 道具 + 工作流列表
- 右栏(x≥360):每集标题 + 分镜竖排;单行内媒体节点横向排列
- 虚线(绿色):素材 → 分镜
- 实线(紫色/蓝):分镜 → 媒体、分镜 ↓ 分镜(顺序链)
- 曲线:所有边为贝塞尔曲线
> 已有 `canvas_layout` 的项目仍使用已保存坐标;清除 metadata 中 `canvas_layout.nodes` 可恢复新默认竖排。
---
## 节点 ID 规范
| ID 格式 | 含义 |
|---------|------|
| `char:{id}` | 角色 |
| `scene:{id}` | 场景 |
| `prop:{id}` | 道具 |
| `episode:{id}` | 集标题 |
| `drama:header` | 项目标题 |
| `sb:{id}` | 分镜 |
| `sbtxt:{id}` | 分镜文本摘要(经典模式) |
| `sbuni:{id}` | 全能分镜词(全能模式) |
| `sbimg:{id}` | 分镜图(经典单图) |
| `sbimg-first:{id}` | 首帧图(首尾帧模式) |
| `sbimg-last:{id}` | 尾帧图(首尾帧模式) |
| `sbvid:{id}` | 分镜视频 |
| `sbaud:{id}:dialogue` | 对白音频 |
---
## 用户使用说明
### 画布基本操作
| 操作 | 效果 |
|------|------|
| 左键在**空白处**拖拽 | 框选多个分镜 |
| Ctrl + 点击分镜 | 多选 / 取消选择 |
| 中键 / 右键拖拽 | 平移画布 |
| 滚轮 | 缩放 |
| 单击节点 | 展开下方操作面板 |
| 单击空白 | 关闭操作面板 |
| 双击分镜 | 跳转列表模式并定位 |
| 拖动节点 | 保存布局(debounce |
### 工作流(批量生成)
1. 框选或 Ctrl 选中多个**分镜节点**(带 `#N` 的卡片,不是媒体子节点)
2. 勾选步骤:生图 / 生视频 / 配音(全能模式分镜建议只勾生视频)
3.**创建工作流**,输入名称
4.**选择工作流** 下拉框(或左侧列表)选中该组
5.**整组重跑**:按组内分镜顺序依次执行;某一镜失败则停止
6. **删除工作流** 仅删除分组配置,不删除分镜与媒体
### 经典 / 首尾帧 / 全能 三种流水线展示
| 模式 | 画布媒体链 |
|------|------------|
| 经典 | 文本 → 分镜图 → 视频 →(音频) |
| 首尾帧(`metadata.storyboard_use_first_last_frame`) | 文本 → 首帧 → 尾帧 → 视频 →(音频) |
| 全能(`creation_mode: universal`) | 全能分镜词 → 视频 →(音频) |
---
## 文件结构
```
frontweb/src/
views/DramaCanvas.vue
composables/
useCanvasContext.js # provide/inject 画布上下文
useCanvasStoryboardMedia.js # 批量加载 images/videos
useCanvasWorkflowRunner.js # 单步/整组生成(export runImageStep 等)
utils/
dramaCanvasAdapter.js # drama → Vue Flow 图
canvasLayout.js # layout 解析/持久化
canvasWorkflow.js # workflow_groups CRUD
storyboardMedia.js # 首帧/尾帧/视频 URL 解析(对齐列表模式)
mediaUrl.js
components/dramaCanvas/
CanvasAssetNode.vue
CanvasAssetPanel.vue
CanvasEpisodeNode.vue
CanvasDramaHeaderNode.vue
CanvasLabelNode.vue
CanvasStoryboardNode.vue
CanvasStoryboardPanel.vue
CanvasMediaNode.vue
CanvasMediaPanel.vue
backend-node/src/services/
dramaService.js # rowToStoryboard 含首尾帧字段
```
### API
- `GET /api/v1/dramas/:id` — 项目数据(含 metadata
- `PUT /api/v1/dramas/:id/canvas-layout` — 保存 `canvas_layout` 和/或 `workflow_groups`
- `GET /api/v1/images?storyboard_id=` — 画布加载分镜图列表
- `GET /api/v1/videos?storyboard_id=` — 画布加载分镜视频列表
---
## Vue Flow 配置要点
```vue
<VueFlow
:selection-key-code="true"
:pan-on-drag="[1, 2]"
:pan-on-scroll="true"
:elements-selectable="true"
/>
```
> **勿使用** 已废弃的 `selection-on-drag`@vue-flow/core 1.41+ 已移除,配置了也不生效)。
---
## 风险与规避
| 风险 | 规避 |
|------|------|
| 分镜过多画布过长 | 集数筛选 + fitView + 小地图 + 竖排一镜一行 |
| metadata 合并覆盖 | update 时 merge 现有 metadata |
| 画布与列表媒体不一致 | 统一走 `storyboardMedia.js` + images/videos API |
| 全能模式误触生图 | 全能分镜隐藏生图;工作流勾选项需用户自行判断 |
| 框选无效 | 必须在空白处拖拽;或 Ctrl 点击多选 |
| Vue Flow 包体积 | 画布路由按需加载 |
---
## 后续可选 / TODO
### 画布与工作流
- [ ] 全能模式画布内润色 / 流式编辑全能词
- [ ] 工作流组内分镜顺序可视化拖拽调整
- [ ] 多集同时展示时的节点虚拟化(仅渲染视口内)
- [ ] 画布内首尾帧单独生图(走 frame-prompt 模块,对齐列表模式完整能力)
- [ ] 顶栏工作流区域增加简短帮助 tooltip
### 场景与素材
- [ ] **场景图 → 全景图**:基于已有场景参考图/背景图,AI 扩展或生成超宽/360° 全景图;可用于场景库展示、分镜大景别运镜参考,以及全能模式 `@图片N` 的环境图;需评估与现有 `scenes` 表、`generateImage` / 四视图流程的衔接方式
### 其他
- [ ] 分镜参考图自由上传(列表模式已有部分能力,画布侧统一入口)
- [ ] 参考图自由选择(生成分镜图时手动指定角色/场景参考)
+258
View File
@@ -0,0 +1,258 @@
# 快速开始 / 开发指南
**导航:[项目主页](../README.md) | [English](en.md) | [AI 配置](configuration.md) | [版本历史](changelog.md)**
---
## 目录
- [运行方式一:下载 exe(推荐普通用户)](#运行方式一下载-exe推荐普通用户)
- [运行方式二:开发模式(推荐开发者)](#运行方式二开发模式推荐开发者)
- [环境要求](#环境要求)
- [启动后端](#1-启动后端)
- [启动前端](#2-启动前端)
- [一键启动脚本](#3-一键启动脚本)
- [打包为 Windows exe](#打包为-windows-exe)
- [配置文件说明](#配置文件说明)
- [数据库与数据目录](#数据库与数据目录)
- [常见问题 FAQ](#常见问题-faq)
---
## 运行方式一:下载 exe(推荐普通用户)
1. 前往 **[Releases](../../releases)** 页面下载最新版本:
- `本地短剧助手 Setup x.x.x.exe` — NSIS 安装包(推荐,可选安装路径)
- `本地短剧助手 x.x.x.exe` — 免安装便携版,解压即用
2. 双击运行,软件会自动启动内置后端服务。
3. 首次运行会在以下路径生成配置文件:
```
%APPDATA%\LocalMiniDrama\backend\configs\config.yaml
```
4. 点击软件右上角「AI 配置」,填入你的 AI API Key,即可开始使用。
> 💡 不知道去哪里申请 API Key?请看 → [AI 配置指南](configuration.md)
---
## 运行方式二:开发模式(推荐开发者)
### 环境要求
| 依赖 | 版本要求 |
|------|----------|
| Node.js | >= 18 |
| npm | 随 Node.js 附带 |
| Git | 任意版本 |
---
### 1. 启动后端
```bash
cd backend-node
# 安装依赖
npm install
# 复制配置文件模板
cp configs/config.example.yaml configs/config.yaml
# Windows PowerShell:
# copy configs\config.example.yaml configs\config.yaml
# 编辑 config.yaml,填入你的 AI API 地址与密钥(见配置指南)
# 首次运行:初始化数据库
npm run migrate
# 启动服务(默认端口 5679
npm start
# 开发模式(热重载)
npm run dev
```
后端启动成功后,终端会输出:
```
Server started on port 5679
```
---
### 2. 启动前端
**新开一个终端窗口:**
```bash
cd frontweb
# 安装依赖
npm install
# 启动开发服务器(默认端口 3013,自动代理到后端 5679)
npm run dev
```
浏览器访问 `http://localhost:3013` 即可看到界面。
---
### 3. 一键启动脚本
在项目根目录提供了一键启动脚本,**同时启动后端和前端**:
**Windows(双击运行):**
```
run_dev.bat
```
**PowerShell**
```powershell
.\run_dev.ps1
```
脚本会分别在两个窗口中启动后端(端口 5679)和前端(端口 3013),并自动打开浏览器。
---
## 打包为 Windows exe
> 打包前请先确保已完成后端和前端的 `npm install`。
```bash
cd desktop
# 安装 Electron 相关依赖
npm install
# 打包(生成 NSIS 安装包 + 便携版 exe
npm run dist
# 国内网络 Electron 下载慢时,使用镜像加速:
npm run dist:cn
```
打包产物位于 `desktop/release/` 目录:
- `本地短剧助手 Setup x.x.x.exe` — NSIS 安装包
- `本地短剧助手 x.x.x.exe` — 便携版
**打包原理:**
1. 构建前端静态文件
2. 复制后端代码与前端产物到 `desktop/`
3. electron-builder 打包为 Windows exe
---
## 配置文件说明
配置文件位于 `backend-node/configs/config.yaml`(开发模式)或 `%APPDATA%\LocalMiniDrama\backend\configs\config.yaml`exe 模式)。
主要配置项:
```yaml
server:
port: 5679 # 后端端口
database:
path: ./data/drama_generator.db # SQLite 数据库路径
storage:
local_path: ./data/storage # 生成图片/视频的本地存储目录
language: zh # 界面及提示词语言(zh / en)
style:
default_style: realistic # 默认画风
default_image_ratio: "16:9" # 默认图片比例
default_video_ratio: "16:9" # 默认视频比例
```
AI 服务配置通过软件内「AI 配置」页面管理,无需手动编辑 YAML。
详细说明请见 → [AI 配置指南](configuration.md)
---
## 数据库与数据目录
| 路径 | 说明 |
|------|------|
| `backend-node/data/drama_generator.db` | SQLite 数据库(开发模式) |
| `backend-node/data/storage/` | 生成的图片和视频文件 |
| `%APPDATA%\LocalMiniDrama\` | exe 模式下的所有数据 |
> ⚠️ 升级版本前建议备份 `data/` 目录;数据库会在启动时自动执行迁移脚本,一般无需手动操作。
---
## 常见问题 FAQ
### Q: 后端启动报错 `Cannot find module 'better-sqlite3'`
```bash
cd backend-node
npm install
```
如果仍然报错,可能是 Node.js 版本不兼容,请升级到 >= 18。
---
### Q: 前端报错 `Failed to fetch` 或 API 请求 404
确认后端已正常启动(终端显示 `Server started on port 5679`),且前端代理配置指向正确端口。
检查 `frontweb/vite.config.js` 中的 `proxy` 配置,确保 target 为 `http://localhost:5679`
---
### Q: 打包 exe 时 Electron 下载失败
使用国内镜像:
```bash
cd desktop
npm run dist:cn
```
或手动设置环境变量后再运行:
```bash
set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
npm run dist
```
---
### Q: 生成的图片/视频保存在哪里?
开发模式:`backend-node/data/storage/`
exe 模式:`%APPDATA%\LocalMiniDrama\backend\data\storage\`
目录结构:
```
storage/
├── images/ # 分镜生成的图片
├── characters/ # 角色图片
├── scenes/ # 场景图片
├── videos/ # 生成的视频片段
└── merged/ # 合成后的完整视频
```
---
### Q: 如何备份/迁移项目数据?
**方法一(推荐)**:在软件首页点击项目卡片上的「导出」按钮,下载 ZIP 格式的工程文件,在新机器上导入即可。
**方法二**:直接备份整个 `data/` 目录,将其复制到新机器的相同位置。
---
### Q: 支持 Mac / Linux 吗?
目前仅测试了 Windows。后端(Node.js)理论上跨平台,前端(Vue 3)完全跨平台,但桌面版(Electron)打包仅配置了 Windows 目标。
欢迎提 PR 添加 Mac / Linux 打包支持。
---
[← 返回项目主页](../README.md)
+80
View File
@@ -0,0 +1,80 @@
# 关于作者 & 开发碎碎念
**导航:[项目主页](../README.md) | [English](en.md) | 作者故事**
---
## 我是谁
一个平平无奇的游戏搬砖工,程序出身,但一直想主导产品,每天在各种 AI 软件里左右横跳、反复横跳——今天觉得这个 AI 好用,明天觉得那个 AI 更香,主打一个没定性。
看着短视频行业的东风吹得漫天响,脑子一热就想扎进 AI 漫剧赛道,但问题来了:**我到底干啥啊??**
**纠结点 1**:老老实实搬砖?不甘心,觉得浪费风口;
**纠结点 2**:搞 AI 剧发行?没资源没渠道,心里发慌;
**纠结点 3**:自己做 AI 剧 / AI 解说剧?又怕做出来没人看,纯属白费功夫;
**纠结点 4**:搞 AI 剧 Agent 开发?好像有点技术含量,但又怕超出自己能力范围;
咱就是说,闲不住星人实锤,纠结到头发掉一把,也没定下来主攻哪个方向。索性参考了市面上一堆同类软件,想起自己就只会点 JavaScript,那就瞎琢磨着做了这个开源项目。
---
## 为什么要做这个项目
短剧生成的项目这两年逐渐多了起来,市面上各种 AI 短剧生成工具层出不穷,但坦率说,真正开源、能够在本地桌面环境下直接运行、无需繁琐配置的项目依然凤毛麟角。很多产品要么仅有网页版,要么是闭源平台,要么部署方式极为复杂,门槛很高。
正因如此,开发这个本地 AI 短剧生成桌面端的初衷,就是让更多用户能够**下载即用,无需科学上网,无需多环境依赖**,打开软件就能直接生成属于自己的 AI 短剧。省心省事,兼顾隐私,对于大众普通用户和开发者来说都更加友好。
这也是我坚持做这个项目并开源发布的主要原因之一:希望推动短剧 AI 工具往易用、本地化方向发展,为有相关需求的同好提供一个开源、可自定义的选择,让 AI 短剧真正跑在你自己的电脑上,随时随地都能体验。
关于大模型的部署,我这里不做介绍,部署完在配置里填入网址就行。
---
## 项目定位(依然很纠结版)
- 说它是练手?好像又有点想往商业化靠;说它是商业化雏形?又觉得自己没那个底气;
- 开源吧,一方面想分享点自己瞎琢磨的技术,跟同好交流交流;另一方面也想抱开源社区的大腿,帮我优化优化,省得我自己瞎折腾;
- 至于未来?随缘吧,纠结归纠结,先做了再说,万一哪天就不纠结了(也万一哪天就放弃了),至少还留下点东西,也希望能碰到同样纠结着入局 AI 漫剧的朋友,一起唠唠、一起进步~
---
## 参考与致谢
项目开发过程中参考了以下工具,在此致谢:
- **Kino 视界**:国内较为活跃的 AI 短剧生成平台,侧重于 AI 生成与剧本可视化,支持云端编辑与分享。
- **Filmaction AI**:主打 AI 自动生成剧情、分镜和配音,强调创作者效率提升,部分功能付费,以 SaaS/Web 端为主。
- **oiioii**:兼具 AI 视频/漫画/短剧生成能力,原为开源项目,定位轻量化 AI 可视化创作工具,部署灵活,较适合技术爱好者。
- **ChatFirechatfire**:以 AI 驱动的剧情生成/对话体短剧创作工具,支持多端发布和 API 对接。**对本项目帮助最大**,后端及剧本生成部分的设计思路主要参考了它。只是它的服务器是 Go 写的,我虽然也写过半年 Go,但好多年没碰了,所以后端改用了自己熟悉的 Node.js。
- 其它如 **AIGC 剧本工厂**、**画冰 AI 剧本**、**追更短剧 AI** 等平台/工具,部分主打面向短视频自媒体/创作者群体,或提供 API 作为 SaaS 接口。
如果你有更好的同类工具推荐,欢迎 [Issue](../../issues) 补充交流。
---
## 联系我
如果你也在内容 / 工具 / 短剧 / AI / 游戏开发之间反复横跳,欢迎一起玩、一起改、一起把它变得更强。
有任何建议、交流、合作欢迎加微信:
<img src="../项目截图/wx.jpg" alt="微信二维码" width="220" />
<br/>
<img src="../项目截图/微信群.jpg" alt="微信交流群" width="220"/><br/>
---
## 📄 License
[MIT](../LICENSE)
Binary file not shown.