Appearance
AI聊天平台 - 项目分析
在线预览
项目概述
基于 Vue 3 构建的 AI 聊天平台,支持多平台 LLM 接入(DeepSeek / 阿里百炼 / 字节火山引擎)、SSE 流式输出、多模态输入(文本+图片)、语音识别、MCP 服务管理,提供完整的对话管理、主题切换等能力。
技术栈
| 类别 | 技术 | 用途 |
|---|---|---|
| 框架 | Vue 3 (Composition API + <script setup>) | 响应式 UI |
| 状态管理 | Pinia 3 | 全局状态(聊天/设置/用户) |
| 路由 | Vue Router 4 | 页面路由 + 导航守卫 |
| UI 组件 | Element Plus 2 | 对话框、输入框、图标等 |
| 构建 | Vite 7 | 开发服务器与生产构建 |
| HTTP | 原生 fetch (SSE) | 流式 AI 对话请求 |
| Markdown | marked + highlight.js | 消息内容渲染 |
| 语音 | Web Speech API | 语音输入识别 |
| 测试 | Playwright | E2E 测试 |
| 工具 | @vueuse/core | Composition API 工具函数 |
目录结构
src/
├── api/
│ ├── chat.js # 聊天 API(SSE 流式请求、图片转 Base64、连接测试)
│ └── models.js # 模型列表获取
├── components/
│ ├── ChatInput.vue # 多模态输入区(文本/图片/语音)
│ ├── ChatMessage.vue # 消息气泡(Markdown 渲染 + 代码高亮)
│ ├── SettingsDialog.vue # 设置弹窗(平台/MCP 配置)
│ └── ThinkingProcess.vue# AI 推理链折叠面板
├── composables/
│ └── useSpeechRecognition.js # Web Speech API 封装
├── router/
│ └── index.js # 路由配置 + 鉴权守卫
├── stores/
│ ├── chat.js # 聊天 Store(对话/消息管理)
│ ├── settings.js # 设置 Store(API/MCP/主题)
│ └── user.js # 用户 Store(模拟登录)
├── views/
│ ├── ChatView.vue # 主聊天视图(侧边栏 + 消息列表 + 输入区)
│ └── LoginView.vue # 登录页面
├── App.vue # 根组件
├── main.js # 应用入口
└── style.css # 全局样式 + 主题变量架构与数据流
整体架构
正在加载图表...
消息发送数据流
正在加载图表...
路由与鉴权流程
正在加载图表...
核心模块详解
1. 路由与鉴权 (src/router/index.js)
- 两个路由:
/login(LoginView)和/(ChatView) /:pathMatch(.*)*兜底重定向至/requiresAuth元信息控制访问权限- 全局前置守卫:未登录访问
/时重定向至/login,已登录访问/login时重定向至/ - 组件懒加载(
() => import(...))
2. Pinia Store
userStore(src/stores/user.js)
- 状态:
token、username(从localStorage初始化) isLoggedIn计算属性(!!token.value)login()/logout()同步localStorage- 纯前端模拟,无真实后端
chatStore(src/stores/chat.js)
- 管理
conversations数组、currentConversationId、当前messages - 全部持久化至
localStorage - 核心操作:
createConversation/switchConversation/deleteConversation/addMessage/updateLastMessage
javascript
// 关键操作:更新流式消息
updateLastMessage(content, thinking) {
const messages = this.messages
const lastMsg = messages[messages.length - 1]
if (lastMsg?.role === 'assistant') {
lastMsg.content = content || lastMsg.content
if (thinking) lastMsg.thinking = thinking
}
}settingsStore(src/stores/settings.js)
- 三个 LLM 提供商预设(DeepSeek / 阿里百炼 / 字节火山引擎)
- 每个提供商配置:
apiKey、apiUrl、model - 通用设置:
theme(light/dark)、enableWebSearch、mcpServers数组 - MCP 服务管理:
addMcpServer/updateMcpServer/removeMcpServer - 主题切换通过操作
document.documentElement.classList的'dark'类 watch(settings, saveSettings, { deep: true })自动持久化
3. 视图层
ChatView.vue(主聊天视图)
- 固定侧边栏(可折叠)+ 主区域布局
- 侧边栏:会话列表(标题 + 删除按钮),底部用户信息入口
- 主区域:顶栏(汉堡菜单、标题、主题切换、设置、退出按钮)
- 消息容器:空状态提示,实时滚动到底部
handleSend流程:构建消息 → 写入 store → 发起 streamChat
LoginView.vue(登录页面)
- 居中卡片布局,径向渐变背景
- 表单:用户名 + 密码输入
- 模拟登录(800ms 延迟,生成
mock-token-{timestamp})
4. 组件
ChatInput.vue(输入区)
- textarea 输入(Enter 发送 / Shift+Enter 换行)
- 图片上传(
<input type="file" accept="image/*">),预览缩略图 - Web Speech API 语音输入(脉冲动画录音指示)
- 联网搜索开关按钮
emit('send', { text, image })向父组件发送
ChatMessage.vue(消息气泡)
- 用户消息右对齐,AI 消息左对齐(
flex-direction: row-reverse) - Markdown 渲染:
marked解析 +highlight.js代码高亮(js/python/markdown) - 多模态消息支持:content 为数组时逐项渲染(图片 + 文本)
- AI 消息可选展示 ThinkingProcess 推理链面板
javascript
// 核心渲染逻辑
const renderedContent = computed(() => {
if (Array.isArray(props.message.content)) {
return props.message.content
.map(item => item.type === 'image_url'
? `<img src="${item.image_url.url}" />`
: marked(item.text))
.join('')
}
return marked(props.message.content || '')
})ThinkingProcess.vue(推理链)
- 可折叠面板展示 AI 推理过程
- 加载旋转动画 + 展开/折叠切换
- 使用
el-collapse-transition实现动画
SettingsDialog.vue(设置弹窗)
- 双 Tab:
platform+mcp - 平台 Tab:三个平台卡片(API Key / URL / 模型选择下拉框 + 搜索创建)
- 模型列表从 API 获取(
/v1/models端点) - MCP Tab:SSE 服务器的增删改查,每项有开关/测试连接/编辑/删除
- 打开时自动获取模型列表
5. API 层
streamChat(src/api/chat.js)
核心流式对话函数:
javascript
export async function streamChat(messages, onChunk, onThinking) {
const settingsStore = useSettingsStore()
const { apiUrl, apiKey, model, platform } = settingsStore.currentSettings
const url = resolveChatCompletionsUrl(apiUrl)
const body = {
model, stream: true,
messages: normalizeMessagesForPlatform(messages, platform)
}
if (settingsStore.settings.enableWebSearch && platform === 'aliyun') {
body.enable_search = true
}
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify(body)
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
// 读取 SSE 流,解析 data: {...} 格式
// delta.reasoning_content → onThinking
// delta.content → onChunk
}关键工具函数:
resolveChatCompletionsUrl(url)— 确保以/chat/completions结尾imageToBase64(file)— File 转 Base64 data URLbuildMessageWithImage(text, imageFile)— 构建多模态消息数组testConnection(platform, apiKey, apiUrl, model)— 测试 API 连接
fetchModels(src/api/models.js)
- 调用
/v1/models获取模型列表(兼容 OpenAI 格式) - 返回
[{ value, label }, ...]并按 ID 排序 - 火山引擎平台异常时返回空数组(前端展示预设模型)
6. Composables
useSpeechRecognition(src/composables/useSpeechRecognition.js)
- 封装
SpeechRecognition/webkitSpeechRecognition - 配置:
continuous: true、interimResults: true、lang: 'zh-CN' - 返回:
isListening/isSupported/error/start(onResult, onError)/stop() start():自动重启(除非手动停止),遍历识别结果合并完整文本onUnmounted时recognition.abort()清理
关键特性
多平台 LLM 支持
| 平台 | 默认 API URL | 默认模型 |
|---|---|---|
| DeepSeek | https://api.deepseek.com/v1/chat/completions | deepseek-chat |
| 阿里百炼 | https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions | qwen-turbo |
| 字节火山引擎 | https://ark.cn-beijing.volces.com/api/v3 | doubao-seed-2-0-pro-260215 |
平台差异适配:
- 火山引擎图片格式需添加
detail: 'auto'字段 - 阿里云支持
enable_search: true联网搜索 - DeepSeek 支持
reasoning_content思维链
数据持久化策略
| localStorage 键 | 存储内容 |
|---|---|
token | 登录令牌 |
username | 用户名 |
conversations | 会话及消息列表 |
ai_settings | API 配置 / 主题 / MCP 服务 |
主题系统
通过 CSS 自定义属性实现主题切换:
:root— 浅色主题变量html.dark— 深色主题变量- 核心变量:
--bg-color、--text-color、--border-color、--hover-bg等
测试
基于 Playwright 的 E2E 测试(tests/e2e.spec.js):
- 标题验证:导航至
/,验证登录页面标题 - 完整流程:登录 → 打开设置 → 测试连接(mock API)→ 发送消息 → 验证消息气泡
配置:Chrome 桌面端,baseURL http://localhost:5173