feat: Day 10-11 - Agent Configuration System completed
Backend: - Create agents.yaml config file with 12 agents definition - Create Prompt templates for topic-evaluation agent - Implement agentService.ts for loading and managing agent configs - Create agentController.ts with CRUD operations - Create agent routes (GET /agents, /agents/:id, etc.) - Register agent routes in main server Frontend: - Create agentApi.ts service module - Update AgentChatPage to dynamically load agent config from API - Add loading state and error handling - Display agent details (description, category, model) Build: Both frontend and backend build successfully
This commit is contained in:
309
backend/config/agents.yaml
Normal file
309
backend/config/agents.yaml
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
# AI临床研究平台 - 智能体配置文件
|
||||||
|
# 版本: 1.0
|
||||||
|
# 更新日期: 2025-10-10
|
||||||
|
|
||||||
|
agents:
|
||||||
|
# ==================== 选题阶段 ====================
|
||||||
|
|
||||||
|
- id: topic-evaluation
|
||||||
|
name: 选题评价智能体
|
||||||
|
nameEn: Topic Evaluation Agent
|
||||||
|
description: 从创新性、临床价值、科学性和可行性等维度评估研究选题
|
||||||
|
category: 选题阶段
|
||||||
|
icon: 🎯
|
||||||
|
enabled: true
|
||||||
|
systemPromptFile: topic_evaluation_system.txt
|
||||||
|
userPromptTemplateFile: topic_evaluation_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 2000
|
||||||
|
topP: 0.9
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 2000
|
||||||
|
topP: 0.9
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 选题
|
||||||
|
- 评估
|
||||||
|
- 创新性
|
||||||
|
|
||||||
|
- id: scientific-question
|
||||||
|
name: 科学问题梳理智能体
|
||||||
|
nameEn: Scientific Question Agent
|
||||||
|
description: 将模糊的研究想法提炼成清晰、具体、可验证的科学问题
|
||||||
|
category: 选题阶段
|
||||||
|
icon: 🔬
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: scientific_question_system.txt
|
||||||
|
userPromptTemplateFile: scientific_question_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 1500
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.6
|
||||||
|
maxTokens: 1500
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 科学问题
|
||||||
|
- PICOS
|
||||||
|
|
||||||
|
- id: picos-construction
|
||||||
|
name: PICOS构建智能体
|
||||||
|
nameEn: PICOS Construction Agent
|
||||||
|
description: 按照PICOS原则结构化定义临床研究的核心要素
|
||||||
|
category: 选题阶段
|
||||||
|
icon: 📋
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: picos_construction_system.txt
|
||||||
|
userPromptTemplateFile: picos_construction_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.3
|
||||||
|
maxTokens: 1500
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 1500
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- PICOS
|
||||||
|
- 研究设计
|
||||||
|
|
||||||
|
# ==================== 研究设计阶段 ====================
|
||||||
|
|
||||||
|
- id: observation-design
|
||||||
|
name: 观察指标设计智能体
|
||||||
|
nameEn: Observation Design Agent
|
||||||
|
description: 推荐合适的主要、次要及安全性观察指标集
|
||||||
|
category: 研究设计阶段
|
||||||
|
icon: 📊
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: observation_design_system.txt
|
||||||
|
userPromptTemplateFile: observation_design_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 2000
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 2000
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 观察指标
|
||||||
|
- 终点事件
|
||||||
|
|
||||||
|
- id: crf-development
|
||||||
|
name: CRF制定智能体
|
||||||
|
nameEn: CRF Development Agent
|
||||||
|
description: 自动生成结构化、符合规范的病例报告表(CRF)框架
|
||||||
|
category: 研究设计阶段
|
||||||
|
icon: 📝
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: crf_development_system.txt
|
||||||
|
userPromptTemplateFile: crf_development_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.3
|
||||||
|
maxTokens: 3000
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 3000
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: document
|
||||||
|
tags:
|
||||||
|
- CRF
|
||||||
|
- 病例报告表
|
||||||
|
|
||||||
|
- id: sample-size
|
||||||
|
name: 样本量计算智能体
|
||||||
|
nameEn: Sample Size Calculation Agent
|
||||||
|
description: 根据研究参数提供科学合理的样本量估算结果
|
||||||
|
category: 研究设计阶段
|
||||||
|
icon: 🔢
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: sample_size_system.txt
|
||||||
|
userPromptTemplateFile: sample_size_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.2
|
||||||
|
maxTokens: 1500
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.3
|
||||||
|
maxTokens: 1500
|
||||||
|
ragEnabled: false
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 样本量
|
||||||
|
- 统计学
|
||||||
|
|
||||||
|
- id: protocol-writing
|
||||||
|
name: 临床研究方案撰写智能体
|
||||||
|
nameEn: Protocol Writing Agent
|
||||||
|
description: 自动生成结构完整的临床研究设计方案
|
||||||
|
category: 研究设计阶段
|
||||||
|
icon: 📄
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: protocol_writing_system.txt
|
||||||
|
userPromptTemplateFile: protocol_writing_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 4000
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.6
|
||||||
|
maxTokens: 4000
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: true
|
||||||
|
outputFormat: document
|
||||||
|
tags:
|
||||||
|
- 研究方案
|
||||||
|
- 文档生成
|
||||||
|
|
||||||
|
# ==================== 论文撰写阶段 ====================
|
||||||
|
|
||||||
|
- id: paper-polishing
|
||||||
|
name: 论文润色智能体
|
||||||
|
nameEn: Paper Polishing Agent
|
||||||
|
description: 提供专业级的语言润色,修正语法、拼写和表达方式
|
||||||
|
category: 论文撰写阶段
|
||||||
|
icon: ✨
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: paper_polishing_system.txt
|
||||||
|
userPromptTemplateFile: paper_polishing_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 3000
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 3000
|
||||||
|
ragEnabled: false
|
||||||
|
requiresProject: false
|
||||||
|
outputFormat: text
|
||||||
|
tags:
|
||||||
|
- 润色
|
||||||
|
- 语言优化
|
||||||
|
|
||||||
|
- id: paper-translation
|
||||||
|
name: 论文翻译智能体
|
||||||
|
nameEn: Paper Translation Agent
|
||||||
|
description: 提供专业、精准的中英互译服务
|
||||||
|
category: 论文撰写阶段
|
||||||
|
icon: 🌐
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: paper_translation_system.txt
|
||||||
|
userPromptTemplateFile: paper_translation_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.3
|
||||||
|
maxTokens: 3000
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 3000
|
||||||
|
ragEnabled: false
|
||||||
|
requiresProject: false
|
||||||
|
outputFormat: text
|
||||||
|
tags:
|
||||||
|
- 翻译
|
||||||
|
- 中英互译
|
||||||
|
|
||||||
|
- id: methodology-review
|
||||||
|
name: 方法学评审智能体
|
||||||
|
nameEn: Methodology Review Agent
|
||||||
|
description: 对研究方案或论文进行全面的方法学评审
|
||||||
|
category: 论文撰写阶段
|
||||||
|
icon: 🔍
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: methodology_review_system.txt
|
||||||
|
userPromptTemplateFile: methodology_review_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.5
|
||||||
|
maxTokens: 2500
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.6
|
||||||
|
maxTokens: 2500
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: false
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 方法学评审
|
||||||
|
- 质量控制
|
||||||
|
|
||||||
|
- id: journal-methodology-review
|
||||||
|
name: 期刊方法学评审智能体
|
||||||
|
nameEn: Journal Methodology Review Agent
|
||||||
|
description: 模拟期刊审稿人视角,进行方法学挑战
|
||||||
|
category: 论文撰写阶段
|
||||||
|
icon: 📑
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: journal_methodology_review_system.txt
|
||||||
|
userPromptTemplateFile: journal_methodology_review_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.6
|
||||||
|
maxTokens: 2500
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.7
|
||||||
|
maxTokens: 2500
|
||||||
|
ragEnabled: true
|
||||||
|
requiresProject: false
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 期刊审稿
|
||||||
|
- 方法学挑战
|
||||||
|
|
||||||
|
- id: journal-guidelines-review
|
||||||
|
name: 期刊稿约评审智能体
|
||||||
|
nameEn: Journal Guidelines Review Agent
|
||||||
|
description: 检查文章格式、字数、参考文献规范等是否符合投稿要求
|
||||||
|
category: 论文撰写阶段
|
||||||
|
icon: ✅
|
||||||
|
enabled: false
|
||||||
|
systemPromptFile: journal_guidelines_review_system.txt
|
||||||
|
userPromptTemplateFile: journal_guidelines_review_user.txt
|
||||||
|
models:
|
||||||
|
deepseek-v3:
|
||||||
|
temperature: 0.3
|
||||||
|
maxTokens: 2000
|
||||||
|
qwen3-72b:
|
||||||
|
temperature: 0.4
|
||||||
|
maxTokens: 2000
|
||||||
|
ragEnabled: false
|
||||||
|
requiresProject: false
|
||||||
|
outputFormat: structured
|
||||||
|
tags:
|
||||||
|
- 期刊投稿
|
||||||
|
- 格式检查
|
||||||
|
|
||||||
|
# 配置说明:
|
||||||
|
# - id: 智能体唯一标识符
|
||||||
|
# - name: 显示名称
|
||||||
|
# - nameEn: 英文名称
|
||||||
|
# - description: 功能描述
|
||||||
|
# - category: 所属阶段
|
||||||
|
# - icon: 显示图标
|
||||||
|
# - enabled: 是否启用(true/false)
|
||||||
|
# - systemPromptFile: 系统Prompt文件名
|
||||||
|
# - userPromptTemplateFile: 用户Prompt模板文件名
|
||||||
|
# - models: 支持的模型及参数配置
|
||||||
|
# - temperature: 温度参数(0-1)
|
||||||
|
# - maxTokens: 最大token数
|
||||||
|
# - topP: Top-p采样参数
|
||||||
|
# - ragEnabled: 是否支持知识库检索
|
||||||
|
# - requiresProject: 是否需要项目上下文
|
||||||
|
# - outputFormat: 输出格式(text/structured/document)
|
||||||
|
# - tags: 标签列表
|
||||||
|
|
||||||
8
backend/package-lock.json
generated
8
backend/package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"prisma": "^6.17.0"
|
"prisma": "^6.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^24.7.1",
|
"@types/node": "^24.7.1",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"pino-pretty": "^13.1.1",
|
"pino-pretty": "^13.1.1",
|
||||||
@@ -784,6 +785,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/js-yaml": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.7.1",
|
"version": "24.7.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.7.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.7.1.tgz",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"prisma": "^6.17.0"
|
"prisma": "^6.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^24.7.1",
|
"@types/node": "^24.7.1",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"pino-pretty": "^13.1.1",
|
"pino-pretty": "^13.1.1",
|
||||||
|
|||||||
119
backend/prompts/topic_evaluation_system.txt
Normal file
119
backend/prompts/topic_evaluation_system.txt
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
你是一位经验丰富的临床研究专家,擅长评估研究选题的质量和可行性。你的任务是从多个维度对研究选题进行专业、客观的评价,并给出建设性的改进建议。
|
||||||
|
|
||||||
|
## 你的职责
|
||||||
|
|
||||||
|
1. **全面评估**:从创新性、临床价值、科学性和可行性四个维度评价研究选题
|
||||||
|
2. **指出问题**:客观指出选题存在的不足和潜在风险
|
||||||
|
3. **提供建议**:给出具体的改进方向和优化思路
|
||||||
|
4. **评分量化**:对每个维度给出1-10分的评分,并说明理由
|
||||||
|
|
||||||
|
## 评价维度
|
||||||
|
|
||||||
|
### 1. 创新性(Innovation)
|
||||||
|
- 研究问题是否新颖?是否填补了现有知识的空白?
|
||||||
|
- 研究方法或角度是否具有创新性?
|
||||||
|
- 与已有研究相比有哪些突破?
|
||||||
|
- 评分标准:
|
||||||
|
* 9-10分:开创性研究,填补重大空白
|
||||||
|
* 7-8分:显著创新,有新的发现或方法
|
||||||
|
* 5-6分:一定创新性,但突破有限
|
||||||
|
* 3-4分:创新性较弱,主要是验证性研究
|
||||||
|
* 1-2分:缺乏创新,重复已有研究
|
||||||
|
|
||||||
|
### 2. 临床价值(Clinical Value)
|
||||||
|
- 研究结果对临床实践有何指导意义?
|
||||||
|
- 能否改善患者预后或生活质量?
|
||||||
|
- 是否解决了临床中的实际问题?
|
||||||
|
- 潜在的临床应用前景如何?
|
||||||
|
- 评分标准:
|
||||||
|
* 9-10分:重大临床价值,可能改变临床实践
|
||||||
|
* 7-8分:明确临床价值,有较大应用潜力
|
||||||
|
* 5-6分:一定临床价值,但应用范围有限
|
||||||
|
* 3-4分:临床价值不明显
|
||||||
|
* 1-2分:临床价值很低或无价值
|
||||||
|
|
||||||
|
### 3. 科学性(Scientific Rigor)
|
||||||
|
- 研究假设是否合理?理论基础是否扎实?
|
||||||
|
- 研究设计是否科学?方法学是否严谨?
|
||||||
|
- 样本量是否合理?统计方法是否恰当?
|
||||||
|
- 是否考虑了混杂因素和偏倚控制?
|
||||||
|
- 评分标准:
|
||||||
|
* 9-10分:研究设计严谨,方法学无懈可击
|
||||||
|
* 7-8分:研究设计合理,方法学较为严谨
|
||||||
|
* 5-6分:研究设计基本合理,但存在一些缺陷
|
||||||
|
* 3-4分:研究设计有明显缺陷,方法学有问题
|
||||||
|
* 1-2分:研究设计不科学,方法学严重缺陷
|
||||||
|
|
||||||
|
### 4. 可行性(Feasibility)
|
||||||
|
- 研究所需资源(时间、经费、设备、人力)是否可获得?
|
||||||
|
- 样本招募是否困难?数据收集是否可行?
|
||||||
|
- 伦理审查是否容易通过?
|
||||||
|
- 研究周期是否合理?
|
||||||
|
- 是否存在重大技术或操作障碍?
|
||||||
|
- 评分标准:
|
||||||
|
* 9-10分:可行性非常高,资源充足,易于实施
|
||||||
|
* 7-8分:可行性较高,资源基本可获得
|
||||||
|
* 5-6分:可行性一般,存在一些困难但可克服
|
||||||
|
* 3-4分:可行性较低,存在较多障碍
|
||||||
|
* 1-2分:可行性很低,难以实施
|
||||||
|
|
||||||
|
## 评价流程
|
||||||
|
|
||||||
|
1. **理解选题**:仔细阅读用户提供的研究选题描述,包括研究背景、目的、方法等
|
||||||
|
2. **维度评分**:对每个维度进行1-10分的评分,并详细说明理由
|
||||||
|
3. **综合评价**:给出选题的总体评价和优先级建议
|
||||||
|
4. **改进建议**:针对不足之处,给出3-5条具体的改进建议
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
你的评价应该包含以下结构:
|
||||||
|
|
||||||
|
### 📊 评分总览
|
||||||
|
- 创新性:X/10分
|
||||||
|
- 临床价值:X/10分
|
||||||
|
- 科学性:X/10分
|
||||||
|
- 可行性:X/10分
|
||||||
|
- **综合得分:XX/40分**
|
||||||
|
|
||||||
|
### 🎯 详细评价
|
||||||
|
|
||||||
|
#### 1. 创新性评价(X/10分)
|
||||||
|
[详细说明为什么给出这个分数,指出创新点或不足]
|
||||||
|
|
||||||
|
#### 2. 临床价值评价(X/10分)
|
||||||
|
[详细说明临床价值体现在哪里,或为什么价值有限]
|
||||||
|
|
||||||
|
#### 3. 科学性评价(X/10分)
|
||||||
|
[分析研究设计和方法学的优缺点]
|
||||||
|
|
||||||
|
#### 4. 可行性评价(X/10分)
|
||||||
|
[分析实施难度和资源需求]
|
||||||
|
|
||||||
|
### 💡 改进建议
|
||||||
|
|
||||||
|
1. **建议一**:[具体的改进方向]
|
||||||
|
2. **建议二**:[具体的改进方向]
|
||||||
|
3. **建议三**:[具体的改进方向]
|
||||||
|
[如有必要,可以提供更多建议]
|
||||||
|
|
||||||
|
### ✅ 总体评价
|
||||||
|
|
||||||
|
[用1-2段话总结选题的整体质量,给出是否建议推进的明确意见,以及优先级建议(高/中/低)]
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 评价要**客观公正**,既要肯定优点,也要指出不足
|
||||||
|
- 评分要**有理有据**,不能主观臆断
|
||||||
|
- 建议要**具体可行**,不能空泛
|
||||||
|
- 语气要**专业友好**,鼓励研究者改进
|
||||||
|
- 如果用户提供的信息不足,要明确指出需要补充哪些信息
|
||||||
|
- 如果选题存在严重缺陷,要明确指出,但也要给出挽救方案(如果有)
|
||||||
|
|
||||||
|
## 特殊情况处理
|
||||||
|
|
||||||
|
- 如果用户提供的选题描述过于简略,要求其补充关键信息(研究目的、方法、预期结果等)
|
||||||
|
- 如果选题涉及伦理敏感问题,要特别提醒伦理审查的注意事项
|
||||||
|
- 如果选题超出你的专业范围(如纯基础研究),要说明你的评价可能有局限性
|
||||||
|
|
||||||
|
记住:你的评价将直接影响研究者的决策,因此要认真负责、专业严谨。
|
||||||
|
|
||||||
15
backend/prompts/topic_evaluation_user.txt
Normal file
15
backend/prompts/topic_evaluation_user.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
请对以下研究选题进行评价:
|
||||||
|
|
||||||
|
## 项目背景
|
||||||
|
{{projectBackground}}
|
||||||
|
|
||||||
|
## 研究选题
|
||||||
|
{{userInput}}
|
||||||
|
|
||||||
|
{{#if knowledgeBaseContext}}
|
||||||
|
## 参考文献(来自知识库)
|
||||||
|
{{knowledgeBaseContext}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
请根据创新性、临床价值、科学性和可行性四个维度,对上述选题进行全面评价,并给出改进建议。
|
||||||
|
|
||||||
215
backend/src/controllers/agentController.ts
Normal file
215
backend/src/controllers/agentController.ts
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { agentService } from '../services/agentService.js';
|
||||||
|
|
||||||
|
interface AgentParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RenderPromptBody {
|
||||||
|
projectBackground?: string;
|
||||||
|
userInput: string;
|
||||||
|
knowledgeBaseContext?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AgentController {
|
||||||
|
// 获取所有智能体列表
|
||||||
|
async getAllAgents(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const agents = agentService.getAllAgents();
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
data: agents,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '获取智能体列表失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取启用的智能体列表
|
||||||
|
async getEnabledAgents(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const agents = agentService.getEnabledAgents();
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
data: agents,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '获取智能体列表失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取单个智能体详情
|
||||||
|
async getAgentById(
|
||||||
|
request: FastifyRequest<{ Params: AgentParams }>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const agent = agentService.getAgentById(id);
|
||||||
|
|
||||||
|
if (!agent) {
|
||||||
|
return reply.code(404).send({
|
||||||
|
success: false,
|
||||||
|
message: '智能体不存在',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
data: agent,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '获取智能体详情失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取智能体的系统Prompt
|
||||||
|
async getSystemPrompt(
|
||||||
|
request: FastifyRequest<{ Params: AgentParams }>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
|
||||||
|
if (!agentService.agentExists(id)) {
|
||||||
|
return reply.code(404).send({
|
||||||
|
success: false,
|
||||||
|
message: '智能体不存在',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemPrompt = agentService.getSystemPrompt(id);
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
agentId: id,
|
||||||
|
systemPrompt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '获取系统Prompt失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染用户Prompt(用于预览或调试)
|
||||||
|
async renderPrompt(
|
||||||
|
request: FastifyRequest<{ Params: AgentParams }>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const body = request.body as RenderPromptBody;
|
||||||
|
|
||||||
|
if (!agentService.agentExists(id)) {
|
||||||
|
return reply.code(404).send({
|
||||||
|
success: false,
|
||||||
|
message: '智能体不存在',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!body.userInput) {
|
||||||
|
return reply.code(400).send({
|
||||||
|
success: false,
|
||||||
|
message: 'userInput为必填项',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedPrompt = agentService.renderUserPrompt(id, {
|
||||||
|
projectBackground: body.projectBackground,
|
||||||
|
userInput: body.userInput,
|
||||||
|
knowledgeBaseContext: body.knowledgeBaseContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
agentId: id,
|
||||||
|
renderedPrompt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '渲染Prompt失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据分类获取智能体
|
||||||
|
async getAgentsByCategory(
|
||||||
|
request: FastifyRequest<{ Querystring: { category: string } }>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { category } = request.query;
|
||||||
|
|
||||||
|
if (!category) {
|
||||||
|
return reply.code(400).send({
|
||||||
|
success: false,
|
||||||
|
message: 'category参数为必填项',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const agents = agentService.getAgentsByCategory(category);
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
data: agents,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '获取智能体列表失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载配置(管理员功能)
|
||||||
|
async reloadConfig(request: FastifyRequest, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
agentService.reloadConfig();
|
||||||
|
|
||||||
|
return reply.code(200).send({
|
||||||
|
success: true,
|
||||||
|
message: '智能体配置已重新加载',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
request.log.error(error);
|
||||||
|
return reply.code(500).send({
|
||||||
|
success: false,
|
||||||
|
message: '重新加载配置失败',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const agentController = new AgentController();
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ import cors from '@fastify/cors';
|
|||||||
import { config } from './config/env.js';
|
import { config } from './config/env.js';
|
||||||
import { testDatabaseConnection, prisma } from './config/database.js';
|
import { testDatabaseConnection, prisma } from './config/database.js';
|
||||||
import { projectRoutes } from './routes/projects.js';
|
import { projectRoutes } from './routes/projects.js';
|
||||||
|
import { agentRoutes } from './routes/agents.js';
|
||||||
|
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
logger: {
|
logger: {
|
||||||
@@ -55,6 +56,9 @@ fastify.get('/api/v1', async () => {
|
|||||||
// 注册项目管理路由
|
// 注册项目管理路由
|
||||||
await fastify.register(projectRoutes, { prefix: '/api/v1' });
|
await fastify.register(projectRoutes, { prefix: '/api/v1' });
|
||||||
|
|
||||||
|
// 注册智能体管理路由
|
||||||
|
await fastify.register(agentRoutes, { prefix: '/api/v1' });
|
||||||
|
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
56
backend/src/routes/agents.ts
Normal file
56
backend/src/routes/agents.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { agentController } from '../controllers/agentController.js';
|
||||||
|
|
||||||
|
interface AgentParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function agentRoutes(fastify: FastifyInstance) {
|
||||||
|
// 获取所有智能体列表
|
||||||
|
fastify.get('/agents', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
return agentController.getAllAgents(request, reply);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取启用的智能体列表
|
||||||
|
fastify.get('/agents/enabled', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
return agentController.getEnabledAgents(request, reply);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据分类获取智能体
|
||||||
|
fastify.get<{ Querystring: { category: string } }>(
|
||||||
|
'/agents/by-category',
|
||||||
|
async (request: FastifyRequest<{ Querystring: { category: string } }>, reply: FastifyReply) => {
|
||||||
|
return agentController.getAgentsByCategory(request, reply);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 获取单个智能体详情
|
||||||
|
fastify.get<{ Params: AgentParams }>(
|
||||||
|
'/agents/:id',
|
||||||
|
async (request: FastifyRequest<{ Params: AgentParams }>, reply: FastifyReply) => {
|
||||||
|
return agentController.getAgentById(request, reply);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 获取智能体的系统Prompt
|
||||||
|
fastify.get<{ Params: AgentParams }>(
|
||||||
|
'/agents/:id/system-prompt',
|
||||||
|
async (request: FastifyRequest<{ Params: AgentParams }>, reply: FastifyReply) => {
|
||||||
|
return agentController.getSystemPrompt(request, reply);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 渲染用户Prompt(预览)
|
||||||
|
fastify.post<{ Params: AgentParams }>(
|
||||||
|
'/agents/:id/render-prompt',
|
||||||
|
async (request: FastifyRequest<{ Params: AgentParams }>, reply: FastifyReply) => {
|
||||||
|
return agentController.renderPrompt(request, reply);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 重新加载配置(管理员功能)
|
||||||
|
fastify.post('/agents/reload-config', async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
return agentController.reloadConfig(request, reply);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
212
backend/src/services/agentService.ts
Normal file
212
backend/src/services/agentService.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// 智能体配置接口
|
||||||
|
export interface AgentConfig {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
nameEn: string;
|
||||||
|
description: string;
|
||||||
|
category: string;
|
||||||
|
icon: string;
|
||||||
|
enabled: boolean;
|
||||||
|
systemPromptFile: string;
|
||||||
|
userPromptTemplateFile: string;
|
||||||
|
models: {
|
||||||
|
[modelName: string]: {
|
||||||
|
temperature: number;
|
||||||
|
maxTokens: number;
|
||||||
|
topP?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ragEnabled: boolean;
|
||||||
|
requiresProject: boolean;
|
||||||
|
outputFormat: 'text' | 'structured' | 'document';
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置文件根结构
|
||||||
|
interface AgentsConfigFile {
|
||||||
|
agents: AgentConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AgentService {
|
||||||
|
private agents: Map<string, AgentConfig> = new Map();
|
||||||
|
private prompts: Map<string, string> = new Map();
|
||||||
|
private configPath: string;
|
||||||
|
private promptsPath: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 配置文件路径
|
||||||
|
this.configPath = path.resolve(__dirname, '../../config/agents.yaml');
|
||||||
|
this.promptsPath = path.resolve(__dirname, '../../prompts');
|
||||||
|
|
||||||
|
// 初始化加载配置
|
||||||
|
this.loadAgents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载智能体配置
|
||||||
|
private loadAgents() {
|
||||||
|
try {
|
||||||
|
const fileContents = fs.readFileSync(this.configPath, 'utf8');
|
||||||
|
const config = yaml.load(fileContents) as AgentsConfigFile;
|
||||||
|
|
||||||
|
if (!config || !config.agents) {
|
||||||
|
throw new Error('Invalid agents configuration file');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储到Map中
|
||||||
|
config.agents.forEach((agent) => {
|
||||||
|
this.agents.set(agent.id, agent);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Loaded ${this.agents.size} agent configurations`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to load agent configurations:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载Prompt模板
|
||||||
|
private loadPrompt(filename: string): string {
|
||||||
|
const cacheKey = filename;
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
if (this.prompts.has(cacheKey)) {
|
||||||
|
return this.prompts.get(cacheKey)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const promptPath = path.join(this.promptsPath, filename);
|
||||||
|
const content = fs.readFileSync(promptPath, 'utf8');
|
||||||
|
|
||||||
|
// 缓存到内存
|
||||||
|
this.prompts.set(cacheKey, content);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Failed to load prompt file: ${filename}`, error);
|
||||||
|
throw new Error(`Prompt file not found: ${filename}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有智能体列表
|
||||||
|
getAllAgents(): AgentConfig[] {
|
||||||
|
return Array.from(this.agents.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取启用的智能体列表
|
||||||
|
getEnabledAgents(): AgentConfig[] {
|
||||||
|
return Array.from(this.agents.values()).filter((agent) => agent.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取智能体配置
|
||||||
|
getAgentById(agentId: string): AgentConfig | null {
|
||||||
|
return this.agents.get(agentId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据分类获取智能体列表
|
||||||
|
getAgentsByCategory(category: string): AgentConfig[] {
|
||||||
|
return Array.from(this.agents.values()).filter(
|
||||||
|
(agent) => agent.category === category
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取智能体的系统Prompt
|
||||||
|
getSystemPrompt(agentId: string): string {
|
||||||
|
const agent = this.getAgentById(agentId);
|
||||||
|
if (!agent) {
|
||||||
|
throw new Error(`Agent not found: ${agentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loadPrompt(agent.systemPromptFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取智能体的用户Prompt模板
|
||||||
|
getUserPromptTemplate(agentId: string): string {
|
||||||
|
const agent = this.getAgentById(agentId);
|
||||||
|
if (!agent) {
|
||||||
|
throw new Error(`Agent not found: ${agentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loadPrompt(agent.userPromptTemplateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染用户Prompt(替换模板变量)
|
||||||
|
renderUserPrompt(
|
||||||
|
agentId: string,
|
||||||
|
variables: {
|
||||||
|
projectBackground?: string;
|
||||||
|
userInput: string;
|
||||||
|
knowledgeBaseContext?: string;
|
||||||
|
}
|
||||||
|
): string {
|
||||||
|
const template = this.getUserPromptTemplate(agentId);
|
||||||
|
|
||||||
|
let rendered = template;
|
||||||
|
|
||||||
|
// 替换变量
|
||||||
|
rendered = rendered.replace(/\{\{projectBackground\}\}/g, variables.projectBackground || '未提供项目背景');
|
||||||
|
rendered = rendered.replace(/\{\{userInput\}\}/g, variables.userInput);
|
||||||
|
|
||||||
|
// 处理条件块(知识库上下文)
|
||||||
|
if (variables.knowledgeBaseContext) {
|
||||||
|
rendered = rendered.replace(
|
||||||
|
/\{\{#if knowledgeBaseContext\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
||||||
|
'$1'
|
||||||
|
);
|
||||||
|
rendered = rendered.replace(/\{\{knowledgeBaseContext\}\}/g, variables.knowledgeBaseContext);
|
||||||
|
} else {
|
||||||
|
// 移除条件块
|
||||||
|
rendered = rendered.replace(
|
||||||
|
/\{\{#if knowledgeBaseContext\}\}[\s\S]*?\{\{\/if\}\}/g,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rendered.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查智能体是否存在
|
||||||
|
agentExists(agentId: string): boolean {
|
||||||
|
return this.agents.has(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查智能体是否启用
|
||||||
|
isAgentEnabled(agentId: string): boolean {
|
||||||
|
const agent = this.getAgentById(agentId);
|
||||||
|
return agent ? agent.enabled : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取智能体的模型配置
|
||||||
|
getModelConfig(agentId: string, modelName: string) {
|
||||||
|
const agent = this.getAgentById(agentId);
|
||||||
|
if (!agent) {
|
||||||
|
throw new Error(`Agent not found: ${agentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelConfig = agent.models[modelName];
|
||||||
|
if (!modelConfig) {
|
||||||
|
throw new Error(`Model ${modelName} not configured for agent ${agentId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载配置(热更新)
|
||||||
|
reloadConfig() {
|
||||||
|
this.agents.clear();
|
||||||
|
this.prompts.clear();
|
||||||
|
this.loadAgents();
|
||||||
|
console.log('✅ Agent configurations reloaded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例
|
||||||
|
export const agentService = new AgentService();
|
||||||
|
|
||||||
10
frontend/package-lock.json
generated
10
frontend/package-lock.json
generated
@@ -9,8 +9,10 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.5.2",
|
"@ant-design/icons": "^5.5.2",
|
||||||
|
"@types/js-yaml": "^4.0.9",
|
||||||
"antd": "^5.22.5",
|
"antd": "^5.22.5",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.28.0"
|
"react-router-dom": "^6.28.0"
|
||||||
@@ -1788,6 +1790,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/js-yaml": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -2245,7 +2253,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
@@ -3577,7 +3584,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
|
|||||||
@@ -10,12 +10,14 @@
|
|||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.3.1",
|
"@ant-design/icons": "^5.5.2",
|
||||||
"react-dom": "^18.3.1",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"react-router-dom": "^6.28.0",
|
|
||||||
"antd": "^5.22.5",
|
"antd": "^5.22.5",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"@ant-design/icons": "^5.5.2"
|
"js-yaml": "^4.1.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.28.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.18",
|
||||||
@@ -33,4 +35,3 @@
|
|||||||
"vite": "^6.0.7"
|
"vite": "^6.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
77
frontend/src/api/agentApi.ts
Normal file
77
frontend/src/api/agentApi.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import request from './request';
|
||||||
|
|
||||||
|
export interface AgentConfig {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
nameEn: string;
|
||||||
|
description: string;
|
||||||
|
category: string;
|
||||||
|
icon: string;
|
||||||
|
enabled: boolean;
|
||||||
|
systemPromptFile: string;
|
||||||
|
userPromptTemplateFile: string;
|
||||||
|
models: {
|
||||||
|
[modelName: string]: {
|
||||||
|
temperature: number;
|
||||||
|
maxTokens: number;
|
||||||
|
topP?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ragEnabled: boolean;
|
||||||
|
requiresProject: boolean;
|
||||||
|
outputFormat: 'text' | 'structured' | 'document';
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T = any> {
|
||||||
|
success: boolean;
|
||||||
|
data?: T;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const agentApi = {
|
||||||
|
// 获取所有智能体列表
|
||||||
|
getAll: async (): Promise<ApiResponse<AgentConfig[]>> => {
|
||||||
|
const response = await request.get('/agents');
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取启用的智能体列表
|
||||||
|
getEnabled: async (): Promise<ApiResponse<AgentConfig[]>> => {
|
||||||
|
const response = await request.get('/agents/enabled');
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取单个智能体详情
|
||||||
|
getById: async (id: string): Promise<ApiResponse<AgentConfig>> => {
|
||||||
|
const response = await request.get(`/agents/${id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 根据分类获取智能体
|
||||||
|
getByCategory: async (category: string): Promise<ApiResponse<AgentConfig[]>> => {
|
||||||
|
const response = await request.get(`/agents/by-category?category=${encodeURIComponent(category)}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取智能体的系统Prompt
|
||||||
|
getSystemPrompt: async (id: string): Promise<ApiResponse<{ agentId: string; systemPrompt: string }>> => {
|
||||||
|
const response = await request.get(`/agents/${id}/system-prompt`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 渲染用户Prompt
|
||||||
|
renderPrompt: async (
|
||||||
|
id: string,
|
||||||
|
data: {
|
||||||
|
projectBackground?: string;
|
||||||
|
userInput: string;
|
||||||
|
knowledgeBaseContext?: string;
|
||||||
|
}
|
||||||
|
): Promise<ApiResponse<{ agentId: string; renderedPrompt: string }>> => {
|
||||||
|
const response = await request.post(`/agents/${id}/render-prompt`, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { Card, Typography, Input, Button, Space, Select, Upload, Tag, Alert, Divider } from 'antd'
|
import { useState, useEffect } from 'react'
|
||||||
|
import { Card, Typography, Input, Button, Space, Select, Upload, Tag, Alert, Divider, Spin } from 'antd'
|
||||||
import {
|
import {
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
PaperClipOutlined,
|
PaperClipOutlined,
|
||||||
@@ -7,20 +8,57 @@ import {
|
|||||||
FolderOpenOutlined,
|
FolderOpenOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { AGENTS } from '../layouts/MainLayout'
|
import { agentApi, type AgentConfig } from '../api/agentApi'
|
||||||
|
import { message } from 'antd'
|
||||||
|
|
||||||
const { Title, Paragraph } = Typography
|
const { Title, Paragraph } = Typography
|
||||||
const { TextArea } = Input
|
const { TextArea } = Input
|
||||||
|
|
||||||
const AgentChatPage = () => {
|
const AgentChatPage = () => {
|
||||||
const { agentId } = useParams()
|
const { agentId } = useParams()
|
||||||
const agent = AGENTS.find((a) => a.id === agentId)
|
const [agent, setAgent] = useState<AgentConfig | null>(null)
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
if (!agent) {
|
// 加载智能体配置
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAgent = async () => {
|
||||||
|
if (!agentId) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const response = await agentApi.getById(agentId)
|
||||||
|
|
||||||
|
if (response.success && response.data) {
|
||||||
|
setAgent(response.data)
|
||||||
|
} else {
|
||||||
|
setError(response.message || '智能体不存在')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load agent:', err)
|
||||||
|
setError('加载智能体配置失败')
|
||||||
|
message.error('加载智能体配置失败')
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAgent()
|
||||||
|
}, [agentId])
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center', padding: '100px 0' }}>
|
||||||
|
<Spin size="large" tip="加载智能体配置中..." />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error || !agent) {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
message="智能体不存在"
|
message="智能体不存在"
|
||||||
description="请从首页选择一个智能体"
|
description={error || "请从首页选择一个智能体"}
|
||||||
type="error"
|
type="error"
|
||||||
showIcon
|
showIcon
|
||||||
/>
|
/>
|
||||||
@@ -49,7 +87,10 @@ const AgentChatPage = () => {
|
|||||||
{agent.name}
|
{agent.name}
|
||||||
</Title>
|
</Title>
|
||||||
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
||||||
当前模型:DeepSeek-V3
|
{agent.description}
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph type="secondary" style={{ marginBottom: 0, fontSize: 12 }}>
|
||||||
|
当前模型:DeepSeek-V3 | 分类:{agent.category}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
Reference in New Issue
Block a user