667 lines
14 KiB
Markdown
667 lines
14 KiB
Markdown
# 前端项目架构设计文档 (DESIGN)
|
||
|
||
## 任务名称
|
||
frontend-vue3
|
||
|
||
## 整体架构图
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "前端应用 (Vue3)"
|
||
A[App.vue] --> B[Vue Router]
|
||
B --> C1[Models 模型管理]
|
||
B --> C2[KnowledgeBase 知识库]
|
||
B --> C3[Conversations 对话列表]
|
||
B --> C4[Chat 聊天页]
|
||
B --> C5[Agent 执行页]
|
||
|
||
C1 --> D1[API: models.js]
|
||
C2 --> D2[API: kb.js]
|
||
C3 --> D3[API: conversations.js]
|
||
C4 --> D3
|
||
C5 --> D4[API: agent.js]
|
||
|
||
D1 --> E[Axios Client]
|
||
D2 --> E
|
||
D3 --> E
|
||
D4 --> E
|
||
end
|
||
|
||
E -->|HTTP Request| F[FastAPI Backend<br/>:8000/api/v1]
|
||
|
||
subgraph "公共层"
|
||
G[Error Handler<br/>错误拦截器]
|
||
H[Loading State<br/>加载状态]
|
||
I[Message Tips<br/>消息提示]
|
||
end
|
||
|
||
E --> G
|
||
E --> H
|
||
E --> I
|
||
|
||
style A fill:#e1f5fe
|
||
style E fill:#fff3e0
|
||
style F fill:#f3e5f5
|
||
style G fill:#ffebee
|
||
style H fill:#e8f5e9
|
||
style I fill:#fff9c4
|
||
```
|
||
|
||
## 系统分层设计
|
||
|
||
### 1. 展示层 (Views)
|
||
**职责**: 页面渲染、用户交互、状态展示
|
||
|
||
**核心页面**:
|
||
- `Models.vue`: 模型管理页
|
||
- `KnowledgeBase.vue`: 知识库管理页
|
||
- `Conversations.vue`: 对话列表页
|
||
- `Chat.vue`: 聊天界面
|
||
- `Agent.vue`: Agent 执行页
|
||
|
||
**页面设计规范**:
|
||
- 使用 Composition API (`<script setup>`)
|
||
- 表单使用 Element Plus Form 组件
|
||
- 数据表格使用 ElTable
|
||
- 弹窗使用 ElDialog
|
||
|
||
### 2. 服务层 (API)
|
||
**职责**: 封装 HTTP 请求、数据转换、错误处理
|
||
|
||
**API 模块**:
|
||
```javascript
|
||
// src/api/client.js - Axios 实例
|
||
axios.create({
|
||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||
timeout: 30000,
|
||
headers: { 'Content-Type': 'application/json' }
|
||
})
|
||
|
||
// src/api/models.js - 模型管理
|
||
export const modelAPI = {
|
||
list(type?: string), // GET /models
|
||
create(data), // POST /models
|
||
getById(id), // GET /models/:id
|
||
update(id, data), // PATCH /models/:id
|
||
delete(id) // DELETE /models/:id
|
||
}
|
||
|
||
// src/api/kb.js - 知识库
|
||
export const kbAPI = {
|
||
list(), // GET /kb
|
||
create(data), // POST /kb
|
||
getById(id), // GET /kb/:id
|
||
delete(id), // DELETE /kb/:id
|
||
ingest(id, data), // POST /kb/:id/ingest
|
||
query(id, data), // POST /kb/:id/query
|
||
getStatus(id) // GET /kb/:id/status
|
||
}
|
||
|
||
// src/api/conversations.js - 对话
|
||
export const conversationAPI = {
|
||
create(data), // POST /conversations
|
||
getById(id), // GET /conversations/:id
|
||
delete(id), // DELETE /conversations/:id
|
||
getMessages(id, params), // GET /conversations/:id/messages
|
||
chat(id, data) // POST /conversations/:id/chat
|
||
}
|
||
|
||
// src/api/agent.js - Agent
|
||
export const agentAPI = {
|
||
execute(data), // POST /agent/execute
|
||
getLogs(agentId, params) // GET /agent/logs/:agent_id
|
||
}
|
||
```
|
||
|
||
### 3. 路由层 (Router)
|
||
**职责**: 页面导航、路由守卫、参数传递
|
||
|
||
**路由配置**:
|
||
```javascript
|
||
// src/router/index.js
|
||
const routes = [
|
||
{
|
||
path: '/',
|
||
redirect: '/models'
|
||
},
|
||
{
|
||
path: '/models',
|
||
name: 'Models',
|
||
component: () => import('@/views/Models.vue')
|
||
},
|
||
{
|
||
path: '/knowledge-base',
|
||
name: 'KnowledgeBase',
|
||
component: () => import('@/views/KnowledgeBase.vue')
|
||
},
|
||
{
|
||
path: '/conversations',
|
||
name: 'Conversations',
|
||
component: () => import('@/views/Conversations.vue')
|
||
},
|
||
{
|
||
path: '/conversations/:id/chat',
|
||
name: 'Chat',
|
||
component: () => import('@/views/Chat.vue'),
|
||
props: true
|
||
},
|
||
{
|
||
path: '/agent',
|
||
name: 'Agent',
|
||
component: () => import('@/views/Agent.vue')
|
||
}
|
||
]
|
||
```
|
||
|
||
### 4. 工具层 (Utils)
|
||
**职责**: 通用函数、常量定义、辅助工具
|
||
|
||
**工具模块**:
|
||
```javascript
|
||
// src/utils/request.js - 请求拦截器
|
||
// src/utils/message.js - 消息提示封装
|
||
// src/utils/validator.js - 表单验证规则
|
||
// src/utils/constants.js - 常量定义
|
||
```
|
||
|
||
## 核心组件设计
|
||
|
||
### Component 1: Models.vue (模型管理)
|
||
|
||
**功能模块**:
|
||
1. 模型列表展示(表格)
|
||
2. 类型筛选(LLM / Embedding)
|
||
3. 创建模型弹窗
|
||
4. 编辑/删除操作
|
||
5. 设置默认模型
|
||
|
||
**数据流**:
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as User
|
||
participant V as Models.vue
|
||
participant API as modelAPI
|
||
participant B as Backend
|
||
|
||
U->>V: 访问页面
|
||
V->>API: modelAPI.list()
|
||
API->>B: GET /models
|
||
B-->>API: 模型列表
|
||
API-->>V: 渲染表格
|
||
|
||
U->>V: 点击"创建模型"
|
||
V->>V: 打开表单弹窗
|
||
U->>V: 填写表单并提交
|
||
V->>API: modelAPI.create(data)
|
||
API->>B: POST /models
|
||
B-->>API: 创建成功
|
||
API-->>V: 更新列表
|
||
V->>U: 提示成功
|
||
```
|
||
|
||
**核心状态**:
|
||
```javascript
|
||
const modelList = ref([])
|
||
const loading = ref(false)
|
||
const dialogVisible = ref(false)
|
||
const formData = ref({
|
||
name: '',
|
||
type: 'llm',
|
||
config: {},
|
||
is_default: false
|
||
})
|
||
const typeFilter = ref('')
|
||
```
|
||
|
||
### Component 2: KnowledgeBase.vue (知识库管理)
|
||
|
||
**功能模块**:
|
||
1. 知识库列表
|
||
2. 创建知识库
|
||
3. 文档摄取(多文档输入)
|
||
4. 向量查询
|
||
5. 状态查看(文档数、索引状态)
|
||
6. 删除知识库
|
||
|
||
**文档摄取界面**:
|
||
- 支持动态添加多个文档
|
||
- 每个文档包含:title, content, source, metadata
|
||
- 选择 embedding 模型
|
||
- 提交后显示成功(不追踪状态)
|
||
|
||
**查询界面**:
|
||
- 输入查询文本
|
||
- 选择 k 值(返回数量)
|
||
- 展示结果列表(content + score)
|
||
|
||
**核心状态**:
|
||
```javascript
|
||
const kbList = ref([])
|
||
const currentKb = ref(null)
|
||
const ingestDialogVisible = ref(false)
|
||
const queryDialogVisible = ref(false)
|
||
const documents = ref([{ title: '', content: '', source: '', metadata: {} }])
|
||
const queryForm = ref({ query: '', k: 3 })
|
||
const queryResults = ref([])
|
||
```
|
||
|
||
### Component 3: Conversations.vue (对话列表)
|
||
|
||
**功能模块**:
|
||
1. 会话列表展示
|
||
2. 创建新会话(user_id + title)
|
||
3. 跳转到聊天页
|
||
4. 删除会话
|
||
|
||
**核心状态**:
|
||
```javascript
|
||
const conversationList = ref([])
|
||
const createDialogVisible = ref(false)
|
||
const newConversation = ref({ user_id: 1, title: '' })
|
||
```
|
||
|
||
### Component 4: Chat.vue (聊天界面)
|
||
|
||
**功能模块**:
|
||
1. 消息历史展示(分页加载)
|
||
2. 发送消息
|
||
3. RAG 开关
|
||
4. 选择知识库和 LLM
|
||
5. 显示来源(sources)
|
||
|
||
**分页加载策略**:
|
||
- 初始加载: limit=50, offset=0
|
||
- "加载更多"按钮: offset += 50
|
||
- 消息逆序展示(最新在下)
|
||
|
||
**消息展示**:
|
||
```vue
|
||
<div class="message-list">
|
||
<div v-for="msg in messages" :key="msg.id" :class="msg.role">
|
||
<div class="content">{{ msg.content }}</div>
|
||
<div v-if="msg.msg_metadata?.sources" class="sources">
|
||
来源: {{ msg.msg_metadata.sources }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**核心状态**:
|
||
```javascript
|
||
const conversationId = ref(route.params.id)
|
||
const messages = ref([])
|
||
const inputText = ref('')
|
||
const useRag = ref(true)
|
||
const selectedKbId = ref(null)
|
||
const selectedLlm = ref(null)
|
||
const pagination = ref({ limit: 50, offset: 0, hasMore: true })
|
||
```
|
||
|
||
### Component 5: Agent.vue (Agent 执行)
|
||
|
||
**功能模块**:
|
||
1. 任务输入
|
||
2. 选择知识库(可选)
|
||
3. 选择 LLM
|
||
4. 执行结果展示
|
||
5. 工具调用日志(自动展示)
|
||
|
||
**执行流程**:
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant U as User
|
||
participant V as Agent.vue
|
||
participant API as agentAPI
|
||
participant B as Backend
|
||
|
||
U->>V: 输入任务并提交
|
||
V->>API: agentAPI.execute(data)
|
||
API->>B: POST /agent/execute
|
||
B-->>API: { agent_id, output, success }
|
||
API-->>V: 展示结果
|
||
|
||
V->>API: agentAPI.getLogs(agent_id)
|
||
API->>B: GET /agent/logs/:agent_id
|
||
B-->>API: { tool_calls: [...] }
|
||
API-->>V: 展示日志表格
|
||
```
|
||
|
||
**核心状态**:
|
||
```javascript
|
||
const taskInput = ref('')
|
||
const selectedKbId = ref(null)
|
||
const selectedLlm = ref(null)
|
||
const executionResult = ref(null)
|
||
const toolCallLogs = ref([])
|
||
const executing = ref(false)
|
||
```
|
||
|
||
## 接口契约定义
|
||
|
||
### Request 数据结构
|
||
|
||
**创建模型**:
|
||
```typescript
|
||
interface CreateModelRequest {
|
||
name: string
|
||
type: 'llm' | 'embedding'
|
||
config: Record<string, any>
|
||
is_default?: boolean
|
||
}
|
||
```
|
||
|
||
**文档摄取**:
|
||
```typescript
|
||
interface IngestRequest {
|
||
documents: Array<{
|
||
content: string
|
||
title?: string
|
||
source?: string
|
||
metadata?: Record<string, any>
|
||
}>
|
||
embedding_name: string
|
||
background?: boolean // 固定为 true
|
||
}
|
||
```
|
||
|
||
**发送消息**:
|
||
```typescript
|
||
interface ChatRequest {
|
||
user_input: string
|
||
kb_id?: number
|
||
llm_name?: string
|
||
use_rag?: boolean
|
||
}
|
||
```
|
||
|
||
**执行 Agent**:
|
||
```typescript
|
||
interface AgentExecuteRequest {
|
||
task: string
|
||
kb_id?: number
|
||
llm_name?: string
|
||
}
|
||
```
|
||
|
||
### Response 数据结构
|
||
|
||
**模型列表**:
|
||
```typescript
|
||
interface ModelResponse {
|
||
id: number
|
||
name: string
|
||
type: 'llm' | 'embedding'
|
||
config: Record<string, any>
|
||
is_default: boolean
|
||
status: string
|
||
created_at: string
|
||
}
|
||
```
|
||
|
||
**查询结果**:
|
||
```typescript
|
||
interface QueryResponse {
|
||
results: Array<{
|
||
content: string
|
||
metadata: Record<string, any>
|
||
score: number
|
||
}>
|
||
result_count: number
|
||
}
|
||
```
|
||
|
||
**消息响应**:
|
||
```typescript
|
||
interface MessageResponse {
|
||
message_id: number
|
||
conversation_id: number
|
||
role: 'user' | 'assistant'
|
||
content: string
|
||
metadata?: {
|
||
sources?: Array<{ title: string, source: string }>
|
||
model?: string
|
||
}
|
||
}
|
||
```
|
||
|
||
**Agent 日志**:
|
||
```typescript
|
||
interface ToolCallLog {
|
||
id: number
|
||
agent_id: string
|
||
tool_name: string
|
||
call_input: Record<string, any>
|
||
call_output: Record<string, any>
|
||
created_at: string
|
||
}
|
||
```
|
||
|
||
## 异常处理策略
|
||
|
||
### HTTP 错误拦截
|
||
|
||
**Axios Response Interceptor**:
|
||
```javascript
|
||
// src/api/client.js
|
||
axios.interceptors.response.use(
|
||
response => response.data,
|
||
error => {
|
||
const { response } = error
|
||
|
||
if (!response) {
|
||
ElMessage.error('网络连接失败,请检查网络')
|
||
return Promise.reject(error)
|
||
}
|
||
|
||
const { status, data } = response
|
||
const message = data?.detail || '请求失败'
|
||
|
||
switch (status) {
|
||
case 400:
|
||
ElMessage.error(`请求错误: ${message}`)
|
||
break
|
||
case 404:
|
||
ElMessage.error(`资源不存在: ${message}`)
|
||
break
|
||
case 500:
|
||
ElMessage.error(`服务器错误: ${message}`)
|
||
break
|
||
default:
|
||
ElMessage.error(`错误 ${status}: ${message}`)
|
||
}
|
||
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
```
|
||
|
||
### 表单验证规则
|
||
|
||
**Element Plus Rules**:
|
||
```javascript
|
||
// src/utils/validator.js
|
||
export const modelRules = {
|
||
name: [
|
||
{ required: true, message: '请输入模型名称', trigger: 'blur' },
|
||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
||
],
|
||
type: [
|
||
{ required: true, message: '请选择模型类型', trigger: 'change' }
|
||
]
|
||
}
|
||
|
||
export const kbRules = {
|
||
name: [
|
||
{ required: true, message: '请输入知识库名称', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
export const chatRules = {
|
||
user_input: [
|
||
{ required: true, message: '请输入消息内容', trigger: 'blur' }
|
||
]
|
||
}
|
||
```
|
||
|
||
### 加载状态管理
|
||
|
||
**统一 Loading 处理**:
|
||
```javascript
|
||
// 组件内使用
|
||
const loading = ref(false)
|
||
|
||
const fetchData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const data = await modelAPI.list()
|
||
// 处理数据
|
||
} catch (error) {
|
||
// 错误已被拦截器处理
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
```
|
||
|
||
## 数据流向图
|
||
|
||
### 对话流程数据流
|
||
```mermaid
|
||
flowchart LR
|
||
A[用户输入消息] --> B{是否启用RAG?}
|
||
B -->|是| C[选择知识库]
|
||
B -->|否| D[直接发送]
|
||
C --> E[构造请求]
|
||
D --> E
|
||
E --> F[POST /conversations/:id/chat]
|
||
F --> G{请求成功?}
|
||
G -->|是| H[追加消息到列表]
|
||
G -->|否| I[显示错误]
|
||
H --> J[滚动到底部]
|
||
I --> K[用户重试]
|
||
```
|
||
|
||
### Agent 执行数据流
|
||
```mermaid
|
||
flowchart TB
|
||
A[输入任务] --> B[选择配置]
|
||
B --> C[POST /agent/execute]
|
||
C --> D{执行成功?}
|
||
D -->|是| E[显示输出]
|
||
D -->|否| F[显示错误]
|
||
E --> G[GET /agent/logs/:agent_id]
|
||
G --> H[展示工具调用日志]
|
||
H --> I[用户查看详情]
|
||
```
|
||
|
||
## 布局设计
|
||
|
||
### 主布局结构
|
||
```vue
|
||
<!-- App.vue -->
|
||
<el-container>
|
||
<el-aside width="200px">
|
||
<el-menu router>
|
||
<el-menu-item index="/models">模型管理</el-menu-item>
|
||
<el-menu-item index="/knowledge-base">知识库</el-menu-item>
|
||
<el-menu-item index="/conversations">对话</el-menu-item>
|
||
<el-menu-item index="/agent">Agent</el-menu-item>
|
||
</el-menu>
|
||
</el-aside>
|
||
<el-main>
|
||
<router-view />
|
||
</el-main>
|
||
</el-container>
|
||
```
|
||
|
||
### 聊天页布局
|
||
```vue
|
||
<!-- Chat.vue -->
|
||
<el-container direction="vertical">
|
||
<el-header>
|
||
<div class="chat-config">
|
||
<el-switch v-model="useRag" label="启用RAG" />
|
||
<el-select v-model="selectedKbId" v-if="useRag">...</el-select>
|
||
<el-select v-model="selectedLlm">...</el-select>
|
||
</div>
|
||
</el-header>
|
||
|
||
<el-main class="message-area">
|
||
<el-button @click="loadMore" v-if="hasMore">加载更多</el-button>
|
||
<div v-for="msg in messages" :key="msg.id">...</div>
|
||
</el-main>
|
||
|
||
<el-footer>
|
||
<el-input v-model="inputText" @keyup.enter="sendMessage" />
|
||
<el-button @click="sendMessage">发送</el-button>
|
||
</el-footer>
|
||
</el-container>
|
||
```
|
||
|
||
## 环境配置
|
||
|
||
### 开发环境
|
||
```bash
|
||
# .env.development
|
||
VITE_API_BASE_URL=http://localhost:8000/api/v1
|
||
```
|
||
|
||
### 生产环境
|
||
```bash
|
||
# .env.production
|
||
VITE_API_BASE_URL=https://your-production-domain.com/api/v1
|
||
```
|
||
|
||
### Vite 配置
|
||
```javascript
|
||
// vite.config.js
|
||
export default defineConfig({
|
||
plugins: [vue()],
|
||
server: {
|
||
port: 5173,
|
||
proxy: {
|
||
'/api': {
|
||
target: 'http://localhost:8000',
|
||
changeOrigin: true
|
||
}
|
||
}
|
||
},
|
||
resolve: {
|
||
alias: {
|
||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||
}
|
||
}
|
||
})
|
||
```
|
||
|
||
## 依赖管理
|
||
|
||
### 核心依赖
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"vue": "^3.4.0",
|
||
"vue-router": "^4.2.5",
|
||
"axios": "^1.6.2",
|
||
"element-plus": "^2.5.0",
|
||
"@element-plus/icons-vue": "^2.3.1"
|
||
},
|
||
"devDependencies": {
|
||
"@vitejs/plugin-vue": "^5.0.0",
|
||
"vite": "^5.0.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
## 设计原则总结
|
||
|
||
1. **单一职责**: 每个组件只负责一个功能模块
|
||
2. **API 封装**: 所有 HTTP 请求集中在 `src/api/`
|
||
3. **错误优先**: 统一拦截器 + 详细提示
|
||
4. **用户体验**: Loading 状态 + 操作反馈
|
||
5. **可维护性**: 清晰的目录结构 + 模块化设计
|
||
|
||
---
|
||
|
||
**架构设计完成,进入任务拆分阶段。**
|