90 Commits

Author SHA1 Message Date
ageerle
3071bfd0f9 Merge pull request #282 from Anush008/main
docs: Docker Compose setup for Qdrant
2026-03-28 20:33:25 +08:00
Anush008
7bb938c145 docs: Docker Compose setup for Qdrant 2026-03-28 13:37:53 +05:30
ageerle
75b21d3633 Merge pull request #281 from Anush008/main
feat: Adds support for Qdrant vector search
2026-03-27 21:51:03 +08:00
Anush008
7ed9d8def4 chore: Rename METADATA_DOC_ID_KEY 2026-03-27 18:36:30 +05:30
Anush008
63ed7ddb02 feat: Adds support for Qdrant vector search 2026-03-27 18:31:05 +05:30
ageerle
11696a016d fix: 修复文件类型匹配和知识库切割配置问题
1. 修复 ResourceLoaderFactory 文件扩展名匹配问题
   - 去除扩展名前导点,确保 .pdf 能正确匹配 PDF 解析器
   - 修复 PDF/Word/Excel 等文件走错解析逻辑的问题

2. 优化文本切割器动态配置
   - CharacterTextSplitter 和 ExcelTextSplitter 支持从知识库读取配置
   - 根据 kid 查询 separator、textBlockSize、overlapChar
   - 查询失败时降级使用默认配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 18:51:01 +08:00
ageerle
1a10104751 docs: 更新README文档,同步中英文版本
- 移除优秀开源项目推荐章节
- 英文版添加Docker部署完整文档
- 英文版添加技术架构详细描述
- 英文版添加RAG排查手册链接
- 统一核心功能表格格式

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:02:46 +08:00
ageerle
f95cb17933 chore: 删除ruoyi-modules-api模块
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 12:31:09 +08:00
ageerle
0687b49542 Merge branch 'v3.0.0' into main
合并 v3.0.0 分支到 main,包含以下主要更新:
- 重构聊天模块架构,引入Handler模式
- 添加 Docker 部署支持
- 恢复 MCP 模块功能
- 工作流与大模型聊天对话整合
- 多项 bug 修复和文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 12:15:40 +08:00
ageerle
27ad00ac3a refactor: 抽离特殊聊天模式处理逻辑
- 将工作流、人机交互恢复、思考模式处理逻辑抽离为独立方法
- 新增 handleSpecialChatModes 方法统一处理特殊模式
- 新增 handleThinkingMode 方法专门处理思考模式
- 简化 sseChat 方法结构,提高代码可读性

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 10:38:54 +08:00
ageerle
c84d6247b0 refactor: 重构聊天模块架构
- 删除废弃的ChatMessageDTO、ChatContext、AbstractChatMessageService等类
- 迁移ChatServiceFactory和IChatMessageService到ruoyi-chat模块
- 重构ChatHandler体系,移除DefaultChatHandler和ChatContextBuilder
- 优化SSE消息处理,新增SseEventDto
- 简化各AI服务提供商实现类代码
- 优化工作流节点消息处理逻辑

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 01:20:41 +08:00
ageerle
f582f38570 docs: 整理Docker配置文件并更新文档
- 将Docker相关配置文件移动到docs/docker/ruoyi-ai/目录
- 更新README.md核心亮点表格格式
- 新增流程编排模块详细说明文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:22:47 +08:00
ageerle
13800dc389 Merge pull request #277 from XiaoEns/v3.0.0
fix: 修复知识库附件上传乱码问题
2026-03-17 22:07:32 +08:00
xiaoen
619d9b1e84 fix: 兼容旧代码,添加deepseek服务调用 2026-03-17 21:30:56 +08:00
xiaoen
556cc93f14 fix: 修复知识库附件上传乱码问题 2026-03-17 18:57:30 +08:00
ageerle
a50375616e Merge pull request #276 from LM20230311/v3.0.0
V3.0.0
2026-03-17 14:09:52 +08:00
LM20230311
e33447d023 Merge branch 'v3.0.0' of https://github.com/ageerle/ruoyi-ai into v3.0.0 2026-03-17 13:48:21 +08:00
LM20230311
7be277b3e6 Merge branch 'v3.0.0-deploy' into v3.0.0 2026-03-17 13:47:27 +08:00
LM20230311
5fa385e90b docs: add Docker deployment instructions and service port mappings to README; update sys_client table in MySQL script 2026-03-17 11:09:13 +08:00
LM20230311
0a78966737 chore: update UPSTREAM_HOST configuration in docker-compose-all.yaml to remove http:// prefix 2026-03-17 10:38:43 +08:00
LM20230311
0eb7f00867 chore: add environment variables for backend API configuration in docker-compose-all.yaml 2026-03-16 17:14:01 +08:00
LM20230311
00ce7f2d98 chore: update port mappings in docker-compose.yaml for MySQL, Weaviate, and backend services 2026-03-16 15:32:27 +08:00
ageerle
003c066361 chore(sql): 更新数据库脚本,移除chat_config表
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 23:57:55 +08:00
ageerle
a5e7c59fd4 refactor: 重构项目架构,优化向量服务
- 移除 Graph 知识图谱相关模块(Neo4j、GraphRAG等)
- 移除 demo、job、wechat 示例模块,简化项目结构
- 修复向量维度获取方式,改为从数据库配置读取
- 添加 gRPC BOM 依赖管理,解决 Milvus SDK 版本冲突
- 新增 PPIO 服务和 Embedding 提供者支持
- 清理冗余代码和未使用的依赖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 23:39:30 +08:00
ageerle
accac603cf chore(sql): 更新数据库脚本v3版本
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:16:45 +08:00
ageerle
7c96c730e6 refactor(chat): 重构聊天服务架构,引入Handler模式
主要变更:
1. 移除ruoyi-ai-copilot模块
2. 重构docker配置目录结构,统一迁移至docs/docker/
3. 聊天服务引入Handler模式:
   - 新增ChatHandler接口及多种实现
   - DefaultChatHandler: 默认聊天处理
   - AgentChatHandler: Agent模式处理
   - WorkflowChatHandler: 工作流处理
   - ResumeChatHandler: 恢复会话处理
   - ChatContextBuilder: 上下文构建器
