1. 项目概述:打造一个极简但功能完整的ChatGPT前端应用
最近一直在研究如何将ChatGPT的API能力整合到自己的前端项目中,经过多次迭代终于实现了一个功能完整但代码极简的解决方案。这个Vue项目不仅支持连续对话、Markdown渲染,还能将对话记录保存在本地存储中。对于想要快速集成AI对话功能的开发者来说,这个方案特别实用。
这个项目最核心的价值在于:用最少的代码实现了最实用的功能组合。很多现成的ChatGPT客户端要么功能过于复杂,要么缺少关键的连续对话能力。而我们的实现方案只需要不到300行代码就能搞定所有核心功能,特别适合作为二次开发的基础模板。
2. 技术选型与架构设计
2.1 为什么选择Vue作为前端框架
Vue.js的响应式特性和组件化开发模式特别适合这类实时交互应用。相比React,Vue的模板语法更直观,学习曲线更平缓;而对比原生JavaScript,Vue的数据绑定能大幅减少DOM操作代码。在这个项目中,我们主要利用了以下Vue特性:
- 组合式API(Composition API):用setup函数组织代码逻辑,比Options API更灵活
- 响应式系统:自动追踪数据变化并更新UI
- 单文件组件:将HTML、CSS和JavaScript封装在一个.vue文件中
2.2 核心功能模块划分
整个应用可以划分为四个主要模块:
- 对话管理模块:处理消息的发送、接收和存储
- Markdown渲染模块:将AI返回的Markdown内容转换为HTML
- 本地存储模块:使用localStorage保存对话历史
- UI交互模块:处理用户输入和界面反馈
这种模块化设计使得每个功能都可以独立开发和测试,也便于后续扩展。
3. 实现连续对话功能
3.1 对话上下文管理
ChatGPT的API本身是无状态的,要实现连续对话,我们需要在客户端维护对话历史。具体实现方式是:
const conversation = ref([ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello!' }, { role: 'assistant', content: 'Hi there! How can I help you today?' } ]);每次发送新消息时,我们都将整个对话历史发送给API,这样AI就能根据上下文生成回复。
3.2 优化对话体验的技巧
在实际使用中发现几个提升体验的关键点:
- 限制历史记录长度:过长的对话会消耗更多token,可以设置一个合理的上限(如最近10条消息)
- 添加加载状态:在等待AI回复时显示加载动画,提升用户体验
- 错误处理:网络请求可能会失败,需要妥善处理错误情况
const sendMessage = async () => { loading.value = true; try { const response = await axios.post('/api/chat', { messages: conversation.value }); conversation.value.push({ role: 'assistant', content: response.data.choices[0].message.content }); } catch (error) { console.error('API请求失败:', error); } finally { loading.value = false; } };4. Markdown渲染的实现方案
4.1 选择合适的Markdown解析器
经过对比测试,最终选择了marked.js作为Markdown解析器,原因如下:
- 轻量级(仅24KB)
- 解析速度快
- 支持GFM(GitHub Flavored Markdown)
- 可自定义渲染规则
安装和使用非常简单:
npm install markedimport { marked } from 'marked'; const renderMarkdown = (text) => { return marked.parse(text); };4.2 安全考虑:防止XSS攻击
直接渲染用户提供的Markdown内容可能存在XSS风险。marked.js提供了sanitizer选项,但更推荐使用DOMPurify进行二次净化:
import DOMPurify from 'dompurify'; const renderMarkdown = (text) => { return DOMPurify.sanitize(marked.parse(text)); };4.3 样式优化技巧
为了让渲染的Markdown内容更美观,可以引入GitHub风格的CSS:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css" > <div class="markdown-body" v-html="renderMarkdown(content)"></div>5. 本地记忆功能的实现
5.1 使用localStorage保存对话历史
localStorage是浏览器提供的持久化存储方案,非常适合保存小型数据。实现原理很简单:
// 保存对话 const saveConversation = () => { localStorage.setItem('chatHistory', JSON.stringify(conversation.value)); }; // 读取对话 const loadConversation = () => { const saved = localStorage.getItem('chatHistory'); if (saved) { conversation.value = JSON.parse(saved); } };5.2 优化存储策略
在实践中发现几个需要注意的点:
- 定期清理:localStorage有大小限制(通常5MB),长时间积累可能占满空间
- 数据压缩:可以对消息内容进行压缩后再存储
- 加密敏感信息:如果对话包含敏感内容,建议加密后再存储
5.3 实现多会话管理
进阶功能可以支持多个独立的对话会话:
const sessions = ref([]); const createNewSession = () => { const sessionId = Date.now().toString(); sessions.value.push({ id: sessionId, title: '新对话', createdAt: new Date() }); localStorage.setItem('sessions', JSON.stringify(sessions.value)); return sessionId; };6. 完整代码结构与关键实现
6.1 项目目录结构
src/ ├── assets/ ├── components/ │ ├── ChatMessage.vue │ └── MarkdownRenderer.vue ├── composables/ │ ├── useChat.js │ └── useStorage.js ├── App.vue └── main.js6.2 核心组件实现
ChatMessage.vue组件负责显示单条消息:
<template> <div :class="['message', role]"> <div class="avatar">{{ role === 'user' ? '👤' : '🤖' }}</div> <div class="content"> <MarkdownRenderer v-if="role === 'assistant'" :content="content" /> <template v-else>{{ content }}</template> </div> </div> </template>6.3 主应用逻辑
App.vue中整合所有功能:
<script setup> import { ref, onMounted } from 'vue'; import ChatMessage from './components/ChatMessage.vue'; import { useChat } from './composables/useChat'; import { useStorage } from './composables/useStorage'; const { conversation, sendMessage, loading } = useChat(); const { loadConversation, saveConversation } = useStorage(); onMounted(() => { loadConversation(); }); </script>7. 常见问题与解决方案
7.1 API调用限制处理
ChatGPT API有速率限制,处理方案:
- 添加请求队列
- 实现自动重试机制
- 在前端显示友好的错误提示
const callApiWithRetry = async (payload, retries = 3) => { for (let i = 0; i < retries; i++) { try { return await axios.post('/api/chat', payload); } catch (error) { if (i === retries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } };7.2 长消息处理技巧
当AI返回很长的消息时:
- 分段渲染提升性能
- 添加"继续阅读"按钮
- 实现自动滚动到最新消息
const renderLongMessage = (text) => { const chunks = []; const chunkSize = 1000; for (let i = 0; i < text.length; i += chunkSize) { chunks.push(text.slice(i, i + chunkSize)); } return chunks; };7.3 移动端适配要点
针对移动设备的优化:
- 输入框固定在底部
- 虚拟列表优化长对话性能
- 手势支持(如滑动删除消息)
@media (max-width: 768px) { .chat-container { padding-bottom: 80px; } .input-area { position: fixed; bottom: 0; left: 0; right: 0; } }8. 项目部署与扩展建议
8.1 部署选项比较
| 部署方式 | 优点 | 缺点 |
|---|---|---|
| Vercel | 简单快速,适合静态站点 | 需要处理API代理 |
| Netlify | 自带CI/CD,功能丰富 | 免费版有构建时间限制 |
| 自有服务器 | 完全控制 | 需要维护成本 |
8.2 可能的扩展方向
- 用户认证:支持多用户登录
- 主题切换:实现深色/浅色模式
- 插件系统:支持自定义功能扩展
- 语音输入:集成语音识别API
- 数据同步:跨设备同步对话记录
8.3 性能优化建议
- 使用虚拟滚动处理长对话列表
- 对Markdown解析结果进行缓存
- 按需加载大型依赖库
- 使用Web Worker处理耗时的计算任务
// 使用动态导入按需加载marked const renderMarkdown = async (text) => { const { marked } = await import('marked'); return marked.parse(text); };经过实际项目验证,这套方案在保持代码简洁的同时,提供了足够强大的功能。特别是在需要快速原型开发的场景下,这种极简但完整的实现方式特别有价值。我在多个内部项目中都使用了这个模板,根据具体需求进行定制化调整,大大提高了开发效率。