857 lines
27 KiB
Markdown
857 lines
27 KiB
Markdown
# AI 短视频创作平台 — 工程骨架方案
|
||||
|
|
|
|||
|
|
> 本方案由 WorkBuddy 产出,交付给 Trae 执行。
|
|||
|
|
> 定位:toC 产品,Python 后端 + React 前端,独立部署。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、技术栈总览
|
|||
|
|
|
|||
|
|
| 层 | 技术 | 版本 | 角色 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| **前端框架** | React | 19.x | UI 层 |
|
|||
|
|
| **构建工具** | Vite | 6.x | 开发/打包 |
|
|||
|
|
| **语言** | TypeScript | 5.x | 前端类型安全 |
|
|||
|
|
| **UI 组件** | shadcn/ui | latest | 原子组件库 |
|
|||
|
|
| **样式** | Tailwind CSS | 4.x | 原子化样式 |
|
|||
|
|
| **拖拽** | @dnd-kit | latest | 时间轴/素材拖拽 |
|
|||
|
|
| **画布** | react-konva | latest | 预览画布/文字贴纸 |
|
|||
|
|
| **状态管理** | zustand | 5.x | 编辑器状态 + 撤销重做 |
|
|||
|
|
| **数据请求** | @tanstack/react-query | 5.x | 服务端状态/缓存 |
|
|||
|
|
| **动画** | framer-motion | 11.x | 过渡动效 |
|
|||
|
|
| **路由** | react-router | 7.x | 页面路由 |
|
|||
|
|
| **表单** | react-hook-form + zod | latest | 表单校验 |
|
|||
|
|
| **后端框架** | FastAPI | 0.115+ | REST API + WebSocket |
|
|||
|
|
| **ASGI 服务器** | uvicorn | latest | 生产级运行 |
|
|||
|
|
| **数据库** | PostgreSQL + Redis | 16 / 7 | 持久化 + 缓存/队列 |
|
|||
|
|
| **ORM** | SQLAlchemy 2.0 | 2.x | 数据库映射 |
|
|||
|
|
| **任务队列** | Celery + Redis | 5.x | AI 异步生成 |
|
|||
|
|
| **文件存储** | MinIO / S3 | — | 视频/素材存储 |
|
|||
|
|
| **AI 集成** | OpenAI SDK / 本地模型 | — | 脚本生成/视频处理 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ai-video-platform/
|
|||
|
|
├── frontend/ # React 前端
|
|||
|
|
│ ├── index.html
|
|||
|
|
│ ├── package.json
|
|||
|
|
│ ├── tsconfig.json
|
|||
|
|
│ ├── vite.config.ts
|
|||
|
|
│ ├── tailwind.config.ts
|
|||
|
|
│ ├── components.json # shadcn/ui 配置
|
|||
|
|
│ ├── public/
|
|||
|
|
│ │ └── favicon.svg
|
|||
|
|
│ └── src/
|
|||
|
|
│ ├── main.tsx # 入口
|
|||
|
|
│ ├── App.tsx # 根组件 + 路由
|
|||
|
|
│ ├── index.css # Tailwind 入口
|
|||
|
|
│ ├── lib/
|
|||
|
|
│ │ └── utils.ts # cn() 工具函数
|
|||
|
|
│ ├── components/
|
|||
|
|
│ │ ├── ui/ # shadcn/ui 组件(自动生成)
|
|||
|
|
│ │ │ ├── button.tsx
|
|||
|
|
│ │ │ ├── card.tsx
|
|||
|
|
│ │ │ ├── dialog.tsx
|
|||
|
|
│ │ │ ├── slider.tsx
|
|||
|
|
│ │ │ ├── tabs.tsx
|
|||
|
|
│ │ │ ├── dropdown-menu.tsx
|
|||
|
|
│ │ │ ├─�� input.tsx
|
|||
|
|
│ │ │ ├── textarea.tsx
|
|||
|
|
│ │ │ ├── select.tsx
|
|||
|
|
│ │ │ ├── toast.tsx
|
|||
|
|
│ │ │ ├── tooltip.tsx
|
|||
|
|
│ │ │ ├── sheet.tsx # 侧边栏
|
|||
|
|
│ │ │ └── skeleton.tsx # 加载骨架
|
|||
|
|
│ │ ├── layout/
|
|||
|
|
│ │ │ ├── AppShell.tsx # 整体布局壳
|
|||
|
|
│ │ │ ├── Sidebar.tsx # 左侧导航
|
|||
|
|
│ │ │ └── Header.tsx # 顶部栏
|
|||
|
|
│ │ ├── editor/ # 🔥 视频编辑器核心
|
|||
|
|
│ │ │ ├── Timeline/
|
|||
|
|
│ │ │ │ ├── Timeline.tsx # 时间轴容器
|
|||
|
|
│ │ │ │ ├── Track.tsx # 单条轨道
|
|||
|
|
│ �� │ │ ├── TrackItem.tsx # 轨道上的片段
|
|||
|
|
│ │ │ │ ├── TimelineRuler.tsx # 时间标尺
|
|||
|
|
│ │ │ │ └── TimelineControls.tsx # 缩放/播放控制
|
|||
|
|
│ │ │ ├── Canvas/
|
|||
|
|
│ │ │ │ ├── PreviewCanvas.tsx # 预览画布(react-konva)
|
|||
|
|
│ │ │ │ ├── TextOverlay.tsx # 文字叠层
|
|||
|
|
│ │ │ │ ├── StickerLayer.tsx # 贴纸层
|
|||
|
|
│ │ │ │ └── VideoLayer.tsx # 视频画面层
|
|||
|
|
│ │ │ ├── Toolbar/
|
|||
|
|
│ │ │ │ ├── EditorToolbar.tsx # 编辑工具栏
|
|||
|
|
│ │ │ │ ├── CropTool.tsx # 裁剪工具
|
|||
|
|
│ │ │ │ └── TextTool.tsx # 文字工具
|
|||
|
|
│ │ │ └── AssetPanel/
|
|||
|
|
│ │ │ ├── AssetLibrary.tsx # 素材库面板
|
|||
|
|
│ │ │ ├── MediaUploader.tsx # 上传组件
|
|||
|
|
│ │ │ └── AIGenerator.tsx # AI 生成入口
|
|||
|
|
│ │ ├── ai/ # AI 功能面板
|
|||
|
|
│ │ │ ├── ScriptGenerator.tsx # 脚本生成
|
|||
|
|
│ │ │ ├── VoiceGenerator.tsx # AI 配音
|
|||
|
|
│ │ │ ├── SubtitleGenerator.tsx # 字幕生成
|
|||
|
|
│ │ │ └── GenerationProgress.tsx # 生成进度(WebSocket)
|
|||
|
|
│ │ └── common/
|
|||
|
|
│ │ ├── LoadingSpinner.tsx
|
|||
|
|
│ │ ├── ErrorBoundary.tsx
|
|||
|
|
│ │ ├── EmptyState.tsx
|
|||
|
|
│ │ └── ConfirmDialog.tsx
|
|||
|
|
│ ├── pages/
|
|||
|
|
│ │ ├── Home.tsx # 首页/项目列表
|
|||
|
|
│ │ ├── Editor.tsx # 🔥 编辑器主页面
|
|||
|
|
│ │ ├── Projects.tsx # 我的项目
|
|||
|
|
│ │ ├── Templates.tsx # 模板市场
|
|||
|
|
│ │ ├── Assets.tsx # 素材管理
|
|||
|
|
│ │ └── Settings.tsx # 用户设置
|
|||
|
|
│ ├── stores/ # zustand 状态
|
|||
|
|
│ │ ├── editorStore.ts # 🔥 编辑器核心状态 + 撤销重做
|
|||
|
|
│ │ ├── projectStore.ts # 项目元信息
|
|||
|
|
│ │ ├── uiStore.ts # UI 状态(面板显隐等)
|
|||
|
|
│ │ └── authStore.ts # 用户认证
|
|||
|
|
│ ├── hooks/ # 自定义 hooks
|
|||
|
|
│ │ ├── useEditor.ts # 编辑器逻辑入口
|
|||
|
|
│ │ ├── useUndoRedo.ts # 撤销重做封装
|
|||
|
|
│ │ ├── useWebSocket.ts # WebSocket 连接
|
|||
|
|
│ │ ├── useMediaUpload.ts # 上传逻辑
|
|||
|
|
│ �� └── useAIGeneration.ts # AI 生成调用
|
|||
|
|
│ ├── api/ # API 层
|
|||
|
|
│ │ ├── client.ts # axios/fetch 实例
|
|||
|
|
│ │ ├── projects.ts # 项目 CRUD
|
|||
|
|
│ │ ├── media.ts # 素材上传/管理
|
|||
|
|
│ │ ├── generation.ts # AI 生成接口
|
|||
|
|
│ │ └── auth.ts # 认证接口
|
|||
|
|
│ ├── types/ # TypeScript 类型
|
|||
|
|
│ │ ├── project.ts
|
|||
|
|
│ │ ├── timeline.ts # 时间轴轨道类型
|
|||
|
|
│ │ ├── media.ts
|
|||
|
|
│ │ └── api.ts # API 响应类型
|
|||
|
|
│ └── utils/
|
|||
|
|
│ ├── time.ts # 时间格式化
|
|||
|
|
│ └── file.ts # 文件工具
|
|||
|
|
│
|
|||
|
|
├── backend/ # Python 后端
|
|||
|
|
│ ├── pyproject.toml # 项目管理(uv/poetry)
|
|||
|
|
│ ├── alembic.ini # 数据库迁移配置
|
|||
|
|
│ ├── Dockerfile
|
|||
|
|
│ ├── docker-compose.yml # 本地开发环境
|
|||
|
|
│ ├── app/
|
|||
|
|
│ │ ├── __init__.py
|
|||
|
|
│ │ ├── main.py # FastAPI 入口
|
|||
|
|
│ │ ├── config.py # 配置管理(pydantic-settings)
|
|||
|
|
│ │ ├── dependencies.py # 依赖注入
|
|||
|
|
│ │ ├── api/
|
|||
|
|
│ │ │ ├── __init__.py
|
|||
|
|
│ │ │ ├── v1/
|
|||
|
|
│ │ │ │ ├── __init__.py
|
|||
|
|
│ │ │ │ ├── router.py # 路由聚合
|
|||
|
|
│ │ │ │ ├── auth.py # 认证
|
|||
|
|
│ │ │ │ ├── projects.py # 项目 CRUD
|
|||
|
|
│ │ │ │ ├── media.py # 素材上传
|
|||
|
|
│ │ │ │ ├── generation.py # AI 生成
|
|||
|
|
│ │ │ │ ├─�� templates.py # 模板
|
|||
|
|
│ │ │ │ └── websocket.py # WebSocket 进度推送
|
|||
|
|
│ │ ├── models/
|
|||
|
|
│ │ │ ├── __init__.py
|
|||
|
|
│ │ │ ├── user.py
|
|||
|
|
│ │ │ ├── project.py
|
|||
|
|
│ │ │ ├── media.py
|
|||
|
|
│ │ │ └── template.py
|
|||
|
|
│ │ ├── schemas/
|
|||
|
|
│ │ │ ├── __init__.py
|
|||
|
|
│ │ │ ├── user.py
|
|||
|
|
│ │ │ ├── project.py
|
|||
|
|
│ │ │ ├── media.py
|
|||
|
|
│ │ │ └── generation.py
|
|||
|
|
│ │ ├── services/
|
|||
|
|
│ │ │ ├── __init__.py
|
|||
|
|
│ │ │ ├── project_service.py
|
|||
|
|
│ │ │ ├── media_service.py
|
|||
|
|
│ │ │ ├── script_generator.py # 🔥 AI 脚本生成
|
|||
|
|
│ │ │ ├── voice_service.py # 🔥 AI 配音
|
|||
|
|
│ │ │ ├── subtitle_service.py # 🔥 AI 字幕
|
|||
|
|
│ │ │ ├── video_renderer.py # 🔥 视频合成
|
|||
|
|
│ │ │ └── storage_service.py # 文件存储(MinIO/S3)
|
|||
|
|
│ │ ├── tasks/ # Celery 异步任务
|
|||
|
|
│ │ │ ├── __init__.py
|
|||
|
|
│ │ │ ├── celery_app.py
|
|||
|
|
│ │ │ ├── generation_tasks.py # AI 生成任务
|
|||
|
|
│ │ │ └── media_tasks.py # 媒体处理任务
|
|||
|
|
│ │ ├── core/
|
|||
|
|
│ │ │ ├── __init__.py
|
|||
|
|
│ │ │ ├── security.py # JWT / OAuth
|
|||
|
|
│ │ │ ├── database.py # SQLAlchemy 引擎
|
|||
|
|
│ │ │ └── exceptions.py
|
|||
|
|
│ │ └── utils/
|
|||
|
|
│ │ ├── __init__.py
|
|||
|
|
│ │ └── helpers.py
|
|||
|
|
│ ├── alembic/
|
|||
|
|
│ │ ├── env.py
|
|||
|
|
│ │ └── versions/
|
|||
|
|
│ ├── tests/
|
|||
|
|
│ │ ├── conftest.py
|
|||
|
|
│ │ ├── test_projects.py
|
|||
|
|
│ │ └── test_generation.py
|
|||
|
|
│ └── requirements/
|
|||
|
|
│ ├── base.txt
|
|||
|
|
│ ├── dev.txt
|
|||
|
|
│ └── prod.txt
|
|||
|
|
│
|
|||
|
|
└── docs/ # 文档
|
|||
|
|
├── api-spec.md # API 接口说明
|
|||
|
|
└── editor-architecture.md # 编辑器架构设计
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、前端核心依赖
|
|||
|
|
|
|||
|
|
```jsonc
|
|||
|
|
// frontend/package.json (关键字段)
|
|||
|
|
{
|
|||
|
|
"name": "ai-video-platform",
|
|||
|
|
"private": true,
|
|||
|
|
"type": "module",
|
|||
|
|
"scripts": {
|
|||
|
|
"dev": "vite",
|
|||
|
|
"build": "tsc -b && vite build",
|
|||
|
|
"preview": "vite preview",
|
|||
|
|
"lint": "eslint ."
|
|||
|
|
},
|
|||
|
|
"dependencies": {
|
|||
|
|
"react": "^19.0.0",
|
|||
|
|
"react-dom": "^19.0.0",
|
|||
|
|
"react-router": "^7.0.0",
|
|||
|
|
"@tanstack/react-query": "^5.0.0",
|
|||
|
|
"zustand": "^5.0.0",
|
|||
|
|
"@dnd-kit/core": "^6.1.0",
|
|||
|
|
"@dnd-kit/sortable": "^8.0.0",
|
|||
|
|
"@dnd-kit/utilities": "^3.2.0",
|
|||
|
|
"react-konva": "^18.2.0",
|
|||
|
|
"konva": "^9.3.0",
|
|||
|
|
"framer-motion": "^11.0.0",
|
|||
|
|
"react-hook-form": "^7.53.0",
|
|||
|
|
"@hookform/resolvers": "^3.9.0",
|
|||
|
|
"zod": "^3.23.0",
|
|||
|
|
"axios": "^1.7.0",
|
|||
|
|
"clsx": "^2.1.0",
|
|||
|
|
"tailwind-merge": "^2.5.0",
|
|||
|
|
"class-variance-authority": "^0.7.0",
|
|||
|
|
"lucide-react": "^0.460.0",
|
|||
|
|
"sonner": "^1.7.0"
|
|||
|
|
},
|
|||
|
|
"devDependencies": {
|
|||
|
|
"@types/react": "^19.0.0",
|
|||
|
|
"@types/react-dom": "^19.0.0",
|
|||
|
|
"@vitejs/plugin-react": "^4.3.0",
|
|||
|
|
"vite": "^6.0.0",
|
|||
|
|
"typescript": "^5.6.0",
|
|||
|
|
"tailwindcss": "^4.0.0",
|
|||
|
|
"@tailwindcss/vite": "^4.0.0",
|
|||
|
|
"eslint": "^9.0.0"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、后端核心依赖
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
# backend/requirements/base.txt
|
|||
|
|
|
|||
|
|
# Web 框架
|
|||
|
|
fastapi>=0.115,<1.0
|
|||
|
|
uvicorn[standard]>=0.32,<1.0
|
|||
|
|
python-multipart>=0.0.18 # 文件上传
|
|||
|
|
|
|||
|
|
# 数据库
|
|||
|
|
sqlalchemy>=2.0,<3.0
|
|||
|
|
asyncpg>=0.30,<1.0 # PostgreSQL 异步驱动
|
|||
|
|
alembic>=1.14,<2.0 # 数据库迁移
|
|||
|
|
pydantic>=2.10,<3.0
|
|||
|
|
pydantic-settings>=2.6,<3.0
|
|||
|
|
|
|||
|
|
# 缓存 & 队列
|
|||
|
|
redis>=5.2,<6.0
|
|||
|
|
celery>=5.4,<6.0
|
|||
|
|
|
|||
|
|
# 认证
|
|||
|
|
python-jose[cryptography]>=3.3,<4.0
|
|||
|
|
passlib[bcrypt]>=1.7,<2.0
|
|||
|
|
|
|||
|
|
# 文件存储
|
|||
|
|
boto3>=1.35,<2.0 # S3/MinIO
|
|||
|
|
minio>=7.2,<8.0
|
|||
|
|
|
|||
|
|
# AI SDK
|
|||
|
|
openai>=1.55,<2.0
|
|||
|
|
|
|||
|
|
# 工具
|
|||
|
|
httpx>=0.28,<1.0
|
|||
|
|
loguru>=0.7,<1.0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、初始化步骤(逐条命令)
|
|||
|
|
|
|||
|
|
### 5.1 创建项目目录
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir ai-video-platform && cd ai-video-platform
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 前端初始化
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1. Vite + React + TypeScript
|
|||
|
|
npm create vite@latest frontend -- --template react-ts
|
|||
|
|
cd frontend
|
|||
|
|
npm install
|
|||
|
|
|
|||
|
|
# 2. 安装运行时依赖
|
|||
|
|
npm install react-router @tanstack/react-query zustand @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities react-konva konva framer-motion react-hook-form @hookform/resolvers zod axios clsx tailwind-merge class-variance-authority lucide-react sonner
|
|||
|
|
|
|||
|
|
# 3. 安装开发依赖
|
|||
|
|
npm install -D tailwindcss @tailwindcss/vite
|
|||
|
|
|
|||
|
|
# 4. 初始化 shadcn/ui
|
|||
|
|
npx shadcn@latest init
|
|||
|
|
# 选择:TypeScript / Neutral / CSS variables / Yes to all
|
|||
|
|
|
|||
|
|
# 5. 添加 shadcn 组件
|
|||
|
|
npx shadcn@latest add button card dialog slider tabs dropdown-menu input textarea select toast tooltip sheet skeleton
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 Tailwind CSS v4 配置
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// frontend/vite.config.ts
|
|||
|
|
import { defineConfig } from 'vite'
|
|||
|
|
import react from '@vitejs/plugin-react'
|
|||
|
|
import tailwindcss from '@tailwindcss/vite'
|
|||
|
|
import path from 'path'
|
|||
|
|
|
|||
|
|
export default defineConfig({
|
|||
|
|
plugins: [react(), tailwindcss()],
|
|||
|
|
resolve: {
|
|||
|
|
alias: {
|
|||
|
|
'@': path.resolve(__dirname, './src'),
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
server: {
|
|||
|
|
port: 3000,
|
|||
|
|
proxy: {
|
|||
|
|
'/api': {
|
|||
|
|
target: 'http://localhost:8000',
|
|||
|
|
changeOrigin: true,
|
|||
|
|
},
|
|||
|
|
'/ws': {
|
|||
|
|
target: 'ws://localhost:8000',
|
|||
|
|
ws: true,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* frontend/src/index.css */
|
|||
|
|
@import "tailwindcss";
|
|||
|
|
@import "tw-animate-css";
|
|||
|
|
|
|||
|
|
@custom-variant dark (&:is(.dark *));
|
|||
|
|
|
|||
|
|
:root {
|
|||
|
|
--radius: 0.625rem;
|
|||
|
|
--background: oklch(1 0 0);
|
|||
|
|
--foreground: oklch(0.145 0 0);
|
|||
|
|
--card: oklch(1 0 0);
|
|||
|
|
--card-foreground: oklch(0.145 0 0);
|
|||
|
|
--popover: oklch(1 0 0);
|
|||
|
|
--popover-foreground: oklch(0.145 0 0);
|
|||
|
|
--primary: oklch(0.205 0 0);
|
|||
|
|
--primary-foreground: oklch(0.985 0 0);
|
|||
|
|
--secondary: oklch(0.97 0 0);
|
|||
|
|
--secondary-foreground: oklch(0.205 0 0);
|
|||
|
|
--muted: oklch(0.97 0 0);
|
|||
|
|
--muted-foreground: oklch(0.556 0 0);
|
|||
|
|
--accent: oklch(0.97 0 0);
|
|||
|
|
--accent-foreground: oklch(0.205 0 0);
|
|||
|
|
--destructive: oklch(0.577 0.245 27.325);
|
|||
|
|
--border: oklch(0.922 0 0);
|
|||
|
|
--input: oklch(0.922 0 0);
|
|||
|
|
--ring: oklch(0.708 0 0);
|
|||
|
|
--chart-1: oklch(0.646 0.222 41.116);
|
|||
|
|
--chart-2: oklch(0.6 0.118 184.704);
|
|||
|
|
--chart-3: oklch(0.398 0.07 227.392);
|
|||
|
|
--chart-4: oklch(0.828 0.189 84.429);
|
|||
|
|
--chart-5: oklch(0.769 0.188 70.08);
|
|||
|
|
--sidebar: oklch(0.985 0 0);
|
|||
|
|
--sidebar-foreground: oklch(0.145 0 0);
|
|||
|
|
--sidebar-primary: oklch(0.205 0 0);
|
|||
|
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|||
|
|
--sidebar-accent: oklch(0.97 0 0);
|
|||
|
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|||
|
|
--sidebar-border: oklch(0.922 0 0);
|
|||
|
|
--sidebar-ring: oklch(0.708 0 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dark {
|
|||
|
|
--background: oklch(0.145 0 0);
|
|||
|
|
--foreground: oklch(0.985 0 0);
|
|||
|
|
/* ... 暗色主题变量 ... */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@theme inline {
|
|||
|
|
--radius-sm: calc(var(--radius) - 4px);
|
|||
|
|
--radius-md: calc(var(--radius) - 2px);
|
|||
|
|
--radius-lg: var(--radius);
|
|||
|
|
--radius-xl: calc(var(--radius) + 4px);
|
|||
|
|
--color-background: var(--background);
|
|||
|
|
--color-foreground: var(--foreground);
|
|||
|
|
--color-card: var(--card);
|
|||
|
|
--color-card-foreground: var(--card-foreground);
|
|||
|
|
--color-popover: var(--popover);
|
|||
|
|
--color-popover-foreground: var(--popover-foreground);
|
|||
|
|
--color-primary: var(--primary);
|
|||
|
|
--color-primary-foreground: var(--primary-foreground);
|
|||
|
|
--color-secondary: var(--secondary);
|
|||
|
|
--color-secondary-foreground: var(--secondary-foreground);
|
|||
|
|
--color-muted: var(--muted);
|
|||
|
|
--color-muted-foreground: var(--muted-foreground);
|
|||
|
|
--color-accent: var(--accent);
|
|||
|
|
--color-accent-foreground: var(--accent-foreground);
|
|||
|
|
--color-destructive: var(--destructive);
|
|||
|
|
--color-border: var(--border);
|
|||
|
|
--color-input: var(--input);
|
|||
|
|
--color-ring: var(--ring);
|
|||
|
|
--color-chart-1: var(--chart-1);
|
|||
|
|
--color-chart-2: var(--chart-2);
|
|||
|
|
--color-chart-3: var(--chart-3);
|
|||
|
|
--color-chart-4: var(--chart-4);
|
|||
|
|
--color-chart-5: var(--chart-5);
|
|||
|
|
--color-sidebar: var(--sidebar);
|
|||
|
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
|||
|
|
--color-sidebar-primary: var(--sidebar-primary);
|
|||
|
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|||
|
|
--color-sidebar-accent: var(--sidebar-accent);
|
|||
|
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|||
|
|
--color-sidebar-border: var(--sidebar-border);
|
|||
|
|
--color-sidebar-ring: var(--sidebar-ring);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@layer base {
|
|||
|
|
* {
|
|||
|
|
@apply border-border;
|
|||
|
|
}
|
|||
|
|
body {
|
|||
|
|
@apply bg-background text-foreground;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.4 后端初始化
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd backend
|
|||
|
|
|
|||
|
|
# 1. 创建虚拟环境
|
|||
|
|
python -m venv .venv
|
|||
|
|
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|||
|
|
|
|||
|
|
# 2. 安装依赖
|
|||
|
|
pip install -r requirements/base.txt
|
|||
|
|
|
|||
|
|
# 3. 初始化 Alembic
|
|||
|
|
alembic init alembic
|
|||
|
|
# 修改 alembic/env.py 指向你的数据库
|
|||
|
|
|
|||
|
|
# 4. 创建初始迁移
|
|||
|
|
alembic revision --autogenerate -m "init"
|
|||
|
|
alembic upgrade head
|
|||
|
|
|
|||
|
|
# 5. 启动开发服务器
|
|||
|
|
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.5 本地基础设施
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# docker-compose.yml — 一键启动 PostgreSQL + Redis + MinIO
|
|||
|
|
docker compose up -d
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# backend/docker-compose.yml
|
|||
|
|
services:
|
|||
|
|
postgres:
|
|||
|
|
image: postgres:16-alpine
|
|||
|
|
environment:
|
|||
|
|
POSTGRES_USER: video_dev
|
|||
|
|
POSTGRES_PASSWORD: video_dev
|
|||
|
|
POSTGRES_DB: ai_video_platform
|
|||
|
|
ports:
|
|||
|
|
- "5432:5432"
|
|||
|
|
volumes:
|
|||
|
|
- pgdata:/var/lib/postgresql/data
|
|||
|
|
|
|||
|
|
redis:
|
|||
|
|
image: redis:7-alpine
|
|||
|
|
ports:
|
|||
|
|
- "6379:6379"
|
|||
|
|
|
|||
|
|
minio:
|
|||
|
|
image: minio/minio:latest
|
|||
|
|
command: server /data --console-address ":9001"
|
|||
|
|
environment:
|
|||
|
|
MINIO_ROOT_USER: minioadmin
|
|||
|
|
MINIO_ROOT_PASSWORD: minioadmin
|
|||
|
|
ports:
|
|||
|
|
- "9000:9000"
|
|||
|
|
- "9001:9001"
|
|||
|
|
volumes:
|
|||
|
|
- miniodata:/data
|
|||
|
|
|
|||
|
|
volumes:
|
|||
|
|
pgdata:
|
|||
|
|
miniodata:
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、编辑器核心状态设计(zustand store)
|
|||
|
|
|
|||
|
|
这是整个前端的灵魂。交给 Trae 后,它是编码优先级最高的模块。
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// frontend/src/stores/editorStore.ts
|
|||
|
|
import { create } from 'zustand';
|
|||
|
|
import { temporal } from 'zundo'; // zustand 撤销重做中间件
|
|||
|
|
|
|||
|
|
interface TrackItem {
|
|||
|
|
id: string;
|
|||
|
|
type: 'video' | 'image' | 'audio' | 'text' | 'sticker';
|
|||
|
|
trackId: string;
|
|||
|
|
startTime: number; // 毫秒
|
|||
|
|
duration: number; // 毫秒
|
|||
|
|
properties: {
|
|||
|
|
x?: number;
|
|||
|
|
y?: number;
|
|||
|
|
width?: number;
|
|||
|
|
height?: number;
|
|||
|
|
rotation?: number;
|
|||
|
|
opacity?: number;
|
|||
|
|
text?: string;
|
|||
|
|
fontSize?: number;
|
|||
|
|
fontColor?: string;
|
|||
|
|
// ... 其他属性
|
|||
|
|
};
|
|||
|
|
source: {
|
|||
|
|
url?: string; // 媒体文件 URL
|
|||
|
|
fileId?: string; // 后端文件 ID
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface EditorState {
|
|||
|
|
// 项目元信息
|
|||
|
|
projectId: string | null;
|
|||
|
|
projectName: string;
|
|||
|
|
|
|||
|
|
// 轨道数据
|
|||
|
|
tracks: TrackItem[];
|
|||
|
|
|
|||
|
|
// 播放状态
|
|||
|
|
currentTime: number;
|
|||
|
|
isPlaying: boolean;
|
|||
|
|
duration: number;
|
|||
|
|
|
|||
|
|
// 选中状态
|
|||
|
|
selectedItemIds: string[];
|
|||
|
|
|
|||
|
|
// 缩放
|
|||
|
|
zoom: number; // 0.25 ~ 4.0
|
|||
|
|
|
|||
|
|
// 操作
|
|||
|
|
addTrackItem: (item: TrackItem) => void;
|
|||
|
|
removeTrackItem: (id: string) => void;
|
|||
|
|
updateTrackItem: (id: string, updates: Partial<TrackItem>) => void;
|
|||
|
|
moveTrackItem: (id: string, newStartTime: number, newTrackId?: string) => void;
|
|||
|
|
setCurrentTime: (time: number) => void;
|
|||
|
|
togglePlay: () => void;
|
|||
|
|
selectItems: (ids: string[]) => void;
|
|||
|
|
setZoom: (zoom: number) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const useEditorStore = create<EditorState>()(
|
|||
|
|
temporal(
|
|||
|
|
(set) => ({
|
|||
|
|
projectId: null,
|
|||
|
|
projectName: '未命名项目',
|
|||
|
|
tracks: [],
|
|||
|
|
currentTime: 0,
|
|||
|
|
isPlaying: false,
|
|||
|
|
duration: 60000,
|
|||
|
|
selectedItemIds: [],
|
|||
|
|
zoom: 1,
|
|||
|
|
|
|||
|
|
addTrackItem: (item) =>
|
|||
|
|
set((s) => ({ tracks: [...s.tracks, item] })),
|
|||
|
|
|
|||
|
|
removeTrackItem: (id) =>
|
|||
|
|
set((s) => ({
|
|||
|
|
tracks: s.tracks.filter((t) => t.id !== id),
|
|||
|
|
selectedItemIds: s.selectedItemIds.filter((sid) => sid !== id),
|
|||
|
|
})),
|
|||
|
|
|
|||
|
|
updateTrackItem: (id, updates) =>
|
|||
|
|
set((s) => ({
|
|||
|
|
tracks: s.tracks.map((t) =>
|
|||
|
|
t.id === id ? { ...t, ...updates } : t
|
|||
|
|
),
|
|||
|
|
})),
|
|||
|
|
|
|||
|
|
moveTrackItem: (id, newStartTime, newTrackId) =>
|
|||
|
|
set((s) => ({
|
|||
|
|
tracks: s.tracks.map((t) =>
|
|||
|
|
t.id === id
|
|||
|
|
? { ...t, startTime: newStartTime, trackId: newTrackId ?? t.trackId }
|
|||
|
|
: t
|
|||
|
|
),
|
|||
|
|
})),
|
|||
|
|
|
|||
|
|
setCurrentTime: (time) => set({ currentTime: time }),
|
|||
|
|
togglePlay: () => set((s) => ({ isPlaying: !s.isPlaying })),
|
|||
|
|
selectItems: (ids) => set({ selectedItemIds: ids }),
|
|||
|
|
setZoom: (zoom) => set({ zoom }),
|
|||
|
|
}),
|
|||
|
|
{ limit: 50 } // 保留 50 步撤销历史
|
|||
|
|
)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、后端 FastAPI 入口骨架
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# backend/app/main.py
|
|||
|
|
from contextlib import asynccontextmanager
|
|||
|
|
from fastapi import FastAPI
|
|||
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|||
|
|
|
|||
|
|
from app.api.v1.router import api_router
|
|||
|
|
from app.core.database import engine, Base
|
|||
|
|
from app.config import settings
|
|||
|
|
|
|||
|
|
|
|||
|
|
@asynccontextmanager
|
|||
|
|
async def lifespan(app: FastAPI):
|
|||
|
|
# 启动时:创建表(开发环境)
|
|||
|
|
# async with engine.begin() as conn:
|
|||
|
|
# await conn.run_sync(Base.metadata.create_all)
|
|||
|
|
yield
|
|||
|
|
# 关闭时清理
|
|||
|
|
await engine.dispose()
|
|||
|
|
|
|||
|
|
|
|||
|
|
app = FastAPI(
|
|||
|
|
title="AI 短视频创作平台",
|
|||
|
|
version="0.1.0",
|
|||
|
|
lifespan=lifespan,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# CORS
|
|||
|
|
app.add_middleware(
|
|||
|
|
CORSMiddleware,
|
|||
|
|
allow_origins=settings.CORS_ORIGINS,
|
|||
|
|
allow_credentials=True,
|
|||
|
|
allow_methods=["*"],
|
|||
|
|
allow_headers=["*"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 注册路由
|
|||
|
|
app.include_router(api_router, prefix="/api/v1")
|
|||
|
|
|
|||
|
|
@app.get("/health")
|
|||
|
|
async def health_check():
|
|||
|
|
return {"status": "ok"}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### WebSocket 进度推送
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# backend/app/api/v1/websocket.py
|
|||
|
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
|||
|
|
|
|||
|
|
router = APIRouter()
|
|||
|
|
|
|||
|
|
class ConnectionManager:
|
|||
|
|
def __init__(self):
|
|||
|
|
self.active_connections: dict[str, WebSocket] = {}
|
|||
|
|
|
|||
|
|
async def connect(self, task_id: str, websocket: WebSocket):
|
|||
|
|
await websocket.accept()
|
|||
|
|
self.active_connections[task_id] = websocket
|
|||
|
|
|
|||
|
|
def disconnect(self, task_id: str):
|
|||
|
|
self.active_connections.pop(task_id, None)
|
|||
|
|
|
|||
|
|
async def send_progress(self, task_id: str, data: dict):
|
|||
|
|
ws = self.active_connections.get(task_id)
|
|||
|
|
if ws:
|
|||
|
|
await ws.send_json(data)
|
|||
|
|
|
|||
|
|
manager = ConnectionManager()
|
|||
|
|
|
|||
|
|
@router.websocket("/ws/generation/{task_id}")
|
|||
|
|
async def generation_progress(websocket: WebSocket, task_id: str):
|
|||
|
|
await manager.connect(task_id, websocket)
|
|||
|
|
try:
|
|||
|
|
while True:
|
|||
|
|
# 保持连接,等待 Celery 任务推送进度
|
|||
|
|
await websocket.receive_text()
|
|||
|
|
except WebSocketDisconnect:
|
|||
|
|
manager.disconnect(task_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、前后端联调约定
|
|||
|
|
|
|||
|
|
### API 规范
|
|||
|
|
|
|||
|
|
| 事项 | 约定 |
|
|||
|
|
|---|---|
|
|||
|
|
| 接口前缀 | `/api/v1/` |
|
|||
|
|
| 请求格式 | `application/json`,上传用 `multipart/form-data` |
|
|||
|
|
| 响应格式 | `{ "code": 0, "data": {...}, "message": "ok" }` |
|
|||
|
|
| 分页格式 | `{ "code": 0, "data": { "items": [...], "total": 100, "page": 1, "page_size": 20 } }` |
|
|||
|
|
| 错误码 | 0=成功,4xxx=客户端错误,5xxx=服务端错误 |
|
|||
|
|
| WebSocket | `/ws/generation/{task_id}` — AI 生成进度实时推送 |
|
|||
|
|
| 认证 | Bearer Token(JWT),Header: `Authorization: Bearer <token>` |
|
|||
|
|
|
|||
|
|
### 统一响应模型
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# backend/app/schemas/common.py
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from typing import Generic, TypeVar
|
|||
|
|
|
|||
|
|
T = TypeVar("T")
|
|||
|
|
|
|||
|
|
class ApiResponse(BaseModel, Generic[T]):
|
|||
|
|
code: int = 0
|
|||
|
|
message: str = "ok"
|
|||
|
|
data: T | None = None
|
|||
|
|
|
|||
|
|
class PageResponse(BaseModel, Generic[T]):
|
|||
|
|
items: list[T]
|
|||
|
|
total: int
|
|||
|
|
page: int
|
|||
|
|
page_size: int
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 前端请求封装
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// frontend/src/api/client.ts
|
|||
|
|
import axios from 'axios';
|
|||
|
|
|
|||
|
|
const client = axios.create({
|
|||
|
|
baseURL: '/api/v1',
|
|||
|
|
timeout: 30000,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 请求拦截:自动带 Token
|
|||
|
|
client.interceptors.request.use((config) => {
|
|||
|
|
const token = localStorage.getItem('token');
|
|||
|
|
if (token) {
|
|||
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|||
|
|
}
|
|||
|
|
return config;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 响应拦截:统一错误处理
|
|||
|
|
client.interceptors.response.use(
|
|||
|
|
(res) => res.data,
|
|||
|
|
(err) => {
|
|||
|
|
if (err.response?.status === 401) {
|
|||
|
|
// 跳转登录
|
|||
|
|
}
|
|||
|
|
return Promise.reject(err);
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
export default client;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、AI 生成核心功能拆解
|
|||
|
|
|
|||
|
|
按优先级排列,建议 Trae 按此顺序实现:
|
|||
|
|
|
|||
|
|
| 优先级 | 功能 | 前端关键组件 | 后端关键服务 | 复杂度 |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| P0 | **AI 脚本生成** | `ScriptGenerator.tsx`(输入主题/关键词 → 流式显示脚本) | `script_generator.py`(调用 LLM API,SSE 流式返回) | ⭐⭐ |
|
|||
|
|
| P0 | **视频编辑器基础** | `Timeline/` + `Canvas/` + `editorStore` | 项目 CRUD、素材上传 | ⭐⭐⭐⭐⭐ |
|
|||
|
|
| P1 | **AI 配音** | `VoiceGenerator.tsx`(选择音色 → 试听 → 插入时间轴) | `voice_service.py`(TTS API) | ⭐⭐⭐ |
|
|||
|
|
| P1 | **AI 字幕** | `SubtitleGenerator.tsx`(语音识别 → 字幕轨道展示) | `subtitle_service.py`(ASR) | ⭐⭐⭐ |
|
|||
|
|
| P2 | **模板市场** | `Templates.tsx`(模板浏览 → 一键套用) | `templates.py`(模板 CRUD) | ⭐⭐ |
|
|||
|
|
| P2 | **视频合成导出** | 导出进度条 | `video_renderer.py`(FFmpeg 合成 + Celery 异步) | ⭐⭐⭐⭐ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十、开发工作流
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|||
|
|
│ WorkBuddy │────▶│ Trae │────▶│ Git Commit │
|
|||
|
|
│ 方案 & 架构 │ │ 编码 & 调试 │ │ 版本管理 │
|
|||
|
|
└──────────────┘ └──────────────┘ └──────────────┘
|
|||
|
|
▲ │
|
|||
|
|
└────────────────────────────────────────┘
|
|||
|
|
遇到架构问题回 WorkBuddy 讨论
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
1. WorkBuddy 产出本方案 → Trae 读者直接执行
|
|||
|
|
2. Trae 按 P0 → P1 → P2 顺序编码
|
|||
|
|
3. 每完成一个模块,前端 `npm run dev` 即时验证
|
|||
|
|
4. 后端 `pytest` + `uvicorn --reload` 同步验证
|
|||
|
|
5. 遇到技术选型/架构疑问 → 回 WorkBuddy 讨论
|
|||
|
|
6. 确定方案后继续在 Trae 编码
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十一、交付清单
|
|||
|
|
|
|||
|
|
给 Trae 的第一阶段任务(建议一次不要超过 5 个文件):
|
|||
|
|
|
|||
|
|
- [ ] `frontend/package.json` — 按第四节配置
|
|||
|
|
- [ ] `frontend/vite.config.ts` — 按 5.3 节配置
|
|||
|
|
- [ ] `frontend/tailwind.config.ts` — 按 5.3 节配置
|
|||
|
|
- [ ] `frontend/src/index.css` — 按 5.3 节配置
|
|||
|
|
- [ ] `frontend/src/main.tsx` — React 入口
|
|||
|
|
- [ ] `frontend/src/App.tsx` — 路由 + 布局壳
|
|||
|
|
- [ ] `frontend/src/components/layout/AppShell.tsx` — shadcn/ui 侧边栏 + 顶栏
|
|||
|
|
- [ ] `frontend/src/stores/editorStore.ts` — 编辑器核心状态(第六节)
|
|||
|
|
- [ ] `frontend/src/pages/Home.tsx` — 首页占位
|
|||
|
|
- [ ] `frontend/src/pages/Editor.tsx` — 编辑器主��面占位
|
|||
|
|
- [ ] `backend/pyproject.toml` — 项目配置
|
|||
|
|
- [ ] `backend/requirements/base.txt` — 按第四节配置
|
|||
|
|
- [ ] `backend/app/main.py` — 第七节完整代码
|
|||
|
|
- [ ] `backend/app/config.py` — pydantic-settings 配置
|
|||
|
|
- [ ] `backend/app/core/database.py` — SQLAlchemy 引擎
|
|||
|
|
- [ ] `backend/app/api/v1/router.py` — 路由聚合
|
|||
|
|
- [ ] `backend/app/api/v1/websocket.py` — WebSocket 进度推送
|
|||
|
|
- [ ] `backend/app/schemas/common.py` — 统一响应模型
|
|||
|
|
- [ ] `backend/docker-compose.yml` — 本地基础设施
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
_方案版本:v1.0 | 产出日期:2026-07-31 | 产出工具:WorkBuddy | 执行工具:Trae_
|