4. 简化AbstractStreamingChatService和ChatServiceFacade代码
5. 优化各Provider实现,统一代码风格

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:14:56 +08:00
LM20230311
a2dbdb30ff fix: remove unnecessary COPY command for ruoyi-modules-api in Dockerfile.backend 2026-03-11 09:57:47 +08:00
LM20230311
772e4af9bf Merge remote-tracking branch 'upstream/v3.0.0' into v3.0.0-deploy 2026-03-11 09:35:48 +08:00
ageerle
e601eb6db5 feat(wechat): 修复微信公众号无法登录 2026-03-09 10:27:53 +08:00
evo
84dbc2cfbf feat:恢复mcp模块 动态agent 2026-03-08 22:41:24 +08:00
evo
f160ec714b feat-恢复mcp模块 2026-03-07 15:53:06 +08:00
ageerle
a8bd4b47a0 chore: 移除项目文档和技术交流链接
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:44:07 +08:00
ageerle
6c4c661516 chore: 移除项目文档和技术交流链接
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:43:54 +08:00
ageerle
b85c17a126 refactor: 重构Issue模板为通用格式
- 删除企业合作登记模板
- 新增漏洞报告模板
- 新增想法建议模板
- 新增自定义模板

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:39:19 +08:00
ageerle
a59ddf6070 refactor: 重构Issue模板为通用格式
- 删除企业合作登记模板
- 新增漏洞报告模板
- 新增想法建议模板
- 新增自定义模板

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:39:06 +08:00
ageerle
797ecbb054 feat: 新增联系人/联系方式字段并添加填写示例
- 新增联系人/联系方式字段,支持登记后主动联系
- 在可复制模板中添加完整填写示例

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:30:15 +08:00
ageerle
b6b78afea9 refactor: 优化企业合作登记模板格式
- 改为评论登记模式,用户在Issue下评论填写
- 提供格式预览和可复制模板
- 新增公司Logo和项目Logo字段
- 移除联系方式模块,保护隐私

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:20:31 +08:00
ageerle
0690156362 refactor: 优化企业合作登记模板格式
- 改为评论登记模式,用户在Issue下评论填写
- 提供格式预览和可复制模板
- 新增公司Logo和项目Logo字段
- 移除联系方式模块,保护隐私

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:19:27 +08:00
ageerle
02240f3fd0 feat: 添加企业AI应用合作登记Issue模板
- 新增企业合作登记模板,用于收集企业AI应用需求
- 包含基本信息、AI应用需求、联系方式三个模块
- 预设筛选字段便于评估合作匹配度

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:07:25 +08:00
ageerle
dc1e84bc52 feat: 添加企业AI应用合作登记Issue模板
- 新增企业合作登记模板,用于收集企业AI应用需求
- 包含基本信息、AI应用需求、联系方式三个模块
- 预设筛选字段便于评估合作匹配度

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:57:53 +08:00
ageerle
4977df0ba8 feat: 更新项目文档 2026-03-06 10:27:45 +08:00
ageerle
27211b67f9 feat: 更新项目文档 2026-03-06 10:20:15 +08:00
LM20230311
1600ab384e feat: 支持手动挂载数据卷; 2026-03-05 09:35:27 +08:00
LM20230311
418805a1ef feat: 源码docker部署完成; 2026-03-05 09:27:34 +08:00
LM20230311
4c4b52bca7 feat: 忽略data目录; 2026-03-05 09:07:10 +08:00
LM20230311
7245259bc4 fix: 忽略所有错误以确保SQL脚本继续执行 2026-03-04 17:57:08 +08:00
LM20230311
6690c8204c fix: 修复表数量异常问题; 2026-03-04 17:56:20 +08:00
LM20230311
d8fc597f85 fix: 解决mysql容器长时间建表导致失败问题; 2026-03-04 17:34:38 +08:00
evo
7f1146ecae Merge pull request #272 from MuSan-Li/v3.0.0
fix: 还原 McpAgent 为通用工具调用版本
2026-03-04 16:37:28 +08:00
evo
0130028952 fix: 还原 McpAgent 为通用工具调用版本 2026-03-04 16:34:56 +08:00
LM20230311
5a716da5a6 feat: 添加docker部署文件; 2026-03-04 16:31:54 +08:00
evo
2470ec7573 Merge pull request #271 from MuSan-Li/feature-20260218-add-mcp-model
增加McpAgent空格
2026-03-04 14:36:32 +08:00
evo
e3fb25fba6 增加McpAgent空格 2026-03-04 14:32:41 +08:00
ageerle
a916f14efc Merge pull request #268 from onestardao/main
docs: add structured RAG 16-problem troubleshooting guide and README entry
2026-03-03 10:40:32 +08:00
PSBigBig × MiniPS
523628ade6 docs: add structured RAG 16-problem troubleshooting guide and README entry 2026-03-02 19:01:59 +08:00
PSBigBig × MiniPS
2259a2f717 docs: add structured RAG 16-problem troubleshooting guide and README entry
This commit introduces a structured RAG troubleshooting guide based on a fixed 16-problem failure map.

