Merge branch 'master' into feature/docker-mirror-source

This commit is contained in:
june-zj
2026-01-21 15:43:01 +08:00
committed by GitHub
14 changed files with 998 additions and 857 deletions

576
README-CN.md Normal file
View File

@@ -0,0 +1,576 @@
# 🎬 Huobao Drama - AI 短剧生成平台
<div align="center">
**基于 Go + Vue3 的全栈 AI 短剧自动化生产平台**
[![Go Version](https://img.shields.io/badge/Go-1.23+-00ADD8?style=flat&logo=go)](https://golang.org)
[![Vue Version](https://img.shields.io/badge/Vue-3.x-4FC08D?style=flat&logo=vue.js)](https://vuejs.org)
[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
[功能特性](#功能特性) • [快速开始](#快速开始) • [部署指南](#部署指南)
[简体中文](README-CN.md) | [English](README.md) | [日本語](README-JA.md)
</div>
---
## 📖 项目简介
Huobao Drama 是一个基于 AI 的短剧自动化生产平台,实现从剧本生成、角色设计、分镜制作到视频合成的全流程自动化。
### 🎯 核心价值
- **🤖 AI 驱动**:使用大语言模型解析剧本,提取角色、场景和分镜信息
- **🎨 智能创作**AI 绘图生成角色形象和场景背景
- **📹 视频生成**:基于文生视频和图生视频模型自动生成分镜视频
- **🔄 工作流**:完整的短剧制作工作流,从创意到成片一站式完成
### 🛠️ 技术架构
采用**DDD 领域驱动设计**,清晰分层:
```
├── API层 (Gin HTTP)
├── 应用服务层 (Business Logic)
├── 领域层 (Domain Models)
└── 基础设施层 (Database, External Services)
```
### 🎥 作品展示 / Demo Videos
体验 AI 短剧生成效果:
<div align="center">
**示例作品 1**
<video src="https://ffile.chatfire.site/cf/public/20260114094337396.mp4" controls width="640"></video>
**示例作品 2**
<video src="https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4" controls width="640"></video>
[点击观看视频 1](https://ffile.chatfire.site/cf/public/20260114094337396.mp4) | [点击观看视频 2](https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4)
</div>
---
## ✨ 功能特性
### 🎭 角色管理
- ✅ AI 生成角色形象
- ✅ 批量角色生成
- ✅ 角色图片上传和管理
### 🎬 分镜制作
- ✅ 自动生成分镜脚本
- ✅ 场景描述和镜头设计
- ✅ 分镜图片生成(文生图)
- ✅ 帧类型选择(首帧/关键帧/尾帧/分镜板)
### 🎥 视频生成
- ✅ 图生视频自动生成
- ✅ 视频合成和剪辑
- ✅ 转场效果
### 📦 资源管理
- ✅ 素材库统一管理
- ✅ 本地存储支持
- ✅ 资源导入导出
- ✅ 任务进度追踪
---
## 🚀 快速开始
### 📋 环境要求
| 软件 | 版本要求 | 说明 |
| ----------- | -------- | -------------------- |
| **Go** | 1.23+ | 后端运行环境 |
| **Node.js** | 18+ | 前端构建环境 |
| **npm** | 9+ | 包管理工具 |
| **FFmpeg** | 4.0+ | 视频处理(**必需** |
| **SQLite** | 3.x | 数据库(已内置) |
#### 安装 FFmpeg
**macOS:**
```bash
brew install ffmpeg
```
**Ubuntu/Debian:**
```bash
sudo apt update
sudo apt install ffmpeg
```
**Windows:**
从 [FFmpeg 官网](https://ffmpeg.org/download.html) 下载并配置环境变量
验证安装:
```bash
ffmpeg -version
```
### ⚙️ 配置文件
复制并编辑配置文件:
```bash
cp configs/config.example.yaml configs/config.yaml
vim configs/config.yaml
```
配置文件格式(`configs/config.yaml`
```yaml
app:
name: "Huobao Drama API"
version: "1.0.0"
debug: true # 开发环境设为true生产环境设为false
server:
port: 5678
host: "0.0.0.0"
cors_origins:
- "http://localhost:3012"
read_timeout: 600
write_timeout: 600
database:
type: "sqlite"
path: "./data/drama_generator.db"
max_idle: 10
max_open: 100
storage:
type: "local"
local_path: "./data/storage"
base_url: "http://localhost:5678/static"
ai:
default_text_provider: "openai"
default_image_provider: "openai"
default_video_provider: "doubao"
```
**重要配置项:**
- `app.debug`: 调试模式开关(开发环境建议设为 true
- `server.port`: 服务运行端口
- `server.cors_origins`: 允许跨域访问的前端地址
- `database.path`: SQLite 数据库文件路径
- `storage.local_path`: 本地文件存储路径
- `storage.base_url`: 静态资源访问 URL
- `ai.default_*_provider`: AI 服务提供商配置(在 Web 界面中配置具体的 API Key
### 📥 安装依赖
```bash
# 克隆项目
git clone https://github.com/chatfire-AI/huobao-drama.git
cd huobao-drama
# 安装Go依赖
go mod download
# 安装前端依赖
cd web
npm install
cd ..
```
### 🎯 启动项目
#### 方式一:开发模式(推荐)
**前后端分离,支持热重载**
```bash
# 终端1启动后端服务
go run main.go
# 终端2启动前端开发服务器
cd web
npm run dev
```
- 前端地址: `http://localhost:3012`
- 后端 API: `http://localhost:5678/api/v1`
- 前端自动代理 API 请求到后端
#### 方式二:单服务模式
**后端同时提供 API 和前端静态文件**
```bash
# 1. 构建前端
cd web
npm run build
cd ..
# 2. 启动服务
go run main.go
```
访问: `http://localhost:5678`
### 🗄️ 数据库初始化
数据库表会在首次启动时自动创建(使用 GORM AutoMigrate无需手动迁移。
---
## 📦 部署指南
### 🐳 Docker 部署(推荐)
#### 方式一Docker Compose推荐
```bash
# 启动服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 停止服务
docker-compose down
```
#### 方式二Docker 命令
> **注意**Linux 用户需添加 `--add-host=host.docker.internal:host-gateway` 以访问宿主机服务
```bash
# 从 Docker Hub 运行
docker run -d \
--name huobao-drama \
-p 5678:5678 \
-v $(pwd)/data:/app/data \
--restart unless-stopped \
huobao/huobao-drama:latest
# 查看日志
docker logs -f huobao-drama
```
**本地构建**(可选):
```bash
docker build -t huobao-drama:latest .
docker run -d --name huobao-drama -p 5678:5678 -v $(pwd)/data:/app/data huobao-drama:latest
```
**Docker 部署优势:**
- ✅ 开箱即用,内置默认配置
- ✅ 环境一致性,避免依赖问题
- ✅ 一键启动,无需安装 Go、Node.js、FFmpeg
- ✅ 易于迁移和扩展
- ✅ 自动健康检查和重启
- ✅ 自动处理文件权限,无需手动配置
#### 🔗 访问宿主机服务Ollama/本地模型)
容器已配置支持访问宿主机服务,直接使用 `http://host.docker.internal:端口号` 即可。
**配置步骤:**
1. **宿主机启动服务(监听所有接口)**
```bash
export OLLAMA_HOST=0.0.0.0:11434 && ollama serve
```
2. **前端 AI 服务配置**
- Base URL: `http://host.docker.internal:11434/v1`
- Provider: `openai`
- Model: `qwen2.5:latest`
---
### 🏭 传统部署方式
#### 1. 编译构建
```bash
# 1. 构建前端
cd web
npm run build
cd ..
# 2. 编译后端
go build -o huobao-drama .
```
生成文件:
- `huobao-drama` - 后端可执行文件
- `web/dist/` - 前端静态文件(已嵌入后端)
#### 2. 准备部署文件
需要上传到服务器的文件:
```
huobao-drama # 后端可执行文件
configs/config.yaml # 配置文件
data/ # 数据目录(可选,首次运行自动创建)
```
#### 3. 服务器配置
```bash
# 上传文件到服务器
scp huobao-drama user@server:/opt/huobao-drama/
scp configs/config.yaml user@server:/opt/huobao-drama/configs/
# SSH登录服务器
ssh user@server
# 修改配置文件
cd /opt/huobao-drama
vim configs/config.yaml
# 设置mode为production
# 配置域名和存储路径
# 创建数据目录并设置权限(重要!)
# 注意:将 YOUR_USER 替换为实际运行服务的用户名(如 www-data、ubuntu、deploy 等)
sudo mkdir -p /opt/huobao-drama/data/storage
sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data
sudo chmod -R 755 /opt/huobao-drama/data
# 赋予执行权限
chmod +x huobao-drama
# 启动服务
./huobao-drama
```
#### 4. 使用 systemd 管理服务
创建服务文件 `/etc/systemd/system/huobao-drama.service`:
```ini
[Unit]
Description=Huobao Drama Service
After=network.target
[Service]
Type=simple
User=YOUR_USER
WorkingDirectory=/opt/huobao-drama
ExecStart=/opt/huobao-drama/huobao-drama
Restart=on-failure
RestartSec=10
# 环境变量(可选)
# Environment="GIN_MODE=release"
[Install]
WantedBy=multi-user.target
```
启动服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable huobao-drama
sudo systemctl start huobao-drama
sudo systemctl status huobao-drama
```
**⚠️ 常见问题SQLite 写权限错误**
如果遇到 `attempt to write a readonly database` 错误:
```bash
# 1. 确认当前运行服务的用户
sudo systemctl status huobao-drama | grep "Main PID"
ps aux | grep huobao-drama
# 2. 修复权限(将 YOUR_USER 替换为实际用户名)
sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data
sudo chmod -R 755 /opt/huobao-drama/data
# 3. 验证权限
ls -la /opt/huobao-drama/data
# 应该显示所有者为运行服务的用户
# 4. 重启服务
sudo systemctl restart huobao-drama
```
**原因说明**
- SQLite 需要对数据库文件 **和** 所在目录都有写权限
- 需要在目录中创建临时文件(如 `-wal`、`-journal`
- **关键**:确保 systemd 配置中的 `User` 与数据目录所有者一致
**常用用户名**
- Ubuntu/Debian: `www-data`、`ubuntu`
- CentOS/RHEL: `nginx`、`apache`
- 自定义部署: `deploy`、`app`、当前登录用户
#### 5. Nginx 反向代理
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 静态文件直接访问
location /static/ {
alias /opt/huobao-drama/data/storage/;
}
}
```
---
## 🎨 技术栈
### 后端技术
- **语言**: Go 1.23+
- **Web 框架**: Gin 1.9+
- **ORM**: GORM
- **数据库**: SQLite
- **日志**: Zap
- **视频处理**: FFmpeg
- **AI 服务**: OpenAI、Gemini、火山等
### 前端技术
- **框架**: Vue 3.4+
- **语言**: TypeScript 5+
- **构建工具**: Vite 5
- **UI 组件**: Element Plus
- **CSS 框架**: TailwindCSS
- **状态管理**: Pinia
- **路由**: Vue Router 4
### 开发工具
- **包管理**: Go Modules, npm
- **代码规范**: ESLint, Prettier
- **版本控制**: Git
---
## 📝 常见问题
### Q: Docker 容器如何访问宿主机的 Ollama
A: 使用 `http://host.docker.internal:11434/v1` 作为 Base URL。注意两点
1. 宿主机 Ollama 需监听 `0.0.0.0``export OLLAMA_HOST=0.0.0.0:11434 && ollama serve`
2. Linux 用户使用 `docker run` 需添加:`--add-host=host.docker.internal:host-gateway`
详见:[DOCKER_HOST_ACCESS.md](docs/DOCKER_HOST_ACCESS.md)
### Q: FFmpeg 未安装或找不到?
A: 确保 FFmpeg 已安装并在 PATH 环境变量中。运行 `ffmpeg -version` 验证。
### Q: 前端无法连接后端 API
A: 检查后端是否启动,端口是否正确。开发模式下前端代理配置在 `web/vite.config.ts`。
### Q: 数据库表未创建?
A: GORM 会在首次启动时自动创建表,检查日志确认迁移是否成功。
---
## <20> 更新日志 / Changelog
### v1.0.2 (2026-01-16)
#### 🚀 重大更新
- SQLite 纯 Go 驱动(`modernc.org/sqlite`),支持 `CGO_ENABLED=0` 跨平台编译
- 优化并发性能WAL 模式),解决 "database is locked" 错误
- Docker 跨平台支持 `host.docker.internal` 访问宿主机服务
- 精简文档和部署指南
### v1.0.1 (2026-01-14)
#### 🐛 Bug Fixes / 🔧 Improvements
- 修复视频生成 API 响应解析问题
- 添加 OpenAI Sora 视频端点配置
- 优化错误处理和日志输出
---
## 🤝 贡献指南
欢迎提交 Issue 和 Pull Request
1. Fork 本项目
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交改动 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
---
## API 配置站点
2 分钟完成配置:[API 聚合站点](https://api.chatfire.site/models)
---
## 👨‍💻 关于我们
**AI 火宝 - AI 工作室创业中**
- 🏠 **位置**: 中国南京
- 🚀 **状态**: 创业中
- 📧 **Email**: [18550175439@163.com](mailto:18550175439@163.com)
- 💬 **微信**: dangbao1117 (私人微信不解答任何技术问题)
- 🐙 **GitHub**: [https://github.com/chatfire-AI/huobao-drama](https://github.com/chatfire-AI/huobao-drama)
> _"让 AI 帮我们做更有创造力的事"_
## 项目交流群
![项目交流群](drama.png)
- 提交 [Issue](../../issues)
- 发送邮件至项目维护者
---
<div align="center">
**⭐ 如果这个项目对你有帮助,请给一个 Star**
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=chatfire-AI/huobao-drama&type=date&legend=top-left)](https://www.star-history.com/#chatfire-AI/huobao-drama&type=date&legend=top-left)
Made with ❤️ by Huobao Team
</div>

View File

@@ -1,577 +0,0 @@
# 🎬 Huobao Drama - AI Short Drama Production Platform
<div align="center">
**Full-stack AI Short Drama Automation Platform Based on Go + Vue3**
[![Go Version](https://img.shields.io/badge/Go-1.23+-00ADD8?style=flat&logo=go)](https://golang.org)
[![Vue Version](https://img.shields.io/badge/Vue-3.x-4FC08D?style=flat&logo=vue.js)](https://vuejs.org)
[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
[Features](#features) • [Quick Start](#quick-start) • [Deployment](#deployment)
[简体中文](README.md) | [English](README-EN.md) | [日本語](README-JA.md)
</div>
---
## 📖 About
Huobao Drama is an AI-powered short drama production platform that automates the entire workflow from script generation, character design, storyboarding to video composition.
### 🎯 Core Features
- **🤖 AI-Driven**: Parse scripts using large language models to extract characters, scenes, and storyboards
- **🎨 Intelligent Creation**: AI-generated character portraits and scene backgrounds
- **📹 Video Generation**: Automatic storyboard video generation using text-to-video and image-to-video models
- **🔄 Complete Workflow**: End-to-end production workflow from idea to final video
### 🛠️ Technical Architecture
Based on **DDD (Domain-Driven Design)** with clear layering:
```
├── API Layer (Gin HTTP)
├── Application Service Layer (Business Logic)
├── Domain Layer (Domain Models)
└── Infrastructure Layer (Database, External Services)
```
### 🎥 Demo Videos
Experience AI short drama generation:
<div align="center">
**Sample Work 1**
<video src="https://ffile.chatfire.site/cf/public/20260114094337396.mp4" controls width="640"></video>
**Sample Work 2**
<video src="https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4" controls width="640"></video>
[Watch Video 1](https://ffile.chatfire.site/cf/public/20260114094337396.mp4) | [Watch Video 2](https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4)
</div>
---
## ✨ Features
### 🎭 Character Management
- ✅ AI-generated character portraits
- ✅ Batch character generation
- ✅ Character image upload and management
### 🎬 Storyboard Production
- ✅ Automatic storyboard script generation
- ✅ Scene descriptions and shot design
- ✅ Storyboard image generation (text-to-image)
- ✅ Frame type selection (first frame/key frame/last frame/panel)
### 🎥 Video Generation
- ✅ Automatic image-to-video generation
- ✅ Video composition and editing
- ✅ Transition effects
### 📦 Asset Management
- ✅ Unified asset library management
- ✅ Local storage support
- ✅ Asset import/export
- ✅ Task progress tracking
---
## 🚀 Quick Start
### 📋 Prerequisites
| Software | Version | Description |
| ----------- | ------- | ------------------------------- |
| **Go** | 1.23+ | Backend runtime |
| **Node.js** | 18+ | Frontend build environment |
| **npm** | 9+ | Package manager |
| **FFmpeg** | 4.0+ | Video processing (**Required**) |
| **SQLite** | 3.x | Database (built-in) |
#### Installing FFmpeg
**macOS:**
```bash
brew install ffmpeg
```
**Ubuntu/Debian:**
```bash
sudo apt update
sudo apt install ffmpeg
```
**Windows:**
Download from [FFmpeg Official Site](https://ffmpeg.org/download.html) and configure environment variables
Verify installation:
```bash
ffmpeg -version
```
### ⚙️ Configuration
Copy and edit the configuration file:
```bash
cp configs/config.example.yaml configs/config.yaml
vim configs/config.yaml
```
Configuration file format (`configs/config.yaml`):
```yaml
app:
name: "Huobao Drama API"
version: "1.0.0"
debug: true # Set to true for development, false for production
server:
port: 5678
host: "0.0.0.0"
cors_origins:
- "http://localhost:3012"
read_timeout: 600
write_timeout: 600
database:
type: "sqlite"
path: "./data/drama_generator.db"
max_idle: 10
max_open: 100
storage:
type: "local"
local_path: "./data/storage"
base_url: "http://localhost:5678/static"
ai:
default_text_provider: "openai"
default_image_provider: "openai"
default_video_provider: "doubao"
```
**Key Configuration Items:**
- `app.debug`: Debug mode switch (recommended true for development)
- `server.port`: Service port
- `server.cors_origins`: Allowed CORS origins for frontend
- `database.path`: SQLite database file path
- `storage.local_path`: Local file storage path
- `storage.base_url`: Static resource access URL
- `ai.default_*_provider`: AI service provider configuration (API keys configured in Web UI)
### 📥 Installation
```bash
# Clone the project
git clone https://github.com/chatfire-AI/huobao-drama.git
cd huobao-drama
# Install Go dependencies
go mod download
# Install frontend dependencies
cd web
npm install
cd ..
```
### 🎯 Starting the Project
#### Method 1: Development Mode (Recommended)
**Frontend and backend separation with hot reload**
```bash
# Terminal 1: Start backend service
go run main.go
# Terminal 2: Start frontend dev server
cd web
npm run dev
```
- Frontend: `http://localhost:3012`
- Backend API: `http://localhost:5678/api/v1`
- Frontend automatically proxies API requests to backend
#### Method 2: Single Service Mode
**Backend serves both API and frontend static files**
```bash
# 1. Build frontend
cd web
npm run build
cd ..
# 2. Start service
go run main.go
```
Access: `http://localhost:5678`
### 🗄️ Database Initialization
Database tables are automatically created on first startup (using GORM AutoMigrate), no manual migration needed.
---
## 📦 Deployment
### 🐳 Docker Deployment (Recommended)
#### Method 1: Docker Compose (Recommended)
```bash
# Start services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop services
docker-compose down
```
#### Method 2: Docker Command
> **Note**: Linux users need to add `--add-host=host.docker.internal:host-gateway` to access host services
```bash
# Run from Docker Hub
docker run -d \
--name huobao-drama \
-p 5678:5678 \
-v $(pwd)/data:/app/data \
--restart unless-stopped \
huobao/huobao-drama:latest
# View logs
docker logs -f huobao-drama
```
**Local Build** (optional):
```bash
docker build -t huobao-drama:latest .
docker run -d --name huobao-drama -p 5678:5678 -v $(pwd)/data:/app/data huobao-drama:latest
```
**Docker Deployment Advantages:**
- ✅ Ready to use with default configuration
- ✅ Environment consistency, avoiding dependency issues
- ✅ One-click start, no need to install Go, Node.js, FFmpeg
- ✅ Easy to migrate and scale
- ✅ Automatic health checks and restarts
- ✅ Automatic file permission handling
#### 🔗 Accessing Host Services (Ollama/Local Models)
The container is configured to access host services using `http://host.docker.internal:PORT`.
**Configuration Steps:**
1. **Start service on host (listen on all interfaces)**
```bash
export OLLAMA_HOST=0.0.0.0:11434 && ollama serve
```
2. **Frontend AI Service Configuration**
- Base URL: `http://host.docker.internal:11434/v1`
- Provider: `openai`
- Model: `qwen2.5:latest`
---
### 🏭 Traditional Deployment
#### 1. Build
```bash
# 1. Build frontend
cd web
npm run build
cd ..
# 2. Compile backend
go build -o huobao-drama .
```
Generated files:
- `huobao-drama` - Backend executable
- `web/dist/` - Frontend static files (embedded in backend)
#### 2. Prepare Deployment Files
Files to upload to server:
```
huobao-drama # Backend executable
configs/config.yaml # Configuration file
data/ # Data directory (optional, auto-created on first run)
```
#### 3. Server Configuration
```bash
# Upload files to server
scp huobao-drama user@server:/opt/huobao-drama/
scp configs/config.yaml user@server:/opt/huobao-drama/configs/
# SSH to server
ssh user@server
# Modify configuration file
cd /opt/huobao-drama
vim configs/config.yaml
# Set mode to production
# Configure domain and storage path
# Create data directory and set permissions (Important!)
# Note: Replace YOUR_USER with actual user running the service (e.g., www-data, ubuntu, deploy)
sudo mkdir -p /opt/huobao-drama/data/storage
sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data
sudo chmod -R 755 /opt/huobao-drama/data
# Grant execute permission
chmod +x huobao-drama
# Start service
./huobao-drama
```
#### 4. Manage Service with systemd
Create service file `/etc/systemd/system/huobao-drama.service`:
```ini
[Unit]
Description=Huobao Drama Service
After=network.target
[Service]
Type=simple
User=YOUR_USER
WorkingDirectory=/opt/huobao-drama
ExecStart=/opt/huobao-drama/huobao-drama
Restart=on-failure
RestartSec=10
# Environment variables (optional)
# Environment="GIN_MODE=release"
[Install]
WantedBy=multi-user.target
```
Start service:
```bash
sudo systemctl daemon-reload
sudo systemctl enable huobao-drama
sudo systemctl start huobao-drama
sudo systemctl status huobao-drama
```
**⚠️ Common Issue: SQLite Write Permission Error**
If you encounter `attempt to write a readonly database` error:
```bash
# 1. Check current user running the service
sudo systemctl status huobao-drama | grep "Main PID"
ps aux | grep huobao-drama
# 2. Fix permissions (replace YOUR_USER with actual username)
sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data
sudo chmod -R 755 /opt/huobao-drama/data
# 3. Verify permissions
ls -la /opt/huobao-drama/data
# Should show owner as the user running the service
# 4. Restart service
sudo systemctl restart huobao-drama
```
**Reason:**
- SQLite requires write permission on both the database file **and** its directory
- Needs to create temporary files in the directory (e.g., `-wal`, `-journal`)
- **Key**: Ensure systemd `User` matches data directory owner
**Common Usernames:**
- Ubuntu/Debian: `www-data`, `ubuntu`
- CentOS/RHEL: `nginx`, `apache`
- Custom deployment: `deploy`, `app`, current logged-in user
#### 5. Nginx Reverse Proxy
```nginx
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Direct access to static files
location /static/ {
alias /opt/huobao-drama/data/storage/;
}
}
```
---
## 🎨 Tech Stack
### Backend
- **Language**: Go 1.23+
- **Web Framework**: Gin 1.9+
- **ORM**: GORM
- **Database**: SQLite
- **Logging**: Zap
- **Video Processing**: FFmpeg
- **AI Services**: OpenAI, Gemini, Doubao, etc.
### Frontend
- **Framework**: Vue 3.4+
- **Language**: TypeScript 5+
- **Build Tool**: Vite 5
- **UI Components**: Element Plus
- **CSS Framework**: TailwindCSS
- **State Management**: Pinia
- **Router**: Vue Router 4
### Development Tools
- **Package Management**: Go Modules, npm
- **Code Standards**: ESLint, Prettier
- **Version Control**: Git
---
## 📝 FAQ
### Q: How can Docker containers access Ollama on the host?
A: Use `http://host.docker.internal:11434/v1` as Base URL. Note two things:
1. Host Ollama needs to listen on `0.0.0.0`: `export OLLAMA_HOST=0.0.0.0:11434 && ollama serve`
2. Linux users using `docker run` need to add: `--add-host=host.docker.internal:host-gateway`
See: [DOCKER_HOST_ACCESS.md](docs/DOCKER_HOST_ACCESS.md)
### Q: FFmpeg not installed or not found?
A: Ensure FFmpeg is installed and in the PATH environment variable. Verify with `ffmpeg -version`.
### Q: Frontend cannot connect to backend API?
A: Check if backend is running and port is correct. In development mode, frontend proxy config is in `web/vite.config.ts`.
### Q: Database tables not created?
A: GORM automatically creates tables on first startup, check logs to confirm migration success.
---
## 📋 Changelog
### v1.0.2 (2026-01-16)
#### 🚀 Major Updates
- Pure Go SQLite driver (`modernc.org/sqlite`), supports `CGO_ENABLED=0` cross-platform compilation
- Optimized concurrency performance (WAL mode), resolved "database is locked" errors
- Docker cross-platform support for `host.docker.internal` to access host services
- Streamlined documentation and deployment guides
### v1.0.1 (2026-01-14)
#### 🐛 Bug Fixes / 🔧 Improvements
- Fixed video generation API response parsing issues
- Added OpenAI Sora video endpoint configuration
- Optimized error handling and logging
---
## 🤝 Contributing
Issues and Pull Requests are welcome!
1. Fork this project
2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
---
## API Configuration Site
Configure in 2 minutes: [API Aggregation Site](https://api.chatfire.site/models)
---
## 👨‍💻 About Us
**AI Huobao - AI Studio Startup**
- 🏠 **Location**: Nanjing, China
- 🚀 **Status**: Startup in Progress
- 📧 **Email**: [18550175439@163.com](mailto:18550175439@163.com)
- 💬 **WeChat**: dangbao1117 (Personal WeChat - No technical support)
- 🐙 **GitHub**: [https://github.com/chatfire-AI/huobao-drama](https://github.com/chatfire-AI/huobao-drama)
> _"Let AI help us do more creative things"_
## Community Group
![Community Group](drama.png)
- Submit [Issue](../../issues)
- Email project maintainers
---
<div align="center">
**⭐ If this project helps you, please give it a Star!**
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=chatfire-AI/huobao-drama&type=date&legend=top-left)](https://www.star-history.com/#chatfire-AI/huobao-drama&type=date&legend=top-left)
Made with ❤️ by Huobao Team
</div>

View File

@@ -10,7 +10,7 @@
[機能](#機能) • [クイックスタート](#クイックスタート) • [デプロイ](#デプロイ) [機能](#機能) • [クイックスタート](#クイックスタート) • [デプロイ](#デプロイ)
[简体中文](README.md) | [English](README-EN.md) | [日本語](README-JA.md) [简体中文](README-CN.md) | [English](README.md) | [日本語](README-JA.md)
</div> </div>

470
README.md
View File

@@ -1,108 +1,106 @@
# 🎬 Huobao Drama - AI 短剧生成平台 # 🎬 Huobao Drama - AI Short Drama Production Platform
<div align="center"> <div align="center">
**基于 Go + Vue3 的全栈 AI 短剧自动化生产平台** **Full-stack AI Short Drama Automation Platform Based on Go + Vue3**
[![Go Version](https://img.shields.io/badge/Go-1.23+-00ADD8?style=flat&logo=go)](https://golang.org) [![Go Version](https://img.shields.io/badge/Go-1.23+-00ADD8?style=flat&logo=go)](https://golang.org)
[![Vue Version](https://img.shields.io/badge/Vue-3.x-4FC08D?style=flat&logo=vue.js)](https://vuejs.org) [![Vue Version](https://img.shields.io/badge/Vue-3.x-4FC08D?style=flat&logo=vue.js)](https://vuejs.org)
[![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/) [![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
[功能特性](#功能特性) • [快速开始](#快速开始) • [部署指南](#部署指南) [Features](#features) • [Quick Start](#quick-start) • [Deployment](#deployment)
[简体中文](README.md) | [English](README-EN.md) | [日本語](README-JA.md) [简体中文](README-CN.md) | [English](README.md) | [日本語](README-JA.md)
</div> </div>
--- ---
## 📖 项目简介 / About ## 📖 About
Huobao Drama 是一个基于 AI 的短剧自动化生产平台,实现从剧本生成、角色设计、分镜制作到视频合成的全流程自动化。
Huobao Drama is an AI-powered short drama production platform that automates the entire workflow from script generation, character design, storyboarding to video composition. Huobao Drama is an AI-powered short drama production platform that automates the entire workflow from script generation, character design, storyboarding to video composition.
### 🎯 核心价值 / Core Features ### 🎯 Core Features
- **🤖 AI 驱动 / AI-Driven**:使用大语言模型解析剧本,提取角色、场景和分镜信息 | Parse scripts using large language models to extract characters, scenes, and storyboards - **🤖 AI-Driven**: Parse scripts using large language models to extract characters, scenes, and storyboards
- **🎨 智能创作 / Intelligent Creation**AI 绘图生成角色形象和场景背景 | AI-generated character portraits and scene backgrounds - **🎨 Intelligent Creation**: AI-generated character portraits and scene backgrounds
- **📹 视频生成 / Video Generation**:基于文生视频和图生视频模型自动生成分镜视频 | Automatic storyboard video generation using text-to-video and image-to-video models - **📹 Video Generation**: Automatic storyboard video generation using text-to-video and image-to-video models
- **🔄 工作流 / Workflow**:完整的短剧制作工作流,从创意到成片一站式完成 | Complete production workflow from idea to final video - **🔄 Complete Workflow**: End-to-end production workflow from idea to final video
### 🛠️ 技术架构 ### 🛠️ Technical Architecture
采用**DDD 领域驱动设计**,清晰分层: Based on **DDD (Domain-Driven Design)** with clear layering:
``` ```
├── API (Gin HTTP) ├── API Layer (Gin HTTP)
├── 应用服务层 (Business Logic) ├── Application Service Layer (Business Logic)
├── 领域层 (Domain Models) ├── Domain Layer (Domain Models)
└── 基础设施层 (Database, External Services) └── Infrastructure Layer (Database, External Services)
``` ```
### 🎥 作品展示 / Demo Videos ### 🎥 Demo Videos
体验 AI 短剧生成效果: Experience AI short drama generation:
<div align="center"> <div align="center">
**示例作品 1** **Sample Work 1**
<video src="https://ffile.chatfire.site/cf/public/20260114094337396.mp4" controls width="640"></video> <video src="https://ffile.chatfire.site/cf/public/20260114094337396.mp4" controls width="640"></video>
**示例作品 2** **Sample Work 2**
<video src="https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4" controls width="640"></video> <video src="https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4" controls width="640"></video>
[点击观看视频 1](https://ffile.chatfire.site/cf/public/20260114094337396.mp4) | [点击观看视频 2](https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4) [Watch Video 1](https://ffile.chatfire.site/cf/public/20260114094337396.mp4) | [Watch Video 2](https://ffile.chatfire.site/cf/public/fcede75e8aeafe22031dbf78f86285b8.mp4)
</div> </div>
--- ---
## ✨ 功能特性 ## ✨ Features
### 🎭 角色管理 ### 🎭 Character Management
- ✅ AI 生成角色形象 - ✅ AI-generated character portraits
-批量角色生成 -Batch character generation
-角色图片上传和管理 -Character image upload and management
### 🎬 分镜制作 ### 🎬 Storyboard Production
-自动生成分镜脚本 -Automatic storyboard script generation
-场景描述和镜头设计 -Scene descriptions and shot design
-分镜图片生成(文生图) -Storyboard image generation (text-to-image)
-帧类型选择(首帧/关键帧/尾帧/分镜板) -Frame type selection (first frame/key frame/last frame/panel)
### 🎥 视频生成 ### 🎥 Video Generation
-图生视频自动生成 -Automatic image-to-video generation
-视频合成和剪辑 -Video composition and editing
-转场效果 -Transition effects
### 📦 资源管理 ### 📦 Asset Management
-素材库统一管理 -Unified asset library management
-本地存储支持 -Local storage support
-资源导入导出 -Asset import/export
-任务进度追踪 -Task progress tracking
--- ---
## 🚀 快速开始 ## 🚀 Quick Start
### 📋 环境要求 ### 📋 Prerequisites
| 软件 | 版本要求 | 说明 | | Software | Version | Description |
| ----------- | -------- | -------------------- | | ----------- | ------- | ------------------------------- |
| **Go** | 1.23+ | 后端运行环境 | | **Go** | 1.23+ | Backend runtime |
| **Node.js** | 18+ | 前端构建环境 | | **Node.js** | 18+ | Frontend build environment |
| **npm** | 9+ | 包管理工具 | | **npm** | 9+ | Package manager |
| **FFmpeg** | 4.0+ | 视频处理(**必需** | | **FFmpeg** | 4.0+ | Video processing (**Required**) |
| **SQLite** | 3.x | 数据库(已内置) | | **SQLite** | 3.x | Database (built-in) |
#### 安装 FFmpeg #### Installing FFmpeg
**macOS:** **macOS:**
@@ -118,30 +116,30 @@ sudo apt install ffmpeg
``` ```
**Windows:** **Windows:**
从 [FFmpeg 官网](https://ffmpeg.org/download.html) 下载并配置环境变量 Download from [FFmpeg Official Site](https://ffmpeg.org/download.html) and configure environment variables
验证安装: Verify installation:
```bash ```bash
ffmpeg -version ffmpeg -version
``` ```
### ⚙️ 配置文件 ### ⚙️ Configuration
复制并编辑配置文件: Copy and edit the configuration file:
```bash ```bash
cp configs/config.example.yaml configs/config.yaml cp configs/config.example.yaml configs/config.yaml
vim configs/config.yaml vim configs/config.yaml
``` ```
配置文件格式(`configs/config.yaml` Configuration file format (`configs/config.yaml`):
```yaml ```yaml
app: app:
name: "Huobao Drama API" name: "Huobao Drama API"
version: "1.0.0" version: "1.0.0"
debug: true # 开发环境设为true生产环境设为false debug: true # Set to true for development, false for production
server: server:
port: 5678 port: 5678
@@ -168,143 +166,96 @@ ai:
default_video_provider: "doubao" default_video_provider: "doubao"
``` ```
**重要配置项:** **Key Configuration Items:**
- `app.debug`: 调试模式开关(开发环境建议设为 true - `app.debug`: Debug mode switch (recommended true for development)
- `server.port`: 服务运行端口 - `server.port`: Service port
- `server.cors_origins`: 允许跨域访问的前端地址 - `server.cors_origins`: Allowed CORS origins for frontend
- `database.path`: SQLite 数据库文件路径 - `database.path`: SQLite database file path
- `storage.local_path`: 本地文件存储路径 - `storage.local_path`: Local file storage path
- `storage.base_url`: 静态资源访问 URL - `storage.base_url`: Static resource access URL
- `ai.default_*_provider`: AI 服务提供商配置(在 Web 界面中配置具体的 API Key - `ai.default_*_provider`: AI service provider configuration (API keys configured in Web UI)
### 📥 安装依赖 ### 📥 Installation
```bash ```bash
# 克隆项目 # Clone the project
git clone https://github.com/chatfire-AI/huobao-drama.git git clone https://github.com/chatfire-AI/huobao-drama.git
cd huobao-drama cd huobao-drama
# 安装Go依赖 # Install Go dependencies
go mod download go mod download
# 安装前端依赖 # Install frontend dependencies
cd web cd web
npm install npm install
cd .. cd ..
``` ```
### 🎯 启动项目 ### 🎯 Starting the Project
#### 方式一:开发模式(推荐) #### Method 1: Development Mode (Recommended)
**前后端分离,支持热重载** **Frontend and backend separation with hot reload**
```bash ```bash
# 终端1启动后端服务 # Terminal 1: Start backend service
go run main.go go run main.go
# 终端2启动前端开发服务器 # Terminal 2: Start frontend dev server
cd web cd web
npm run dev npm run dev
``` ```
- 前端地址: `http://localhost:3012` - Frontend: `http://localhost:3012`
- 后端 API: `http://localhost:5678/api/v1` - Backend API: `http://localhost:5678/api/v1`
- 前端自动代理 API 请求到后端 - Frontend automatically proxies API requests to backend
#### 方式二:单服务模式 #### Method 2: Single Service Mode
**后端同时提供 API 和前端静态文件** **Backend serves both API and frontend static files**
```bash ```bash
# 1. 构建前端 # 1. Build frontend
cd web cd web
npm run build npm run build
cd .. cd ..
# 2. 启动服务 # 2. Start service
go run main.go go run main.go
``` ```
访问: `http://localhost:5678` Access: `http://localhost:5678`
### 🗄️ 数据库初始化 ### 🗄️ Database Initialization
数据库表会在首次启动时自动创建(使用 GORM AutoMigrate无需手动迁移。 Database tables are automatically created on first startup (using GORM AutoMigrate), no manual migration needed.
--- ---
## 📦 部署指南 ## 📦 Deployment
### 🐳 Docker 部署(推荐) ### 🐳 Docker Deployment (Recommended)
#### 🚀 国内网络加速(可选) #### Method 1: Docker Compose (Recommended)
如果您在国内网络环境下Docker 拉取镜像和安装依赖可能较慢。可以通过配置镜像源加速构建过程。
**步骤1创建环境变量文件**
```bash ```bash
cp .env.example .env # Start services
```
**步骤2编辑 `.env` 文件,取消注释需要的镜像源**
```bash
# 启用 Docker Hub 镜像(推荐)
DOCKER_REGISTRY=docker.1ms.run/
# 启用 npm 镜像
NPM_REGISTRY=https://registry.npmmirror.com/
# 启用 Go 代理
GO_PROXY=https://goproxy.cn,direct
# 启用 Alpine 镜像
ALPINE_MIRROR=mirrors.aliyun.com
```
**步骤3使用 docker compose 构建(必须)**
```bash
docker compose build
```
> **重要说明**
> - ⚠️ 必须使用 `docker compose build` 才能自动加载 `.env` 文件中的镜像源配置
> - ❌ 如果使用 `docker build` 命令,需要手动传递 `--build-arg` 参数
> - ✅ 推荐始终使用 `docker compose build` 进行构建
**效果对比**
| 操作 | 不配置镜像源 | 配置镜像源后 |
|------|------------|-------------|
| 拉取基础镜像 | 5-30分钟 | 1-5分钟 |
| 安装 npm 依赖 | 可能失败 | 快速成功 |
| 下载 Go 依赖 | 5-10分钟 | 30秒-1分钟 |
> **注意**:国外用户请勿配置镜像源,使用默认配置即可。
#### 方式一Docker Compose推荐
```bash
# 启动服务
docker-compose up -d docker-compose up -d
# 查看日志 # View logs
docker-compose logs -f docker-compose logs -f
# 停止服务 # Stop services
docker-compose down docker-compose down
``` ```
#### 方式二Docker 命令 #### Method 2: Docker Command
> **注意**Linux 用户需添加 `--add-host=host.docker.internal:host-gateway` 以访问宿主机服务 > **Note**: Linux users need to add `--add-host=host.docker.internal:host-gateway` to access host services
```bash ```bash
# Docker Hub 运行 # Run from Docker Hub
docker run -d \ docker run -d \
--name huobao-drama \ --name huobao-drama \
-p 5678:5678 \ -p 5678:5678 \
@@ -312,106 +263,106 @@ docker run -d \
--restart unless-stopped \ --restart unless-stopped \
huobao/huobao-drama:latest huobao/huobao-drama:latest
# 查看日志 # View logs
docker logs -f huobao-drama docker logs -f huobao-drama
``` ```
**本地构建**(可选): **Local Build** (optional):
```bash ```bash
docker build -t huobao-drama:latest . docker build -t huobao-drama:latest .
docker run -d --name huobao-drama -p 5678:5678 -v $(pwd)/data:/app/data huobao-drama:latest docker run -d --name huobao-drama -p 5678:5678 -v $(pwd)/data:/app/data huobao-drama:latest
``` ```
**Docker 部署优势:** **Docker Deployment Advantages:**
-开箱即用,内置默认配置 -Ready to use with default configuration
-环境一致性,避免依赖问题 -Environment consistency, avoiding dependency issues
-一键启动,无需安装 GoNode.jsFFmpeg -One-click start, no need to install Go, Node.js, FFmpeg
-易于迁移和扩展 -Easy to migrate and scale
-自动健康检查和重启 -Automatic health checks and restarts
-自动处理文件权限,无需手动配置 -Automatic file permission handling
#### 🔗 访问宿主机服务Ollama/本地模型) #### 🔗 Accessing Host Services (Ollama/Local Models)
容器已配置支持访问宿主机服务,直接使用 `http://host.docker.internal:端口号` 即可。 The container is configured to access host services using `http://host.docker.internal:PORT`.
**配置步骤:** **Configuration Steps:**
1. **宿主机启动服务(监听所有接口)** 1. **Start service on host (listen on all interfaces)**
```bash ```bash
export OLLAMA_HOST=0.0.0.0:11434 && ollama serve export OLLAMA_HOST=0.0.0.0:11434 && ollama serve
``` ```
2. **前端 AI 服务配置** 2. **Frontend AI Service Configuration**
- Base URL: `http://host.docker.internal:11434/v1` - Base URL: `http://host.docker.internal:11434/v1`
- Provider: `openai` - Provider: `openai`
- Model: `qwen2.5:latest` - Model: `qwen2.5:latest`
--- ---
### 🏭 传统部署方式 ### 🏭 Traditional Deployment
#### 1. 编译构建 #### 1. Build
```bash ```bash
# 1. 构建前端 # 1. Build frontend
cd web cd web
npm run build npm run build
cd .. cd ..
# 2. 编译后端 # 2. Compile backend
go build -o huobao-drama . go build -o huobao-drama .
``` ```
生成文件: Generated files:
- `huobao-drama` - 后端可执行文件 - `huobao-drama` - Backend executable
- `web/dist/` - 前端静态文件(已嵌入后端) - `web/dist/` - Frontend static files (embedded in backend)
#### 2. 准备部署文件 #### 2. Prepare Deployment Files
需要上传到服务器的文件: Files to upload to server:
``` ```
huobao-drama # 后端可执行文件 huobao-drama # Backend executable
configs/config.yaml # 配置文件 configs/config.yaml # Configuration file
data/ # 数据目录(可选,首次运行自动创建) data/ # Data directory (optional, auto-created on first run)
``` ```
#### 3. 服务器配置 #### 3. Server Configuration
```bash ```bash
# 上传文件到服务器 # Upload files to server
scp huobao-drama user@server:/opt/huobao-drama/ scp huobao-drama user@server:/opt/huobao-drama/
scp configs/config.yaml user@server:/opt/huobao-drama/configs/ scp configs/config.yaml user@server:/opt/huobao-drama/configs/
# SSH登录服务器 # SSH to server
ssh user@server ssh user@server
# 修改配置文件 # Modify configuration file
cd /opt/huobao-drama cd /opt/huobao-drama
vim configs/config.yaml vim configs/config.yaml
# 设置mode为production # Set mode to production
# 配置域名和存储路径 # Configure domain and storage path
# 创建数据目录并设置权限(重要!) # Create data directory and set permissions (Important!)
# 注意:将 YOUR_USER 替换为实际运行服务的用户名(如 www-dataubuntudeploy 等) # Note: Replace YOUR_USER with actual user running the service (e.g., www-data, ubuntu, deploy)
sudo mkdir -p /opt/huobao-drama/data/storage sudo mkdir -p /opt/huobao-drama/data/storage
sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data
sudo chmod -R 755 /opt/huobao-drama/data sudo chmod -R 755 /opt/huobao-drama/data
# 赋予执行权限 # Grant execute permission
chmod +x huobao-drama chmod +x huobao-drama
# 启动服务 # Start service
./huobao-drama ./huobao-drama
``` ```
#### 4. 使用 systemd 管理服务 #### 4. Manage Service with systemd
创建服务文件 `/etc/systemd/system/huobao-drama.service`: Create service file `/etc/systemd/system/huobao-drama.service`:
```ini ```ini
[Unit] [Unit]
@@ -426,14 +377,14 @@ ExecStart=/opt/huobao-drama/huobao-drama
Restart=on-failure Restart=on-failure
RestartSec=10 RestartSec=10
# 环境变量(可选) # Environment variables (optional)
# Environment="GIN_MODE=release" # Environment="GIN_MODE=release"
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
启动服务: Start service:
```bash ```bash
sudo systemctl daemon-reload sudo systemctl daemon-reload
@@ -442,40 +393,40 @@ sudo systemctl start huobao-drama
sudo systemctl status huobao-drama sudo systemctl status huobao-drama
``` ```
**⚠️ 常见问题SQLite 写权限错误** **⚠️ Common Issue: SQLite Write Permission Error**
如果遇到 `attempt to write a readonly database` 错误: If you encounter `attempt to write a readonly database` error:
```bash ```bash
# 1. 确认当前运行服务的用户 # 1. Check current user running the service
sudo systemctl status huobao-drama | grep "Main PID" sudo systemctl status huobao-drama | grep "Main PID"
ps aux | grep huobao-drama ps aux | grep huobao-drama
# 2. 修复权限(将 YOUR_USER 替换为实际用户名) # 2. Fix permissions (replace YOUR_USER with actual username)
sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data sudo chown -R YOUR_USER:YOUR_USER /opt/huobao-drama/data
sudo chmod -R 755 /opt/huobao-drama/data sudo chmod -R 755 /opt/huobao-drama/data
# 3. 验证权限 # 3. Verify permissions
ls -la /opt/huobao-drama/data ls -la /opt/huobao-drama/data
# 应该显示所有者为运行服务的用户 # Should show owner as the user running the service
# 4. 重启服务 # 4. Restart service
sudo systemctl restart huobao-drama sudo systemctl restart huobao-drama
``` ```
**原因说明** **Reason:**
- SQLite 需要对数据库文件 **和** 所在目录都有写权限 - SQLite requires write permission on both the database file **and** its directory
- 需要在目录中创建临时文件(如 `-wal``-journal` - Needs to create temporary files in the directory (e.g., `-wal`, `-journal`)
- **关键**:确保 systemd 配置中的 `User` 与数据目录所有者一致 - **Key**: Ensure systemd `User` matches data directory owner
**常用用户名** **Common Usernames:**
- Ubuntu/Debian: `www-data``ubuntu` - Ubuntu/Debian: `www-data`, `ubuntu`
- CentOS/RHEL: `nginx``apache` - CentOS/RHEL: `nginx`, `apache`
- 自定义部署: `deploy``app`、当前登录用户 - Custom deployment: `deploy`, `app`, current logged-in user
#### 5. Nginx 反向代理 #### 5. Nginx Reverse Proxy
```nginx ```nginx
server { server {
@@ -489,7 +440,7 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
} }
# 静态文件直接访问 # Direct access to static files
location /static/ { location /static/ {
alias /opt/huobao-drama/data/storage/; alias /opt/huobao-drama/data/storage/;
} }
@@ -498,128 +449,129 @@ server {
--- ---
## 🎨 技术栈 ## 🎨 Tech Stack
### 后端技术 ### Backend
- **语言**: Go 1.23+ - **Language**: Go 1.23+
- **Web 框架**: Gin 1.9+ - **Web Framework**: Gin 1.9+
- **ORM**: GORM - **ORM**: GORM
- **数据库**: SQLite - **Database**: SQLite
- **日志**: Zap - **Logging**: Zap
- **视频处理**: FFmpeg - **Video Processing**: FFmpeg
- **AI 服务**: OpenAIGemini、火山等 - **AI Services**: OpenAI, Gemini, Doubao, etc.
### 前端技术 ### Frontend
- **框架**: Vue 3.4+ - **Framework**: Vue 3.4+
- **语言**: TypeScript 5+ - **Language**: TypeScript 5+
- **构建工具**: Vite 5 - **Build Tool**: Vite 5
- **UI 组件**: Element Plus - **UI Components**: Element Plus
- **CSS 框架**: TailwindCSS - **CSS Framework**: TailwindCSS
- **状态管理**: Pinia - **State Management**: Pinia
- **路由**: Vue Router 4 - **Router**: Vue Router 4
### 开发工具 ### Development Tools
- **包管理**: Go Modules, npm - **Package Management**: Go Modules, npm
- **代码规范**: ESLint, Prettier - **Code Standards**: ESLint, Prettier
- **版本控制**: Git - **Version Control**: Git
--- ---
## 📝 常见问题 ## 📝 FAQ
### Q: Docker 容器如何访问宿主机的 Ollama ### Q: How can Docker containers access Ollama on the host?
A: 使用 `http://host.docker.internal:11434/v1` 作为 Base URL。注意两点: A: Use `http://host.docker.internal:11434/v1` as Base URL. Note two things:
1. 宿主机 Ollama 需监听 `0.0.0.0``export OLLAMA_HOST=0.0.0.0:11434 && ollama serve` 1. Host Ollama needs to listen on `0.0.0.0`: `export OLLAMA_HOST=0.0.0.0:11434 && ollama serve`
2. Linux 用户使用 `docker run` 需添加:`--add-host=host.docker.internal:host-gateway` 2. Linux users using `docker run` need to add: `--add-host=host.docker.internal:host-gateway`
详见:[DOCKER_HOST_ACCESS.md](docs/DOCKER_HOST_ACCESS.md) See: [DOCKER_HOST_ACCESS.md](docs/DOCKER_HOST_ACCESS.md)
### Q: FFmpeg 未安装或找不到? ### Q: FFmpeg not installed or not found?
A: 确保 FFmpeg 已安装并在 PATH 环境变量中。运行 `ffmpeg -version` 验证。 A: Ensure FFmpeg is installed and in the PATH environment variable. Verify with `ffmpeg -version`.
### Q: 前端无法连接后端 API ### Q: Frontend cannot connect to backend API?
A: 检查后端是否启动,端口是否正确。开发模式下前端代理配置在 `web/vite.config.ts` A: Check if backend is running and port is correct. In development mode, frontend proxy config is in `web/vite.config.ts`.
### Q: 数据库表未创建? ### Q: Database tables not created?
A: GORM 会在首次启动时自动创建表,检查日志确认迁移是否成功。 A: GORM automatically creates tables on first startup, check logs to confirm migration success.
--- ---
## <EFBFBD> 更新日志 / Changelog ## 📋 Changelog
### v1.0.2 (2026-01-16) ### v1.0.2 (2026-01-16)
#### 🚀 重大更新 #### 🚀 Major Updates
- SQLite 纯 Go 驱动(`modernc.org/sqlite`),支持 `CGO_ENABLED=0` 跨平台编译 - Pure Go SQLite driver (`modernc.org/sqlite`), supports `CGO_ENABLED=0` cross-platform compilation
- 优化并发性能WAL 模式),解决 "database is locked" 错误 - Optimized concurrency performance (WAL mode), resolved "database is locked" errors
- Docker 跨平台支持 `host.docker.internal` 访问宿主机服务 - Docker cross-platform support for `host.docker.internal` to access host services
- 精简文档和部署指南 - Streamlined documentation and deployment guides
### v1.0.1 (2026-01-14) ### v1.0.1 (2026-01-14)
#### 🐛 Bug Fixes / 🔧 Improvements #### 🐛 Bug Fixes / 🔧 Improvements
- 修复视频生成 API 响应解析问题 - Fixed video generation API response parsing issues
- 添加 OpenAI Sora 视频端点配置 - Added OpenAI Sora video endpoint configuration
- 优化错误处理和日志输出 - Optimized error handling and logging
--- ---
## 🤝 贡献指南 ## 🤝 Contributing
欢迎提交 Issue 和 Pull Request Issues and Pull Requests are welcome!
1. Fork 本项目 1. Fork this project
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`) 2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
3. 提交改动 (`git commit -m 'Add some AmazingFeature'`) 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`) 4. Push to the branch (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request 5. Open a Pull Request
--- ---
## API 配置站点 ## API Configuration Site
2 分钟完成配置:[API 聚合站点](https://api.chatfire.site/models) Configure in 2 minutes: [API Aggregation Site](https://api.chatfire.site/models)
--- ---
## 👨‍💻 关于我们 ## 👨‍💻 About Us
**AI 火宝 - AI 工作室创业中** **AI Huobao - AI Studio Startup**
- 🏠 **位置**: 中国南京 - 🏠 **Location**: Nanjing, China
- 🚀 **状态**: 创业中 - 🚀 **Status**: Startup in Progress
- 📧 **Email**: [18550175439@163.com](mailto:18550175439@163.com) - 📧 **Email**: [18550175439@163.com](mailto:18550175439@163.com)
- 💬 **微信**: dangbao1117 (私人微信不解答任何技术问题) - 💬 **WeChat**: dangbao1117 (Personal WeChat - No technical support)
- 🐙 **GitHub**: [https://github.com/chatfire-AI/huobao-drama](https://github.com/chatfire-AI/huobao-drama) - 🐙 **GitHub**: [https://github.com/chatfire-AI/huobao-drama](https://github.com/chatfire-AI/huobao-drama)
> _" AI 帮我们做更有创造力的事"_ > _"Let AI help us do more creative things"_
## 项目交流群 ## Community Group
![项目交流群](drama.png) ![Community Group](drama.png)
- 提交 [Issue](../../issues) - Submit [Issue](../../issues)
- 发送邮件至项目维护者 - Email project maintainers
--- ---
<div align="center"> <div align="center">
**如果这个项目对你有帮助,请给一个 Star** **If this project helps you, please give it a Star!**
## Star History ## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=chatfire-AI/huobao-drama&type=date&legend=top-left)](https://www.star-history.com/#chatfire-AI/huobao-drama&type=date&legend=top-left) [![Star History Chart](https://api.star-history.com/svg?repos=chatfire-AI/huobao-drama&type=date&legend=top-left)](https://www.star-history.com/#chatfire-AI/huobao-drama&type=date&legend=top-left)
Made with ❤️ by Huobao Team Made with ❤️ by Huobao Team
</div> </div>

View File

@@ -74,6 +74,28 @@ func (h *SceneHandler) GenerateSceneImage(c *gin.Context) {
}) })
} }
func (h *SceneHandler) UpdateScenePrompt(c *gin.Context) {
sceneID := c.Param("scene_id")
var req services2.UpdateScenePromptRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request")
return
}
if err := h.sceneService.UpdateScenePrompt(sceneID, &req); err != nil {
h.log.Errorw("Failed to update scene prompt", "error", err, "scene_id", sceneID)
if err.Error() == "scene not found" {
response.NotFound(c, "场景不存在")
return
}
response.InternalError(c, err.Error())
return
}
response.Success(c, gin.H{"message": "场景提示词已更新"})
}
func (h *SceneHandler) DeleteScene(c *gin.Context) { func (h *SceneHandler) DeleteScene(c *gin.Context) {
sceneID := c.Param("scene_id") sceneID := c.Param("scene_id")

View File

@@ -137,6 +137,7 @@ func SetupRouter(cfg *config.Config, db *gorm.DB, log *logger.Logger, localStora
scenes := api.Group("/scenes") scenes := api.Group("/scenes")
{ {
scenes.PUT("/:scene_id", sceneHandler.UpdateScene) scenes.PUT("/:scene_id", sceneHandler.UpdateScene)
scenes.PUT("/:scene_id/prompt", sceneHandler.UpdateScenePrompt)
scenes.DELETE("/:scene_id", sceneHandler.DeleteScene) scenes.DELETE("/:scene_id", sceneHandler.DeleteScene)
scenes.POST("/generate-image", sceneHandler.GenerateSceneImage) scenes.POST("/generate-image", sceneHandler.GenerateSceneImage)
} }

View File

@@ -399,6 +399,28 @@ func (s *StoryboardCompositionService) GenerateSceneImage(req *GenerateSceneImag
return nil, fmt.Errorf("image generation service not available") return nil, fmt.Errorf("image generation service not available")
} }
type UpdateScenePromptRequest struct {
Prompt string `json:"prompt"`
}
func (s *StoryboardCompositionService) UpdateScenePrompt(sceneID string, req *UpdateScenePromptRequest) error {
var scene models.Scene
if err := s.db.Where("id = ?", sceneID).First(&scene).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return fmt.Errorf("scene not found")
}
return fmt.Errorf("failed to find scene: %w", err)
}
scene.Prompt = req.Prompt
if err := s.db.Save(&scene).Error; err != nil {
return fmt.Errorf("failed to update scene prompt: %w", err)
}
s.log.Infow("Scene prompt updated", "scene_id", sceneID, "prompt", req.Prompt)
return nil
}
func (s *StoryboardCompositionService) DeleteScene(sceneID string) error { func (s *StoryboardCompositionService) DeleteScene(sceneID string) error {
var scene models.Scene var scene models.Scene
if err := s.db.Where("id = ?", sceneID).First(&scene).Error; err != nil { if err := s.db.Where("id = ?", sceneID).First(&scene).Error; err != nil {

View File

@@ -107,7 +107,7 @@ func NewGeminiImageClient(baseURL, apiKey, model, endpoint string) *GeminiImageC
func (c *GeminiImageClient) GenerateImage(prompt string, opts ...ImageOption) (*ImageResult, error) { func (c *GeminiImageClient) GenerateImage(prompt string, opts ...ImageOption) (*ImageResult, error) {
options := &ImageOptions{ options := &ImageOptions{
Size: "1024x1024", Size: "1920x1920",
Quality: "standard", Quality: "standard",
} }

View File

@@ -63,7 +63,7 @@ func NewVolcEngineImageClient(baseURL, apiKey, model, endpoint, queryEndpoint st
func (c *VolcEngineImageClient) GenerateImage(prompt string, opts ...ImageOption) (*ImageResult, error) { func (c *VolcEngineImageClient) GenerateImage(prompt string, opts ...ImageOption) (*ImageResult, error) {
options := &ImageOptions{ options := &ImageOptions{
Size: "1024x1024", Size: "1920x1920",
Quality: "standard", Quality: "standard",
} }

View File

@@ -108,8 +108,12 @@ export const dramaAPI = {
return request.put(`/scenes/${sceneId}`, data) return request.put(`/scenes/${sceneId}`, data)
}, },
generateSceneImage(data: { scene_id: string; prompt?: string; model?: string }) { generateSceneImage(data: { scene_id: number; prompt?: string; model?: string }) {
return request.post('/scenes/generate-image', data) return request.post<{ image_generation: { id: number } }>('/scenes/generate-image', data)
},
updateScenePrompt(sceneId: string, prompt: string) {
return request.put(`/scenes/${sceneId}/prompt`, { prompt })
}, },
deleteScene(sceneId: string) { deleteScene(sceneId: string) {

View File

@@ -68,7 +68,9 @@ export default {
tip: '提示', tip: '提示',
status: '状态', status: '状态',
createdAt: '创建时间', createdAt: '创建时间',
updatedAt: '更新时间' updatedAt: '更新时间',
name: '名称',
description: '描述'
}, },
settings: { settings: {
title: '设置', title: '设置',

View File

@@ -241,10 +241,10 @@
<el-dialog v-model="addSceneDialogVisible" :title="$t('common.add')" width="600px"> <el-dialog v-model="addSceneDialogVisible" :title="$t('common.add')" width="600px">
<el-form :model="newScene" label-width="100px"> <el-form :model="newScene" label-width="100px">
<el-form-item :label="$t('common.name')"> <el-form-item :label="$t('common.name')">
<el-input v-model="newScene.name" :placeholder="$t('common.name')" /> <el-input v-model="newScene.location" :placeholder="$t('common.name')" />
</el-form-item> </el-form-item>
<el-form-item :label="$t('common.description')"> <el-form-item :label="$t('common.description')">
<el-input v-model="newScene.description" type="textarea" :rows="4" :placeholder="$t('common.description')" /> <el-input v-model="newScene.prompt" type="textarea" :rows="4" :placeholder="$t('common.description')" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
@@ -285,8 +285,8 @@ const newCharacter = ref({
}) })
const newScene = ref({ const newScene = ref({
name: '', location: '',
description: '' prompt: ''
}) })
const episodesCount = computed(() => drama.value?.episodes?.length || 0) const episodesCount = computed(() => drama.value?.episodes?.length || 0)
@@ -494,14 +494,14 @@ const deleteCharacter = async (character: any) => {
const openAddSceneDialog = () => { const openAddSceneDialog = () => {
newScene.value = { newScene.value = {
name: '', location: '',
description: '' prompt: ''
} }
addSceneDialogVisible.value = true addSceneDialogVisible.value = true
} }
const addScene = async () => { const addScene = async () => {
if (!newScene.value.name.trim()) { if (!newScene.value.location.trim()) {
ElMessage.warning('请输入场景名称') ElMessage.warning('请输入场景名称')
return return
} }
@@ -517,7 +517,11 @@ const addScene = async () => {
} }
const editScene = (scene: any) => { const editScene = (scene: any) => {
ElMessage.info('编辑功能开发中') newScene.value = {
location: scene.location || scene.name || '',
prompt: scene.prompt || scene.description || ''
}
addSceneDialogVisible.value = true
} }
const deleteScene = async (scene: any) => { const deleteScene = async (scene: any) => {

View File

@@ -1601,8 +1601,9 @@ const saveShotEdit = async () => {
// 对话框相关方法 // 对话框相关方法
const openPromptDialog = (item: any, type: 'character' | 'scene') => { const openPromptDialog = (item: any, type: 'character' | 'scene') => {
currentEditItem.value = item currentEditItem.value = item
currentEditItem.value.name = item.name || item.location
currentEditType.value = type currentEditType.value = type
editPrompt.value = item.appearance || item.description || '' editPrompt.value = item.prompt || item.appearance || item.description || ''
promptDialogVisible.value = true promptDialogVisible.value = true
} }
@@ -1614,10 +1615,32 @@ const savePrompt = async () => {
}) })
await generateCharacterImage(currentEditItem.value.id) await generateCharacterImage(currentEditItem.value.id)
} else { } else {
await dramaAPI.generateSceneImage({ // 1. 先保存场景提示词
await dramaAPI.updateScenePrompt(currentEditItem.value.id.toString(), editPrompt.value)
// 2. 生成场景图片
const model = selectedImageModel.value || undefined
const response = await dramaAPI.generateSceneImage({
scene_id: parseInt(currentEditItem.value.id), scene_id: parseInt(currentEditItem.value.id),
prompt: editPrompt.value prompt: editPrompt.value,
model
}) })
const imageGenId = response.image_generation?.id
// 3. 轮询图片生成状态
if (imageGenId) {
ElMessage.info('场景图片生成中,请稍候...')
generatingSceneImages.value[currentEditItem.value.id] = true
pollImageStatus(imageGenId, async () => {
await loadDramaData()
ElMessage.success('场景图片生成完成!')
}).finally(() => {
generatingSceneImages.value[currentEditItem.value.id] = false
})
} else {
ElMessage.success('场景图片生成已启动')
await loadDramaData()
}
} }
promptDialogVisible.value = false promptDialogVisible.value = false
} catch (error: any) { } catch (error: any) {

View File

@@ -360,6 +360,32 @@
<!-- 首帧 --> <!-- 首帧 -->
<div v-show="selectedVideoFrameType === 'first'" class="image-scroll-container" <div v-show="selectedVideoFrameType === 'first'" class="image-scroll-container"
style="max-height: 280px; overflow-y: auto; overflow-x: hidden;"> style="max-height: 280px; overflow-y: auto; overflow-x: hidden;">
<!-- 上一镜头尾帧推荐(紧凑版) -->
<div v-if="previousStoryboardLastFrames.length > 0" class="previous-frame-section">
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 6px;">
<el-tag size="small" type="primary">
上一镜头 #{{ previousStoryboard?.storyboard_number }} 尾帧
</el-tag>
<span class="hint-text">点击添加为首帧参考</span>
</div>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<div v-for="img in previousStoryboardLastFrames" :key="'prev-' + img.id"
class="reference-item"
:class="{ selected: selectedImagesForVideo.includes(img.id) }"
style="position: relative; border: 2px solid #1890ff; border-radius: 4px; overflow: hidden; cursor: pointer;"
@click="selectPreviousLastFrame(img)">
<el-image :src="img.image_url" fit="cover"
style="width: 60px; height: 40px; display: block; pointer-events: none;" />
<div v-if="selectedImagesForVideo.includes(img.id)"
style="position: absolute; top: 0; right: 0; background: #52c41a; color: #fff; font-size: 10px; padding: 1px 4px;">
</div>
</div>
</div>
</div>
<!-- 当前镜头首帧列表 -->
<div class="reference-grid" <div class="reference-grid"
style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; max-width: 600px;"> style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; max-width: 600px;">
<div <div
@@ -378,7 +404,7 @@
</div> </div>
</div> </div>
<el-empty <el-empty
v-if="!videoReferenceImages.some(i => i.status === 'completed' && i.image_url && i.frame_type === 'first')" v-if="!videoReferenceImages.some(i => i.status === 'completed' && i.image_url && i.frame_type === 'first') && previousStoryboardLastFrames.length === 0"
description="暂无首帧图片" size="small" /> description="暂无首帧图片" size="small" />
</div> </div>
@@ -883,7 +909,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { import {
ArrowLeft, Plus, Picture, VideoPlay, VideoPause, View, Setting, ArrowLeft, Plus, Picture, VideoPlay, VideoPause, View, Setting,
Upload, MagicStick, VideoCamera, ZoomIn, ZoomOut, Top, Bottom, Check, Close, Right, Upload, MagicStick, VideoCamera, ZoomIn, ZoomOut, Top, Bottom, Check, Close, Right,
Timer, Calendar, Clock, Loading, WarningFilled, Delete Timer, Calendar, Clock, Loading, WarningFilled, Delete, Connection
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import { dramaAPI } from '@/api/drama' import { dramaAPI } from '@/api/drama'
import { generateFramePrompt, type FrameType } from '@/api/frame' import { generateFramePrompt, type FrameType } from '@/api/frame'
@@ -1226,6 +1252,67 @@ const currentStoryboard = computed(() => {
return storyboards.value.find(s => String(s.id) === String(currentStoryboardId.value)) || null return storyboards.value.find(s => String(s.id) === String(currentStoryboardId.value)) || null
}) })
// 获取上一个镜头
const previousStoryboard = computed(() => {
if (!currentStoryboardId.value || storyboards.value.length < 2) return null
const currentIndex = storyboards.value.findIndex(s => String(s.id) === String(currentStoryboardId.value))
if (currentIndex <= 0) return null
return storyboards.value[currentIndex - 1]
})
// 上一个镜头的尾帧图片列表(支持多个)
const previousStoryboardLastFrames = ref<any[]>([])
// 加载上一个镜头的尾帧
const loadPreviousStoryboardLastFrame = async () => {
if (!previousStoryboard.value) {
previousStoryboardLastFrames.value = []
return
}
try {
const result = await imageAPI.listImages({
storyboard_id: previousStoryboard.value.id,
frame_type: 'last',
page: 1,
page_size: 10
})
const images = result.items || []
previousStoryboardLastFrames.value = images.filter((img: any) => img.status === 'completed' && img.image_url)
} catch (error) {
console.error('加载上一镜头尾帧失败:', error)
previousStoryboardLastFrames.value = []
}
}
// 选择上一镜头尾帧作为首帧参考
const selectPreviousLastFrame = (img: any) => {
// 检查是否已选中,已选中则取消
const currentIndex = selectedImagesForVideo.value.indexOf(img.id)
if (currentIndex > -1) {
selectedImagesForVideo.value.splice(currentIndex, 1)
ElMessage.success('已取消首帧参考')
return
}
// 参考handleImageSelect的逻辑根据模式处理
if (!selectedReferenceMode.value || selectedReferenceMode.value === 'single') {
// 单图模式或未选模式:直接替换
selectedImagesForVideo.value = [img.id]
} else if (selectedReferenceMode.value === 'first_last') {
// 首尾帧模式:作为首帧参考
selectedImagesForVideo.value = [img.id]
} else if (selectedReferenceMode.value === 'multiple') {
// 多图模式:添加到列表
const capability = currentModelCapability.value
if (capability && selectedImagesForVideo.value.length >= capability.maxImages) {
ElMessage.warning(`最多只能选择${capability.maxImages}张图片`)
return
}
selectedImagesForVideo.value.push(img.id)
}
ElMessage.success('已添加为首帧参考')
}
// 监听帧类型切换,从存储中加载或清空 // 监听帧类型切换,从存储中加载或清空
watch(selectedFrameType, (newType) => { watch(selectedFrameType, (newType) => {
// 切换帧类型时,停止之前的轮询,避免旧结果覆盖新帧类型 // 切换帧类型时,停止之前的轮询,避免旧结果覆盖新帧类型
@@ -1269,6 +1356,7 @@ watch(currentStoryboard, async (newStoryboard) => {
generatedImages.value = [] generatedImages.value = []
generatedVideos.value = [] generatedVideos.value = []
videoReferenceImages.value = [] videoReferenceImages.value = []
previousStoryboardLastFrames.value = []
return return
} }
@@ -1297,6 +1385,9 @@ watch(currentStoryboard, async (newStoryboard) => {
// 加载该分镜的视频列表 // 加载该分镜的视频列表
await loadStoryboardVideos(newStoryboard.id) await loadStoryboardVideos(newStoryboard.id)
// 加载上一镜头的尾帧
await loadPreviousStoryboardLastFrame()
}) })
// 监听提示词变化自动保存到sessionStorage // 监听提示词变化自动保存到sessionStorage
@@ -1801,13 +1892,17 @@ const selectedImageObjects = computed(() => {
const firstFrameSlotImage = computed(() => { const firstFrameSlotImage = computed(() => {
if (selectedImagesForVideo.value.length === 0) return null if (selectedImagesForVideo.value.length === 0) return null
const firstImageId = selectedImagesForVideo.value[0] const firstImageId = selectedImagesForVideo.value[0]
// 同时搜索当前镜头图片和上一镜头尾帧
return videoReferenceImages.value.find(img => img.id === firstImageId) return videoReferenceImages.value.find(img => img.id === firstImageId)
|| previousStoryboardLastFrames.value.find(img => img.id === firstImageId)
}) })
// 首尾帧模式:获取尾帧图片 // 首尾帧模式:获取尾帧图片
const lastFrameSlotImage = computed(() => { const lastFrameSlotImage = computed(() => {
if (!selectedLastImageForVideo.value) return null if (!selectedLastImageForVideo.value) return null
// 同时搜索当前镜头图片和上一镜头尾帧
return videoReferenceImages.value.find(img => img.id === selectedLastImageForVideo.value) return videoReferenceImages.value.find(img => img.id === selectedLastImageForVideo.value)
|| previousStoryboardLastFrames.value.find(img => img.id === selectedLastImageForVideo.value)
}) })
// 移除已选择的图片 // 移除已选择的图片
@@ -1846,7 +1941,9 @@ const generateVideo = async () => {
// 获取第一张选中的图片(仅在需要图片的模式下) // 获取第一张选中的图片(仅在需要图片的模式下)
let selectedImage = null let selectedImage = null
if (selectedReferenceMode.value !== 'none' && selectedImagesForVideo.value.length > 0) { if (selectedReferenceMode.value !== 'none' && selectedImagesForVideo.value.length > 0) {
// 同时搜索当前镜头图片和上一镜头尾帧
selectedImage = videoReferenceImages.value.find(img => img.id === selectedImagesForVideo.value[0]) selectedImage = videoReferenceImages.value.find(img => img.id === selectedImagesForVideo.value[0])
|| previousStoryboardLastFrames.value.find(img => img.id === selectedImagesForVideo.value[0])
if (!selectedImage || !selectedImage.image_url) { if (!selectedImage || !selectedImage.image_url) {
ElMessage.error('请选择有效的参考图片') ElMessage.error('请选择有效的参考图片')
return return
@@ -1878,9 +1975,11 @@ const generateVideo = async () => {
break break
case 'first_last': case 'first_last':
// 首尾帧模式 // 首尾帧模式(同时搜索当前镜头图片和上一镜头尾帧)
const firstImage = videoReferenceImages.value.find(img => img.id === selectedImagesForVideo.value[0]) const firstImage = videoReferenceImages.value.find(img => img.id === selectedImagesForVideo.value[0])
|| previousStoryboardLastFrames.value.find(img => img.id === selectedImagesForVideo.value[0])
const lastImage = videoReferenceImages.value.find(img => img.id === selectedLastImageForVideo.value) const lastImage = videoReferenceImages.value.find(img => img.id === selectedLastImageForVideo.value)
|| previousStoryboardLastFrames.value.find(img => img.id === selectedLastImageForVideo.value)
if (firstImage?.image_url) { if (firstImage?.image_url) {
requestParams.first_frame_url = firstImage.image_url requestParams.first_frame_url = firstImage.image_url
@@ -3947,6 +4046,19 @@ onBeforeUnmount(() => {
} }
} }
.previous-frame-section {
margin-bottom: 12px;
padding: 8px;
background: var(--bg-secondary);
border: 1px solid var(--border-primary);
border-radius: 6px;
.hint-text {
color: var(--text-muted);
font-size: 11px;
}
}
.reference-grid { .reference-grid {
display: grid !important; display: grid !important;
grid-template-columns: repeat(4, 1fr) !important; grid-template-columns: repeat(4, 1fr) !important;