基于 Vue 3 + TypeScript + Vite + Naive UI 构建的现代化 AI 聊天客户端,提供流畅的实时对话体验,支持 Markdown 渲染、代码高亮和流式停止功能。
项目地址 喜欢的可以点点star
https://github.com/lxiiang/nestjs-ai-stream
🚀 项目概述
这是一个现代化的 AI 聊天前端应用,采用最新的前端技术栈,为用户提供流畅的实时对话体验。
✨ 核心特性
- 🎯 Vue 3 Composition API: 使用最新的 Vue 3 组合式 API,提供更好的逻辑复用和类型推导
- 🔧 TypeScript 支持: 完整的类型安全保障,提升开发体验和代码质量
- ⚡ 实时流式显示: 基于 SSE 技术实现 AI 回复逐字实时显示,用户体验流畅
- 📝 Markdown 渲染: 集成 markdown-it 自动渲染 Markdown 格式内容
- 🌈 代码高亮: 使用 highlight.js 支持多语言代码语法高亮
- ⏹️ 流式停止功能: 支持用户随时中断 AI 生成过程
- 📱 响应式设计: 适配桌面端和移动端各种屏幕尺寸
- 🧩 组件化架构: 模块化组件设计,易于维护和功能扩展
- 🎨 现代化 UI: 集成 Naive UI 组件库,提供美观的界面设计
- 🔄 自动导入: 使用 unplugin-auto-import 自动导入 Vue API,提升开发效率
📁 项目结构
web-client/
├── src/
│ ├── components/ # 组件目录
│ │ ├── Head/ # 头部组件
│ │ │ └── index.vue # 应用头部,包含标题和渐变背景
│ │ ├── Chat/ # 聊天组件
│ │ │ └── index.vue # 聊天内容区域,支持消息渲染和欢迎页面
│ │ └── Input/ # 输入组件
│ │ └── index.vue # 消息输入框,支持快捷键和流式控制
│ ├── hooks/ # 组合式函数
│ │ ├── useChat.js # 聊天逻辑钩子,处理流式数据接收和状态管理
│ │ └── useMitt.js # 事件总线钩子,用于组件间通信
│ ├── App.vue # 根组件,定义整体布局和样式
│ ├── main.ts # 应用入口,Vue 应用初始化
│ └── style.css # 全局样式文件
├── public/ # 公共资源目录
│ └── vite.svg # Vite 默认图标
├── index.html # HTML 模板文件
├── vite.config.ts # Vite 构建配置
├── tsconfig.json # TypeScript 配置
├── tsconfig.app.json # 应用 TypeScript 配置
├── tsconfig.node.json # Node.js TypeScript 配置
├── package.json # 项目依赖和脚本配置
├── auto-imports.d.ts # 自动导入类型声明文件
├── vite-env.d.ts # Vite 环境类型声明
└── README.md # 项目文档
🔍 核心实现详解
1. 聊天逻辑钩子:useChat.js
核心的聊天逻辑封装,处理流式数据接收和状态管理:
import { ref } from "vue";
import { v4 as uuidv4 } from "uuid";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import MarkdownIt from "markdown-it";
import hljs from "highlight.js";
// 响应式状态管理
const messages = ref([]);
const curMessage = ref({});
const controller = ref(null);
const isStreaming = ref(false);
// Markdown 渲染器配置
const markdown = MarkdownIt({
html: true, // 允许在 Markdown 中使用原始 HTML 标签
linkify: true, // 自动将 URL 转换为链接
typographer: true, // 启用智能引号和连字符
breaks: true, // 启用自动换行
xhtmlOut: true, // 使用 XHTML 模式
langPrefix: "language-", // 添加语言前缀
});
// 代码高亮配置
markdown.set({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return (
'<pre class="hljs"><code>' +
hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
"</code></pre>"
);
} catch (__) {}
}
return (
'<pre class="hljs"><code>' +
markdown.utils.escapeHtml(str) +
"</code></pre>"
);
},
});
export function useChat() {
// 处理流式消息
const handleStreamMessage = (ev) => {
if (ev.data) {
const data = JSON.parse(ev.data);
if (data.type === "start") {
// 开始流式响应
curMessage.value = {
mid: uuidv4(),
role: "assistant",
content: "",
};
messages.value.push(curMessage.value);
}
if (data.type === "chunk") {
// 追加内容并渲染 Markdown
curMessage.value.content += data.content;
curMessage.value.htmlStr = markdown.render(curMessage.value.content);
}
if (data.type === "end") {
curMessage.value = {};
isStreaming.value = false;
} else if (data.type === "error") {
// 处理错误情况
isStreaming.value = false;
messages.value.push({
role: "assistant",
content: data.message,
});
}
}
};
// 处理流式错误
const handleStreamError = (ev) => {
isStreaming.value = false;
messages.value.push({
role: "assistant",
content: "服务异常",
});
};
// API 调用
const queryAnswer = async (message) => {
controller.value = new AbortController();
const signal = controller.value.signal;
fetchEventSource("/api/ai/chat/stream-sse", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(message),
signal, // 中断信号
openWhenHidden: true, // 页面隐藏时仍保持连接
onmessage: (ev) => handleStreamMessage(ev),
onerror: (ev) => handleStreamError(ev),
});
};
// 发送消息
const sendMessage = (userMsg) => {
if (!userMsg.trim()) return;
const userMessage = {
mid: uuidv4(),
role: "user",
content: userMsg,
};
messages.value.push(userMessage);
isStreaming.value = true;
queryAnswer({ message: userMsg });
};
// 停止对话
const stopConversation = () => {
if (controller.value) {
controller.value.abort();
isStreaming.value = false;
}
};
return {
messages,
isStreaming,
sendMessage,
stopConversation,
};
}
技术亮点:
- 组合式 API: 使用 Vue 3 的
ref管理响应式状态 - 流式数据处理: 通过
fetchEventSource处理 Server-Sent Events 数据流 - Markdown 渲染: 集成
markdown-it实现富文本显示,支持 HTML 标签 - 代码高亮: 使用
highlight.js提供多语言语法高亮 - 错误处理: 完善的错误处理机制,包括网络异常和服务异常
- 连接管理: 支持页面隐藏时保持连接,提供更好的用户体验
- 中断控制: 使用
AbortController实现流式响应的中断功能
2. 聊天内容组件:Chat/index.vue
负责展示聊天消息列表和处理消息渲染,包含欢迎页面和消息展示:
<script setup>
import { useChat } from "@/hooks/useChat";
const { messages } = useChat();
</script>
<template>
<div id="chat-container" class="chat-container">
<!-- 欢迎页面 -->
<div v-if="messages.length === 0" class="welcome-message">
<div class="welcome-content">
<h2>👋 你好!我是你的数字人助手</h2>
<p>有什么可以帮助你的吗?</p>
</div>
</div>
<!-- 消息列表 -->
<div
v-for="message in messages"
:key="message.id"
:class="['message', message.role]"
>
<!-- 用户头像 -->
<div class="message-avatar">
<span v-if="message.role === 'user'">👤</span>
<span v-else>🤖</span>
</div>
<!-- 消息内容 -->
<div class="message-content">
<div
class="message-text"
v-if="message.htmlStr"
v-html="message.htmlStr"
></div>
<div class="message-text" v-else>
{{ message.content }}
<span v-if="message.isStreaming" class="typing-indicator">▋</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.chat-container {
flex: 1;
overflow-y: auto;
padding: 20px;
background: white;
}
.welcome-message {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
text-align: center;
}
.message {
display: flex;
margin-bottom: 20px;
animation: fadeIn 0.3s ease-in;
}
.message.user {
flex-direction: row-reverse;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
margin: 0 10px;
background: #f0f0f0;
}
.message.user .message-avatar {
background: #667eea;
color: white;
}
.message-content {
max-width: 70%;
background: #f8f9fa;
padding: 12px 16px;
border-radius: 18px;
position: relative;
}
.message.user .message-content {
background: #667eea;
color: white;
}
.typing-indicator {
animation: blink 1s infinite;
color: #667eea;
}
</style>
组件特性:
- 欢迎页面: 首次访问时显示友好的欢迎界面
- 消息渲染: 区分用户和 AI 消息的不同样式和布局
- 头像系统: 使用 emoji 图标区分用户和 AI 身份
- Markdown 支持: AI 消息支持 HTML 渲染显示 Markdown 内容
- 打字指示器: 流式输入时显示闪烁的光标动画
- 响应式布局: 用户消息右对齐,AI 消息左对齐
- 动画效果: 消息出现时的淡入动画效果
3. 输入组件:Input/index.vue
处理用户输入和发送逻辑,支持快捷键和流式控制:
<script setup>
import { useChat } from "@/hooks/useChat";
const { sendMessage, isStreaming, stopConversation } = useChat();
const inputMessage = ref("");
// 处理键盘事件
const handleKeyPress = (event) => {
if (isStreaming.value) {
stopConversation();
return;
}
if (!inputMessage.value.trim()) return;
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
}
sendMessage(inputMessage.value);
inputMessage.value = "";
};
</script>
<template>
<!-- 输入区域 -->
<div class="input-area">
<div class="input-container">
<textarea
v-model="inputMessage"
@keypress="handleKeyPress"
placeholder="输入你的消息... (Enter发送,Shift+Enter换行)"
class="message-input"
rows="1"
></textarea>
<button @click="handleKeyPress(inputMessage)" class="send-btn">
{{ isStreaming ? " ⏹️ 停止" : "发送" }}
</button>
</div>
</div>
</template>
<style scoped>
.input-area {
padding: 20px;
background: white;
border-top: 1px solid #eee;
}
.input-container {
display: flex;
gap: 10px;
align-items: flex-end;
}
.message-input {
flex: 1;
border: 2px solid #e0e0e0;
border-radius: 20px;
padding: 12px 16px;
font-size: 14px;
resize: none;
outline: none;
transition: border-color 0.2s;
min-height: 20px;
max-height: 120px;
}
.message-input:focus {
border-color: #667eea;
}
.send-btn {
padding: 12px 24px;
background: #667eea;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
white-space: nowrap;
}
.send-btn:hover {
background: #5a6fd8;
}
</style>
输入特性:
- 智能键盘处理: Enter 键根据流式状态执行发送或停止操作
- 快捷键支持: Shift+Enter 换行,Enter 发送消息
- 动态按钮状态: 根据流式状态切换"发送"和"停止"按钮
- 自适应输入框: 支持多行输入,最大高度限制
- 输入验证: 防止发送空消息
- 视觉反馈: 输入框聚焦时边框颜色变化
- 响应式设计: 适配不同屏幕尺寸的布局
4. 头部组件:Head/index.vue
简洁的应用头部组件,提供品牌展示:
<script setup></script>
<template>
<div class="chat-header">
<h1>🤖 数字人助手</h1>
</div>
</template>
<style scoped>
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.chat-header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
}
</style>
头部特性:
- 渐变背景: 使用 CSS 渐变创建美观的视觉效果
- 品牌展示: 清晰的标题和图标展示
- 预留扩展: 预留了操作按钮的位置,便于后续功能扩展
🎨 UI 组件库支持
Naive UI 集成
项目已集成 Naive UI 组件库,这是一个基于 Vue 3 的现代化 UI 组件库:
特性优势:
- 🎯 TypeScript 友好: 完整的 TypeScript 支持
- 🎨 主题定制: 强大的主题系统,支持暗黑模式
- 📱 响应式设计: 适配各种屏幕尺寸
- ⚡ 性能优化: 按需加载,减少打包体积
- 🔧 易于使用: 简洁的 API 设计
使用示例:
<script setup>
import { NButton, NMessage } from "naive-ui";
// 显示消息提示
const showMessage = () => {
window.$message.success("操作成功!");
};
</script>
<template>
<div>
<n-button type="primary" @click="showMessage"> 点击我 </n-button>
</div>
</template>
按需导入配置:
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: [
"vue",
{
"naive-ui": [
"useMessage",
"useDialog",
"useNotification",
"useLoadingBar",
],
},
],
dts: true,
}),
],
});
🔧 开发配置
环境要求
- Node.js >= 16.0.0
- pnpm >= 7.0.0
本地开发
快速开始
# 1. 克隆项目
git clone <repository-url>
cd web-client
# 2. 安装依赖
pnpm install
# 3. 启动开发服务器
pnpm run dev
# 4. 打开浏览器访问
# http://localhost:5173
开发命令
# 启动开发服务器(热重载)
pnpm run dev
# 构建生产版本
pnpm run build
# 预览生产构建
pnpm run preview
# 类型检查
pnpm run type-check
# 代码格式化
pnpm run format
# 代码检查
pnpm run lint
开发环境配置
环境变量配置
创建 .env.local 文件:
# API 基础地址
VITE_API_BASE_URL=http://localhost:3000
# 应用标题
VITE_APP_TITLE=数字人助手
# 是否启用调试模式
VITE_DEBUG=true
Vite 配置优化
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ["vue"],
dts: true,
}),
],
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
server: {
port: 5173,
host: true, // 允许外部访问
proxy: {
"/api": {
target: process.env.VITE_API_BASE_URL || "http://localhost:3000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
build: {
// 构建优化
rollupOptions: {
output: {
manualChunks: {
"vue-vendor": ["vue"],
"ui-vendor": ["naive-ui"],
"utils-vendor": ["markdown-it", "highlight.js"],
},
},
},
// 压缩配置
minify: "terser",
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
},
});
开发工具推荐
VS Code 扩展
{
"recommendations": [
"Vue.volar",
"Vue.vscode-typescript-vue-plugin",
"bradlc.vscode-tailwindcss",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
}
VS Code 设置
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.preferences.importModuleSpecifier": "relative"
}
Vite 配置
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ["vue"],
dts: true, // 自动生成类型声明
}),
],
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
server: {
port: 5173,
proxy: {
"/api": {
target: "http://localhost:3000", // 后端服务地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});
📦 核心依赖
生产依赖
| 包名 | 版本 | 用途 |
|---|---|---|
| vue | ^3.5.18 | Vue 3 框架核心,提供响应式系统和组件化开发 |
| @microsoft/fetch-event-source | ^2.0.1 | SSE 客户端库,处理流式数据接收 |
| markdown-it | ^14.1.0 | Markdown 解析和渲染,支持扩展插件 |
| highlight.js | ^11.11.1 | 代码语法高亮库,支持多种编程语言 |
| uuid | ^13.0.0 | 生成唯一标识符,用于消息 ID |
| mitt | ^3.0.1 | 轻量级事件总线,用于组件间通信 |
| naive-ui | ^2.42.0 | Vue 3 UI 组件库,提供现代化界面组件 |
| qs | ^6.14.0 | URL 查询字符串解析和序列化库 |
依赖说明
核心功能依赖
-
Vue 3 生态
vue: 核心框架,提供 Composition API 和响应式系统@vitejs/plugin-vue: 支持.vue单文件组件
-
流式通信
@microsoft/fetch-event-source: 处理 Server-Sent Events,实现实时数据流- 支持连接中断、错误重试等高级功能
-
内容渲染
markdown-it: 将 Markdown 文本转换为 HTMLhighlight.js: 为代码块提供语法高亮
-
工具库
uuid: 生成全局唯一标识符mitt: 轻量级事件发布订阅系统qs: URL 参数处理
- Auto Import: 自动导入 Vue API
- 减少重复的 import 语句
- 自动生成类型声明文件
- 提升开发效率
版本兼容性
- Node.js: >= 16.0.0
- pnpm: >= 7.0.0
- Vue: 3.5.x (支持 Composition API)
- TypeScript: 5.x
包管理器
项目使用 pnpm 作为包管理器,相比 npm 和 yarn 具有以下优势:
- 🚀 更快的安装速度: 通过硬链接和符号链接优化
- 💾 节省磁盘空间: 共享依赖包,避免重复存储
- 🔒 严格的依赖管理: 避免幽灵依赖问题
- 📦 更好的 monorepo 支持: 适合多包项目管理
🎯 总结
这个方案不仅实现了基础的流式聊天功能,还考虑了错误处理、用户体验和可扩展性。实际项目中可以基于这个框架,添加身份验证、对话历史存储等功能。 希望这篇分享对你有帮助,有任何问题欢迎在评论区交流!