# 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(action: string, payload?: RequestType, options?: FetchOptions) // 请求格式示例(来自 useHistory) useFetchWrapped>( 'App.User_User.CheckSession', // action 作为查询参数 ?s= { token: loginState.token, user_id: loginState.user.id, ...payload }, { method: 'POST' } // 默认 POST ) ``` **约定**: - **每个请求必须包含** `token` 和 `user_id`(来自 `useLoginState`) - **响应结构统一**: `BaseResponse` 包含 `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 工具