mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-03 23:16:12 +00:00
Compare commits
90 Commits
ace7e961a4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3071bfd0f9 | ||
|
|
7bb938c145 | ||
|
|
75b21d3633 | ||
|
|
7ed9d8def4 | ||
|
|
63ed7ddb02 | ||
|
|
11696a016d | ||
|
|
1a10104751 | ||
|
|
f95cb17933 | ||
|
|
0687b49542 | ||
|
|
27ad00ac3a | ||
|
|
c84d6247b0 | ||
|
|
f582f38570 | ||
|
|
13800dc389 | ||
|
|
619d9b1e84 | ||
|
|
556cc93f14 | ||
|
|
a50375616e | ||
|
|
e33447d023 | ||
|
|
7be277b3e6 | ||
|
|
5fa385e90b | ||
|
|
0a78966737 | ||
|
|
0eb7f00867 | ||
|
|
00ce7f2d98 | ||
|
|
003c066361 | ||
|
|
a5e7c59fd4 | ||
|
|
accac603cf | ||
|
|
7c96c730e6 | ||
|
|
a2dbdb30ff | ||
|
|
772e4af9bf | ||
|
|
e601eb6db5 | ||
|
|
84dbc2cfbf | ||
|
|
f160ec714b | ||
|
|
a8bd4b47a0 | ||
|
|
6c4c661516 | ||
|
|
b85c17a126 | ||
|
|
a59ddf6070 | ||
|
|
797ecbb054 | ||
|
|
b6b78afea9 | ||
|
|
0690156362 | ||
|
|
02240f3fd0 | ||
|
|
dc1e84bc52 | ||
|
|
4977df0ba8 | ||
|
|
27211b67f9 | ||
|
|
1600ab384e | ||
|
|
418805a1ef | ||
|
|
4c4b52bca7 | ||
|
|
7245259bc4 | ||
|
|
6690c8204c | ||
|
|
d8fc597f85 | ||
|
|
7f1146ecae | ||
|
|
0130028952 | ||
|
|
5a716da5a6 | ||
|
|
2470ec7573 | ||
|
|
e3fb25fba6 | ||
|
|
a916f14efc | ||
|
|
523628ade6 | ||
|
|
2259a2f717 | ||
|
|
8df37274da | ||
|
|
393057ab24 | ||
|
|
2aa7e0f4f1 | ||
|
|
b2d2fb34b8 | ||
|
|
c1e2a03178 | ||
|
|
fd38d2030e | ||
|
|
a42f881b67 | ||
|
|
20d531c0db | ||
|
|
1e6046cf52 | ||
|
|
495f2de6ef | ||
|
|
e97bd4e201 | ||
|
|
70e5e393ef | ||
|
|
8954f59cd7 | ||
|
|
c78b1a14a9 | ||
|
|
e768c4b356 | ||
|
|
d059f50ba6 | ||
|
|
26bcfbba8a | ||
|
|
d6e4a50d6e | ||
|
|
0a115f289e | ||
|
|
31e8df8bc0 | ||
|
|
ee477213e0 | ||
|
|
1e6e236d3c | ||
|
|
593a0d0049 | ||
|
|
d4f8f91893 | ||
|
|
f25ebdf9ec | ||
|
|
32b8144b56 | ||
|
|
b40805cb57 | ||
|
|
008f64617f | ||
|
|
3539e222d2 | ||
|
|
c4d1ea974d | ||
|
|
420e05ecf3 | ||
|
|
ee8c882b6f | ||
|
|
69ec2a33a4 | ||
|
|
1cd8ae1cd9 |
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: 漏洞报告
|
||||
about: 报告项目中的Bug或安全问题
|
||||
title: '[Bug] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 问题描述
|
||||
|
||||
简要描述遇到的问题:
|
||||
|
||||
## 复现步骤
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## 期望行为
|
||||
|
||||
描述你期望发生的情况:
|
||||
|
||||
## 实际行为
|
||||
|
||||
描述实际发生的情况:
|
||||
|
||||
## 环境信息
|
||||
|
||||
| 项目 | 信息 |
|
||||
|:---|:---|
|
||||
| 操作系统 | |
|
||||
| JDK版本 | |
|
||||
| 项目版本 | |
|
||||
|
||||
## 截图/日志
|
||||
|
||||
如有相关信息,请在此粘贴:
|
||||
|
||||
## 补充说明
|
||||
|
||||
其他补充信息:
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
15
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: 自定义
|
||||
about: 其他问题或讨论
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 描述
|
||||
|
||||
请详细描述你的问题或需求:
|
||||
|
||||
## 补充信息
|
||||
|
||||
如有其他补充,请在此填写:
|
||||
57
.github/ISSUE_TEMPLATE/enterprise-collaboration.md
vendored
Normal file
57
.github/ISSUE_TEMPLATE/enterprise-collaboration.md
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: 企业AI应用合作登记
|
||||
about: 登记企业信息与合作意向,获取免费技术支持
|
||||
title: '[合作登记] 企业AI应用需求登记'
|
||||
labels: '合作登记'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 登记说明
|
||||
|
||||
感谢您关注 RuoYi AI!我们团队今年的重点是帮助企业落地AI应用,如果贵公司符合要求,我们可以提供**免费的技术支持**。
|
||||
|
||||
请在下方评论中填写登记信息,格式如下:
|
||||
|
||||
---
|
||||
|
||||
## 登记格式预览
|
||||
|
||||
| 字段 | 内容 |
|
||||
|:---|:---|
|
||||
| 公司名称 | (必填) |
|
||||
| 公司Logo地址 | (可选) |
|
||||
| 所属行业 | (必填) |
|
||||
| 公司所在地 | (必填) |
|
||||
| 项目名称 | (必填) |
|
||||
| 项目Logo地址 | (可选) |
|
||||
| 项目简介 | (必填) |
|
||||
| 当前AI应用状态 | □ 尚未开始 □ 规划中 □ 已有初步应用 □ 已有成熟应用 |
|
||||
| 计划落地时间 | □ 1个月内 □ 1-3个月 □ 3-6个月 □ 6个月以上 |
|
||||
| 当前痛点或挑战 | (可选) |
|
||||
| 公司简介 | (可选) |
|
||||
|
||||
---
|
||||
|
||||
## 可复制格式
|
||||
|
||||
复制下方内容并在评论中填写:
|
||||
|
||||
```
|
||||
| 字段 | 内容 |
|
||||
|:---|:---|
|
||||
| 公司名称 | |
|
||||
| 公司Logo地址 | |
|
||||
| 所属行业 | |
|
||||
| 公司所在地 | |
|
||||
| 项目名称 | |
|
||||
| 项目Logo地址 | |
|
||||
| 项目简介 | |
|
||||
| 当前AI应用状态 | |
|
||||
| 计划落地时间 | |
|
||||
| 当前痛点或挑战 | |
|
||||
| 公司简介 | |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **温馨提示**:提交的信息仅用于合作沟通,不会对外公开。
|
||||
31
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: 想法建议
|
||||
about: 提出新功能建议或改进想法
|
||||
title: '[Feature] '
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 建议类型
|
||||
|
||||
□ 新功能 □ 功能改进 □ 文档完善 □ 其他
|
||||
|
||||
## 建议描述
|
||||
|
||||
清晰描述你的建议内容:
|
||||
|
||||
## 使用场景
|
||||
|
||||
描述这个功能在什么场景下会用到:
|
||||
|
||||
## 期望效果
|
||||
|
||||
描述你期望的效果:
|
||||
|
||||
## 参考示例
|
||||
|
||||
如有类似的参考实现或产品,请提供链接:
|
||||
|
||||
## 补充说明
|
||||
|
||||
其他补充信息:
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -21,10 +21,13 @@ target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
.claude
|
||||
.github
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
|
||||
### JRebel ###
|
||||
rebel.xml
|
||||
|
||||
@@ -41,9 +44,11 @@ nbdist/
|
||||
*.log.gz
|
||||
*.xml.versionsBackup
|
||||
*.swp
|
||||
data/
|
||||
|
||||
!*/build/*.java
|
||||
!*/build/*.html
|
||||
!*/build/*.xml
|
||||
|
||||
.flattened-pom.xml
|
||||
/.claude/settings.local.json
|
||||
|
||||
173
README.md
173
README.md
@@ -17,12 +17,9 @@
|
||||
|
||||
<img src="docs/image/logo.png" alt="RuoYi AI Logo" width="120" height="120">
|
||||
|
||||
## 功能建议&bug提交:【腾讯文档】
|
||||
https://docs.qq.com/sheet/DR3hoR3FVVkpJcnVm
|
||||
|
||||
### 企业级AI助手平台
|
||||
|
||||
*开箱即用的全栈AI平台,集成Coze、DIFY等主流AI平台,提供先进的RAG技术、知识图谱、数字人和AI流程编排能力*
|
||||
*开箱即用的全栈AI平台,支持多智能体协同、Supervisor模式编排、多种决策模式、RAG技术和流程编排能力*
|
||||
|
||||
**[English](README_EN.md)** | **[📖 使用文档](https://doc.pandarobot.chat)** |
|
||||
**[🚀 在线体验](https://web.pandarobot.chat)** | **[🐛 问题反馈](https://github.com/ageerle/ruoyi-ai/issues)** | **[💡 功能建议](https://github.com/ageerle/ruoyi-ai/issues)**
|
||||
@@ -30,38 +27,24 @@ https://docs.qq.com/sheet/DR3hoR3FVVkpJcnVm
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
## ✨ 核心亮点
|
||||
|
||||
### 智能AI引擎
|
||||
- **多模型接入**:支持 OpenAI、DeepSeek、通义千问、智谱AI 等主流厂商的模型
|
||||
- **多模态理解**:支持文本、图片、文档等多种格式的智能处理
|
||||
- **AI平台集成**:集成了 **扣子(Coze)**、**DIFY**、**FastGPT** 等主流AI应用平台
|
||||
- **MCP能力集成**:基于模型上下文协议,打造可扩展的AI工具生态系统
|
||||
- **AI编程助手**:内置智能代码分析和项目脚手架生成能力
|
||||
|
||||
### 本地化RAG方案
|
||||
- **私有知识库**:基于 Langchain4j 框架 + BGE-large-zh-v1.5 中文向量模型实现本地私有知识库
|
||||
- **多种向量库**:支持 Milvus、Weaviate、Qdrant 等主流向量数据库
|
||||
- **数据安全可控**:支持完全本地部署,保护企业数据隐私
|
||||
- **灵活模型部署**:兼容 Ollama、vLLM 等本地推理框架
|
||||
|
||||
### AI创作工具
|
||||
- **AI 绘画创作**: 集成 MidJourney、GPT-4o-image
|
||||
- **智能PPT生成**:一键将文本内容转换为精美演示文稿
|
||||
|
||||
### 知识图谱与智能编排
|
||||
- **知识图谱构建**:自动从文档和对话中提取实体关系,构建可视化知识网络
|
||||
- **AI 流程编排**:可视化工作流设计器,支持复杂AI任务的编排和自动化执行
|
||||
- **数字人交互**:集成数字人形象,提供更自然的人机交互体验
|
||||
| 模块 | 现有能力
|
||||
|:----------:|---
|
||||
| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成
|
||||
| **知识管理** | 本地RAG + 向量库(Milvus/Weaviate/Qdrant) + 文档解析
|
||||
| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态
|
||||
| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点
|
||||
| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型
|
||||
|
||||
## 🚀 快速体验
|
||||
|
||||
### 在线演示
|
||||
|
||||
- **用户端体验**:[web.pandarobot.chat](https://web.pandarobot.chat) (账号:admin 密码:admin123)
|
||||
- **管理后台**:[admin.pandarobot.chat](https://admin.pandarobot.chat) (账号:admin 密码:admin123)
|
||||
| 平台 | 地址 | 账号 |
|
||||
|:------:|---|---|
|
||||
| 用户端 | [web.pandarobot.chat](https://web.pandarobot.chat) | admin / admin123 |
|
||||
| 管理后台 | [admin.pandarobot.chat](https://admin.pandarobot.chat) | admin / admin123 |
|
||||
|
||||
### 项目源码
|
||||
|
||||
@@ -71,25 +54,147 @@ https://docs.qq.com/sheet/DR3hoR3FVVkpJcnVm
|
||||
| 🎨 用户前端 | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) |
|
||||
| 🛠️ 管理后台 | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) |
|
||||
|
||||
### 合作项目
|
||||
| 项目名称 | GitHub 仓库 | Gitee 仓库
|
||||
|----------------|-------------------------------------------------------|------------------------------------------------------|
|
||||
| element-plus-x | [element-plus-x](https://github.com/element-plus-x/Element-Plus-X) | [element-plus-x](https://gitee.com/he-jiayue/element-plus-x) |
|
||||
|
||||
## 🛠️ 技术架构
|
||||
|
||||
### 核心框架
|
||||
- **后端架构**:Spring Boot 3.5 + Langchain4j
|
||||
- **后端架构**:Spring Boot 4.0 + Spring ai 2.0 + Langchain4j
|
||||
- **数据存储**:MySQL 8.0 + Redis + 向量数据库(Milvus/Weaviate/Qdrant)
|
||||
- **前端技术**:Vue 3 + Vben Admin + Element UI
|
||||
- **前端技术**:Vue 3 + Vben Admin + element-plus-x
|
||||
- **安全认证**:Sa-Token + JWT 双重保障
|
||||
|
||||
### 系统组件
|
||||
|
||||
- **文档处理**:PDF、Word、Excel 解析,图像智能分析
|
||||
- **实时通信**:WebSocket 实时通信,SSE 流式响应
|
||||
- **系统监控**:完善的日志体系、性能监控、服务健康检查
|
||||
|
||||
## 🐳 Docker 部署
|
||||
|
||||
本项目提供两种 Docker 部署方式:
|
||||
|
||||
### 方式一:一键启动所有服务(推荐)
|
||||
|
||||
使用 `docker-compose-all.yaml` 可以一键启动所有服务(包括后端、管理端、用户端及依赖服务):
|
||||
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://github.com/ageerle/ruoyi-ai.git
|
||||
cd ruoyi-ai
|
||||
|
||||
# 启动所有服务(从镜像仓库拉取预构建镜像)
|
||||
docker-compose -f docker-compose-all.yaml up -d
|
||||
|
||||
# 查看服务状态
|
||||
docker-compose -f docker-compose-all.yaml ps
|
||||
|
||||
# 访问服务
|
||||
# 管理端: http://localhost:25666 (admin / admin123)
|
||||
# 用户端: http://localhost:25137
|
||||
# 后端API: http://localhost:26039
|
||||
```
|
||||
|
||||
### 方式二:分步部署(源码编译)
|
||||
|
||||
如果您需要从源码构建后端服务,请按照以下步骤操作:
|
||||
|
||||
#### 第一步:部署后端服务
|
||||
|
||||
```bash
|
||||
# 进入后端项目目录
|
||||
cd ruoyi-ai
|
||||
|
||||
# 启动后端服务(源码编译构建)
|
||||
docker-compose up -d --build
|
||||
|
||||
# 等待后端服务启动完成
|
||||
docker-compose logs -f backend
|
||||
```
|
||||
|
||||
#### 第二步:部署管理端
|
||||
|
||||
```bash
|
||||
# 进入管理端项目目录
|
||||
cd ruoyi-admin
|
||||
|
||||
# 构建并启动管理端
|
||||
docker-compose up -d --build
|
||||
|
||||
# 访问管理端
|
||||
# 地址: http://localhost:5666
|
||||
```
|
||||
|
||||
#### 第三步:部署用户端(可选)
|
||||
|
||||
```bash
|
||||
# 进入用户端项目目录
|
||||
cd ruoyi-web
|
||||
|
||||
# 构建并启动用户端
|
||||
docker-compose up -d --build
|
||||
|
||||
# 访问用户端
|
||||
# 地址: http://localhost:5137
|
||||
```
|
||||
|
||||
### 服务端口说明
|
||||
|
||||
| 服务 | 一键启动端口 | 分步部署端口 | 说明 |
|
||||
|------|-------------|-------------|------|
|
||||
| 管理端 | 25666 | 5666 | 管理后台访问地址 |
|
||||
| 用户端 | 25137 | 5137 | 用户前端访问地址 |
|
||||
| 后端服务 | 26039 | 6039 | 后端 API 服务 |
|
||||
| MySQL | 23306 | 23306 | 数据库服务 |
|
||||
| Redis | 26379 | 6379 | 缓存服务 |
|
||||
| Weaviate | 28080 | 28080 | 向量数据库 |
|
||||
| MinIO API | 29000 | 9000 | 对象存储 API |
|
||||
| MinIO Console | 29090 | 9090 | 对象存储控制台 |
|
||||
|
||||
### 镜像仓库
|
||||
|
||||
所有镜像托管在阿里云容器镜像服务:
|
||||
|
||||
```
|
||||
crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
|
||||
```
|
||||
|
||||
可用镜像:
|
||||
- `mysql:v3` - MySQL 数据库(包含初始化 SQL)
|
||||
- `redis:6.2` - Redis 缓存
|
||||
- `weaviate:1.30.0` - 向量数据库
|
||||
- `minio:latest` - 对象存储
|
||||
- `ruoyi-ai-backend:latest` - 后端服务
|
||||
- `ruoyi-ai-admin:latest` - 管理端前端
|
||||
- `ruoyi-ai-web:latest` - 用户端前端
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
# 停止所有服务
|
||||
docker-compose -f docker-compose-all.yaml down
|
||||
|
||||
# 查看服务日志
|
||||
docker-compose -f docker-compose-all.yaml logs -f [服务名]
|
||||
|
||||
# 重启某个服务
|
||||
docker-compose -f docker-compose-all.yaml restart [服务名]
|
||||
```
|
||||
|
||||
## 📚 使用文档
|
||||
|
||||
想要深入了解安装部署、功能配置和二次开发?
|
||||
|
||||
**👉 [完整使用文档](https://doc.pandarobot.chat)**
|
||||
|
||||
遇到知识库或 RAG 回答异常问题?
|
||||
|
||||
**👉 [RAG 回答异常排查手册](docs/troubleshooting/rag-failures.md)**
|
||||
|
||||
---
|
||||
|
||||
## 🤝 参与贡献
|
||||
|
||||
我们热烈欢迎社区贡献!无论您是资深开发者还是初学者,都可以为项目贡献力量 💪
|
||||
@@ -123,9 +228,6 @@ https://docs.qq.com/sheet/DR3hoR3FVVkpJcnVm
|
||||
算力和模型 API 服务
|
||||
- [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - 万卡RTX40系GPU+海内外主流模型API服务,秒级响应,按量计费,新客免费用。
|
||||
|
||||
## 优秀开源项目及社区推荐
|
||||
- [imaiwork](https://gitee.com/tsinghua-open/imaiwork) - AI手机开源版,AI获客手机项目,基于无障碍模式,RPA,比豆包AI手机更强大。
|
||||
|
||||
## 💬 社区交流
|
||||
|
||||
<div align="center">
|
||||
@@ -149,7 +251,6 @@ https://docs.qq.com/sheet/DR3hoR3FVVkpJcnVm
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**[⭐ 点个Star支持一下](https://github.com/ageerle/ruoyi-ai)** • **[ Fork 开始贡献](https://github.com/ageerle/ruoyi-ai/fork)** • **[📚 English](README_EN.md)** • **[📖 查看完整文档](https://doc.pandarobot.chat)**
|
||||
|
||||
205
README_EN.md
205
README_EN.md
@@ -1,3 +1,4 @@
|
||||
|
||||
# RuoYi AI
|
||||
|
||||
<div align="center">
|
||||
@@ -19,64 +20,171 @@
|
||||
|
||||
### Enterprise-Grade AI Assistant Platform
|
||||
|
||||
*An out-of-the-box intelligent AI platform that integrates mainstream AI platforms such as Coze and DIFY, providing advanced RAG technology, knowledge graphs, digital humans, and AI workflow orchestration capabilities*
|
||||
*An out-of-the-box full-stack AI platform supporting multi-agent collaboration, Supervisor mode orchestration, and multiple decision models, with advanced RAG technology and visual workflow orchestration capabilities*
|
||||
|
||||
**[中文](README.md)** | **[📖 Documentation](https://doc.pandarobot.chat)** |
|
||||
**[🚀 Live Demo](https://web.pandarobot.chat)** | **[🐛 Report Issues](https://github.com/ageerle/ruoyi-ai/issues)** | **[💡 Feature Requests](https://github.com/ageerle/ruoyi-ai/issues)**
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
## ✨ Core Features
|
||||
|
||||
### Intelligent AI Engine
|
||||
- **Multi-Model Integration**: Supports mainstream LLM providers including OpenAI, DeepSeek, Alibaba's Tongyi Qianwen, and Zhipu AI
|
||||
- **Multi-Modal Understanding**: Intelligently processes multiple formats including text, images, and documents
|
||||
- **AI Platform Integration**: Integrates mainstream AI application platforms like **Coze**, **DIFY**, and **FastGPT**
|
||||
- **MCP Capability Integration**: Build an extensible AI toolkit ecosystem based on the Model Context Protocol
|
||||
- **AI Coding Assistant**: Built-in intelligent code analysis and project scaffolding generation capabilities
|
||||
|
||||
### Local RAG Solution
|
||||
- **Private Knowledge Base**: Implements local private knowledge base based on Langchain4j framework + BGE-large-zh-v1.5 Chinese vector model
|
||||
- **Multiple Vector Databases**: Supports mainstream vector databases including Milvus, Weaviate, and Qdrant
|
||||
- **Data Security & Privacy**: Supports fully local deployment to protect enterprise data privacy
|
||||
- **Flexible Model Deployment**: Compatible with local inference frameworks like Ollama and vLLM
|
||||
|
||||
### AI Creative Tools
|
||||
- **AI Image Generation**: Integrates MidJourney and GPT-4o-image
|
||||
- **Intelligent PPT Generation**: Convert text content to beautiful presentations with one click
|
||||
|
||||
### Knowledge Graph & Intelligent Orchestration
|
||||
- **Knowledge Graph Construction**: Automatically extract entity relationships from documents and conversations, build visualized knowledge networks
|
||||
- **AI Workflow Orchestration**: Visual workflow designer supporting complex AI task orchestration and automated execution
|
||||
- **Digital Human Interaction**: Integrated digital avatars providing more natural human-machine interaction experience
|
||||
| Module | Current Capabilities |
|
||||
|:---:|---|
|
||||
| **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu), multi-modal understanding, Coze/DIFY/FastGPT platform integration |
|
||||
| **Knowledge Base** | Local RAG + Vector DB (Milvus/Weaviate/Qdrant) + Document parsing |
|
||||
| **Tool Management** | MCP protocol integration, Skills capability + Extensible tool ecosystem |
|
||||
| **Workflow Orchestration** | Visual workflow designer, drag-and-drop node orchestration, SSE streaming execution, currently supports model calls, email sending, manual review nodes |
|
||||
| **Multi-Agent** | Agent framework based on Langchain4j, Supervisor mode orchestration, supports multiple decision models |
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Live Demo
|
||||
|
||||
- **User Experience**: [web.pandarobot.chat](https://web.pandarobot.chat) (Username: admin, Password: admin123)
|
||||
- **Admin Dashboard**: [admin.pandarobot.chat](https://admin.pandarobot.chat) (Username: admin, Password: admin123)
|
||||
| Platform | URL | Account |
|
||||
|:------:|---|---|
|
||||
| User Frontend | [web.pandarobot.chat](https://web.pandarobot.chat) | admin / admin123 |
|
||||
| Admin Panel | [admin.pandarobot.chat](https://admin.pandarobot.chat) | admin / admin123 |
|
||||
|
||||
### Project Repositories
|
||||
|
||||
| Module | GitHub Repository | Gitee Repository | GitCode Repository |
|
||||
|------------------|-------------------------------------------------------|------------------------------------------------------|--------------------------------------------------------|
|
||||
| 🔧 Backend | [ruoyi-ai](https://github.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitee.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitcode.com/ageerle/ruoyi-ai) |
|
||||
| 🎨 User Frontend | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) |
|
||||
| 🛠️ Admin Panel | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) |
|
||||
| Module | GitHub Repository | Gitee Repository | GitCode Repository |
|
||||
|----------|-------------------------------------------------------|------------------------------------------------------|--------------------------------------------------------|
|
||||
| 🔧 Backend | [ruoyi-ai](https://github.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitee.com/ageerle/ruoyi-ai) | [ruoyi-ai](https://gitcode.com/ageerle/ruoyi-ai) |
|
||||
| 🎨 User Frontend | [ruoyi-web](https://github.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitee.com/ageerle/ruoyi-web) | [ruoyi-web](https://gitcode.com/ageerle/ruoyi-web) |
|
||||
| 🛠️ Admin Panel | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) |
|
||||
|
||||
### Partner Projects
|
||||
| Project Name | GitHub Repository | Gitee Repository |
|
||||
|----------------|-------------------------------------------------------|------------------------------------------------------|
|
||||
| element-plus-x | [element-plus-x](https://github.com/element-plus-x/Element-Plus-X) | [element-plus-x](https://gitee.com/he-jiayue/element-plus-x) |
|
||||
|
||||
## 🛠️ Technical Architecture
|
||||
|
||||
### Core Framework
|
||||
- **Backend**: Spring Boot 3.5 + Langchain4j
|
||||
- **Backend**: Spring Boot 4.0 + Spring AI 2.0 + Langchain4j
|
||||
- **Data Storage**: MySQL 8.0 + Redis + Vector Databases (Milvus/Weaviate/Qdrant)
|
||||
- **Frontend**: Vue 3 + Vben Admin + Element UI
|
||||
- **Frontend**: Vue 3 + Vben Admin + element-plus-x
|
||||
- **Security**: Sa-Token + JWT dual-layer security
|
||||
|
||||
### System Components
|
||||
- **Document Processing**: PDF, Word, and Excel parsing with intelligent image analysis
|
||||
- **Real-Time Communication**: WebSocket real-time communication with SSE streaming responses
|
||||
- **System Monitoring**: Comprehensive logging system, performance monitoring, and service health checks
|
||||
|
||||
- **Document Processing**: PDF, Word, Excel parsing, intelligent image analysis
|
||||
- **Real-time Communication**: WebSocket real-time communication, SSE streaming response
|
||||
- **System Monitoring**: Comprehensive logging system, performance monitoring, service health checks
|
||||
|
||||
## 🐳 Docker Deployment
|
||||
|
||||
This project provides two Docker deployment methods:
|
||||
|
||||
### Method 1: One-click Start All Services (Recommended)
|
||||
|
||||
Use `docker-compose-all.yaml` to start all services at once (including backend, admin panel, user frontend, and dependencies):
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/ageerle/ruoyi-ai.git
|
||||
cd ruoyi-ai
|
||||
|
||||
# Start all services (pull pre-built images from registry)
|
||||
docker-compose -f docker-compose-all.yaml up -d
|
||||
|
||||
# Check service status
|
||||
docker-compose -f docker-compose-all.yaml ps
|
||||
|
||||
# Access services
|
||||
# Admin Panel: http://localhost:25666 (admin / admin123)
|
||||
# User Frontend: http://localhost:25137
|
||||
# Backend API: http://localhost:26039
|
||||
```
|
||||
|
||||
### Method 2: Step-by-step Deployment (Source Build)
|
||||
|
||||
If you need to build backend services from source, follow these steps:
|
||||
|
||||
#### Step 1: Deploy Backend Service
|
||||
|
||||
```bash
|
||||
# Enter backend project directory
|
||||
cd ruoyi-ai
|
||||
|
||||
# Start backend service (build from source)
|
||||
docker-compose up -d --build
|
||||
|
||||
# Wait for backend service to start
|
||||
docker-compose logs -f backend
|
||||
```
|
||||
|
||||
#### Step 2: Deploy Admin Panel
|
||||
|
||||
```bash
|
||||
# Enter admin panel project directory
|
||||
cd ruoyi-admin
|
||||
|
||||
# Build and start admin panel
|
||||
docker-compose up -d --build
|
||||
|
||||
# Access admin panel
|
||||
# URL: http://localhost:5666
|
||||
```
|
||||
|
||||
#### Step 3: Deploy User Frontend (Optional)
|
||||
|
||||
```bash
|
||||
# Enter user frontend project directory
|
||||
cd ruoyi-web
|
||||
|
||||
# Build and start user frontend
|
||||
docker-compose up -d --build
|
||||
|
||||
# Access user frontend
|
||||
# URL: http://localhost:5137
|
||||
```
|
||||
|
||||
### Service Ports
|
||||
|
||||
| Service | One-click Port | Step-by-step Port | Description |
|
||||
|------|-------------|-------------|------|
|
||||
| Admin Panel | 25666 | 5666 | Admin backend access |
|
||||
| User Frontend | 25137 | 5137 | User frontend access |
|
||||
| Backend Service | 26039 | 6039 | Backend API service |
|
||||
| MySQL | 23306 | 23306 | Database service |
|
||||
| Redis | 26379 | 6379 | Cache service |
|
||||
| Weaviate | 28080 | 28080 | Vector database |
|
||||
| MinIO API | 29000 | 9000 | Object storage API |
|
||||
| MinIO Console | 29090 | 9090 | Object storage console |
|
||||
|
||||
### Image Registry
|
||||
|
||||
All images are hosted on Alibaba Cloud Container Registry:
|
||||
|
||||
```
|
||||
crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
|
||||
```
|
||||
|
||||
Available images:
|
||||
- `mysql:v3` - MySQL database (includes initialization SQL)
|
||||
- `redis:6.2` - Redis cache
|
||||
- `weaviate:1.30.0` - Vector database
|
||||
- `minio:latest` - Object storage
|
||||
- `ruoyi-ai-backend:latest` - Backend service
|
||||
- `ruoyi-ai-admin:latest` - Admin frontend
|
||||
- `ruoyi-ai-web:latest` - User frontend
|
||||
|
||||
### Common Commands
|
||||
|
||||
```bash
|
||||
# Stop all services
|
||||
docker-compose -f docker-compose-all.yaml down
|
||||
|
||||
# View service logs
|
||||
docker-compose -f docker-compose-all.yaml logs -f [service-name]
|
||||
|
||||
# Restart a service
|
||||
docker-compose -f docker-compose-all.yaml restart [service-name]
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
@@ -84,6 +192,12 @@ Want to learn more about installation, deployment, configuration, and secondary
|
||||
|
||||
**👉 [Complete Documentation](https://doc.pandarobot.chat)**
|
||||
|
||||
Experiencing issues with knowledge base or RAG responses?
|
||||
|
||||
**👉 [RAG Troubleshooting Guide](docs/troubleshooting/rag-failures.md)**
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We warmly welcome community contributions! Whether you are a seasoned developer or just getting started, you can contribute to the project 💪
|
||||
@@ -115,28 +229,13 @@ Thanks to the following excellent open-source projects for their support:
|
||||
- [PPIO Cloud](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai) - Provides cost-effective GPU computing and model API services
|
||||
- [Youyun Intelligent Computing](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - Thousands of RTX40 series GPUs + mainstream models API services, second-level response, pay-per-use, free for new customers.
|
||||
|
||||
## Outstanding Open-Source Projects and Community Recommendations
|
||||
- [imaiwork](https://gitee.com/tsinghua-open/imaiwork) - Open-source AI phone, AI customer acquisition phone project, based on accessibility mode and RPA, more powerful than Doubao AI phone.
|
||||
|
||||
## 💬 Community Chat
|
||||
|
||||
<div align="center">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="docs/image/wx.png" alt="WeChat QR Code" width="200" height="200"><br>
|
||||
<strong>Scan to Add Author's WeChat</strong><br>
|
||||
<em>Invitation to join the group</em>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="docs/image/qq.png" alt="QQ Group QR Code" width="200" height="200"><br>
|
||||
<strong>QQ Technical Discussion Group</strong><br>
|
||||
<em>Technical discussions</em>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
**[📱 Join Telegram Group](
|
||||
https://t.me/+LqooQAc5HxRmYmE1)**
|
||||
|
||||
</div>
|
||||
|
||||
@@ -170,4 +269,4 @@ Thanks to the following excellent open-source projects for their support:
|
||||
|
||||
[license-shield]: https://img.shields.io/github/license/ageerle/ruoyi-ai.svg?style=flat-square
|
||||
|
||||
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE
|
||||
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE
|
||||
|
||||
28
docs/docker/ minio/ docker-compose.yml
Normal file
28
docs/docker/ minio/ docker-compose.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: minio
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9090:9090"
|
||||
environment:
|
||||
- MINIO_ACCESS_KEY=ruoyi
|
||||
- MINIO_SECRET_KEY=ruoyi123
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
- minio_config:/root/.minio
|
||||
command: server /data --console-address ":9090"
|
||||
restart: always
|
||||
networks:
|
||||
- minio-net
|
||||
|
||||
networks:
|
||||
minio-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
minio_data:
|
||||
minio_config:
|
||||
65
docs/docker/ neo4j/docker-compose.yml
Normal file
65
docs/docker/ neo4j/docker-compose.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
neo4j:
|
||||
image: neo4j:5.15.0
|
||||
container_name: ruoyi-neo4j
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# HTTP端口
|
||||
- "7474:7474"
|
||||
# HTTPS端口
|
||||
- "7473:7473"
|
||||
# Bolt端口
|
||||
- "7687:7687"
|
||||
environment:
|
||||
# 初始密码设置(首次启动后需要修改)
|
||||
- NEO4J_AUTH=neo4j/your_password
|
||||
# 接受许可协议
|
||||
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
|
||||
# 内存配置(根据服务器配置调整)
|
||||
- NEO4J_dbms_memory_heap_initial__size=512m
|
||||
- NEO4J_dbms_memory_heap_max__size=2g
|
||||
- NEO4J_dbms_memory_pagecache_size=1g
|
||||
# 事务日志配置
|
||||
- NEO4J_dbms_tx__log_rotation_retention__policy=3 days
|
||||
# 允许从任何主机连接
|
||||
- NEO4J_dbms_default__listen__address=0.0.0.0
|
||||
# 启用APOC插件
|
||||
- NEO4J_dbms_security_procedures_unrestricted=apoc.*
|
||||
- NEO4J_dbms_security_procedures_allowlist=apoc.*
|
||||
# 日志级别
|
||||
- NEO4J_dbms_logs_debug_level=INFO
|
||||
volumes:
|
||||
# 数据持久化
|
||||
- neo4j_data:/data
|
||||
# 日志持久化
|
||||
- neo4j_logs:/logs
|
||||
# 导入目录
|
||||
- neo4j_import:/var/lib/neo4j/import
|
||||
# 插件目录
|
||||
- neo4j_plugins:/plugins
|
||||
networks:
|
||||
- ruoyi-network
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1" ]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 40s
|
||||
|
||||
volumes:
|
||||
neo4j_data:
|
||||
name: ruoyi-neo4j-data
|
||||
neo4j_logs:
|
||||
name: ruoyi-neo4j-logs
|
||||
neo4j_import:
|
||||
name: ruoyi-neo4j-import
|
||||
neo4j_plugins:
|
||||
name: ruoyi-neo4j-plugins
|
||||
|
||||
networks:
|
||||
ruoyi-network:
|
||||
name: ruoyi-network
|
||||
driver: bridge
|
||||
|
||||
75
docs/docker/milvus/docker-compose.yml
Normal file
75
docs/docker/milvus/docker-compose.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
etcd:
|
||||
container_name: milvus-etcd
|
||||
image: quay.io/coreos/etcd:v3.5.18
|
||||
environment:
|
||||
- ETCD_AUTO_COMPACTION_MODE=revision
|
||||
- ETCD_AUTO_COMPACTION_RETENTION=1000
|
||||
- ETCD_QUOTA_BACKEND_BYTES=4294967296
|
||||
- ETCD_SNAPSHOT_COUNT=50000
|
||||
volumes:
|
||||
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
|
||||
command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
|
||||
healthcheck:
|
||||
test: ["CMD", "etcdctl", "endpoint", "health"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
|
||||
minio:
|
||||
container_name: milvus-minio
|
||||
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
|
||||
environment:
|
||||
MINIO_ACCESS_KEY: minioadmin
|
||||
MINIO_SECRET_KEY: minioadmin
|
||||
ports:
|
||||
- "9001:9001"
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
|
||||
command: minio server /minio_data --console-address ":9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
|
||||
standalone:
|
||||
container_name: milvus-standalone
|
||||
image: milvusdb/milvus:v2.5.7
|
||||
command: ["milvus", "run", "standalone"]
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
environment:
|
||||
ETCD_ENDPOINTS: etcd:2379
|
||||
MINIO_ADDRESS: minio:9000
|
||||
volumes:
|
||||
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
|
||||
interval: 30s
|
||||
start_period: 90s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
ports:
|
||||
- "19530:19530"
|
||||
- "9091:9091"
|
||||
depends_on:
|
||||
- "etcd"
|
||||
- "minio"
|
||||
|
||||
attu:
|
||||
container_name: attu
|
||||
image: zilliz/attu:v2.5.7
|
||||
environment:
|
||||
MILVUS_URL: milvus-standalone:19530
|
||||
ports:
|
||||
- "19500:3000" # 外部端口19500可以自定义
|
||||
depends_on:
|
||||
- "standalone"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: milvus
|
||||
12
docs/docker/qdrant/docker-compose.yml
Normal file
12
docs/docker/qdrant/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
services:
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
ports:
|
||||
- 6333:6333
|
||||
- 6334:6334
|
||||
volumes:
|
||||
- qdrant_data:/qdrant/storage
|
||||
volumes:
|
||||
qdrant_data:
|
||||
...
|
||||
36
docs/docker/ruoyi-ai/Dockerfile.backend
Normal file
36
docs/docker/ruoyi-ai/Dockerfile.backend
Normal file
@@ -0,0 +1,36 @@
|
||||
# RuoYi-AI 后端 Dockerfile
|
||||
# 基于 Maven + OpenJDK 17
|
||||
|
||||
FROM maven:3.9-eclipse-temurin-17 AS builder
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /build
|
||||
|
||||
# 复制 pom.xml 和源码
|
||||
COPY pom.xml .
|
||||
COPY ruoyi-admin ./ruoyi-admin
|
||||
COPY ruoyi-common ./ruoyi-common
|
||||
COPY ruoyi-modules ./ruoyi-modules
|
||||
COPY ruoyi-extend ./ruoyi-extend
|
||||
|
||||
|
||||
# 构建项目 (使用 prod profile)
|
||||
RUN mvn clean package -Pprod -DskipTests
|
||||
|
||||
# 最终运行镜像
|
||||
FROM eclipse-temurin:17-jre-alpine
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 从构建阶段复制 jar 包
|
||||
COPY --from=builder /build/ruoyi-admin/target/ruoyi-admin.jar ./ruoyi-admin.jar
|
||||
|
||||
# 创建日志目录
|
||||
RUN mkdir -p /ruoyi/server/logs
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 6039
|
||||
|
||||
# 启动命令
|
||||
ENTRYPOINT ["java", "-jar", "ruoyi-admin.jar", "--spring.profiles.active=prod"]
|
||||
21
docs/docker/ruoyi-ai/Dockerfile.mysql
Normal file
21
docs/docker/ruoyi-ai/Dockerfile.mysql
Normal file
@@ -0,0 +1,21 @@
|
||||
# 基于官方MySQL 8.0镜像构建自定义镜像
|
||||
# 构建命令: docker build -t registry.cn-hangzhou.aliyuncs.com/ruoyi-ai/mysql:v3 -f Dockerfile.mysql .
|
||||
FROM mysql:8.0.33
|
||||
|
||||
# 设置时区
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
# 复制初始化脚本和SQL文件到镜像中
|
||||
COPY docs/script/docker/mysql/init/init-db.sh /docker-entrypoint-initdb.d/init-db.sh
|
||||
COPY docs/script/sql/ruoyi-ai-v3_mysql8.sql /docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql
|
||||
|
||||
# 设置脚本可执行权限
|
||||
RUN chmod +x /docker-entrypoint-initdb.d/init-db.sh
|
||||
|
||||
# MySQL启动参数
|
||||
CMD ["--default-authentication-plugin=mysql_native_password", \
|
||||
"--character-set-server=utf8mb4", \
|
||||
"--collation-server=utf8mb4_general_ci", \
|
||||
"--explicit_defaults_for_timestamp=true", \
|
||||
"--lower_case_table_names=1", \
|
||||
"--skip-ssl"]
|
||||
180
docs/docker/ruoyi-ai/docker-compose-all.yaml
Normal file
180
docs/docker/ruoyi-ai/docker-compose-all.yaml
Normal file
@@ -0,0 +1,180 @@
|
||||
# RuoYi-AI 一键启动全部服务
|
||||
# 使用方式: docker-compose up -d
|
||||
#
|
||||
# 包含服务:
|
||||
# - MySQL 8.0 (数据库,包含初始化SQL)
|
||||
# - Redis 6.2 (缓存)
|
||||
# - Weaviate (向量数据库)
|
||||
# - MinIO (对象存储)
|
||||
# - RuoYi-Backend (后端服务)
|
||||
# - RuoYi-Admin (管理端前端)
|
||||
# - RuoYi-Web (用户端前端)
|
||||
#
|
||||
# 镜像仓库地址: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# ==================== MySQL 数据库 ====================
|
||||
mysql:
|
||||
# 阿里云镜像地址(包含初始化SQL)
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/mysql:v3
|
||||
container_name: ruoyi-ai-mysql
|
||||
restart: always
|
||||
ports:
|
||||
- "23306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: ruoyi-ai-agent
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== Redis 缓存 ====================
|
||||
redis:
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/redis:6.2
|
||||
container_name: ruoyi-ai-redis
|
||||
restart: always
|
||||
ports:
|
||||
- "26379:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== Weaviate 向量数据库 ====================
|
||||
weaviate:
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/weaviate:1.30.0
|
||||
container_name: ruoyi-ai-weaviate
|
||||
restart: always
|
||||
ports:
|
||||
- "28080:8080"
|
||||
environment:
|
||||
QUERY_DEFAULTS_LIMIT: 25
|
||||
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: true
|
||||
PERSISTENCE_DATA_PATH: /var/lib/weaviate
|
||||
DEFAULT_VECTORIZER_MODULE: none
|
||||
ENABLE_MODULES: text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai
|
||||
CLUSTER_HOSTNAME: node1
|
||||
volumes:
|
||||
- weaviate-data:/var/lib/weaviate
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== MinIO 对象存储 ====================
|
||||
minio:
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/minio:latest
|
||||
container_name: ruoyi-ai-minio
|
||||
restart: always
|
||||
ports:
|
||||
- "29000:9000"
|
||||
- "29090:9090"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ruoyi
|
||||
MINIO_ROOT_PASSWORD: ruoyi123
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
command: server /data --console-address ":9090"
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== RuoYi-AI 后端服务 ====================
|
||||
backend:
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/ruoyi-ai-backend:latest
|
||||
container_name: ruoyi-ai-backend
|
||||
restart: always
|
||||
ports:
|
||||
- "26039:6039"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
# MySQL 配置
|
||||
SPRING_DATASOURCE_DYNAMIC_PRIMARY: master
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://mysql:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: root
|
||||
# Redis 配置
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
SPRING_DATA_REDIS_DATABASE: 0
|
||||
# 日志配置
|
||||
LOGGING_LEVEL_ORG_RUOYI: info
|
||||
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK: warn
|
||||
SYS_UPLOAD_PATH: /ruoyi/upload
|
||||
volumes:
|
||||
- logs-data:/ruoyi/server/logs
|
||||
- upload-data:/ruoyi/upload
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== RuoYi-AI 管理端前端 ====================
|
||||
admin-frontend:
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/ruoyi-ai-admin:latest
|
||||
container_name: ruoyi-ai-admin
|
||||
restart: always
|
||||
ports:
|
||||
- "25666:5666"
|
||||
environment:
|
||||
# 后端 API 地址 - 运行时动态配置(无需重新构建镜像)
|
||||
# nginx upstream 配置不需要 http:// 前缀,直接使用 host:port
|
||||
UPSTREAM_HOST: backend:6039
|
||||
# 资源限制 - 防止 CPU 和内存耗尽
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 3G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== RuoYi-AI 用户端前端 ====================
|
||||
web-frontend:
|
||||
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/ruoyi-ai-web:latest
|
||||
container_name: ruoyi-ai-web
|
||||
restart: always
|
||||
ports:
|
||||
- "25137:5137"
|
||||
environment:
|
||||
UPSTREAM_URL: http://backend:6039
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# ==================== 网络配置 ====================
|
||||
networks:
|
||||
ruoyi-net:
|
||||
driver: bridge
|
||||
|
||||
# ==================== 数据卷配置 ====================
|
||||
volumes:
|
||||
mysql-data:
|
||||
redis-data:
|
||||
weaviate-data:
|
||||
minio-data:
|
||||
logs-data:
|
||||
upload-data:
|
||||
144
docs/docker/ruoyi-ai/docker-compose.yaml
Normal file
144
docs/docker/ruoyi-ai/docker-compose.yaml
Normal file
@@ -0,0 +1,144 @@
|
||||
# RuoYi-AI 一键启动后端服务
|
||||
# 使用方式: docker-compose up -d --build
|
||||
#
|
||||
# 包含服务:
|
||||
# - MySQL 8.0 (数据库)
|
||||
# - Redis 6.2 (缓存)
|
||||
# - Weaviate (向量数据库)
|
||||
# - MinIO (对象存储)
|
||||
# - RuoYi-Backend (后端服务,源码编译)
|
||||
|
||||
services:
|
||||
# MySQL 数据库
|
||||
mysql:
|
||||
image: mysql:8.0.33
|
||||
container_name: ruoyi-ai-mysql
|
||||
restart: always
|
||||
ports:
|
||||
- "23306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: ruoyi-ai-agent
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- ./docs/script/docker/mysql/init/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro
|
||||
- ./docs/script/sql/ruoyi-ai-v3_mysql8.sql:/docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql:ro
|
||||
- mysql-data:/var/lib/mysql
|
||||
command:
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--explicit_defaults_for_timestamp=true
|
||||
--lower_case_table_names=1
|
||||
--skip-ssl
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"]
|
||||
interval: 15s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 60s
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# Redis 缓存
|
||||
redis:
|
||||
image: redis:6.2
|
||||
container_name: ruoyi-ai-redis
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# Weaviate 向量数据库
|
||||
weaviate:
|
||||
image: semitechnologies/weaviate:1.30.0
|
||||
container_name: ruoyi-ai-weaviate
|
||||
restart: always
|
||||
ports:
|
||||
- "28080:8080"
|
||||
environment:
|
||||
QUERY_DEFAULTS_LIMIT: 25
|
||||
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: true
|
||||
PERSISTENCE_DATA_PATH: /var/lib/weaviate
|
||||
DEFAULT_VECTORIZER_MODULE: none
|
||||
ENABLE_MODULES: text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai
|
||||
CLUSTER_HOSTNAME: node1
|
||||
volumes:
|
||||
- weaviate-data:/var/lib/weaviate
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# MinIO 对象存储
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: ruoyi-ai-minio
|
||||
restart: always
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9090:9090"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ruoyi
|
||||
MINIO_ROOT_PASSWORD: ruoyi123
|
||||
volumes:
|
||||
- minio-data:/data
|
||||
command: server /data --console-address ":9090"
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
# RuoYi-AI 后端服务 (源码编译)
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.backend
|
||||
container_name: ruoyi-ai-backend
|
||||
restart: always
|
||||
ports:
|
||||
- "26039:6039"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
# MySQL 配置
|
||||
SPRING_DATASOURCE_DYNAMIC_PRIMARY: master
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://mysql:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root
|
||||
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: root
|
||||
# Redis 配置
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
SPRING_DATA_REDIS_PORT: 6379
|
||||
SPRING_DATA_REDIS_DATABASE: 0
|
||||
# 日志配置
|
||||
LOGGING_LEVEL_ORG_RUOYI: info
|
||||
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK: warn
|
||||
SYS_UPLOAD_PATH: /ruoyi/upload # 新增:对应 sys.upload.path
|
||||
volumes:
|
||||
- logs-data:/ruoyi/server/logs
|
||||
- upload-data:/ruoyi/upload
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
networks:
|
||||
- ruoyi-net
|
||||
|
||||
networks:
|
||||
ruoyi-net:
|
||||
driver: bridge
|
||||
|
||||
# 数据卷 支持手动指定 空为默认值
|
||||
volumes:
|
||||
mysql-data:
|
||||
redis-data:
|
||||
weaviate-data:
|
||||
minio-data:
|
||||
logs-data:
|
||||
upload-data:
|
||||
25
docs/docker/weaviate/docker-compose.yml
Normal file
25
docs/docker/weaviate/docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
services:
|
||||
weaviate:
|
||||
command:
|
||||
- --host
|
||||
- 0.0.0.0
|
||||
- --port
|
||||
- '6038'
|
||||
- --scheme
|
||||
- http
|
||||
image: semitechnologies/weaviate:1.19.7
|
||||
ports:
|
||||
- 6038:6038
|
||||
- 50051:50051
|
||||
volumes:
|
||||
- weaviate_data:/var/lib/weaviate
|
||||
environment:
|
||||
QUERY_DEFAULTS_LIMIT: 25
|
||||
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
|
||||
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
|
||||
DEFAULT_VECTORIZER_MODULE: 'none'
|
||||
CLUSTER_HOSTNAME: 'node1'
|
||||
volumes:
|
||||
weaviate_data:
|
||||
...
|
||||
BIN
docs/image/bibi.png
Normal file
BIN
docs/image/bibi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/image/dy.png
Normal file
BIN
docs/image/dy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
10
docs/script/docker/mysql/init/init-db.sh
Normal file
10
docs/script/docker/mysql/init/init-db.sh
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
# 数据库初始化脚本
|
||||
# 使用 --force 参数确保即使出错也继续执行
|
||||
|
||||
echo "开始初始化数据库..."
|
||||
|
||||
# 使用 --force 参数忽略错误继续执行
|
||||
mysql -uroot -proot ruoyi-ai-agent --force < /docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql
|
||||
|
||||
echo "数据库初始化完成"
|
||||
@@ -1,161 +0,0 @@
|
||||
worker_processes 1;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
# 可以根据业务并发量适当调高
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
# 高效传输文件
|
||||
sendfile on;
|
||||
# 长连接超时时间
|
||||
keepalive_timeout 65;
|
||||
# 单连接最大请求数,提高长连接复用率
|
||||
keepalive_requests 100000;
|
||||
# 限制body大小
|
||||
client_max_body_size 100m;
|
||||
client_header_buffer_size 32k;
|
||||
client_body_buffer_size 512k;
|
||||
# 开启静态资源压缩
|
||||
gzip_static on;
|
||||
# 连接数限制 (防御类配置) 10m 一般够用了,能存储上万 IP 的计数
|
||||
limit_conn_zone $binary_remote_addr zone=perip:10m;
|
||||
limit_conn_zone $server_name zone=perserver:10m;
|
||||
# 隐藏 nginx 版本号,防止暴露版本信息
|
||||
server_tokens off;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
upstream server {
|
||||
ip_hash;
|
||||
server 127.0.0.1:8080;
|
||||
server 127.0.0.1:8081;
|
||||
}
|
||||
|
||||
upstream monitor-admin {
|
||||
server 127.0.0.1:9090;
|
||||
}
|
||||
|
||||
upstream snailjob-server {
|
||||
server 127.0.0.1:8800;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# https配置参考 start
|
||||
#listen 443 ssl;
|
||||
|
||||
# 证书直接存放 /docker/nginx/cert/ 目录下即可 更改证书名称即可 无需更改证书路径
|
||||
#ssl on;
|
||||
#ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为docker映射路径 不允许更改
|
||||
#ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改
|
||||
#ssl_session_timeout 5m;
|
||||
#ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
|
||||
#ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
|
||||
#ssl_prefer_server_ciphers on;
|
||||
# https配置参考 end
|
||||
|
||||
# 演示环境配置 拦截除 GET POST 之外的所有请求
|
||||
# if ($request_method !~* GET|POST) {
|
||||
# rewrite ^/(.*)$ /403;
|
||||
# }
|
||||
|
||||
# location = /403 {
|
||||
# default_type application/json;
|
||||
# return 200 '{"msg":"演示模式,不允许操作","code":500}';
|
||||
# }
|
||||
|
||||
# 限制外网访问内网 actuator 相关路径
|
||||
location ~ ^(/[^/]*)?/actuator.*(/.*)?$ {
|
||||
return 403;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html; # docker映射路径 不允许更改
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /prod-api/ {
|
||||
# 设置客户端请求头中的 Host 信息(保持原始 Host)
|
||||
proxy_set_header Host $http_host;
|
||||
# 获取客户端真实 IP
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# 自定义头 REMOTE-HOST,记录客户端 IP
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
# 获取完整的客户端 IP 链(经过多级代理时)
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# 设置后端响应超时时间(这里是 24 小时,适合长连接/SSE)
|
||||
proxy_read_timeout 86400s;
|
||||
# SSE (Server-Sent Events) 与 WebSocket 支持参数
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
# 禁用代理缓冲,数据直接传给客户端
|
||||
proxy_buffering off;
|
||||
# 禁用代理缓存
|
||||
proxy_cache off;
|
||||
# 按 IP 限制连接数(防 CC 攻击) 小型站:10~20 就够 中型站:50~100
|
||||
limit_conn perip 20;
|
||||
|
||||
# 按 Server 限制总并发连接数 根据服务器的最大并发处理能力来定 太小会限制合法用户访问,太大会占满服务器资源
|
||||
limit_conn perserver 500;
|
||||
proxy_pass http://server/;
|
||||
}
|
||||
|
||||
# https 会拦截内链所有的 http 请求 造成功能无法使用
|
||||
# 解决方案1 将 admin 服务 也配置成 https
|
||||
# 解决方案2 将菜单配置为外链访问 走独立页面 http 访问
|
||||
location /admin/ {
|
||||
# 设置客户端请求头中的 Host 信息(保持原始 Host)
|
||||
proxy_set_header Host $http_host;
|
||||
# 获取客户端真实 IP
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# 自定义头 REMOTE-HOST,记录客户端 IP
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
# 获取完整的客户端 IP 链(经过多级代理时)
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# 禁用代理缓冲,数据直接传给客户端
|
||||
proxy_buffering off;
|
||||
# 禁用代理缓存
|
||||
proxy_cache off;
|
||||
proxy_pass http://monitor-admin/admin/;
|
||||
}
|
||||
|
||||
location /snail-job/ {
|
||||
# 设置客户端请求头中的 Host 信息(保持原始 Host)
|
||||
proxy_set_header Host $http_host;
|
||||
# 获取客户端真实 IP
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
# 自定义头 REMOTE-HOST,记录客户端 IP
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
# 获取完整的客户端 IP 链(经过多级代理时)
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# SSE (Server-Sent Events) 与 WebSocket 支持参数
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
# 禁用代理缓冲,直接传输给客户端
|
||||
proxy_buffering off;
|
||||
# 禁用代理缓存
|
||||
proxy_cache off;
|
||||
proxy_pass http://snailjob-server/snail-job/;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
# redis 密码
|
||||
requirepass ruoyi123
|
||||
|
||||
# key 监听器配置
|
||||
# notify-keyspace-events Ex
|
||||
|
||||
# 配置持久化文件存储路径
|
||||
dir /redis/data
|
||||
# 配置rdb
|
||||
# 15分钟内有至少1个key被更改则进行快照
|
||||
save 900 1
|
||||
# 5分钟内有至少10个key被更改则进行快照
|
||||
save 300 10
|
||||
# 1分钟内有至少10000个key被更改则进行快照
|
||||
save 60 10000
|
||||
# 开启压缩
|
||||
rdbcompression yes
|
||||
# rdb文件名 用默认的即可
|
||||
dbfilename dump.rdb
|
||||
|
||||
# 开启aof
|
||||
appendonly yes
|
||||
# 文件名
|
||||
appendfilename "appendonly.aof"
|
||||
# 持久化策略,no:不同步,everysec:每秒一次,always:总是同步,速度比较慢
|
||||
# appendfsync always
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
@@ -1 +0,0 @@
|
||||
数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据
|
||||
@@ -1,85 +1,23 @@
|
||||
/*
|
||||
Navicat MySQL Dump SQL
|
||||
|
||||
Source Server : 本地
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80045 (8.0.45)
|
||||
Source Host : localhost:3306
|
||||
Source Schema : ruoyi-ai
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80045 (8.0.45)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 15/03/2026 23:56:19
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for aihuman_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `aihuman_config`;
|
||||
CREATE TABLE `aihuman_config` (
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`model_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`model_params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||
`agent_params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||
`create_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||
`status` int NULL DEFAULT NULL,
|
||||
`publish` int NULL DEFAULT NULL,
|
||||
`create_dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`update_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '数字人配置信息' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of aihuman_config
|
||||
-- ----------------------------
|
||||
INSERT INTO `aihuman_config` VALUES (9, '关爱老婆数字人(梅朵)', '梅朵吉祥物', '/Live2D/models/梅朵吉祥物/梅朵吉祥物.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"梅朵吉祥物.moc3\",\n \"Textures\": [\n \"梅朵吉祥物.4096/texture_00.png\",\n \"梅朵吉祥物.4096/texture_01.png\"\n ],\n \"Physics\": \"梅朵吉祥物.physics3.json\",\n \"DisplayInfo\": \"梅朵吉祥物.cdi3.json\",\n \"MotionSync\": \"梅朵吉祥物.motionsync3.json\",\n \"Expressions\": [\n {\n \"Name\": \"kaixin\",\n \"File\": \"kaixin.exp3.json\"\n },\n {\n \"Name\": \"maozi\",\n \"File\": \"maozi.exp3.json\"\n },\n {\n \"Name\": \"mouth open\",\n \"File\": \"mouth open.exp3.json\"\n },\n {\n \"Name\": \"shibai\",\n \"File\": \"shibai.exp3.json\"\n },\n {\n \"Name\": \"yinchen\",\n \"File\": \"yinchen.exp3.json\"\n }\n ],\n \"Motions\": {\n \"\": [\n {\n \"File\": \"mouth.motion3.json\"\n }\n ]\n }\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthForm\",\n \"ParamMouthOpenY\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ],\n \"HitAreas\": []\n}', '{\n \"bot_id\": \"7504596188201746470\",\n \"user_id\": \"7376476310010937396\",\n \"stream\": true,\n \"auto_save_history\": true\n}', '2025-09-29 16:36:46', '2025-09-29 16:36:46', 0, 1, NULL, NULL, '1', 0);
|
||||
INSERT INTO `aihuman_config` VALUES (10, '关爱老婆数字人(K)', 'kei_vowels_pro', '/Live2D/models/kei_vowels_pro/kei_vowels_pro.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"kei_vowels_pro.moc3\",\n \"Textures\": [\n \"kei_vowels_pro.2048/texture_00.png\"\n ],\n \"Physics\": \"kei_vowels_pro.physics3.json\",\n \"DisplayInfo\": \"kei_vowels_pro.cdi3.json\",\n \"MotionSync\": \"kei_vowels_pro.motionsync3.json\",\n \"Motions\": {\n \"\": [\n {\n \"File\": \"motions/01_kei_en.motion3.json\",\n \"Sound\": \"sounds/01_kei_en.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_jp.motion3.json\",\n \"Sound\": \"sounds/01_kei_jp.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_ko.motion3.json\",\n \"Sound\": \"sounds/01_kei_ko.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n },\n {\n \"File\": \"motions/01_kei_zh.motion3.json\",\n \"Sound\": \"sounds/01_kei_zh.wav\",\n \"MotionSync\": \"Vowels_CRI\"\n }\n ]\n }\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": []\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ],\n \"HitAreas\": [\n {\n \"Id\": \"HitAreaHead\",\n \"Name\": \"Head\"\n }\n ]\n}', '3', '2025-09-29 16:35:27', '2025-09-29 16:35:27', 0, 1, NULL, NULL, '1', 0);
|
||||
INSERT INTO `aihuman_config` VALUES (11, '关爱老婆数字人(March 7th)', 'March 7th', '/Live2D/models/March 7th/March 7th.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"March 7th.moc3\",\n \"Textures\": [\n \"March 7th.4096/texture_00.png\",\n \"March 7th.4096/texture_01.png\"\n ],\n \"Physics\": \"March 7th.physics3.json\",\n \"DisplayInfo\": \"March 7th.cdi3.json\",\n \"Expressions\": [\n {\n \"Name\": \"捂脸\",\n \"File\": \"1.exp3.json\"\n },\n {\n \"Name\": \"比耶\",\n \"File\": \"2.exp3.json\"\n },\n {\n \"Name\": \"照相\",\n \"File\": \"3.exp3.json\"\n },\n {\n \"Name\": \"脸红\",\n \"File\": \"4.exp3.json\"\n },\n {\n \"Name\": \"黑脸\",\n \"File\": \"5.exp3.json\"\n },\n {\n \"Name\": \"哭\",\n \"File\": \"6.exp3.json\"\n },\n {\n \"Name\": \"流汗\",\n \"File\": \"7.exp3.json\"\n },\n {\n \"Name\": \"星星\",\n \"File\": \"8.exp3.json\"\n }\n ]\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthOpenY\"\n ]\n }\n ],\n \"HitAreas\": []\n}', '3', '2025-09-29 21:09:26', '2025-09-29 21:09:28', 0, 1, NULL, NULL, NULL, 0);
|
||||
INSERT INTO `aihuman_config` VALUES (12, '关爱老婆数字人(pachan)', 'pachan', '/Live2D/models/pachan/pachan.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"pachirisu anime girl - top half.moc3\",\n \"Textures\": [\n \"pachirisu anime girl - top half.4096/texture_00.png\"\n ],\n \"Physics\": \"pachirisu anime girl - top half.physics3.json\",\n \"DisplayInfo\": \"pachirisu anime girl - top half.cdi3.json\"\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": []\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": []\n }\n ]\n}', NULL, '2025-10-05 19:49:56', '2025-10-05 19:49:56', 0, 1, NULL, NULL, NULL, 0);
|
||||
INSERT INTO `aihuman_config` VALUES (13, '关爱老婆数字人(230108)', '230108', '/Live2D/models/230108/230108.model3.json', '{\n \"Version\": 3,\n \"FileReferences\": {\n \"Moc\": \"230108.moc3\",\n \"Textures\": [\n \"230108.4096/texture_00.png\"\n ],\n \"Physics\": \"230108.physics3.json\",\n \"DisplayInfo\": \"230108.cdi3.json\"\n },\n \"Groups\": [\n {\n \"Target\": \"Parameter\",\n \"Name\": \"LipSync\",\n \"Ids\": [\n \"ParamMouthOpenY\"\n ]\n },\n {\n \"Target\": \"Parameter\",\n \"Name\": \"EyeBlink\",\n \"Ids\": [\n \"ParamEyeLOpen\",\n \"ParamEyeROpen\"\n ]\n }\n ]\n}', NULL, '2025-10-06 19:28:20', '2025-10-06 19:28:23', 0, 1, NULL, NULL, NULL, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for aihuman_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `aihuman_info`;
|
||||
CREATE TABLE `aihuman_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '交互名称',
|
||||
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '交互内容',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'AI人类交互信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of aihuman_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `aihuman_info` VALUES (1, '1', '1', '2025-09-26 18:02:00', '2025-09-26 18:02:02', '0', 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `chat_config`;
|
||||
CREATE TABLE `chat_config` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`category` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置类型',
|
||||
`config_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置名称',
|
||||
`config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置值',
|
||||
`config_dict` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '说明',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`version` int NULL DEFAULT NULL COMMENT '版本',
|
||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
`update_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '更新IP',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `unique_category_key`(`category` ASC, `config_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '配置信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of chat_config
|
||||
-- ----------------------------
|
||||
-- 忽略所有错误,继续执行
|
||||
SET sql_mode = '';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_message
|
||||
@@ -91,10 +29,8 @@ CREATE TABLE `chat_message` (
|
||||
`user_id` bigint NOT NULL COMMENT '用户id',
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '消息内容',
|
||||
`role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '对话角色',
|
||||
`deduct_cost` double(20, 2) NULL DEFAULT 0.00 COMMENT '扣除金额',
|
||||
`total_tokens` int NULL DEFAULT 0 COMMENT '累计 Tokens',
|
||||
`model_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型名称',
|
||||
`billing_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '计费类型(1-token计费,2-次数计费)',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
@@ -119,11 +55,8 @@ CREATE TABLE `chat_model` (
|
||||
`model_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型名称',
|
||||
`provider_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型供应商',
|
||||
`model_describe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型描述',
|
||||
`model_price` double NULL DEFAULT NULL COMMENT '模型价格',
|
||||
`model_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '计费类型',
|
||||
`model_dimension` int NULL DEFAULT NULL COMMENT '模型维度',
|
||||
`model_show` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '是否显示',
|
||||
`model_free` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '是否免费',
|
||||
`priority` int NULL DEFAULT 1 COMMENT '模型优先级(值越大优先级越高)',
|
||||
`api_host` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求地址',
|
||||
`api_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密钥',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
@@ -139,8 +72,8 @@ CREATE TABLE `chat_model` (
|
||||
-- ----------------------------
|
||||
-- Records of chat_model
|
||||
-- ----------------------------
|
||||
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'openai', 'deepseek', 1, '1', 'Y', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-02-06 01:02:31', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
||||
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'openai', 'bge-m3', 0, '1', 'N', 'Y', 1, 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-02-06 01:02:35', 'bge-large-zh-v1.5', 0);
|
||||
INSERT INTO `chat_model` VALUES (2000585866022060033, 'chat', 'deepseek/deepseek-v3.2', 'ppio', 'deepseek', NULL, 'Y', 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2025-12-15 23:16:54', 1, '2026-03-15 19:18:48', 'DeepSeek-V3.2 是一款在高效推理、复杂推理能力与智能体场景中表现突出的领先模型。其基于 DeepSeek Sparse Attention(DSA)稀疏注意力机制,在显著降低计算开销的同时优化长上下文性能;通过可扩展强化学习框架,整体能力达到 GPT-5 同级,高算力版本 V3.2-Speciale 更在推理表现上接近 Gemini-3.0-Pro;同时,模型依托大型智能体任务合成管线,具备更强的工具调用与多步骤决策能力,并在 2025 年 IMO 与 IOI 中取得金牌级表现。作为 MaaS 平台,我们已对 DeepSeek-V3.2 完成深度适配,通过动态调度、批处理加速、低延迟推理与企业级 SLA 保障,进一步增强其在企业生产环境中的稳定性、性价比与可控性,适用于搜索、问答、智能体、代码、数据处理等多类高价值场景。', 0);
|
||||
INSERT INTO `chat_model` VALUES (2007528268536287233, 'vector', 'baai/bge-m3', 'ppio', 'bge-m3', 1024, 'N', 'https://api.ppinfra.com/openai', 'sk_xx', 103, 1, '2026-01-04 03:03:32', 1, '2026-03-15 19:18:51', 'BGE-M3 是一款具备多维度能力的文本嵌入模型,可同时实现密集检索、多向量检索和稀疏检索三大核心功能。该模型设计上兼容超过100种语言,并支持从短句到长达8192词元的长文本等多种输入形式。在跨语言检索任务中,BGE-M3展现出显著优势,其性能在MIRACL、MKQA等国际基准测试中位居前列。此外,针对长文档检索场景,该模型在MLDR、NarritiveQA等数据集上的表现同样达到行业领先水平。', 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_provider
|
||||
@@ -173,11 +106,11 @@ CREATE TABLE `chat_provider` (
|
||||
-- ----------------------------
|
||||
-- Records of chat_provider
|
||||
-- ----------------------------
|
||||
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/9d944a6abfcd46e2bd6e364f07202589.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-01-22 15:05:55', 'OpenAI厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/039ad13f690649f0ade139f8c803727b.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:58:22', '阿里云厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (1, 'OpenAI', 'openai', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'OpenAI官方API服务商', 'https://api.openai.com', '0', 1, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:46:59', 'OpenAI厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2, '阿里云百炼', 'qianwen', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '阿里云百炼大模型服务', 'https://dashscope.aliyuncs.com', '0', 2, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:49:13', '阿里云厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (3, '智谱AI', 'zhipu', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/a43e98fb7b3b4861b8caa6184e6fa40a.png', '智谱AI大模型服务', 'https://open.bigmodel.cn', '0', 3, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-06 00:49:14', '智谱AI厂商', NULL, '1', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/2ff984bc9e4249df992733b31959056b.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2025-12-15 00:49:05', 'ollama厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyi-ai-1254149996.cos.ap-guangzhou.myqcloud.com/2025/12/15/c4f8e304ce7740029b0024934d4625bc.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-01-02 00:54:45', 'api聚合厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (5, 'ollama', 'ollama', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama大模型', 'http://127.0.0.1:11434', '0', 5, NULL, '2025-12-14 21:48:11', '1', '1', '2026-02-25 20:48:48', 'ollama厂商', NULL, '0', NULL, 0);
|
||||
INSERT INTO `chat_provider` VALUES (2000585060904435714, 'PPIO', 'ppio', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'api聚合厂商', 'https://api.ppinfra.com/openai', '0', 5, 103, '2025-12-15 23:13:42', '1', '1', '2026-02-25 20:49:01', 'api聚合厂商', NULL, '0', NULL, 0);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for chat_session
|
||||
@@ -274,11 +207,11 @@ CREATE TABLE `flow_definition` (
|
||||
-- Records of flow_definition
|
||||
-- ----------------------------
|
||||
INSERT INTO `flow_definition` VALUES (2008122523722002434, 'leave1', '请假申请-普通', 'CLASSICS', '103', '1', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:24:53', '1', '2026-01-05 18:25:52', '1', '1', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008122793122148354, 'leave2', '请假申请-排他网关', 'CLASSICS', '103', '1', 1, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:25:58', '1', '2026-01-06 13:53:34', '1', '0', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008122793122148354, 'leave2', '请假申请-排他网关', 'CLASSICS', '103', '1', 1, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:25:58', '1', '2026-03-10 21:24:36', '1', '1', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008125302444208129, 'leave1', '请假申请-普通', 'CLASSICS', '103', '1', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:35:56', '1', '2026-01-05 18:38:00', '1', '0', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008125415698804737, 'leave3', '请假申请-并行网关', 'CLASSICS', '103', '1', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:36:23', '1', '2026-01-05 18:36:23', '1', '0', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008125510645264385, 'leave4', '请假申请-会签', 'CLASSICS', '103', '1', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '0', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008125553481691138, 'leave5', '请假申请-并行会签网关', 'MIMIC', '103', '1', 1, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:36:56', '1', '2026-01-28 10:41:46', '1', '0', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008125553481691138, 'leave5', '请假申请-并行会签网关', 'MIMIC', '103', '1', 1, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:36:56', '1', '2026-03-10 21:24:34', '1', '1', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008125579910000641, 'leave6', '请假申请-排他并行会签', 'CLASSICS', '103', '1', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-05 18:37:02', '1', '2026-01-06 13:57:50', '1', '1', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008416817060646913, 'leave2', '请假申请-排他网关', 'CLASSICS', '103', '2', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-06 13:54:19', '1', '2026-01-06 13:54:31', '1', '0', '000000');
|
||||
INSERT INTO `flow_definition` VALUES (2008418755936391170, 'level2', 'test', 'CLASSICS', '105', '1', 0, 'N', '/workflow/leaveEdit/index', 1, NULL, NULL, NULL, '2026-01-06 14:02:01', '1', '2026-01-06 14:02:01', '1', '0', '000000');
|
||||
@@ -466,13 +399,13 @@ INSERT INTO `flow_node` VALUES (2008122524263067650, 1, 2008122523722002434, 'dd
|
||||
INSERT INTO `flow_node` VALUES (2008122524263067651, 1, 2008122523722002434, '78fa8e5b-e809-44ed-978a-41092409ebcf', '组长', 'role:1', '0.000', NULL, '540,200|540,200', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:24:54', '1', '2026-01-05 18:24:54', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,copy,transfer,trust,file\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122524263067652, 1, 2008122523722002434, 'a8abf15f-b83e-428a-86cc-033555ea9bbe', '部门主管', 'role:3@@role:4', '0.000', NULL, '720,200|720,200', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:24:54', '1', '2026-01-05 18:24:54', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,copy,transfer,trust,file\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122524263067653, 2, 2008122523722002434, '8b82b7d7-8660-455e-b880-d6d22ea3eb6d', '结束', NULL, '0.000', NULL, '900,200|900,200', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:24:54', '1', '2026-01-05 18:24:54', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047746, 0, 2008122793122148354, 'cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a', '开始', NULL, '0.000', NULL, '300,240|300,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047747, 1, 2008122793122148354, 'fdcae93b-b69c-498a-b231-09255e74bcbd', '申请人', '', '0.000', NULL, '440,240|440,240', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047748, 3, 2008122793122148354, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', NULL, NULL, '0.000', NULL, '560,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047749, 1, 2008122793122148354, 'b3528155-dcb7-4445-bbdf-3d00e3499e86', '组长', '3@@4', '0.000', NULL, '720,320|720,320', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047750, 1, 2008122793122148354, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', '总经理', 'role:1', '0.000', NULL, '860,240|860,240', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047751, 2, 2008122793122148354, '40aa65fd-0712-4d23-b6f7-d0432b920fd1', '结束', NULL, '0.000', NULL, '1000,240|1000,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047752, 1, 2008122793122148354, '5ed2362b-fc0c-4d52-831f-95208b830605', '部门领导', 'role:1', '0.000', NULL, '720,160|720,160', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047746, 0, 2008122793122148354, 'cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a', '开始', NULL, '0.000', NULL, '300,240|300,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047747, 1, 2008122793122148354, 'fdcae93b-b69c-498a-b231-09255e74bcbd', '申请人', '', '0.000', NULL, '440,240|440,240', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047748, 3, 2008122793122148354, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', NULL, NULL, '0.000', NULL, '560,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047749, 1, 2008122793122148354, 'b3528155-dcb7-4445-bbdf-3d00e3499e86', '组长', '3@@4', '0.000', NULL, '720,320|720,320', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047750, 1, 2008122793122148354, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', '总经理', 'role:1', '0.000', NULL, '860,240|860,240', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047751, 2, 2008122793122148354, '40aa65fd-0712-4d23-b6f7-d0432b920fd1', '结束', NULL, '0.000', NULL, '1000,240|1000,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008122793638047752, 1, 2008122793122148354, '5ed2362b-fc0c-4d52-831f-95208b830605', '部门领导', 'role:1', '0.000', NULL, '720,160|720,160', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125302918164481, 0, 2008125302444208129, 'd5ee3ddf-3968-4379-a86f-9ceabde5faac', '开始', NULL, '0.000', NULL, '200,200|200,200', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:35:56', '1', '2026-01-05 18:35:56', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125302918164482, 1, 2008125302444208129, 'dd515cdd-59f6-446f-94ca-25ca062afb42', '申请人', '', '0.000', NULL, '360,200|360,200', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:35:56', '1', '2026-01-05 18:35:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,copy\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125302918164483, 1, 2008125302444208129, '78fa8e5b-e809-44ed-978a-41092409ebcf', '组长', 'role:1', '0.000', NULL, '540,200|540,200', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:35:56', '1', '2026-01-05 18:35:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,copy,transfer,trust,file\"}]', '0', '000000', NULL);
|
||||
@@ -492,14 +425,14 @@ INSERT INTO `flow_node` VALUES (2008125511194718212, 1, 2008125510645264385, '76
|
||||
INSERT INTO `flow_node` VALUES (2008125511194718213, 1, 2008125510645264385, '2f9f2e21-9bcf-42a3-a07c-13037aad22d1', '全部审批通过', 'role:1@@role:3', '100.000', NULL, '820,240|820,240', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125511194718214, 1, 2008125510645264385, '27461e01-3d9f-4530-8fe3-bd5ec7f9571f', 'CEO', '1', '0.000', NULL, '1000,240|1000,240', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125511194718215, 2, 2008125510645264385, 'b62b88c3-8d8d-4969-911e-2aaea219e7fc', '结束', NULL, '0.000', NULL, '1120,240|1120,240', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893698, 0, 2008125553481691138, 'ebebaf26-9cb6-497e-8119-4c9fed4c597c', '开始', NULL, '0.000', NULL, '300,220|300,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893699, 1, 2008125553481691138, 'e1b04e96-dc81-4858-a309-2fe945d2f374', '申请人', '', '0.000', NULL, '420,220|420,220', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893700, 4, 2008125553481691138, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', NULL, NULL, '0.000', NULL, '560,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893701, 1, 2008125553481691138, 'c80f273e-1f17-4bd8-9ad1-04a4a94ea862', '会签', 'role:1@@role:3', '100.000', NULL, '700,320|700,320', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893702, 4, 2008125553481691138, '1a20169e-3d82-4926-a151-e2daad28de1b', NULL, NULL, '0.000', NULL, '860,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893703, 1, 2008125553481691138, '7a8f0473-e409-442e-a843-5c2b813d00e9', 'CEO', '1', '0.000', NULL, '1000,220|1000,220', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893704, 2, 2008125553481691138, '03c4d2bc-58b5-4408-a2e4-65afb046f169', '结束', NULL, '0.000', NULL, '1140,220|1140,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893705, 1, 2008125553481691138, '1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4', '百分之60票签', '${userList}', '60.000', NULL, '700,120|700,120', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]', '0', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893698, 0, 2008125553481691138, 'ebebaf26-9cb6-497e-8119-4c9fed4c597c', '开始', NULL, '0.000', NULL, '300,220|300,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893699, 1, 2008125553481691138, 'e1b04e96-dc81-4858-a309-2fe945d2f374', '申请人', '', '0.000', NULL, '420,220|420,220', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893700, 4, 2008125553481691138, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', NULL, NULL, '0.000', NULL, '560,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893701, 1, 2008125553481691138, 'c80f273e-1f17-4bd8-9ad1-04a4a94ea862', '会签', 'role:1@@role:3', '100.000', NULL, '700,320|700,320', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893702, 4, 2008125553481691138, '1a20169e-3d82-4926-a151-e2daad28de1b', NULL, NULL, '0.000', NULL, '860,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893703, 1, 2008125553481691138, '7a8f0473-e409-442e-a843-5c2b813d00e9', 'CEO', '1', '0.000', NULL, '1000,220|1000,220', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,transfer,trust,copy\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893704, 2, 2008125553481691138, '03c4d2bc-58b5-4408-a2e4-65afb046f169', '结束', NULL, '0.000', NULL, '1140,220|1140,220', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125554068893705, 1, 2008125553481691138, '1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4', '百分之60票签', '${userList}', '60.000', NULL, '700,120|700,120', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file,addSign,subSign\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125580362985474, 0, 2008125579910000641, '122b89a5-7c6f-40a3-aa09-7a263f902054', '开始', NULL, '0.000', NULL, '240,300|240,300', NULL, NULL, NULL, 'N', NULL, '1', '2026-01-05 18:37:02', '1', '2026-01-05 18:37:02', '1', '[]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125580362985475, 1, 2008125579910000641, 'c25a0e86-fdd1-4f03-8e22-14db70389dbd', '申请人', '', '0.000', NULL, '400,300|400,300', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:37:02', '1', '2026-01-05 18:37:02', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination,file\"}]', '1', '000000', NULL);
|
||||
INSERT INTO `flow_node` VALUES (2008125580362985476, 1, 2008125579910000641, '2bfa3919-78cf-4bc1-b59b-df463a4546f9', '副经理', 'role:1@@role:3@@role:4', '0.000', NULL, '860,200|860,200', NULL, '', '', 'N', NULL, '1', '2026-01-05 18:37:02', '1', '2026-01-05 18:37:02', '1', '[{\"code\":\"ButtonPermissionEnum\",\"value\":\"back,termination\"}]', '1', '000000', NULL);
|
||||
@@ -730,13 +663,13 @@ INSERT INTO `flow_skip` VALUES (2008122525873680386, 2008122523722002434, 'd5ee3
|
||||
INSERT INTO `flow_skip` VALUES (2008122525873680387, 2008122523722002434, 'dd515cdd-59f6-446f-94ca-25ca062afb42', 1, '78fa8e5b-e809-44ed-978a-41092409ebcf', 1, NULL, 'PASS', NULL, '410,200;490,200', '2026-01-05 18:24:54', '1', '2026-01-05 18:24:54', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122525873680388, 2008122523722002434, '78fa8e5b-e809-44ed-978a-41092409ebcf', 1, 'a8abf15f-b83e-428a-86cc-033555ea9bbe', 1, NULL, 'PASS', NULL, '590,200;670,200', '2026-01-05 18:24:54', '1', '2026-01-05 18:24:54', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122525873680389, 2008122523722002434, 'a8abf15f-b83e-428a-86cc-033555ea9bbe', 1, '8b82b7d7-8660-455e-b880-d6d22ea3eb6d', 2, NULL, 'PASS', NULL, '770,200;880,200', '2026-01-05 18:24:54', '1', '2026-01-05 18:24:54', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536449, 2008122793122148354, 'cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a', 0, 'fdcae93b-b69c-498a-b231-09255e74bcbd', 1, NULL, 'PASS', NULL, '320,240;390,240', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536450, 2008122793122148354, 'fdcae93b-b69c-498a-b231-09255e74bcbd', 1, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', 3, NULL, 'PASS', NULL, '490,240;535,240', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536451, 2008122793122148354, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', 3, 'b3528155-dcb7-4445-bbdf-3d00e3499e86', 1, NULL, 'PASS', 'le@@leaveDays|2', '560,265;560,320;670,320', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536452, 2008122793122148354, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', 3, '5ed2362b-fc0c-4d52-831f-95208b830605', 1, '大于两天', 'PASS', 'gt@@leaveDays|2', '560,215;560,160;670,160|560,187', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536453, 2008122793122148354, 'b3528155-dcb7-4445-bbdf-3d00e3499e86', 1, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', 1, NULL, 'PASS', NULL, '770,320;860,320;860,280', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536454, 2008122793122148354, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', 1, '40aa65fd-0712-4d23-b6f7-d0432b920fd1', 2, NULL, 'PASS', NULL, '910,240;980,240', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536455, 2008122793122148354, '5ed2362b-fc0c-4d52-831f-95208b830605', 1, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', 1, NULL, 'PASS', NULL, '770,160;860,160;860,200', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536449, 2008122793122148354, 'cef3895c-f7d8-4598-8bf3-8ec2ef6ce84a', 0, 'fdcae93b-b69c-498a-b231-09255e74bcbd', 1, NULL, 'PASS', NULL, '320,240;390,240', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536450, 2008122793122148354, 'fdcae93b-b69c-498a-b231-09255e74bcbd', 1, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', 3, NULL, 'PASS', NULL, '490,240;535,240', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536451, 2008122793122148354, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', 3, 'b3528155-dcb7-4445-bbdf-3d00e3499e86', 1, NULL, 'PASS', 'le@@leaveDays|2', '560,265;560,320;670,320', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536452, 2008122793122148354, '7b8c7ead-7dc8-4951-a7f3-f0c41995909e', 3, '5ed2362b-fc0c-4d52-831f-95208b830605', 1, '大于两天', 'PASS', 'gt@@leaveDays|2', '560,215;560,160;670,160|560,187', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536453, 2008122793122148354, 'b3528155-dcb7-4445-bbdf-3d00e3499e86', 1, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', 1, NULL, 'PASS', NULL, '770,320;860,320;860,280', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536454, 2008122793122148354, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', 1, '40aa65fd-0712-4d23-b6f7-d0432b920fd1', 2, NULL, 'PASS', NULL, '910,240;980,240', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008122795634536455, 2008122793122148354, '5ed2362b-fc0c-4d52-831f-95208b830605', 1, 'c9fa6d7d-2a74-4e78-b947-0cad8a6af869', 1, NULL, 'PASS', NULL, '770,160;860,160;860,200', '2026-01-05 18:25:58', '1', '2026-01-05 18:25:58', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125304423919617, 2008125302444208129, 'd5ee3ddf-3968-4379-a86f-9ceabde5faac', 0, 'dd515cdd-59f6-446f-94ca-25ca062afb42', 1, NULL, 'PASS', NULL, '220,200;310,200', '2026-01-05 18:35:56', '1', '2026-01-05 18:35:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125304423919618, 2008125302444208129, 'dd515cdd-59f6-446f-94ca-25ca062afb42', 1, '78fa8e5b-e809-44ed-978a-41092409ebcf', 1, NULL, 'PASS', NULL, '410,200;490,200', '2026-01-05 18:35:56', '1', '2026-01-05 18:35:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125304423919619, 2008125302444208129, '78fa8e5b-e809-44ed-978a-41092409ebcf', 1, 'a8abf15f-b83e-428a-86cc-033555ea9bbe', 1, NULL, 'PASS', NULL, '590,200;670,200', '2026-01-05 18:35:56', '1', '2026-01-05 18:35:56', '1', '0', '000000');
|
||||
@@ -754,14 +687,14 @@ INSERT INTO `flow_skip` VALUES (2008125512977297411, 2008125510645264385, 'e90b9
|
||||
INSERT INTO `flow_skip` VALUES (2008125512977297412, 2008125510645264385, '768b5b1a-6726-4d67-8853-4cc70d5b1045', 1, '2f9f2e21-9bcf-42a3-a07c-13037aad22d1', 1, NULL, 'PASS', NULL, '690,240;770,240', '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125512977297413, 2008125510645264385, '2f9f2e21-9bcf-42a3-a07c-13037aad22d1', 1, '27461e01-3d9f-4530-8fe3-bd5ec7f9571f', 1, NULL, 'PASS', NULL, '870,240;950,240', '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125512977297414, 2008125510645264385, '27461e01-3d9f-4530-8fe3-bd5ec7f9571f', 1, 'b62b88c3-8d8d-4969-911e-2aaea219e7fc', 2, NULL, 'PASS', NULL, '1050,240;1080,240;1080,240;1070,240;1070,240;1100,240', '2026-01-05 18:36:46', '1', '2026-01-05 18:36:46', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869762, 2008125553481691138, 'ebebaf26-9cb6-497e-8119-4c9fed4c597c', 0, 'e1b04e96-dc81-4858-a309-2fe945d2f374', 1, NULL, 'PASS', NULL, '320,220;350,220;350,220;340,220;340,220;370,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869763, 2008125553481691138, 'e1b04e96-dc81-4858-a309-2fe945d2f374', 1, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', 4, NULL, 'PASS', NULL, '470,220;535,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869764, 2008125553481691138, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', 4, 'c80f273e-1f17-4bd8-9ad1-04a4a94ea862', 1, NULL, 'PASS', NULL, '560,245;560,320;650,320', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869765, 2008125553481691138, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', 4, '1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4', 1, NULL, 'PASS', NULL, '560,195;560,120;650,120', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869766, 2008125553481691138, 'c80f273e-1f17-4bd8-9ad1-04a4a94ea862', 1, '1a20169e-3d82-4926-a151-e2daad28de1b', 4, NULL, 'PASS', NULL, '750,320;860,320;860,245', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869767, 2008125553481691138, '1a20169e-3d82-4926-a151-e2daad28de1b', 4, '7a8f0473-e409-442e-a843-5c2b813d00e9', 1, NULL, 'PASS', NULL, '885,220;950,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869768, 2008125553481691138, '7a8f0473-e409-442e-a843-5c2b813d00e9', 1, '03c4d2bc-58b5-4408-a2e4-65afb046f169', 2, NULL, 'PASS', NULL, '1050,220;1120,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869769, 2008125553481691138, '1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4', 1, '1a20169e-3d82-4926-a151-e2daad28de1b', 4, NULL, 'PASS', NULL, '750,120;860,120;860,195', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '0', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869762, 2008125553481691138, 'ebebaf26-9cb6-497e-8119-4c9fed4c597c', 0, 'e1b04e96-dc81-4858-a309-2fe945d2f374', 1, NULL, 'PASS', NULL, '320,220;350,220;350,220;340,220;340,220;370,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869763, 2008125553481691138, 'e1b04e96-dc81-4858-a309-2fe945d2f374', 1, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', 4, NULL, 'PASS', NULL, '470,220;535,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869764, 2008125553481691138, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', 4, 'c80f273e-1f17-4bd8-9ad1-04a4a94ea862', 1, NULL, 'PASS', NULL, '560,245;560,320;650,320', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869765, 2008125553481691138, '3e743f4f-51ca-41d4-8e94-21f5dd9b59c9', 4, '1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4', 1, NULL, 'PASS', NULL, '560,195;560,120;650,120', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869766, 2008125553481691138, 'c80f273e-1f17-4bd8-9ad1-04a4a94ea862', 1, '1a20169e-3d82-4926-a151-e2daad28de1b', 4, NULL, 'PASS', NULL, '750,320;860,320;860,245', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869767, 2008125553481691138, '1a20169e-3d82-4926-a151-e2daad28de1b', 4, '7a8f0473-e409-442e-a843-5c2b813d00e9', 1, NULL, 'PASS', NULL, '885,220;950,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869768, 2008125553481691138, '7a8f0473-e409-442e-a843-5c2b813d00e9', 1, '03c4d2bc-58b5-4408-a2e4-65afb046f169', 2, NULL, 'PASS', NULL, '1050,220;1120,220', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125556442869769, 2008125553481691138, '1e3e8d3b-18ae-4d6c-a814-ce0d724adfa4', 1, '1a20169e-3d82-4926-a151-e2daad28de1b', 4, NULL, 'PASS', NULL, '750,120;860,120;860,195', '2026-01-05 18:36:56', '1', '2026-01-05 18:36:56', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125584578260994, 2008125579910000641, '122b89a5-7c6f-40a3-aa09-7a263f902054', 0, 'c25a0e86-fdd1-4f03-8e22-14db70389dbd', 1, NULL, 'PASS', NULL, '260,300;350,300', '2026-01-05 18:37:03', '1', '2026-01-05 18:37:03', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125584578260995, 2008125579910000641, 'c25a0e86-fdd1-4f03-8e22-14db70389dbd', 1, '07ecda1d-7a0a-47b5-8a91-6186c9473742', 1, NULL, 'PASS', NULL, '450,300;510,300', '2026-01-05 18:37:03', '1', '2026-01-05 18:37:03', '1', '1', '000000');
|
||||
INSERT INTO `flow_skip` VALUES (2008125584578260996, 2008125579910000641, '2bfa3919-78cf-4bc1-b59b-df463a4546f9', 1, '394e1cc8-b8b2-4189-9f81-44448e88ac32', 3, NULL, 'PASS', NULL, '910,200;1000,200;1000,275', '2026-01-05 18:37:03', '1', '2026-01-05 18:37:03', '1', '1', '000000');
|
||||
@@ -1135,171 +1068,6 @@ INSERT INTO `gen_table_column` VALUES (2018961776721399811, 2018961776515878913,
|
||||
INSERT INTO `gen_table_column` VALUES (2018961776721399812, 2018961776515878913, 'open_id', '微信用户标识', 'varchar(100)', 'String', 'openId', '0', '0', '0', '1', '1', '1', '1', 'EQ', 'input', '', 22, 103, 1, '2026-02-04 16:16:13', 1, '2026-02-04 16:20:23');
|
||||
INSERT INTO `gen_table_column` VALUES (2018961776721399813, 2018961776515878913, 'user_balance', '账户余额', 'double(20,2)', 'Long', 'userBalance', '0', '0', '0', '1', '1', '1', '1', 'EQ', 'input', '', 23, 103, 1, '2026-02-04 16:16:13', 1, '2026-02-04 16:20:23');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for graph_build_task
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `graph_build_task`;
|
||||
CREATE TABLE `graph_build_task` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`task_uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务UUID',
|
||||
`graph_uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图谱UUID',
|
||||
`knowledge_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '知识库ID',
|
||||
`doc_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档ID(可选,null表示全量构建)',
|
||||
`task_type` tinyint NULL DEFAULT 1 COMMENT '任务类型:1全量构建、2增量更新、3重建',
|
||||
`task_status` tinyint NULL DEFAULT 1 COMMENT '任务状态:1待执行、2执行中、3成功、4失败',
|
||||
`progress` int NULL DEFAULT 0 COMMENT '进度百分比(0-100)',
|
||||
`total_docs` int NULL DEFAULT 0 COMMENT '总文档数',
|
||||
`processed_docs` int NULL DEFAULT 0 COMMENT '已处理文档数',
|
||||
`extracted_entities` int NULL DEFAULT 0 COMMENT '提取的实体数',
|
||||
`extracted_relations` int NULL DEFAULT 0 COMMENT '提取的关系数',
|
||||
`error_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息',
|
||||
`result_summary` json NULL COMMENT '结果摘要(JSON格式)',
|
||||
`start_time` datetime NULL DEFAULT NULL COMMENT '开始时间',
|
||||
`end_time` datetime NULL DEFAULT NULL COMMENT '结束时间',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_task_uuid`(`task_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_graph_uuid`(`graph_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_knowledge_id`(`knowledge_id` ASC) USING BTREE,
|
||||
INDEX `idx_task_status`(`task_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_time`(`create_time` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '图谱构建任务表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of graph_build_task
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for graph_entity_type
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `graph_entity_type`;
|
||||
CREATE TABLE `graph_entity_type` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`type_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '实体类型名称',
|
||||
`type_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '类型编码',
|
||||
`description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
|
||||
`color` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '#1890ff' COMMENT '可视化颜色',
|
||||
`icon` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图标',
|
||||
`sort` int NULL DEFAULT 0 COMMENT '显示顺序',
|
||||
`is_enable` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用(0否 1是)',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_type_code`(`type_code` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '图谱实体类型定义表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of graph_entity_type
|
||||
-- ----------------------------
|
||||
INSERT INTO `graph_entity_type` VALUES (1, '人物', 'PERSON', '人物实体,包括真实人物和虚拟角色', '#1890ff', 'user', 1, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (2, '机构', 'ORGANIZATION', '组织机构,包括公司、政府机构等', '#52c41a', 'bank', 2, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (3, '地点', 'LOCATION', '地理位置,包括国家、城市、地址等', '#fa8c16', 'environment', 3, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (4, '概念', 'CONCEPT', '抽象概念,包括理论、方法等', '#722ed1', 'bulb', 4, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (5, '事件', 'EVENT', '事件记录,包括历史事件、活动等', '#eb2f96', 'calendar', 5, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (6, '产品', 'PRODUCT', '产品或服务', '#13c2c2', 'shopping', 6, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (7, '技术', 'TECHNOLOGY', '技术或工具', '#2f54eb', 'tool', 7, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_entity_type` VALUES (8, '文档', 'DOCUMENT', '文档或资料', '#faad14', 'file-text', 8, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for graph_query_history
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `graph_query_history`;
|
||||
CREATE TABLE `graph_query_history` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`query_uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '查询UUID',
|
||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
||||
`knowledge_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '知识库ID',
|
||||
`graph_uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图谱UUID',
|
||||
`query_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '查询文本',
|
||||
`query_type` tinyint NULL DEFAULT 1 COMMENT '查询类型:1实体查询、2关系查询、3路径查询、4混合查询',
|
||||
`cypher_query` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '生成的Cypher查询',
|
||||
`result_count` int NULL DEFAULT 0 COMMENT '结果数量',
|
||||
`response_time` int NULL DEFAULT 0 COMMENT '响应时间(ms)',
|
||||
`is_success` tinyint(1) NULL DEFAULT 1 COMMENT '是否成功(0否 1是)',
|
||||
`error_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_query_uuid`(`query_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_knowledge_id`(`knowledge_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_time`(`create_time` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '图谱查询历史表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of graph_query_history
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for graph_relation_type
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `graph_relation_type`;
|
||||
CREATE TABLE `graph_relation_type` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`relation_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '关系名称',
|
||||
`relation_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '关系编码',
|
||||
`description` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述',
|
||||
`direction` tinyint(1) NULL DEFAULT 1 COMMENT '关系方向:0双向、1单向',
|
||||
`style` json NULL COMMENT '可视化样式(JSON格式)',
|
||||
`sort` int NULL DEFAULT 0 COMMENT '显示顺序',
|
||||
`is_enable` tinyint(1) NULL DEFAULT 1 COMMENT '是否启用(0否 1是)',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_relation_code`(`relation_code` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '图谱关系类型定义表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of graph_relation_type
|
||||
-- ----------------------------
|
||||
INSERT INTO `graph_relation_type` VALUES (1, '属于', 'BELONGS_TO', '隶属关系,表示从属或归属', 1, NULL, 1, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (2, '位于', 'LOCATED_IN', '地理位置关系', 1, NULL, 2, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (3, '相关', 'RELATED_TO', '一般关联关系', 0, NULL, 3, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (4, '导致', 'CAUSES', '因果关系', 1, NULL, 4, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (5, '包含', 'CONTAINS', '包含关系', 1, NULL, 5, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (6, '提及', 'MENTIONS', '文档提及实体的关系', 1, NULL, 6, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (7, '部分', 'PART_OF', '部分关系', 1, NULL, 7, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (8, '实例', 'INSTANCE_OF', '实例关系', 1, NULL, 8, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (9, '相似', 'SIMILAR_TO', '相似关系', 0, NULL, 9, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (10, '前序', 'PRECEDES', '时序关系', 1, NULL, 10, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (11, '工作于', 'WORKS_AT', '人物与机构的工作关系', 1, NULL, 11, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (12, '创建', 'CREATED_BY', '创建关系', 1, NULL, 12, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
INSERT INTO `graph_relation_type` VALUES (13, '使用', 'USES', '使用关系', 1, NULL, 13, 1, '', '2025-11-07 16:33:37', '', '2025-11-07 16:33:37', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for graph_statistics
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `graph_statistics`;
|
||||
CREATE TABLE `graph_statistics` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`graph_uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图谱UUID',
|
||||
`stat_date` date NOT NULL COMMENT '统计日期',
|
||||
`total_nodes` int NULL DEFAULT 0 COMMENT '总节点数',
|
||||
`total_relationships` int NULL DEFAULT 0 COMMENT '总关系数',
|
||||
`node_type_distribution` json NULL COMMENT '节点类型分布(JSON格式)',
|
||||
`relation_type_distribution` json NULL COMMENT '关系类型分布(JSON格式)',
|
||||
`query_count` int NULL DEFAULT 0 COMMENT '查询次数',
|
||||
`avg_query_time` int NULL DEFAULT 0 COMMENT '平均查询时间(ms)',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_graph_date`(`graph_uuid` ASC, `stat_date` ASC) USING BTREE,
|
||||
INDEX `idx_stat_date`(`stat_date` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '图谱统计信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of graph_statistics
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for knowledge_attach
|
||||
-- ----------------------------
|
||||
@@ -1320,7 +1088,7 @@ CREATE TABLE `knowledge_attach` (
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `idx_kname`(`knowledge_id` ASC, `name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2016797369199366146 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库附件' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2033199209203183619 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库附件' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of knowledge_attach
|
||||
@@ -1343,81 +1111,12 @@ CREATE TABLE `knowledge_fragment` (
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2016797369027399683 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识片段' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2033199209131880451 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识片段' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of knowledge_fragment
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for knowledge_graph_instance
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `knowledge_graph_instance`;
|
||||
CREATE TABLE `knowledge_graph_instance` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`graph_uuid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图谱UUID',
|
||||
`knowledge_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '关联knowledge_info.kid',
|
||||
`graph_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '图谱名称',
|
||||
`graph_status` tinyint NULL DEFAULT 10 COMMENT '构建状态:10构建中、20已完成、30失败',
|
||||
`node_count` int NULL DEFAULT 0 COMMENT '节点数量',
|
||||
`relationship_count` int NULL DEFAULT 0 COMMENT '关系数量',
|
||||
`config` json NULL COMMENT '图谱配置(JSON格式)',
|
||||
`model_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'LLM模型名称',
|
||||
`entity_types` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '实体类型(逗号分隔)',
|
||||
`relation_types` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '关系类型(逗号分隔)',
|
||||
`error_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '错误信息',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '' COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_graph_uuid`(`graph_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_knowledge_id`(`knowledge_id` ASC) USING BTREE,
|
||||
INDEX `idx_graph_status`(`graph_status` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识图谱实例表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of knowledge_graph_instance
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for knowledge_graph_segment
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `knowledge_graph_segment`;
|
||||
CREATE TABLE `knowledge_graph_segment` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '片段UUID',
|
||||
`kb_uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '知识库UUID',
|
||||
`kb_item_uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '知识库条目UUID',
|
||||
`doc_uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '文档UUID',
|
||||
`segment_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '片段文本内容',
|
||||
`chunk_index` int NULL DEFAULT 0 COMMENT '片段索引(第几个片段)',
|
||||
`total_chunks` int NULL DEFAULT 1 COMMENT '总片段数',
|
||||
`extraction_status` tinyint NULL DEFAULT 0 COMMENT '抽取状态:0-待处理 1-处理中 2-已完成 3-失败',
|
||||
`entity_count` int NULL DEFAULT 0 COMMENT '抽取的实体数量',
|
||||
`relation_count` int NULL DEFAULT 0 COMMENT '抽取的关系数量',
|
||||
`token_used` int NULL DEFAULT 0 COMMENT '消耗的token数',
|
||||
`error_message` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '错误信息',
|
||||
`user_id` bigint NULL DEFAULT NULL COMMENT '用户ID',
|
||||
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_uuid`(`uuid` ASC) USING BTREE,
|
||||
INDEX `idx_kb_uuid`(`kb_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_kb_item_uuid`(`kb_item_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_doc_uuid`(`doc_uuid` ASC) USING BTREE,
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_time`(`create_time` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识图谱片段表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of knowledge_graph_segment
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for knowledge_info
|
||||
-- ----------------------------
|
||||
@@ -1442,12 +1141,99 @@ CREATE TABLE `knowledge_info` (
|
||||
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户Id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2018245281372573699 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2033198818050781187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '知识库' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of knowledge_info
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for mcp_market_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `mcp_market_info`;
|
||||
CREATE TABLE `mcp_market_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '市场ID',
|
||||
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '市场名称',
|
||||
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '市场URL',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '市场描述',
|
||||
`auth_config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '认证配置(JSON格式)',
|
||||
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED-启用, DISABLED-禁用',
|
||||
`tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_name`(`name` ASC) USING BTREE,
|
||||
INDEX `idx_status`(`status` ASC) USING BTREE,
|
||||
INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of mcp_market_info
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for mcp_market_tool
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `mcp_market_tool`;
|
||||
CREATE TABLE `mcp_market_tool` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||
`market_id` bigint NOT NULL COMMENT '市场ID',
|
||||
`tool_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工具名称',
|
||||
`tool_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '工具描述',
|
||||
`tool_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '工具版本',
|
||||
`tool_metadata` json NULL COMMENT '工具元数据(JSON格式)',
|
||||
`is_loaded` tinyint(1) NULL DEFAULT 0 COMMENT '是否已加载到本地',
|
||||
`local_tool_id` bigint NULL DEFAULT NULL COMMENT '关联的本地工具ID',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_market_id`(`market_id` ASC) USING BTREE,
|
||||
INDEX `idx_tool_name`(`tool_name` ASC) USING BTREE,
|
||||
INDEX `idx_is_loaded`(`is_loaded` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP市场工具关联表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of mcp_market_tool
|
||||
-- ----------------------------
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for mcp_tool_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `mcp_tool_info`;
|
||||
CREATE TABLE `mcp_tool_info` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '工具ID',
|
||||
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工具名称',
|
||||
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '工具描述',
|
||||
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'LOCAL' COMMENT '工具类型:LOCAL-本地, REMOTE-远程, BUILTIN-内置',
|
||||
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'ENABLED' COMMENT '状态:ENABLED-启用, DISABLED-禁用',
|
||||
`config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '配置信息(JSON格式)',
|
||||
`tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
`create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
|
||||
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` bigint NULL DEFAULT NULL COMMENT '更新者',
|
||||
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_name`(`name` ASC) USING BTREE,
|
||||
INDEX `idx_type`(`type` ASC) USING BTREE,
|
||||
INDEX `idx_status`(`status` ASC) USING BTREE,
|
||||
INDEX `idx_tenant_id`(`tenant_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MCP工具表' ROW_FORMAT = Dynamic;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of mcp_tool_info
|
||||
-- ----------------------------
|
||||
INSERT INTO `mcp_tool_info` VALUES (1, 'edit_file', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-03-10 21:21:09', '0');
|
||||
INSERT INTO `mcp_tool_info` VALUES (2, 'list_directory', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-03-10 21:21:09', '0');
|
||||
INSERT INTO `mcp_tool_info` VALUES (3, 'read_file', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-02-24 20:19:41', -1, '2026-03-10 21:21:09', '0');
|
||||
INSERT INTO `mcp_tool_info` VALUES (4, 'query_all_tables', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-03-10 21:21:09', '0');
|
||||
INSERT INTO `mcp_tool_info` VALUES (5, 'execute_sql_query', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-03-10 21:21:09', '0');
|
||||
INSERT INTO `mcp_tool_info` VALUES (6, 'query_table_schema', '', 'BUILTIN', 'ENABLED', NULL, '000000', -1, -1, '2026-03-10 21:21:09', -1, '2026-03-10 21:21:09', '0');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sj_distributed_lock
|
||||
-- ----------------------------
|
||||
@@ -1460,7 +1246,7 @@ CREATE TABLE `sj_distributed_lock` (
|
||||
`create_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`name`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '锁定表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '锁定表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_distributed_lock
|
||||
@@ -1485,7 +1271,7 @@ CREATE TABLE `sj_group_config` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '组配置' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '组配置' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_group_config
|
||||
@@ -1531,7 +1317,7 @@ CREATE TABLE `sj_job` (
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_job_status_bucket_index`(`job_status` ASC, `bucket_index` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务信息' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务信息' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job
|
||||
@@ -1553,7 +1339,7 @@ CREATE TABLE `sj_job_executor` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务执行器信息' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务执行器信息' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_executor
|
||||
@@ -1579,7 +1365,7 @@ CREATE TABLE `sj_job_log_message` (
|
||||
INDEX `idx_task_batch_id_task_id`(`task_batch_id` ASC, `task_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '调度日志' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '调度日志' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_log_message
|
||||
@@ -1608,7 +1394,7 @@ CREATE TABLE `sj_job_summary` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_trigger_at_system_task_type_business_id`(`trigger_at` ASC, `system_task_type` ASC, `business_id` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name_business_id`(`namespace_id` ASC, `group_name` ASC, `business_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Job' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Job' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_summary
|
||||
@@ -1642,7 +1428,7 @@ CREATE TABLE `sj_job_task` (
|
||||
INDEX `idx_task_batch_id_task_status`(`task_batch_id` ASC, `task_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务实例' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务实例' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_task
|
||||
@@ -1674,7 +1460,7 @@ CREATE TABLE `sj_job_task_batch` (
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_workflow_task_batch_id_workflow_node_id`(`workflow_task_batch_id` ASC, `workflow_node_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务批次' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务批次' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_job_task_batch
|
||||
@@ -1695,7 +1481,7 @@ CREATE TABLE `sj_namespace` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_unique_id`(`unique_id` ASC) USING BTREE,
|
||||
INDEX `idx_name`(`name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '命名空间' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '命名空间' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_namespace
|
||||
@@ -1724,7 +1510,7 @@ CREATE TABLE `sj_notify_config` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name_scene_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知配置' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通知配置' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_notify_config
|
||||
@@ -1745,7 +1531,7 @@ CREATE TABLE `sj_notify_recipient` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id`(`namespace_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '告警通知接收人' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '告警通知接收人' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_notify_recipient
|
||||
@@ -1784,7 +1570,7 @@ CREATE TABLE `sj_retry` (
|
||||
INDEX `idx_retry_status_bucket_index`(`retry_status` ASC, `bucket_index` ASC) USING BTREE,
|
||||
INDEX `idx_parent_id`(`parent_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试信息表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试信息表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry
|
||||
@@ -1813,7 +1599,7 @@ CREATE TABLE `sj_retry_dead_letter` (
|
||||
INDEX `idx_idempotent_id`(`idempotent_id` ASC) USING BTREE,
|
||||
INDEX `idx_biz_no`(`biz_no` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '死信队列表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '死信队列表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_dead_letter
|
||||
@@ -1848,7 +1634,7 @@ CREATE TABLE `sj_retry_scene_config` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_namespace_id_group_name_scene_name`(`namespace_id` ASC, `group_name` ASC, `scene_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景配置' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '场景配置' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_scene_config
|
||||
@@ -1873,7 +1659,7 @@ CREATE TABLE `sj_retry_summary` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_scene_name_trigger_at`(`namespace_id` ASC, `group_name` ASC, `scene_name` ASC, `trigger_at` ASC) USING BTREE,
|
||||
INDEX `idx_trigger_at`(`trigger_at` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Retry' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DashBoard_Retry' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_summary
|
||||
@@ -1901,7 +1687,7 @@ CREATE TABLE `sj_retry_task` (
|
||||
INDEX `task_status`(`task_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_retry_id`(`retry_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试任务表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '重试任务表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_task
|
||||
@@ -1924,7 +1710,7 @@ CREATE TABLE `sj_retry_task_log_message` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name_retry_task_id`(`namespace_id` ASC, `group_name` ASC, `retry_task_id` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务调度日志信息记录表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '任务调度日志信息记录表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_retry_task_log_message
|
||||
@@ -1951,7 +1737,7 @@ CREATE TABLE `sj_server_node` (
|
||||
UNIQUE INDEX `uk_host_id_host_ip`(`host_id` ASC, `host_ip` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE,
|
||||
INDEX `idx_expire_at_node_type`(`expire_at` ASC, `node_type` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '服务器节点' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '服务器节点' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_server_node
|
||||
@@ -1970,7 +1756,7 @@ CREATE TABLE `sj_system_user` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_username`(`username` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_system_user
|
||||
@@ -1990,7 +1776,7 @@ CREATE TABLE `sj_system_user_permission` (
|
||||
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE INDEX `uk_namespace_id_group_name_system_user_id`(`namespace_id` ASC, `group_name` ASC, `system_user_id` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户权限表' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户权限表' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_system_user_permission
|
||||
@@ -2025,7 +1811,7 @@ CREATE TABLE `sj_workflow` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_workflow
|
||||
@@ -2056,7 +1842,7 @@ CREATE TABLE `sj_workflow_node` (
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流节点' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流节点' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_workflow_node
|
||||
@@ -2085,7 +1871,7 @@ CREATE TABLE `sj_workflow_task_batch` (
|
||||
INDEX `idx_job_id_task_batch_status`(`workflow_id` ASC, `task_batch_status` ASC) USING BTREE,
|
||||
INDEX `idx_create_dt`(`create_dt` ASC) USING BTREE,
|
||||
INDEX `idx_namespace_id_group_name`(`namespace_id` ASC, `group_name` ASC) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流批次' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流批次' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of sj_workflow_task_batch
|
||||
@@ -2119,6 +1905,7 @@ CREATE TABLE `sys_client` (
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_client` VALUES (1, 'e5cd7e4891bf95d1d19206ce24a7b32e', 'pc', 'pc123', 'password,social', 'pc', 1800, 604800, '0', '0', 103, 1, '2026-02-03 05:14:53', 1, '2026-02-03 05:14:53');
|
||||
INSERT INTO `sys_client` VALUES (2, '428a8310cd442757ae699df5d894f051', 'app', 'app123', 'password,sms,social', 'android', 1800, 604800, '0', '0', 103, 1, '2026-02-03 05:14:53', 1, '2026-02-03 05:14:53');
|
||||
INSERT INTO `sys_client` VALUES (2033738530356912129, '0d4c873ff6146ecd7f38e2e45526ab1b', 'web', 'web123', 'sms,email,password', 'pc', 1800, 604800, '0', '0', 103, 1, '2026-03-17 10:53:45', 1, '2026-03-17 10:59:16');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_config
|
||||
@@ -2288,6 +2075,11 @@ INSERT INTO `sys_dict_data` VALUES (2018858143757504522, '154726', 0, 'PC', 'pc'
|
||||
INSERT INTO `sys_dict_data` VALUES (2018858143761698817, '154726', 0, '安卓', 'android', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '安卓');
|
||||
INSERT INTO `sys_dict_data` VALUES (2018858143761698818, '154726', 0, 'iOS', 'ios', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', 'iOS');
|
||||
INSERT INTO `sys_dict_data` VALUES (2018858143761698819, '154726', 0, '小程序', 'xcx', 'sys_device_type', '', 'default', 'N', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '小程序');
|
||||
INSERT INTO `sys_dict_data` VALUES (2026642472673288194, '000000', 0, '对话', 'chat', 'chat_model_category', NULL, 'cyan', 'N', 103, 1, '2026-02-25 20:56:33', 1, '2026-02-25 21:01:42', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2026642525081116674, '000000', 1, '图像', 'image', 'chat_model_category', NULL, 'success', 'N', 103, 1, '2026-02-25 20:56:46', 1, '2026-02-25 21:01:37', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2026643983713247233, '000000', 1, '次数计费', '1', 'sys_model_billing', NULL, 'green', 'N', 103, 1, '2026-02-25 21:02:34', 1, '2026-02-25 21:02:56', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2026644058522853378, '000000', 2, 'token计费', '2', 'sys_model_billing', NULL, 'primary', 'N', 103, 1, '2026-02-25 21:02:51', 1, '2026-02-25 21:02:51', NULL);
|
||||
INSERT INTO `sys_dict_data` VALUES (2027261114955931650, '000000', 2, '向量', 'vector', 'chat_model_category', NULL, 'default', 'N', 103, 1, '2026-02-27 13:54:49', 1, '2026-02-27 13:54:54', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_dict_type
|
||||
@@ -2331,6 +2123,8 @@ INSERT INTO `sys_dict_type` VALUES (2018858143723950085, '154726', '操作类型
|
||||
INSERT INTO `sys_dict_type` VALUES (2018858143723950086, '154726', '系统状态', 'sys_common_status', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '登录状态列表');
|
||||
INSERT INTO `sys_dict_type` VALUES (2018858143723950087, '154726', '授权类型', 'sys_grant_type', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '认证授权类型');
|
||||
INSERT INTO `sys_dict_type` VALUES (2018858143723950088, '154726', '设备类型', 'sys_device_type', 103, 1, '2026-02-04 09:24:25', 1, '2026-02-04 09:24:25', '客户端设备类型');
|
||||
INSERT INTO `sys_dict_type` VALUES (2026642112982360066, '000000', '模型分类', 'chat_model_category', 103, 1, '2026-02-25 20:55:08', 1, '2026-02-25 20:55:08', '模型分类');
|
||||
INSERT INTO `sys_dict_type` VALUES (2026642183606050817, '000000', '计费方式', 'sys_model_billing', 103, 1, '2026-02-25 20:55:24', 1, '2026-02-25 20:55:24', '计费方式');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_logininfor
|
||||
@@ -2569,6 +2363,31 @@ INSERT INTO `sys_logininfor` VALUES (2019224998575153153, '000000', 'admin', 'pc
|
||||
INSERT INTO `sys_logininfor` VALUES (2019225059417726977, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-05 09:42:24');
|
||||
INSERT INTO `sys_logininfor` VALUES (2019240817392693249, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-05 10:45:01');
|
||||
INSERT INTO `sys_logininfor` VALUES (2019447979716972545, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-06 00:28:13');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026536636865163265, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 13:56:00');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026556949535502337, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 15:16:43');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026578433112911874, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 16:42:05');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026638437400518657, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 20:40:31');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026647463072952321, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:16:23');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026653919016968194, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '退出成功', '2026-02-25 21:42:02');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026654082020204546, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:42:41');
|
||||
INSERT INTO `sys_logininfor` VALUES (2026654455514587138, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-25 21:44:10');
|
||||
INSERT INTO `sys_logininfor` VALUES (2027260957187186689, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-02-27 13:54:12');
|
||||
INSERT INTO `sys_logininfor` VALUES (2030617171346399233, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-08 20:10:35');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033083137191841794, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 15:29:27');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033102367094214657, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 16:45:52');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033116897354551298, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 17:43:36');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033133175565836289, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 18:48:17');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033167392953675778, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 21:04:16');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033168637663719425, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 21:09:12');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033170263812157441, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '退出成功', '2026-03-15 21:15:40');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033170654197002242, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 21:17:13');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033170805703651330, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '退出成功', '2026-03-15 21:17:49');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033170821767835650, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 21:17:53');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033170964009267201, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 21:18:27');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033197762549985282, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 23:04:56');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033200372921217025, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '1', '密码输入错误1次', '2026-03-15 23:15:19');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033200386498179073, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 23:15:22');
|
||||
INSERT INTO `sys_logininfor` VALUES (2033210457315696642, '000000', 'admin', 'pc', 'pc', '127.0.0.1', '内网IP', 'MSEdge', 'Windows 10 or Windows Server 2016', '0', '登录成功', '2026-03-15 23:55:23');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_menu
|
||||
@@ -2604,7 +2423,7 @@ CREATE TABLE `sys_menu` (
|
||||
INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 3, 'system', '', '', 1, 0, 'M', '0', '0', '', 'eos-icons:system-group', 103, 1, '2025-12-14 16:11:49', 1, '2026-01-01 19:06:19', '系统管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 3, 'monitor', '', '', 1, 0, 'M', '0', '0', '', 'solar:monitor-camera-outline', 103, 1, '2025-12-14 16:11:49', 1, '2025-12-14 17:56:44', '系统监控目录');
|
||||
INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 4, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'ant-design:tool-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '系统工具目录');
|
||||
INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 2, 'tenant', NULL, '', 1, 0, 'M', '0', '0', '', 'ph:users-light', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '租户管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (6, '租户管理', 0, 8, 'tenant', '', '', 1, 0, 'M', '0', '0', '', 'ph:users-light', 103, 1, '2025-12-14 16:11:49', 1, '2026-02-25 20:42:14', '租户管理目录');
|
||||
INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'ant-design:user-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '用户管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'eos-icons:role-binding-outlined', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '角色管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'ic:sharp-menu', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '菜单管理菜单');
|
||||
@@ -2708,6 +2527,22 @@ INSERT INTO `sys_menu` VALUES (1620, '配置列表', 118, 5, '#', '', '', 1, 0,
|
||||
INSERT INTO `sys_menu` VALUES (1621, '配置添加', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:add', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1622, '配置编辑', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:edit', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1623, '配置删除', 118, 6, '#', '', '', 1, 0, 'F', '0', '0', 'system:ossConfig:remove', '#', 103, 1, '2025-12-14 16:11:49', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000, 'MCP管理', 0, 2, 'mcp', '', '', 1, 0, 'M', '0', '0', '', 'mdi:robot-industrial', 103, 1, '2026-02-24 20:02:47', 1, '2026-02-25 20:41:54', 'MCP模块管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2001, 'MCP工具管理', 2000, 1, 'tool', 'mcp/tool/index', '', 1, 0, 'C', '0', '0', 'mcp:tool:list', 'octicon:tools-24', 103, 1, '2026-02-24 20:02:47', 1, '2026-02-25 20:41:27', 'MCP工具管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2002, 'MCP工具查询', 2001, 1, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:query', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2003, 'MCP工具新增', 2001, 2, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:add', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2004, 'MCP工具修改', 2001, 3, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:edit', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2005, 'MCP工具删除', 2001, 4, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:remove', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006, 'MCP工具测试', 2001, 5, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:test', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2007, 'MCP工具导出', 2001, 6, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:tool:export', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2010, 'MCP市场管理', 2000, 2, 'market', 'mcp/market/index', '', 1, 0, 'C', '0', '0', 'mcp:market:list', 'mdi:storefront-outline', 103, 1, '2026-02-24 20:02:47', NULL, NULL, 'MCP市场管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2011, 'MCP市场查询', 2010, 1, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:query', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2012, 'MCP市场新增', 2010, 2, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:add', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2013, 'MCP市场修改', 2010, 3, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:edit', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2014, 'MCP市场删除', 2010, 4, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:remove', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2015, 'MCP市场刷新', 2010, 5, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:refresh', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2016, 'MCP工具加载', 2010, 6, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:load', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2017, 'MCP市场导出', 2010, 7, '#', '', '', 1, 0, 'F', '0', '0', 'mcp:market:export', '#', 103, 1, '2026-02-24 20:02:47', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (11616, '工作流', 0, 6, 'workflow', '', '', 1, 0, 'M', '0', '0', '', 'mdi:workflow-outline', 103, 1, '2026-01-05 14:39:33', 1, '2026-01-05 14:56:07', '');
|
||||
INSERT INTO `sys_menu` VALUES (11618, '我的任务', 0, 7, 'task', '', '', 1, 0, 'M', '0', '0', '', 'carbon:task-approved', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (11619, '我的待办', 11618, 2, 'taskWaiting', 'workflow/task/taskWaiting', '', 1, 1, 'C', '0', '0', '', 'ri:todo-line', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
@@ -2732,13 +2567,6 @@ INSERT INTO `sys_menu` VALUES (11803, '流程达式定义新增', 11801, 2, '#',
|
||||
INSERT INTO `sys_menu` VALUES (11804, '流程达式定义修改', 11801, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:edit', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (11805, '流程达式定义删除', 11801, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:remove', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (11806, '流程达式定义导出', 11801, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'workflow:spel:export', '#', 103, 1, '2026-01-05 14:39:33', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597696, '数字人体验', 2019459914910994434, 10, 'aihumanPublish', 'aihuman/aihumanPublish/index', NULL, 1, 0, 'C', '0', '0', '', 'mdi:human-child', 103, 1, '2026-02-06 01:13:22', 1, '2026-02-06 01:29:34', '数字人信息管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597697, '数字人信息管理查询', 1971546066781597696, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:query', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597698, '数字人信息管理新增', 1971546066781597696, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:add', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597699, '数字人信息管理修改', 1971546066781597696, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:edit', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597700, '数字人信息管理删除', 1971546066781597696, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:remove', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1971546066781597701, '数字人信息管理导出', 1971546066781597696, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'aihuman:aihumanInfo:export', '#', 103, 1, '2026-02-06 01:13:22', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (1980480880138051584, '数字人配置', 2019459914910994434, 1, 'aihumanConfig', 'aihuman/aihumanConfig/index', NULL, 1, 0, 'C', '0', '0', 'aihuman:aihumanConfig:list', 'mdi:human-child', 103, 1, '2026-02-06 01:13:35', 1, '2026-02-06 01:28:12', '');
|
||||
INSERT INTO `sys_menu` VALUES (2000209300188356609, '对话管理', 0, 0, 'chat', '', NULL, 1, 0, 'M', '0', '0', NULL, 'material-symbols:chat-outline', 103, 1, '2025-12-14 22:20:34', 1, '2025-12-14 22:21:24', '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210913451892738, '厂商管理', 2000209300188356609, 1, 'provider', 'chat/provider/index', NULL, 1, 0, 'C', '0', '0', 'system:provider:list', 'tabler:cube-spark', 103, 1, '2025-12-14 22:28:05', 1, '2025-12-14 23:42:55', '厂商管理菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2000210913451892739, '厂商管理查询', 2000210913451892738, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:provider:query', '#', 103, 1, '2025-12-14 22:28:05', NULL, NULL, '');
|
||||
@@ -2752,30 +2580,19 @@ INSERT INTO `sys_menu` VALUES (2000210913846157316, '模型管理新增', 200021
|
||||
INSERT INTO `sys_menu` VALUES (2000210913846157317, '模型管理修改', 2000210913846157314, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:edit', '#', 103, 1, '2025-12-14 22:27:59', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210913846157318, '模型管理删除', 2000210913846157314, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:remove', '#', 103, 1, '2025-12-14 22:27:59', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210913846157319, '模型管理导出', 2000210913846157314, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:model:export', '#', 103, 1, '2025-12-14 22:28:00', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914299142145, '聊天配置', 2000209300188356609, 1, 'config', 'chat/config/index', NULL, 1, 0, 'C', '0', '0', 'system:config:list', 'tdesign:task-setting', 103, 1, '2025-12-14 22:27:46', 1, '2025-12-15 00:59:48', '配置信息菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914299142146, '配置信息查询', 2000210914299142145, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:query', '#', 103, 1, '2025-12-14 22:27:46', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914299142147, '配置信息新增', 2000210914299142145, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:add', '#', 103, 1, '2025-12-14 22:27:46', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914299142148, '配置信息修改', 2000210914299142145, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:edit', '#', 103, 1, '2025-12-14 22:27:46', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914299142149, '配置信息删除', 2000210914299142145, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:remove', '#', 103, 1, '2025-12-14 22:27:46', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914299142150, '配置信息导出', 2000210914299142145, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:config:export', '#', 103, 1, '2025-12-14 22:27:46', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914680823809, '聊天消息', 2000209300188356609, 1, 'message', 'chat/message/index', NULL, 1, 0, 'C', '0', '0', 'system:message:list', 'system-uicons:message', 103, 1, '2025-12-14 22:27:54', 1, '2025-12-15 00:53:47', '聊天消息菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914680823810, '聊天消息查询', 2000210914680823809, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:query', '#', 103, 1, '2025-12-14 22:27:54', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914680823811, '聊天消息新增', 2000210914680823809, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:add', '#', 103, 1, '2025-12-14 22:27:54', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914680823812, '聊天消息修改', 2000210914680823809, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:edit', '#', 103, 1, '2025-12-14 22:27:54', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914680823813, '聊天消息删除', 2000210914680823809, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:remove', '#', 103, 1, '2025-12-14 22:27:54', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2000210914680823814, '聊天消息导出', 2000210914680823809, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:message:export', '#', 103, 1, '2025-12-14 22:27:54', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813441, '知识库', 2006683336984580098, 1, 'info', 'knowledge/info/index', NULL, 1, 0, 'C', '0', '0', 'knowledge:info:list', 'solar:book-line-duotone', 103, 1, '2026-01-01 18:59:05', 1, '2026-01-01 19:08:03', '知识库菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813441, '知识管理', 2000209300188356609, 1, 'info', 'knowledge/info/index', NULL, 1, 0, 'C', '0', '0', 'knowledge:info:list', 'solar:book-line-duotone', 103, 1, '2026-01-01 18:59:05', 1, '2026-03-15 21:07:50', '知识库菜单');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813442, '知识库查询', 2006681261898813441, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:query', '#', 103, 1, '2026-01-01 18:59:05', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813443, '知识库新增', 2006681261898813441, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:add', '#', 103, 1, '2026-01-01 18:59:05', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813444, '知识库修改', 2006681261898813441, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:edit', '#', 103, 1, '2026-01-01 18:59:05', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813445, '知识库删除', 2006681261898813441, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:remove', '#', 103, 1, '2026-01-01 18:59:06', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006681261898813446, '知识库导出', 2006681261898813441, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'system:info:export', '#', 103, 1, '2026-01-01 18:59:06', NULL, NULL, '');
|
||||
INSERT INTO `sys_menu` VALUES (2006683336984580098, '知识管理', 0, 2, 'knowledge', '', NULL, 1, 0, 'M', '0', '0', NULL, 'bx:book', 103, 1, '2026-01-01 19:06:05', 1, '2026-01-01 19:06:05', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019459914910994434, '数字人管理', 0, 2, 'human', '', NULL, 1, 0, 'M', '0', '0', NULL, 'tdesign:user', 103, 1, '2026-02-06 01:15:38', 1, '2026-02-06 01:16:58', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464280262905857, '图谱实例', 2019464531388469250, 1, 'graphInstance', 'graph/graphInstance/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:list', 'ant-design:node-index-outlined', 103, 1, '2026-02-06 01:32:59', 1, '2026-02-06 01:40:06', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464531388469250, '知识图谱', 2006683336984580098, 15, 'graph', '', NULL, 1, 0, 'M', '0', '0', NULL, 'carbon:chart-relationship', 103, 1, '2026-02-06 01:33:59', 1, '2026-02-06 01:33:59', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464779217309697, '图谱可视化', 2019464531388469250, 2, 'graphVisualization', 'graph/graphVisualization/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:view', 'carbon:chart-network', 103, 1, '2026-02-06 01:34:58', 1, '2026-02-06 01:40:14', '');
|
||||
INSERT INTO `sys_menu` VALUES (2019464917407043585, '图谱检索', 2019464531388469250, 3, 'graphRAG', 'graph/graphRAG/index', NULL, 1, 0, 'C', '0', '0', 'operator:graph:retrieve', 'carbon:search-advanced', 103, 1, '2026-02-06 01:35:31', 1, '2026-02-06 01:40:19', '');
|
||||
INSERT INTO `sys_menu` VALUES (2031361596464902145, '编排管理', 2000209300188356609, 1, 'aiflow', 'aiflow/index', NULL, 1, 0, 'C', '0', '0', NULL, 'carbon:flow', 103, 1, '2026-03-10 21:28:40', 1, '2026-03-15 21:06:01', '');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_notice
|
||||
@@ -2860,6 +2677,18 @@ CREATE TABLE `sys_oss` (
|
||||
-- ----------------------------
|
||||
-- Records of sys_oss
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_oss` VALUES (2026580908423340033, '000000', '2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', 'logo.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/9219d6d71a6d45e19192014609d92dc9.png', '{\"fileSize\":\"183613\",\"contentType\":\"image/png\"}', 103, '2026-02-25 16:51:55', 1, '2026-02-25 16:51:55', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640059920883713, '000000', '2026/02/25/01091be272334383a1efd9bc22b73ee6.png', 'openai.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/01091be272334383a1efd9bc22b73ee6.png', '{\"fileSize\":\"11297\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:46:58', 1, '2026-02-25 20:46:58', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640515967557633, '000000', '2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', 'ollama.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/afecabebc8014d80b0f06b4796a74c5d.png', '{\"fileSize\":\"8746\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:47', 1, '2026-02-25 20:48:47', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640548213366785, '000000', '2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/e16429a462e54e14a1d36673146b9e3c.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:48:55', 1, '2026-02-25 20:48:55', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640572443860993, '000000', '2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', 'ppio-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/049bb6a507174f73bba4b8d8b9e55b8a.png', '{\"fileSize\":\"7382\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:00', 1, '2026-02-25 20:49:00', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2026640621945036802, '000000', '2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', 'bailian-color.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/02/25/de2aa7e649de44f3ba5c6380ac6acd04.png', '{\"fileSize\":\"5901\",\"contentType\":\"image/png\"}', 103, '2026-02-25 20:49:12', 1, '2026-02-25 20:49:12', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2033120065673043969, '000000', '2026/03/15/68c4d853814e444982c2517ffabac0f3.jpg', '9b75e600b2df6160261a507055eabfdf.jpg', '.jpg', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/68c4d853814e444982c2517ffabac0f3.jpg', '{\"fileSize\":\"28027\",\"contentType\":\"image/jpeg\"}', 103, '2026-03-15 17:56:12', 1, '2026-03-15 17:56:12', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2033169884118593537, '000000', '2026/03/15/4b7e93a72bf04805ae59985cc0845ef1.png', 'logo.png', '.png', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/4b7e93a72bf04805ae59985cc0845ef1.png', '{\"fileSize\":\"61537\",\"contentType\":\"image/png\"}', 103, '2026-03-15 21:14:10', 1, '2026-03-15 21:14:10', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2033198191581147137, '000000', '2026/03/15/66d9e6d216c74652bb466a13d24f4440.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/66d9e6d216c74652bb466a13d24f4440.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:06:39', 1, '2026-03-15 23:06:39', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2033198447232364546, '000000', '2026/03/15/ae1b1e0d363e4bc1b3321d696453fdbf.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/ae1b1e0d363e4bc1b3321d696453fdbf.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:07:39', 1, '2026-03-15 23:07:39', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2033198841211727874, '000000', '2026/03/15/83564059b4f643b69a1e0ea727d17364.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/83564059b4f643b69a1e0ea727d17364.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:09:13', 1, '2026-03-15 23:09:13', 1, 'qcloud');
|
||||
INSERT INTO `sys_oss` VALUES (2033199209064771586, '000000', '2026/03/15/695360eb380d43d6af34e8a308c09696.txt', 'ruoyi-ai介绍.txt', '.txt', 'https://ruoyiai-1254149996.cos.ap-guangzhou.myqcloud.com/2026/03/15/695360eb380d43d6af34e8a308c09696.txt', '{\"fileSize\":\"1166\",\"contentType\":\"text/plain\"}', 103, '2026-03-15 23:10:41', 1, '2026-03-15 23:10:41', 1, 'qcloud');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for sys_oss_config
|
||||
@@ -2892,10 +2721,10 @@ CREATE TABLE `sys_oss_config` (
|
||||
-- ----------------------------
|
||||
-- Records of sys_oss_config
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-25 15:44:13', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '', 'N', 'ap-beijing', '1', '1', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-02-03 05:14:52', NULL);
|
||||
INSERT INTO `sys_oss_config` VALUES (4, '000000', 'qcloud', 'xx', 'xx', 'ruoyiai-1254149996', '', 'cos.ap-guangzhou.myqcloud.com', '', 'Y', 'ap-guangzhou', '1', '0', '', 103, 1, '2026-02-03 05:14:52', 1, '2026-03-15 17:56:06', '');
|
||||
INSERT INTO `sys_oss_config` VALUES (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '', 'N', '', '1', '1', '', 103, 1, '2026-02-03 05:14:53', 1, '2026-02-03 05:14:53', NULL);
|
||||
|
||||
-- ----------------------------
|
||||
@@ -3342,7 +3171,7 @@ CREATE TABLE `sys_user` (
|
||||
-- ----------------------------
|
||||
-- Records of sys_user
|
||||
-- ----------------------------
|
||||
INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-02-06 00:28:13', 103, 1, '2026-02-05 09:22:12', -1, '2026-02-06 00:28:13', '管理员', NULL, 0.00);
|
||||
INSERT INTO `sys_user` VALUES (1, '000000', 103, 'admin', 'admin', 'sys_user', 'ageerle@163.com', '15888888888', '1', NULL, '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2026-03-15 23:55:23', 103, 1, '2026-02-05 09:22:12', -1, '2026-03-15 23:55:23', '管理员', NULL, 0.00);
|
||||
INSERT INTO `sys_user` VALUES (3, '000000', 108, 'test', '本部门及以下 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 3, '2026-02-05 09:22:12', NULL, NULL, 0.00);
|
||||
INSERT INTO `sys_user` VALUES (4, '000000', 102, 'test1', '仅本人 密码666666', 'sys_user', '', '', '0', NULL, '$2a$10$b8yUzN0C71sbz.PhNOCgJe.Tu1yWC3RNrTyjSQ8p1W0.aaUXUJ.Ne', '0', '0', '127.0.0.1', '2026-02-05 09:22:12', 103, 1, '2026-02-05 09:22:12', 4, '2026-02-05 09:22:12', NULL, NULL, 0.00);
|
||||
|
||||
@@ -3421,7 +3250,7 @@ CREATE TABLE `t_workflow_component` (
|
||||
`tenant_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '000000' COMMENT '租户编号',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_display_order`(`display_order` ASC) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流组件库 | Workflow Component' ROW_FORMAT = DYNAMIC;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '工作流组件库 | Workflow Component' ROW_FORMAT = DYNAMIC;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of t_workflow_component
|
||||
@@ -3429,6 +3258,9 @@ CREATE TABLE `t_workflow_component` (
|
||||
INSERT INTO `t_workflow_component` VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` VALUES (25, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` VALUES (26, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` VALUES (36, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_workflow_edge
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (26, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (36, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
352
docs/troubleshooting/rag-failures.md
Normal file
352
docs/troubleshooting/rag-failures.md
Normal file
@@ -0,0 +1,352 @@
|
||||
<a id="top"></a>
|
||||
|
||||
# RAG 常见故障排查(16 问题清单)
|
||||
|
||||
当知识库已经接入,系统也能正常回答,但结果仍然出现命中错误、引用旧内容、推理漂移、跨轮次失忆,或部署后表面可用但实际异常时,最常见的问题不是“模型不行”,而是**不同层的故障被混在一起处理**。
|
||||
|
||||
这份页面不重新发明一套新方法。
|
||||
它直接使用一份固定的 **16 问题清单** 作为排查主轴,让你先把问题标到正确的 **No.X**,再决定下一步查哪里、改哪里,而不是一次性乱改检索、模型、切块、会话和部署配置。
|
||||
|
||||
这份清单的核心目的只有一个:
|
||||
|
||||
**先把问题放进正确的故障域,再做修复。**
|
||||
|
||||
快速导航:
|
||||
[这页怎么用](#how-to-use) | [标签说明](#legend) | [常见症状入口](#symptoms) | [16 问题清单](#map16) | [按层排查](#by-layer) |
|
||||
|
||||
---
|
||||
|
||||
<a id="how-to-use"></a>
|
||||
|
||||
## 一、这页怎么用
|
||||
|
||||
这不是一篇“从头到尾照着做”的传统教程。
|
||||
它更像一张固定的 RAG 故障地图,作用是先帮助你**判断故障属于哪一种类型**。
|
||||
|
||||
建议按下面顺序使用:
|
||||
|
||||
### 1. 先看现象,不要先改配置
|
||||
|
||||
先回答两个问题:
|
||||
|
||||
1. 你看到的故障,最像哪一种症状
|
||||
2. 这个故障更像发生在输入检索层、推理层、状态层,还是部署层
|
||||
|
||||
在还没判断层级之前,不要先一起改这些东西:
|
||||
|
||||
- 检索条数
|
||||
- 切块大小
|
||||
- 会话配置
|
||||
- 模型参数
|
||||
- 部署顺序
|
||||
- 依赖服务
|
||||
|
||||
如果先全部一起动,问题通常只会更难定位。
|
||||
|
||||
### 2. 先给问题打上 No.X 标签
|
||||
|
||||
这份页面最重要的动作,不是“立刻修好”,而是先做一件小事:
|
||||
|
||||
**给当前问题贴上最接近的 No.X。**
|
||||
|
||||
例如:
|
||||
|
||||
- 检索结果看起来相似,但其实答非所问,先看 `No.1` 或 `No.5`
|
||||
- 切块是对的,但结论还是错,先看 `No.2`
|
||||
- 系统回答很自信,但没有根据,先看 `No.4`
|
||||
- 刚部署完就炸,先看 `No.14` 到 `No.16`
|
||||
|
||||
### 3. 一次只排一个故障域
|
||||
|
||||
同一个表面现象,背后可能是不同层的问题。
|
||||
例如“答案不对”既可能是:
|
||||
|
||||
- `No.1` 检索漂移
|
||||
- `No.2` 理解塌陷
|
||||
- `No.4` 自信乱答
|
||||
- `No.8` 根本看不到错误路径
|
||||
|
||||
所以这张表的用法不是“多选全改”,而是:
|
||||
|
||||
**先挑最接近的一项,优先验证这一项是否成立。**
|
||||
|
||||
[返回顶部](#top) | [下一节:标签说明](#legend)
|
||||
|
||||
---
|
||||
|
||||
<a id="legend"></a>
|
||||
|
||||
## 二、标签说明
|
||||
|
||||
这份 16 问题清单本身已经带有层级 / 标签结构。
|
||||
这些标签不是装饰,而是用来帮助你快速判断故障发生在哪一层。
|
||||
|
||||
### 1. 层级标签
|
||||
|
||||
- `[IN]`:输入与检索
|
||||
输入、切块、召回、语义匹配、可见性问题
|
||||
|
||||
- `[RE]`:推理与规划
|
||||
理解、推理、归纳、逻辑链、抽象处理问题
|
||||
|
||||
- `[ST]`:状态与上下文
|
||||
会话、记忆、上下文连续性、多代理状态问题
|
||||
|
||||
- `[OP]`:基础设施与部署
|
||||
启动顺序、依赖就绪、部署锁死、预部署状态问题
|
||||
|
||||
### 2. `{OBS}` 标签
|
||||
|
||||
带 `{OBS}` 的项,通常都和“**你是否看得见问题是怎么坏掉的**”有关。
|
||||
它们往往不是单纯回答错误,而是:
|
||||
|
||||
- 错误路径不可见
|
||||
- 漂移过程不可见
|
||||
- 状态熔化过程不可见
|
||||
- 多代理覆盖过程不可见
|
||||
|
||||
所以一旦你发现“我知道结果错,但我根本看不到它是怎么错的”,通常就已经很接近 `{OBS}` 类问题了。
|
||||
|
||||
### 3. 为什么要保留这些标签
|
||||
|
||||
因为同样叫“答错了”,实际含义完全不同。
|
||||
|
||||
例如:
|
||||
|
||||
- `[IN]` 的答错,常常是**拿错材料**
|
||||
- `[RE]` 的答错,常常是**拿对材料但理解错**
|
||||
- `[ST]` 的答错,常常是**前文断掉、状态漂移**
|
||||
- `[OP]` 的答错,常常是**系统根本没在完整状态下运行**
|
||||
|
||||
如果不先分层,就会掉进典型的 RAG 地狱:
|
||||
表面在改答案,实际上在盲修。
|
||||
|
||||
[返回顶部](#top) | [下一节:常见症状入口](#symptoms)
|
||||
|
||||
---
|
||||
|
||||
<a id="symptoms"></a>
|
||||
|
||||
## 三、常见症状入口
|
||||
|
||||
如果你现在还不知道该从哪一项开始,就先从症状入口反查。
|
||||
|
||||
### 1. 检索返回了错误内容,或看起来相关但其实不回答问题
|
||||
|
||||
这类问题最常见的是:
|
||||
“有命中,但命中的不是该用的内容。”
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.1](#no1) `幻觉与切块漂移`
|
||||
- [No.5](#no5) `语义 ≠ 向量嵌入`
|
||||
- [No.8](#no8) `调试是一个黑箱`
|
||||
|
||||
### 2. 切块本身是对的,但最终答案还是错的
|
||||
|
||||
这类问题不是简单没检索到,而是后面那层坏了。
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.2](#no2) `解释塌陷`
|
||||
- [No.4](#no4) `虚张声势 / 过度自信`
|
||||
- [No.6](#no6) `逻辑塌陷与恢复`
|
||||
|
||||
### 3. 多步任务一开始正常,后面越来越偏
|
||||
|
||||
这类问题通常不是单点错误,而是中途漂移或熔化。
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.3](#no3) `长推理链`
|
||||
- [No.6](#no6) `逻辑塌陷与恢复`
|
||||
- [No.9](#no9) `熵塌陷`
|
||||
|
||||
### 4. 多轮对话后开始失忆,跨轮次接不上
|
||||
|
||||
这类问题一般已经进入状态层。
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.7](#no7) `跨会话记忆断裂`
|
||||
- [No.9](#no9) `熵塌陷`
|
||||
- [No.13](#no13) `多代理混乱`
|
||||
|
||||
### 5. 遇到抽象、逻辑、规则、符号关系就崩
|
||||
|
||||
这类问题通常不是检索空,而是推理结构扛不住。
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.11](#no11) `符号塌陷`
|
||||
- [No.12](#no12) `哲学递归`
|
||||
|
||||
### 6. 你根本不知道错在哪一层,只知道结果不对
|
||||
|
||||
这类问题先不要乱调参数。
|
||||
先解决“不可见”的问题。
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.8](#no8) `调试是一个黑箱`
|
||||
|
||||
### 7. 刚部署完最容易炸,首轮调用异常,重启后偶尔恢复
|
||||
|
||||
这类问题通常不在答案逻辑,而在部署状态。
|
||||
|
||||
优先看:
|
||||
|
||||
- [No.14](#no14) `引导启动顺序`
|
||||
- [No.15](#no15) `部署死锁`
|
||||
- [No.16](#no16) `预部署塌陷`
|
||||
|
||||
[返回顶部](#top) | [下一节:16 问题清单](#map16)
|
||||
|
||||
---
|
||||
|
||||
<a id="map16"></a>
|
||||
|
||||
## 四、16 问题清单(固定主表)
|
||||
|
||||
下面这 16 项按固定顺序使用。
|
||||
不要先重组,不要先混类,先判断最接近哪一个 **No.X**。
|
||||
|
||||
| # | 问题域(含层级/标签) | 会坏在哪里 |
|
||||
|---|---|---|
|
||||
| <a id="no1"></a> 1 | `[IN] 幻觉与切块漂移 {OBS}` | 检索返回错误/无关内容 |
|
||||
| <a id="no2"></a> 2 | `[RE] 解释塌陷` | 切块是对的,逻辑是错的 |
|
||||
| <a id="no3"></a> 3 | `[RE] 长推理链 {OBS}` | 在多步任务中逐步漂移 |
|
||||
| <a id="no4"></a> 4 | `[RE] 虚张声势 / 过度自信` | 自信但没有根据的回答 |
|
||||
| <a id="no5"></a> 5 | `[IN] 语义 ≠ 向量嵌入 {OBS}` | 余弦匹配 ≠ 真实语义 |
|
||||
| <a id="no6"></a> 6 | `[RE] 逻辑塌陷与恢复 {OBS}` | 走入死胡同,需要受控重置 |
|
||||
| <a id="no7"></a> 7 | `[ST] 跨会话记忆断裂` | 线索丢失,没有连续性 |
|
||||
| <a id="no8"></a> 8 | `[IN] 调试是一个黑箱 {OBS}` | 看不到故障路径 |
|
||||
| <a id="no9"></a> 9 | `[ST] 熵塌陷` | 注意力熔化,输出失去连贯性 |
|
||||
| <a id="no10"></a> 10 | `[RE] 创造力冻结` | 平直、字面化输出 |
|
||||
| <a id="no11"></a> 11 | `[RE] 符号塌陷` | 抽象/逻辑性提示词失效 |
|
||||
| <a id="no12"></a> 12 | `[RE] 哲学递归` | 自我引用循环、悖论陷阱 |
|
||||
| <a id="no13"></a> 13 | `[ST] 多代理混乱 {OBS}` | 代理互相覆盖或使逻辑错位 |
|
||||
| <a id="no14"></a> 14 | `[OP] 引导启动顺序` | 依赖未就绪时服务先启动 |
|
||||
| <a id="no15"></a> 15 | `[OP] 部署死锁` | 基础设施中的循环等待 |
|
||||
| <a id="no16"></a> 16 | `[OP] 预部署塌陷 {OBS}` | 首次调用时版本错配 / 缺少密钥 |
|
||||
|
||||
这张表是主表。
|
||||
如果你时间很少,只做一件事也行:
|
||||
|
||||
**先从这 16 项里选出最接近的一项。**
|
||||
|
||||
[返回顶部](#top) | [下一节:按层排查](#by-layer)
|
||||
|
||||
---
|
||||
|
||||
<a id="by-layer"></a>
|
||||
|
||||
## 五、按层排查:不要改错层
|
||||
|
||||
这一节不重写 16 项,只是告诉你:
|
||||
当你已经选到某个 No.X 时,第一眼应该优先查哪一层。
|
||||
|
||||
### A. `[IN]` 层:先确认你拿到的是不是对的材料
|
||||
|
||||
对应编号:
|
||||
|
||||
- [No.1](#no1)
|
||||
- [No.5](#no5)
|
||||
- [No.8](#no8)
|
||||
|
||||
这层最常见的误判是:
|
||||
|
||||
“我以为系统理解错了,其实它一开始就拿错了东西。”
|
||||
|
||||
如果你命中了弱相关片段、表面相似文本、错误切块,后面推理再强也没用。
|
||||
所以 `[IN]` 层优先看的是:
|
||||
|
||||
1. 原始召回内容到底是什么
|
||||
2. 命中的片段是否只是“相似”,而不是“正确”
|
||||
3. 你是否能看到检索过程,还是整个过程像黑箱
|
||||
|
||||
这层如果没先排好,后面的推理诊断通常会失真。
|
||||
|
||||
### B. `[RE]` 层:材料可能是对的,但系统用错了
|
||||
|
||||
对应编号:
|
||||
|
||||
- [No.2](#no2)
|
||||
- [No.3](#no3)
|
||||
- [No.4](#no4)
|
||||
- [No.6](#no6)
|
||||
- [No.10](#no10)
|
||||
- [No.11](#no11)
|
||||
- [No.12](#no12)
|
||||
|
||||
这层最常见的误判是:
|
||||
|
||||
“我以为是检索坏了,其实是后面理解、归纳、逻辑链坏了。”
|
||||
|
||||
例如:
|
||||
|
||||
- 切块是对的,但结论错了 → 常见是 `No.2`
|
||||
- 多步任务中途开始偏 → 常见是 `No.3`
|
||||
- 回答很笃定,但完全站不住 → 常见是 `No.4`
|
||||
- 遇到抽象规则就崩 → 常见是 `No.11`
|
||||
- 陷入循环解释 → 常见是 `No.12`
|
||||
|
||||
如果 `[IN]` 层已经基本没问题,答案还是不对,就应该优先回到 `[RE]` 层判断是哪一种塌陷。
|
||||
|
||||
### C. `[ST]` 层:单轮正常,不代表状态层正常
|
||||
|
||||
对应编号:
|
||||
|
||||
- [No.7](#no7)
|
||||
- [No.9](#no9)
|
||||
- [No.13](#no13)
|
||||
|
||||
这层最常见的误判是:
|
||||
|
||||
“单轮看起来还行,所以我以为系统没问题。”
|
||||
|
||||
其实很多 RAG 地狱不是单轮错误,而是:
|
||||
|
||||
- 多轮之后前文断掉
|
||||
- 上下文越来越乱
|
||||
- 多角色、多代理之间互相覆盖
|
||||
|
||||
如果你发现:
|
||||
|
||||
- 第一轮没事,后面越来越歪
|
||||
- 切换角色后前面的约束消失
|
||||
- 多个步骤之间状态彼此污染
|
||||
|
||||
那就不要再只盯着检索条数了,应该直接回到 `[ST]` 层看 `No.7 / No.9 / No.13`。
|
||||
|
||||
### D. `[OP]` 层:别把部署问题误诊成回答问题
|
||||
|
||||
对应编号:
|
||||
|
||||
- [No.14](#no14)
|
||||
- [No.15](#no15)
|
||||
- [No.16](#no16)
|
||||
|
||||
这层最常见的误判是:
|
||||
|
||||
“答案不稳定,所以我先去调模型或检索。”
|
||||
|
||||
但如果系统根本没有在完整状态下启动,所有上层表现都会像鬼打墙。
|
||||
尤其是这些情况:
|
||||
|
||||
- 依赖还没就绪,服务先起了 → `No.14`
|
||||
- 多个组件互相等待,长期半可用 → `No.15`
|
||||
- 首次调用就因为版本、密钥、环境没对齐而塌陷 → `No.16`
|
||||
|
||||
只要你看到“刚部署最容易出事”“首轮异常最严重”“重启后暂时恢复”,就要优先怀疑 `[OP]` 层,而不是先改提示词或参数。
|
||||
|
||||
[返回顶部](#top) |
|
||||
|
||||
---
|
||||
|
||||
<a id="issue-report"></a>
|
||||
|
||||
|
||||
## 六、快速返回
|
||||
|
||||
[返回顶部](#top) | [这页怎么用](#how-to-use) | [标签说明](#legend) | [常见症状入口](#symptoms) | [16 问题清单](#map16) | [按层排查](#by-layer)
|
||||
42
docs/文件上传接口文档.md
Normal file
42
docs/文件上传接口文档.md
Normal file
@@ -0,0 +1,42 @@
|
||||
## 接口信息
|
||||
|
||||
**接口路径**: `POST /resource/oss/upload`
|
||||
**请求类型**: `multipart/form-data`
|
||||
**权限要求**: `system:oss:upload`
|
||||
**业务类型**: [INSERT]
|
||||
|
||||
### 接口描述
|
||||
上传OSS对象存储接口,用于将文件上传到对象存储服务。
|
||||
|
||||
### 请求参数
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ---- | ------------- | ---- | ------ |
|
||||
| file | MultipartFile | 是 | 要上传的文件 |
|
||||
|
||||
### 请求头
|
||||
- `Content-Type`: `multipart/form-data`
|
||||
|
||||
### 返回值
|
||||
返回 `R<SysOssUploadVo>` 类型,包含以下字段:
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| -------- | ------ | ------- |
|
||||
| url | String | 文件访问URL |
|
||||
| fileName | String | 原始文件名 |
|
||||
| ossId | String | 文件ID |
|
||||
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"url": "fileid://xxx",
|
||||
"fileName": "example.jpg",
|
||||
"ossId": "123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 异常情况
|
||||
- 当上传文件为空时,返回错误信息:"上传文件不能为空"
|
||||
57
pom.xml
57
pom.xml
@@ -50,15 +50,19 @@
|
||||
<!-- 工作流配置 -->
|
||||
<warm-flow.version>1.8.2</warm-flow.version>
|
||||
<!-- 企业微信SDK -->
|
||||
<weixin-java-cp.version>4.4.0</weixin-java-cp.version>
|
||||
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
|
||||
<!-- Jackson XML -->
|
||||
<jackson-dataformat-xml.version>2.20.1</jackson-dataformat-xml.version>
|
||||
<jackson-dataformat-xml.version>2.18.2</jackson-dataformat-xml.version>
|
||||
<!-- AI 相关依赖 -->
|
||||
<langchain4j.version>1.11.0</langchain4j.version>
|
||||
<langchain4j.community.version>1.11.0-beta19</langchain4j.community.version>
|
||||
<langgraph4j.version>1.5.3</langgraph4j.version>
|
||||
<weaviate.version>1.19.6</weaviate.version>
|
||||
<dify.version>1.0.7</dify.version>
|
||||
<!-- gRPC 版本 - 解决 Milvus SDK 依赖冲突 -->
|
||||
<grpc.version>1.62.2</grpc.version>
|
||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式 -->
|
||||
<commons-compress.version>1.27.1</commons-compress.version>
|
||||
|
||||
|
||||
<avatar-generator.version>1.1.0</avatar-generator.version>
|
||||
@@ -127,6 +131,15 @@
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- gRPC BOM - 解决 Milvus SDK 依赖冲突,强制统一版本 -->
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-bom</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- hutool 的依赖配置-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
@@ -350,24 +363,12 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-job</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-demo</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-chat</artifactId>
|
||||
@@ -381,20 +382,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 微信模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-wechat</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 数字人模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-aihuman</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AI流程编排模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
@@ -402,13 +389,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 企业微信SDK -->
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>weixin-java-cp</artifactId>
|
||||
<version>${weixin-java-cp.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson XML -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
@@ -416,6 +396,13 @@
|
||||
<version>${jackson-dataformat-xml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式,解决导出Excel时的NoSuchMethodError -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>${commons-compress.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -70,23 +70,12 @@
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-job</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成-->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- demo模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-demo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-chat</artifactId>
|
||||
@@ -98,24 +87,13 @@
|
||||
<artifactId>ruoyi-workflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 微信模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-wechat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数字人模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-aihuman</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AI流程编排模块 -->
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-aiflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||
|
||||
@@ -16,7 +16,7 @@ public class RuoYiAIApplication {
|
||||
SpringApplication application = new SpringApplication(RuoYiAIApplication.class);
|
||||
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||
application.run(args);
|
||||
System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙");
|
||||
System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.ruoyi.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* BeanDefinitionRegistry后置处理器
|
||||
* 解决 MapStruct Plus 生成的 mapper 冲突问题
|
||||
*
|
||||
* @author ruoyi team
|
||||
*/
|
||||
@Configuration
|
||||
public class MapperConflictResolver implements BeanDefinitionRegistryPostProcessor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MapperConflictResolver.class);
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
String[] beanNames = registry.getBeanDefinitionNames();
|
||||
|
||||
// 查找冲突的 mapper bean
|
||||
for (String beanName : beanNames) {
|
||||
if (beanName.equals("chatMessageBoToChatMessageMapperImpl")) {
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
|
||||
String beanClassName = beanDefinition.getBeanClassName();
|
||||
|
||||
log.info("Found mapper bean: {} -> {}", beanName, beanClassName);
|
||||
|
||||
// 如果是 org.ruoyi.domain.bo.chat 包下的(冲突的),移除它
|
||||
if (beanClassName != null && beanClassName.startsWith("org.ruoyi.domain.bo.chat")) {
|
||||
log.warn("Removing conflicting bean definition: {} ({})", beanName, beanClassName);
|
||||
registry.removeBeanDefinition(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
// 不需要实现
|
||||
}
|
||||
}
|
||||
@@ -80,10 +80,16 @@ public class AuthController {
|
||||
// 授权类型和客户端id
|
||||
String clientId = loginBody.getClientId();
|
||||
String grantType = loginBody.getGrantType();
|
||||
log.info("登录请求 - clientId: {}, grantType: {}", clientId, grantType);
|
||||
SysClientVo client = clientService.queryByClientId(clientId);
|
||||
log.info("查询客户端结果 - client: {}, grantType: {}", client, client != null ? client.getGrantType() : "null");
|
||||
// 查询不到 client 或 client 内不包含 grantType
|
||||
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
|
||||
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
|
||||
if (ObjectUtil.isNull(client)) {
|
||||
log.info("客户端id: {} 不存在!", clientId);
|
||||
return R.fail(MessageUtils.message("auth.grant.type.error"));
|
||||
}
|
||||
if (!StringUtils.contains(client.getGrantType(), grantType)) {
|
||||
log.info("客户端id: {} 认证类型:{} 不匹配! 数据库grantType: {}", clientId, grantType, client.getGrantType());
|
||||
return R.fail(MessageUtils.message("auth.grant.type.error"));
|
||||
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
|
||||
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
|
||||
|
||||
@@ -11,6 +11,17 @@ spring.boot.admin.client:
|
||||
username: @monitor.username@
|
||||
password: @monitor.password@
|
||||
|
||||
--- # mcp配置信息
|
||||
mcp:
|
||||
sse:
|
||||
enabled: false
|
||||
url: http://localhost:8085/sse
|
||||
|
||||
--- # 上传文件地址
|
||||
sys:
|
||||
upload:
|
||||
path: D:\\DownLoad
|
||||
|
||||
--- # snail-job 配置
|
||||
snail-job:
|
||||
enabled: false
|
||||
@@ -47,9 +58,16 @@ spring:
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai-v3?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: ruoyi-ai-v3
|
||||
password: ruoyi-ai-v3
|
||||
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
password: root
|
||||
# agent:
|
||||
# url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# # url: jdbc:mysql://localhost:3306/agent_db
|
||||
# username: root
|
||||
# password: root
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
|
||||
hikari:
|
||||
# 最大连接池数量
|
||||
maxPoolSize: 20
|
||||
@@ -66,17 +84,13 @@ spring:
|
||||
# 多久检查一次连接的活性
|
||||
keepaliveTime: 30000
|
||||
|
||||
agent:
|
||||
mysql:
|
||||
url: jdbc:mysql://localhost:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
password: root
|
||||
|
||||
|
||||
--- # 上传文件地址
|
||||
sys:
|
||||
upload:
|
||||
path: D:\\DownLoad
|
||||
|
||||
|
||||
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
||||
spring.data:
|
||||
redis:
|
||||
@@ -87,7 +101,7 @@ spring.data:
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# redis 密码必须配置
|
||||
password: 123456
|
||||
# password: 123456
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
# 是否开启ssl
|
||||
@@ -254,3 +268,4 @@ justauth:
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitea
|
||||
|
||||
AGENT_ALLOWED_TABLES: ""
|
||||
|
||||
271
ruoyi-admin/src/main/resources/application-prod.yml
Normal file
271
ruoyi-admin/src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,271 @@
|
||||
--- # 监控中心配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: false
|
||||
url: http://localhost:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
username: ${spring.boot.admin.client.username}
|
||||
userpassword: ${spring.boot.admin.client.password}
|
||||
username: @monitor.username@
|
||||
password: @monitor.password@
|
||||
|
||||
--- # mcp配置信息
|
||||
mcp:
|
||||
sse:
|
||||
enabled: false
|
||||
url: http://localhost:8085/sse
|
||||
|
||||
--- # 上传文件地址
|
||||
sys:
|
||||
upload:
|
||||
path: D:\\DownLoad
|
||||
|
||||
--- # snail-job 配置
|
||||
snail-job:
|
||||
enabled: false
|
||||
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
|
||||
group: "ruoyi_group"
|
||||
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
|
||||
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 17888
|
||||
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
|
||||
namespace: ${spring.profiles.active}
|
||||
# 随主应用端口漂移
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
|
||||
dynamic:
|
||||
# 性能分析插件(有性能损耗 不建议生产环境使用)
|
||||
p6spy: true
|
||||
# 设置默认的数据源或者数据源组,默认值即为 master
|
||||
primary: master
|
||||
# 严格模式 匹配不到数据源则报错
|
||||
strict: true
|
||||
datasource:
|
||||
# 主库数据源
|
||||
master:
|
||||
type: ${spring.datasource.type}
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
|
||||
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
|
||||
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
username: root
|
||||
password: root
|
||||
# agent:
|
||||
# url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
|
||||
# # url: jdbc:mysql://localhost:3306/agent_db
|
||||
# username: root
|
||||
# password: root
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
|
||||
hikari:
|
||||
# 最大连接池数量
|
||||
maxPoolSize: 20
|
||||
# 最小空闲线程数量
|
||||
minIdle: 10
|
||||
# 配置获取连接等待超时的时间
|
||||
connectionTimeout: 30000
|
||||
# 校验超时时间
|
||||
validationTimeout: 5000
|
||||
# 空闲连接存活最大时间,默认10分钟
|
||||
idleTimeout: 600000
|
||||
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
|
||||
maxLifetime: 1800000
|
||||
# 多久检查一次连接的活性
|
||||
keepaliveTime: 30000
|
||||
|
||||
|
||||
|
||||
--- # 上传文件地址
|
||||
sys:
|
||||
upload:
|
||||
path: D:\\DownLoad
|
||||
|
||||
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
|
||||
spring.data:
|
||||
redis:
|
||||
# 地址
|
||||
host: localhost
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# redis 密码必须配置
|
||||
# password: 123456
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
# 是否开启ssl
|
||||
ssl.enabled: false
|
||||
|
||||
# redisson 配置
|
||||
redisson:
|
||||
# redis key前缀
|
||||
keyPrefix:
|
||||
# 线程池数量
|
||||
threads: 4
|
||||
# Netty线程池数量
|
||||
nettyThreads: 8
|
||||
# 单节点配置
|
||||
singleServerConfig:
|
||||
# 客户端名称 不能用中文
|
||||
clientName: ruoyi-ai
|
||||
# 最小空闲连接数
|
||||
connectionMinimumIdleSize: 8
|
||||
# 连接池大小
|
||||
connectionPoolSize: 32
|
||||
# 连接空闲超时,单位:毫秒
|
||||
idleConnectionTimeout: 10000
|
||||
# 命令等待超时,单位:毫秒
|
||||
timeout: 3000
|
||||
# 发布和订阅连接池大小
|
||||
subscriptionConnectionPoolSize: 50
|
||||
|
||||
--- # mail 邮件发送
|
||||
mail:
|
||||
enabled: false
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
# 是否需要用户名密码验证
|
||||
auth: true
|
||||
# 发送方,遵循RFC-822标准
|
||||
from: xxx@163.com
|
||||
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
|
||||
user: xxx@163.com
|
||||
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
|
||||
pass: xxxxxxxxxx
|
||||
# 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
|
||||
starttlsEnable: true
|
||||
# 使用SSL安全连接
|
||||
sslEnable: true
|
||||
# SMTP超时时长,单位毫秒,缺省值不超时
|
||||
timeout: 0
|
||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||
connectionTimeout: 0
|
||||
|
||||
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
|
||||
sms:
|
||||
# 配置源类型用于标定配置来源(interface,yaml)
|
||||
config-type: yaml
|
||||
# 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制
|
||||
restricted: true
|
||||
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
|
||||
minute-max: 1
|
||||
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
|
||||
account-max: 30
|
||||
# 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
|
||||
blends:
|
||||
# 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
|
||||
# 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
|
||||
config1:
|
||||
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: alibaba
|
||||
# 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。
|
||||
access-key-id: 您的accessKey
|
||||
# 称为accessSecret有些称之为apiSecret
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
config2:
|
||||
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
|
||||
supplier: tencent
|
||||
access-key-id: 您的accessKey
|
||||
access-key-secret: 您的accessKeySecret
|
||||
signature: 您的短信签名
|
||||
sdk-app-id: 您的sdkAppId
|
||||
|
||||
|
||||
--- # 三方授权
|
||||
justauth:
|
||||
# 前端外网访问地址
|
||||
address: http://localhost:80
|
||||
type:
|
||||
maxkey:
|
||||
# maxkey 服务器地址
|
||||
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
|
||||
server-url: http://sso.maxkey.top
|
||||
client-id: 876892492581044224
|
||||
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
|
||||
redirect-uri: ${justauth.address}/social-callback?source=maxkey
|
||||
topiam:
|
||||
# topiam 服务器地址
|
||||
server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
|
||||
client-id: 449c4*********937************759
|
||||
client-secret: ac7***********1e0************28d
|
||||
redirect-uri: ${justauth.address}/social-callback?source=topiam
|
||||
scopes: [openid, email, phone, profile]
|
||||
qq:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=qq
|
||||
union-id: false
|
||||
weibo:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=weibo
|
||||
gitee:
|
||||
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
|
||||
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitee
|
||||
dingtalk:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
|
||||
baidu:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=baidu
|
||||
csdn:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=csdn
|
||||
coding:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=coding
|
||||
coding-group-name: xx
|
||||
oschina:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=oschina
|
||||
alipay_wallet:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
|
||||
alipay-public-key: MIIB**************DAQAB
|
||||
wechat_open:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
|
||||
wechat_mp:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
|
||||
wechat_enterprise:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
|
||||
agent-id: 1000002
|
||||
gitlab:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
||||
gitea:
|
||||
# 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
|
||||
# gitea 服务器地址
|
||||
server-url: https://demo.gitea.com
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
redirect-uri: ${justauth.address}/social-callback?source=gitea
|
||||
|
||||
AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task"
|
||||
@@ -124,7 +124,7 @@ security:
|
||||
- /*/api-docs
|
||||
- /*/api-docs/**
|
||||
- /warm-flow-ui/config
|
||||
|
||||
- /workflow/run
|
||||
# 多租户配置
|
||||
tenant:
|
||||
# 是否开启
|
||||
@@ -203,6 +203,7 @@ springdoc:
|
||||
name: ageerle
|
||||
email: ageerle@163.com
|
||||
url: https://gitee.com/ageerle/ruoyi-ai
|
||||
|
||||
#这里定义了两个分组,可定义多个,也可以不定义
|
||||
group-configs:
|
||||
- group: 1.演示模块
|
||||
@@ -215,6 +216,8 @@ springdoc:
|
||||
packages-to-scan: org.ruoyi.generator
|
||||
- group: 5.工作流模块
|
||||
packages-to-scan: org.ruoyi.workflow
|
||||
- group: 6.MCP模块
|
||||
packages-to-scan: org.ruoyi.mcp
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
@@ -242,12 +245,6 @@ management:
|
||||
show-details: ALWAYS
|
||||
logfile:
|
||||
external-file: ./logs/sys-console.log
|
||||
health:
|
||||
# ⚠️ 禁用 Neo4j 健康检查
|
||||
# Spring Boot Actuator 会自动为 Neo4j 创建健康检查器
|
||||
# 这会导致应用在启动时尝试连接到 Neo4j
|
||||
neo4j:
|
||||
enabled: false
|
||||
|
||||
--- # 默认/推荐使用sse推送
|
||||
sse:
|
||||
@@ -278,9 +275,9 @@ warm-flow:
|
||||
|
||||
# 向量库配置
|
||||
vector-store:
|
||||
# 向量存储类型 可选(weaviate/milvus)
|
||||
# 向量存储类型 可选(weaviate/milvus/qdrant)
|
||||
# 如需修改向量库类型,请修改此配置值!
|
||||
type: weaviate
|
||||
type: milvus
|
||||
# Weaviate配置
|
||||
weaviate:
|
||||
protocol: http
|
||||
@@ -290,69 +287,10 @@ vector-store:
|
||||
milvus:
|
||||
url: http://localhost:19530
|
||||
collectionname: LocalKnowledge
|
||||
|
||||
chat:
|
||||
memory:
|
||||
enabled: true
|
||||
maxMessages: 20
|
||||
persistenceEnabled: true
|
||||
|
||||
# 企业微信应用
|
||||
wechat:
|
||||
cp:
|
||||
corpId:
|
||||
appConfigs:
|
||||
- agentId:
|
||||
secret: ''
|
||||
token: ''
|
||||
aesKey: ''
|
||||
--- # Neo4j 知识图谱配置
|
||||
neo4j:
|
||||
uri: bolt://117.72.192.162:7687
|
||||
username: neo4j
|
||||
password: MySecurePass123!
|
||||
database: neo4j
|
||||
max-connection-pool-size: 50
|
||||
connection-timeout-seconds: 30
|
||||
|
||||
# 知识图谱配置
|
||||
knowledge:
|
||||
graph:
|
||||
# 是否启用知识图谱功能
|
||||
enabled: false
|
||||
# 图数据库类型: neo4j 或 apache-age
|
||||
database-type: neo4j
|
||||
# 是否自动创建索引
|
||||
auto-create-index: true
|
||||
# 批量处理大小
|
||||
batch-size: 1000
|
||||
# 最大重试次数
|
||||
max-retry-count: 3
|
||||
|
||||
# 实体抽取配置
|
||||
extraction:
|
||||
# 置信度阈值(低于此值的实体将被过滤)
|
||||
confidence-threshold: 0.7
|
||||
# 最大实体数量(每个文档)
|
||||
max-entities-per-doc: 100
|
||||
# 最大关系数量(每个文档)
|
||||
max-relations-per-doc: 200
|
||||
# 文本分片大小(用于长文档)
|
||||
chunk-size: 2000
|
||||
# 分片重叠大小
|
||||
chunk-overlap: 200
|
||||
|
||||
# 查询配置
|
||||
query:
|
||||
# 默认查询限制数量
|
||||
default-limit: 100
|
||||
# 最大查询限制数量
|
||||
max-limit: 1000
|
||||
# 路径查询最大深度
|
||||
max-path-depth: 5
|
||||
# 查询超时时间(秒)
|
||||
timeout-seconds: 30
|
||||
# 是否启用查询缓存
|
||||
cache-enabled: true
|
||||
# 缓存过期时间(分钟)
|
||||
cache-expire-minutes: 60
|
||||
# Qdrant配置
|
||||
qdrant:
|
||||
host: localhost
|
||||
port: 6334
|
||||
collectionname: LocalKnowledge
|
||||
api-key:
|
||||
use-tls: false
|
||||
|
||||
@@ -62,6 +62,12 @@
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger-annotations.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.ruoyi.common.chat.Service;
|
||||
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* 公共大模型对话接口
|
||||
*/
|
||||
public interface IChatService {
|
||||
|
||||
/**
|
||||
* 客户端发送对话消息到服务端
|
||||
*/
|
||||
SseEmitter chat(ChatModelVo chatModelVo, ChatRequest chatRequest, SseEmitter emitter, Long userId, String tokenValue);
|
||||
|
||||
/**
|
||||
* 工作流专用对话
|
||||
*/
|
||||
SseEmitter chat(ChatModelVo chatModelVo, ChatRequest chatRequest,SseEmitter emitter,Long userId,String tokenValue, StreamingChatResponseHandler handler);
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
*/
|
||||
String getProviderName();
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package org.ruoyi.workflow.base;
|
||||
package org.ruoyi.common.chat.base;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.ruoyi.common.chat.enums.UserStatusEnum;
|
||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.workflow.entity.User;
|
||||
import org.ruoyi.workflow.enums.UserStatusEnum;
|
||||
|
||||
import static org.ruoyi.workflow.enums.ErrorEnum.A_USER_NOT_FOUND;
|
||||
import static org.ruoyi.common.chat.enums.ErrorEnum.A_USER_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* 线程上下文适配器,统一接入 Sa-Token 登录态。
|
||||
@@ -1,13 +1,14 @@
|
||||
package org.ruoyi.domain.bo.chat;
|
||||
package org.ruoyi.common.chat.domain.bo.chat;
|
||||
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.domain.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
|
||||
/**
|
||||
* 聊天消息业务对象 chat_message
|
||||
@@ -47,11 +48,6 @@ public class ChatMessageBo extends BaseEntity {
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 扣除金额
|
||||
*/
|
||||
private Long deductCost;
|
||||
|
||||
/**
|
||||
* 累计 Tokens
|
||||
*/
|
||||
@@ -62,11 +58,6 @@ public class ChatMessageBo extends BaseEntity {
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 计费类型(1-token计费,2-次数计费)
|
||||
*/
|
||||
private String billingType;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@@ -4,7 +4,7 @@ import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.core.validate.EditGroup;
|
||||
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
@@ -45,30 +45,15 @@ public class ChatModelBo extends BaseEntity {
|
||||
*/
|
||||
private String modelDescribe;
|
||||
|
||||
/**
|
||||
* 模型价格
|
||||
*/
|
||||
private Long modelPrice;
|
||||
|
||||
/**
|
||||
* 计费类型
|
||||
*/
|
||||
private String modelType;
|
||||
|
||||
/**
|
||||
* 是否显示
|
||||
*/
|
||||
private String modelShow;
|
||||
|
||||
/**
|
||||
* 是否免费
|
||||
* 向量维度
|
||||
*/
|
||||
private String modelFree;
|
||||
|
||||
/**
|
||||
* 模型优先级(值越大优先级越高)
|
||||
*/
|
||||
private Long priority;
|
||||
private Integer modelDimension;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package org.ruoyi.common.chat.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 聊天消息DTO - 用于上下文传递
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* @date 2025/12/13
|
||||
*/
|
||||
@Data
|
||||
public class ChatMessageDTO {
|
||||
|
||||
/**
|
||||
* 消息角色: system/user/assistant
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
public static ChatMessageDTO system(String content) {
|
||||
ChatMessageDTO msg = new ChatMessageDTO();
|
||||
msg.role = "system";
|
||||
msg.content = content;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ChatMessageDTO user(String content) {
|
||||
ChatMessageDTO msg = new ChatMessageDTO();
|
||||
msg.role = "user";
|
||||
msg.content = content;
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static ChatMessageDTO assistant(String content) {
|
||||
ChatMessageDTO msg = new ChatMessageDTO();
|
||||
msg.role = "assistant";
|
||||
msg.content = content;
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.ruoyi.common.chat.domain.dto.request;
|
||||
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 对话请求对象
|
||||
@@ -16,54 +16,68 @@ import java.util.List;
|
||||
@Data
|
||||
public class ChatRequest {
|
||||
|
||||
@NotEmpty(message = "对话消息不能为空")
|
||||
private List<ChatMessageDTO> messages;
|
||||
@NotEmpty(message = "传入的模型不能为空")
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
* 对话消息
|
||||
*/
|
||||
private Long sessionId;
|
||||
@NotEmpty(message = "对话消息不能为空")
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
* 工作流请求体
|
||||
*/
|
||||
private String appId;
|
||||
private WorkFlowRunner workFlowRunner;
|
||||
|
||||
/**
|
||||
* 人机交互信息体
|
||||
*/
|
||||
private ReSumeRunner reSumeRunner;
|
||||
|
||||
/**
|
||||
* 是否为人机交互用户继续输入
|
||||
*/
|
||||
private Boolean isResume = false;
|
||||
|
||||
/**
|
||||
* 是否启用工作流
|
||||
*/
|
||||
private Boolean enableWorkFlow = false;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@JSONField(serializeUsing = String.class)
|
||||
private Long sessionId;
|
||||
|
||||
/**
|
||||
* 知识库id
|
||||
*/
|
||||
private String knowledgeId;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
|
||||
/**
|
||||
* 对话id(每个聊天窗口都不一样)
|
||||
*/
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
@JSONField(serializeUsing = String.class)
|
||||
private Long uuid;
|
||||
|
||||
/**
|
||||
* 是否启用深度思考
|
||||
*/
|
||||
private Boolean enableThinking;
|
||||
|
||||
/**
|
||||
* 是否自动切换模型
|
||||
*/
|
||||
private Boolean autoSelectModel;
|
||||
private Boolean enableThinking = false;
|
||||
|
||||
/**
|
||||
* 是否支持联网
|
||||
*/
|
||||
private Boolean enableInternet;
|
||||
|
||||
/**
|
||||
* 会话令牌(为避免在非Web线程中获取Request,入口处注入)
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 原生对话对象
|
||||
*/
|
||||
private List<ChatMessage> chatMessages;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.ruoyi.common.chat.domain.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 人机交互输入信息
|
||||
*/
|
||||
@Data
|
||||
public class ReSumeRunner {
|
||||
/**
|
||||
* 运行节点UUID
|
||||
*/
|
||||
private String runtimeUuid;
|
||||
|
||||
/**
|
||||
* 人机交互用户输入信息
|
||||
*/
|
||||
private String feedbackContent;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.ruoyi.common.chat.domain.dto.request;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流请求体信息
|
||||
*/
|
||||
@Data
|
||||
public class WorkFlowRunner {
|
||||
private List<ObjectNode> inputs;
|
||||
private String uuid;
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
package org.ruoyi.domain.vo.chat;
|
||||
package org.ruoyi.common.chat.domain.vo.chat;
|
||||
|
||||
import org.ruoyi.domain.entity.chat.ChatMessage;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
|
||||
import org.ruoyi.common.excel.convert.ExcelDictConvert;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatMessage;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 聊天消息视图对象 chat_message
|
||||
*
|
||||
@@ -57,12 +54,6 @@ public class ChatMessageVo implements Serializable {
|
||||
@ExcelProperty(value = "对话角色")
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 扣除金额
|
||||
*/
|
||||
@ExcelProperty(value = "扣除金额")
|
||||
private Long deductCost;
|
||||
|
||||
/**
|
||||
* 累计 Tokens
|
||||
*/
|
||||
@@ -75,12 +66,6 @@ public class ChatMessageVo implements Serializable {
|
||||
@ExcelProperty(value = "模型名称")
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 计费类型(1-token计费,2-次数计费)
|
||||
*/
|
||||
@ExcelProperty(value = "计费类型", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(readConverterExp = "1=-token计费,2-次数计费")
|
||||
private String billingType;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
@@ -5,7 +5,7 @@ import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatModel;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -54,17 +54,6 @@ public class ChatModelVo implements Serializable {
|
||||
@ExcelProperty(value = "模型描述")
|
||||
private String modelDescribe;
|
||||
|
||||
/**
|
||||
* 模型价格
|
||||
*/
|
||||
@ExcelProperty(value = "模型价格")
|
||||
private Long modelPrice;
|
||||
|
||||
/**
|
||||
* 计费类型
|
||||
*/
|
||||
@ExcelProperty(value = "计费类型")
|
||||
private String modelType;
|
||||
|
||||
/**
|
||||
* 是否显示
|
||||
@@ -73,16 +62,10 @@ public class ChatModelVo implements Serializable {
|
||||
private String modelShow;
|
||||
|
||||
/**
|
||||
* 是否免费
|
||||
* 向量维度
|
||||
*/
|
||||
@ExcelProperty(value = "是否免费")
|
||||
private String modelFree;
|
||||
|
||||
/**
|
||||
* 模型优先级(值越大优先级越高)
|
||||
*/
|
||||
@ExcelProperty(value = "模型优先级(值越大优先级越高)")
|
||||
private Long priority;
|
||||
@ExcelProperty(value = "向量维度")
|
||||
private Integer modelDimension;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
@@ -102,11 +85,5 @@ public class ChatModelVo implements Serializable {
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 模型维度
|
||||
*/
|
||||
private Integer dimension;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.entity;
|
||||
package org.ruoyi.common.chat.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
@@ -6,12 +6,14 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.ruoyi.workflow.entity;
|
||||
package org.ruoyi.common.chat.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.workflow.enums.UserStatusEnum;
|
||||
import org.ruoyi.common.chat.enums.UserStatusEnum;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.ruoyi.domain.entity.chat;
|
||||
package org.ruoyi.common.chat.entity.chat;
|
||||
|
||||
import org.ruoyi.common.tenant.core.TenantEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.tenant.core.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
@@ -47,10 +48,6 @@ public class ChatMessage extends TenantEntity {
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 扣除金额
|
||||
*/
|
||||
private Long deductCost;
|
||||
|
||||
/**
|
||||
* 累计 Tokens
|
||||
@@ -62,11 +59,6 @@ public class ChatMessage extends TenantEntity {
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 计费类型(1-token计费,2-次数计费)
|
||||
*/
|
||||
private String billingType;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.common.chat.domain.entity.chat;
|
||||
package org.ruoyi.common.chat.entity.chat;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@@ -48,15 +48,6 @@ public class ChatModel extends TenantEntity {
|
||||
*/
|
||||
private String modelDescribe;
|
||||
|
||||
/**
|
||||
* 模型价格
|
||||
*/
|
||||
private Long modelPrice;
|
||||
|
||||
/**
|
||||
* 计费类型
|
||||
*/
|
||||
private String modelType;
|
||||
|
||||
/**
|
||||
* 是否显示
|
||||
@@ -64,14 +55,9 @@ public class ChatModel extends TenantEntity {
|
||||
private String modelShow;
|
||||
|
||||
/**
|
||||
* 是否免费
|
||||
* 向量维度
|
||||
*/
|
||||
private String modelFree;
|
||||
|
||||
/**
|
||||
* 模型优先级(值越大优先级越高)
|
||||
*/
|
||||
private Long priority;
|
||||
private Integer modelDimension;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.common.chat.entity.image;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
|
||||
/**
|
||||
* 文生图对话上下文对象
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Builder
|
||||
public class ImageContext {
|
||||
|
||||
/**
|
||||
* 模型管理视图对象
|
||||
*/
|
||||
@NotNull(message = "模型管理视图对象不能为空")
|
||||
private ChatModelVo chatModelVo;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
@NotNull(message = "提示词不能为空")
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 图片尺寸大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 随机数种子
|
||||
*/
|
||||
@Min(value = 0, message = "随机数种子不能小于0")
|
||||
@Max(value = 2147483647, message = "随机数种子不能大于2147483647")
|
||||
private Integer seed;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -18,6 +18,7 @@ public enum RoleType {
|
||||
ASSISTANT("assistant"),
|
||||
FUNCTION("function"),
|
||||
TOOL("tool"),
|
||||
WORKFLOW("workFlow")
|
||||
;
|
||||
|
||||
private final String name;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.enums;
|
||||
package org.ruoyi.common.chat.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.common.chat.factory;
|
||||
|
||||
import org.ruoyi.common.chat.service.image.IImageGenerationService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 文生图服务工厂类
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Component
|
||||
public class ImageServiceFactory implements ApplicationContextAware {
|
||||
|
||||
private final Map<String, IImageGenerationService> imageSerivceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
// 初始化时收集所有IImageGenerationService的实现
|
||||
Map<String, IImageGenerationService> serviceMap = applicationContext.getBeansOfType(IImageGenerationService.class);
|
||||
for (IImageGenerationService service : serviceMap.values()) {
|
||||
if (service != null ) {
|
||||
imageSerivceMap.put(service.getProviderName(), service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取原始服务(不包装代理)
|
||||
*/
|
||||
public IImageGenerationService getOriginalService(String category) {
|
||||
IImageGenerationService service = imageSerivceMap.get(category);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("不支持的模型类别: " + category);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.common.chat.Service;
|
||||
package org.ruoyi.common.chat.service.chat;
|
||||
|
||||
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.ruoyi.common.chat.service.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import jakarta.validation.Valid;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* 公共大模型对话接口
|
||||
*/
|
||||
public interface IChatService {
|
||||
|
||||
/**
|
||||
* 客户端发送对话消息到服务端
|
||||
*/
|
||||
SseEmitter chat(@Valid ChatRequest chatRequest);
|
||||
|
||||
/**
|
||||
* 支持外部 handler 的对话接口(跨模块调用)
|
||||
* 同时发送到 SSE 和外部 handler
|
||||
*
|
||||
* @param chatRequest 聊天请求
|
||||
* @param externalHandler 外部响应处理器(可为 null)
|
||||
*/
|
||||
void chat(@Valid ChatRequest chatRequest, StreamingChatResponseHandler externalHandler);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.ruoyi.common.chat.service.image;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.ruoyi.common.chat.entity.image.ImageContext;
|
||||
|
||||
/**
|
||||
* 公共文生图接口
|
||||
*/
|
||||
public interface IImageGenerationService {
|
||||
|
||||
/**
|
||||
* 根据文字生成图片
|
||||
*/
|
||||
String generateImage(@Valid ImageContext imageContext);
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
*/
|
||||
String getProviderName();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.ruoyi.common.chat.service.workFlow;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.ruoyi.common.chat.entity.User;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作流启动Service接口
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-24
|
||||
*/
|
||||
public interface IWorkFlowStarterService {
|
||||
|
||||
/**
|
||||
* 启动工作流
|
||||
* @param user 用户
|
||||
* @param workflowUuid 工作流UUID
|
||||
* @param userInputs 用户输入信息
|
||||
* @return 流式输出结果
|
||||
*/
|
||||
SseEmitter streaming(User user, String workflowUuid, List<ObjectNode> userInputs, Long sessionId);
|
||||
|
||||
/**
|
||||
* 恢复工作流
|
||||
* @param runtimeUuid 运行时UUID
|
||||
* @param userInput 用户输入
|
||||
* @param sseEmitter SSE连接对象
|
||||
*/
|
||||
void resumeFlow(String runtimeUuid, String userInput, SseEmitter sseEmitter);
|
||||
}
|
||||
@@ -15,4 +15,13 @@ public interface ConfigService {
|
||||
*/
|
||||
String getConfigValue(String configKey);
|
||||
|
||||
/**
|
||||
* 根据配置类型和配置key获取值
|
||||
*
|
||||
* @param category 配置类型
|
||||
* @param configKey 配置key
|
||||
* @return 配置属性
|
||||
*/
|
||||
String getConfigValue(String category, String configKey);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.ruoyi.common.core.utils.file;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author: xiaoen
|
||||
* @Description: Content-Type 映射工具
|
||||
* @Date: Created in 18:50 2026/3/17
|
||||
*/
|
||||
public class ContentTypeUtil {
|
||||
|
||||
private static final Map<String, String> CONTENT_TYPE_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
// 文本文件
|
||||
CONTENT_TYPE_MAP.put(".txt", "text/plain; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".html", "text/html; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".htm", "text/html; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".css", "text/css; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".js", "application/javascript; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".json", "application/json; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".xml", "application/xml; charset=UTF-8");
|
||||
CONTENT_TYPE_MAP.put(".csv", "text/csv; charset=UTF-8");
|
||||
|
||||
// 图片文件
|
||||
CONTENT_TYPE_MAP.put(".jpg", "image/jpeg");
|
||||
CONTENT_TYPE_MAP.put(".jpeg", "image/jpeg");
|
||||
CONTENT_TYPE_MAP.put(".png", "image/png");
|
||||
CONTENT_TYPE_MAP.put(".gif", "image/gif");
|
||||
CONTENT_TYPE_MAP.put(".bmp", "image/bmp");
|
||||
CONTENT_TYPE_MAP.put(".webp", "image/webp");
|
||||
CONTENT_TYPE_MAP.put(".svg", "image/svg+xml");
|
||||
|
||||
// 应用文件
|
||||
CONTENT_TYPE_MAP.put(".pdf", "application/pdf");
|
||||
CONTENT_TYPE_MAP.put(".doc", "application/msword");
|
||||
CONTENT_TYPE_MAP.put(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||
CONTENT_TYPE_MAP.put(".xls", "application/vnd.ms-excel");
|
||||
CONTENT_TYPE_MAP.put(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
CONTENT_TYPE_MAP.put(".ppt", "application/vnd.ms-powerpoint");
|
||||
CONTENT_TYPE_MAP.put(".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
|
||||
|
||||
// 其他
|
||||
CONTENT_TYPE_MAP.put(".mp3", "audio/mpeg");
|
||||
CONTENT_TYPE_MAP.put(".mp4", "video/mp4");
|
||||
CONTENT_TYPE_MAP.put(".zip", "application/zip");
|
||||
CONTENT_TYPE_MAP.put(".rar", "application/x-rar-compressed");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据后缀返回对应的 content-type
|
||||
* @param suffix
|
||||
* @param defaultContentType
|
||||
* @return
|
||||
*/
|
||||
public static String getContentType(String suffix, String defaultContentType) {
|
||||
String contentType = CONTENT_TYPE_MAP.get(suffix.toLowerCase());
|
||||
return contentType != null ? contentType : defaultContentType;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,12 @@
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons Compress - 用于POI处理ZIP格式,解决Excel导出时的依赖冲突 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.ruoyi.common.sse.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||
import org.ruoyi.common.sse.dto.SseEventDto;
|
||||
import org.ruoyi.common.sse.dto.SseMessageDto;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
@@ -65,7 +67,7 @@ public class SseEmitterManager {
|
||||
emitter.onCompletion(() -> {
|
||||
SseEmitter remove = emitters.remove(token);
|
||||
if (remove != null) {
|
||||
remove.complete();
|
||||
remove.complete();
|
||||
}
|
||||
});
|
||||
emitter.onTimeout(() -> {
|
||||
@@ -174,9 +176,11 @@ public class SseEmitterManager {
|
||||
if (MapUtil.isNotEmpty(emitters)) {
|
||||
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
|
||||
try {
|
||||
// 格式化为标准SSE JSON格式
|
||||
SseEventDto eventDto = SseEventDto.content(message);
|
||||
entry.getValue().send(SseEmitter.event()
|
||||
.name("message")
|
||||
.data(message));
|
||||
.data(JSONUtil.toJsonStr(eventDto)));
|
||||
} catch (Exception e) {
|
||||
SseEmitter remove = emitters.remove(entry.getKey());
|
||||
if (remove != null) {
|
||||
@@ -189,6 +193,33 @@ public class SseEmitterManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定的用户会话发送结构化事件
|
||||
*
|
||||
* @param userId 要发送消息的用户id
|
||||
* @param eventDto SSE事件对象
|
||||
*/
|
||||
public void sendEvent(Long userId, SseEventDto eventDto) {
|
||||
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
|
||||
if (MapUtil.isNotEmpty(emitters)) {
|
||||
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
|
||||
try {
|
||||
entry.getValue().send(SseEmitter.event()
|
||||
.name(eventDto.getEvent())
|
||||
.data(JSONUtil.toJsonStr(eventDto)));
|
||||
} catch (Exception e) {
|
||||
SseEmitter remove = emitters.remove(entry.getKey());
|
||||
if (remove != null) {
|
||||
remove.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
USER_TOKEN_EMITTERS.remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 本机全用户会话发送消息
|
||||
*
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.ruoyi.common.sse.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* SSE 事件数据传输对象
|
||||
* <p>
|
||||
* 标准的 SSE 消息格式,支持不同事件类型
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* @date 2025/03/19
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SseEventDto implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 事件类型
|
||||
*/
|
||||
private String event;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 推理内容(深度思考模式)
|
||||
*/
|
||||
private String reasoningContent;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 是否完成
|
||||
*/
|
||||
private Boolean done;
|
||||
|
||||
/**
|
||||
* 创建内容事件
|
||||
*/
|
||||
public static SseEventDto content(String content) {
|
||||
return SseEventDto.builder()
|
||||
.event("content")
|
||||
.content(content)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建推理内容事件
|
||||
*/
|
||||
public static SseEventDto reasoning(String reasoningContent) {
|
||||
return SseEventDto.builder()
|
||||
.event("reasoning")
|
||||
.reasoningContent(reasoningContent)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建完成事件
|
||||
*/
|
||||
public static SseEventDto done() {
|
||||
return SseEventDto.builder()
|
||||
.event("done")
|
||||
.done(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误事件
|
||||
*/
|
||||
public static SseEventDto error(String error) {
|
||||
return SseEventDto.builder()
|
||||
.event("error")
|
||||
.error(error)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
package org.ruoyi.common.sse.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.sse.core.SseEmitterManager;
|
||||
import org.ruoyi.common.sse.dto.SseEventDto;
|
||||
import org.ruoyi.common.sse.dto.SseMessageDto;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SSE工具类
|
||||
@@ -30,6 +29,7 @@ public class SseMessageUtils {
|
||||
|
||||
/**
|
||||
* 向指定的SSE会话发送消息
|
||||
* 通过 Redis Pub/Sub 广播,确保跨模块消息可达
|
||||
*
|
||||
* @param userId 要发送消息的用户id
|
||||
* @param message 要发送的消息内容
|
||||
@@ -38,7 +38,11 @@ public class SseMessageUtils {
|
||||
if (!isEnable()) {
|
||||
return;
|
||||
}
|
||||
MANAGER.sendMessage(userId, message);
|
||||
// 通过 Redis 广播,让所有模块的 SseTopicListener 接收并转发到本地 SSE 连接
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setMessage(message);
|
||||
dto.setUserIds(Collections.singletonList(userId));
|
||||
MANAGER.publishMessage(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +93,58 @@ public class SseMessageUtils {
|
||||
MANAGER.disconnect(userId, tokenValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定的SSE会话发送结构化事件
|
||||
*
|
||||
* @param userId 要发送消息的用户id
|
||||
* @param eventDto SSE事件对象
|
||||
*/
|
||||
public static void sendEvent(Long userId, SseEventDto eventDto) {
|
||||
if (!isEnable()) {
|
||||
return;
|
||||
}
|
||||
MANAGER.sendEvent(userId, eventDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内容事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param content 内容
|
||||
*/
|
||||
public static void sendContent(Long userId, String content) {
|
||||
sendEvent(userId, SseEventDto.content(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送推理内容事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param reasoningContent 推理内容
|
||||
*/
|
||||
public static void sendReasoning(Long userId, String reasoningContent) {
|
||||
sendEvent(userId, SseEventDto.reasoning(reasoningContent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送完成事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public static void sendDone(Long userId) {
|
||||
sendEvent(userId, SseEventDto.done());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public static void sendError(Long userId, String error) {
|
||||
sendEvent(userId, SseEventDto.error(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
|
||||
@@ -121,16 +121,23 @@ public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
* 注意:对于文件下载/导出等场景,IOException 可能是正常流程的一部分,
|
||||
* 需要排除 export/download 等路径,避免干扰文件导出
|
||||
*/
|
||||
@ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(IOException.class)
|
||||
public void handleIoException(IOException e, HttpServletRequest request) {
|
||||
public R<Void> handleIoException(IOException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
if (requestURI.contains("sse")) {
|
||||
// sse 经常性连接中断 例如关闭浏览器 直接屏蔽
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
// 排除文件下载/导出相关的 IOException,让异常正常传播以便上层处理
|
||||
if (requestURI.contains("/export") || requestURI.contains("/download")) {
|
||||
// 重新抛出,让调用方处理
|
||||
throw new RuntimeException("文件导出/下载IO异常: " + e.getMessage(), e);
|
||||
}
|
||||
log.error("请求地址'{}',连接中断", requestURI, e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,6 +153,13 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
// 对于文件导出相关异常,不进行封装处理,让原始异常信息传播
|
||||
Throwable cause = e.getCause();
|
||||
if (requestURI.contains("/export") || requestURI.contains("/download")) {
|
||||
log.error("请求地址'{}',文件导出/下载异常.", requestURI, e);
|
||||
// 对于文件导出,直接返回异常信息,不进行额外封装
|
||||
return R.fail(cause != null ? cause.getMessage() : e.getMessage());
|
||||
}
|
||||
log.error("请求地址'{}',发生未知异常.", requestURI, e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-ai-copilot</module>
|
||||
<module>ruoyi-monitor-admin</module>
|
||||
<module>ruoyi-snailjob-server</module>
|
||||
</modules>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>ruoyi-ai-copilot</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>copilot</name>
|
||||
<description>SpringAI - Alibaba - Copilot</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-boot.version>4.0.1</spring-boot.version>
|
||||
<spring-ai.version>2.0.0-M2</spring-ai.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starters -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring AI -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>spring-ai-agent-utils</artifactId>
|
||||
<version>0.4.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON Processing -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON Schema Validation -->
|
||||
<dependency>
|
||||
<groupId>com.networknt</groupId>
|
||||
<artifactId>json-schema-validator</artifactId>
|
||||
<version>1.0.87</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Diff Utils -->
|
||||
<dependency>
|
||||
<groupId>io.github.java-diff-utils</groupId>
|
||||
<artifactId>java-diff-utils</artifactId>
|
||||
<version>4.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import com.example.demo.config.AppProperties;
|
||||
import com.example.demo.utils.BrowserUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* 主要功能:
|
||||
* 1. 文件读取、写入、编辑
|
||||
* 2. 目录列表和结构查看
|
||||
* 4. 连续性文件操作
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableConfigurationProperties(AppProperties.class)
|
||||
public class CopilotApplication {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CopilotApplication.class);
|
||||
|
||||
@Autowired
|
||||
private AppProperties appProperties;
|
||||
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CopilotApplication.class, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用启动完成后的事件监听器
|
||||
* 自动打开浏览器访问应用首页
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onApplicationReady() {
|
||||
AppProperties.Browser browserConfig = appProperties.getBrowser();
|
||||
|
||||
if (!browserConfig.isAutoOpen()) {
|
||||
logger.info("Browser auto-open is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取实际的服务器端口
|
||||
String port = environment.getProperty("server.port", "8080");
|
||||
String actualUrl = browserConfig.getUrl().replace("${server.port:8080}", port);
|
||||
|
||||
logger.info("Application started successfully!");
|
||||
logger.info("Preparing to open browser in {} seconds...", browserConfig.getDelaySeconds());
|
||||
|
||||
// 在新线程中延迟打开浏览器,避免阻塞主线程
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(browserConfig.getDelaySeconds() * 1000L);
|
||||
|
||||
if (BrowserUtil.isValidUrl(actualUrl)) {
|
||||
boolean success = BrowserUtil.openBrowser(actualUrl);
|
||||
if (success) {
|
||||
logger.info("✅ Browser opened successfully: {}", actualUrl);
|
||||
System.out.println("🌐 Web interface opened: " + actualUrl);
|
||||
} else {
|
||||
logger.warn("❌ Failed to open browser automatically");
|
||||
System.out.println("⚠️ Please manually open: " + actualUrl);
|
||||
}
|
||||
} else {
|
||||
logger.error("❌ Invalid URL: {}", actualUrl);
|
||||
System.out.println("⚠️ Invalid URL configured: " + actualUrl);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
logger.warn("Browser opening was interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用配置属性
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "app")
|
||||
public class AppProperties {
|
||||
|
||||
private Workspace workspace = new Workspace();
|
||||
private Security security = new Security();
|
||||
private Tools tools = new Tools();
|
||||
private Browser browser = new Browser();
|
||||
|
||||
// Getters and Setters
|
||||
public Workspace getWorkspace() {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
public void setWorkspace(Workspace workspace) {
|
||||
this.workspace = workspace;
|
||||
}
|
||||
|
||||
public Security getSecurity() {
|
||||
return security;
|
||||
}
|
||||
|
||||
public void setSecurity(Security security) {
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public Tools getTools() {
|
||||
return tools;
|
||||
}
|
||||
|
||||
public void setTools(Tools tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
public Browser getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
public void setBrowser(Browser browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批模式
|
||||
*/
|
||||
public enum ApprovalMode {
|
||||
DEFAULT, // 默认模式,危险操作需要确认
|
||||
AUTO_EDIT, // 自动编辑模式,文件编辑不需要确认
|
||||
YOLO // 完全自动模式,所有操作都不需要确认
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作空间配置
|
||||
*/
|
||||
public static class Workspace {
|
||||
// 使用 Paths.get() 和 File.separator 实现跨平台兼容
|
||||
private String rootDirectory = Paths.get(System.getProperty("user.dir"), "workspace").toString();
|
||||
private long maxFileSize = 10485760L; // 10MB
|
||||
private List<String> allowedExtensions = List.of(
|
||||
".txt", ".md", ".java", ".js", ".ts", ".json", ".xml",
|
||||
".yml", ".yaml", ".properties", ".html", ".css", ".sql"
|
||||
);
|
||||
|
||||
// Getters and Setters
|
||||
public String getRootDirectory() {
|
||||
return rootDirectory;
|
||||
}
|
||||
|
||||
public void setRootDirectory(String rootDirectory) {
|
||||
// 确保设置的路径也是跨平台兼容的
|
||||
this.rootDirectory = Paths.get(rootDirectory).toString();
|
||||
}
|
||||
|
||||
public long getMaxFileSize() {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
public void setMaxFileSize(long maxFileSize) {
|
||||
this.maxFileSize = maxFileSize;
|
||||
}
|
||||
|
||||
public List<String> getAllowedExtensions() {
|
||||
return allowedExtensions;
|
||||
}
|
||||
|
||||
public void setAllowedExtensions(List<String> allowedExtensions) {
|
||||
this.allowedExtensions = allowedExtensions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全配置
|
||||
*/
|
||||
public static class Security {
|
||||
private ApprovalMode approvalMode = ApprovalMode.DEFAULT;
|
||||
private List<String> dangerousCommands = List.of("rm", "del", "format", "fdisk", "mkfs");
|
||||
|
||||
// Getters and Setters
|
||||
public ApprovalMode getApprovalMode() {
|
||||
return approvalMode;
|
||||
}
|
||||
|
||||
public void setApprovalMode(ApprovalMode approvalMode) {
|
||||
this.approvalMode = approvalMode;
|
||||
}
|
||||
|
||||
public List<String> getDangerousCommands() {
|
||||
return dangerousCommands;
|
||||
}
|
||||
|
||||
public void setDangerousCommands(List<String> dangerousCommands) {
|
||||
this.dangerousCommands = dangerousCommands;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具配置
|
||||
*/
|
||||
public static class Tools {
|
||||
private ToolConfig readFile = new ToolConfig(true);
|
||||
private ToolConfig writeFile = new ToolConfig(true);
|
||||
private ToolConfig editFile = new ToolConfig(true);
|
||||
private ToolConfig listDirectory = new ToolConfig(true);
|
||||
private ToolConfig shell = new ToolConfig(true);
|
||||
|
||||
// Getters and Setters
|
||||
public ToolConfig getReadFile() {
|
||||
return readFile;
|
||||
}
|
||||
|
||||
public void setReadFile(ToolConfig readFile) {
|
||||
this.readFile = readFile;
|
||||
}
|
||||
|
||||
public ToolConfig getWriteFile() {
|
||||
return writeFile;
|
||||
}
|
||||
|
||||
public void setWriteFile(ToolConfig writeFile) {
|
||||
this.writeFile = writeFile;
|
||||
}
|
||||
|
||||
public ToolConfig getEditFile() {
|
||||
return editFile;
|
||||
}
|
||||
|
||||
public void setEditFile(ToolConfig editFile) {
|
||||
this.editFile = editFile;
|
||||
}
|
||||
|
||||
public ToolConfig getListDirectory() {
|
||||
return listDirectory;
|
||||
}
|
||||
|
||||
public void setListDirectory(ToolConfig listDirectory) {
|
||||
this.listDirectory = listDirectory;
|
||||
}
|
||||
|
||||
public ToolConfig getShell() {
|
||||
return shell;
|
||||
}
|
||||
|
||||
public void setShell(ToolConfig shell) {
|
||||
this.shell = shell;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具配置
|
||||
*/
|
||||
public static class ToolConfig {
|
||||
private boolean enabled;
|
||||
|
||||
public ToolConfig() {
|
||||
}
|
||||
|
||||
public ToolConfig(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器配置
|
||||
*/
|
||||
public static class Browser {
|
||||
private boolean autoOpen = true;
|
||||
private String url = "http://localhost:8080";
|
||||
private int delaySeconds = 2;
|
||||
|
||||
// Getters and Setters
|
||||
public boolean isAutoOpen() {
|
||||
return autoOpen;
|
||||
}
|
||||
|
||||
public void setAutoOpen(boolean autoOpen) {
|
||||
this.autoOpen = autoOpen;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getDelaySeconds() {
|
||||
return delaySeconds;
|
||||
}
|
||||
|
||||
public void setDelaySeconds(int delaySeconds) {
|
||||
this.delaySeconds = delaySeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
|
||||
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理超时异常
|
||||
*/
|
||||
@ExceptionHandler({TimeoutException.class, AsyncRequestTimeoutException.class})
|
||||
public ResponseEntity<ErrorResponse> handleTimeoutException(Exception e, WebRequest request) {
|
||||
logger.error("Request timeout occurred", e);
|
||||
|
||||
ErrorResponse errorResponse = new ErrorResponse(
|
||||
"TIMEOUT_ERROR",
|
||||
"Request timed out. The operation took too long to complete.",
|
||||
"Please try again with a simpler request or check your network connection."
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理AI相关异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e, WebRequest request) {
|
||||
logger.error("Runtime exception occurred", e);
|
||||
|
||||
// 检查是否是AI调用相关的异常
|
||||
String message = e.getMessage();
|
||||
if (message != null && (message.contains("tool") || message.contains("function") || message.contains("AI"))) {
|
||||
ErrorResponse errorResponse = new ErrorResponse(
|
||||
"AI_TOOL_ERROR",
|
||||
"An error occurred during AI tool execution: " + message,
|
||||
"The AI encountered an issue while processing your request. Please try rephrasing your request or try again."
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
||||
}
|
||||
|
||||
ErrorResponse errorResponse = new ErrorResponse(
|
||||
"RUNTIME_ERROR",
|
||||
"An unexpected error occurred: " + message,
|
||||
"Please try again. If the problem persists, contact support."
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleGenericException(Exception e, WebRequest request) {
|
||||
logger.error("Unexpected exception occurred", e);
|
||||
|
||||
ErrorResponse errorResponse = new ErrorResponse(
|
||||
"INTERNAL_ERROR",
|
||||
"An internal server error occurred",
|
||||
"Something went wrong on our end. Please try again later."
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应类
|
||||
*/
|
||||
public static class ErrorResponse {
|
||||
private String errorCode;
|
||||
private String message;
|
||||
private String suggestion;
|
||||
private long timestamp;
|
||||
|
||||
public ErrorResponse(String errorCode, String message, String suggestion) {
|
||||
this.errorCode = errorCode;
|
||||
this.message = message;
|
||||
this.suggestion = suggestion;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// Getters and setters
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public void setErrorCode(String errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getSuggestion() {
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
public void setSuggestion(String suggestion) {
|
||||
this.suggestion = suggestion;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 日志配置类
|
||||
* 确保日志目录存在并记录应用启动信息
|
||||
*/
|
||||
@Configuration
|
||||
public class LoggingConfiguration {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoggingConfiguration.class);
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void onApplicationReady() {
|
||||
// 确保日志目录存在
|
||||
File logsDir = new File("logs");
|
||||
if (!logsDir.exists()) {
|
||||
boolean created = logsDir.mkdirs();
|
||||
if (created) {
|
||||
logger.info("📁 创建日志目录: {}", logsDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
// 记录应用启动信息
|
||||
String startTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
logger.info("🎉 ========================================");
|
||||
logger.info("🚀 (♥◠‿◠)ノ゙ AI Copilot启动成功 ლ(´ڡ`ლ)゙");
|
||||
logger.info("🕐 启动时间: {}", startTime);
|
||||
logger.info("📝 日志级别: DEBUG (工具调用详细日志已启用)");
|
||||
logger.info("📁 日志文件: logs/copilot-file-ops.log");
|
||||
logger.info("🔧 支持的工具:");
|
||||
logger.info(" 📖 read_file - 读取文件内容");
|
||||
logger.info(" ✏️ write_file - 写入文件内容");
|
||||
logger.info(" 📝 edit_file - 编辑文件内容");
|
||||
logger.info(" 🔍 analyze_project - 分析项目结构");
|
||||
logger.info(" 🏗️ scaffold_project - 创建项目脚手架");
|
||||
logger.info(" 🧠 smart_edit - 智能编辑项目");
|
||||
logger.info("🎉 ========================================");
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import org.springaicommunity.agent.tools.FileSystemTools;
|
||||
import org.springaicommunity.agent.tools.ShellTools;
|
||||
import org.springaicommunity.agent.tools.SkillsTool;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring AI 配置 - 使用Spring AI 1.0.0规范
|
||||
*/
|
||||
@Configuration
|
||||
public class SpringAIConfiguration {
|
||||
|
||||
@Value("${agent.skills.dirs:Unknown}") List<Resource> agentSkillsDirs;
|
||||
|
||||
@Bean
|
||||
public ChatClient chatClient(ChatModel chatModel, AppProperties appProperties) {
|
||||
// 动态获取工作目录路径
|
||||
ChatClient.Builder chatClientBuilder = ChatClient.builder(chatModel);
|
||||
|
||||
return chatClientBuilder
|
||||
.defaultSystem("Always use the available skills to assist the user in their requests.")
|
||||
// Skills tool callbacks
|
||||
.defaultToolCallbacks(SkillsTool.builder().addSkillsResources(agentSkillsDirs).build())
|
||||
// Built-in tools
|
||||
.defaultTools(
|
||||
// FileSystemTools.builder().build(),
|
||||
ShellTools.builder().build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
/**
|
||||
* 任务上下文持有者
|
||||
* 使用ThreadLocal存储当前任务ID,供AOP切面使用
|
||||
*/
|
||||
public class TaskContextHolder {
|
||||
|
||||
private static final ThreadLocal<String> taskIdHolder = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 获取当前任务ID
|
||||
*/
|
||||
public static String getCurrentTaskId() {
|
||||
return taskIdHolder.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前任务ID
|
||||
*/
|
||||
public static void setCurrentTaskId(String taskId) {
|
||||
taskIdHolder.set(taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前任务ID
|
||||
*/
|
||||
public static void clearCurrentTaskId() {
|
||||
taskIdHolder.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有当前任务ID
|
||||
*/
|
||||
public static boolean hasCurrentTaskId() {
|
||||
return taskIdHolder.get() != null;
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.dto.ChatRequestDto;
|
||||
import com.example.demo.service.ContinuousConversationService;
|
||||
import com.example.demo.service.ToolExecutionLogger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 聊天控制器
|
||||
* 处理与AI的对话和工具调用
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/chat")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class ChatController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChatController.class);
|
||||
|
||||
private final ChatClient chatClient;
|
||||
private final ContinuousConversationService continuousConversationService;
|
||||
private final ToolExecutionLogger executionLogger;
|
||||
|
||||
// 简单的会话存储(生产环境应该使用数据库或Redis)
|
||||
private final List<Message> conversationHistory = new ArrayList<>();
|
||||
|
||||
public ChatController(ChatClient chatClient, ContinuousConversationService continuousConversationService, ToolExecutionLogger executionLogger) {
|
||||
this.chatClient = chatClient;
|
||||
this.continuousConversationService = continuousConversationService;
|
||||
this.executionLogger = executionLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式聊天 - 直接返回流式数据
|
||||
*/
|
||||
@PostMapping(value = "/message", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> streamMessage(@RequestBody ChatRequestDto request) {
|
||||
logger.info("📨 开始流式聊天: {}", request.getMessage());
|
||||
|
||||
return Flux.create(sink -> {
|
||||
try {
|
||||
UserMessage userMessage = new UserMessage(request.getMessage());
|
||||
conversationHistory.add(userMessage);
|
||||
|
||||
// 使用Spring AI的流式API
|
||||
Flux<String> contentStream = chatClient.prompt()
|
||||
.messages(conversationHistory)
|
||||
.stream()
|
||||
.content();
|
||||
|
||||
// 订阅流式内容并转发给前端
|
||||
contentStream
|
||||
.doOnNext(content -> {
|
||||
logger.debug("📨 流式内容片段: {}", content);
|
||||
// 发送内容片段(SSE格式会自动添加 "data: " 前缀)
|
||||
sink.next(content);
|
||||
})
|
||||
.doOnComplete(() -> {
|
||||
logger.info("✅ 流式聊天完成");
|
||||
// 发送完成标记
|
||||
sink.next("[DONE]");
|
||||
sink.complete();
|
||||
})
|
||||
.doOnError(error -> {
|
||||
logger.error("❌ 流式聊天错误: {}", error.getMessage());
|
||||
sink.error(error);
|
||||
})
|
||||
.subscribe();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("❌ 流式聊天启动失败: {}", e.getMessage());
|
||||
sink.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清除对话历史
|
||||
*/
|
||||
@PostMapping("/clear")
|
||||
public Mono<Map<String, String>> clearHistory() {
|
||||
conversationHistory.clear();
|
||||
logger.info("Conversation history cleared");
|
||||
return Mono.just(Map.of("status", "success", "message", "Conversation history cleared"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话历史
|
||||
*/
|
||||
@GetMapping("/history")
|
||||
public Mono<List<MessageDto>> getHistory() {
|
||||
List<MessageDto> history = conversationHistory.stream()
|
||||
.map(message -> {
|
||||
MessageDto dto = new MessageDto();
|
||||
dto.setContent(message.getText());
|
||||
dto.setRole(message instanceof UserMessage ? "user" : "assistant");
|
||||
return dto;
|
||||
})
|
||||
.toList();
|
||||
|
||||
return Mono.just(history);
|
||||
}
|
||||
|
||||
// 注意:Spring AI 1.0.0 使用不同的函数调用方式
|
||||
// 函数需要在配置中注册,而不是在运行时动态创建
|
||||
|
||||
public static class ChatResponseDto {
|
||||
private String taskId;
|
||||
private String message;
|
||||
private boolean success;
|
||||
private boolean asyncTask;
|
||||
private boolean streamResponse; // 新增:标识是否为流式响应
|
||||
private int totalTurns;
|
||||
private boolean reachedMaxTurns;
|
||||
private String stopReason;
|
||||
private long totalDurationMs;
|
||||
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public boolean isAsyncTask() {
|
||||
return asyncTask;
|
||||
}
|
||||
|
||||
public void setAsyncTask(boolean asyncTask) {
|
||||
this.asyncTask = asyncTask;
|
||||
}
|
||||
|
||||
public boolean isStreamResponse() {
|
||||
return streamResponse;
|
||||
}
|
||||
|
||||
public void setStreamResponse(boolean streamResponse) {
|
||||
this.streamResponse = streamResponse;
|
||||
}
|
||||
|
||||
public int getTotalTurns() {
|
||||
return totalTurns;
|
||||
}
|
||||
|
||||
public void setTotalTurns(int totalTurns) {
|
||||
this.totalTurns = totalTurns;
|
||||
}
|
||||
|
||||
public boolean isReachedMaxTurns() {
|
||||
return reachedMaxTurns;
|
||||
}
|
||||
|
||||
public void setReachedMaxTurns(boolean reachedMaxTurns) {
|
||||
this.reachedMaxTurns = reachedMaxTurns;
|
||||
}
|
||||
|
||||
public String getStopReason() {
|
||||
return stopReason;
|
||||
}
|
||||
|
||||
public void setStopReason(String stopReason) {
|
||||
this.stopReason = stopReason;
|
||||
}
|
||||
|
||||
public long getTotalDurationMs() {
|
||||
return totalDurationMs;
|
||||
}
|
||||
|
||||
public void setTotalDurationMs(long totalDurationMs) {
|
||||
this.totalDurationMs = totalDurationMs;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MessageDto {
|
||||
private String content;
|
||||
private String role;
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.service.LogStreamService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* SSE日志流控制器
|
||||
* 提供SSE连接端点
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/logs")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class LogStreamController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogStreamController.class);
|
||||
|
||||
@Autowired
|
||||
private LogStreamService logStreamService;
|
||||
|
||||
/**
|
||||
* 建立SSE连接
|
||||
* 前端通过此端点建立实时日志推送连接
|
||||
*/
|
||||
@GetMapping(value = "/stream/{taskId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter streamLogs(@PathVariable("taskId") String taskId) {
|
||||
logger.info("🔗 收到SSE连接请求: taskId={}", taskId);
|
||||
|
||||
try {
|
||||
SseEmitter emitter = logStreamService.createConnection(taskId);
|
||||
logger.info("✅ SSE连接建立成功: taskId={}", taskId);
|
||||
return emitter;
|
||||
} catch (Exception e) {
|
||||
logger.error("❌ SSE连接建立失败: taskId={}, error={}", taskId, e.getMessage());
|
||||
throw new RuntimeException("Failed to create SSE connection: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭SSE连接
|
||||
*/
|
||||
@PostMapping("/close/{taskId}")
|
||||
public void closeConnection(@PathVariable("taskId") String taskId) {
|
||||
logger.info("🔚 收到关闭SSE连接请求: taskId={}", taskId);
|
||||
logStreamService.closeConnection(taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
public ConnectionStatus getConnectionStatus() {
|
||||
int activeConnections = logStreamService.getActiveConnectionCount();
|
||||
logger.debug("📊 当前活跃SSE连接数: {}", activeConnections);
|
||||
|
||||
ConnectionStatus status = new ConnectionStatus();
|
||||
status.setActiveConnections(activeConnections);
|
||||
status.setStatus("OK");
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接状态DTO
|
||||
*/
|
||||
public static class ConnectionStatus {
|
||||
private int activeConnections;
|
||||
private String status;
|
||||
|
||||
public int getActiveConnections() {
|
||||
return activeConnections;
|
||||
}
|
||||
|
||||
public void setActiveConnections(int activeConnections) {
|
||||
this.activeConnections = activeConnections;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.model.TaskStatus;
|
||||
import com.example.demo.service.ContinuousConversationService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/task")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class TaskStatusController {
|
||||
|
||||
private final ContinuousConversationService conversationService;
|
||||
|
||||
public TaskStatusController(ContinuousConversationService conversationService) {
|
||||
this.conversationService = conversationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务状态
|
||||
*/
|
||||
@GetMapping("/status/{taskId}")
|
||||
public Mono<TaskStatusDto> getTaskStatus(@PathVariable("taskId") String taskId) {
|
||||
return Mono.fromCallable(() -> {
|
||||
TaskStatus status = conversationService.getTaskStatus(taskId);
|
||||
if (status == null) {
|
||||
throw new RuntimeException("Task not found: " + taskId);
|
||||
}
|
||||
|
||||
TaskStatusDto dto = new TaskStatusDto();
|
||||
dto.setTaskId(status.getTaskId());
|
||||
dto.setStatus(status.getStatus());
|
||||
dto.setCurrentAction(status.getCurrentAction());
|
||||
dto.setSummary(status.getSummary());
|
||||
dto.setCurrentTurn(status.getCurrentTurn());
|
||||
dto.setTotalEstimatedTurns(status.getTotalEstimatedTurns());
|
||||
dto.setProgressPercentage(status.getProgressPercentage());
|
||||
dto.setElapsedTime(status.getElapsedTime());
|
||||
dto.setErrorMessage(status.getErrorMessage());
|
||||
|
||||
return dto;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话结果
|
||||
*/
|
||||
@GetMapping("/result/{taskId}")
|
||||
public Mono<ConversationResultDto> getConversationResult(@PathVariable("taskId") String taskId) {
|
||||
return Mono.fromCallable(() -> {
|
||||
ContinuousConversationService.ConversationResult result = conversationService.getConversationResult(taskId);
|
||||
if (result == null) {
|
||||
throw new RuntimeException("Conversation result not found: " + taskId);
|
||||
}
|
||||
|
||||
ConversationResultDto dto = new ConversationResultDto();
|
||||
dto.setTaskId(taskId);
|
||||
dto.setFullResponse(result.getFullResponse());
|
||||
dto.setTurnResponses(result.getTurnResponses());
|
||||
dto.setTotalTurns(result.getTotalTurns());
|
||||
dto.setReachedMaxTurns(result.isReachedMaxTurns());
|
||||
dto.setStopReason(result.getStopReason());
|
||||
dto.setTotalDurationMs(result.getTotalDurationMs());
|
||||
|
||||
return dto;
|
||||
});
|
||||
}
|
||||
|
||||
// DTO类
|
||||
public static class TaskStatusDto {
|
||||
private String taskId;
|
||||
private String status;
|
||||
private String currentAction;
|
||||
private String summary;
|
||||
private int currentTurn;
|
||||
private int totalEstimatedTurns;
|
||||
private double progressPercentage;
|
||||
private long elapsedTime;
|
||||
private String errorMessage;
|
||||
|
||||
// Getters and Setters
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getCurrentAction() {
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public void setCurrentAction(String currentAction) {
|
||||
this.currentAction = currentAction;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public int getCurrentTurn() {
|
||||
return currentTurn;
|
||||
}
|
||||
|
||||
public void setCurrentTurn(int currentTurn) {
|
||||
this.currentTurn = currentTurn;
|
||||
}
|
||||
|
||||
public int getTotalEstimatedTurns() {
|
||||
return totalEstimatedTurns;
|
||||
}
|
||||
|
||||
public void setTotalEstimatedTurns(int totalEstimatedTurns) {
|
||||
this.totalEstimatedTurns = totalEstimatedTurns;
|
||||
}
|
||||
|
||||
public double getProgressPercentage() {
|
||||
return progressPercentage;
|
||||
}
|
||||
|
||||
public void setProgressPercentage(double progressPercentage) {
|
||||
this.progressPercentage = progressPercentage;
|
||||
}
|
||||
|
||||
public long getElapsedTime() {
|
||||
return elapsedTime;
|
||||
}
|
||||
|
||||
public void setElapsedTime(long elapsedTime) {
|
||||
this.elapsedTime = elapsedTime;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
// 对话结果DTO类
|
||||
public static class ConversationResultDto {
|
||||
private String taskId;
|
||||
private String fullResponse;
|
||||
private java.util.List<String> turnResponses;
|
||||
private int totalTurns;
|
||||
private boolean reachedMaxTurns;
|
||||
private String stopReason;
|
||||
private long totalDurationMs;
|
||||
|
||||
// Getters and Setters
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getFullResponse() {
|
||||
return fullResponse;
|
||||
}
|
||||
|
||||
public void setFullResponse(String fullResponse) {
|
||||
this.fullResponse = fullResponse;
|
||||
}
|
||||
|
||||
public java.util.List<String> getTurnResponses() {
|
||||
return turnResponses;
|
||||
}
|
||||
|
||||
public void setTurnResponses(java.util.List<String> turnResponses) {
|
||||
this.turnResponses = turnResponses;
|
||||
}
|
||||
|
||||
public int getTotalTurns() {
|
||||
return totalTurns;
|
||||
}
|
||||
|
||||
public void setTotalTurns(int totalTurns) {
|
||||
this.totalTurns = totalTurns;
|
||||
}
|
||||
|
||||
public boolean isReachedMaxTurns() {
|
||||
return reachedMaxTurns;
|
||||
}
|
||||
|
||||
public void setReachedMaxTurns(boolean reachedMaxTurns) {
|
||||
this.reachedMaxTurns = reachedMaxTurns;
|
||||
}
|
||||
|
||||
public String getStopReason() {
|
||||
return stopReason;
|
||||
}
|
||||
|
||||
public void setStopReason(String stopReason) {
|
||||
this.stopReason = stopReason;
|
||||
}
|
||||
|
||||
public long getTotalDurationMs() {
|
||||
return totalDurationMs;
|
||||
}
|
||||
|
||||
public void setTotalDurationMs(long totalDurationMs) {
|
||||
this.totalDurationMs = totalDurationMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* Web页面控制器
|
||||
*/
|
||||
@Controller
|
||||
public class WebController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理favicon.ico请求,避免404错误
|
||||
*/
|
||||
@GetMapping("/favicon.ico")
|
||||
public ResponseEntity<Void> favicon() {
|
||||
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package com.example.demo.dto;
|
||||
|
||||
/**
|
||||
* 聊天请求数据传输对象
|
||||
*/
|
||||
public class ChatRequestDto {
|
||||
private String message;
|
||||
private String sessionId; // 可选:用于会话管理
|
||||
|
||||
public ChatRequestDto() {
|
||||
}
|
||||
|
||||
public ChatRequestDto(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public ChatRequestDto(String message, String sessionId) {
|
||||
this.message = message;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChatRequestDto{" +
|
||||
"message='" + message + '\'' +
|
||||
", sessionId='" + sessionId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
package com.example.demo.model;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Project context information
|
||||
* Contains complete project analysis results for AI understanding
|
||||
*/
|
||||
public class ProjectContext {
|
||||
private Path projectRoot;
|
||||
private ProjectType projectType;
|
||||
private ProjectStructure projectStructure;
|
||||
private List<DependencyInfo> dependencies;
|
||||
private List<ConfigFile> configFiles;
|
||||
private CodeStatistics codeStatistics;
|
||||
private Map<String, Object> metadata;
|
||||
private String contextSummary;
|
||||
|
||||
public ProjectContext() {
|
||||
this.dependencies = new ArrayList<>();
|
||||
this.configFiles = new ArrayList<>();
|
||||
this.metadata = new HashMap<>();
|
||||
}
|
||||
|
||||
public ProjectContext(Path projectRoot) {
|
||||
this();
|
||||
this.projectRoot = projectRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate project context summary
|
||||
*/
|
||||
public String generateContextSummary() {
|
||||
StringBuilder summary = new StringBuilder();
|
||||
|
||||
// Basic information
|
||||
summary.append("=== PROJECT CONTEXT ===\n");
|
||||
summary.append("Project: ").append(projectRoot != null ? projectRoot.getFileName() : "Unknown").append("\n");
|
||||
summary.append("Type: ").append(projectType != null ? projectType.getDisplayName() : "Unknown").append("\n");
|
||||
summary.append("Language: ").append(projectType != null ? projectType.getPrimaryLanguage() : "Unknown").append("\n");
|
||||
summary.append("Package Manager: ").append(projectType != null ? projectType.getPackageManager() : "Unknown").append("\n\n");
|
||||
|
||||
// Project structure
|
||||
if (projectStructure != null) {
|
||||
summary.append("=== PROJECT STRUCTURE ===\n");
|
||||
summary.append(projectStructure.getStructureSummary()).append("\n");
|
||||
}
|
||||
|
||||
// Dependencies
|
||||
if (!dependencies.isEmpty()) {
|
||||
summary.append("=== DEPENDENCIES ===\n");
|
||||
dependencies.stream()
|
||||
.filter(DependencyInfo::isDirectDependency)
|
||||
.limit(10)
|
||||
.forEach(dep -> summary.append("- ").append(dep.toString()).append("\n"));
|
||||
if (dependencies.size() > 10) {
|
||||
summary.append("... and ").append(dependencies.size() - 10).append(" more dependencies\n");
|
||||
}
|
||||
summary.append("\n");
|
||||
}
|
||||
|
||||
// Configuration files
|
||||
if (!configFiles.isEmpty()) {
|
||||
summary.append("=== CONFIGURATION FILES ===\n");
|
||||
configFiles.stream()
|
||||
.filter(ConfigFile::isMainConfig)
|
||||
.forEach(config -> summary.append("- ").append(config.getFileName())
|
||||
.append(" (").append(config.getFileType()).append(")\n"));
|
||||
summary.append("\n");
|
||||
}
|
||||
|
||||
// Code statistics
|
||||
if (codeStatistics != null) {
|
||||
summary.append("=== CODE STATISTICS ===\n");
|
||||
summary.append("Total Lines: ").append(codeStatistics.getTotalLines()).append("\n");
|
||||
summary.append("Code Lines: ").append(codeStatistics.getCodeLines()).append("\n");
|
||||
if (codeStatistics.getTotalClasses() > 0) {
|
||||
summary.append("Classes: ").append(codeStatistics.getTotalClasses()).append("\n");
|
||||
}
|
||||
if (codeStatistics.getTotalMethods() > 0) {
|
||||
summary.append("Methods: ").append(codeStatistics.getTotalMethods()).append("\n");
|
||||
}
|
||||
summary.append("\n");
|
||||
}
|
||||
|
||||
this.contextSummary = summary.toString();
|
||||
return this.contextSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dependency summary
|
||||
*/
|
||||
public String getDependencySummary() {
|
||||
if (dependencies.isEmpty()) {
|
||||
return "No dependencies found";
|
||||
}
|
||||
|
||||
return dependencies.stream()
|
||||
.filter(DependencyInfo::isDirectDependency)
|
||||
.limit(5)
|
||||
.map(DependencyInfo::getName)
|
||||
.reduce((a, b) -> a + ", " + b)
|
||||
.orElse("No direct dependencies");
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Path getProjectRoot() {
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
public void setProjectRoot(Path projectRoot) {
|
||||
this.projectRoot = projectRoot;
|
||||
}
|
||||
|
||||
public ProjectType getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
public void setProjectType(ProjectType projectType) {
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
public ProjectStructure getProjectStructure() {
|
||||
return projectStructure;
|
||||
}
|
||||
|
||||
public void setProjectStructure(ProjectStructure projectStructure) {
|
||||
this.projectStructure = projectStructure;
|
||||
}
|
||||
|
||||
public List<DependencyInfo> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public void setDependencies(List<DependencyInfo> dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
public List<ConfigFile> getConfigFiles() {
|
||||
return configFiles;
|
||||
}
|
||||
|
||||
public void setConfigFiles(List<ConfigFile> configFiles) {
|
||||
this.configFiles = configFiles;
|
||||
}
|
||||
|
||||
public CodeStatistics getCodeStatistics() {
|
||||
return codeStatistics;
|
||||
}
|
||||
|
||||
public void setCodeStatistics(CodeStatistics codeStatistics) {
|
||||
this.codeStatistics = codeStatistics;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public String getContextSummary() {
|
||||
return contextSummary;
|
||||
}
|
||||
|
||||
public void setContextSummary(String contextSummary) {
|
||||
this.contextSummary = contextSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency information class
|
||||
*/
|
||||
public static class DependencyInfo {
|
||||
private String name;
|
||||
private String version;
|
||||
private String type; // "compile", "test", "runtime", etc.
|
||||
private String scope;
|
||||
private boolean isDirectDependency;
|
||||
|
||||
public DependencyInfo(String name, String version, String type) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.type = type;
|
||||
this.isDirectDependency = true;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public boolean isDirectDependency() {
|
||||
return isDirectDependency;
|
||||
}
|
||||
|
||||
public void setDirectDependency(boolean directDependency) {
|
||||
isDirectDependency = directDependency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s:%s (%s)", name, version, type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration file information class
|
||||
*/
|
||||
public static class ConfigFile {
|
||||
private String fileName;
|
||||
private String relativePath;
|
||||
private String fileType; // "properties", "yaml", "json", "xml", etc.
|
||||
private Map<String, Object> keySettings;
|
||||
private boolean isMainConfig;
|
||||
|
||||
public ConfigFile(String fileName, String relativePath, String fileType) {
|
||||
this.fileName = fileName;
|
||||
this.relativePath = relativePath;
|
||||
this.fileType = fileType;
|
||||
this.keySettings = new HashMap<>();
|
||||
this.isMainConfig = false;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getRelativePath() {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
public void setRelativePath(String relativePath) {
|
||||
this.relativePath = relativePath;
|
||||
}
|
||||
|
||||
public String getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
|
||||
public void setFileType(String fileType) {
|
||||
this.fileType = fileType;
|
||||
}
|
||||
|
||||
public Map<String, Object> getKeySettings() {
|
||||
return keySettings;
|
||||
}
|
||||
|
||||
public void setKeySettings(Map<String, Object> keySettings) {
|
||||
this.keySettings = keySettings;
|
||||
}
|
||||
|
||||
public boolean isMainConfig() {
|
||||
return isMainConfig;
|
||||
}
|
||||
|
||||
public void setMainConfig(boolean mainConfig) {
|
||||
isMainConfig = mainConfig;
|
||||
}
|
||||
|
||||
public void addSetting(String key, Object value) {
|
||||
this.keySettings.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Code statistics information class
|
||||
*/
|
||||
public static class CodeStatistics {
|
||||
private int totalLines;
|
||||
private int codeLines;
|
||||
private int commentLines;
|
||||
private int blankLines;
|
||||
private Map<String, Integer> languageLines;
|
||||
private int totalClasses;
|
||||
private int totalMethods;
|
||||
private int totalFunctions;
|
||||
|
||||
public CodeStatistics() {
|
||||
this.languageLines = new HashMap<>();
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public int getTotalLines() {
|
||||
return totalLines;
|
||||
}
|
||||
|
||||
public void setTotalLines(int totalLines) {
|
||||
this.totalLines = totalLines;
|
||||
}
|
||||
|
||||
public int getCodeLines() {
|
||||
return codeLines;
|
||||
}
|
||||
|
||||
public void setCodeLines(int codeLines) {
|
||||
this.codeLines = codeLines;
|
||||
}
|
||||
|
||||
public int getCommentLines() {
|
||||
return commentLines;
|
||||
}
|
||||
|
||||
public void setCommentLines(int commentLines) {
|
||||
this.commentLines = commentLines;
|
||||
}
|
||||
|
||||
public int getBlankLines() {
|
||||
return blankLines;
|
||||
}
|
||||
|
||||
public void setBlankLines(int blankLines) {
|
||||
this.blankLines = blankLines;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getLanguageLines() {
|
||||
return languageLines;
|
||||
}
|
||||
|
||||
public void setLanguageLines(Map<String, Integer> languageLines) {
|
||||
this.languageLines = languageLines;
|
||||
}
|
||||
|
||||
public int getTotalClasses() {
|
||||
return totalClasses;
|
||||
}
|
||||
|
||||
public void setTotalClasses(int totalClasses) {
|
||||
this.totalClasses = totalClasses;
|
||||
}
|
||||
|
||||
public int getTotalMethods() {
|
||||
return totalMethods;
|
||||
}
|
||||
|
||||
public void setTotalMethods(int totalMethods) {
|
||||
this.totalMethods = totalMethods;
|
||||
}
|
||||
|
||||
public int getTotalFunctions() {
|
||||
return totalFunctions;
|
||||
}
|
||||
|
||||
public void setTotalFunctions(int totalFunctions) {
|
||||
this.totalFunctions = totalFunctions;
|
||||
}
|
||||
|
||||
public void addLanguageLines(String language, int lines) {
|
||||
this.languageLines.put(language, this.languageLines.getOrDefault(language, 0) + lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package com.example.demo.model;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Project structure information model
|
||||
* Contains project directory structure, file statistics and other information
|
||||
*/
|
||||
public class ProjectStructure {
|
||||
private Path projectRoot;
|
||||
private ProjectType projectType;
|
||||
private List<DirectoryInfo> directories;
|
||||
private Map<String, Integer> fileTypeCount;
|
||||
private List<String> keyFiles;
|
||||
private int totalFiles;
|
||||
private int totalDirectories;
|
||||
private long totalSize;
|
||||
|
||||
public ProjectStructure() {
|
||||
this.directories = new ArrayList<>();
|
||||
this.fileTypeCount = new HashMap<>();
|
||||
this.keyFiles = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ProjectStructure(Path projectRoot, ProjectType projectType) {
|
||||
this();
|
||||
this.projectRoot = projectRoot;
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add directory information
|
||||
*/
|
||||
public void addDirectory(DirectoryInfo directoryInfo) {
|
||||
this.directories.add(directoryInfo);
|
||||
this.totalDirectories++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file type statistics
|
||||
*/
|
||||
public void addFileType(String extension, int count) {
|
||||
this.fileTypeCount.put(extension, this.fileTypeCount.getOrDefault(extension, 0) + count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add key file
|
||||
*/
|
||||
public void addKeyFile(String fileName) {
|
||||
if (!this.keyFiles.contains(fileName)) {
|
||||
this.keyFiles.add(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project structure summary
|
||||
*/
|
||||
public String getStructureSummary() {
|
||||
StringBuilder summary = new StringBuilder();
|
||||
summary.append("Project: ").append(projectRoot != null ? projectRoot.getFileName() : "Unknown").append("\n");
|
||||
summary.append("Type: ").append(projectType != null ? projectType.getDisplayName() : "Unknown").append("\n");
|
||||
summary.append("Directories: ").append(totalDirectories).append("\n");
|
||||
summary.append("Files: ").append(totalFiles).append("\n");
|
||||
|
||||
if (!keyFiles.isEmpty()) {
|
||||
summary.append("Key Files: ").append(String.join(", ", keyFiles)).append("\n");
|
||||
}
|
||||
|
||||
if (!fileTypeCount.isEmpty()) {
|
||||
summary.append("File Types: ");
|
||||
fileTypeCount.entrySet().stream()
|
||||
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
|
||||
.limit(5)
|
||||
.forEach(entry -> summary.append(entry.getKey()).append("(").append(entry.getValue()).append(") "));
|
||||
summary.append("\n");
|
||||
}
|
||||
|
||||
return summary.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get important directories list
|
||||
*/
|
||||
public List<DirectoryInfo> getImportantDirectories() {
|
||||
return directories.stream()
|
||||
.filter(DirectoryInfo::isImportant)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark important directories based on project type
|
||||
*/
|
||||
public void markImportantDirectories() {
|
||||
if (projectType == null) return;
|
||||
|
||||
for (DirectoryInfo dir : directories) {
|
||||
String dirName = dir.getName().toLowerCase();
|
||||
|
||||
// Common important directories
|
||||
if (dirName.equals("src") || dirName.equals("source") ||
|
||||
dirName.equals("test") || dirName.equals("tests") ||
|
||||
dirName.equals("config") || dirName.equals("conf") ||
|
||||
dirName.equals("docs") || dirName.equals("doc")) {
|
||||
dir.setImportant(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Project type specific important directories
|
||||
switch (projectType) {
|
||||
case JAVA_MAVEN:
|
||||
case JAVA_GRADLE:
|
||||
case SPRING_BOOT:
|
||||
if (dirName.equals("main") || dirName.equals("resources") ||
|
||||
dirName.equals("webapp") || dirName.equals("target") ||
|
||||
dirName.equals("build")) {
|
||||
dir.setImportant(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case NODE_JS:
|
||||
case REACT:
|
||||
case VUE:
|
||||
case ANGULAR:
|
||||
case NEXT_JS:
|
||||
if (dirName.equals("node_modules") || dirName.equals("public") ||
|
||||
dirName.equals("dist") || dirName.equals("build") ||
|
||||
dirName.equals("components") || dirName.equals("pages")) {
|
||||
dir.setImportant(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case PYTHON:
|
||||
case DJANGO:
|
||||
case FLASK:
|
||||
case FASTAPI:
|
||||
if (dirName.equals("venv") || dirName.equals("env") ||
|
||||
dirName.equals("__pycache__") || dirName.equals("migrations") ||
|
||||
dirName.equals("static") || dirName.equals("templates")) {
|
||||
dir.setImportant(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Path getProjectRoot() {
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
public void setProjectRoot(Path projectRoot) {
|
||||
this.projectRoot = projectRoot;
|
||||
}
|
||||
|
||||
public ProjectType getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
public void setProjectType(ProjectType projectType) {
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
public List<DirectoryInfo> getDirectories() {
|
||||
return directories;
|
||||
}
|
||||
|
||||
public void setDirectories(List<DirectoryInfo> directories) {
|
||||
this.directories = directories;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getFileTypeCount() {
|
||||
return fileTypeCount;
|
||||
}
|
||||
|
||||
public void setFileTypeCount(Map<String, Integer> fileTypeCount) {
|
||||
this.fileTypeCount = fileTypeCount;
|
||||
}
|
||||
|
||||
public List<String> getKeyFiles() {
|
||||
return keyFiles;
|
||||
}
|
||||
|
||||
public void setKeyFiles(List<String> keyFiles) {
|
||||
this.keyFiles = keyFiles;
|
||||
}
|
||||
|
||||
public int getTotalFiles() {
|
||||
return totalFiles;
|
||||
}
|
||||
|
||||
public void setTotalFiles(int totalFiles) {
|
||||
this.totalFiles = totalFiles;
|
||||
}
|
||||
|
||||
public int getTotalDirectories() {
|
||||
return totalDirectories;
|
||||
}
|
||||
|
||||
public void setTotalDirectories(int totalDirectories) {
|
||||
this.totalDirectories = totalDirectories;
|
||||
}
|
||||
|
||||
public long getTotalSize() {
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
public void setTotalSize(long totalSize) {
|
||||
this.totalSize = totalSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directory information inner class
|
||||
*/
|
||||
public static class DirectoryInfo {
|
||||
private String name;
|
||||
private String relativePath;
|
||||
private int fileCount;
|
||||
private List<String> files;
|
||||
private boolean isImportant; // Whether it's an important directory (like src, test, etc.)
|
||||
|
||||
public DirectoryInfo(String name, String relativePath) {
|
||||
this.name = name;
|
||||
this.relativePath = relativePath;
|
||||
this.files = new ArrayList<>();
|
||||
this.isImportant = false;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getRelativePath() {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
public void setRelativePath(String relativePath) {
|
||||
this.relativePath = relativePath;
|
||||
}
|
||||
|
||||
public int getFileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
public void setFileCount(int fileCount) {
|
||||
this.fileCount = fileCount;
|
||||
}
|
||||
|
||||
public List<String> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setFiles(List<String> files) {
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public boolean isImportant() {
|
||||
return isImportant;
|
||||
}
|
||||
|
||||
public void setImportant(boolean important) {
|
||||
isImportant = important;
|
||||
}
|
||||
|
||||
public void addFile(String fileName) {
|
||||
this.files.add(fileName);
|
||||
this.fileCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package com.example.demo.model;
|
||||
|
||||
/**
|
||||
* Project type enumeration
|
||||
* Supports mainstream project type detection
|
||||
*/
|
||||
public enum ProjectType {
|
||||
// Java projects
|
||||
JAVA_MAVEN("Java Maven", "pom.xml", "Maven-based Java project"),
|
||||
JAVA_GRADLE("Java Gradle", "build.gradle", "Gradle-based Java project"),
|
||||
SPRING_BOOT("Spring Boot", "pom.xml", "Spring Boot application"),
|
||||
|
||||
// JavaScript/Node.js projects
|
||||
NODE_JS("Node.js", "package.json", "Node.js project"),
|
||||
REACT("React", "package.json", "React application"),
|
||||
VUE("Vue.js", "package.json", "Vue.js application"),
|
||||
ANGULAR("Angular", "package.json", "Angular application"),
|
||||
NEXT_JS("Next.js", "package.json", "Next.js application"),
|
||||
|
||||
// Python projects
|
||||
PYTHON("Python", "requirements.txt", "Python project"),
|
||||
DJANGO("Django", "manage.py", "Django web application"),
|
||||
FLASK("Flask", "app.py", "Flask web application"),
|
||||
FASTAPI("FastAPI", "main.py", "FastAPI application"),
|
||||
|
||||
// Other project types
|
||||
DOTNET("ASP.NET", "*.csproj", ".NET project"),
|
||||
GO("Go", "go.mod", "Go project"),
|
||||
RUST("Rust", "Cargo.toml", "Rust project"),
|
||||
PHP("PHP", "composer.json", "PHP project"),
|
||||
|
||||
// Web frontend
|
||||
HTML_STATIC("Static HTML", "index.html", "Static HTML website"),
|
||||
|
||||
// Unknown type
|
||||
UNKNOWN("Unknown", "", "Unknown project type");
|
||||
|
||||
private final String displayName;
|
||||
private final String keyFile;
|
||||
private final String description;
|
||||
|
||||
ProjectType(String displayName, String keyFile, String description) {
|
||||
this.displayName = displayName;
|
||||
this.keyFile = keyFile;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getKeyFile() {
|
||||
return keyFile;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's a Java project
|
||||
*/
|
||||
public boolean isJavaProject() {
|
||||
return this == JAVA_MAVEN || this == JAVA_GRADLE || this == SPRING_BOOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's a JavaScript project
|
||||
*/
|
||||
public boolean isJavaScriptProject() {
|
||||
return this == NODE_JS || this == REACT || this == VUE ||
|
||||
this == ANGULAR || this == NEXT_JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's a Python project
|
||||
*/
|
||||
public boolean isPythonProject() {
|
||||
return this == PYTHON || this == DJANGO || this == FLASK || this == FASTAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it's a Web project
|
||||
*/
|
||||
public boolean isWebProject() {
|
||||
return isJavaScriptProject() || this == HTML_STATIC ||
|
||||
this == DJANGO || this == FLASK || this == FASTAPI || this == SPRING_BOOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary programming language of the project
|
||||
*/
|
||||
public String getPrimaryLanguage() {
|
||||
if (isJavaProject()) return "Java";
|
||||
if (isJavaScriptProject()) return "JavaScript";
|
||||
if (isPythonProject()) return "Python";
|
||||
|
||||
switch (this) {
|
||||
case DOTNET:
|
||||
return "C#";
|
||||
case GO:
|
||||
return "Go";
|
||||
case RUST:
|
||||
return "Rust";
|
||||
case PHP:
|
||||
return "PHP";
|
||||
case HTML_STATIC:
|
||||
return "HTML";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the recommended package manager
|
||||
*/
|
||||
public String getPackageManager() {
|
||||
switch (this) {
|
||||
case JAVA_MAVEN:
|
||||
case SPRING_BOOT:
|
||||
return "Maven";
|
||||
case JAVA_GRADLE:
|
||||
return "Gradle";
|
||||
case NODE_JS:
|
||||
case REACT:
|
||||
case VUE:
|
||||
case ANGULAR:
|
||||
case NEXT_JS:
|
||||
return "npm/yarn";
|
||||
case PYTHON:
|
||||
case DJANGO:
|
||||
case FLASK:
|
||||
case FASTAPI:
|
||||
return "pip";
|
||||
case DOTNET:
|
||||
return "NuGet";
|
||||
case GO:
|
||||
return "go mod";
|
||||
case RUST:
|
||||
return "Cargo";
|
||||
case PHP:
|
||||
return "Composer";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package com.example.demo.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TaskStatus {
|
||||
private String taskId;
|
||||
private String status; // RUNNING, COMPLETED, ERROR
|
||||
private String currentAction;
|
||||
private String summary;
|
||||
private int currentTurn;
|
||||
private int totalEstimatedTurns;
|
||||
private long startTime;
|
||||
private long lastUpdateTime;
|
||||
private List<String> actionHistory;
|
||||
private String errorMessage;
|
||||
private double progressPercentage;
|
||||
|
||||
public TaskStatus(String taskId) {
|
||||
this.taskId = taskId;
|
||||
this.status = "RUNNING";
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.lastUpdateTime = this.startTime;
|
||||
this.actionHistory = new ArrayList<>();
|
||||
this.progressPercentage = 0.0;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
this.lastUpdateTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getCurrentAction() {
|
||||
return currentAction;
|
||||
}
|
||||
|
||||
public void setCurrentAction(String currentAction) {
|
||||
this.currentAction = currentAction;
|
||||
this.lastUpdateTime = System.currentTimeMillis();
|
||||
if (currentAction != null && !currentAction.trim().isEmpty()) {
|
||||
this.actionHistory.add(currentAction);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public int getCurrentTurn() {
|
||||
return currentTurn;
|
||||
}
|
||||
|
||||
public void setCurrentTurn(int currentTurn) {
|
||||
this.currentTurn = currentTurn;
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
public int getTotalEstimatedTurns() {
|
||||
return totalEstimatedTurns;
|
||||
}
|
||||
|
||||
public void setTotalEstimatedTurns(int totalEstimatedTurns) {
|
||||
this.totalEstimatedTurns = totalEstimatedTurns;
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public long getLastUpdateTime() {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
|
||||
public List<String> getActionHistory() {
|
||||
return actionHistory;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public double getProgressPercentage() {
|
||||
return progressPercentage;
|
||||
}
|
||||
|
||||
private void updateProgress() {
|
||||
if (totalEstimatedTurns > 0) {
|
||||
this.progressPercentage = Math.min(100.0, (double) currentTurn / totalEstimatedTurns * 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
public long getElapsedTime() {
|
||||
return System.currentTimeMillis() - startTime;
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package com.example.demo.schema;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JSON Schema definition class
|
||||
* Used to define tool parameter structure and validation rules
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class JsonSchema {
|
||||
|
||||
private String type;
|
||||
private String description;
|
||||
private String pattern;
|
||||
private Number minimum;
|
||||
private Number maximum;
|
||||
private List<Object> enumValues;
|
||||
|
||||
@JsonProperty("properties")
|
||||
private Map<String, JsonSchema> properties;
|
||||
|
||||
@JsonProperty("required")
|
||||
private List<String> requiredFields;
|
||||
|
||||
@JsonProperty("items")
|
||||
private JsonSchema items;
|
||||
|
||||
// Constructor
|
||||
public JsonSchema() {
|
||||
}
|
||||
|
||||
// Static factory methods
|
||||
public static JsonSchema object() {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "object";
|
||||
schema.properties = new HashMap<>();
|
||||
return schema;
|
||||
}
|
||||
|
||||
public static JsonSchema string(String description) {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "string";
|
||||
schema.description = description;
|
||||
return schema;
|
||||
}
|
||||
|
||||
public static JsonSchema number(String description) {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "number";
|
||||
schema.description = description;
|
||||
return schema;
|
||||
}
|
||||
|
||||
public static JsonSchema integer(String description) {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "integer";
|
||||
schema.description = description;
|
||||
return schema;
|
||||
}
|
||||
|
||||
public static JsonSchema bool(String description) {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "boolean";
|
||||
schema.description = description;
|
||||
return schema;
|
||||
}
|
||||
|
||||
public static JsonSchema array(JsonSchema items) {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "array";
|
||||
schema.items = items;
|
||||
return schema;
|
||||
}
|
||||
|
||||
public static JsonSchema array(String description, JsonSchema items) {
|
||||
JsonSchema schema = new JsonSchema();
|
||||
schema.type = "array";
|
||||
schema.description = description;
|
||||
schema.items = items;
|
||||
return schema;
|
||||
}
|
||||
|
||||
// Fluent methods
|
||||
public JsonSchema addProperty(String name, JsonSchema property) {
|
||||
if (this.properties == null) {
|
||||
this.properties = new HashMap<>();
|
||||
}
|
||||
this.properties.put(name, property);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonSchema required(String... fields) {
|
||||
this.requiredFields = Arrays.asList(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonSchema pattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonSchema minimum(Number minimum) {
|
||||
this.minimum = minimum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonSchema maximum(Number maximum) {
|
||||
this.maximum = maximum;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JsonSchema enumValues(Object... values) {
|
||||
this.enumValues = Arrays.asList(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public void setPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public Number getMinimum() {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
public void setMinimum(Number minimum) {
|
||||
this.minimum = minimum;
|
||||
}
|
||||
|
||||
public Number getMaximum() {
|
||||
return maximum;
|
||||
}
|
||||
|
||||
public void setMaximum(Number maximum) {
|
||||
this.maximum = maximum;
|
||||
}
|
||||
|
||||
public List<Object> getEnumValues() {
|
||||
return enumValues;
|
||||
}
|
||||
|
||||
public void setEnumValues(List<Object> enumValues) {
|
||||
this.enumValues = enumValues;
|
||||
}
|
||||
|
||||
public Map<String, JsonSchema> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public void setProperties(Map<String, JsonSchema> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public List<String> getRequiredFields() {
|
||||
return requiredFields;
|
||||
}
|
||||
|
||||
public void setRequiredFields(List<String> requiredFields) {
|
||||
this.requiredFields = requiredFields;
|
||||
}
|
||||
|
||||
public JsonSchema getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(JsonSchema items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.example.demo.schema;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.networknt.schema.JsonSchemaFactory;
|
||||
import com.networknt.schema.SpecVersion;
|
||||
import com.networknt.schema.ValidationMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JSON Schema validator
|
||||
* Used to validate tool parameters against defined schema
|
||||
*/
|
||||
@Component
|
||||
public class SchemaValidator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SchemaValidator.class);
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final JsonSchemaFactory schemaFactory;
|
||||
|
||||
public SchemaValidator() {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate data against schema
|
||||
*
|
||||
* @param schema JSON Schema definition
|
||||
* @param data Data to validate
|
||||
* @return Validation error message, null means validation passed
|
||||
*/
|
||||
public String validate(JsonSchema schema, Object data) {
|
||||
try {
|
||||
// Convert custom JsonSchema to standard JSON Schema string
|
||||
String schemaJson = objectMapper.writeValueAsString(schema);
|
||||
logger.debug("Schema JSON: {}", schemaJson);
|
||||
|
||||
// Create JSON Schema validator
|
||||
com.networknt.schema.JsonSchema jsonSchema = schemaFactory.getSchema(schemaJson);
|
||||
|
||||
// Convert data to JsonNode
|
||||
String dataJson = objectMapper.writeValueAsString(data);
|
||||
JsonNode dataNode = objectMapper.readTree(dataJson);
|
||||
logger.debug("Data JSON: {}", dataJson);
|
||||
|
||||
// Execute validation
|
||||
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
logger.debug("Schema validation passed");
|
||||
return null; // Validation passed
|
||||
} else {
|
||||
String errorMessage = errors.stream()
|
||||
.map(ValidationMessage::getMessage)
|
||||
.collect(Collectors.joining("; "));
|
||||
logger.warn("Schema validation failed: {}", errorMessage);
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "Schema validation error: " + e.getMessage();
|
||||
logger.error(errorMessage, e);
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple type validation (fallback solution)
|
||||
* Used when JSON Schema validation fails
|
||||
*/
|
||||
public String validateSimple(JsonSchema schema, Object data) {
|
||||
if (schema == null || data == null) {
|
||||
return "Schema or data is null";
|
||||
}
|
||||
|
||||
// Basic type checking
|
||||
String expectedType = schema.getType();
|
||||
if (expectedType != null) {
|
||||
String actualType = getDataType(data);
|
||||
if (!isTypeCompatible(expectedType, actualType)) {
|
||||
return String.format("Type mismatch: expected %s, got %s", expectedType, actualType);
|
||||
}
|
||||
}
|
||||
|
||||
// Required field checking (only for object type)
|
||||
if ("object".equals(expectedType) && schema.getRequiredFields() != null) {
|
||||
if (!(data instanceof java.util.Map)) {
|
||||
return "Expected object type for required field validation";
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.Map<String, Object> dataMap = (java.util.Map<String, Object>) data;
|
||||
|
||||
for (String requiredField : schema.getRequiredFields()) {
|
||||
if (!dataMap.containsKey(requiredField) || dataMap.get(requiredField) == null) {
|
||||
return "Missing required field: " + requiredField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // Validation passed
|
||||
}
|
||||
|
||||
private String getDataType(Object data) {
|
||||
if (data == null) return "null";
|
||||
if (data instanceof String) return "string";
|
||||
if (data instanceof Integer || data instanceof Long) return "integer";
|
||||
if (data instanceof Number) return "number";
|
||||
if (data instanceof Boolean) return "boolean";
|
||||
if (data instanceof java.util.List) return "array";
|
||||
if (data instanceof java.util.Map) return "object";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private boolean isTypeCompatible(String expectedType, String actualType) {
|
||||
if (expectedType.equals(actualType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Number type compatibility
|
||||
if ("number".equals(expectedType) && "integer".equals(actualType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.config.TaskContextHolder;
|
||||
import com.example.demo.model.TaskStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 连续对话服务
|
||||
*/
|
||||
@Service
|
||||
public class ContinuousConversationService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContinuousConversationService.class);
|
||||
// 最大轮数限制,防止无限循环
|
||||
private static final int MAX_TURNS = 20;
|
||||
// 单轮对话超时时间(毫秒)
|
||||
private static final long TURN_TIMEOUT_MS = 60_000; // 60秒
|
||||
// 总对话超时时间(毫秒)
|
||||
private static final long TOTAL_TIMEOUT_MS = 10 * 60_000; // 10分钟
|
||||
// 继续对话的提示语
|
||||
private static final String[] CONTINUE_PROMPTS = {
|
||||
"Continue with the next steps to complete the task.",
|
||||
"Please proceed with the remaining work.",
|
||||
"What's the next step? Please continue.",
|
||||
"Keep going with the task.",
|
||||
"Continue the implementation."
|
||||
};
|
||||
private final ChatClient chatClient;
|
||||
private final NextSpeakerService nextSpeakerService;
|
||||
// 添加依赖注入
|
||||
private final TaskSummaryService taskSummaryService;
|
||||
|
||||
// 在现有的ContinuousConversationService中添加以下改进
|
||||
private final Map<String, TaskStatus> taskStatusMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConversationResult> conversationResults = new ConcurrentHashMap<>();
|
||||
@Autowired
|
||||
private LogStreamService logStreamService;
|
||||
|
||||
// 修改构造函数
|
||||
public ContinuousConversationService(ChatClient chatClient,
|
||||
NextSpeakerService nextSpeakerService,
|
||||
TaskSummaryService taskSummaryService) {
|
||||
this.chatClient = chatClient;
|
||||
this.nextSpeakerService = nextSpeakerService;
|
||||
this.taskSummaryService = taskSummaryService;
|
||||
}
|
||||
|
||||
// 添加任务状态管理方法
|
||||
public TaskStatus getTaskStatus(String taskId) {
|
||||
return taskStatusMap.get(taskId);
|
||||
}
|
||||
|
||||
// 获取对话结果
|
||||
public ConversationResult getConversationResult(String taskId) {
|
||||
return conversationResults.get(taskId);
|
||||
}
|
||||
|
||||
// 存储对话结果
|
||||
private void storeConversationResult(String taskId, ConversationResult result) {
|
||||
conversationResults.put(taskId, result);
|
||||
}
|
||||
|
||||
public String startTask(String initialMessage) {
|
||||
String taskId = UUID.randomUUID().toString();
|
||||
TaskStatus status = new TaskStatus(taskId);
|
||||
|
||||
// 估算任务复杂度
|
||||
int estimatedTurns = taskSummaryService.estimateTaskComplexity(initialMessage);
|
||||
status.setTotalEstimatedTurns(estimatedTurns);
|
||||
status.setCurrentAction("开始分析任务...");
|
||||
|
||||
taskStatusMap.put(taskId, status);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能判断用户消息是否可能需要工具调用
|
||||
* 用于决定是否使用异步模式和显示工具执行状态
|
||||
*/
|
||||
public boolean isLikelyToNeedTools(String message) {
|
||||
if (message == null || message.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lowerMessage = message.toLowerCase().trim();
|
||||
|
||||
// 明确的简单对话模式 - 不需要工具
|
||||
String[] simplePatterns = {
|
||||
"你好", "hello", "hi", "嗨", "哈喽",
|
||||
"谢谢", "thank you", "thanks", "感谢",
|
||||
"再见", "goodbye", "bye", "拜拜",
|
||||
"好的", "ok", "okay", "行", "可以",
|
||||
"不用了", "算了", "没事", "不需要",
|
||||
"怎么样", "如何", "什么意思", "是什么",
|
||||
"介绍一下", "解释一下", "说明一下"
|
||||
};
|
||||
|
||||
// 检查是否是简单问候或确认
|
||||
for (String pattern : simplePatterns) {
|
||||
if (lowerMessage.equals(pattern) ||
|
||||
(lowerMessage.length() <= 10 && lowerMessage.contains(pattern))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 明确需要工具的关键词
|
||||
String[] toolRequiredPatterns = {
|
||||
"创建", "create", "新建", "生成", "建立",
|
||||
"编辑", "edit", "修改", "更新", "改变",
|
||||
"删除", "delete", "移除", "清除",
|
||||
"文件", "file", "目录", "folder", "项目", "project",
|
||||
"代码", "code", "程序", "script", "函数", "function",
|
||||
"分析", "analyze", "检查", "查看", "读取", "read",
|
||||
"写入", "write", "保存", "save",
|
||||
"搜索", "search", "查找", "find",
|
||||
"下载", "download", "获取", "fetch",
|
||||
"安装", "install", "配置", "config",
|
||||
"运行", "run", "执行", "execute",
|
||||
"测试", "test", "调试", "debug"
|
||||
};
|
||||
|
||||
// 检查是否包含工具相关关键词
|
||||
for (String pattern : toolRequiredPatterns) {
|
||||
if (lowerMessage.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 基于消息长度和复杂度的启发式判断
|
||||
// 长消息更可能需要工具处理
|
||||
if (message.length() > 50) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 包含路径、URL、代码片段等的消息
|
||||
if (lowerMessage.contains("/") || lowerMessage.contains("\\") ||
|
||||
lowerMessage.contains("http") || lowerMessage.contains("www") ||
|
||||
lowerMessage.contains("{") || lowerMessage.contains("}") ||
|
||||
lowerMessage.contains("<") || lowerMessage.contains(">")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 默认情况下,对于不确定的消息,倾向于不使用工具
|
||||
// 这样可以避免不必要的工具准备状态显示
|
||||
return false;
|
||||
}
|
||||
|
||||
// 修改executeContinuousConversation方法
|
||||
public ConversationResult executeContinuousConversation(String taskId, String initialMessage, List<Message> conversationHistory) {
|
||||
TaskStatus taskStatus = taskStatusMap.get(taskId);
|
||||
if (taskStatus == null) {
|
||||
throw new IllegalArgumentException("Task not found: " + taskId);
|
||||
}
|
||||
|
||||
// 设置任务上下文,供AOP切面使用
|
||||
TaskContextHolder.setCurrentTaskId(taskId);
|
||||
|
||||
long conversationStartTime = System.currentTimeMillis();
|
||||
logger.info("Starting continuous conversation with message: {}", initialMessage);
|
||||
|
||||
// 更新任务状态
|
||||
taskStatus.setCurrentAction("开始处理对话...");
|
||||
taskStatus.setCurrentTurn(0);
|
||||
|
||||
// 创建工作副本
|
||||
List<Message> workingHistory = new ArrayList<>(conversationHistory);
|
||||
StringBuilder fullResponse = new StringBuilder();
|
||||
List<String> turnResponses = new ArrayList<>();
|
||||
|
||||
// 添加初始用户消息
|
||||
UserMessage userMessage = new UserMessage(initialMessage);
|
||||
workingHistory.add(userMessage);
|
||||
|
||||
int turnCount = 0;
|
||||
boolean shouldContinue = true;
|
||||
String stopReason = null;
|
||||
|
||||
try {
|
||||
while (shouldContinue && turnCount < MAX_TURNS) {
|
||||
turnCount++;
|
||||
logger.debug("Executing conversation turn: {}", turnCount);
|
||||
|
||||
// 更新任务状态
|
||||
taskStatus.setCurrentTurn(turnCount);
|
||||
taskStatus.setCurrentAction(String.format("执行第 %d 轮对话...", turnCount));
|
||||
|
||||
// 检查总超时
|
||||
long elapsedTime = System.currentTimeMillis() - conversationStartTime;
|
||||
if (elapsedTime > TOTAL_TIMEOUT_MS) {
|
||||
logger.warn("Conversation timed out after {}ms", elapsedTime);
|
||||
stopReason = "Total conversation timeout exceeded";
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行单轮对话
|
||||
TurnResult turnResult = executeSingleTurn(workingHistory, turnCount);
|
||||
|
||||
if (!turnResult.isSuccess()) {
|
||||
logger.error("Turn {} failed: {}", turnCount, turnResult.getErrorMessage());
|
||||
stopReason = "Turn execution failed: " + turnResult.getErrorMessage();
|
||||
break;
|
||||
}
|
||||
|
||||
// 添加响应到历史
|
||||
String responseText = turnResult.getResponse();
|
||||
if (responseText != null && !responseText.trim().isEmpty()) {
|
||||
AssistantMessage assistantMessage = new AssistantMessage(responseText);
|
||||
workingHistory.add(assistantMessage);
|
||||
|
||||
// 累积响应
|
||||
if (fullResponse.length() > 0) {
|
||||
fullResponse.append("\n\n");
|
||||
}
|
||||
fullResponse.append(responseText);
|
||||
turnResponses.add(responseText);
|
||||
|
||||
// 更新任务状态 - 显示当前响应的简短摘要
|
||||
String responseSummary = responseText.length() > 100 ?
|
||||
responseText.substring(0, 100) + "..." : responseText;
|
||||
taskStatus.setCurrentAction(String.format("第 %d 轮完成: %s", turnCount, responseSummary));
|
||||
}
|
||||
|
||||
// 判断是否应该继续
|
||||
taskStatus.setCurrentAction(String.format("分析第 %d 轮结果,判断是否继续...", turnCount));
|
||||
shouldContinue = shouldContinueConversation(workingHistory, turnCount, responseText);
|
||||
|
||||
if (shouldContinue && turnCount < MAX_TURNS) {
|
||||
// 添加继续提示
|
||||
String continuePrompt = getContinuePrompt(turnCount);
|
||||
UserMessage continueMessage = new UserMessage(continuePrompt);
|
||||
workingHistory.add(continueMessage);
|
||||
logger.debug("Added continue prompt for turn {}: {}", turnCount + 1, continuePrompt);
|
||||
taskStatus.setCurrentAction(String.format("准备第 %d 轮对话...", turnCount + 1));
|
||||
} else {
|
||||
taskStatus.setCurrentAction("对话即将结束...");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in conversation turn {}: {}", turnCount, e.getMessage(), e);
|
||||
stopReason = "Exception in turn " + turnCount + ": " + e.getMessage();
|
||||
|
||||
// 添加错误信息到响应中
|
||||
String errorMessage = String.format("❌ Error in turn %d: %s", turnCount, e.getMessage());
|
||||
if (fullResponse.length() > 0) {
|
||||
fullResponse.append("\n\n");
|
||||
}
|
||||
fullResponse.append(errorMessage);
|
||||
turnResponses.add(errorMessage);
|
||||
|
||||
// 更新任务状态为错误
|
||||
taskStatus.setStatus("FAILED");
|
||||
taskStatus.setErrorMessage(e.getMessage());
|
||||
taskStatus.setCurrentAction("执行出错: " + e.getMessage());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
long totalDuration = System.currentTimeMillis() - conversationStartTime;
|
||||
logger.info("Continuous conversation completed after {} turns in {}ms. Stop reason: {}",
|
||||
turnCount, totalDuration, stopReason);
|
||||
|
||||
// 创建结果对象
|
||||
ConversationResult result = new ConversationResult(
|
||||
fullResponse.toString(),
|
||||
turnResponses,
|
||||
workingHistory,
|
||||
turnCount,
|
||||
turnCount >= MAX_TURNS,
|
||||
stopReason,
|
||||
totalDuration
|
||||
);
|
||||
|
||||
// 更新任务状态为完成
|
||||
taskStatus.setStatus("COMPLETED");
|
||||
taskStatus.setCurrentAction("对话完成");
|
||||
String summary = String.format("对话完成,共 %d 轮,耗时 %.1f 秒",
|
||||
turnCount, totalDuration / 1000.0);
|
||||
if (stopReason != null) {
|
||||
summary += ",停止原因: " + stopReason;
|
||||
}
|
||||
taskStatus.setSummary(summary);
|
||||
|
||||
// 存储结果到任务状态中
|
||||
storeConversationResult(taskId, result);
|
||||
|
||||
// 推送任务完成事件
|
||||
logStreamService.pushTaskComplete(taskId);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 处理整个对话过程中的异常
|
||||
logger.error("Fatal error in continuous conversation: {}", e.getMessage(), e);
|
||||
taskStatus.setStatus("FAILED");
|
||||
taskStatus.setErrorMessage("Fatal error: " + e.getMessage());
|
||||
taskStatus.setCurrentAction("执行失败");
|
||||
throw e;
|
||||
} finally {
|
||||
// 清理任务上下文
|
||||
TaskContextHolder.clearCurrentTaskId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单轮对话
|
||||
*/
|
||||
private TurnResult executeSingleTurn(List<Message> conversationHistory, int turnNumber) {
|
||||
long turnStartTime = System.currentTimeMillis();
|
||||
try {
|
||||
logger.debug("Executing turn {} with {} messages in history", turnNumber, conversationHistory.size());
|
||||
|
||||
// 调用AI(这里可以添加超时控制,但Spring AI目前不直接支持)
|
||||
ChatResponse response = chatClient.prompt()
|
||||
.messages(conversationHistory)
|
||||
.call()
|
||||
.chatResponse();
|
||||
|
||||
// 处理响应
|
||||
Generation generation = response.getResult();
|
||||
AssistantMessage assistantMessage = generation.getOutput();
|
||||
String responseText = assistantMessage.getText();
|
||||
|
||||
long turnDuration = System.currentTimeMillis() - turnStartTime;
|
||||
logger.debug("Turn {} completed in {}ms, response length: {} characters",
|
||||
turnNumber, turnDuration, responseText != null ? responseText.length() : 0);
|
||||
|
||||
return new TurnResult(true, responseText, null);
|
||||
|
||||
} catch (Exception e) {
|
||||
long turnDuration = System.currentTimeMillis() - turnStartTime;
|
||||
logger.error("Failed to execute turn {} after {}ms: {}", turnNumber, turnDuration, e.getMessage(), e);
|
||||
return new TurnResult(false, null, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该继续对话 - 优化版本
|
||||
*/
|
||||
private boolean shouldContinueConversation(List<Message> conversationHistory, int turnCount, String lastResponse) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 达到最大轮数
|
||||
if (turnCount >= MAX_TURNS) {
|
||||
logger.debug("Reached maximum turns ({}), stopping conversation", MAX_TURNS);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 响应为空
|
||||
if (lastResponse == null || lastResponse.trim().isEmpty()) {
|
||||
logger.debug("Empty response, stopping conversation");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 优化:首先使用增强的内容分析判断
|
||||
boolean contentSuggestsContinue = nextSpeakerService.shouldContinueBasedOnContent(lastResponse);
|
||||
logger.debug("Content analysis result: {}", contentSuggestsContinue);
|
||||
|
||||
// 如果内容分析明确建议停止,直接停止(避免LLM调用)
|
||||
if (!contentSuggestsContinue) {
|
||||
logger.debug("Content analysis suggests stopping, skipping LLM check");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 优化:对于文件编辑等明确的工具调用场景,可以基于简单规则继续
|
||||
if (isObviousToolCallScenario(lastResponse, turnCount)) {
|
||||
logger.debug("Obvious tool call scenario detected, continuing without LLM check");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 只有在不确定的情况下才使用智能判断服务(包含LLM调用)
|
||||
try {
|
||||
NextSpeakerService.NextSpeakerResponse nextSpeaker =
|
||||
nextSpeakerService.checkNextSpeaker(conversationHistory);
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.debug("Next speaker check completed in {}ms, result: {}", duration, nextSpeaker);
|
||||
|
||||
return nextSpeaker.isModelNext();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to check next speaker, defaulting to stop: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否是明显的工具调用场景
|
||||
*/
|
||||
private boolean isObviousToolCallScenario(String lastResponse, int turnCount) {
|
||||
if (lastResponse == null) return false;
|
||||
|
||||
String lowerResponse = lastResponse.toLowerCase();
|
||||
|
||||
// 如果是前几轮且包含工具调用成功的标志,很可能需要继续
|
||||
if (turnCount <= 10) {
|
||||
String[] toolCallIndicators = {
|
||||
"successfully created",
|
||||
"successfully updated",
|
||||
"file created",
|
||||
"file updated",
|
||||
"✅",
|
||||
"created file",
|
||||
"updated file",
|
||||
"next, i'll",
|
||||
"now i'll",
|
||||
"let me create",
|
||||
"let me edit"
|
||||
};
|
||||
|
||||
for (String indicator : toolCallIndicators) {
|
||||
if (lowerResponse.contains(indicator)) {
|
||||
// 但如果同时包含明确的完成信号,则不继续
|
||||
String[] completionSignals = {
|
||||
"all files created", "project complete", "setup complete",
|
||||
"everything is ready", "task completed", "all done"
|
||||
};
|
||||
|
||||
boolean hasCompletionSignal = false;
|
||||
for (String signal : completionSignals) {
|
||||
if (lowerResponse.contains(signal)) {
|
||||
hasCompletionSignal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasCompletionSignal) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取继续对话的提示语
|
||||
*/
|
||||
private String getContinuePrompt(int turnNumber) {
|
||||
int index = (turnNumber - 1) % CONTINUE_PROMPTS.length;
|
||||
return CONTINUE_PROMPTS[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* 单轮对话结果
|
||||
*/
|
||||
public static class TurnResult {
|
||||
private final boolean success;
|
||||
private final String response;
|
||||
private final String errorMessage;
|
||||
|
||||
public TurnResult(boolean success, String response, String errorMessage) {
|
||||
this.success = success;
|
||||
this.response = response;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public String getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连续对话结果
|
||||
*/
|
||||
public static class ConversationResult {
|
||||
private final String fullResponse;
|
||||
private final List<String> turnResponses;
|
||||
private final List<Message> finalHistory;
|
||||
private final int totalTurns;
|
||||
private final boolean reachedMaxTurns;
|
||||
private final String stopReason;
|
||||
private final long totalDurationMs;
|
||||
|
||||
public ConversationResult(String fullResponse, List<String> turnResponses,
|
||||
List<Message> finalHistory, int totalTurns, boolean reachedMaxTurns,
|
||||
String stopReason, long totalDurationMs) {
|
||||
this.fullResponse = fullResponse;
|
||||
this.turnResponses = turnResponses;
|
||||
this.finalHistory = finalHistory;
|
||||
this.totalTurns = totalTurns;
|
||||
this.reachedMaxTurns = reachedMaxTurns;
|
||||
this.stopReason = stopReason;
|
||||
this.totalDurationMs = totalDurationMs;
|
||||
}
|
||||
|
||||
public String getFullResponse() {
|
||||
return fullResponse;
|
||||
}
|
||||
|
||||
public List<String> getTurnResponses() {
|
||||
return turnResponses;
|
||||
}
|
||||
|
||||
public List<Message> getFinalHistory() {
|
||||
return finalHistory;
|
||||
}
|
||||
|
||||
public int getTotalTurns() {
|
||||
return totalTurns;
|
||||
}
|
||||
|
||||
public boolean isReachedMaxTurns() {
|
||||
return reachedMaxTurns;
|
||||
}
|
||||
|
||||
public String getStopReason() {
|
||||
return stopReason;
|
||||
}
|
||||
|
||||
public long getTotalDurationMs() {
|
||||
return totalDurationMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
/**
|
||||
* 日志事件基类
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class LogEvent {
|
||||
|
||||
private String type;
|
||||
private String taskId;
|
||||
private String message;
|
||||
private String timestamp;
|
||||
|
||||
// Constructors
|
||||
public LogEvent() {
|
||||
}
|
||||
|
||||
public LogEvent(String type, String taskId, String message, String timestamp) {
|
||||
this.type = type;
|
||||
this.taskId = taskId;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
// Static factory methods
|
||||
public static LogEvent createConnectionEvent(String taskId) {
|
||||
LogEvent event = new LogEvent();
|
||||
event.setType("CONNECTION_ESTABLISHED");
|
||||
event.setTaskId(taskId);
|
||||
event.setMessage("SSE连接已建立");
|
||||
event.setTimestamp(java.time.LocalDateTime.now().format(
|
||||
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
return event;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getTaskId() {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(String taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LogEvent{" +
|
||||
"type='" + type + '\'' +
|
||||
", taskId='" + taskId + '\'' +
|
||||
", message='" + message + '\'' +
|
||||
", timestamp='" + timestamp + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* SSE日志推送服务
|
||||
* 负责将AOP日志实时推送到前端
|
||||
*/
|
||||
@Service
|
||||
public class LogStreamService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogStreamService.class);
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 活跃的SSE连接 taskId -> SseEmitter
|
||||
private final Map<String, SseEmitter> activeConnections = new ConcurrentHashMap<>();
|
||||
|
||||
// JSON序列化器
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 建立SSE连接
|
||||
*/
|
||||
public SseEmitter createConnection(String taskId) {
|
||||
logger.info("🔗 建立SSE连接: taskId={}", taskId);
|
||||
|
||||
SseEmitter emitter = new SseEmitter(0L); // 无超时
|
||||
|
||||
// 设置连接事件处理
|
||||
emitter.onCompletion(() -> {
|
||||
logger.info("✅ SSE连接完成: taskId={}", taskId);
|
||||
activeConnections.remove(taskId);
|
||||
});
|
||||
|
||||
emitter.onTimeout(() -> {
|
||||
logger.warn("⏰ SSE连接超时: taskId={}", taskId);
|
||||
activeConnections.remove(taskId);
|
||||
});
|
||||
|
||||
emitter.onError((ex) -> {
|
||||
logger.error("❌ SSE连接错误: taskId={}, error={}", taskId, ex.getMessage());
|
||||
activeConnections.remove(taskId);
|
||||
});
|
||||
|
||||
// 保存连接
|
||||
activeConnections.put(taskId, emitter);
|
||||
|
||||
// 发送连接成功消息
|
||||
sendLogEvent(taskId, LogEvent.createConnectionEvent(taskId));
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭SSE连接
|
||||
*/
|
||||
public void closeConnection(String taskId) {
|
||||
SseEmitter emitter = activeConnections.remove(taskId);
|
||||
if (emitter != null) {
|
||||
try {
|
||||
emitter.complete();
|
||||
logger.info("🔚 关闭SSE连接: taskId={}", taskId);
|
||||
} catch (Exception e) {
|
||||
logger.error("关闭SSE连接失败: taskId={}, error={}", taskId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送工具开始执行事件
|
||||
*/
|
||||
public void pushToolStart(String taskId, String toolName, String filePath, String message) {
|
||||
ToolLogEvent event = new ToolLogEvent();
|
||||
event.setType("TOOL_START");
|
||||
event.setTaskId(taskId);
|
||||
event.setToolName(toolName);
|
||||
event.setFilePath(filePath);
|
||||
event.setMessage(message);
|
||||
event.setTimestamp(LocalDateTime.now().format(formatter));
|
||||
event.setIcon(getToolIcon(toolName));
|
||||
event.setStatus("RUNNING");
|
||||
|
||||
sendLogEvent(taskId, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送工具执行成功事件
|
||||
*/
|
||||
public void pushToolSuccess(String taskId, String toolName, String filePath, String message, long executionTime) {
|
||||
ToolLogEvent event = new ToolLogEvent();
|
||||
event.setType("TOOL_SUCCESS");
|
||||
event.setTaskId(taskId);
|
||||
event.setToolName(toolName);
|
||||
event.setFilePath(filePath);
|
||||
event.setMessage(message);
|
||||
event.setTimestamp(LocalDateTime.now().format(formatter));
|
||||
event.setIcon(getToolIcon(toolName));
|
||||
event.setStatus("SUCCESS");
|
||||
event.setExecutionTime(executionTime);
|
||||
|
||||
sendLogEvent(taskId, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送工具执行失败事件
|
||||
*/
|
||||
public void pushToolError(String taskId, String toolName, String filePath, String message, long executionTime) {
|
||||
ToolLogEvent event = new ToolLogEvent();
|
||||
event.setType("TOOL_ERROR");
|
||||
event.setTaskId(taskId);
|
||||
event.setToolName(toolName);
|
||||
event.setFilePath(filePath);
|
||||
event.setMessage(message);
|
||||
event.setTimestamp(LocalDateTime.now().format(formatter));
|
||||
event.setIcon("❌");
|
||||
event.setStatus("ERROR");
|
||||
event.setExecutionTime(executionTime);
|
||||
|
||||
sendLogEvent(taskId, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送任务完成事件
|
||||
*/
|
||||
public void pushTaskComplete(String taskId) {
|
||||
LogEvent event = new LogEvent();
|
||||
event.setType("TASK_COMPLETE");
|
||||
event.setTaskId(taskId);
|
||||
event.setMessage("任务执行完成");
|
||||
event.setTimestamp(LocalDateTime.now().format(formatter));
|
||||
|
||||
sendLogEvent(taskId, event);
|
||||
|
||||
// 延迟关闭连接
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(2000); // 等待2秒让前端处理完成事件
|
||||
closeConnection(taskId);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送日志事件到前端
|
||||
*/
|
||||
private void sendLogEvent(String taskId, Object event) {
|
||||
SseEmitter emitter = activeConnections.get(taskId);
|
||||
if (emitter != null) {
|
||||
try {
|
||||
String jsonData = objectMapper.writeValueAsString(event);
|
||||
logger.info("📤 准备推送日志事件: taskId={}, type={}, data={}", taskId,
|
||||
event instanceof LogEvent ? ((LogEvent) event).getType() : "unknown", jsonData);
|
||||
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("log")
|
||||
.data(jsonData));
|
||||
|
||||
logger.info("✅ 日志事件推送成功: taskId={}", taskId);
|
||||
} catch (IOException e) {
|
||||
logger.error("推送日志事件失败: taskId={}, error={}", taskId, e.getMessage());
|
||||
activeConnections.remove(taskId);
|
||||
}
|
||||
} else {
|
||||
logger.warn("⚠️ 未找到SSE连接: taskId={}, 无法推送事件", taskId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具图标
|
||||
*/
|
||||
private String getToolIcon(String toolName) {
|
||||
switch (toolName) {
|
||||
case "readFile":
|
||||
return "📖";
|
||||
case "writeFile":
|
||||
return "✏️";
|
||||
case "editFile":
|
||||
return "📝";
|
||||
case "listDirectory":
|
||||
return "📁";
|
||||
case "analyzeProject":
|
||||
return "🔍";
|
||||
case "scaffoldProject":
|
||||
return "🏗️";
|
||||
case "smartEdit":
|
||||
return "🧠";
|
||||
default:
|
||||
return "⚙️";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活跃连接数
|
||||
*/
|
||||
public int getActiveConnectionCount() {
|
||||
return activeConnections.size();
|
||||
}
|
||||
}
|
||||
@@ -1,509 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.converter.BeanOutputConverter;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 智能判断下一步发言者的服务
|
||||
*/
|
||||
@Service
|
||||
public class NextSpeakerService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(NextSpeakerService.class);
|
||||
private static final String CHECK_PROMPT = """
|
||||
Analyze *only* the content and structure of your immediately preceding response (your last turn in the conversation history).
|
||||
Based *strictly* on that response, determine who should logically speak next: the 'user' or the 'model' (you).
|
||||
|
||||
**Decision Rules (apply in order):**
|
||||
1. **Model Continues:** If your last response explicitly states an immediate next action *you* intend to take
|
||||
(e.g., "Next, I will...", "Now I'll process...", "Moving on to analyze...", "Let me create...", "I'll now...",
|
||||
indicates an intended tool call that didn't execute), OR if the response seems clearly incomplete
|
||||
(cut off mid-thought without a natural conclusion), then the **'model'** should speak next.
|
||||
2. **Question to User:** If your last response ends with a direct question specifically addressed *to the user*,
|
||||
then the **'user'** should speak next.
|
||||
3. **Waiting for User:** If your last response completed a thought, statement, or task *and* does not meet
|
||||
the criteria for Rule 1 (Model Continues) or Rule 2 (Question to User), it implies a pause expecting
|
||||
user input or reaction. In this case, the **'user'** should speak next.
|
||||
|
||||
**Output Format:**
|
||||
Respond *only* in JSON format. Do not include any text outside the JSON structure.
|
||||
""";
|
||||
// 简化版本的检查提示,用于减少LLM调用开销
|
||||
private static final String SIMPLIFIED_CHECK_PROMPT = """
|
||||
Based on your last response, who should speak next: 'user' or 'model'?
|
||||
Rules: If you stated a next action or response is incomplete -> 'model'.
|
||||
If you asked user a question -> 'user'.
|
||||
If task completed -> 'user'.
|
||||
Respond in JSON: {"next_speaker": "user/model", "reasoning": "brief reason"}
|
||||
""";
|
||||
private final ChatModel chatModel;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public NextSpeakerService(ChatModel chatModel) {
|
||||
this.chatModel = chatModel;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断下一步应该由谁发言 - 优化版本,添加快速路径
|
||||
*/
|
||||
public NextSpeakerResponse checkNextSpeaker(List<Message> conversationHistory) {
|
||||
try {
|
||||
// 确保有对话历史
|
||||
if (conversationHistory.isEmpty()) {
|
||||
return new NextSpeakerResponse("user", "No conversation history available");
|
||||
}
|
||||
|
||||
// 获取最后一条消息
|
||||
Message lastMessage = conversationHistory.get(conversationHistory.size() - 1);
|
||||
|
||||
// 如果最后一条不是助手消息,用户应该发言
|
||||
if (!(lastMessage instanceof AssistantMessage)) {
|
||||
return new NextSpeakerResponse("user", "Last message was not from assistant");
|
||||
}
|
||||
|
||||
// 检查是否是空响应
|
||||
String lastContent = lastMessage.getText();
|
||||
if (lastContent == null || lastContent.trim().isEmpty()) {
|
||||
return new NextSpeakerResponse("model", "Last message was empty, model should continue");
|
||||
}
|
||||
|
||||
// 快速路径1: 使用增强的内容分析进行快速判断
|
||||
NextSpeakerResponse fastPathResult = performFastPathCheck(lastContent);
|
||||
if (fastPathResult != null) {
|
||||
logger.debug("Fast path decision: {}", fastPathResult);
|
||||
return fastPathResult;
|
||||
}
|
||||
|
||||
// 快速路径2: 检查对话历史模式
|
||||
NextSpeakerResponse patternResult = checkConversationPattern(conversationHistory);
|
||||
if (patternResult != null) {
|
||||
logger.debug("Pattern-based decision: {}", patternResult);
|
||||
return patternResult;
|
||||
}
|
||||
|
||||
// 只有在快速路径无法确定时才使用LLM判断
|
||||
logger.debug("Fast paths inconclusive, falling back to LLM check");
|
||||
return performLLMCheck(conversationHistory);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to check next speaker, defaulting to user", e);
|
||||
return new NextSpeakerResponse("user", "Error occurred during check: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速路径检查 - 基于内容分析的快速判断
|
||||
*/
|
||||
private NextSpeakerResponse performFastPathCheck(String lastContent) {
|
||||
String lowerContent = lastContent.toLowerCase();
|
||||
|
||||
// 明确的停止信号 - 直接返回user
|
||||
String[] definiteStopSignals = {
|
||||
"task completed successfully",
|
||||
"all files created successfully",
|
||||
"project setup complete",
|
||||
"website is ready",
|
||||
"application is ready",
|
||||
"everything is ready",
|
||||
"setup is complete",
|
||||
"all tasks completed",
|
||||
"work is complete"
|
||||
};
|
||||
|
||||
for (String signal : definiteStopSignals) {
|
||||
if (lowerContent.contains(signal)) {
|
||||
return new NextSpeakerResponse("user", "Fast path: Definite completion signal detected");
|
||||
}
|
||||
}
|
||||
|
||||
// 明确的继续信号 - 直接返回model
|
||||
String[] definiteContinueSignals = {
|
||||
"next, i will",
|
||||
"now i will",
|
||||
"let me create",
|
||||
"let me edit",
|
||||
"let me update",
|
||||
"i'll create",
|
||||
"i'll edit",
|
||||
"i'll update",
|
||||
"moving on to",
|
||||
"proceeding to",
|
||||
"next step is to"
|
||||
};
|
||||
|
||||
for (String signal : definiteContinueSignals) {
|
||||
if (lowerContent.contains(signal)) {
|
||||
return new NextSpeakerResponse("model", "Fast path: Definite continue signal detected");
|
||||
}
|
||||
}
|
||||
|
||||
// 工具调用成功模式 - 通常需要继续
|
||||
if (isToolCallSuccessPattern(lastContent)) {
|
||||
// 但如果同时包含完成信号,则停止
|
||||
if (containsCompletionSignal(lowerContent)) {
|
||||
return new NextSpeakerResponse("user", "Fast path: Tool success with completion signal");
|
||||
}
|
||||
return new NextSpeakerResponse("model", "Fast path: Tool call success, should continue");
|
||||
}
|
||||
|
||||
// 直接问用户问题 - 应该等待用户回答
|
||||
if (lowerContent.trim().endsWith("?") && containsUserQuestion(lowerContent)) {
|
||||
return new NextSpeakerResponse("user", "Fast path: Direct question to user");
|
||||
}
|
||||
|
||||
return null; // 无法快速判断
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对话历史模式
|
||||
*/
|
||||
private NextSpeakerResponse checkConversationPattern(List<Message> conversationHistory) {
|
||||
if (conversationHistory.size() < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查最近几轮的模式
|
||||
int recentTurns = Math.min(4, conversationHistory.size());
|
||||
int modelTurns = 0;
|
||||
int userTurns = 0;
|
||||
|
||||
for (int i = conversationHistory.size() - recentTurns; i < conversationHistory.size(); i++) {
|
||||
Message msg = conversationHistory.get(i);
|
||||
if (msg instanceof AssistantMessage) {
|
||||
modelTurns++;
|
||||
} else if (msg instanceof UserMessage) {
|
||||
userTurns++;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果模型连续说话太多轮,可能需要用户介入
|
||||
if (modelTurns >= 3 && userTurns == 0) {
|
||||
return new NextSpeakerResponse("user", "Pattern: Too many consecutive model turns");
|
||||
}
|
||||
|
||||
return null; // 模式不明确
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含完成信号
|
||||
*/
|
||||
private boolean containsCompletionSignal(String lowerContent) {
|
||||
String[] completionSignals = {
|
||||
"all done", "complete", "finished", "ready", "that's it",
|
||||
"we're done", "task complete", "project complete"
|
||||
};
|
||||
|
||||
for (String signal : completionSignals) {
|
||||
if (lowerContent.contains(signal)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含对用户的直接问题
|
||||
*/
|
||||
private boolean containsUserQuestion(String lowerContent) {
|
||||
String[] userQuestionPatterns = {
|
||||
"what would you like",
|
||||
"what do you want",
|
||||
"would you like me to",
|
||||
"do you want me to",
|
||||
"should i",
|
||||
"would you prefer",
|
||||
"any preferences",
|
||||
"what's next",
|
||||
"what should i do next"
|
||||
};
|
||||
|
||||
for (String pattern : userQuestionPatterns) {
|
||||
if (lowerContent.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private NextSpeakerResponse performLLMCheck(List<Message> conversationHistory) {
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 优化:只使用最近的对话历史,减少上下文长度
|
||||
List<Message> recentHistory = getRecentHistory(conversationHistory, 6); // 最多6条消息
|
||||
|
||||
// 创建用于判断的对话历史 - 简化版本
|
||||
List<Message> checkMessages = recentHistory.stream()
|
||||
.map(msg -> {
|
||||
if (msg instanceof UserMessage) {
|
||||
// 截断过长的用户消息
|
||||
String text = msg.getText();
|
||||
if (text.length() > 500) {
|
||||
text = text.substring(0, 500) + "...";
|
||||
}
|
||||
return new UserMessage(text);
|
||||
} else if (msg instanceof AssistantMessage) {
|
||||
// 截断过长的助手消息
|
||||
String text = msg.getText();
|
||||
if (text.length() > 500) {
|
||||
text = text.substring(0, 500) + "...";
|
||||
}
|
||||
return new AssistantMessage(text);
|
||||
}
|
||||
return msg;
|
||||
})
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// 添加简化的检查提示
|
||||
checkMessages.add(new UserMessage(SIMPLIFIED_CHECK_PROMPT));
|
||||
|
||||
// 使用输出转换器
|
||||
BeanOutputConverter<NextSpeakerResponse> outputConverter =
|
||||
new BeanOutputConverter<>(NextSpeakerResponse.class);
|
||||
|
||||
// 调用LLM - 这里可以考虑添加超时,但Spring AI目前不直接支持
|
||||
ChatResponse response = ChatClient.create(chatModel)
|
||||
.prompt()
|
||||
.messages(checkMessages)
|
||||
.call()
|
||||
.chatResponse();
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
logger.debug("LLM check completed in {}ms", duration);
|
||||
|
||||
String responseText = response.getResult().getOutput().getText();
|
||||
logger.debug("Next speaker check response: {}", responseText);
|
||||
|
||||
// 解析响应
|
||||
try {
|
||||
return outputConverter.convert(responseText);
|
||||
} catch (Exception parseError) {
|
||||
logger.warn("Failed to parse next speaker response, trying manual parsing", parseError);
|
||||
return parseManually(responseText);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("LLM check failed, defaulting to user: {}", e.getMessage());
|
||||
return new NextSpeakerResponse("user", "LLM check failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的对话历史
|
||||
*/
|
||||
private List<Message> getRecentHistory(List<Message> fullHistory, int maxMessages) {
|
||||
if (fullHistory.size() <= maxMessages) {
|
||||
return fullHistory;
|
||||
}
|
||||
|
||||
return fullHistory.subList(fullHistory.size() - maxMessages, fullHistory.size());
|
||||
}
|
||||
|
||||
private NextSpeakerResponse parseManually(String responseText) {
|
||||
try {
|
||||
// 简单的手动解析
|
||||
if (responseText.toLowerCase().contains("\"next_speaker\"") &&
|
||||
responseText.toLowerCase().contains("\"model\"")) {
|
||||
return new NextSpeakerResponse("model", "Parsed manually - model should continue");
|
||||
}
|
||||
return new NextSpeakerResponse("user", "Parsed manually - user should speak");
|
||||
} catch (Exception e) {
|
||||
return new NextSpeakerResponse("user", "Manual parsing failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查响应内容是否表明需要继续 - 优化版本
|
||||
*/
|
||||
public boolean shouldContinueBasedOnContent(String response) {
|
||||
if (response == null || response.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lowerResponse = response.toLowerCase();
|
||||
|
||||
// 优先检查明确的停止指示词 - 扩展版本
|
||||
String[] stopIndicators = {
|
||||
"completed", "finished", "done", "ready", "all set", "task complete",
|
||||
"project complete", "successfully created all", "that's it", "we're done",
|
||||
"everything is ready", "all files created", "project is ready",
|
||||
"task completed successfully", "all tasks completed", "work is complete",
|
||||
"implementation complete", "setup complete", "configuration complete",
|
||||
"files have been created", "project has been set up", "website is ready",
|
||||
"application is ready", "all necessary files", "setup is complete"
|
||||
};
|
||||
|
||||
// 检查停止指示词
|
||||
for (String indicator : stopIndicators) {
|
||||
if (lowerResponse.contains(indicator)) {
|
||||
logger.debug("Found stop indicator: '{}' in response", indicator);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查工具调用成功的模式 - 新增:这是文件编辑场景的关键优化
|
||||
if (isToolCallSuccessPattern(response)) {
|
||||
logger.debug("Detected successful tool call pattern, should continue");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 扩展的继续指示词
|
||||
String[] continueIndicators = {
|
||||
"next, i", "now i", "let me", "i'll", "i will", "moving on",
|
||||
"proceeding", "continuing", "then i", "after that", "following this",
|
||||
"now let's", "let's now", "i need to", "i should", "i'm going to",
|
||||
"next step", "continuing with", "moving to", "proceeding to",
|
||||
"now creating", "now editing", "now updating", "now modifying",
|
||||
"let me create", "let me edit", "let me update", "let me modify",
|
||||
"i'll create", "i'll edit", "i'll update", "i'll modify",
|
||||
"creating the", "editing the", "updating the", "modifying the"
|
||||
};
|
||||
|
||||
// 检查继续指示词
|
||||
for (String indicator : continueIndicators) {
|
||||
if (lowerResponse.contains(indicator)) {
|
||||
logger.debug("Found continue indicator: '{}' in response", indicator);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含文件操作相关内容 - 针对文件编辑场景优化
|
||||
if (containsFileOperationIntent(lowerResponse)) {
|
||||
logger.debug("Detected file operation intent, should continue");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果响应很短且没有明确结束,可能需要继续
|
||||
boolean shortResponseContinue = response.length() < 200 && !lowerResponse.contains("?");
|
||||
if (shortResponseContinue) {
|
||||
logger.debug("Short response without question mark, should continue");
|
||||
}
|
||||
|
||||
return shortResponseContinue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否是工具调用成功的模式
|
||||
*/
|
||||
private boolean isToolCallSuccessPattern(String response) {
|
||||
String lowerResponse = response.toLowerCase();
|
||||
|
||||
// 工具调用成功的典型模式
|
||||
String[] toolSuccessPatterns = {
|
||||
"successfully created",
|
||||
"successfully updated",
|
||||
"successfully modified",
|
||||
"successfully edited",
|
||||
"file created",
|
||||
"file updated",
|
||||
"file modified",
|
||||
"file edited",
|
||||
"created file",
|
||||
"updated file",
|
||||
"modified file",
|
||||
"edited file",
|
||||
"✅", // 成功标记
|
||||
"file has been created",
|
||||
"file has been updated",
|
||||
"file has been modified",
|
||||
"content has been",
|
||||
"successfully wrote",
|
||||
"successfully saved"
|
||||
};
|
||||
|
||||
for (String pattern : toolSuccessPatterns) {
|
||||
if (lowerResponse.contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含文件操作意图
|
||||
*/
|
||||
private boolean containsFileOperationIntent(String lowerResponse) {
|
||||
String[] fileOperationIntents = {
|
||||
"create a", "create the", "creating a", "creating the",
|
||||
"edit a", "edit the", "editing a", "editing the",
|
||||
"update a", "update the", "updating a", "updating the",
|
||||
"modify a", "modify the", "modifying a", "modifying the",
|
||||
"write a", "write the", "writing a", "writing the",
|
||||
"generate a", "generate the", "generating a", "generating the",
|
||||
"add to", "adding to", "append to", "appending to",
|
||||
"need to create", "need to edit", "need to update", "need to modify",
|
||||
"will create", "will edit", "will update", "will modify",
|
||||
"going to create", "going to edit", "going to update", "going to modify"
|
||||
};
|
||||
|
||||
for (String intent : fileOperationIntents) {
|
||||
if (lowerResponse.contains(intent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一步发言者响应
|
||||
*/
|
||||
public static class NextSpeakerResponse {
|
||||
@JsonProperty("next_speaker")
|
||||
private String nextSpeaker;
|
||||
|
||||
@JsonProperty("reasoning")
|
||||
private String reasoning;
|
||||
|
||||
public NextSpeakerResponse() {
|
||||
}
|
||||
|
||||
public NextSpeakerResponse(String nextSpeaker, String reasoning) {
|
||||
this.nextSpeaker = nextSpeaker;
|
||||
this.reasoning = reasoning;
|
||||
}
|
||||
|
||||
public String getNextSpeaker() {
|
||||
return nextSpeaker;
|
||||
}
|
||||
|
||||
public void setNextSpeaker(String nextSpeaker) {
|
||||
this.nextSpeaker = nextSpeaker;
|
||||
}
|
||||
|
||||
public String getReasoning() {
|
||||
return reasoning;
|
||||
}
|
||||
|
||||
public void setReasoning(String reasoning) {
|
||||
this.reasoning = reasoning;
|
||||
}
|
||||
|
||||
public boolean isModelNext() {
|
||||
return "model".equals(nextSpeaker);
|
||||
}
|
||||
|
||||
public boolean isUserNext() {
|
||||
return "user".equals(nextSpeaker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("NextSpeaker{speaker='%s', reasoning='%s'}", nextSpeaker, reasoning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.ProjectContext;
|
||||
import com.example.demo.model.ProjectStructure;
|
||||
import com.example.demo.model.ProjectType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 项目上下文分析器
|
||||
* 提供完整的项目分析功能,生成AI可理解的项目上下文
|
||||
*/
|
||||
@Service
|
||||
public class ProjectContextAnalyzer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProjectContextAnalyzer.class);
|
||||
|
||||
@Autowired
|
||||
public ProjectTypeDetector projectTypeDetector;
|
||||
|
||||
@Autowired
|
||||
public ProjectDiscoveryService projectDiscoveryService;
|
||||
|
||||
/**
|
||||
* 分析项目并生成完整上下文
|
||||
*
|
||||
* @param projectRoot 项目根目录
|
||||
* @return 项目上下文信息
|
||||
*/
|
||||
public ProjectContext analyzeProject(Path projectRoot) {
|
||||
logger.info("Starting comprehensive project analysis for: {}", projectRoot);
|
||||
|
||||
ProjectContext context = new ProjectContext(projectRoot);
|
||||
|
||||
try {
|
||||
// 1. 检测项目类型
|
||||
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
|
||||
context.setProjectType(projectType);
|
||||
logger.debug("Detected project type: {}", projectType);
|
||||
|
||||
// 2. 分析项目结构
|
||||
ProjectStructure structure = projectDiscoveryService.analyzeProjectStructure(projectRoot);
|
||||
context.setProjectStructure(structure);
|
||||
logger.debug("Analyzed project structure with {} directories",
|
||||
structure.getDirectories().size());
|
||||
|
||||
// 3. 分析依赖关系
|
||||
List<ProjectContext.DependencyInfo> dependencies =
|
||||
projectDiscoveryService.analyzeDependencies(projectRoot);
|
||||
context.setDependencies(dependencies);
|
||||
logger.debug("Found {} dependencies", dependencies.size());
|
||||
|
||||
// 4. 查找配置文件
|
||||
List<ProjectContext.ConfigFile> configFiles =
|
||||
projectDiscoveryService.findConfigurationFiles(projectRoot);
|
||||
context.setConfigFiles(configFiles);
|
||||
logger.debug("Found {} configuration files", configFiles.size());
|
||||
|
||||
// 5. 分析代码统计
|
||||
ProjectContext.CodeStatistics codeStats = analyzeCodeStatistics(projectRoot, projectType);
|
||||
context.setCodeStatistics(codeStats);
|
||||
logger.debug("Code statistics: {} total lines", codeStats.getTotalLines());
|
||||
|
||||
// 6. 收集项目元数据
|
||||
Map<String, Object> metadata = collectProjectMetadata(projectRoot, projectType);
|
||||
context.setMetadata(metadata);
|
||||
|
||||
// 7. 生成上下文摘要
|
||||
String summary = context.generateContextSummary();
|
||||
logger.debug("Generated context summary with {} characters", summary.length());
|
||||
|
||||
logger.info("Project analysis completed successfully for: {}", projectRoot);
|
||||
return context;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during project analysis for: " + projectRoot, e);
|
||||
// 返回部分分析结果
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析代码统计信息
|
||||
*/
|
||||
private ProjectContext.CodeStatistics analyzeCodeStatistics(Path projectRoot, ProjectType projectType) {
|
||||
logger.debug("Analyzing code statistics for: {}", projectRoot);
|
||||
|
||||
ProjectContext.CodeStatistics stats = new ProjectContext.CodeStatistics();
|
||||
|
||||
try {
|
||||
analyzeCodeInDirectory(projectRoot, stats, projectType, 0, 3);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error analyzing code statistics", e);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归分析目录中的代码
|
||||
*/
|
||||
private void analyzeCodeInDirectory(Path directory, ProjectContext.CodeStatistics stats,
|
||||
ProjectType projectType, int currentDepth, int maxDepth) {
|
||||
if (currentDepth > maxDepth) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<Path> paths = Files.list(directory)) {
|
||||
paths.forEach(path -> {
|
||||
try {
|
||||
if (Files.isDirectory(path)) {
|
||||
String dirName = path.getFileName().toString();
|
||||
// 跳过不需要分析的目录
|
||||
if (!shouldSkipDirectory(dirName)) {
|
||||
analyzeCodeInDirectory(path, stats, projectType, currentDepth + 1, maxDepth);
|
||||
}
|
||||
} else if (Files.isRegularFile(path)) {
|
||||
analyzeCodeFile(path, stats, projectType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error processing path during code analysis: " + path, e);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error listing directory: " + directory, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析单个代码文件
|
||||
*/
|
||||
private void analyzeCodeFile(Path filePath, ProjectContext.CodeStatistics stats, ProjectType projectType) {
|
||||
String fileName = filePath.getFileName().toString();
|
||||
String extension = getFileExtension(fileName).toLowerCase();
|
||||
|
||||
// 只分析代码文件
|
||||
if (!isCodeFile(extension, projectType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(filePath);
|
||||
int totalLines = lines.size();
|
||||
int codeLines = 0;
|
||||
int commentLines = 0;
|
||||
int blankLines = 0;
|
||||
|
||||
for (String line : lines) {
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.isEmpty()) {
|
||||
blankLines++;
|
||||
} else if (isCommentLine(trimmedLine, extension)) {
|
||||
commentLines++;
|
||||
} else {
|
||||
codeLines++;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计信息
|
||||
stats.setTotalLines(stats.getTotalLines() + totalLines);
|
||||
stats.setCodeLines(stats.getCodeLines() + codeLines);
|
||||
stats.setCommentLines(stats.getCommentLines() + commentLines);
|
||||
stats.setBlankLines(stats.getBlankLines() + blankLines);
|
||||
|
||||
// 按语言统计
|
||||
String language = getLanguageByExtension(extension);
|
||||
stats.addLanguageLines(language, totalLines);
|
||||
|
||||
// 分析类和方法(简单实现)
|
||||
if (extension.equals(".java")) {
|
||||
analyzeJavaFile(lines, stats);
|
||||
} else if (extension.equals(".js") || extension.equals(".ts")) {
|
||||
analyzeJavaScriptFile(lines, stats);
|
||||
} else if (extension.equals(".py")) {
|
||||
analyzePythonFile(lines, stats);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading file for code analysis: " + filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Java文件
|
||||
*/
|
||||
private void analyzeJavaFile(List<String> lines, ProjectContext.CodeStatistics stats) {
|
||||
for (String line : lines) {
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.matches(".*\\bclass\\s+\\w+.*")) {
|
||||
stats.setTotalClasses(stats.getTotalClasses() + 1);
|
||||
}
|
||||
if (trimmedLine.matches(".*\\b(public|private|protected)\\s+.*\\s+\\w+\\s*\\(.*\\).*")) {
|
||||
stats.setTotalMethods(stats.getTotalMethods() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析JavaScript文件
|
||||
*/
|
||||
private void analyzeJavaScriptFile(List<String> lines, ProjectContext.CodeStatistics stats) {
|
||||
for (String line : lines) {
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.matches(".*\\bfunction\\s+\\w+.*") ||
|
||||
trimmedLine.matches(".*\\w+\\s*:\\s*function.*") ||
|
||||
trimmedLine.matches(".*\\w+\\s*=\\s*\\(.*\\)\\s*=>.*")) {
|
||||
stats.setTotalFunctions(stats.getTotalFunctions() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Python文件
|
||||
*/
|
||||
private void analyzePythonFile(List<String> lines, ProjectContext.CodeStatistics stats) {
|
||||
for (String line : lines) {
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.matches("^class\\s+\\w+.*:")) {
|
||||
stats.setTotalClasses(stats.getTotalClasses() + 1);
|
||||
}
|
||||
if (trimmedLine.matches("^def\\s+\\w+.*:")) {
|
||||
stats.setTotalFunctions(stats.getTotalFunctions() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集项目元数据
|
||||
*/
|
||||
private Map<String, Object> collectProjectMetadata(Path projectRoot, ProjectType projectType) {
|
||||
Map<String, Object> metadata = new HashMap<>();
|
||||
|
||||
metadata.put("projectName", projectRoot.getFileName().toString());
|
||||
metadata.put("projectType", projectType.name());
|
||||
metadata.put("primaryLanguage", projectType.getPrimaryLanguage());
|
||||
metadata.put("packageManager", projectType.getPackageManager());
|
||||
metadata.put("analysisTimestamp", System.currentTimeMillis());
|
||||
|
||||
// 检查版本控制
|
||||
if (Files.exists(projectRoot.resolve(".git"))) {
|
||||
metadata.put("versionControl", "Git");
|
||||
}
|
||||
|
||||
// 检查CI/CD配置
|
||||
if (Files.exists(projectRoot.resolve(".github"))) {
|
||||
metadata.put("cicd", "GitHub Actions");
|
||||
} else if (Files.exists(projectRoot.resolve(".gitlab-ci.yml"))) {
|
||||
metadata.put("cicd", "GitLab CI");
|
||||
}
|
||||
|
||||
// 检查Docker支持
|
||||
if (Files.exists(projectRoot.resolve("Dockerfile"))) {
|
||||
metadata.put("containerization", "Docker");
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成编辑上下文
|
||||
*/
|
||||
public String buildEditContext(Path projectRoot, String editDescription) {
|
||||
logger.debug("Building edit context for: {}", projectRoot);
|
||||
|
||||
ProjectContext context = analyzeProject(projectRoot);
|
||||
|
||||
StringBuilder contextBuilder = new StringBuilder();
|
||||
contextBuilder.append("=== EDIT CONTEXT ===\n");
|
||||
contextBuilder.append("Edit Request: ").append(editDescription).append("\n\n");
|
||||
contextBuilder.append(context.generateContextSummary());
|
||||
|
||||
return contextBuilder.toString();
|
||||
}
|
||||
|
||||
// 辅助方法
|
||||
private boolean shouldSkipDirectory(String dirName) {
|
||||
return dirName.equals(".git") || dirName.equals("node_modules") ||
|
||||
dirName.equals("target") || dirName.equals("build") ||
|
||||
dirName.equals("dist") || dirName.equals("__pycache__") ||
|
||||
dirName.startsWith(".");
|
||||
}
|
||||
|
||||
private String getFileExtension(String fileName) {
|
||||
int lastDot = fileName.lastIndexOf('.');
|
||||
return lastDot > 0 ? fileName.substring(lastDot) : "";
|
||||
}
|
||||
|
||||
private boolean isCodeFile(String extension, ProjectType projectType) {
|
||||
return extension.equals(".java") || extension.equals(".js") || extension.equals(".ts") ||
|
||||
extension.equals(".py") || extension.equals(".html") || extension.equals(".css") ||
|
||||
extension.equals(".jsx") || extension.equals(".tsx") || extension.equals(".vue") ||
|
||||
extension.equals(".go") || extension.equals(".rs") || extension.equals(".php") ||
|
||||
extension.equals(".cs") || extension.equals(".cpp") || extension.equals(".c");
|
||||
}
|
||||
|
||||
private boolean isCommentLine(String line, String extension) {
|
||||
switch (extension) {
|
||||
case ".java":
|
||||
case ".js":
|
||||
case ".ts":
|
||||
case ".jsx":
|
||||
case ".tsx":
|
||||
case ".css":
|
||||
return line.startsWith("//") || line.startsWith("/*") || line.startsWith("*");
|
||||
case ".py":
|
||||
return line.startsWith("#");
|
||||
case ".html":
|
||||
return line.startsWith("<!--");
|
||||
default:
|
||||
return line.startsWith("#") || line.startsWith("//");
|
||||
}
|
||||
}
|
||||
|
||||
private String getLanguageByExtension(String extension) {
|
||||
switch (extension) {
|
||||
case ".java":
|
||||
return "Java";
|
||||
case ".js":
|
||||
case ".jsx":
|
||||
return "JavaScript";
|
||||
case ".ts":
|
||||
case ".tsx":
|
||||
return "TypeScript";
|
||||
case ".py":
|
||||
return "Python";
|
||||
case ".html":
|
||||
return "HTML";
|
||||
case ".css":
|
||||
return "CSS";
|
||||
case ".vue":
|
||||
return "Vue";
|
||||
case ".go":
|
||||
return "Go";
|
||||
case ".rs":
|
||||
return "Rust";
|
||||
case ".php":
|
||||
return "PHP";
|
||||
case ".cs":
|
||||
return "C#";
|
||||
default:
|
||||
return "Other";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.ProjectContext;
|
||||
import com.example.demo.model.ProjectStructure;
|
||||
import com.example.demo.model.ProjectType;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 项目发现和分析服务
|
||||
* 负责分析项目结构、依赖关系和配置信息
|
||||
*/
|
||||
@Service
|
||||
public class ProjectDiscoveryService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProjectDiscoveryService.class);
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
@Autowired
|
||||
private ProjectTypeDetector projectTypeDetector;
|
||||
|
||||
/**
|
||||
* 分析项目结构
|
||||
*
|
||||
* @param projectRoot 项目根目录
|
||||
* @return 项目结构信息
|
||||
*/
|
||||
public ProjectStructure analyzeProjectStructure(Path projectRoot) {
|
||||
logger.debug("Analyzing project structure for: {}", projectRoot);
|
||||
|
||||
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
|
||||
ProjectStructure structure = new ProjectStructure(projectRoot, projectType);
|
||||
|
||||
try {
|
||||
analyzeDirectoryStructure(projectRoot, structure, 0, 3); // 最大深度3层
|
||||
structure.markImportantDirectories();
|
||||
|
||||
logger.info("Project structure analysis completed for: {}", projectRoot);
|
||||
return structure;
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("Error analyzing project structure for: " + projectRoot, e);
|
||||
return structure; // 返回部分分析结果
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归分析目录结构
|
||||
*/
|
||||
private void analyzeDirectoryStructure(Path currentPath, ProjectStructure structure,
|
||||
int currentDepth, int maxDepth) throws IOException {
|
||||
if (currentDepth > maxDepth) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<Path> paths = Files.list(currentPath)) {
|
||||
paths.forEach(path -> {
|
||||
try {
|
||||
if (Files.isDirectory(path)) {
|
||||
String dirName = path.getFileName().toString();
|
||||
String relativePath = structure.getProjectRoot().relativize(path).toString();
|
||||
|
||||
// 跳过常见的忽略目录
|
||||
if (shouldIgnoreDirectory(dirName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProjectStructure.DirectoryInfo dirInfo =
|
||||
new ProjectStructure.DirectoryInfo(dirName, relativePath);
|
||||
|
||||
// 分析目录中的文件
|
||||
analyzeDirectoryFiles(path, dirInfo);
|
||||
structure.addDirectory(dirInfo);
|
||||
|
||||
// 递归分析子目录
|
||||
if (currentDepth < maxDepth) {
|
||||
analyzeDirectoryStructure(path, structure, currentDepth + 1, maxDepth);
|
||||
}
|
||||
|
||||
} else if (Files.isRegularFile(path)) {
|
||||
// 处理根目录下的文件
|
||||
String fileName = path.getFileName().toString();
|
||||
String extension = getFileExtension(fileName);
|
||||
|
||||
structure.addFileType(extension, 1);
|
||||
structure.setTotalFiles(structure.getTotalFiles() + 1);
|
||||
|
||||
// 检查是否为关键文件
|
||||
if (isKeyFile(fileName, structure.getProjectType())) {
|
||||
structure.addKeyFile(fileName);
|
||||
}
|
||||
|
||||
// 累计文件大小
|
||||
try {
|
||||
structure.setTotalSize(structure.getTotalSize() + Files.size(path));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not get size for file: {}", path);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error processing path: " + path, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析目录中的文件
|
||||
*/
|
||||
private void analyzeDirectoryFiles(Path directory, ProjectStructure.DirectoryInfo dirInfo) {
|
||||
try (Stream<Path> files = Files.list(directory)) {
|
||||
files.filter(Files::isRegularFile)
|
||||
.forEach(file -> {
|
||||
String fileName = file.getFileName().toString();
|
||||
dirInfo.addFile(fileName);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error analyzing files in directory: {}", directory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析项目依赖
|
||||
*/
|
||||
public List<ProjectContext.DependencyInfo> analyzeDependencies(Path projectRoot) {
|
||||
logger.debug("Analyzing dependencies for: {}", projectRoot);
|
||||
|
||||
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
|
||||
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
|
||||
|
||||
try {
|
||||
switch (projectType) {
|
||||
case JAVA_MAVEN:
|
||||
case SPRING_BOOT:
|
||||
dependencies.addAll(analyzeMavenDependencies(projectRoot));
|
||||
break;
|
||||
case NODE_JS:
|
||||
case REACT:
|
||||
case VUE:
|
||||
case ANGULAR:
|
||||
case NEXT_JS:
|
||||
dependencies.addAll(analyzeNpmDependencies(projectRoot));
|
||||
break;
|
||||
case PYTHON:
|
||||
case DJANGO:
|
||||
case FLASK:
|
||||
case FASTAPI:
|
||||
dependencies.addAll(analyzePythonDependencies(projectRoot));
|
||||
break;
|
||||
default:
|
||||
logger.info("Dependency analysis not supported for project type: {}", projectType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error analyzing dependencies for: " + projectRoot, e);
|
||||
}
|
||||
|
||||
logger.info("Found {} dependencies for project: {}", dependencies.size(), projectRoot);
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Maven依赖
|
||||
*/
|
||||
private List<ProjectContext.DependencyInfo> analyzeMavenDependencies(Path projectRoot) {
|
||||
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
|
||||
Path pomFile = projectRoot.resolve("pom.xml");
|
||||
|
||||
if (!Files.exists(pomFile)) {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
try {
|
||||
String pomContent = Files.readString(pomFile);
|
||||
// 简单的XML解析 - 在实际项目中应该使用专门的XML解析器
|
||||
if (pomContent.contains("spring-boot-starter-web")) {
|
||||
dependencies.add(new ProjectContext.DependencyInfo(
|
||||
"spring-boot-starter-web", "auto", "compile"));
|
||||
}
|
||||
if (pomContent.contains("spring-boot-starter-data-jpa")) {
|
||||
dependencies.add(new ProjectContext.DependencyInfo(
|
||||
"spring-boot-starter-data-jpa", "auto", "compile"));
|
||||
}
|
||||
if (pomContent.contains("spring-boot-starter-test")) {
|
||||
dependencies.add(new ProjectContext.DependencyInfo(
|
||||
"spring-boot-starter-test", "auto", "test"));
|
||||
}
|
||||
// 可以添加更多依赖检测逻辑
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading pom.xml", e);
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析NPM依赖
|
||||
*/
|
||||
private List<ProjectContext.DependencyInfo> analyzeNpmDependencies(Path projectRoot) {
|
||||
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
|
||||
Path packageJsonPath = projectRoot.resolve("package.json");
|
||||
|
||||
if (!Files.exists(packageJsonPath)) {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
try {
|
||||
String content = Files.readString(packageJsonPath);
|
||||
JsonNode packageJson = objectMapper.readTree(content);
|
||||
|
||||
// 分析生产依赖
|
||||
JsonNode deps = packageJson.get("dependencies");
|
||||
if (deps != null) {
|
||||
deps.fields().forEachRemaining(entry -> {
|
||||
dependencies.add(new ProjectContext.DependencyInfo(
|
||||
entry.getKey(), entry.getValue().asText(), "production"));
|
||||
});
|
||||
}
|
||||
|
||||
// 分析开发依赖
|
||||
JsonNode devDeps = packageJson.get("devDependencies");
|
||||
if (devDeps != null) {
|
||||
devDeps.fields().forEachRemaining(entry -> {
|
||||
ProjectContext.DependencyInfo depInfo = new ProjectContext.DependencyInfo(
|
||||
entry.getKey(), entry.getValue().asText(), "development");
|
||||
depInfo.setDirectDependency(true);
|
||||
dependencies.add(depInfo);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading package.json", e);
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Python依赖
|
||||
*/
|
||||
private List<ProjectContext.DependencyInfo> analyzePythonDependencies(Path projectRoot) {
|
||||
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
|
||||
Path requirementsFile = projectRoot.resolve("requirements.txt");
|
||||
|
||||
if (Files.exists(requirementsFile)) {
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(requirementsFile);
|
||||
for (String line : lines) {
|
||||
line = line.trim();
|
||||
if (!line.isEmpty() && !line.startsWith("#")) {
|
||||
String[] parts = line.split("==|>=|<=|>|<");
|
||||
String name = parts[0].trim();
|
||||
String version = parts.length > 1 ? parts[1].trim() : "latest";
|
||||
dependencies.add(new ProjectContext.DependencyInfo(name, version, "runtime"));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading requirements.txt", e);
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找配置文件
|
||||
*/
|
||||
public List<ProjectContext.ConfigFile> findConfigurationFiles(Path projectRoot) {
|
||||
logger.debug("Finding configuration files for: {}", projectRoot);
|
||||
|
||||
List<ProjectContext.ConfigFile> configFiles = new ArrayList<>();
|
||||
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
|
||||
|
||||
try {
|
||||
// 通用配置文件
|
||||
addConfigFileIfExists(configFiles, projectRoot, "application.properties", "properties");
|
||||
addConfigFileIfExists(configFiles, projectRoot, "application.yml", "yaml");
|
||||
addConfigFileIfExists(configFiles, projectRoot, "application.yaml", "yaml");
|
||||
addConfigFileIfExists(configFiles, projectRoot, "config.json", "json");
|
||||
|
||||
// 项目类型特定的配置文件
|
||||
switch (projectType) {
|
||||
case JAVA_MAVEN:
|
||||
case SPRING_BOOT:
|
||||
addConfigFileIfExists(configFiles, projectRoot, "pom.xml", "xml");
|
||||
break;
|
||||
case NODE_JS:
|
||||
case REACT:
|
||||
case VUE:
|
||||
case ANGULAR:
|
||||
case NEXT_JS:
|
||||
addConfigFileIfExists(configFiles, projectRoot, "package.json", "json");
|
||||
addConfigFileIfExists(configFiles, projectRoot, "webpack.config.js", "javascript");
|
||||
break;
|
||||
case PYTHON:
|
||||
case DJANGO:
|
||||
case FLASK:
|
||||
case FASTAPI:
|
||||
addConfigFileIfExists(configFiles, projectRoot, "requirements.txt", "text");
|
||||
addConfigFileIfExists(configFiles, projectRoot, "setup.py", "python");
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error finding configuration files for: " + projectRoot, e);
|
||||
}
|
||||
|
||||
logger.info("Found {} configuration files for project: {}", configFiles.size(), projectRoot);
|
||||
return configFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加配置文件(如果存在)
|
||||
*/
|
||||
private void addConfigFileIfExists(List<ProjectContext.ConfigFile> configFiles,
|
||||
Path projectRoot, String fileName, String fileType) {
|
||||
Path configPath = projectRoot.resolve(fileName);
|
||||
if (Files.exists(configPath)) {
|
||||
String relativePath = projectRoot.relativize(configPath).toString();
|
||||
ProjectContext.ConfigFile configFile =
|
||||
new ProjectContext.ConfigFile(fileName, relativePath, fileType);
|
||||
|
||||
// 标记主要配置文件
|
||||
if (fileName.equals("pom.xml") || fileName.equals("package.json") ||
|
||||
fileName.startsWith("application.")) {
|
||||
configFile.setMainConfig(true);
|
||||
}
|
||||
|
||||
configFiles.add(configFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该忽略目录
|
||||
*/
|
||||
private boolean shouldIgnoreDirectory(String dirName) {
|
||||
return dirName.equals(".git") || dirName.equals(".svn") ||
|
||||
dirName.equals("node_modules") || dirName.equals("target") ||
|
||||
dirName.equals("build") || dirName.equals("dist") ||
|
||||
dirName.equals("__pycache__") || dirName.equals(".idea") ||
|
||||
dirName.equals(".vscode") || dirName.startsWith(".");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
private String getFileExtension(String fileName) {
|
||||
int lastDot = fileName.lastIndexOf('.');
|
||||
return lastDot > 0 ? fileName.substring(lastDot) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为关键文件
|
||||
*/
|
||||
private boolean isKeyFile(String fileName, ProjectType projectType) {
|
||||
// 通用关键文件
|
||||
if (fileName.equals("README.md") || fileName.equals("LICENSE") ||
|
||||
fileName.equals("Dockerfile") || fileName.equals(".gitignore")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 项目类型特定的关键文件
|
||||
if (projectType != null) {
|
||||
String keyFile = projectType.getKeyFile();
|
||||
if (keyFile != null && !keyFile.isEmpty()) {
|
||||
return fileName.equals(keyFile) || fileName.matches(keyFile);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.ProjectType;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 项目模板扩展服务
|
||||
* 生成README、gitignore等通用文件模板
|
||||
*/
|
||||
@Service
|
||||
public class ProjectTemplateExtensions {
|
||||
|
||||
/**
|
||||
* 生成README.md内容
|
||||
*/
|
||||
public String generateReadmeContent(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
# %s
|
||||
|
||||
%s
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Java 17 or higher (for Java projects)
|
||||
- Node.js 16+ (for JavaScript projects)
|
||||
- Python 3.8+ (for Python projects)
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd %s
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
# For Java Maven projects
|
||||
mvn clean install
|
||||
|
||||
# For Node.js projects
|
||||
npm install
|
||||
|
||||
# For Python projects
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Run the application:
|
||||
```bash
|
||||
# For Java Maven projects
|
||||
mvn spring-boot:run
|
||||
|
||||
# For Node.js projects
|
||||
npm start
|
||||
|
||||
# For Python projects
|
||||
python main.py
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
%s/
|
||||
├── src/ # Source code
|
||||
├── test/ # Test files
|
||||
├── docs/ # Documentation
|
||||
├── README.md # This file
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# For Java projects
|
||||
mvn test
|
||||
|
||||
# For Node.js projects
|
||||
npm test
|
||||
|
||||
# For Python projects
|
||||
python -m pytest
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# For Java projects
|
||||
mvn clean package
|
||||
|
||||
# For Node.js projects
|
||||
npm run build
|
||||
|
||||
# For Python projects
|
||||
python setup.py build
|
||||
```
|
||||
|
||||
## 📝 Features
|
||||
|
||||
- Feature 1: Description
|
||||
- Feature 2: Description
|
||||
- Feature 3: Description
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
1. Fork the project
|
||||
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 👥 Authors
|
||||
|
||||
- **%s** - *Initial work* - [%s](mailto:%s)
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- Hat tip to anyone whose code was used
|
||||
- Inspiration
|
||||
- etc
|
||||
|
||||
---
|
||||
|
||||
Created with ❤️ by %s
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION"),
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("EMAIL"),
|
||||
variables.get("AUTHOR")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成.gitignore内容
|
||||
*/
|
||||
public String generateGitignoreContent(ProjectType projectType) {
|
||||
StringBuilder gitignore = new StringBuilder();
|
||||
|
||||
// 通用忽略规则
|
||||
gitignore.append("""
|
||||
# General
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
""");
|
||||
|
||||
// 项目类型特定的忽略规则
|
||||
switch (projectType) {
|
||||
case JAVA_MAVEN:
|
||||
case JAVA_GRADLE:
|
||||
case SPRING_BOOT:
|
||||
gitignore.append("""
|
||||
# Java
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.nar
|
||||
hs_err_pid*
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
# Spring Boot
|
||||
spring-boot-*.log
|
||||
|
||||
""");
|
||||
break;
|
||||
|
||||
case NODE_JS:
|
||||
case REACT:
|
||||
case VUE:
|
||||
case ANGULAR:
|
||||
case NEXT_JS:
|
||||
gitignore.append("""
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage/
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# parcel-bundler cache
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
public
|
||||
|
||||
# Storybook build outputs
|
||||
.out
|
||||
.storybook-out
|
||||
|
||||
# Temporary folders
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
""");
|
||||
break;
|
||||
|
||||
case PYTHON:
|
||||
case DJANGO:
|
||||
case FLASK:
|
||||
case FASTAPI:
|
||||
gitignore.append("""
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
""");
|
||||
break;
|
||||
|
||||
default:
|
||||
// 基本忽略规则已经添加
|
||||
break;
|
||||
}
|
||||
|
||||
return gitignore.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,681 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 项目模板服务
|
||||
* 生成各种项目类型的模板文件内容
|
||||
*/
|
||||
@Service
|
||||
public class ProjectTemplateService {
|
||||
|
||||
/**
|
||||
* 生成Maven pom.xml
|
||||
*/
|
||||
public String generatePomXml(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>%s</artifactId>
|
||||
<version>%s</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>%s</name>
|
||||
<description>%s</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
""",
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("VERSION"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Spring Boot pom.xml
|
||||
*/
|
||||
public String generateSpringBootPomXml(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>%s</artifactId>
|
||||
<version>%s</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>%s</name>
|
||||
<description>%s</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
""",
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("VERSION"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Java主类
|
||||
*/
|
||||
public String generateJavaMainClass(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
package com.example.%s;
|
||||
|
||||
/**
|
||||
* Main application class for %s
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello from %s!");
|
||||
System.out.println("Application started successfully.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application name
|
||||
* @return application name
|
||||
*/
|
||||
public String getApplicationName() {
|
||||
return "%s";
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME").toLowerCase(),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Spring Boot主类
|
||||
*/
|
||||
public String generateSpringBootMainClass(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
package com.example.%s;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Spring Boot main application class for %s
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME").toLowerCase(),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Spring Boot Controller
|
||||
*/
|
||||
public String generateSpringBootController(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
package com.example.%s.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Hello controller for %s
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
@RestController
|
||||
public class HelloController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String hello() {
|
||||
return "Hello from %s!";
|
||||
}
|
||||
|
||||
@GetMapping("/health")
|
||||
public String health() {
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME").toLowerCase(),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Java测试类
|
||||
*/
|
||||
public String generateJavaTestClass(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
package com.example.%s;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test class for %s Application
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
public class ApplicationTest {
|
||||
|
||||
@Test
|
||||
public void testApplicationName() {
|
||||
Application app = new Application();
|
||||
assertEquals("%s", app.getApplicationName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplicationCreation() {
|
||||
Application app = new Application();
|
||||
assertNotNull(app);
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME").toLowerCase(),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成application.yml
|
||||
*/
|
||||
public String generateApplicationYml(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
# Application configuration for %s
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: %s
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
# Logging configuration
|
||||
logging:
|
||||
level:
|
||||
com.example.%s: DEBUG
|
||||
org.springframework: INFO
|
||||
pattern:
|
||||
console: "%%d{yyyy-MM-dd HH:mm:ss} - %%msg%%n"
|
||||
|
||||
# Management endpoints
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info
|
||||
endpoint:
|
||||
health:
|
||||
show-details: when-authorized
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("PROJECT_NAME").toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成package.json
|
||||
*/
|
||||
public String generatePackageJson(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
{
|
||||
"name": "%s",
|
||||
"version": "%s",
|
||||
"description": "%s",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "echo \\"Error: no test specified\\" && exit 1",
|
||||
"dev": "node index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"nodejs",
|
||||
"%s"
|
||||
],
|
||||
"author": "%s <%s>",
|
||||
"license": "MIT",
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("VERSION"),
|
||||
variables.get("DESCRIPTION"),
|
||||
variables.get("PROJECT_NAME"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("EMAIL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Node.js主文件
|
||||
*/
|
||||
public String generateNodeJsMainFile(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
/**
|
||||
* Main application file for %s
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
|
||||
console.log('Hello from %s!');
|
||||
console.log('Node.js application started successfully.');
|
||||
|
||||
// Simple HTTP server example
|
||||
const http = require('http');
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('Hello from %s!\\n');
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成React App.js
|
||||
*/
|
||||
public String generateReactAppJs(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
|
||||
/**
|
||||
* Main App component for %s
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<h1>Welcome to %s</h1>
|
||||
<p>%s</p>
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成React index.html
|
||||
*/
|
||||
public String generateReactIndexHtml(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>%s</title>
|
||||
<meta name="description" content="%s">
|
||||
<meta name="author" content="%s">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION"),
|
||||
variables.get("AUTHOR")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Python主文件
|
||||
*/
|
||||
public String generatePythonMainFile(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
#!/usr/bin/env python3
|
||||
\"\"\"
|
||||
Main application file for %s
|
||||
|
||||
Author: %s
|
||||
\"\"\"
|
||||
|
||||
def main():
|
||||
\"\"\"Main function\"\"\"
|
||||
print("Hello from %s!")
|
||||
print("Python application started successfully.")
|
||||
|
||||
def get_application_name():
|
||||
\"\"\"Get application name\"\"\"
|
||||
return "%s"
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成requirements.txt
|
||||
*/
|
||||
public String generateRequirementsTxt(Map<String, String> variables) {
|
||||
return """
|
||||
# Python dependencies for """ + variables.get("PROJECT_NAME") + """
|
||||
# Add your dependencies here
|
||||
|
||||
# Example dependencies:
|
||||
# requests>=2.28.0
|
||||
# flask>=2.3.0
|
||||
# pytest>=7.0.0
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成静态HTML index.html
|
||||
*/
|
||||
public String generateStaticIndexHtml(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>%s</title>
|
||||
<meta name="description" content="%s">
|
||||
<meta name="author" content="%s">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Welcome to %s</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<h2>About</h2>
|
||||
<p>%s</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li>Modern HTML5 structure</li>
|
||||
<li>Responsive design</li>
|
||||
<li>Clean CSS styling</li>
|
||||
<li>JavaScript functionality</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© %s %s. All rights reserved.</p>
|
||||
</footer>
|
||||
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("DESCRIPTION"),
|
||||
variables.get("CURRENT_YEAR"),
|
||||
variables.get("AUTHOR")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基本CSS
|
||||
*/
|
||||
public String generateBasicCss(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
/* CSS styles for %s */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
header {
|
||||
background: #35424a;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #35424a;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background: #35424a;
|
||||
color: white;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
main {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基本JavaScript
|
||||
*/
|
||||
public String generateBasicJs(Map<String, String> variables) {
|
||||
return String.format("""
|
||||
/**
|
||||
* JavaScript functionality for %s
|
||||
*
|
||||
* @author %s
|
||||
*/
|
||||
|
||||
// Wait for DOM to be fully loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('%s application loaded successfully!');
|
||||
|
||||
// Add click event to header
|
||||
const header = document.querySelector('header h1');
|
||||
if (header) {
|
||||
header.addEventListener('click', function() {
|
||||
alert('Welcome to %s!');
|
||||
});
|
||||
}
|
||||
|
||||
// Add smooth scrolling for anchor links
|
||||
const links = document.querySelectorAll('a[href^="#"]');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Utility function to get application name
|
||||
*/
|
||||
function getApplicationName() {
|
||||
return '%s';
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to show notification
|
||||
*/
|
||||
function showNotification(message) {
|
||||
console.log('Notification:', message);
|
||||
// You can implement a proper notification system here
|
||||
}
|
||||
""",
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("AUTHOR"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("PROJECT_NAME_PASCAL"),
|
||||
variables.get("PROJECT_NAME_PASCAL")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.example.demo.model.ProjectType;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 项目类型检测器
|
||||
* 基于文件特征自动识别项目类型
|
||||
*/
|
||||
@Component
|
||||
public class ProjectTypeDetector {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProjectTypeDetector.class);
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 检测项目类型
|
||||
*
|
||||
* @param projectRoot 项目根目录
|
||||
* @return 检测到的项目类型
|
||||
*/
|
||||
public ProjectType detectProjectType(Path projectRoot) {
|
||||
if (!Files.exists(projectRoot) || !Files.isDirectory(projectRoot)) {
|
||||
logger.warn("Project root does not exist or is not a directory: {}", projectRoot);
|
||||
return ProjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Detecting project type for: {}", projectRoot);
|
||||
|
||||
// 按优先级检测项目类型
|
||||
ProjectType detectedType = detectByKeyFiles(projectRoot);
|
||||
if (detectedType != ProjectType.UNKNOWN) {
|
||||
logger.info("Detected project type: {} for {}", detectedType, projectRoot);
|
||||
return detectedType;
|
||||
}
|
||||
|
||||
// 如果关键文件检测失败,尝试基于目录结构检测
|
||||
detectedType = detectByDirectoryStructure(projectRoot);
|
||||
if (detectedType != ProjectType.UNKNOWN) {
|
||||
logger.info("Detected project type by structure: {} for {}", detectedType, projectRoot);
|
||||
return detectedType;
|
||||
}
|
||||
|
||||
logger.info("Could not determine project type for: {}", projectRoot);
|
||||
return ProjectType.UNKNOWN;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error detecting project type for: " + projectRoot, e);
|
||||
return ProjectType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于关键文件检测项目类型
|
||||
*/
|
||||
private ProjectType detectByKeyFiles(Path projectRoot) throws IOException {
|
||||
// Java Maven项目
|
||||
if (Files.exists(projectRoot.resolve("pom.xml"))) {
|
||||
// 检查是否为Spring Boot项目
|
||||
if (isSpringBootProject(projectRoot)) {
|
||||
return ProjectType.SPRING_BOOT;
|
||||
}
|
||||
return ProjectType.JAVA_MAVEN;
|
||||
}
|
||||
|
||||
// Java Gradle项目
|
||||
if (Files.exists(projectRoot.resolve("build.gradle")) ||
|
||||
Files.exists(projectRoot.resolve("build.gradle.kts"))) {
|
||||
return ProjectType.JAVA_GRADLE;
|
||||
}
|
||||
|
||||
// Node.js项目
|
||||
if (Files.exists(projectRoot.resolve("package.json"))) {
|
||||
return analyzeNodeJsProject(projectRoot);
|
||||
}
|
||||
|
||||
// Python项目
|
||||
if (Files.exists(projectRoot.resolve("requirements.txt")) ||
|
||||
Files.exists(projectRoot.resolve("setup.py")) ||
|
||||
Files.exists(projectRoot.resolve("pyproject.toml"))) {
|
||||
return analyzePythonProject(projectRoot);
|
||||
}
|
||||
|
||||
// .NET项目
|
||||
try (Stream<Path> files = Files.list(projectRoot)) {
|
||||
if (files.anyMatch(path -> path.toString().endsWith(".csproj") ||
|
||||
path.toString().endsWith(".sln"))) {
|
||||
return ProjectType.DOTNET;
|
||||
}
|
||||
}
|
||||
|
||||
// Go项目
|
||||
if (Files.exists(projectRoot.resolve("go.mod"))) {
|
||||
return ProjectType.GO;
|
||||
}
|
||||
|
||||
// Rust项目
|
||||
if (Files.exists(projectRoot.resolve("Cargo.toml"))) {
|
||||
return ProjectType.RUST;
|
||||
}
|
||||
|
||||
// PHP项目
|
||||
if (Files.exists(projectRoot.resolve("composer.json"))) {
|
||||
return ProjectType.PHP;
|
||||
}
|
||||
|
||||
// 静态HTML项目
|
||||
if (Files.exists(projectRoot.resolve("index.html"))) {
|
||||
return ProjectType.HTML_STATIC;
|
||||
}
|
||||
|
||||
return ProjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为Spring Boot项目
|
||||
*/
|
||||
private boolean isSpringBootProject(Path projectRoot) {
|
||||
try {
|
||||
Path pomFile = projectRoot.resolve("pom.xml");
|
||||
if (!Files.exists(pomFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String pomContent = Files.readString(pomFile);
|
||||
return pomContent.contains("spring-boot-starter") ||
|
||||
pomContent.contains("org.springframework.boot");
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading pom.xml for Spring Boot detection", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Node.js项目类型
|
||||
*/
|
||||
private ProjectType analyzeNodeJsProject(Path projectRoot) {
|
||||
try {
|
||||
Path packageJsonPath = projectRoot.resolve("package.json");
|
||||
String content = Files.readString(packageJsonPath);
|
||||
JsonNode packageJson = objectMapper.readTree(content);
|
||||
|
||||
// 检查依赖来确定具体的框架类型
|
||||
JsonNode dependencies = packageJson.get("dependencies");
|
||||
JsonNode devDependencies = packageJson.get("devDependencies");
|
||||
|
||||
if (hasDependency(dependencies, "react") || hasDependency(devDependencies, "react")) {
|
||||
return ProjectType.REACT;
|
||||
}
|
||||
|
||||
if (hasDependency(dependencies, "vue") || hasDependency(devDependencies, "vue")) {
|
||||
return ProjectType.VUE;
|
||||
}
|
||||
|
||||
if (hasDependency(dependencies, "@angular/core") ||
|
||||
hasDependency(devDependencies, "@angular/cli")) {
|
||||
return ProjectType.ANGULAR;
|
||||
}
|
||||
|
||||
if (hasDependency(dependencies, "next") || hasDependency(devDependencies, "next")) {
|
||||
return ProjectType.NEXT_JS;
|
||||
}
|
||||
|
||||
return ProjectType.NODE_JS;
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error analyzing package.json", e);
|
||||
return ProjectType.NODE_JS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析Python项目类型
|
||||
*/
|
||||
private ProjectType analyzePythonProject(Path projectRoot) {
|
||||
// 检查Django项目
|
||||
if (Files.exists(projectRoot.resolve("manage.py"))) {
|
||||
return ProjectType.DJANGO;
|
||||
}
|
||||
|
||||
// 检查Flask项目
|
||||
if (Files.exists(projectRoot.resolve("app.py")) ||
|
||||
Files.exists(projectRoot.resolve("application.py"))) {
|
||||
return ProjectType.FLASK;
|
||||
}
|
||||
|
||||
// 检查FastAPI项目
|
||||
if (Files.exists(projectRoot.resolve("main.py"))) {
|
||||
try {
|
||||
String content = Files.readString(projectRoot.resolve("main.py"));
|
||||
if (content.contains("from fastapi import") || content.contains("import fastapi")) {
|
||||
return ProjectType.FASTAPI;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error reading main.py for FastAPI detection", e);
|
||||
}
|
||||
}
|
||||
|
||||
return ProjectType.PYTHON;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于目录结构检测项目类型
|
||||
*/
|
||||
private ProjectType detectByDirectoryStructure(Path projectRoot) {
|
||||
try {
|
||||
List<String> directories = Files.list(projectRoot)
|
||||
.filter(Files::isDirectory)
|
||||
.map(path -> path.getFileName().toString().toLowerCase())
|
||||
.toList();
|
||||
|
||||
// Java项目特征目录
|
||||
if (directories.contains("src") &&
|
||||
(directories.contains("target") || directories.contains("build"))) {
|
||||
return ProjectType.JAVA_MAVEN; // 默认为Maven
|
||||
}
|
||||
|
||||
// Node.js项目特征目录
|
||||
if (directories.contains("node_modules") ||
|
||||
directories.contains("public") ||
|
||||
directories.contains("dist")) {
|
||||
return ProjectType.NODE_JS;
|
||||
}
|
||||
|
||||
// Python项目特征目录
|
||||
if (directories.contains("venv") ||
|
||||
directories.contains("env") ||
|
||||
directories.contains("__pycache__")) {
|
||||
return ProjectType.PYTHON;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error analyzing directory structure", e);
|
||||
}
|
||||
|
||||
return ProjectType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否存在特定依赖
|
||||
*/
|
||||
private boolean hasDependency(JsonNode dependencies, String dependencyName) {
|
||||
return dependencies != null && dependencies.has(dependencyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目类型的详细信息
|
||||
*/
|
||||
public String getProjectTypeDetails(Path projectRoot, ProjectType projectType) {
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append("Project Type: ").append(projectType.getDisplayName()).append("\n");
|
||||
details.append("Primary Language: ").append(projectType.getPrimaryLanguage()).append("\n");
|
||||
details.append("Package Manager: ").append(projectType.getPackageManager()).append("\n");
|
||||
|
||||
// 添加特定项目类型的详细信息
|
||||
switch (projectType) {
|
||||
case SPRING_BOOT:
|
||||
details.append("Framework: Spring Boot\n");
|
||||
details.append("Build Tool: Maven\n");
|
||||
break;
|
||||
case REACT:
|
||||
details.append("Framework: React\n");
|
||||
details.append("Runtime: Node.js\n");
|
||||
break;
|
||||
case DJANGO:
|
||||
details.append("Framework: Django\n");
|
||||
details.append("Language: Python\n");
|
||||
break;
|
||||
// 可以添加更多项目类型的详细信息
|
||||
}
|
||||
|
||||
return details.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class TaskSummaryService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TaskSummaryService.class);
|
||||
|
||||
private static final Pattern[] ACTION_PATTERNS = {
|
||||
Pattern.compile("(?i)creating?\\s+(?:a\\s+)?(?:new\\s+)?(.{1,50}?)(?:\\s+file|\\s+directory|\\s+project)?", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)writing?\\s+(?:to\\s+)?(.{1,50}?)(?:\\s+file)?", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)reading?\\s+(?:from\\s+)?(.{1,50}?)(?:\\s+file)?", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)editing?\\s+(.{1,50}?)(?:\\s+file)?", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)listing?\\s+(?:the\\s+)?(.{1,50}?)(?:\\s+directory)?", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)analyzing?\\s+(.{1,50}?)", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)generating?\\s+(.{1,50}?)", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("(?i)building?\\s+(.{1,50}?)", Pattern.CASE_INSENSITIVE)
|
||||
};
|
||||
|
||||
private static final String[] ACTION_VERBS = {
|
||||
"创建", "写入", "读取", "编辑", "列出", "分析", "生成", "构建",
|
||||
"creating", "writing", "reading", "editing", "listing", "analyzing", "generating", "building"
|
||||
};
|
||||
|
||||
/**
|
||||
* 从AI响应中提取任务摘要
|
||||
*/
|
||||
public String extractTaskSummary(String aiResponse) {
|
||||
if (aiResponse == null || aiResponse.trim().isEmpty()) {
|
||||
return "处理中...";
|
||||
}
|
||||
|
||||
// 清理响应文本
|
||||
String cleanResponse = aiResponse.replaceAll("```[\\s\\S]*?```", "").trim();
|
||||
|
||||
// 尝试匹配具体操作
|
||||
for (Pattern pattern : ACTION_PATTERNS) {
|
||||
Matcher matcher = pattern.matcher(cleanResponse);
|
||||
if (matcher.find()) {
|
||||
String action = matcher.group(0).trim();
|
||||
if (action.length() > 50) {
|
||||
action = action.substring(0, 47) + "...";
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
// 查找动作词汇
|
||||
String lowerResponse = cleanResponse.toLowerCase();
|
||||
for (String verb : ACTION_VERBS) {
|
||||
if (lowerResponse.contains(verb.toLowerCase())) {
|
||||
// 提取包含动作词的句子
|
||||
String[] sentences = cleanResponse.split("[.!?\\n]");
|
||||
for (String sentence : sentences) {
|
||||
if (sentence.toLowerCase().contains(verb.toLowerCase())) {
|
||||
String summary = sentence.trim();
|
||||
if (summary.length() > 60) {
|
||||
summary = summary.substring(0, 57) + "...";
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到具体操作,返回通用描述
|
||||
if (cleanResponse.length() > 60) {
|
||||
return cleanResponse.substring(0, 57) + "...";
|
||||
}
|
||||
|
||||
return cleanResponse.isEmpty() ? "处理中..." : cleanResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算任务复杂度和预期轮数
|
||||
*/
|
||||
public int estimateTaskComplexity(String initialMessage) {
|
||||
if (initialMessage == null) return 1;
|
||||
|
||||
String lowerMessage = initialMessage.toLowerCase();
|
||||
int complexity = 1;
|
||||
|
||||
// 基于关键词估算复杂度
|
||||
if (lowerMessage.contains("project") || lowerMessage.contains("项目")) complexity += 3;
|
||||
if (lowerMessage.contains("complete") || lowerMessage.contains("完整")) complexity += 2;
|
||||
if (lowerMessage.contains("multiple") || lowerMessage.contains("多个")) complexity += 2;
|
||||
if (lowerMessage.contains("full-stack") || lowerMessage.contains("全栈")) complexity += 4;
|
||||
if (lowerMessage.contains("website") || lowerMessage.contains("网站")) complexity += 2;
|
||||
if (lowerMessage.contains("api") || lowerMessage.contains("接口")) complexity += 2;
|
||||
|
||||
// 基于文件操作数量估算
|
||||
long fileOperations = lowerMessage.chars()
|
||||
.mapToObj(c -> String.valueOf((char) c))
|
||||
.filter(s -> s.matches(".*(?:create|write|edit|file|directory).*"))
|
||||
.count();
|
||||
|
||||
complexity += (int) Math.min(fileOperations / 2, 5);
|
||||
|
||||
return Math.min(complexity, 15); // 最大15轮
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成当前状态的用户友好描述
|
||||
*/
|
||||
public String generateStatusDescription(String status, String currentAction, int currentTurn, int totalTurns) {
|
||||
StringBuilder desc = new StringBuilder();
|
||||
|
||||
switch (status) {
|
||||
case "RUNNING":
|
||||
if (currentAction != null && !currentAction.trim().isEmpty()) {
|
||||
desc.append("🔄 ").append(currentAction);
|
||||
} else {
|
||||
desc.append("🤔 AI正在思考...");
|
||||
}
|
||||
|
||||
if (totalTurns > 1) {
|
||||
desc.append(String.format(" (第%d/%d轮)", currentTurn, totalTurns));
|
||||
}
|
||||
break;
|
||||
|
||||
case "COMPLETED":
|
||||
desc.append("✅ 任务完成");
|
||||
if (totalTurns > 1) {
|
||||
desc.append(String.format(" (共%d轮)", currentTurn));
|
||||
}
|
||||
break;
|
||||
|
||||
case "ERROR":
|
||||
desc.append("❌ 执行出错");
|
||||
break;
|
||||
|
||||
default:
|
||||
desc.append("⏳ 处理中...");
|
||||
}
|
||||
|
||||
return desc.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 工具执行日志记录服务
|
||||
* 记录所有工具调用的详细信息,使用中文日志
|
||||
*/
|
||||
@Service
|
||||
public class ToolExecutionLogger {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ToolExecutionLogger.class);
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
// 工具调用计数器
|
||||
private final AtomicLong callCounter = new AtomicLong(0);
|
||||
|
||||
// 工具执行统计
|
||||
private final Map<String, ToolStats> toolStats = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 记录工具调用开始
|
||||
*/
|
||||
public long logToolStart(String toolName, String description, Object parameters) {
|
||||
long callId = callCounter.incrementAndGet();
|
||||
String timestamp = LocalDateTime.now().format(formatter);
|
||||
|
||||
logger.info("🚀 [工具调用-{}] 开始执行工具: {}", callId, toolName);
|
||||
logger.info("📝 [工具调用-{}] 工具描述: {}", callId, description);
|
||||
logger.info("⚙️ [工具调用-{}] 调用参数: {}", callId, formatParameters(parameters));
|
||||
logger.info("🕐 [工具调用-{}] 开始时间: {}", callId, timestamp);
|
||||
|
||||
// 更新统计信息
|
||||
toolStats.computeIfAbsent(toolName, k -> new ToolStats()).incrementCalls();
|
||||
|
||||
return callId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录工具调用成功
|
||||
*/
|
||||
public void logToolSuccess(long callId, String toolName, String result, long executionTimeMs) {
|
||||
String timestamp = LocalDateTime.now().format(formatter);
|
||||
|
||||
logger.info("✅ [工具调用-{}] 工具执行成功: {}", callId, toolName);
|
||||
logger.info("📊 [工具调用-{}] 执行结果: {}", callId, truncateResult(result));
|
||||
logger.info("⏱️ [工具调用-{}] 执行耗时: {}ms", callId, executionTimeMs);
|
||||
logger.info("🕐 [工具调用-{}] 完成时间: {}", callId, timestamp);
|
||||
|
||||
// 更新统计信息
|
||||
ToolStats stats = toolStats.get(toolName);
|
||||
if (stats != null) {
|
||||
stats.incrementSuccess();
|
||||
stats.addExecutionTime(executionTimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录工具调用失败
|
||||
*/
|
||||
public void logToolError(long callId, String toolName, String error, long executionTimeMs) {
|
||||
String timestamp = LocalDateTime.now().format(formatter);
|
||||
|
||||
logger.error("❌ [工具调用-{}] 工具执行失败: {}", callId, toolName);
|
||||
logger.error("🚨 [工具调用-{}] 错误信息: {}", callId, error);
|
||||
logger.error("⏱️ [工具调用-{}] 执行耗时: {}ms", callId, executionTimeMs);
|
||||
logger.error("🕐 [工具调用-{}] 失败时间: {}", callId, timestamp);
|
||||
|
||||
// 更新统计信息
|
||||
ToolStats stats = toolStats.get(toolName);
|
||||
if (stats != null) {
|
||||
stats.incrementError();
|
||||
stats.addExecutionTime(executionTimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录工具调用的详细步骤
|
||||
*/
|
||||
public void logToolStep(long callId, String toolName, String step, String details) {
|
||||
logger.debug("🔄 [工具调用-{}] [{}] 执行步骤: {} - {}", callId, toolName, step, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录文件操作
|
||||
*/
|
||||
public void logFileOperation(long callId, String operation, String filePath, String details) {
|
||||
logger.info("📁 [工具调用-{}] 文件操作: {} - 文件: {} - 详情: {}", callId, operation, filePath, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录项目分析
|
||||
*/
|
||||
public void logProjectAnalysis(long callId, String projectPath, String projectType, String details) {
|
||||
logger.info("🔍 [工具调用-{}] 项目分析: 路径={}, 类型={}, 详情={}", callId, projectPath, projectType, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录项目创建
|
||||
*/
|
||||
public void logProjectCreation(long callId, String projectName, String projectType, String projectPath) {
|
||||
logger.info("🏗️ [工具调用-{}] 项目创建: 名称={}, 类型={}, 路径={}", callId, projectName, projectType, projectPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具执行统计
|
||||
*/
|
||||
public void logToolStatistics() {
|
||||
logger.info("📈 ========== 工具执行统计 ==========");
|
||||
toolStats.forEach((toolName, stats) -> {
|
||||
logger.info("🔧 工具: {} | 调用次数: {} | 成功: {} | 失败: {} | 平均耗时: {}ms",
|
||||
toolName, stats.getTotalCalls(), stats.getSuccessCount(),
|
||||
stats.getErrorCount(), stats.getAverageExecutionTime());
|
||||
});
|
||||
logger.info("📈 ================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化参数显示
|
||||
*/
|
||||
private String formatParameters(Object parameters) {
|
||||
if (parameters == null) {
|
||||
return "无参数";
|
||||
}
|
||||
String paramStr = parameters.toString();
|
||||
return paramStr.length() > 200 ? paramStr.substring(0, 200) + "..." : paramStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断结果显示
|
||||
*/
|
||||
private String truncateResult(String result) {
|
||||
if (result == null) {
|
||||
return "无结果";
|
||||
}
|
||||
return result.length() > 300 ? result.substring(0, 300) + "..." : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工具统计信息内部类
|
||||
*/
|
||||
private static class ToolStats {
|
||||
private long totalCalls = 0;
|
||||
private long successCount = 0;
|
||||
private long errorCount = 0;
|
||||
private long totalExecutionTime = 0;
|
||||
|
||||
public void incrementCalls() {
|
||||
totalCalls++;
|
||||
}
|
||||
|
||||
public void incrementSuccess() {
|
||||
successCount++;
|
||||
}
|
||||
|
||||
public void incrementError() {
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
public void addExecutionTime(long time) {
|
||||
totalExecutionTime += time;
|
||||
}
|
||||
|
||||
public long getTotalCalls() {
|
||||
return totalCalls;
|
||||
}
|
||||
|
||||
public long getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public long getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
public long getAverageExecutionTime() {
|
||||
return totalCalls > 0 ? totalExecutionTime / totalCalls : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package com.example.demo.service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
/**
|
||||
* 工具日志事件类
|
||||
* 继承自LogEvent,添加工具相关的字段
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ToolLogEvent extends LogEvent {
|
||||
|
||||
private String toolName;
|
||||
private String filePath;
|
||||
private String icon;
|
||||
private String status; // RUNNING, SUCCESS, ERROR
|
||||
private Long executionTime; // 执行时间(毫秒)
|
||||
private String summary; // 操作摘要
|
||||
|
||||
// Constructors
|
||||
public ToolLogEvent() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ToolLogEvent(String type, String taskId, String toolName, String filePath,
|
||||
String message, String timestamp, String icon, String status) {
|
||||
super(type, taskId, message, timestamp);
|
||||
this.toolName = toolName;
|
||||
this.filePath = filePath;
|
||||
this.icon = icon;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getToolName() {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
public void setToolName(String toolName) {
|
||||
this.toolName = toolName;
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getExecutionTime() {
|
||||
return executionTime;
|
||||
}
|
||||
|
||||
public void setExecutionTime(Long executionTime) {
|
||||
this.executionTime = executionTime;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary) {
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ToolLogEvent{" +
|
||||
"toolName='" + toolName + '\'' +
|
||||
", filePath='" + filePath + '\'' +
|
||||
", icon='" + icon + '\'' +
|
||||
", status='" + status + '\'' +
|
||||
", executionTime=" + executionTime +
|
||||
", summary='" + summary + '\'' +
|
||||
"} " + super.toString();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user