8.9 KiB
8.9 KiB
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 包装器:
// 基础签名
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: 可复制文本
消息通知用法:
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 处理)
开发工作流
启动项目
ni # 安装依赖
nr dev # 启动 http://localhost:3000
nr generate # 生产构建 (生成静态文件)
常见任务
添加新的 API 端点:
- 定义 Request 和 Response 类型(参考
typings/llm.ts) - 在 composable 中使用
useFetchWrapped调用 - 自动包含 token/user_id(来自
useLoginState)
添加新的视频处理功能:
- 使用
useFFmpeg()获取实例(自动初始化) - 写入文件到 vFS:
ffmpeg.writeFile() - 执行命令:
ffmpeg.exec([...filterArgs]) - 清理临时文件:
ffmpeg.deleteFile() - 使用 progress callback 通报处理进度
添加新的 UI 组件:
- 创建在
components/下(自动注册) - 优先使用 Radix Vue + Nuxt UI(已集成)
- 使用 Tailwind CSS utility classes +
@apply指令 - 通过
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 优化设置
// nuxt.config.ts 中排除以下包进行优化,避免 bundling WASM
optimizeDeps.exclude: ['@ffmpeg/ffmpeg', 'idb-keyval', '@webav/av-cliper', 'gsap', 'markdown-it']
构建排除项
Worker 格式设置为 ES Module,避免 Vite 默认处理:
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 文档
- Pinia 文档
- FFmpeg.wasm 文档
- Radix Vue
- Nuxt UI 组件库:可使用 nuxt-ui MCP 工具