227 lines
8.6 KiB
Markdown
227 lines
8.6 KiB
Markdown
# XSH Assistant 编码指南
|
||
|
||
## 项目概览
|
||
|
||
**xsh-assistant** 是一个基于 Nuxt 3 的 AI 驱动内容生成平台,专注于数字内容创作(微课视频、虚拟讲师、绿幕合成等)。核心特性:
|
||
|
||
- **前端框架**: Nuxt 3 + Vue 3 + TypeScript + Tailwind CSS + Radix Vue UI
|
||
- **状态管理**: Pinia(带持久化)
|
||
- **媒体处理**: FFmpeg WASM(客户端视频处理)、WebAV(视频剪辑)
|
||
- **API 集成**: 统一的 `useFetchWrapped` 包装器,所有请求都通过 `API_BASE` 代理(`https://service1.fenshenzhike.com/`)
|
||
- **部署模式**: SPA(SSR=false)
|
||
|
||
## 核心架构模式
|
||
|
||
### 1. Composables 设计(Pinia + 自定义 Composables)
|
||
|
||
**状态管理采用分层设计**:
|
||
|
||
```
|
||
Pinia Stores(持久化状态)
|
||
├── useLoginState: 用户认证、token、个人资料
|
||
├── useHistory: AIGC 会话、聊天历史
|
||
└── useTourState: 新手引导状态
|
||
|
||
业务 Composables(无状态或短生命周期)
|
||
├── useFetchWrapped: API 请求包装(自动添加 token、user_id)
|
||
├── useLLM: LLM API 调用(Spark 模型集成)
|
||
├── useFFmpeg: FFmpeg WASM 单例管理
|
||
├── useVideoBackgroundCompositing: 视频合成(数字人+背景)
|
||
├── useVideoSubtitleEmbedding: 字幕嵌入
|
||
└── useDownload: 文件下载管理
|
||
```
|
||
|
||
**关键模式**:
|
||
- **Pinia stores 必须是单一实例**,通过 `storeToRefs()` 获取响应式引用
|
||
- **API 请求必须通过 `useFetchWrapped`** 来自动处理认证头(token/user_id)
|
||
- **FFmpeg 采用单例模式**(`useFFmpeg()` 返回全局加载的实例),避免重复初始化
|
||
|
||
### 2. API 请求模式
|
||
|
||
所有 API 请求使用统一的 `useFetchWrapped` 包装器:
|
||
|
||
```typescript
|
||
// 基础签名
|
||
useFetchWrapped<RequestType, ResponseType>(action: string, payload?: RequestType, options?: FetchOptions)
|
||
|
||
// 请求格式示例(来自 useHistory)
|
||
useFetchWrapped<AuthedRequest, BaseResponse<resp.xxx>>(
|
||
'App.User_User.CheckSession', // action 作为查询参数 ?s=
|
||
{ token: loginState.token, user_id: loginState.user.id, ...payload },
|
||
{ method: 'POST' } // 默认 POST
|
||
)
|
||
```
|
||
|
||
**约定**:
|
||
- **每个请求必须包含** `token` 和 `user_id`(来自 `useLoginState`)
|
||
- **响应结构统一**: `BaseResponse<T>` 包含 `ret: number` 状态码和 `data: T` 数据
|
||
- **API_BASE 在 `nuxt.config.ts` 中定义**,所有请求都相对于此 URL
|
||
|
||
### 3. 媒体处理架构
|
||
|
||
#### FFmpeg 初始化流程
|
||
- **单例加载**: 首次调用 `useFFmpeg()` 时初始化,后续复用缓存的实例
|
||
- **WASM 资源加载**: 从 CDN(`cdn.jsdelivr.net`)加载 FFmpeg core、wasm、worker
|
||
- **错误恢复**: 调用 `cleanupFFmpeg()` 清理资源并重置单例
|
||
|
||
#### 视频合成流程(核心用例)
|
||
```
|
||
输入: 透明通道视频 (WebM) + 背景图 (PNG/File)
|
||
↓
|
||
1. 获取背景图尺寸
|
||
2. 计算等比缩放到 720P
|
||
3. 加载文件到 FFmpeg vFS
|
||
4. 执行 FFmpeg 滤镜链:
|
||
- 背景: scale → ${outputWidth}x${outputHeight}
|
||
- 视频: scale → ${outputWidth}x${outputHeight} (保留 alpha)
|
||
- overlay: 视频叠加到背景(format=auto)
|
||
5. 使用 VP9 编码(支持 alpha)+ Opus 音频编码
|
||
6. 返回 Blob → 可直接上传或本地预览
|
||
```
|
||
|
||
### 4. UI 组件架构
|
||
|
||
#### 内置组件库(`components/uni/`)
|
||
自定义包装组件,提供统一 API:
|
||
- `UniButton`: 按钮 + loading 状态
|
||
- `UniInput`/`UniTextArea`: 表单输入
|
||
- `UniSelect`: 下拉选择
|
||
- `UniMessage`: 全局消息通知(通过 provide/inject)
|
||
- `UniCopyable`: 可复制文本
|
||
|
||
**消息通知用法**:
|
||
```typescript
|
||
const toast = useToast() // Radix Vue 的 Toast(顶部通知)
|
||
// 或从 provide 注入
|
||
const messageApi = inject('uni-message')
|
||
messageApi.success('操作成功')
|
||
messageApi.error('操作失败', 5000)
|
||
```
|
||
|
||
#### Radix Vue + Nuxt UI 集成
|
||
- 使用 Radix Vue for 基础组件(button, dialog, select)
|
||
- Nuxt UI 用于高级组件 + 主题管理
|
||
- **颜色方案**: primary='indigo', gray='neutral',详见 `app.config.ts`
|
||
|
||
### 5. 路由与页面结构
|
||
|
||
**目录映射**:
|
||
```
|
||
pages/
|
||
├── generation.vue (导航枢纽)
|
||
└── aigc/
|
||
├── chat/index.vue (聊天页,支持多 LLM 模型)
|
||
├── draw/index.vue (绘图生成)
|
||
└── generation/
|
||
├── course.vue (微课生成)
|
||
├── green-screen.vue (绿幕视频)
|
||
├── avatar-models.vue (数字讲师)
|
||
├── materials.vue (片头片尾)
|
||
├── ppt-templates.vue (PPT 库)
|
||
└── admin/ (管理功能)
|
||
```
|
||
|
||
**导航约定**:
|
||
- `/generation` → 功能导航页面
|
||
- `/aigc/chat` → 聊天/文本生成
|
||
- `/generation/course` → 视频生成工作流
|
||
- 所有生成功能都需要登录(ModalAuthentication 处理)
|
||
|
||
## 开发工作流
|
||
|
||
### 启动项目
|
||
```bash
|
||
ni # 安装依赖
|
||
nr dev # 启动 http://localhost:3000
|
||
nr generate # 生产构建 (生成静态文件)
|
||
```
|
||
|
||
### 常见任务
|
||
|
||
**添加新的 API 端点**:
|
||
1. 定义 Request 和 Response 类型(参考 `typings/llm.ts`)
|
||
2. 在 composable 中使用 `useFetchWrapped` 调用
|
||
3. 自动包含 token/user_id(来自 `useLoginState`)
|
||
|
||
**添加新的视频处理功能**:
|
||
1. 使用 `useFFmpeg()` 获取实例(自动初始化)
|
||
2. 写入文件到 vFS: `ffmpeg.writeFile()`
|
||
3. 执行命令:`ffmpeg.exec([...filterArgs])`
|
||
4. 清理临时文件:`ffmpeg.deleteFile()`
|
||
5. 使用 progress callback 通报处理进度
|
||
|
||
**添加新的 UI 组件**:
|
||
1. 创建在 `components/` 下(自动注册)
|
||
2. 优先使用 Radix Vue + Nuxt UI(已集成)
|
||
3. 使用 Tailwind CSS utility classes + `@apply` 指令
|
||
4. 通过 `app.config.ts` 自定义 UI 主题
|
||
|
||
## 项目特定的约定
|
||
|
||
### 类型定义位置
|
||
- **LLM 相关**: `typings/llm.ts`(ChatMessage, ChatSession, ModelTag, LLMModal)
|
||
- **全局类型**: `typings/types.d.ts`(BaseResponse, AuthedRequest, UserSchema)
|
||
- **组件接口**: 组件目录下的 `index.d.ts`(例 `components/aigc/drawing/index.d.ts`)
|
||
|
||
### 命名规范
|
||
- **Composables**: `use` 前缀(`useLoginState`, `useLLM`)
|
||
- **Stores**: `use` + 功能名(`useHistory`, `useTourState`)
|
||
- **组件**: PascalCase(`ChatItem.vue`, `ModalAuthentication.vue`)
|
||
- **工具函数**: camelCase,放在 `composables/` 或各功能目录
|
||
|
||
### 响应式数据模式
|
||
- **Pinia store 返回值**: 必须通过 `storeToRefs()` 才能保持响应式
|
||
- **模板中的 ref**: 直接访问(Vue 自动展开)
|
||
- **跨组件数据**: 优先使用 Pinia store(带持久化)
|
||
|
||
### 进度反馈与错误处理
|
||
- **长时间操作** (视频处理): 通过 callback 函数报告 progress(0-100)
|
||
- **错误处理**: 返回 Promise reject,上层 catch 处理;可选通过 toast/message 提示
|
||
- **FFmpeg 错误**: 捕获 exitCode 非零,记录详细的 FFmpeg 输出
|
||
|
||
## 依赖与性能优化
|
||
|
||
### 关键依赖
|
||
- **@ffmpeg/ffmpeg@0.12.15**: WASM 视频处理(从 CDN 加载)
|
||
- **@webav/av-cliper**: 客户端视频剪辑库
|
||
- **markdown-it + highlight.js**: 内容渲染(支持代码高亮)
|
||
- **date-fns/dayjs**: 时间处理(dayjs-nuxt 提供全局实例)
|
||
- **idb-keyval**: IndexedDB 简化操作(缓存大文件)
|
||
|
||
### Vite 优化设置
|
||
```typescript
|
||
// nuxt.config.ts 中排除以下包进行优化,避免 bundling WASM
|
||
optimizeDeps.exclude: ['@ffmpeg/ffmpeg', 'idb-keyval', '@webav/av-cliper', 'gsap', 'markdown-it']
|
||
```
|
||
|
||
### 构建排除项
|
||
Worker 格式设置为 ES Module,避免 Vite 默认处理:
|
||
```typescript
|
||
vite.worker.format = 'es'
|
||
```
|
||
|
||
## 测试与调试
|
||
|
||
- **开发服务器日志**: 浏览器控制台查看 FFmpeg、API、业务日志
|
||
- **FFmpeg 调试**: `[FFmpeg]` 前缀的日志输出包含加载进度、命令执行信息
|
||
- **状态调试**: Pinia DevTools(启用 `devtools: true`)
|
||
- **样式调试**: Tailwind 配置在 `tailwind.config.ts`,按需自定义
|
||
|
||
## 常见陷阱与解决方案
|
||
|
||
| 问题 | 原因 | 解决方案 |
|
||
|------|------|--------|
|
||
| API 请求 401 | 缺少 token 或已过期 | 检查 `useLoginState().token`,通过 ModalAuthentication 重新登录 |
|
||
| FFmpeg 加载超时 | CDN 资源加载慢 | 检查网络,可切换到本地 `/public/assets/ffmpeg` |
|
||
| 视频输出无声音 | 滤镜链未映射音频 | 确保 FFmpeg 命令包含 `-map '1:a?'` 映射音频轨道 |
|
||
| 组件未注册 | 文件位置错误 | 确保在 `components/` 目录下,子目录自动扁平化注册 |
|
||
| Pinia 状态未持久化 | 未配置 persist 选项 | 在 store 返回语句后添加 persist 配置(参考 `useLoginState`) |
|
||
|
||
## 资源链接
|
||
|
||
- [Nuxt 3 文档](https://nuxt.com/docs)
|
||
- [Pinia 文档](https://pinia.vuejs.org)
|
||
- [FFmpeg.wasm 文档](https://ffmpegwasm.netlify.app/)
|
||
- [Radix Vue](https://www.radix-vue.com/)
|
||
- [Nuxt UI 组件库](https://ui.nuxt.com/):可使用 nuxt-ui MCP 工具
|