12 KiB
12 KiB
大语言模型聊天网站设计文档
概述
本项目是一个全栈大语言模型聊天网站,支持用户与AI进行实时对话。系统采用前后端分离架构,前端使用Vue.js构建用户界面,后端使用Node.js提供API服务,通过调用兼容OpenAI格式的第三方API实现智能对话功能。
核心特性
- 实时AI对话体验
- 对话历史记录与管理
- 用户会话状态保持
- 响应式Web界面
- 高性能缓存机制
- 轻量级本地数据存储
技术栈
- 前端: Vue.js 3, Vue Router, Pinia, Axios, Element Plus
- 后端: Node.js, Express.js, SQLite3, LRU-Cache
- 包管理: pnpm
- 数据库: SQLite (本地文件数据库)
- 第三方集成: OpenAI兼容API服务
系统架构
整体架构图
graph TB
subgraph "客户端"
A[Vue.js前端]
A1[聊天界面]
A2[历史记录]
A3[用户设置]
end
subgraph "服务端"
B[Node.js后端]
B1[Express路由]
B2[会话管理]
B3[缓存层]
B4[数据访问层]
end
subgraph "数据存储"
C[SQLite数据库]
C1[用户表]
C2[对话表]
C3[消息表]
end
subgraph "外部服务"
D[第三方LLM API]
D1[OpenAI兼容接口]
end
A -->|HTTP/WebSocket| B
B -->|SQL查询| C
B -->|API调用| D
B3 -->|LRU缓存| B2
数据流架构
sequenceDiagram
participant U as 用户
participant F as Vue前端
participant B as Node.js后端
participant C as LRU缓存
participant DB as SQLite
participant API as 第三方LLM API
U->>F: 发送消息
F->>B: POST /api/chat/send
B->>C: 检查缓存
alt 缓存命中
C-->>B: 返回缓存结果
else 缓存未命中
B->>DB: 保存用户消息
B->>API: 调用LLM API
API-->>B: 返回AI回复
B->>DB: 保存AI回复
B->>C: 更新缓存
end
B-->>F: 返回对话结果
F-->>U: 显示AI回复
前端架构
组件层次结构
graph TD
A[App.vue] --> B[Layout组件]
B --> C[Header组件]
B --> D[Sidebar组件]
B --> E[Main组件]
E --> F[ChatView]
E --> G[HistoryView]
E --> H[SettingsView]
F --> I[ChatInput]
F --> J[MessageList]
F --> K[TypingIndicator]
J --> L[MessageItem]
L --> M[UserMessage]
L --> N[AIMessage]
D --> O[ConversationList]
O --> P[ConversationItem]
组件定义
核心聊天组件
ChatView.vue - 主聊天界面
- 管理当前对话状态
- 处理消息发送逻辑
- 实现消息流式显示
MessageList.vue - 消息列表容器
- 虚拟滚动优化
- 消息懒加载
- 自动滚动到底部
ChatInput.vue - 消息输入组件
- 多行文本输入
- 发送按钮状态管理
- 快捷键支持(Ctrl+Enter)
布局组件
Layout.vue - 主布局容器
- 响应式布局设计
- 侧边栏展开/收缩
- 移动端适配
Sidebar.vue - 侧边栏组件
- 对话历史列表
- 新建对话按钮
- 对话搜索功能
状态管理架构
使用Pinia进行状态管理,主要包含以下Store:
chatStore.js - 聊天状态管理
{
currentConversation: null,
messages: [],
isLoading: false,
isTyping: false
}
conversationStore.js - 对话管理
{
conversations: [],
activeConversationId: null,
searchQuery: ''
}
userStore.js - 用户设置
{
settings: {
theme: 'light',
fontSize: 'medium',
autoSave: true
}
}
路由配置
const routes = [
{
path: '/',
component: Layout,
children: [
{ path: '', redirect: '/chat' },
{ path: '/chat/:id?', component: ChatView },
{ path: '/history', component: HistoryView },
{ path: '/settings', component: SettingsView }
]
}
]
API集成层
apiClient.js - HTTP客户端封装
- Axios实例配置
- 请求/响应拦截器
- 错误处理机制
- 重试逻辑
chatApi.js - 聊天相关API
export const chatApi = {
sendMessage: (conversationId, message) => {},
getConversations: () => {},
getMessages: (conversationId) => {},
createConversation: () => {},
deleteConversation: (id) => {}
}
后端架构
API端点设计
对话管理端点
方法 | 路径 | 描述 | 请求体 | 响应 |
---|---|---|---|---|
POST | /api/conversations |
创建新对话 | { title?: string } |
{ id, title, createdAt } |
GET | /api/conversations |
获取对话列表 | - | [{ id, title, lastMessage, updatedAt }] |
GET | /api/conversations/:id |
获取对话详情 | - | { id, title, messages } |
DELETE | /api/conversations/:id |
删除对话 | - | { success: true } |
消息处理端点
方法 | 路径 | 描述 | 请求体 | 响应 |
---|---|---|---|---|
POST | /api/chat/send |
发送消息 | { conversationId, message, stream? } |
{ message, response } |
GET | /api/messages/:conversationId |
获取消息历史 | - | [{ id, role, content, timestamp }] |
数据模型设计
数据库表结构
conversations表
CREATE TABLE conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT '新对话',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
messages表
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
conversation_id INTEGER NOT NULL,
role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
content TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
);
业务逻辑层
对话服务 (ConversationService)
class ConversationService {
// 创建新对话
async createConversation(title) {}
// 获取对话列表
async getConversations() {}
// 获取对话详情
async getConversationById(id) {}
// 删除对话
async deleteConversation(id) {}
// 更新对话标题
async updateConversationTitle(id, title) {}
}
聊天服务 (ChatService)
class ChatService {
constructor(llmApiClient, cache) {
this.llmApiClient = llmApiClient;
this.cache = cache;
}
// 处理用户消息
async processMessage(conversationId, userMessage) {
// 1. 保存用户消息到数据库
// 2. 构建上下文(包含历史消息)
// 3. 调用LLM API
// 4. 保存AI回复到数据库
// 5. 更新缓存
// 6. 返回结果
}
// 流式响应处理
async processStreamMessage(conversationId, userMessage) {}
}
LLM API客户端 (LLMApiClient)
class LLMApiClient {
constructor(apiConfig) {
this.baseURL = apiConfig.baseURL;
this.apiKey = apiConfig.apiKey;
this.model = apiConfig.model;
}
// 调用聊天完成API
async createChatCompletion(messages, options = {}) {
const response = await fetch(`${this.baseURL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: this.model,
messages: messages,
stream: options.stream || false,
max_tokens: options.maxTokens || 2000,
temperature: options.temperature || 0.7
})
});
return response;
}
}
缓存策略
LRU缓存配置
const LRU = require('lru-cache');
const cache = new LRU({
max: 500, // 最大缓存条目数
maxAge: 1000 * 60 * 30 // 30分钟过期
});
// 缓存键策略
const getCacheKey = (conversationId, messageHash) => {
return `conv:${conversationId}:msg:${messageHash}`;
};
缓存使用场景
- 对话上下文缓存 - 缓存最近的对话上下文,减少数据库查询
- API响应缓存 - 对相同输入的LLM响应进行缓存
- 用户会话缓存 - 缓存用户会话信息
中间件系统
请求日志中间件
const requestLogger = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
};
错误处理中间件
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : '服务器内部错误'
});
};
速率限制中间件
const rateLimit = require('express-rate-limit');
const chatRateLimit = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
max: 20, // 限制每个IP每分钟最多20次请求
message: '请求过于频繁,请稍后再试'
});
项目结构
chat-website/
├── package.json
├── pnpm-workspace.yaml
├── README.md
├── .gitignore
├── frontend/
│ ├── package.json
│ ├── vite.config.js
│ ├── index.html
│ ├── src/
│ │ ├── main.js
│ │ ├── App.vue
│ │ ├── components/
│ │ │ ├── layout/
│ │ │ │ ├── Layout.vue
│ │ │ │ ├── Header.vue
│ │ │ │ └── Sidebar.vue
│ │ │ ├── chat/
│ │ │ │ ├── ChatView.vue
│ │ │ │ ├── MessageList.vue
│ │ │ │ ├── MessageItem.vue
│ │ │ │ ├── ChatInput.vue
│ │ │ │ └── TypingIndicator.vue
│ │ │ └── common/
│ │ │ ├── Loading.vue
│ │ │ └── ErrorMessage.vue
│ │ ├── stores/
│ │ │ ├── index.js
│ │ │ ├── chat.js
│ │ │ ├── conversation.js
│ │ │ └── user.js
│ │ ├── api/
│ │ │ ├── client.js
│ │ │ ├── chat.js
│ │ │ └── conversation.js
│ │ ├── router/
│ │ │ └── index.js
│ │ ├── styles/
│ │ │ ├── main.css
│ │ │ ├── variables.css
│ │ │ └── components.css
│ │ └── utils/
│ │ ├── helpers.js
│ │ └── constants.js
│ └── public/
│ └── favicon.ico
└── backend/
├── package.json
├── app.js
├── server.js
├── config/
│ ├── database.js
│ └── llm.js
├── routes/
│ ├── index.js
│ ├── chat.js
│ └── conversations.js
├── services/
│ ├── ConversationService.js
│ ├── ChatService.js
│ └── LLMApiClient.js
├── models/
│ ├── database.js
│ ├── Conversation.js
│ └── Message.js
├── middleware/
│ ├── auth.js
│ ├── rateLimit.js
│ ├── logger.js
│ └── errorHandler.js
├── utils/
│ ├── cache.js
│ └── helpers.js
└── database/
├── schema.sql
└── chat.db
测试策略
前端测试
单元测试 (Vitest)
- 组件逻辑测试
- Store状态管理测试
- 工具函数测试
组件测试 (Vue Test Utils)
- 组件渲染测试
- 用户交互测试
- Props和Events测试
E2E测试 (Playwright)
- 完整对话流程测试
- 跨浏览器兼容性测试
- 移动端响应式测试
后端测试
单元测试 (Jest)
API端点测试
describe('Chat API', () => {
test('should send message successfully', async () => {
const response = await request(app)
.post('/api/chat/send')
.send({
conversationId: 1,
message: '你好'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('response');
});
});
服务层测试
describe('ChatService', () => {
test('should process message correctly', async () => {
const chatService = new ChatService(mockApiClient, mockCache);
const result = await chatService.processMessage(1, '测试消息');
expect(result).toHaveProperty('userMessage');
expect(result).toHaveProperty('aiResponse');
});
});
集成测试
- 数据库操作测试
- 第三方API集成测试
- 缓存机制测试
性能测试
- API响应时间测试
- 并发请求处理测试
- 内存使用监控