Changes include:

1. Added a dedicated RAG troubleshooting document:
   - docs/troubleshooting/rag-failures.md
   - Includes full 16-problem map (No.1–No.16) with layer tags [IN]/[RE]/[ST]/[OP]
   - Adds symptom-based entry points
   - Adds layer-based diagnostics section
   - Adds issue reporting template referencing No.X labels

2. Updated README.md:
   - Added entry link under the "使用文档" section
   - Direct link to the new RAG troubleshooting guide
   - Keeps README structure and tone consistent

No functional changes.
Documentation only.
2026-03-02 18:51:36 +08:00
PSBigBig × MiniPS
8df37274da docs: add RAG answer troubleshooting guide 2026-03-02 17:44:59 +08:00
PSBigBig × MiniPS
393057ab24 docs: add RAG answer troubleshooting guide 2026-03-02 17:41:47 +08:00
ageerle
2aa7e0f4f1 Merge pull request #267 from StevenJack666/v3.0.0
V3.0.0
2026-03-02 09:10:12 +08:00
steve
b2d2fb34b8 Merge branch 'ageerle:v3.0.0' into v3.0.0 2026-03-01 11:37:10 +08:00
zhang
c1e2a03178 解决冲突 2026-03-01 11:29:58 +08:00
zhang
fd38d2030e 修改异常节点 2026-02-28 11:53:36 +08:00
zhang
a42f881b67 Merge branch 'vFuture3.0.0' into v3.0.0 2026-02-27 17:35:42 +08:00
zengxb
20d531c0db context:新增工作流节点提供模板消息输出以及智谱大模型Chat对话接入 2026-02-27 14:53:07 +08:00
ageerle
1e6046cf52 feat: 移除数字人模块 2026-02-27 14:00:57 +08:00
zhang
495f2de6ef Merge remote-tracking branch 'origin/v3.0.0' into v3.0.0 2026-02-27 10:30:33 +08:00
zhang
e97bd4e201 去掉分号 2026-02-27 10:14:53 +08:00
zhang
70e5e393ef Merge branch 'work_flow_v3.0' into v3.0.0
# Conflicts:
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java
2026-02-26 14:48:19 +08:00
zengxb
8954f59cd7 context:工作流和Ai Chat对话消息功能整合 2026-02-26 14:36:33 +08:00
ageerle
c78b1a14a9 feat: 增加ppio厂商支持 2026-02-25 21:20:22 +08:00
ageerle
e768c4b356 feat: 调整默认sql脚本 2026-02-24 22:46:47 +08:00
zhang
d059f50ba6 配置信息修改 2026-02-24 16:09:37 +08:00
zhang
26bcfbba8a 修改数据库读取工具 2026-02-24 16:07:18 +08:00
zengxb
d6e4a50d6e context:根据模型分类找到实现类修改为由模型供应商找到实现类(保持工作流和对话流程一致) 2026-02-24 10:58:11 +08:00
zengxb
0a115f289e context:通义万相文生图节点功能以及发送邮箱和HTTP请求节点调研 2026-02-24 10:34:26 +08:00
ageerle
31e8df8bc0 Merge pull request #262 from MuSan-Li/feature-20260218-add-mcp-model
add mcp model
2026-02-23 22:57:44 +08:00
evo
ee477213e0 优化mcp模块功能 2026-02-23 18:21:31 +08:00
evo
1e6e236d3c 优化导出报错提示 2026-02-23 16:07:38 +08:00
evo
593a0d0049 增加mcp工具模块 2026-02-23 16:07:13 +08:00
ageerle
d4f8f91893 Merge pull request #259 from StevenJack666/v3.0.0
chat对话接口如参改为ChatContext,方便后续扩展
2026-02-15 15:54:21 +08:00
zhang
f25ebdf9ec 去掉多余字符 2026-02-14 22:07:09 +08:00
zhang
32b8144b56 Merge branch 'work_flow_v3.0' into v3.0.0
# Conflicts:
#	ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java
#	ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java
2026-02-14 21:14:50 +08:00
zengxb
b40805cb57 context:公共聊天接口调整为对话上下文对象传输 2026-02-14 10:55:53 +08:00
steve
008f64617f Merge pull request #4 from StevenJack666/feature-v3.0.0
Feature v3.0.0
2026-02-14 10:49:52 +08:00
zhang
3539e222d2 init 2026-02-14 10:48:43 +08:00
zhang
c4d1ea974d init 2026-02-14 10:48:33 +08:00
zengxb
420e05ecf3 context:工作流与大模型聊天对话整合(新增Common-Chat公共对话接口) 2026-02-13 17:56:55 +08:00
ageerle
ee8c882b6f Merge pull request #256 from StevenJack666/main
修改AI工作流后端逻辑
2026-02-11 17:03:31 +08:00
zhang
69ec2a33a4 init 2026-02-09 17:47:18 +08:00
zhang
1cd8ae1cd9 人机交互节点逻辑修改 2026-02-09 17:43:29 +08:00
442 changed files with 8643 additions and 28627 deletions

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

