fix(pkb): fix create KB and upload issues - remove simulated upload, fix department mapping, add upload modal
Fixed issues: - Remove simulateUpload function from DashboardPage Step 3 - Map department to description field when creating KB - Add upload modal in WorkspacePage knowledge assets tab - Fix DocumentUpload import path (../../stores to ../stores) Known issue: Dify API validation error during document upload (file uploaded but DB record failed, needs investigation) Testing: KB creation works, upload dialog opens correctly
This commit is contained in:
540
docs/03-业务模块/AIA-AI智能问答/04-开发计划/01-AIA-V2.1开发计划.md
Normal file
540
docs/03-业务模块/AIA-AI智能问答/04-开发计划/01-AIA-V2.1开发计划.md
Normal file
@@ -0,0 +1,540 @@
|
||||
# AIA 模块 V2.1 开发计划
|
||||
|
||||
> **版本**:V2.1
|
||||
> **创建日期**:2026-01-11
|
||||
> **计划周期**:约 8-11 天
|
||||
> **策略**:重写后端 + 复用数据库 + 新开发前端
|
||||
> **关联PRD**:`01-需求分析/AIA模块PRD.md`
|
||||
> **关联原型**:`01-需求分析/AI智能问答V2.html`
|
||||
|
||||
---
|
||||
|
||||
## 📋 开发策略概述
|
||||
|
||||
### 核心决策
|
||||
|
||||
| 组件 | 策略 | 理由 |
|
||||
|------|------|------|
|
||||
| **后端** | 🔴 **重写** | 旧版不符合云原生规范(console.log、未使用平台能力) |
|
||||
| **数据库** | ✅ **复用** | aia_schema 已完善,字段满足需求 |
|
||||
| **前端** | 🔴 **新开发** | 复用 shared/components/Chat 通用组件,全新UI |
|
||||
|
||||
### 技术规范
|
||||
|
||||
| 规范 | 要求 |
|
||||
|------|------|
|
||||
| **日志** | 使用 `logger` from `@/common/logging`(禁止 console.log) |
|
||||
| **存储** | 使用 `storage` from `@/common/storage` |
|
||||
| **缓存** | 使用 `cache` from `@/common/cache` |
|
||||
| **队列** | 使用 `jobQueue` from `@/common/jobs` |
|
||||
| **Prompt** | 使用 `promptService.get()` from `@/common/prompts` |
|
||||
| **代码位置** | `backend/src/modules/aia/` |
|
||||
| **API路由** | `/api/v2/aia/*` |
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库状态(✅ 已完成)
|
||||
|
||||
### 表结构(3个表)
|
||||
|
||||
```
|
||||
aia_schema.projects ✅ 保留
|
||||
aia_schema.conversations ✅ 保留
|
||||
aia_schema.messages ✅ 保留 + 新增字段
|
||||
```
|
||||
|
||||
### 新增字段(2026-01-11 已迁移)
|
||||
|
||||
| 表 | 字段 | 类型 | 说明 |
|
||||
|----|------|------|------|
|
||||
| messages | `thinking_content` | TEXT | 深度思考内容 `<think>...</think>` |
|
||||
| messages | `attachments` | JSONB | 附件数组(上限5个,单个≤20MB,文本≤30K tokens) |
|
||||
|
||||
### 已删除表
|
||||
|
||||
| 表 | 原因 |
|
||||
|----|------|
|
||||
| `general_conversations` | 功能重叠,使用 conversations.project_id = NULL |
|
||||
| `general_messages` | 功能重叠 |
|
||||
|
||||
---
|
||||
|
||||
## 🗓️ 开发阶段
|
||||
|
||||
### Phase 1: 后端重写(3-4天)
|
||||
|
||||
#### Day 1: 模块骨架 + 核心服务
|
||||
|
||||
**目标**:搭建模块结构,迁移核心对话服务
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **1.1 创建模块目录结构**
|
||||
```
|
||||
backend/src/modules/aia/
|
||||
├── controllers/
|
||||
│ ├── conversationController.ts
|
||||
│ ├── agentController.ts
|
||||
│ └── projectController.ts
|
||||
├── services/
|
||||
│ ├── conversationService.ts
|
||||
│ ├── agentService.ts
|
||||
│ └── projectService.ts
|
||||
├── routes/
|
||||
│ └── index.ts
|
||||
├── types/
|
||||
│ └── index.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
- [ ] **1.2 重写 conversationService**
|
||||
- 从 legacy 复制核心逻辑
|
||||
- 替换 `console.log` → `logger`
|
||||
- 使用 `prisma.message`(已在 aia_schema)
|
||||
- 添加 `thinkingContent` 处理逻辑
|
||||
- 保持流式输出能力
|
||||
|
||||
- [ ] **1.3 重写 agentService**
|
||||
- 改用 `promptService.get()` 获取 Prompt
|
||||
- 缓存智能体配置(使用 `cache`)
|
||||
|
||||
- [ ] **1.4 注册 v2 路由**
|
||||
- 注册到 `/api/v2/aia/*`
|
||||
- 保持 legacy 路由兼容(逐步迁移)
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 基础对话功能可用
|
||||
- [ ] 流式输出正常
|
||||
- [ ] 日志输出到 logger
|
||||
|
||||
---
|
||||
|
||||
#### Day 2: 深度思考 + 附件上传
|
||||
|
||||
**目标**:实现 V2.1 新增功能
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **2.1 深度思考模式**
|
||||
- 检测 LLM 输出中的 `<think>...</think>` 标签
|
||||
- 提取并存储到 `messages.thinking_content`
|
||||
- 从 `content` 中移除 think 标签
|
||||
- 流式输出时分离 thinking 和 content
|
||||
|
||||
- [ ] **2.2 附件上传服务**
|
||||
- 使用 `storage.upload()` 上传到 OSS
|
||||
- 调用 Python 微服务提取文本
|
||||
- Token 计数(使用 tiktoken)
|
||||
- 截断处理(超过 30K tokens)
|
||||
- 存储附件信息到 `messages.attachments`
|
||||
|
||||
- [ ] **2.3 附件注入 LLM 上下文**
|
||||
- 组装附件文本到 User Prompt
|
||||
- 控制总 Token 长度
|
||||
|
||||
**技术规格**:
|
||||
|
||||
```typescript
|
||||
// 附件处理配置
|
||||
const ATTACHMENT_CONFIG = {
|
||||
maxCount: 5, // 每条消息最多5个附件
|
||||
maxSizePerFile: 20 * 1024 * 1024, // 单个文件20MB
|
||||
maxTokens: 30000, // 提取文本最多30K tokens
|
||||
supportedTypes: ['pdf', 'docx', 'txt', 'xlsx'],
|
||||
};
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 深度思考内容正确分离存储
|
||||
- [ ] 附件上传成功
|
||||
- [ ] 附件文本正确注入 LLM
|
||||
|
||||
---
|
||||
|
||||
#### Day 3: 意图路由 + 知识库集成
|
||||
|
||||
**目标**:实现全局意图路由,完善知识库引用
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **3.1 意图路由服务**
|
||||
- 新建 `intentRouterService.ts`
|
||||
- 调用 Router Agent 识别意图
|
||||
- 返回目标 Agent ID + 预填 Prompt
|
||||
- 添加 500ms 防抖(前端实现)
|
||||
|
||||
- [ ] **3.2 完善知识库集成**
|
||||
- 复用 PKB 模块的 RAG 检索
|
||||
- 智能引用系统([来源N])
|
||||
- 引用清单格式化
|
||||
|
||||
- [ ] **3.3 API 端点完善**
|
||||
```
|
||||
POST /api/v2/aia/intent/route # 意图路由
|
||||
POST /api/v2/aia/conversations # 创建对话
|
||||
GET /api/v2/aia/conversations # 对话列表
|
||||
GET /api/v2/aia/conversations/:id # 对话详情
|
||||
POST /api/v2/aia/conversations/:id/messages/stream # 发送消息(流式)
|
||||
POST /api/v2/aia/conversations/:id/attachments # 上传附件
|
||||
GET /api/v2/aia/agents # 智能体列表
|
||||
GET /api/v2/aia/agents/:id # 智能体详情
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 意图路由正确识别并跳转
|
||||
- [ ] 知识库引用正确显示
|
||||
- [ ] 所有 API 端点可用
|
||||
|
||||
---
|
||||
|
||||
#### Day 4: 测试 + 文档
|
||||
|
||||
**目标**:完成后端测试和文档
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **4.1 单元测试**
|
||||
- conversationService 测试
|
||||
- 深度思考解析测试
|
||||
- 附件处理测试
|
||||
|
||||
- [ ] **4.2 集成测试**
|
||||
- 完整对话流程
|
||||
- 附件上传流程
|
||||
- 知识库检索流程
|
||||
|
||||
- [ ] **4.3 API 文档**
|
||||
- 更新 REST Client 测试文件
|
||||
- 编写 API 使用示例
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 测试覆盖核心功能
|
||||
- [ ] API 文档完整
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: 前端开发(5-7天)
|
||||
|
||||
#### Day 5-6: 智能体大厅(Dashboard)
|
||||
|
||||
**目标**:实现首页智能体大厅
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **5.1 创建模块目录结构**
|
||||
```
|
||||
frontend-v2/src/modules/aia/
|
||||
├── pages/
|
||||
│ ├── Dashboard.tsx # 智能体大厅
|
||||
│ └── Workspace.tsx # 对话工作台
|
||||
├── components/
|
||||
│ ├── AgentPipeline.tsx # 5阶段流水线
|
||||
│ ├── AgentCard.tsx # 智能体卡片
|
||||
│ ├── IntentSearch.tsx # 意图搜索框
|
||||
│ ├── ConversationList.tsx # 历史会话列表
|
||||
│ ├── ThinkingBlock.tsx # 深度思考折叠块
|
||||
│ ├── AttachmentUpload.tsx # 附件上传
|
||||
│ ├── AttachmentCard.tsx # 附件卡片
|
||||
│ └── SlashCommands.tsx # 快捷指令
|
||||
├── hooks/
|
||||
│ ├── useConversation.ts
|
||||
│ ├── useAgents.ts
|
||||
│ └── useIntentRouter.ts
|
||||
├── api/
|
||||
│ └── index.ts
|
||||
├── types/
|
||||
│ └── index.ts
|
||||
└── index.tsx
|
||||
```
|
||||
|
||||
- [ ] **5.2 智能体流水线(AgentPipeline)**
|
||||
- 5阶段布局(严格还原 V11 原型)
|
||||
- 3色视觉体系
|
||||
- 卡片点击跳转 Workspace
|
||||
- 工具卡片跳转外部模块(DC/ST)
|
||||
|
||||
- [ ] **5.3 意图搜索框(IntentSearch)**
|
||||
- 顶部大搜索框
|
||||
- 500ms 防抖
|
||||
- 调用意图路由 API
|
||||
- 自动跳转目标 Agent
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 5阶段流水线正确展示
|
||||
- [ ] 意图搜索功能可用
|
||||
- [ ] 与原型图一致
|
||||
|
||||
---
|
||||
|
||||
#### Day 7-8: 对话工作台(Workspace)
|
||||
|
||||
**目标**:实现沉浸式对话界面
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **7.1 工作台布局**
|
||||
- Gemini 风格(大留白、少分割线)
|
||||
- 左侧侧边栏(历史会话,可折叠)
|
||||
- 主对话区(Header + 消息列表 + 输入框)
|
||||
|
||||
- [ ] **7.2 复用 Chat 通用组件**
|
||||
```tsx
|
||||
import { ChatContainer } from '@/shared/components/Chat';
|
||||
|
||||
<ChatContainer
|
||||
providerConfig={{
|
||||
apiEndpoint: '/api/v2/aia/conversations/:id/messages/stream',
|
||||
requestFn: sendMessageWithStream,
|
||||
}}
|
||||
customMessageRenderer={renderAIAMessage}
|
||||
/>
|
||||
```
|
||||
|
||||
- [ ] **7.3 深度思考折叠块(ThinkingBlock)**
|
||||
- 可折叠灰色引用块
|
||||
- 生成中展开,完成后自动收起
|
||||
- 显示"已深度思考 (耗时 Xs)"
|
||||
|
||||
- [ ] **7.4 附件上传组件**
|
||||
- 支持拖拽上传
|
||||
- 文件类型/大小校验
|
||||
- 上传进度显示
|
||||
- 消息气泡下方附件卡片
|
||||
|
||||
- [ ] **7.5 历史会话列表**
|
||||
- 按时间分组(今天、昨天、7天前)
|
||||
- 桌面端固定显示
|
||||
- 移动端抽屉滑出
|
||||
|
||||
**验收标准**:
|
||||
- [ ] Gemini 风格 UI
|
||||
- [ ] 深度思考正确展示
|
||||
- [ ] 附件上传完整流程
|
||||
- [ ] 历史会话切换
|
||||
|
||||
---
|
||||
|
||||
#### Day 9: Markdown 增强 + 快捷指令
|
||||
|
||||
**目标**:增强对话体验
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **9.1 Markdown 渲染增强**
|
||||
- KaTeX 公式渲染(医学公式)
|
||||
- 表格横向滚动
|
||||
- 代码块语法高亮 + 一键复制
|
||||
|
||||
- [ ] **9.2 快捷指令(SlashCommands)**
|
||||
- 输入 `/` 弹出菜单
|
||||
- 支持:/润色, /扩写, /翻译, /导出Word
|
||||
- 键盘导航
|
||||
|
||||
- [ ] **9.3 结果操作栏**
|
||||
- Hover 显示工具栏
|
||||
- 复制(Markdown 源码)
|
||||
- 重新生成
|
||||
- 导出 Word(调用 RVW 导出服务)
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 公式正确渲染
|
||||
- [ ] 快捷指令可用
|
||||
- [ ] 操作栏功能完整
|
||||
|
||||
---
|
||||
|
||||
#### Day 10: 移动端适配
|
||||
|
||||
**目标**:响应式布局适配
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **10.1 Dashboard 移动端**
|
||||
- 隐藏复杂导航
|
||||
- 卡片单列流式布局
|
||||
- 时间轴样式调整
|
||||
|
||||
- [ ] **10.2 Workspace 移动端**
|
||||
- 侧边栏改为抽屉
|
||||
- 输入框键盘适配(scrollIntoView)
|
||||
- 发送按钮始终可见
|
||||
|
||||
- [ ] **10.3 触控优化**
|
||||
- 按钮 active 态
|
||||
- 触控区域优化
|
||||
|
||||
**验收标准**:
|
||||
- [ ] md (768px) 断点响应正确
|
||||
- [ ] 移动端交互流畅
|
||||
|
||||
---
|
||||
|
||||
#### Day 11: 集成测试 + 优化
|
||||
|
||||
**目标**:完成整体测试和优化
|
||||
|
||||
**任务清单**:
|
||||
|
||||
- [ ] **11.1 端到端测试**
|
||||
- 完整对话流程
|
||||
- 附件上传流程
|
||||
- 深度思考流程
|
||||
- 知识库引用流程
|
||||
|
||||
- [ ] **11.2 性能优化**
|
||||
- TTFB < 1.5s
|
||||
- 移动端 LCP < 1s
|
||||
- 意图搜索防抖
|
||||
|
||||
- [ ] **11.3 Bug 修复**
|
||||
|
||||
- [ ] **11.4 文档更新**
|
||||
- 更新模块状态文档
|
||||
- 更新系统当前状态
|
||||
|
||||
**验收标准**:
|
||||
- [ ] 所有功能正常
|
||||
- [ ] 性能达标
|
||||
- [ ] 文档完整
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件清单
|
||||
|
||||
### 后端新增文件
|
||||
|
||||
```
|
||||
backend/src/modules/aia/
|
||||
├── controllers/
|
||||
│ ├── conversationController.ts # ~300行
|
||||
│ ├── agentController.ts # ~150行
|
||||
│ └── projectController.ts # ~200行
|
||||
├── services/
|
||||
│ ├── conversationService.ts # ~500行(核心)
|
||||
│ ├── agentService.ts # ~200行
|
||||
│ ├── projectService.ts # ~150行
|
||||
│ ├── intentRouterService.ts # ~100行(新)
|
||||
│ └── attachmentService.ts # ~200行(新)
|
||||
├── routes/
|
||||
│ └── index.ts # ~100行
|
||||
├── types/
|
||||
│ └── index.ts # ~100行
|
||||
└── index.ts # ~20行
|
||||
|
||||
预计总计:~2000行
|
||||
```
|
||||
|
||||
### 前端新增文件
|
||||
|
||||
```
|
||||
frontend-v2/src/modules/aia/
|
||||
├── pages/
|
||||
│ ├── Dashboard.tsx # ~400行
|
||||
│ └── Workspace.tsx # ~500行
|
||||
├── components/
|
||||
│ ├── AgentPipeline.tsx # ~300行
|
||||
│ ├── AgentCard.tsx # ~100行
|
||||
│ ├── IntentSearch.tsx # ~150行
|
||||
│ ├── ConversationList.tsx # ~200行
|
||||
│ ├── ThinkingBlock.tsx # ~100行
|
||||
│ ├── AttachmentUpload.tsx # ~200行
|
||||
│ ├── AttachmentCard.tsx # ~80行
|
||||
│ └── SlashCommands.tsx # ~150行
|
||||
├── hooks/
|
||||
│ ├── useConversation.ts # ~150行
|
||||
│ ├── useAgents.ts # ~100行
|
||||
│ └── useIntentRouter.ts # ~80行
|
||||
├── api/
|
||||
│ └── index.ts # ~200行
|
||||
├── types/
|
||||
│ └── index.ts # ~100行
|
||||
└── index.tsx # ~50行
|
||||
|
||||
预计总计:~2900行
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 依赖关系
|
||||
|
||||
### 后端依赖
|
||||
|
||||
| 依赖 | 来源 | 说明 |
|
||||
|------|------|------|
|
||||
| `logger` | `@/common/logging` | 日志服务 |
|
||||
| `storage` | `@/common/storage` | OSS存储 |
|
||||
| `cache` | `@/common/cache` | Redis/PG缓存 |
|
||||
| `jobQueue` | `@/common/jobs` | 异步任务 |
|
||||
| `promptService` | `@/common/prompts` | Prompt管理 |
|
||||
| `LLMFactory` | `@/common/llm` | LLM适配器 |
|
||||
| `prisma` | `@/config/database` | 数据库 |
|
||||
| `ExtractionClient` | `@/clients` | 文档提取 |
|
||||
| `TokenService` | `@/services` | Token计数 |
|
||||
|
||||
### 前端依赖
|
||||
|
||||
| 依赖 | 来源 | 说明 |
|
||||
|------|------|------|
|
||||
| `ChatContainer` | `@/shared/components/Chat` | 通用对话组件 |
|
||||
| `Ant Design` | `antd` | UI组件库 |
|
||||
| `Ant Design X` | `@ant-design/x` | AI对话组件 |
|
||||
| `KaTeX` | `katex` | 公式渲染 |
|
||||
| `react-markdown` | `react-markdown` | Markdown渲染 |
|
||||
|
||||
---
|
||||
|
||||
## 📈 风险评估
|
||||
|
||||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||
|------|------|------|---------|
|
||||
| 流式输出兼容性 | 中 | 高 | 复用已验证的 SSE 代码 |
|
||||
| 附件提取超时 | 中 | 中 | 使用 jobQueue 异步处理 |
|
||||
| 移动端适配问题 | 低 | 中 | 提前规划断点和布局 |
|
||||
| Prompt管理服务未就绪 | 低 | 中 | 可临时回退到文件读取 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
### 功能验收
|
||||
|
||||
- [ ] 智能体大厅完整展示
|
||||
- [ ] 意图搜索正确路由
|
||||
- [ ] 多轮对话正常
|
||||
- [ ] 流式输出流畅
|
||||
- [ ] 深度思考正确折叠
|
||||
- [ ] 附件上传完整
|
||||
- [ ] 知识库引用正确
|
||||
- [ ] 快捷指令可用
|
||||
- [ ] 移动端适配正常
|
||||
|
||||
### 性能验收
|
||||
|
||||
- [ ] TTFB < 1.5s
|
||||
- [ ] 移动端 LCP < 1s
|
||||
- [ ] 意图搜索响应 < 500ms
|
||||
|
||||
### 质量验收
|
||||
|
||||
- [ ] 无 console.log(使用 logger)
|
||||
- [ ] 代码符合云原生规范
|
||||
- [ ] API 文档完整
|
||||
- [ ] 单元测试覆盖核心功能
|
||||
|
||||
---
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
| 日期 | 版本 | 内容 |
|
||||
|------|------|------|
|
||||
| 2026-01-11 | V1.0 | 创建开发计划 |
|
||||
|
||||
---
|
||||
|
||||
**计划制定人**:AI Assistant
|
||||
**审核人**:待定
|
||||
**批准人**:待定
|
||||
|
||||
|
||||
|
||||
|
||||
568
docs/03-业务模块/AIA-AI智能问答/04-开发计划/02-后端API设计.md
Normal file
568
docs/03-业务模块/AIA-AI智能问答/04-开发计划/02-后端API设计.md
Normal file
@@ -0,0 +1,568 @@
|
||||
# AIA V2.1 后端 API 设计
|
||||
|
||||
> **版本**:V2.1
|
||||
> **创建日期**:2026-01-11
|
||||
> **基础路径**:`/api/v2/aia`
|
||||
|
||||
---
|
||||
|
||||
## 📋 API 概览
|
||||
|
||||
| 方法 | 路径 | 描述 | 认证 |
|
||||
|------|------|------|------|
|
||||
| GET | `/agents` | 获取智能体列表 | ✅ |
|
||||
| GET | `/agents/:id` | 获取智能体详情 | ✅ |
|
||||
| POST | `/intent/route` | 意图路由 | ✅ |
|
||||
| GET | `/conversations` | 获取对话列表 | ✅ |
|
||||
| POST | `/conversations` | 创建对话 | ✅ |
|
||||
| GET | `/conversations/:id` | 获取对话详情 | ✅ |
|
||||
| DELETE | `/conversations/:id` | 删除对话 | ✅ |
|
||||
| POST | `/conversations/:id/messages/stream` | 发送消息(流式) | ✅ |
|
||||
| POST | `/conversations/:id/attachments` | 上传附件 | ✅ |
|
||||
| GET | `/projects` | 获取项目列表 | ✅ |
|
||||
| GET | `/projects/:id` | 获取项目详情 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 🔐 认证
|
||||
|
||||
所有 API 需要在请求头中携带 JWT Token:
|
||||
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 API 详细定义
|
||||
|
||||
### 1. 智能体相关
|
||||
|
||||
#### 1.1 获取智能体列表
|
||||
|
||||
```http
|
||||
GET /api/v2/aia/agents
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 描述 |
|
||||
|------|------|------|------|
|
||||
| stage | string | 否 | 筛选阶段:`design`, `data`, `analysis`, `write`, `publish` |
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"agents": [
|
||||
{
|
||||
"id": "research-design",
|
||||
"name": "科研设计小助手",
|
||||
"description": "帮助您完成研究方案设计、文献检索、方法学指导",
|
||||
"icon": "🔬",
|
||||
"stage": "design",
|
||||
"color": "#3B82F6",
|
||||
"knowledgeBaseId": "kb-001",
|
||||
"isTool": false
|
||||
},
|
||||
{
|
||||
"id": "dc-tool",
|
||||
"name": "数据采集工具",
|
||||
"description": "跳转到数据采集模块",
|
||||
"icon": "📊",
|
||||
"stage": "data",
|
||||
"color": "#8B5CF6",
|
||||
"targetModule": "/data-collection",
|
||||
"isTool": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 获取智能体详情
|
||||
|
||||
```http
|
||||
GET /api/v2/aia/agents/:id
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "research-design",
|
||||
"name": "科研设计小助手",
|
||||
"description": "帮助您完成研究方案设计、文献检索、方法学指导",
|
||||
"icon": "🔬",
|
||||
"stage": "design",
|
||||
"color": "#3B82F6",
|
||||
"knowledgeBaseId": "kb-001",
|
||||
"systemPrompt": "你是一个专业的医学科研设计专家...",
|
||||
"welcomeMessage": "您好!我是科研设计小助手,我可以帮您:\n- 设计研究方案\n- 检索相关文献\n- 指导研究方法",
|
||||
"suggestedQuestions": [
|
||||
"如何设计一个RCT研究?",
|
||||
"帮我检索近5年糖尿病研究文献",
|
||||
"什么情况下使用倾向性评分匹配?"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 意图路由
|
||||
|
||||
#### 2.1 智能意图识别
|
||||
|
||||
```http
|
||||
POST /api/v2/aia/intent/route
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "帮我分析一下这份数据"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"agentId": "data-analysis",
|
||||
"agentName": "统计分析小助手",
|
||||
"confidence": 0.92,
|
||||
"prefillPrompt": "请帮我分析这份数据,包括描述性统计和相关性分析"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 对话管理
|
||||
|
||||
#### 3.1 获取对话列表
|
||||
|
||||
```http
|
||||
GET /api/v2/aia/conversations
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 描述 |
|
||||
|------|------|------|------|
|
||||
| agentId | string | 否 | 按智能体筛选 |
|
||||
| projectId | string | 否 | 按项目筛选(NULL 表示通用对话) |
|
||||
| page | number | 否 | 页码,默认 1 |
|
||||
| pageSize | number | 否 | 每页数量,默认 20 |
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"conversations": [
|
||||
{
|
||||
"id": "conv-001",
|
||||
"title": "RCT研究设计咨询",
|
||||
"agentId": "research-design",
|
||||
"agentName": "科研设计小助手",
|
||||
"projectId": null,
|
||||
"messageCount": 12,
|
||||
"lastMessage": "好的,我来帮您设计研究方案...",
|
||||
"createdAt": "2026-01-11T10:00:00Z",
|
||||
"updatedAt": "2026-01-11T12:30:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total": 25,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 创建对话
|
||||
|
||||
```http
|
||||
POST /api/v2/aia/conversations
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"agentId": "research-design",
|
||||
"projectId": null,
|
||||
"title": "新对话"
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "conv-002",
|
||||
"title": "新对话",
|
||||
"agentId": "research-design",
|
||||
"projectId": null,
|
||||
"createdAt": "2026-01-11T14:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 获取对话详情(含历史消息)
|
||||
|
||||
```http
|
||||
GET /api/v2/aia/conversations/:id
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 描述 |
|
||||
|------|------|------|------|
|
||||
| limit | number | 否 | 获取最近N条消息,默认 50 |
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "conv-001",
|
||||
"title": "RCT研究设计咨询",
|
||||
"agentId": "research-design",
|
||||
"agentName": "科研设计小助手",
|
||||
"projectId": null,
|
||||
"createdAt": "2026-01-11T10:00:00Z",
|
||||
"updatedAt": "2026-01-11T12:30:00Z",
|
||||
"messages": [
|
||||
{
|
||||
"id": "msg-001",
|
||||
"role": "user",
|
||||
"content": "帮我设计一个关于糖尿病的RCT研究",
|
||||
"attachments": [],
|
||||
"createdAt": "2026-01-11T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "msg-002",
|
||||
"role": "assistant",
|
||||
"content": "好的,我来帮您设计一个糖尿病RCT研究方案...",
|
||||
"thinkingContent": "用户想设计RCT研究,需要考虑:1)研究目的 2)入排标准 3)样本量 4)随机化方法 5)盲法 6)结局指标...",
|
||||
"model": "deepseek-v3",
|
||||
"tokens": 1250,
|
||||
"createdAt": "2026-01-11T10:00:30Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 删除对话
|
||||
|
||||
```http
|
||||
DELETE /api/v2/aia/conversations/:id
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"deleted": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 消息发送(流式)
|
||||
|
||||
#### 4.1 发送消息并获取流式响应
|
||||
|
||||
```http
|
||||
POST /api/v2/aia/conversations/:id/messages/stream
|
||||
```
|
||||
|
||||
**请求头**:
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
|
||||
```json
|
||||
{
|
||||
"content": "帮我分析这份数据",
|
||||
"attachmentIds": ["att-001", "att-002"],
|
||||
"enableDeepThinking": true
|
||||
}
|
||||
```
|
||||
|
||||
**响应(SSE 格式)**:
|
||||
|
||||
```
|
||||
event: thinking_start
|
||||
data: {}
|
||||
|
||||
event: thinking_delta
|
||||
data: {"content": "用户上传了数据文件,需要"}
|
||||
|
||||
event: thinking_delta
|
||||
data: {"content": "进行描述性统计分析..."}
|
||||
|
||||
event: thinking_end
|
||||
data: {"duration": 3200}
|
||||
|
||||
event: message_start
|
||||
data: {"id": "msg-003"}
|
||||
|
||||
event: delta
|
||||
data: {"content": "根据您上传的数据,"}
|
||||
|
||||
event: delta
|
||||
data: {"content": "我来为您进行分析..."}
|
||||
|
||||
event: message_end
|
||||
data: {"id": "msg-003", "tokens": 850, "model": "deepseek-v3"}
|
||||
|
||||
event: done
|
||||
data: {}
|
||||
```
|
||||
|
||||
**SSE 事件类型**:
|
||||
|
||||
| 事件 | 描述 | 数据格式 |
|
||||
|------|------|---------|
|
||||
| `thinking_start` | 开始深度思考 | `{}` |
|
||||
| `thinking_delta` | 思考内容片段 | `{"content": "..."}` |
|
||||
| `thinking_end` | 思考结束 | `{"duration": number}` |
|
||||
| `message_start` | 开始生成回复 | `{"id": "..."}` |
|
||||
| `delta` | 回复内容片段 | `{"content": "..."}` |
|
||||
| `message_end` | 回复结束 | `{"id": "...", "tokens": number, "model": "..."}` |
|
||||
| `error` | 发生错误 | `{"code": "...", "message": "..."}` |
|
||||
| `done` | 流结束 | `{}` |
|
||||
|
||||
---
|
||||
|
||||
### 5. 附件上传
|
||||
|
||||
#### 5.1 上传附件
|
||||
|
||||
```http
|
||||
POST /api/v2/aia/conversations/:id/attachments
|
||||
```
|
||||
|
||||
**请求头**:
|
||||
|
||||
```
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
**请求体(FormData)**:
|
||||
|
||||
| 字段 | 类型 | 必填 | 描述 |
|
||||
|------|------|------|------|
|
||||
| file | File | 是 | 文件(PDF/Word/TXT/Excel,最大20MB) |
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": "att-001",
|
||||
"filename": "研究数据.xlsx",
|
||||
"mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"size": 1024000,
|
||||
"ossUrl": "https://oss.example.com/attachments/att-001.xlsx",
|
||||
"textExtracted": true,
|
||||
"tokenCount": 15000,
|
||||
"truncated": false,
|
||||
"createdAt": "2026-01-11T14:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误码**:
|
||||
|
||||
| 错误码 | 描述 |
|
||||
|--------|------|
|
||||
| `ATTACHMENT_TOO_LARGE` | 文件超过 20MB |
|
||||
| `ATTACHMENT_TYPE_NOT_SUPPORTED` | 不支持的文件类型 |
|
||||
| `ATTACHMENT_LIMIT_EXCEEDED` | 附件数量超过上限(5个) |
|
||||
| `TEXT_EXTRACTION_FAILED` | 文本提取失败 |
|
||||
|
||||
---
|
||||
|
||||
### 6. 项目管理
|
||||
|
||||
#### 6.1 获取项目列表
|
||||
|
||||
```http
|
||||
GET /api/v2/aia/projects
|
||||
```
|
||||
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"projects": [
|
||||
{
|
||||
"id": "proj-001",
|
||||
"name": "糖尿病研究项目",
|
||||
"description": "2型糖尿病患者生活方式干预研究",
|
||||
"conversationCount": 5,
|
||||
"createdAt": "2026-01-01T10:00:00Z",
|
||||
"updatedAt": "2026-01-11T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 错误处理
|
||||
|
||||
### 错误响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -1,
|
||||
"error": {
|
||||
"code": "CONVERSATION_NOT_FOUND",
|
||||
"message": "对话不存在"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 通用错误码
|
||||
|
||||
| 错误码 | HTTP 状态码 | 描述 |
|
||||
|--------|------------|------|
|
||||
| `UNAUTHORIZED` | 401 | 未授权 |
|
||||
| `FORBIDDEN` | 403 | 无权限 |
|
||||
| `NOT_FOUND` | 404 | 资源不存在 |
|
||||
| `VALIDATION_ERROR` | 400 | 参数验证失败 |
|
||||
| `INTERNAL_ERROR` | 500 | 服务器内部错误 |
|
||||
| `LLM_ERROR` | 500 | LLM 调用失败 |
|
||||
| `RATE_LIMITED` | 429 | 请求过于频繁 |
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据模型
|
||||
|
||||
### Attachment(附件)
|
||||
|
||||
```typescript
|
||||
interface Attachment {
|
||||
id: string; // 附件ID
|
||||
filename: string; // 原始文件名
|
||||
mimeType: string; // MIME 类型
|
||||
size: number; // 文件大小(字节)
|
||||
ossUrl: string; // OSS 存储地址
|
||||
textContent?: string; // 提取的文本内容(存储时截断)
|
||||
tokenCount: number; // 文本 Token 数
|
||||
truncated: boolean; // 是否被截断(超过30K tokens)
|
||||
createdAt: string; // 创建时间
|
||||
}
|
||||
```
|
||||
|
||||
### Message(消息)
|
||||
|
||||
```typescript
|
||||
interface Message {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
thinkingContent?: string; // 深度思考内容
|
||||
attachments?: Attachment[];
|
||||
model?: string;
|
||||
tokens?: number;
|
||||
isPinned: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试示例(REST Client)
|
||||
|
||||
```http
|
||||
### 获取智能体列表
|
||||
GET {{baseUrl}}/api/v2/aia/agents
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 意图路由
|
||||
POST {{baseUrl}}/api/v2/aia/intent/route
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"query": "帮我分析数据"
|
||||
}
|
||||
|
||||
### 创建对话
|
||||
POST {{baseUrl}}/api/v2/aia/conversations
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"agentId": "research-design",
|
||||
"title": "测试对话"
|
||||
}
|
||||
|
||||
### 发送消息(流式)
|
||||
POST {{baseUrl}}/api/v2/aia/conversations/{{conversationId}}/messages/stream
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
Accept: text/event-stream
|
||||
|
||||
{
|
||||
"content": "帮我设计一个RCT研究",
|
||||
"enableDeepThinking": true
|
||||
}
|
||||
|
||||
### 上传附件
|
||||
POST {{baseUrl}}/api/v2/aia/conversations/{{conversationId}}/attachments
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
|
||||
|
||||
------WebKitFormBoundary
|
||||
Content-Disposition: form-data; name="file"; filename="data.xlsx"
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
|
||||
< ./data.xlsx
|
||||
------WebKitFormBoundary--
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
| 日期 | 版本 | 内容 |
|
||||
|------|------|------|
|
||||
| 2026-01-11 | V1.0 | 创建 API 设计文档 |
|
||||
|
||||
|
||||
|
||||
|
||||
883
docs/03-业务模块/AIA-AI智能问答/04-开发计划/03-前端组件设计.md
Normal file
883
docs/03-业务模块/AIA-AI智能问答/04-开发计划/03-前端组件设计.md
Normal file
@@ -0,0 +1,883 @@
|
||||
# AIA V2.1 前端组件设计
|
||||
|
||||
> **版本**:V2.1
|
||||
> **创建日期**:2026-01-11
|
||||
> **技术栈**:React 19 + TypeScript 5 + Ant Design 6 + Ant Design X 2.1
|
||||
|
||||
---
|
||||
|
||||
## 📁 模块结构
|
||||
|
||||
```
|
||||
frontend-v2/src/modules/aia/
|
||||
├── pages/
|
||||
│ ├── Dashboard.tsx # 智能体大厅(首页)
|
||||
│ └── Workspace.tsx # 对话工作台
|
||||
├── components/
|
||||
│ ├── AgentPipeline/
|
||||
│ │ ├── index.tsx # 5阶段流水线容器
|
||||
│ │ ├── StageColumn.tsx # 单阶段列
|
||||
│ │ └── AgentCard.tsx # 智能体卡片
|
||||
│ ├── IntentSearch/
|
||||
│ │ ├── index.tsx # 意图搜索框
|
||||
│ │ └── SuggestionDropdown.tsx # 建议下拉框
|
||||
│ ├── ConversationList/
|
||||
│ │ ├── index.tsx # 历史会话列表
|
||||
│ │ └── ConversationItem.tsx # 会话项
|
||||
│ ├── MessageList/
|
||||
│ │ ├── index.tsx # 消息列表
|
||||
│ │ ├── UserMessage.tsx # 用户消息
|
||||
│ │ ├── AssistantMessage.tsx # AI回复
|
||||
│ │ └── ThinkingBlock.tsx # 深度思考折叠块
|
||||
│ ├── Attachment/
|
||||
│ │ ├── AttachmentUpload.tsx # 附件上传
|
||||
│ │ ├── AttachmentCard.tsx # 附件卡片
|
||||
│ │ └── AttachmentPreview.tsx # 附件预览
|
||||
│ ├── SlashCommands/
|
||||
│ │ └── index.tsx # 快捷指令菜单
|
||||
│ └── ActionBar/
|
||||
│ └── index.tsx # 结果操作栏
|
||||
├── hooks/
|
||||
│ ├── useConversation.ts # 对话管理
|
||||
│ ├── useAgents.ts # 智能体数据
|
||||
│ ├── useIntentRouter.ts # 意图路由
|
||||
│ ├── useStreamMessage.ts # 流式消息
|
||||
│ └── useAttachment.ts # 附件上传
|
||||
├── api/
|
||||
│ └── index.ts # API 封装
|
||||
├── types/
|
||||
│ └── index.ts # TypeScript 类型
|
||||
├── styles/
|
||||
│ ├── dashboard.module.css # Dashboard 样式
|
||||
│ └── workspace.module.css # Workspace 样式
|
||||
└── index.tsx # 模块入口 + 路由
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 设计规范
|
||||
|
||||
### 色彩系统
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 5阶段流水线主题色 */
|
||||
--stage-design: #3B82F6; /* 蓝色 - 研究设计 */
|
||||
--stage-data: #8B5CF6; /* 紫色 - 数据采集 */
|
||||
--stage-analysis: #10B981; /* 绿色 - 统计分析 */
|
||||
--stage-write: #F59E0B; /* 橙色 - 论文撰写 */
|
||||
--stage-publish: #EF4444; /* 红色 - 成果发布 */
|
||||
|
||||
/* 功能色 */
|
||||
--ai-assistant: #6366F1; /* AI助手主色 */
|
||||
--thinking-bg: #F3F4F6; /* 思考块背景 */
|
||||
--thinking-border: #E5E7EB; /* 思考块边框 */
|
||||
|
||||
/* Gemini 风格 */
|
||||
--bg-primary: #FFFFFF;
|
||||
--bg-secondary: #F9FAFB;
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #6B7280;
|
||||
--border-light: #E5E7EB;
|
||||
}
|
||||
```
|
||||
|
||||
### 间距系统
|
||||
|
||||
```css
|
||||
/* 遵循 8px 网格 */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--spacing-2xl: 48px;
|
||||
```
|
||||
|
||||
### 断点
|
||||
|
||||
```css
|
||||
/* 移动优先 */
|
||||
--breakpoint-sm: 640px; /* 手机 */
|
||||
--breakpoint-md: 768px; /* 平板 */
|
||||
--breakpoint-lg: 1024px; /* 桌面 */
|
||||
--breakpoint-xl: 1280px; /* 大屏 */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 页面设计
|
||||
|
||||
### 1. Dashboard(智能体大厅)
|
||||
|
||||
```tsx
|
||||
// pages/Dashboard.tsx
|
||||
|
||||
import { IntentSearch } from '../components/IntentSearch';
|
||||
import { AgentPipeline } from '../components/AgentPipeline';
|
||||
|
||||
export const Dashboard: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.dashboard}>
|
||||
{/* 顶部意图搜索框 */}
|
||||
<header className={styles.header}>
|
||||
<h1>AI 智能助手</h1>
|
||||
<p>有什么可以帮助您的?</p>
|
||||
<IntentSearch />
|
||||
</header>
|
||||
|
||||
{/* 5阶段智能体流水线 */}
|
||||
<main className={styles.main}>
|
||||
<AgentPipeline />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**布局特点**:
|
||||
- 顶部居中大搜索框
|
||||
- 5阶段流水线横向平铺(桌面)/ 纵向滚动(移动)
|
||||
- Gemini 风格大留白
|
||||
|
||||
---
|
||||
|
||||
### 2. Workspace(对话工作台)
|
||||
|
||||
```tsx
|
||||
// pages/Workspace.tsx
|
||||
|
||||
import { ChatContainer } from '@/shared/components/Chat';
|
||||
import { ConversationList } from '../components/ConversationList';
|
||||
import { ThinkingBlock } from '../components/MessageList/ThinkingBlock';
|
||||
import { AttachmentUpload } from '../components/Attachment/AttachmentUpload';
|
||||
|
||||
export const Workspace: React.FC = () => {
|
||||
const { conversationId } = useParams();
|
||||
const { conversation, messages, sendMessage } = useConversation(conversationId);
|
||||
|
||||
return (
|
||||
<div className={styles.workspace}>
|
||||
{/* 左侧边栏 - 历史会话 */}
|
||||
<aside className={styles.sidebar}>
|
||||
<ConversationList />
|
||||
</aside>
|
||||
|
||||
{/* 主对话区 */}
|
||||
<main className={styles.main}>
|
||||
{/* 对话头部 */}
|
||||
<header className={styles.header}>
|
||||
<AgentAvatar agent={conversation?.agent} />
|
||||
<h2>{conversation?.agent?.name}</h2>
|
||||
</header>
|
||||
|
||||
{/* 消息列表 - 复用通用 Chat 组件 */}
|
||||
<ChatContainer
|
||||
messages={messages}
|
||||
onSend={sendMessage}
|
||||
renderMessage={(msg) => (
|
||||
<div className={styles.message}>
|
||||
{/* 深度思考块 */}
|
||||
{msg.thinkingContent && (
|
||||
<ThinkingBlock content={msg.thinkingContent} />
|
||||
)}
|
||||
{/* 消息内容 */}
|
||||
<MarkdownRenderer content={msg.content} />
|
||||
{/* 附件卡片 */}
|
||||
{msg.attachments?.map(att => (
|
||||
<AttachmentCard key={att.id} attachment={att} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
inputFooter={<AttachmentUpload />}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 组件详细设计
|
||||
|
||||
### 1. AgentPipeline(5阶段流水线)
|
||||
|
||||
```tsx
|
||||
// components/AgentPipeline/index.tsx
|
||||
|
||||
interface AgentPipelineProps {
|
||||
onAgentClick: (agentId: string) => void;
|
||||
}
|
||||
|
||||
export const AgentPipeline: React.FC<AgentPipelineProps> = ({ onAgentClick }) => {
|
||||
const { agents } = useAgents();
|
||||
|
||||
const stages = [
|
||||
{ key: 'design', title: '研究设计', color: 'var(--stage-design)' },
|
||||
{ key: 'data', title: '数据采集', color: 'var(--stage-data)' },
|
||||
{ key: 'analysis', title: '统计分析', color: 'var(--stage-analysis)' },
|
||||
{ key: 'write', title: '论文撰写', color: 'var(--stage-write)' },
|
||||
{ key: 'publish', title: '成果发布', color: 'var(--stage-publish)' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.pipeline}>
|
||||
{stages.map((stage, index) => (
|
||||
<StageColumn
|
||||
key={stage.key}
|
||||
title={stage.title}
|
||||
color={stage.color}
|
||||
agents={agents.filter(a => a.stage === stage.key)}
|
||||
onAgentClick={onAgentClick}
|
||||
showConnector={index < stages.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**样式特点**:
|
||||
```css
|
||||
.pipeline {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
overflow-x: auto;
|
||||
padding: var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
/* 移动端纵向布局 */
|
||||
@media (max-width: 768px) {
|
||||
.pipeline {
|
||||
flex-direction: column;
|
||||
overflow-x: visible;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. IntentSearch(意图搜索框)
|
||||
|
||||
```tsx
|
||||
// components/IntentSearch/index.tsx
|
||||
|
||||
export const IntentSearch: React.FC = () => {
|
||||
const [query, setQuery] = useState('');
|
||||
const [suggestions, setSuggestions] = useState<IntentSuggestion[]>([]);
|
||||
const { routeIntent, isLoading } = useIntentRouter();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 500ms 防抖
|
||||
const debouncedQuery = useDebouncedValue(query, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedQuery.length >= 2) {
|
||||
routeIntent(debouncedQuery).then(setSuggestions);
|
||||
}
|
||||
}, [debouncedQuery]);
|
||||
|
||||
const handleSelect = (suggestion: IntentSuggestion) => {
|
||||
// 跳转到对应智能体
|
||||
navigate(`/aia/workspace?agent=${suggestion.agentId}&prompt=${encodeURIComponent(suggestion.prefillPrompt)}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.searchContainer}>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder="描述您的需求,AI 将为您推荐合适的助手..."
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
loading={isLoading}
|
||||
className={styles.searchInput}
|
||||
/>
|
||||
|
||||
{suggestions.length > 0 && (
|
||||
<SuggestionDropdown
|
||||
suggestions={suggestions}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. ThinkingBlock(深度思考折叠块)
|
||||
|
||||
```tsx
|
||||
// components/MessageList/ThinkingBlock.tsx
|
||||
|
||||
interface ThinkingBlockProps {
|
||||
content: string;
|
||||
duration?: number; // 思考耗时(秒)
|
||||
isStreaming?: boolean; // 是否正在生成
|
||||
}
|
||||
|
||||
export const ThinkingBlock: React.FC<ThinkingBlockProps> = ({
|
||||
content,
|
||||
duration,
|
||||
isStreaming = false,
|
||||
}) => {
|
||||
// 生成中展开,完成后自动收起
|
||||
const [expanded, setExpanded] = useState(isStreaming);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isStreaming && expanded) {
|
||||
// 完成后 1.5s 自动收起
|
||||
const timer = setTimeout(() => setExpanded(false), 1500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isStreaming]);
|
||||
|
||||
return (
|
||||
<div className={styles.thinkingBlock}>
|
||||
<div
|
||||
className={styles.header}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
<span className={styles.icon}>
|
||||
{isStreaming ? <LoadingOutlined spin /> : <BulbOutlined />}
|
||||
</span>
|
||||
<span className={styles.title}>
|
||||
{isStreaming ? '正在深度思考...' : `已深度思考 (耗时 ${duration?.toFixed(1)}s)`}
|
||||
</span>
|
||||
<span className={styles.expandIcon}>
|
||||
{expanded ? <UpOutlined /> : <DownOutlined />}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{expanded && (
|
||||
<div className={styles.content}>
|
||||
<Typography.Text type="secondary">
|
||||
{content}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**样式**:
|
||||
```css
|
||||
.thinkingBlock {
|
||||
background: var(--thinking-bg);
|
||||
border: 1px solid var(--thinking-border);
|
||||
border-radius: 8px;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--spacing-md);
|
||||
padding-top: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. AttachmentUpload(附件上传)
|
||||
|
||||
```tsx
|
||||
// components/Attachment/AttachmentUpload.tsx
|
||||
|
||||
interface AttachmentUploadProps {
|
||||
conversationId: string;
|
||||
onUploadComplete: (attachment: Attachment) => void;
|
||||
maxCount?: number; // 默认 5
|
||||
}
|
||||
|
||||
export const AttachmentUpload: React.FC<AttachmentUploadProps> = ({
|
||||
conversationId,
|
||||
onUploadComplete,
|
||||
maxCount = 5,
|
||||
}) => {
|
||||
const { uploadFile, uploading, progress } = useAttachment();
|
||||
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
||||
|
||||
const handleUpload = async (file: File) => {
|
||||
if (attachments.length >= maxCount) {
|
||||
message.error(`最多上传 ${maxCount} 个附件`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 文件类型校验
|
||||
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
message.error('仅支持 PDF、Word、TXT、Excel 文件');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 文件大小校验(20MB)
|
||||
if (file.size > 20 * 1024 * 1024) {
|
||||
message.error('文件大小不能超过 20MB');
|
||||
return false;
|
||||
}
|
||||
|
||||
const attachment = await uploadFile(conversationId, file);
|
||||
setAttachments([...attachments, attachment]);
|
||||
onUploadComplete(attachment);
|
||||
return false; // 阻止默认上传
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.uploadContainer}>
|
||||
<Upload
|
||||
beforeUpload={handleUpload}
|
||||
showUploadList={false}
|
||||
accept=".pdf,.docx,.txt,.xlsx"
|
||||
multiple
|
||||
>
|
||||
<Button icon={<PaperClipOutlined />} type="text">
|
||||
添加附件
|
||||
</Button>
|
||||
</Upload>
|
||||
|
||||
{/* 已上传附件列表 */}
|
||||
<div className={styles.attachmentList}>
|
||||
{attachments.map(att => (
|
||||
<AttachmentCard
|
||||
key={att.id}
|
||||
attachment={att}
|
||||
onRemove={() => {
|
||||
setAttachments(attachments.filter(a => a.id !== att.id));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 上传进度 */}
|
||||
{uploading && (
|
||||
<Progress percent={progress} size="small" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. SlashCommands(快捷指令)
|
||||
|
||||
```tsx
|
||||
// components/SlashCommands/index.tsx
|
||||
|
||||
interface SlashCommandsProps {
|
||||
onSelect: (command: SlashCommand) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const commands: SlashCommand[] = [
|
||||
{ key: 'polish', icon: '✨', label: '润色', description: '优化文本表达' },
|
||||
{ key: 'expand', icon: '📝', label: '扩写', description: '扩展内容细节' },
|
||||
{ key: 'translate', icon: '🌐', label: '翻译', description: '中英互译' },
|
||||
{ key: 'export', icon: '📄', label: '导出Word', description: '导出为 Word 文档' },
|
||||
];
|
||||
|
||||
export const SlashCommands: React.FC<SlashCommandsProps> = ({
|
||||
onSelect,
|
||||
onClose,
|
||||
}) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
// 键盘导航
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
setSelectedIndex(i => Math.max(0, i - 1));
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
setSelectedIndex(i => Math.min(commands.length - 1, i + 1));
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
onSelect(commands[selectedIndex]);
|
||||
break;
|
||||
case 'Escape':
|
||||
onClose();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [selectedIndex]);
|
||||
|
||||
return (
|
||||
<div className={styles.commandMenu}>
|
||||
{commands.map((cmd, index) => (
|
||||
<div
|
||||
key={cmd.key}
|
||||
className={`${styles.commandItem} ${index === selectedIndex ? styles.selected : ''}`}
|
||||
onClick={() => onSelect(cmd)}
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
>
|
||||
<span className={styles.icon}>{cmd.icon}</span>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.label}>{cmd.label}</div>
|
||||
<div className={styles.description}>{cmd.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. ActionBar(结果操作栏)
|
||||
|
||||
```tsx
|
||||
// components/ActionBar/index.tsx
|
||||
|
||||
interface ActionBarProps {
|
||||
message: Message;
|
||||
onCopy: () => void;
|
||||
onRegenerate: () => void;
|
||||
onExport: () => void;
|
||||
}
|
||||
|
||||
export const ActionBar: React.FC<ActionBarProps> = ({
|
||||
message,
|
||||
onCopy,
|
||||
onRegenerate,
|
||||
onExport,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.actionBar}>
|
||||
<Tooltip title="复制">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={onCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="重新生成">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={onRegenerate}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="导出 Word">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<FileWordOutlined />}
|
||||
onClick={onExport}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎣 Hooks 设计
|
||||
|
||||
### useConversation
|
||||
|
||||
```typescript
|
||||
// hooks/useConversation.ts
|
||||
|
||||
interface UseConversationReturn {
|
||||
conversation: Conversation | null;
|
||||
messages: Message[];
|
||||
isLoading: boolean;
|
||||
sendMessage: (content: string, attachmentIds?: string[]) => Promise<void>;
|
||||
regenerate: (messageId: string) => Promise<void>;
|
||||
deleteConversation: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useConversation(conversationId?: string): UseConversationReturn {
|
||||
const [conversation, setConversation] = useState<Conversation | null>(null);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// 加载对话
|
||||
useEffect(() => {
|
||||
if (conversationId) {
|
||||
api.getConversation(conversationId).then(data => {
|
||||
setConversation(data);
|
||||
setMessages(data.messages);
|
||||
});
|
||||
}
|
||||
}, [conversationId]);
|
||||
|
||||
// 发送消息(流式)
|
||||
const sendMessage = async (content: string, attachmentIds?: string[]) => {
|
||||
setIsLoading(true);
|
||||
|
||||
// 添加用户消息
|
||||
const userMessage: Message = {
|
||||
id: `temp-${Date.now()}`,
|
||||
role: 'user',
|
||||
content,
|
||||
attachments: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
setMessages(prev => [...prev, userMessage]);
|
||||
|
||||
// 初始化 AI 消息
|
||||
const aiMessage: Message = {
|
||||
id: `temp-ai-${Date.now()}`,
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
thinkingContent: '',
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
setMessages(prev => [...prev, aiMessage]);
|
||||
|
||||
// 流式接收
|
||||
await api.sendMessageStream(conversationId!, content, attachmentIds, {
|
||||
onThinkingDelta: (delta) => {
|
||||
setMessages(prev => {
|
||||
const last = prev[prev.length - 1];
|
||||
return [...prev.slice(0, -1), {
|
||||
...last,
|
||||
thinkingContent: (last.thinkingContent || '') + delta,
|
||||
}];
|
||||
});
|
||||
},
|
||||
onDelta: (delta) => {
|
||||
setMessages(prev => {
|
||||
const last = prev[prev.length - 1];
|
||||
return [...prev.slice(0, -1), {
|
||||
...last,
|
||||
content: last.content + delta,
|
||||
}];
|
||||
});
|
||||
},
|
||||
onComplete: (finalMessage) => {
|
||||
setMessages(prev => {
|
||||
return [...prev.slice(0, -1), finalMessage];
|
||||
});
|
||||
setIsLoading(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(error.message);
|
||||
setIsLoading(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
conversation,
|
||||
messages,
|
||||
isLoading,
|
||||
sendMessage,
|
||||
regenerate: async () => {},
|
||||
deleteConversation: async () => {},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### useStreamMessage
|
||||
|
||||
```typescript
|
||||
// hooks/useStreamMessage.ts
|
||||
|
||||
interface StreamCallbacks {
|
||||
onThinkingStart?: () => void;
|
||||
onThinkingDelta?: (content: string) => void;
|
||||
onThinkingEnd?: (duration: number) => void;
|
||||
onMessageStart?: (id: string) => void;
|
||||
onDelta?: (content: string) => void;
|
||||
onMessageEnd?: (message: Message) => void;
|
||||
onComplete?: (message: Message) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
export function useStreamMessage() {
|
||||
const streamMessage = async (
|
||||
url: string,
|
||||
body: object,
|
||||
callbacks: StreamCallbacks
|
||||
) => {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader!.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value);
|
||||
const events = parseSSE(chunk);
|
||||
|
||||
for (const event of events) {
|
||||
switch (event.type) {
|
||||
case 'thinking_start':
|
||||
callbacks.onThinkingStart?.();
|
||||
break;
|
||||
case 'thinking_delta':
|
||||
callbacks.onThinkingDelta?.(event.data.content);
|
||||
break;
|
||||
case 'thinking_end':
|
||||
callbacks.onThinkingEnd?.(event.data.duration);
|
||||
break;
|
||||
case 'message_start':
|
||||
callbacks.onMessageStart?.(event.data.id);
|
||||
break;
|
||||
case 'delta':
|
||||
callbacks.onDelta?.(event.data.content);
|
||||
break;
|
||||
case 'message_end':
|
||||
callbacks.onMessageEnd?.(event.data);
|
||||
break;
|
||||
case 'done':
|
||||
callbacks.onComplete?.(event.data);
|
||||
break;
|
||||
case 'error':
|
||||
callbacks.onError?.(new Error(event.data.message));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { streamMessage };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 响应式设计
|
||||
|
||||
### 断点策略
|
||||
|
||||
```typescript
|
||||
// 断点定义
|
||||
const breakpoints = {
|
||||
sm: 640, // 手机
|
||||
md: 768, // 平板(主要断点)
|
||||
lg: 1024, // 桌面
|
||||
xl: 1280, // 大屏
|
||||
};
|
||||
```
|
||||
|
||||
### Dashboard 响应式
|
||||
|
||||
| 断点 | 布局 |
|
||||
|------|------|
|
||||
| `< 768px` | 流水线纵向滚动,卡片单列 |
|
||||
| `≥ 768px` | 流水线横向 5 列 |
|
||||
|
||||
### Workspace 响应式
|
||||
|
||||
| 断点 | 布局 |
|
||||
|------|------|
|
||||
| `< 768px` | 侧边栏隐藏(抽屉滑出),输入框键盘适配 |
|
||||
| `≥ 768px` | 侧边栏固定显示 240px |
|
||||
|
||||
---
|
||||
|
||||
## 📝 类型定义
|
||||
|
||||
```typescript
|
||||
// types/index.ts
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
stage: 'design' | 'data' | 'analysis' | 'write' | 'publish';
|
||||
color: string;
|
||||
knowledgeBaseId?: string;
|
||||
isTool?: boolean;
|
||||
targetModule?: string;
|
||||
welcomeMessage?: string;
|
||||
suggestedQuestions?: string[];
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string;
|
||||
title: string;
|
||||
agentId: string;
|
||||
agent?: Agent;
|
||||
projectId?: string;
|
||||
messageCount: number;
|
||||
lastMessage?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
conversationId?: string;
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
thinkingContent?: string;
|
||||
attachments?: Attachment[];
|
||||
model?: string;
|
||||
tokens?: number;
|
||||
isPinned?: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Attachment {
|
||||
id: string;
|
||||
filename: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
ossUrl: string;
|
||||
textExtracted: boolean;
|
||||
tokenCount: number;
|
||||
truncated: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface IntentSuggestion {
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
confidence: number;
|
||||
prefillPrompt: string;
|
||||
}
|
||||
|
||||
export interface SlashCommand {
|
||||
key: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
| 日期 | 版本 | 内容 |
|
||||
|------|------|------|
|
||||
| 2026-01-11 | V1.0 | 创建前端组件设计文档 |
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user