苦于网上很多 GPT 应用收费偏高,我就申请了自己的接口。
最近刚好在学 Vue,于是顺手写了一个极简版的 ChatGPT 前端页面。
目前已经实现了 连续对话、模型切换、Markdown 渲染、本地记录保存 等基础功能。
页面比较简单,布局也还有优化空间,不过很适合作为一个二开基础版本。
一、效果图
效果如下:

二、项目实现了什么?
这个小项目目前主要做了以下几个功能:
- 支持基础聊天对话
- 支持连续对话记忆
- 支持 GPT-3.5 / GPT-4 模型切换
- 支持简单的权限验证,防止误用 GPT-4
- 支持 Markdown 内容渲染
- 支持本地存储聊天记录
- 支持"模拟打字机效果"
整体思路不复杂,适合 Vue 初学者练手,也方便后续继续扩展。
三、连续对话记忆是怎么实现的?
1. 基本思路
连续对话的核心其实很简单:
把用户之前提问过的内容暂时保存起来,每次发送新问题时,把最近几条历史问题一起发给接口,作为上下文参考。
为了节省 token 和调用成本,我这里没有把完整历史全量发送,而是采用了 数组切片 的方式,只保留最近几条记录。
2. 处理方式
我这里只提取了最近几条 用户输入,然后拼接成一段上下文提示词,再加上当前新问题一起发送给 GPT。
提示语如下:
以上是我之前问你的问题,只供你回答我接下来问题做参考,请你不要重复回答我之前问过的问题,只对新问题进行处理。新问题是:
这样做虽然比较"朴素",但实际效果还不错,已经能满足基本的连续对话需求。
3. 关键代码
javascript
// 在发送消息时,将最近的对话历史一并发送
const maxHistoryLength = 4; // 最多保留最近 4 条记录
const trimmedChatHistory = this.chatHistory.slice(-maxHistoryLength);
// 提取用户消息
const userMessages = trimmedChatHistory
.filter(item => item.sender === 'user')
.map(item => item.message);
// 将用户历史问题拼接为字符串
const userMessageString = userMessages.join(' ');
// 拼接上下文提示词 + 当前问题
const fullUserMessage =
userMessageString +
' 以上是我之前问你的问题,只供你回答我接下来问题做参考,请你不要重复回答我之前问过的问题,只对新问题进行处理。新问题是:' +
this.userInput;
// 发起请求
const response = await this.chatGPTRequest(fullUserMessage);
this.handleBackendResponse(response);
4. 一个小优化
我还加了一个长度限制:
当上下文长度超过一定范围时,自动清空历史记录,避免内容越来越长导致费用增加或响应变慢。
javascript
if (userMessageString.length >= 1200) {
this.chatHistory = [];
localStorage.removeItem('chatHistory');
}
四、为了防止误用 GPT-4,我加了一个简单权限验证
因为 GPT-4 的成本更高,有时候自己用着用着不小心切过去,钱包就开始难受了。
所以我做了一个非常简单的密码验证逻辑:只有输入正确密码,才能使用 GPT-4。
当然,这种前端校验只适合演示或自用。
如果你要真正上线,建议把校验逻辑放到后端去做,前端明文判断并不安全。
关键代码
javascript
submitPassword() {
if (this.password === 'useit') {
ElNotification({
title: 'Success',
message: '权限已经获得!',
type: 'success',
})
this.selectedModel = 'gpt-4'
this.accessGranted = true
this.showPasswordCard = false
} else {
ElNotification({
title: 'Warning',
message: '都是贫困惹的祸!',
type: 'warning',
})
this.selectedModel = 'gpt-3.5-turbo'
}
}
在提交问题前,也会再做一次判断:
javascript
if (this.selectedModel === 'gpt-4' && !this.accessGranted) {
ElNotification({
title: 'Warning',
message: '都是贫困惹的祸!',
type: 'success',
})
return;
}
五、模拟打字效果
最开始为了提升一点"像聊天机器人"的感觉,我自己写了个模拟打字效果,让回复内容一个字一个字显示出来。
实现方式
拿到完整回复后,循环截取字符串逐步渲染:
javascript
const content = response.choices[0].message.content;
if (content) {
for (let i = 0; i < content.length; i++) {
this.messages = [
...this.messages.slice(0, -1),
{ content: content.slice(0, i + 1), isUser: false }
];
await this.$nextTick(() => {
const messageContainer = this.$refs.messageContainer;
messageContainer.scrollTop = messageContainer.scrollHeight;
});
}
}
不过后来我发现,如果直接用 流式输出(stream) ,本身就能达到类似的逐字效果,所以这个功能严格来说并不是必须的。
但既然都写出来了,也算是一个练手过程。
六、Markdown 渲染
GPT 返回的内容里,经常会有代码块、列表、标题等 Markdown 格式。
如果直接用普通文本显示,阅读体验会差很多,所以这里引入了 markdown-it 来做渲染。
安装依赖
bash
npm install markdown-it
使用方式
javascript
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
parseMarkdown(text) {
return md.render(text);
}
模板中通过 v-html 输出:
html
<div v-else-if="message.content" class="text-message" v-html="parseMarkdown(message.content)"></div>
这样返回的 Markdown 内容就能正常显示为 HTML 结构,像代码块、标题、列表这些都更清晰。
七、本地存储聊天记录
为了让页面刷新后还能保留聊天记录,我用了 localStorage 做本地存储。
这部分和"上下文记忆"功能结合在一起后,体验会更像一个真正可持续聊天的应用。
保存记录
javascript
handleBackendResponse(response) {
this.chatHistory.push({
sender: 'bot',
message: response.message
});
this.saveChatHistoryToLocalStorage();
},
saveChatHistoryToLocalStorage() {
localStorage.setItem('chatHistory', JSON.stringify(this.chatHistory));
}
加载记录
javascript
loadChatHistoryFromLocalStorage() {
const storedChatHistory = localStorage.getItem('chatHistory');
if (storedChatHistory) {
this.chatHistory = JSON.parse(storedChatHistory);
}
}
清空记录
javascript
clear() {
this.userInput = '';
this.chatHistory = [];
this.messages = [];
localStorage.removeItem('chatHistory');
ElNotification({
title: 'Success',
message: '记录已经清空!',
type: 'success',
});
}
八、核心请求代码
这里通过 axios 向 OpenAI 接口发起请求,模型可以根据下拉框选择不同版本。
javascript
chatGPTRequest(msg) {
return new Promise((resolve, reject) => {
axios({
method: 'post',
url: 'https://api.openai.com/v1/chat/completions',
data: {
max_tokens: 1200,
model: this.selectedModel,
temperature: 0.8,
top_p: 1,
presence_penalty: 1,
messages: [
{
role: 'system',
content: 'You are ChatGPT'
},
{
role: 'user',
content: msg
}
],
stream: false
},
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer 你的key'
}
})
.then(response => {
resolve(response.data);
})
.catch(error => reject(error));
});
}
九、页面结构
页面部分我用的是 Element Plus,整体布局比较简单,主要由以下几个模块组成:
- 顶部模型选择
- 左侧功能按钮
- 中间消息展示区
- 底部输入框与发送按钮
目前布局还比较朴素,主要先把功能跑通。
如果后续要继续优化,可以考虑:
- 调整消息卡片宽度与留白
- 优化移动端适配
- 增加暗黑模式
- 增加"新建会话 / 历史会话切换"
- 支持流式输出
- 支持代码高亮
- 支持后端代理,隐藏 API Key
十、开发过程中几个值得注意的问题
1. 不建议把 API Key 直接写在前端
虽然这样调试方便,但如果你部署到线上,前端代码是可以被看到的,密钥很容易泄露。
更合理的做法是:
- 前端请求你自己的后端
- 后端再去请求 OpenAI 接口
- 由后端统一做鉴权、限流、日志和模型控制
2. 连续对话最好使用标准 messages 结构
我目前这个版本是把历史问题拼成一段文本发给 GPT,比较简单直接。
如果想要更规范,建议使用 OpenAI 官方推荐的 messages 数组格式,把 user 和 assistant 的历史消息一起传入,这样上下文质量会更好。
3. 模拟打字效果不如流式输出自然
手动逐字渲染能实现视觉效果,但严格来说不是真正的流式响应。
如果想体验更真实的"边生成边显示",建议后续接入 stream: true。
十一、总结
这个 Vue 版 ChatGPT 前端应用,整体来说算是一个比较轻量的小练手项目。
虽然页面不算精致、实现方式也不算特别复杂,但基础功能已经具备:
- 连续对话
- Markdown 渲染
- 本地记录存储
- 模型选择
- 简单权限控制
对于刚接触 Vue 或者想自己搭一个简单 AI 对话页面的同学来说,还是挺适合参考和二开的。
如果后续有时间,我准备继续补这些功能:
- 流式输出
- 多轮会话管理
- 代码高亮
- 后端代理接口
- 更美观的聊天布局
- 移动端适配
十二、完整代码
完整代码我就不在正文里逐段展开分析了,有兴趣的朋友可以自己继续研究、优化和二开。
如果大家有更好的实现思路,也欢迎评论区交流,一起进步。
十三、结尾
这个项目本质上就是一个 "能用、简单、方便继续改" 的版本。
如果你也是一边学 Vue 一边折腾 AI 应用,希望这篇文章能给你一点思路。
如果这篇文章对你有帮助,欢迎点赞、收藏、评论支持一下。
也欢迎大佬们指点优化方案,互相交流学习。谢谢!