15
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

@@ -0,0 +1,15 @@
---
name: 自定义
about: 其他问题或讨论
title: ''
labels: ''
assignees: ''
---
## 描述
请详细描述你的问题或需求:
## 补充信息
如有其他补充,请在此填写:

View 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应用状态 | |
| 计划落地时间 | |
| 当前痛点或挑战 | |
| 公司简介 | |
```
---
> **温馨提示**:提交的信息仅用于合作沟通,不会对外公开。

View File

@@ -0,0 +1,31 @@
---
name: 想法建议
about: 提出新功能建议或改进想法
title: '[Feature] '
labels: 'enhancement'
assignees: ''
---
## 建议类型
□ 新功能 □ 功能改进 □ 文档完善 □ 其他
## 建议描述
清晰描述你的建议内容:
## 使用场景
描述这个功能在什么场景下会用到:
## 期望效果
描述你期望的效果:
## 参考示例
如有类似的参考实现或产品,请提供链接:
## 补充说明
其他补充信息:

5
.gitignore vendored
View File

@@ -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
View File

@@ -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)**

View File

@@ -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

View 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:

View 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

View 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

View File

@@ -0,0 +1,12 @@
---
services:
qdrant:
image: qdrant/qdrant:latest
ports:
- 6333:6333
- 6334:6334
volumes:
- qdrant_data:/qdrant/storage
volumes:
qdrant_data:
...

View 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"]

View 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"]

View 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:

View 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:

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
docs/image/dy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View 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 "数据库初始化完成"

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -1 +0,0 @@
数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据

View File

@@ -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 AttentionDSA稀疏注意力机制在显著降低计算开销的同时优化长上下文性能通过可扩展强化学习框架整体能力达到 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 AttentionDSA稀疏注意力机制在显著降低计算开销的同时优化长上下文性能通过可扩展强化学习框架整体能力达到 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

View File

@@ -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');

View 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)

View 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
View File

@@ -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>

View File

@@ -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>

View File

@@ -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启动成功 ლ(´ڡ`ლ)゙");
}
}

View File

@@ -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 {
// 不需要实现
}
}

View File

@@ -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"));

View File

@@ -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: ""

View 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"

View File

@@ -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

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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 登录态

View File

@@ -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;
/**
* 备注
*/

View File

@@ -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;
/**
* 请求地址

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
/**
* 备注

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;
/**
* 备注
*/

View File

@@ -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;
/**
* 请求地址

View File

@@ -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;
}

View File

@@ -1,4 +1,4 @@
package org.ruoyi.workflow.enums;
package org.ruoyi.common.chat.enums;
import com.baomidou.mybatisplus.annotation.IEnum;

View File

@@ -1,4 +1,4 @@
package org.ruoyi.workflow.enums;
package org.ruoyi.common.chat.enums;
import lombok.Getter;

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
package org.ruoyi.workflow.enums;
package org.ruoyi.common.chat.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -15,4 +15,13 @@ public interface ConfigService {
*/
String getConfigValue(String configKey);
/**
* 根据配置类型和配置key获取值
*
* @param category 配置类型
* @param configKey 配置key
* @return 配置属性
*/
String getConfigValue(String category, String configKey);
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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);
}
}
/**
* 本机全用户会话发送消息
*

View File

@@ -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();
}
}

View File

@@ -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));
}
/**
* 是否开启
*/

View File

@@ -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());
}

View File

@@ -12,7 +12,6 @@
<packaging>pom</packaging>
<modules>
<module>ruoyi-ai-copilot</module>
<module>ruoyi-monitor-admin</module>
<module>ruoyi-snailjob-server</module>
</modules>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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("🎉 ========================================");
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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);
}
}
}

View File

@@ -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++;
}
}
}

View File

@@ -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";
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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";
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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>&copy; %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")
);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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