first commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>PoJie</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-member</module>
|
||||
<module>ruoyi-generator</module>
|
||||
<module>ruoyi-job</module>
|
||||
<module>ruoyi-mp</module>
|
||||
<module>ruoyi-system</module>
|
||||
<module>ruoyi-workflow</module>
|
||||
<module>ruoyi-ai</module>
|
||||
<module>ruoyi-knowledge</module>
|
||||
<module>ruoyi-chat</module>
|
||||
<module>ruoyi-pay</module>
|
||||
<module>ruoyi-mall</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<description>
|
||||
ruoyi-modules 业务模块
|
||||
</description>
|
||||
|
||||
</project>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,549 @@
|
||||
# AI模块 (ruoyi-ai)
|
||||
|
||||
## 模块简介
|
||||
|
||||
`ruoyi-ai` 是 PoJie 项目的 AI 服务中心模块,提供统一的 AI 能力接入,标准化接入主流模型与平台,支持聊天、流式对话、AI创作、智能PPT、多模态理解、代码助手、RAG增强与统一监控等功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. Provider 管理与编排 ✅
|
||||
- ✅ 统一接口:`AiProviderManager` 管理所有模型提供商,支持热切换
|
||||
- ✅ 智能路由:`AiOrchestrationEngine` 支持最快响应、最佳质量、成本最优、负载均衡等策略
|
||||
- ✅ 自动降级:错误率>30%或响应超时自动熔断与故障转移
|
||||
- ✅ 多模型接入:统一调度 OpenAI、Azure、ChatGLM、通义千问、智谱等主流模型
|
||||
|
||||
### 2. 聊天与流式交互 ✅
|
||||
- ✅ 同步/异步聊天
|
||||
- ✅ SSE 流式推送
|
||||
- ✅ WebSocket 实时通道 (`/ws/ai/chat`)
|
||||
- ✅ 聊天历史管理:自动保存聊天记录到Redis,支持历史查询、清除和会话统计
|
||||
|
||||
### 3. AI 辅助编码 ✅
|
||||
- ✅ 代码分析:提供代码审查与重构建议
|
||||
- ✅ 脚手架生成:自动生成项目结构与配置
|
||||
|
||||
### 4. Simple AI & RAG ✅
|
||||
- ✅ 知识库问答:基于知识库的精准问答
|
||||
- ✅ 向量检索:支持多模型向量相似度搜索
|
||||
|
||||
### 5. AI 创作与多模态 ✅
|
||||
- ✅ 绘画调度:统一管理 DALL·E-3、MidJourney、Stable Diffusion 任务
|
||||
- ✅ 智能 PPT:文本生成 PPTX,支持导出图片序列
|
||||
- ✅ 多模态:文件解析(PDF/Word)、OCR识别、图像理解
|
||||
|
||||
### 6. 平台集成 ✅
|
||||
- ✅ FastGPT:深度对接 FastGPT 平台
|
||||
- ✅ Coze:深度对接 Coze 平台(SSE流式)
|
||||
- ✅ DIFY:深度对接 DIFY 平台
|
||||
|
||||
### 7. MCP 生态 ✅
|
||||
- ✅ 工具注册:`McpToolRegistry` 支持动态工具加载与调用
|
||||
- ✅ 工具模板:提供多个内置工具(ProjectScaffold、DataTransform、ApiCall、AlgorithmCompute、FileParse、HttpFetch)
|
||||
|
||||
### 8. 安全与监控 ✅
|
||||
- ✅ 安全卫士:`AiSecurityManager` 提供内容过滤、IP黑白名单、速率限制
|
||||
- ✅ 全链路监控:实时监控队列深度、模型健康度、响应时间与 Token 消耗
|
||||
- ✅ 使用统计:记录和统计AI使用情况
|
||||
|
||||
## 数据库表
|
||||
|
||||
- `ai_provider_config` - AI Provider配置表
|
||||
- `ai_provider_config_history` - AI Provider配置历史表
|
||||
- `ai_chat_record` - AI聊天记录表
|
||||
- `ai_usage_stats` - AI使用统计表
|
||||
- `ai_rating_record` - AI评价记录表
|
||||
|
||||
详细表结构请参考:`script/sql/ry_ai.sql`
|
||||
|
||||
## 模块结构
|
||||
|
||||
```
|
||||
org.dromara.ai
|
||||
├── config/ # 模块配置类
|
||||
│ ├── AiAutoConfiguration.java
|
||||
│ ├── AiGatewayProperties.java
|
||||
│ ├── AiProviderRegistrar.java
|
||||
│ └── *ProviderConfig.java (各Provider配置)
|
||||
├── controller/ # REST API控制器
|
||||
│ ├── admin/ # 管理端接口
|
||||
│ │ ├── AdminAiChatRecordController.java # 聊天记录管理接口
|
||||
│ │ ├── AdminAiUsageStatsController.java # 使用统计管理接口
|
||||
│ │ ├── AdminAiProviderController.java # Provider管理接口
|
||||
│ │ └── AdminSysModelController.java # 系统模型管理接口
|
||||
│ └── app/ # 用户端接口
|
||||
│ ├── AiChatController.java # 聊天接口
|
||||
│ ├── AiAssistantController.java # 代码助手接口
|
||||
│ ├── AiPaintingController.java # 绘画接口
|
||||
│ ├── AiImageController.java # DALL·E-3接口
|
||||
│ ├── PptController.java # PPT生成接口
|
||||
│ ├── MultimodalController.java # 多模态接口
|
||||
│ ├── SimpleAiController.java # Simple AI接口
|
||||
│ ├── FastGptController.java # FastGPT接口
|
||||
│ ├── CozeController.java # Coze接口
|
||||
│ ├── DifyController.java # DIFY接口
|
||||
│ ├── McpController.java # MCP工具接口
|
||||
│ └── QueueMetricsController.java # 队列指标接口
|
||||
├── domain/ # 数据传输对象 (DTO) 与枚举
|
||||
│ ├── dto/
|
||||
│ └── enums/
|
||||
│ └── AiProviderType.java
|
||||
├── provider/ # 模型 Provider 实现
|
||||
│ ├── AiProvider.java
|
||||
│ ├── AiProviderManager.java
|
||||
│ ├── OpenAiProvider.java
|
||||
│ ├── AzureOpenAiProvider.java
|
||||
│ ├── QwenProvider.java
|
||||
│ ├── ZhipuAiProvider.java
|
||||
│ └── *Provider.java
|
||||
├── service/ # 核心业务服务
|
||||
│ ├── AiChatService.java
|
||||
│ ├── AiChatHistoryService.java
|
||||
│ ├── AiCodingAssistantService.java
|
||||
│ ├── AiModelService.java
|
||||
│ ├── AiPaintingService.java
|
||||
│ └── SimpleAiService.java
|
||||
├── orchestration/ # AI 编排引擎
|
||||
│ └── AiOrchestrationEngine.java
|
||||
├── painting/ # 绘画服务与 Provider
|
||||
│ ├── PaintingEngineType.java
|
||||
│ ├── PaintingTask.java
|
||||
│ ├── PaintingStatus.java
|
||||
│ └── provider/
|
||||
├── platform/ # 外部平台集成
|
||||
│ ├── FastGptService.java
|
||||
│ ├── CozeService.java
|
||||
│ └── DifyClientService.java
|
||||
├── mcp/ # MCP 工具链与集成
|
||||
│ ├── McpToolRegistry.java
|
||||
│ ├── McpTool.java
|
||||
│ └── tools/
|
||||
├── queue/ # 任务队列服务
|
||||
│ └── TaskQueueService.java
|
||||
├── security/ # 安全管理器
|
||||
│ ├── AiSecurityManager.java
|
||||
│ ├── AiContentFilterService.java
|
||||
│ └── AiAccessControlService.java
|
||||
├── monitor/ # 监控指标与度量
|
||||
│ ├── AiMonitorService.java
|
||||
│ └── *Metrics.java
|
||||
├── websocket/ # WebSocket 处理器
|
||||
│ ├── ChatWebSocketConfig.java
|
||||
│ └── ChatWebSocketHandler.java
|
||||
└── error/ # 全局异常处理
|
||||
└── AiErrorHandler.java
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
在 `application.yml` 中添加以下配置:
|
||||
|
||||
```yaml
|
||||
ai:
|
||||
gateway:
|
||||
enabled: true
|
||||
auth-required: true
|
||||
base-path: /api/ai
|
||||
provider:
|
||||
timeout-ms: 60000
|
||||
retry: 1
|
||||
|
||||
ruoyi:
|
||||
ai:
|
||||
enabled: true
|
||||
external-ai:
|
||||
openai:
|
||||
base-url: https://api.openai.com
|
||||
api-key: sk-xxxx
|
||||
azure-openai:
|
||||
endpoint: https://your-endpoint.openai.azure.com
|
||||
api-key: your-api-key
|
||||
api-version: 2023-12-01-preview
|
||||
qwen:
|
||||
base-url: https://dashscope.aliyuncs.com/api/v1
|
||||
api-key: sk-xxxx
|
||||
zhipu:
|
||||
base-url: https://open.bigmodel.cn/api/paas/v4
|
||||
api-key: sk-xxxx
|
||||
sd:
|
||||
base-url: http://localhost:7860
|
||||
midjourney:
|
||||
base-url: https://mj.example.com
|
||||
token: mj-xxxx
|
||||
ocr:
|
||||
base-url: https://ocr.example.com
|
||||
platform:
|
||||
fastgpt:
|
||||
base-url: https://fastgpt.example.com
|
||||
token: fgpt-xxxx
|
||||
coze:
|
||||
base-url: https://coze.example.com
|
||||
token: coze-xxxx
|
||||
dify:
|
||||
base-url: https://dify.example.com
|
||||
token: dify-xxxx
|
||||
```
|
||||
|
||||
## API接口
|
||||
|
||||
### 聊天接口 (`/ai/chat`)
|
||||
|
||||
#### 同步聊天
|
||||
- `POST /ai/chat/sync` - 同步聊天(返回完整响应)
|
||||
|
||||
#### 异步聊天
|
||||
- `POST /ai/chat/async` - 异步聊天(返回任务ID)
|
||||
|
||||
#### 流式聊天
|
||||
- `POST /ai/chat/stream` - SSE流式聊天
|
||||
- `POST /ai/chat/stream-chunks` - 流式聊天(分块返回)
|
||||
|
||||
#### 批量聊天
|
||||
- `POST /ai/chat/batch` - 批量聊天请求
|
||||
|
||||
#### 聊天历史
|
||||
- `GET /ai/chat/history/{sessionId}` - 获取聊天历史
|
||||
- `DELETE /ai/chat/history/{sessionId}` - 清除聊天历史
|
||||
|
||||
#### 会话统计
|
||||
- `GET /ai/chat/stats/{sessionId}` - 获取会话统计
|
||||
|
||||
### AI代码助手接口 (`/ai/assistant`)
|
||||
|
||||
- `POST /ai/assistant/analyze` - 代码分析
|
||||
- `POST /ai/assistant/scaffold` - 生成项目脚手架
|
||||
|
||||
### Provider管理接口 (`/ai/provider`)
|
||||
|
||||
- `GET /ai/provider/list` - 列出所有Provider
|
||||
- `GET /ai/provider/models` - 列出所有模型
|
||||
- `GET /ai/provider/default` - 获取默认Provider
|
||||
- `POST /ai/provider/setDefault` - 设置默认Provider
|
||||
- `GET /ai/provider/stats/system` - 系统统计
|
||||
- `GET /ai/provider/stats/providers` - Provider统计
|
||||
- `POST /ai/provider/stats/reset` - 重置统计
|
||||
|
||||
### Simple AI接口 (`/ai`)
|
||||
|
||||
- `POST /ai/ask` - 知识库问答
|
||||
- `POST /ai/search` - 向量搜索
|
||||
- `GET /ai/stats/{knowledgeId}` - 知识库统计
|
||||
- `GET /ai/model/check/{modelName}` - 检查模型可用性
|
||||
- `GET /ai/models` - 获取可用模型列表
|
||||
- `GET /ai/health` - 健康检查
|
||||
|
||||
### 绘画接口 (`/ai/painting`)
|
||||
|
||||
- `POST /ai/painting/task` - 创建绘画任务
|
||||
- `GET /ai/painting/status/{taskId}` - 查询任务状态
|
||||
- `GET /ai/painting/stream/{taskId}` - SSE流式获取任务进度
|
||||
|
||||
### DALL·E-3接口
|
||||
|
||||
- `POST /dall3` - 文生图
|
||||
- `POST /dall3/edit` - 图片编辑(image+mask)
|
||||
- `POST /dall3/variations` - 图片变体
|
||||
|
||||
### 智能PPT接口 (`/ppt`)
|
||||
|
||||
- `POST /ppt/generate` - 生成PPTX文件
|
||||
- `POST /ppt/export/pngs` - 导出PNG序列
|
||||
|
||||
### 多模态接口 (`/multimodal`)
|
||||
|
||||
- `POST /multimodal/extract` - 文件解析(PDF/Word等)
|
||||
- `POST /multimodal/ocr` - OCR识别
|
||||
- `POST /multimodal/analyze/text` - 文本分析
|
||||
- `POST /multimodal/analyze/image` - 图像识别(占位)
|
||||
- `POST /multimodal/caption` - 图生文(占位)
|
||||
|
||||
### 平台集成接口
|
||||
|
||||
#### FastGPT (`/ai/fastgpt`)
|
||||
- `POST /ai/fastgpt/knowledge/search` - 知识搜索
|
||||
- `POST /ai/fastgpt/workflow/run` - 运行工作流
|
||||
- `GET /ai/fastgpt/context/{sessionId}` - 获取上下文
|
||||
|
||||
#### Coze (`/ai/coze`)
|
||||
- `POST /ai/coze/stream` - SSE流式对话
|
||||
|
||||
#### DIFY (`/ai/dify`)
|
||||
- `GET /ai/dify/apps` - 获取应用列表
|
||||
- `POST /ai/dify/apps` - 创建/更新应用
|
||||
|
||||
### MCP工具接口 (`/ai/mcp`)
|
||||
|
||||
- `GET /ai/mcp/tools` - 获取工具列表
|
||||
- `POST /ai/mcp/invoke/{name}` - 调用工具
|
||||
|
||||
### 队列指标接口 (`/ai/queue`)
|
||||
|
||||
- `GET /ai/queue/metrics` - 获取队列指标
|
||||
|
||||
## WebSocket实时通道
|
||||
|
||||
### 聊天WebSocket
|
||||
- **端点**: `ws://localhost:8080/ws/ai/chat`
|
||||
- **协议**: JSON格式 `AiChatRequest`,支持实时双向对话
|
||||
|
||||
## 依赖模块
|
||||
|
||||
- `ruoyi-common-core` - 通用核心工具
|
||||
- `ruoyi-common-web` - Web通用组件
|
||||
- `ruoyi-common-redis` - Redis缓存
|
||||
- `ruoyi-common-security` - 安全组件
|
||||
- `ruoyi-common-log` - 日志组件
|
||||
- `ruoyi-common-mybatis` - MyBatis组件
|
||||
- `ruoyi-knowledge` - 知识库模块(RAG增强,可选)
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 聊天与流式对话
|
||||
|
||||
```bash
|
||||
# 同步聊天
|
||||
curl -X POST http://localhost:8080/api/ai/chat/sync \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"你好"}],"sessionId":"session_123"}'
|
||||
|
||||
# 流式聊天
|
||||
curl -X POST http://localhost:8080/api/ai/chat/stream \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"你好"}],"stream":true,"sessionId":"session_123"}'
|
||||
|
||||
# 获取聊天历史
|
||||
curl -X GET "http://localhost:8080/api/ai/chat/history/session_123?limit=50" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### 2. AI代码助手
|
||||
|
||||
```bash
|
||||
# 代码分析
|
||||
curl -X POST http://localhost:8080/api/ai/assistant/analyze \
|
||||
-d 'code=public void test(){...}&model=gpt-4'
|
||||
|
||||
# 生成脚手架
|
||||
curl -X POST http://localhost:8080/api/ai/assistant/scaffold \
|
||||
-d 'appType=SpringBoot&options={"db":"mysql"}'
|
||||
```
|
||||
|
||||
### 3. Simple AI (RAG)
|
||||
|
||||
```bash
|
||||
# 知识库问答
|
||||
curl -X POST "http://localhost:8080/api/ai/ask?knowledgeId=kb_123&question=合同流程是怎样的"
|
||||
|
||||
# 向量搜索
|
||||
curl -X POST "http://localhost:8080/api/ai/search?knowledgeId=kb_123&queryText=条款&topK=3"
|
||||
```
|
||||
|
||||
### 4. AI绘画任务
|
||||
|
||||
```bash
|
||||
# 创建绘画任务
|
||||
curl -X POST http://localhost:8080/api/ai/painting/task \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"engine":"DALL_E_3","prompt":"未来城市","params":{"size":"1024x1024"}}'
|
||||
|
||||
# 查询任务状态
|
||||
curl http://localhost:8080/api/ai/painting/status/{taskId}
|
||||
```
|
||||
|
||||
### 5. 智能PPT生成
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/ai/ppt/generate?content=第一页标题\n内容1\n第二页标题\n内容2"
|
||||
```
|
||||
|
||||
### 6. MCP工具调用
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/ai/mcp/invoke/api_call \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"url":"https://httpbin.org/get"}'
|
||||
```
|
||||
|
||||
## 安全与合规
|
||||
|
||||
`AiSecurityManager` 默认开启,提供以下保护:
|
||||
|
||||
- **API密钥验证**:校验请求中的 `apiKey`
|
||||
- **IP访问控制**:基于策略拦截非法IP
|
||||
- **内容过滤**:输入/输出双向敏感词过滤(Moderate策略)
|
||||
- **资源限额**:基于Token数与模型复杂度的动态配额扣除
|
||||
- **速率限制**:防止API滥用
|
||||
|
||||
## 监控与统计
|
||||
|
||||
### 监控指标
|
||||
- 队列深度监控
|
||||
- 模型健康度监控
|
||||
- 响应时间监控
|
||||
- Token消耗监控
|
||||
- 错误率监控
|
||||
|
||||
### 使用统计
|
||||
- 用户使用统计(按用户、Provider、模型)
|
||||
- 请求次数统计
|
||||
- 成功率统计
|
||||
- 成本统计
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **密钥安全**:生产环境务必通过环境变量注入 API Key,不要将密钥写入配置文件
|
||||
2. **流式代理**:Nginx/Gateway 需配置 `proxy_buffering off` 以支持 SSE
|
||||
3. **依赖服务**:Redis 必须可用,用于存储任务状态与缓存
|
||||
4. **性能优化**:建议对热点数据使用Redis缓存
|
||||
5. **错误处理**:模块提供自动降级和熔断机制,但建议监控错误率
|
||||
6. **并发控制**:Provider支持最大并发数配置,合理设置以避免资源耗尽
|
||||
|
||||
## 扩展开发
|
||||
|
||||
### 添加新的Provider
|
||||
|
||||
1. 实现 `AiProvider` 接口
|
||||
2. 创建对应的配置类(如 `XxxProviderConfig.java`)
|
||||
3. 在 `AiProviderRegistrar` 中注册Provider
|
||||
4. 在 `AiProviderType` 枚举中添加新的Provider类型
|
||||
|
||||
### 添加新的MCP工具
|
||||
|
||||
1. 实现 `McpTool` 接口
|
||||
2. 在 `AiProviderRegistrar` 中注册工具
|
||||
3. 工具会自动出现在工具列表中
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [API详细文档](docs/API.md)
|
||||
- [配置说明](docs/CONFIG.md)
|
||||
- [集成指南](docs/INTEGRATION.md)
|
||||
- [AI创作系统开发指南](#ruoyi-ai-创作系统开发指南)
|
||||
|
||||
---
|
||||
|
||||
# RuoYi-AI 创作系统开发指南
|
||||
|
||||
本指南介绍 `ruoyi-ai` 模块中的 AI 创作能力,包括绘画引擎接入、智能 PPT 生成、多模态内容理解,以及统一的任务流与接口规范。
|
||||
|
||||
> 该文档同时覆盖平台深度集成(FastGPT/Coze/DIFY)、MCP生态扩展、RAG增强、队列与长任务监控、质量与性能保障等内容,便于端到端落地与运维。
|
||||
|
||||
## 模块概览
|
||||
|
||||
### 绘画引擎集成(DALL·E-3 / MidJourney / Stable Diffusion)
|
||||
- 统一引擎枚举:`painting/PaintingEngineType.java`
|
||||
- 任务模型:`painting/PaintingTask.java`
|
||||
- 任务状态:`painting/PaintingStatus.java`
|
||||
- 引擎适配:
|
||||
- DALL·E-3:`painting/provider/DallE3Provider.java`
|
||||
- MidJourney:`painting/provider/MidJourneyProvider.java`
|
||||
- Stable Diffusion:`painting/provider/StableDiffusionProvider.java`
|
||||
- 统一服务:`service/AiPaintingService.java`
|
||||
- 控制器:`controller/AiPaintingController.java`
|
||||
|
||||
### 智能 PPT 生成
|
||||
- 控制器:`controller/PptController.java`
|
||||
- 能力:按文本生成 PPTX、导出每页 PNG 序列、主题占位与扩展点
|
||||
|
||||
### 多模态内容理解
|
||||
- 控制器:`controller/MultimodalController.java`
|
||||
- 能力:文件解析(Tika)、文本分析、OCR占位、图像识别占位、图生文占位
|
||||
|
||||
### DALL·E-3 扩展
|
||||
- 控制器:`controller/AiImageController.java`
|
||||
- 能力:文生图、编辑(image+mask)、变体(variations)
|
||||
|
||||
### 平台接入与统一API
|
||||
- FastGPT:服务 `platform/FastGptService.java`,接口 `controller/FastGptController.java`
|
||||
- Coze:服务 `platform/CozeService.java`,接口 `controller/CozeController.java`(SSE流式)
|
||||
- DIFY:服务 `platform/DifyClientService.java`,接口 `controller/DifyController.java`
|
||||
|
||||
### MCP生态扩展
|
||||
- 工具注册:`mcp/McpToolRegistry.java`(动态加载/版本管理/热重载)
|
||||
- 工具模板:`mcp/tools/*.java`(≥5个:ProjectScaffold/DataTransform/ApiCall/AlgorithmCompute/FileParse/HttpFetch)
|
||||
- 控制器:`controller/McpController.java`(工具列表与调用)
|
||||
|
||||
### 统一模型调度与降级
|
||||
- 管理器:`provider/AiProviderManager.java`(模型优先→性能优选→近似轮询、自动降级)
|
||||
- 管理接口:`controller/admin/AdminAiProviderController.java`(列表/模型/默认/统计)
|
||||
|
||||
### 队列与长任务
|
||||
- 服务:`queue/TaskQueueService.java`(优先级队列、任务状态与指标)
|
||||
- 指标接口:`controller/QueueMetricsController.java`
|
||||
|
||||
## 配置项
|
||||
|
||||
### OpenAI
|
||||
- `ruoyi.ai.external-ai.openai.base-url`(默认 `https://api.openai.com`)
|
||||
- `ruoyi.ai.external-ai.openai.api-key`
|
||||
|
||||
### Azure OpenAI
|
||||
- `ruoyi.ai.external-ai.azure-openai.endpoint`
|
||||
- `ruoyi.ai.external-ai.azure-openai.api-key`
|
||||
- `ruoyi.ai.external-ai.azure-openai.api-version`
|
||||
|
||||
### MidJourney
|
||||
- `ruoyi.ai.external-ai.midjourney.base-url`
|
||||
- `ruoyi.ai.external-ai.midjourney.token`
|
||||
|
||||
### Stable Diffusion
|
||||
- `ruoyi.ai.external-ai.sd.base-url`(例如 `http://localhost:7860`)
|
||||
|
||||
### OCR
|
||||
- `ruoyi.ai.ocr.base-url`
|
||||
|
||||
### 平台
|
||||
- `ruoyi.ai.platform.fastgpt.base-url`、`ruoyi.ai.platform.fastgpt.token`
|
||||
- `ruoyi.ai.platform.coze.base-url`、`ruoyi.ai.platform.coze.token`
|
||||
- `ruoyi.ai.platform.dify.base-url`、`ruoyi.ai.platform.dify.token`
|
||||
|
||||
## API 速览
|
||||
|
||||
### 绘画任务统一接口(REST + SSE)
|
||||
- 创建任务:`POST /ai/painting/task`
|
||||
- body:`{"engine":"DALL_E_3","prompt":"海边日落","params":{"size":"1024x1024"}}`
|
||||
- 返回:`{"taskId":"..."}`(任务 ID)
|
||||
- 查询状态:`GET /ai/painting/status/{taskId}`
|
||||
- 进度流:`GET /ai/painting/stream/{taskId}`(SSE,[DONE] 结束)
|
||||
|
||||
### DALL·E-3
|
||||
- 文生图:`POST /dall3`(`prompt`,`size`)
|
||||
- 编辑:`POST /dall3/edit`(`prompt`,`size`,`image`,`mask?`)
|
||||
- 变体:`POST /dall3/variations`(`size`,`image`)
|
||||
|
||||
### 智能 PPT
|
||||
- 生成 PPTX:`POST /ppt/generate`(按行拆分内容为多页)
|
||||
- 导出 PNG 序列:`POST /ppt/export/pngs`(返回 `data:image/png;base64,...` 列表)
|
||||
|
||||
### 多模态
|
||||
- 文件解析:`POST /multimodal/extract`(`multipart/form-data:file`)
|
||||
- OCR识别:`POST /multimodal/ocr`(需配置 OCR 服务)
|
||||
- 文本分析:`POST /multimodal/analyze/text`(返回 `title/summary/keywords`)
|
||||
- 图像识别占位:`POST /multimodal/analyze/image`
|
||||
- 图生文占位:`POST /multimodal/caption`
|
||||
|
||||
## 流式与实时
|
||||
|
||||
- **SSE**:`/ai/painting/stream/{taskId}` 推送任务状态与进度;统一以 `[DONE]` 结束
|
||||
- **WebSocket**:模块已提供聊天通道 `ws/ai/chat`;如需绘画通道,可参考该模式新增 `/ws/ai/painting`
|
||||
|
||||
## 任务与历史
|
||||
|
||||
- 任务创建后进入线程池异步执行,状态与结果存储于 Redis:键 `ai:painting:task:{taskId}`
|
||||
- 可基于该结构扩展作品历史管理、检索与归档
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- **引擎负载均衡**:为 Provider 增加实例列表与健康检查,依据权重分配
|
||||
- **任务队列**:接入消息队列(如 Kafka/RabbitMQ)实现更大的并发与可靠性
|
||||
- **权限与用量**:接入 `sa-token` 与用量计费,限制频率与配额
|
||||
- **监控与审计**:统一接入日志、指标与告警,便于运维与合规
|
||||
- **RAG增强**:在 `ruoyi-knowledge` 实现 `VectorDbClient` 接入层(Milvus/Weaviate/Qdrant),集成 BGE-large-zh-v1.5 批量嵌入与缓存,混合检索(关键词+向量)召回≥90%
|
||||
- **队列看板**:在管理端提供队列深度/处理速率可视化与失败重试控制
|
||||
|
||||
## 测试与质量
|
||||
|
||||
- **单元测试**:建议使用 MockWebServer 模拟外部 API,覆盖率目标 ≥ 80%
|
||||
- **性能**:核心 API 响应时间 < 500ms(外部调用除外),并发可通过线程池与队列扩展至 1000+
|
||||
- **压测与限流**:模拟 1000+ 并发,结合令牌桶限流,P99 延迟控制在 2s 以内
|
||||
- **可用性**:关键服务可用性目标 ≥ 99.9%,结合自动降级与熔断策略
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
|
||||
<description>
|
||||
AI模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- HTTP 客户端 -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp-sse</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON 解析 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 配置处理 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 知识库模块依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-knowledge</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
<version>2.9.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
// springdoc group
|
||||
// Use class name to avoid hard dependency if springdoc is absent at compile-time
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AiGatewayProperties.class)
|
||||
public class AiApiConfig {
|
||||
|
||||
@Bean
|
||||
public Object aiGroupedOpenApi() {
|
||||
try {
|
||||
Class<?> groupedOpenApiClass = Class.forName("org.springdoc.core.models.GroupedOpenApi");
|
||||
return groupedOpenApiClass
|
||||
.getMethod("builder")
|
||||
.invoke(null)
|
||||
.getClass()
|
||||
.getMethod("group", String.class)
|
||||
.invoke(
|
||||
groupedOpenApiClass.getMethod("builder").invoke(null),
|
||||
"AI"
|
||||
)
|
||||
.getClass()
|
||||
.getMethod("packagesToScan", String[].class)
|
||||
.invoke(
|
||||
groupedOpenApiClass.getMethod("builder").invoke(null)
|
||||
.getClass().getMethod("group", String.class)
|
||||
.invoke(groupedOpenApiClass.getMethod("builder").invoke(null), "AI"),
|
||||
(Object) new String[]{"org.dromara.ai.api"}
|
||||
)
|
||||
.getClass()
|
||||
.getMethod("build")
|
||||
.invoke(
|
||||
groupedOpenApiClass.getMethod("builder").invoke(null)
|
||||
.getClass().getMethod("group", String.class)
|
||||
.invoke(groupedOpenApiClass.getMethod("builder").invoke(null), "AI")
|
||||
.getClass().getMethod("packagesToScan", String[].class)
|
||||
.invoke(
|
||||
groupedOpenApiClass.getMethod("builder").invoke(null)
|
||||
.getClass().getMethod("group", String.class)
|
||||
.invoke(groupedOpenApiClass.getMethod("builder").invoke(null), "AI"),
|
||||
(Object) new String[]{"org.dromara.ai.api"}
|
||||
)
|
||||
);
|
||||
} catch (Throwable ignore) {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 简化的AI自动配置类
|
||||
* 主要用于扫描AI模块的组件
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "org.dromara.ai")
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class AiAutoConfiguration {
|
||||
|
||||
public AiAutoConfiguration() {
|
||||
log.info("AI模块已启用");
|
||||
}
|
||||
}
|
||||
+532
@@ -0,0 +1,532 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* AI配置管理服务
|
||||
* 支持动态配置和热更新
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AiConfigurationManager {
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
// 配置存储
|
||||
private final Map<String, AiConfiguration> configurations = new ConcurrentHashMap<>();
|
||||
|
||||
// 配置监听器
|
||||
private final Map<String, Consumer<AiConfiguration>> configListeners = new ConcurrentHashMap<>();
|
||||
|
||||
// 配置版本
|
||||
private final Map<String, Long> configVersions = new ConcurrentHashMap<>();
|
||||
|
||||
// 配置历史
|
||||
private final Map<String, Map<Long, AiConfiguration>> configHistory = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* AI配置类
|
||||
*/
|
||||
public static class AiConfiguration {
|
||||
private String key;
|
||||
private Object value;
|
||||
private String description;
|
||||
private ConfigType type;
|
||||
private boolean hotReloadable;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
private String updatedBy;
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
// Getters and Setters
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public AiConfiguration setKey(String key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public AiConfiguration setValue(Object value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public AiConfiguration setDescription(String description) {
|
||||
this.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConfigType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public AiConfiguration setType(ConfigType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isHotReloadable() {
|
||||
return hotReloadable;
|
||||
}
|
||||
|
||||
public AiConfiguration setHotReloadable(boolean hotReloadable) {
|
||||
this.hotReloadable = hotReloadable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public AiConfiguration setCreateTime(LocalDateTime createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public AiConfiguration setUpdateTime(LocalDateTime updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUpdatedBy() {
|
||||
return updatedBy;
|
||||
}
|
||||
|
||||
public AiConfiguration setUpdatedBy(String updatedBy) {
|
||||
this.updatedBy = updatedBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public AiConfiguration setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置类型枚举
|
||||
*/
|
||||
public enum ConfigType {
|
||||
PROVIDER_CONFIG, // Provider配置
|
||||
MODEL_CONFIG, // 模型配置
|
||||
SECURITY_CONFIG, // 安全配置
|
||||
MONITOR_CONFIG, // 监控配置
|
||||
SYSTEM_CONFIG // 系统配置
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置变更事件
|
||||
*/
|
||||
public static class ConfigChangeEvent {
|
||||
private String key;
|
||||
private Object oldValue;
|
||||
private Object newValue;
|
||||
private ConfigType type;
|
||||
private LocalDateTime changeTime;
|
||||
private String changedBy;
|
||||
|
||||
public ConfigChangeEvent(String key, Object oldValue, Object newValue, ConfigType type, String changedBy) {
|
||||
this.key = key;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
this.type = type;
|
||||
this.changeTime = LocalDateTime.now();
|
||||
this.changedBy = changedBy;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Object getOldValue() {
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
public Object getNewValue() {
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public ConfigType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public LocalDateTime getChangeTime() {
|
||||
return changeTime;
|
||||
}
|
||||
|
||||
public String getChangedBy() {
|
||||
return changedBy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用启动后初始化
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void initialize() {
|
||||
log.info("初始化AI配置管理器");
|
||||
|
||||
// 加载默认配置
|
||||
loadDefaultConfigurations();
|
||||
|
||||
// 启动配置监控
|
||||
startConfigMonitoring();
|
||||
|
||||
log.info("AI配置管理器初始化完成,已加载{}个配置项", configurations.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return 配置值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getConfig(String key) {
|
||||
AiConfiguration config = configurations.get(key);
|
||||
return config != null ? (T) config.getValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置(带默认值)
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param defaultValue 默认值
|
||||
* @return 配置值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getConfig(String key, T defaultValue) {
|
||||
AiConfiguration config = configurations.get(key);
|
||||
return config != null ? (T) config.getValue() : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
* @param type 配置类型
|
||||
* @param updatedBy 更新者
|
||||
*/
|
||||
public void setConfig(String key, Object value, ConfigType type, String updatedBy) {
|
||||
setConfig(key, value, type, true, null, updatedBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置(完整参数)
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param value 配置值
|
||||
* @param type 配置类型
|
||||
* @param hotReloadable 是否支持热更新
|
||||
* @param description 配置描述
|
||||
* @param updatedBy 更新者
|
||||
*/
|
||||
public void setConfig(String key, Object value, ConfigType type, boolean hotReloadable,
|
||||
String description, String updatedBy) {
|
||||
AiConfiguration oldConfig = configurations.get(key);
|
||||
Object oldValue = oldConfig != null ? oldConfig.getValue() : null;
|
||||
|
||||
AiConfiguration newConfig = new AiConfiguration()
|
||||
.setKey(key)
|
||||
.setValue(value)
|
||||
.setType(type)
|
||||
.setHotReloadable(hotReloadable)
|
||||
.setDescription(description)
|
||||
.setUpdatedBy(updatedBy)
|
||||
.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
if (oldConfig == null) {
|
||||
newConfig.setCreateTime(LocalDateTime.now());
|
||||
} else {
|
||||
newConfig.setCreateTime(oldConfig.getCreateTime());
|
||||
}
|
||||
|
||||
configurations.put(key, newConfig);
|
||||
|
||||
// 更新版本
|
||||
long newVersion = configVersions.getOrDefault(key, 0L) + 1;
|
||||
configVersions.put(key, newVersion);
|
||||
|
||||
// 保存历史
|
||||
configHistory.computeIfAbsent(key, k -> new ConcurrentHashMap<>())
|
||||
.put(newVersion, newConfig);
|
||||
|
||||
// 触发配置变更事件
|
||||
ConfigChangeEvent event = new ConfigChangeEvent(key, oldValue, value, type, updatedBy);
|
||||
eventPublisher.publishEvent(event);
|
||||
|
||||
// 通知监听器
|
||||
Consumer<AiConfiguration> listener = configListeners.get(key);
|
||||
if (listener != null && hotReloadable) {
|
||||
try {
|
||||
listener.accept(newConfig);
|
||||
log.info("配置热更新成功: {} = {}", key, value);
|
||||
} catch (Exception e) {
|
||||
log.error("配置热更新失败: {}", key, e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("配置已更新: {} = {} (版本: {})", key, value, newVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除配置
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param updatedBy 操作者
|
||||
*/
|
||||
public void removeConfig(String key, String updatedBy) {
|
||||
AiConfiguration config = configurations.remove(key);
|
||||
if (config != null) {
|
||||
// 触发配置变更事件
|
||||
ConfigChangeEvent event = new ConfigChangeEvent(key, config.getValue(), null, config.getType(), updatedBy);
|
||||
eventPublisher.publishEvent(event);
|
||||
|
||||
log.info("配置已删除: {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册配置监听器
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param listener 监听器
|
||||
*/
|
||||
public void addConfigListener(String key, Consumer<AiConfiguration> listener) {
|
||||
configListeners.put(key, listener);
|
||||
log.debug("已注册配置监听器: {}", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除配置监听器
|
||||
*
|
||||
* @param key 配置键
|
||||
*/
|
||||
public void removeConfigListener(String key) {
|
||||
configListeners.remove(key);
|
||||
log.debug("已移除配置监听器: {}", key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有配置
|
||||
*
|
||||
* @return 配置映射
|
||||
*/
|
||||
public Map<String, AiConfiguration> getAllConfigurations() {
|
||||
return new ConcurrentHashMap<>(configurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的配置
|
||||
*
|
||||
* @param type 配置类型
|
||||
* @return 配置映射
|
||||
*/
|
||||
public Map<String, AiConfiguration> getConfigurationsByType(ConfigType type) {
|
||||
Map<String, AiConfiguration> result = new ConcurrentHashMap<>();
|
||||
configurations.forEach((key, config) -> {
|
||||
if (config.getType() == type) {
|
||||
result.put(key, config);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置历史
|
||||
*
|
||||
* @param key 配置键
|
||||
* @return 配置历史
|
||||
*/
|
||||
public Map<Long, AiConfiguration> getConfigHistory(String key) {
|
||||
return configHistory.getOrDefault(key, new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚配置到指定版本
|
||||
*
|
||||
* @param key 配置键
|
||||
* @param version 版本号
|
||||
* @param updatedBy 操作者
|
||||
*/
|
||||
public void rollbackConfig(String key, long version, String updatedBy) {
|
||||
Map<Long, AiConfiguration> history = configHistory.get(key);
|
||||
if (history == null || !history.containsKey(version)) {
|
||||
throw new IllegalArgumentException("配置版本不存在: " + key + " v" + version);
|
||||
}
|
||||
|
||||
AiConfiguration historicalConfig = history.get(version);
|
||||
setConfig(key, historicalConfig.getValue(), historicalConfig.getType(),
|
||||
historicalConfig.isHotReloadable(), historicalConfig.getDescription(), updatedBy);
|
||||
|
||||
log.info("配置已回滚: {} 到版本 {}", key, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载所有配置
|
||||
*/
|
||||
public void reloadAllConfigurations() {
|
||||
log.info("开始重新加载所有配置");
|
||||
|
||||
configurations.forEach((key, config) -> {
|
||||
if (config.isHotReloadable()) {
|
||||
Consumer<AiConfiguration> listener = configListeners.get(key);
|
||||
if (listener != null) {
|
||||
try {
|
||||
listener.accept(config);
|
||||
log.debug("配置重新加载成功: {}", key);
|
||||
} catch (Exception e) {
|
||||
log.error("配置重新加载失败: {}", key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
log.info("所有配置重新加载完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载默认配置
|
||||
*/
|
||||
private void loadDefaultConfigurations() {
|
||||
// Provider配置
|
||||
setConfig("ai.provider.openai.enabled", true, ConfigType.PROVIDER_CONFIG, true,
|
||||
"OpenAI Provider启用状态", "system");
|
||||
setConfig("ai.provider.openai.timeout", 30000, ConfigType.PROVIDER_CONFIG, true,
|
||||
"OpenAI Provider超时时间(ms)", "system");
|
||||
setConfig("ai.provider.openai.retry.max", 3, ConfigType.PROVIDER_CONFIG, true,
|
||||
"OpenAI Provider最大重试次数", "system");
|
||||
|
||||
// 模型配置
|
||||
setConfig("ai.model.default", "gpt-3.5-turbo", ConfigType.MODEL_CONFIG, true,
|
||||
"默认AI模型", "system");
|
||||
setConfig("ai.model.temperature.default", 0.7, ConfigType.MODEL_CONFIG, true,
|
||||
"默认温度参数", "system");
|
||||
setConfig("ai.model.max_tokens.default", 2048, ConfigType.MODEL_CONFIG, true,
|
||||
"默认最大token数", "system");
|
||||
|
||||
// 安全配置
|
||||
setConfig("ai.security.content_filter.enabled", true, ConfigType.SECURITY_CONFIG, true,
|
||||
"内容过滤启用状态", "system");
|
||||
setConfig("ai.security.rate_limit.enabled", true, ConfigType.SECURITY_CONFIG, true,
|
||||
"速率限制启用状态", "system");
|
||||
setConfig("ai.security.rate_limit.requests_per_minute", 60, ConfigType.SECURITY_CONFIG, true,
|
||||
"每分钟请求限制", "system");
|
||||
|
||||
// 监控配置
|
||||
setConfig("ai.monitor.enabled", true, ConfigType.MONITOR_CONFIG, true,
|
||||
"监控启用状态", "system");
|
||||
setConfig("ai.monitor.metrics.retention_days", 30, ConfigType.MONITOR_CONFIG, true,
|
||||
"监控数据保留天数", "system");
|
||||
setConfig("ai.monitor.health_check.interval", 60, ConfigType.MONITOR_CONFIG, true,
|
||||
"健康检查间隔(秒)", "system");
|
||||
|
||||
// 系统配置
|
||||
setConfig("ai.system.thread_pool.core_size", 10, ConfigType.SYSTEM_CONFIG, true,
|
||||
"线程池核心大小", "system");
|
||||
setConfig("ai.system.thread_pool.max_size", 50, ConfigType.SYSTEM_CONFIG, true,
|
||||
"线程池最大大小", "system");
|
||||
setConfig("ai.system.cache.enabled", true, ConfigType.SYSTEM_CONFIG, true,
|
||||
"缓存启用状态", "system");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动配置监控
|
||||
*/
|
||||
private void startConfigMonitoring() {
|
||||
// 定期检查配置变更
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
// 这里可以实现从外部数据源检查配置变更
|
||||
// 例如数据库、配置中心等
|
||||
log.debug("配置监控检查完成");
|
||||
} catch (Exception e) {
|
||||
log.error("配置监控检查失败", e);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
|
||||
// 定期清理历史配置
|
||||
scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
cleanupConfigHistory();
|
||||
} catch (Exception e) {
|
||||
log.error("配置历史清理失败", e);
|
||||
}
|
||||
}, 1, 24, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理配置历史
|
||||
*/
|
||||
private void cleanupConfigHistory() {
|
||||
int maxHistoryVersions = getConfig("ai.config.history.max_versions", 10);
|
||||
|
||||
configHistory.forEach((key, history) -> {
|
||||
if (history.size() > maxHistoryVersions) {
|
||||
// 保留最新的版本
|
||||
history.entrySet().stream()
|
||||
.sorted(Map.Entry.<Long, AiConfiguration>comparingByKey().reversed())
|
||||
.skip(maxHistoryVersions)
|
||||
.map(Map.Entry::getKey)
|
||||
.forEach(history::remove);
|
||||
}
|
||||
});
|
||||
|
||||
log.debug("配置历史清理完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁资源
|
||||
*/
|
||||
public void destroy() {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
log.info("AI配置管理器已销毁");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(prefix = "ai.gateway")
|
||||
public class AiGatewayProperties {
|
||||
private boolean enabled = true;
|
||||
private boolean authRequired = true;
|
||||
private String basePath = "/api/ai";
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isAuthRequired() {
|
||||
return authRequired;
|
||||
}
|
||||
|
||||
public void setAuthRequired(boolean authRequired) {
|
||||
this.authRequired = authRequired;
|
||||
}
|
||||
|
||||
public String getBasePath() {
|
||||
return basePath;
|
||||
}
|
||||
|
||||
public void setBasePath(String basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProvider;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AiProviderRegistrar {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
private final List<AiProvider> providers;
|
||||
private final org.dromara.ai.mcp.McpToolRegistry registry;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
for (AiProvider provider : providers) {
|
||||
try {
|
||||
provider.initialize(Collections.emptyMap());
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("已注册AI提供商: {}", provider.getProviderName());
|
||||
} catch (Exception e) {
|
||||
log.error("注册AI提供商失败: {}", provider.getProviderName(), e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
registry.register(new org.dromara.ai.mcp.tools.ProjectScaffoldTool());
|
||||
registry.register(new org.dromara.ai.mcp.tools.DataTransformTool());
|
||||
registry.register(new org.dromara.ai.mcp.tools.ApiCallTool());
|
||||
registry.register(new org.dromara.ai.mcp.tools.AlgorithmComputeTool());
|
||||
registry.register(new org.dromara.ai.mcp.tools.FileParseTool());
|
||||
registry.register(new org.dromara.ai.mcp.tools.HttpFetchTool());
|
||||
} catch (Exception e) {
|
||||
log.error("注册MCP工具失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.AzureOpenAiProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.external-ai.azure", name = "enabled", havingValue = "true")
|
||||
public class AzureOpenAiProviderConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.azure.api-key:}")
|
||||
private String apiKey;
|
||||
@Value("${ruoyi.ai.external-ai.azure.endpoint:}")
|
||||
private String endpoint;
|
||||
@Value("${ruoyi.ai.external-ai.azure.deployment-name:}")
|
||||
private String deploymentName;
|
||||
@Value("${ruoyi.ai.external-ai.azure.api-version:2023-12-01-preview}")
|
||||
private String apiVersion;
|
||||
@Value("${ruoyi.ai.providers.azure-openai.supported-models[0]:gpt-35-turbo}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.providers.azure-openai.supported-models[1]:gpt-4}")
|
||||
private String supportedModel1;
|
||||
@Value("${ruoyi.ai.providers.azure-openai.default-model:gpt-35-turbo}")
|
||||
private String defaultModel;
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank() || endpoint == null || endpoint.isBlank() || deploymentName == null || deploymentName.isBlank()) {
|
||||
log.warn("Azure OpenAI 配置不完整,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
AzureOpenAiProvider provider = new AzureOpenAiProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("endpoint", endpoint);
|
||||
cfg.put("deployment-name", deploymentName);
|
||||
cfg.put("api-version", apiVersion);
|
||||
cfg.put("default-model", defaultModel);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0, supportedModel1));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("Azure OpenAI Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.CozeProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.ai-platform.coze", name = "enabled", havingValue = "true")
|
||||
public class CozePlatformConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.ai-platform.coze.api-key:}")
|
||||
private String apiKey;
|
||||
@Value("${ruoyi.ai.ai-platform.coze.base-url:}")
|
||||
private String baseUrl;
|
||||
@Value("${ruoyi.ai.ai-platform.coze.default-bot-id:}")
|
||||
private String defaultBotId;
|
||||
@Value("${ruoyi.ai.providers.coze.supported-models[0]:coze}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank() || baseUrl == null || baseUrl.isBlank() || defaultBotId == null || defaultBotId.isBlank()) {
|
||||
log.warn("Coze 配置不完整,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
CozeProvider provider = new CozeProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("base-url", baseUrl);
|
||||
cfg.put("default-bot-id", defaultBotId);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("Coze Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.DifyProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.ai-platform.dify", name = "enabled", havingValue = "true")
|
||||
public class DifyPlatformConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.ai-platform.dify.api-key:}")
|
||||
private String apiKey;
|
||||
@Value("${ruoyi.ai.ai-platform.dify.base-url:}")
|
||||
private String baseUrl;
|
||||
@Value("${ruoyi.ai.ai-platform.dify.default-app-id:}")
|
||||
private String defaultAppId;
|
||||
@Value("${ruoyi.ai.providers.dify.supported-models[0]:dify}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank() || baseUrl == null || baseUrl.isBlank() || defaultAppId == null || defaultAppId.isBlank()) {
|
||||
log.warn("Dify 配置不完整,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
DifyProvider provider = new DifyProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("base-url", baseUrl);
|
||||
cfg.put("default-app-id", defaultAppId);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("Dify Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.FastGptProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.ai-platform.fastgpt", name = "enabled", havingValue = "true")
|
||||
public class FastGptPlatformConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.ai-platform.fastgpt.api-key:}")
|
||||
private String apiKey;
|
||||
@Value("${ruoyi.ai.ai-platform.fastgpt.base-url:}")
|
||||
private String baseUrl;
|
||||
@Value("${ruoyi.ai.ai-platform.fastgpt.default-app-id:}")
|
||||
private String defaultAppId;
|
||||
@Value("${ruoyi.ai.providers.fastgpt.supported-models[0]:fastgpt}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank() || baseUrl == null || baseUrl.isBlank() || defaultAppId == null || defaultAppId.isBlank()) {
|
||||
log.warn("FastGPT 配置不完整,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
FastGptProvider provider = new FastGptProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("base-url", baseUrl);
|
||||
cfg.put("default-app-id", defaultAppId);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("FastGPT Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.OpenAiProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.external-ai.openai", name = "enabled", havingValue = "true")
|
||||
public class OpenAiProviderConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.api-key:}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.base-url:https://api.openai.com}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.default-model:gpt-3.5-turbo}")
|
||||
private String defaultModel;
|
||||
|
||||
@Value("${ruoyi.ai.providers.openai.supported-models[0]:gpt-3.5-turbo}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.providers.openai.supported-models[1]:gpt-4}")
|
||||
private String supportedModel1;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank()) {
|
||||
log.warn("OpenAI api-key 未配置,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
OpenAiProvider provider = new OpenAiProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("base-url", baseUrl);
|
||||
cfg.put("default-model", defaultModel);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0, supportedModel1));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("OpenAI Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.QwenProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.external-ai.qwen", name = "enabled", havingValue = "true")
|
||||
public class QwenProviderConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.qwen.api-key:}")
|
||||
private String apiKey;
|
||||
@Value("${ruoyi.ai.external-ai.qwen.base-url:https://dashscope.aliyuncs.com}")
|
||||
private String baseUrl;
|
||||
@Value("${ruoyi.ai.providers.qwen.default-model:qwen-turbo}")
|
||||
private String defaultModel;
|
||||
@Value("${ruoyi.ai.providers.qwen.supported-models[0]:qwen-turbo}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.providers.qwen.supported-models[1]:qwen-plus}")
|
||||
private String supportedModel1;
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank()) {
|
||||
log.warn("Qwen api-key 未配置,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
QwenProvider provider = new QwenProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("base-url", baseUrl);
|
||||
cfg.put("default-model", defaultModel);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0, supportedModel1));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("Qwen Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.dromara.ai.config;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.provider.ZhipuAiProvider;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "ruoyi.ai.external-ai.zhipu", name = "enabled", havingValue = "true")
|
||||
public class ZhipuAiProviderConfig {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.zhipu.api-key:}")
|
||||
private String apiKey;
|
||||
@Value("${ruoyi.ai.external-ai.zhipu.base-url:https://open.bigmodel.cn}")
|
||||
private String baseUrl;
|
||||
@Value("${ruoyi.ai.providers.zhipu.default-model:glm-4}")
|
||||
private String defaultModel;
|
||||
@Value("${ruoyi.ai.providers.zhipu.supported-models[0]:glm-4}")
|
||||
private String supportedModel0;
|
||||
@Value("${ruoyi.ai.providers.zhipu.supported-models[1]:glm-3-turbo}")
|
||||
private String supportedModel1;
|
||||
@Value("${ruoyi.ai.external-ai.openai.timeout:60s}")
|
||||
private String timeout;
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (apiKey == null || apiKey.isBlank()) {
|
||||
log.warn("ZhipuAI api-key 未配置,Provider 不注册");
|
||||
return;
|
||||
}
|
||||
ZhipuAiProvider provider = new ZhipuAiProvider();
|
||||
Map<String, Object> cfg = new HashMap<>();
|
||||
cfg.put("api-key", apiKey);
|
||||
cfg.put("base-url", baseUrl);
|
||||
cfg.put("default-model", defaultModel);
|
||||
cfg.put("timeout-ms", parseTimeoutMs(timeout));
|
||||
cfg.put("supported-models", List.of(supportedModel0, supportedModel1));
|
||||
provider.initialize(cfg);
|
||||
providerManager.registerProvider(provider);
|
||||
log.info("ZhipuAI Provider 已注册");
|
||||
}
|
||||
|
||||
private int parseTimeoutMs(String t) {
|
||||
try {
|
||||
if (t == null || t.isBlank()) return 60000;
|
||||
if (t.endsWith("ms")) return Integer.parseInt(t.substring(0, t.length() - 2));
|
||||
if (t.endsWith("s")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 1000;
|
||||
if (t.endsWith("m")) return Integer.parseInt(t.substring(0, t.length() - 1)) * 60_000;
|
||||
return Integer.parseInt(t);
|
||||
} catch (Exception e) {
|
||||
return 60000;
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiChatRecordBo;
|
||||
import org.dromara.ai.domain.vo.AiChatRecordVo;
|
||||
import org.dromara.ai.service.AiChatRecordService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI聊天记录管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI聊天记录管理", description = "AI聊天记录管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat/record")
|
||||
public class AdminAiChatRecordController extends BaseController {
|
||||
|
||||
private final AiChatRecordService aiChatRecordService;
|
||||
|
||||
/**
|
||||
* 查询AI聊天记录列表
|
||||
*/
|
||||
@Operation(summary = "查询AI聊天记录列表", description = "分页查询AI聊天记录列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:chat:record:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiChatRecordVo> list(@Parameter(description = "聊天记录查询条件") AiChatRecordBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiChatRecordService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI聊天记录列表
|
||||
*/
|
||||
@Operation(summary = "导出AI聊天记录列表", description = "导出AI聊天记录到Excel")
|
||||
@SaCheckPermission("ai:chat:record:export")
|
||||
@Log(title = "AI聊天记录", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "聊天记录查询条件") AiChatRecordBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiChatRecordVo> list = aiChatRecordService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI聊天记录", AiChatRecordVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI聊天记录详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI聊天记录详情", description = "根据记录ID获取详细信息")
|
||||
@SaCheckPermission("ai:chat:record:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiChatRecordVo> getInfo(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiChatRecordService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI聊天记录
|
||||
*/
|
||||
@Operation(summary = "批量删除AI聊天记录", description = "根据ID数组批量删除AI聊天记录")
|
||||
@SaCheckPermission("ai:chat:record:remove")
|
||||
@Log(title = "AI聊天记录", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "记录ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiChatRecordService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档聊天记录(删除指定日期之前的记录)
|
||||
*/
|
||||
@Operation(summary = "归档聊天记录", description = "删除指定日期之前的聊天记录")
|
||||
@SaCheckPermission("ai:chat:record:remove")
|
||||
@Log(title = "AI聊天记录归档", businessType = BusinessType.DELETE)
|
||||
@PostMapping("/archive")
|
||||
public R<Integer> archive(@Parameter(description = "归档日期(删除此日期之前的记录)", required = true)
|
||||
@RequestParam java.util.Date beforeDate) {
|
||||
Integer count = aiChatRecordService.archiveChatRecords(beforeDate);
|
||||
return R.ok(count);
|
||||
}
|
||||
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiPaintingFavoriteBo;
|
||||
import org.dromara.ai.domain.vo.AiPaintingFavoriteVo;
|
||||
import org.dromara.ai.service.AiPaintingFavoriteService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI绘画作品收藏管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI绘画作品收藏管理", description = "AI绘画作品收藏管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/painting/favorite")
|
||||
public class AdminAiPaintingFavoriteController extends BaseController {
|
||||
|
||||
private final AiPaintingFavoriteService aiPaintingFavoriteService;
|
||||
|
||||
/**
|
||||
* 查询AI绘画作品收藏列表
|
||||
*/
|
||||
@Operation(summary = "查询AI绘画作品收藏列表", description = "分页查询AI绘画作品收藏列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:painting:favorite:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiPaintingFavoriteVo> list(@Parameter(description = "收藏查询条件") AiPaintingFavoriteBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiPaintingFavoriteService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI绘画作品收藏列表
|
||||
*/
|
||||
@Operation(summary = "导出AI绘画作品收藏列表", description = "导出AI绘画作品收藏到Excel")
|
||||
@SaCheckPermission("ai:painting:favorite:export")
|
||||
@Log(title = "AI绘画作品收藏", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "收藏查询条件") AiPaintingFavoriteBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiPaintingFavoriteVo> list = aiPaintingFavoriteService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI绘画作品收藏", AiPaintingFavoriteVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI绘画作品收藏详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI绘画作品收藏详情", description = "根据收藏ID获取详细信息")
|
||||
@SaCheckPermission("ai:painting:favorite:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiPaintingFavoriteVo> getInfo(@Parameter(description = "收藏ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiPaintingFavoriteService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI绘画作品收藏
|
||||
*/
|
||||
@Operation(summary = "批量删除AI绘画作品收藏", description = "根据ID数组批量删除AI绘画作品收藏")
|
||||
@SaCheckPermission("ai:painting:favorite:remove")
|
||||
@Log(title = "AI绘画作品收藏", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "收藏ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiPaintingFavoriteService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiPaintingRecordBo;
|
||||
import org.dromara.ai.domain.vo.AiPaintingRecordVo;
|
||||
import org.dromara.ai.service.AiPaintingRecordService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI绘画历史记录管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI绘画历史记录管理", description = "AI绘画历史记录管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/painting/record")
|
||||
public class AdminAiPaintingRecordController extends BaseController {
|
||||
|
||||
private final AiPaintingRecordService aiPaintingRecordService;
|
||||
|
||||
/**
|
||||
* 查询AI绘画历史记录列表
|
||||
*/
|
||||
@Operation(summary = "查询AI绘画历史记录列表", description = "分页查询AI绘画历史记录列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:painting:record:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiPaintingRecordVo> list(@Parameter(description = "绘画历史记录查询条件") AiPaintingRecordBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiPaintingRecordService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI绘画历史记录列表
|
||||
*/
|
||||
@Operation(summary = "导出AI绘画历史记录列表", description = "导出AI绘画历史记录到Excel")
|
||||
@SaCheckPermission("ai:painting:record:export")
|
||||
@Log(title = "AI绘画历史记录", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "绘画历史记录查询条件") AiPaintingRecordBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiPaintingRecordVo> list = aiPaintingRecordService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI绘画历史记录", AiPaintingRecordVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI绘画历史记录详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI绘画历史记录详情", description = "根据记录ID获取详细信息")
|
||||
@SaCheckPermission("ai:painting:record:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiPaintingRecordVo> getInfo(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiPaintingRecordService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据任务ID获取记录
|
||||
*/
|
||||
@Operation(summary = "根据任务ID获取记录", description = "根据任务ID获取绘画记录")
|
||||
@SaCheckPermission("ai:painting:record:query")
|
||||
@GetMapping("/task/{taskId}")
|
||||
public R<AiPaintingRecordVo> getByTaskId(@Parameter(description = "任务ID", required = true)
|
||||
@NotNull(message = "任务ID不能为空")
|
||||
@PathVariable String taskId) {
|
||||
return R.ok(aiPaintingRecordService.queryByTaskId(taskId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI绘画历史记录
|
||||
*/
|
||||
@Operation(summary = "批量删除AI绘画历史记录", description = "根据ID数组批量删除AI绘画历史记录")
|
||||
@SaCheckPermission("ai:painting:record:remove")
|
||||
@Log(title = "AI绘画历史记录", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "记录ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiPaintingRecordService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成分享码
|
||||
*/
|
||||
@Operation(summary = "生成分享码", description = "为指定的绘画作品生成分享码")
|
||||
@SaCheckPermission("ai:painting:record:edit")
|
||||
@Log(title = "AI绘画历史记录分享", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/{id}/share")
|
||||
public R<Map<String, String>> generateShareCode(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "记录ID不能为空")
|
||||
@PathVariable Long id) {
|
||||
String shareCode = aiPaintingRecordService.generateShareCode(id);
|
||||
return R.ok(Map.of("shareCode", shareCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消分享
|
||||
*/
|
||||
@Operation(summary = "取消分享", description = "取消指定绘画作品的分享")
|
||||
@SaCheckPermission("ai:painting:record:edit")
|
||||
@Log(title = "AI绘画历史记录取消分享", businessType = BusinessType.UPDATE)
|
||||
@DeleteMapping("/{id}/share")
|
||||
public R<Void> cancelShare(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "记录ID不能为空")
|
||||
@PathVariable Long id) {
|
||||
return toAjax(aiPaintingRecordService.cancelShare(id));
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiPaintingTemplateBo;
|
||||
import org.dromara.ai.domain.vo.AiPaintingTemplateVo;
|
||||
import org.dromara.ai.service.AiPaintingTemplateService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI绘画参数模板管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI绘画参数模板管理", description = "AI绘画参数模板管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/painting/template")
|
||||
public class AdminAiPaintingTemplateController extends BaseController {
|
||||
|
||||
private final AiPaintingTemplateService aiPaintingTemplateService;
|
||||
|
||||
/**
|
||||
* 查询AI绘画参数模板列表
|
||||
*/
|
||||
@Operation(summary = "查询AI绘画参数模板列表", description = "分页查询AI绘画参数模板列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:painting:template:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiPaintingTemplateVo> list(@Parameter(description = "模板查询条件") AiPaintingTemplateBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiPaintingTemplateService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI绘画参数模板列表
|
||||
*/
|
||||
@Operation(summary = "导出AI绘画参数模板列表", description = "导出AI绘画参数模板到Excel")
|
||||
@SaCheckPermission("ai:painting:template:export")
|
||||
@Log(title = "AI绘画参数模板", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "模板查询条件") AiPaintingTemplateBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiPaintingTemplateVo> list = aiPaintingTemplateService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI绘画参数模板", AiPaintingTemplateVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI绘画参数模板详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI绘画参数模板详情", description = "根据模板ID获取详细信息")
|
||||
@SaCheckPermission("ai:painting:template:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiPaintingTemplateVo> getInfo(@Parameter(description = "模板ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiPaintingTemplateService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI绘画参数模板
|
||||
*/
|
||||
@Operation(summary = "新增AI绘画参数模板", description = "新增AI绘画参数模板")
|
||||
@SaCheckPermission("ai:painting:template:add")
|
||||
@Log(title = "AI绘画参数模板", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<Void> add(@Valid @RequestBody AiPaintingTemplateBo bo) {
|
||||
return toAjax(aiPaintingTemplateService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI绘画参数模板
|
||||
*/
|
||||
@Operation(summary = "修改AI绘画参数模板", description = "修改AI绘画参数模板")
|
||||
@SaCheckPermission("ai:painting:template:edit")
|
||||
@Log(title = "AI绘画参数模板", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public R<Void> edit(@Valid @RequestBody AiPaintingTemplateBo bo) {
|
||||
return toAjax(aiPaintingTemplateService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI绘画参数模板
|
||||
*/
|
||||
@Operation(summary = "批量删除AI绘画参数模板", description = "根据ID数组批量删除AI绘画参数模板")
|
||||
@SaCheckPermission("ai:painting:template:remove")
|
||||
@Log(title = "AI绘画参数模板", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "模板ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiPaintingTemplateService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiProviderConfigBo;
|
||||
import org.dromara.ai.domain.vo.AiProviderConfigVo;
|
||||
import org.dromara.ai.service.AiProviderConfigService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI Provider配置管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI Provider配置管理", description = "AI Provider配置管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/provider/config")
|
||||
public class AdminAiProviderConfigController extends BaseController {
|
||||
|
||||
private final AiProviderConfigService aiProviderConfigService;
|
||||
|
||||
/**
|
||||
* 查询AI Provider配置列表
|
||||
*/
|
||||
@Operation(summary = "查询AI Provider配置列表", description = "分页查询AI Provider配置列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:provider:config:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiProviderConfigVo> list(@Parameter(description = "Provider配置查询条件") AiProviderConfigBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiProviderConfigService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI Provider配置列表
|
||||
*/
|
||||
@Operation(summary = "导出AI Provider配置列表", description = "导出AI Provider配置到Excel")
|
||||
@SaCheckPermission("ai:provider:config:export")
|
||||
@Log(title = "AI Provider配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "Provider配置查询条件") AiProviderConfigBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiProviderConfigVo> list = aiProviderConfigService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI Provider配置", AiProviderConfigVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI Provider配置详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI Provider配置详情", description = "根据配置ID获取详细信息")
|
||||
@SaCheckPermission("ai:provider:config:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiProviderConfigVo> getInfo(@Parameter(description = "配置ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiProviderConfigService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI Provider配置
|
||||
*/
|
||||
@Operation(summary = "新增AI Provider配置", description = "新增AI Provider配置")
|
||||
@SaCheckPermission("ai:provider:config:add")
|
||||
@Log(title = "AI Provider配置", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<Void> add(@Validated @RequestBody AiProviderConfigBo bo) {
|
||||
return toAjax(aiProviderConfigService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI Provider配置
|
||||
*/
|
||||
@Operation(summary = "修改AI Provider配置", description = "修改AI Provider配置")
|
||||
@SaCheckPermission("ai:provider:config:edit")
|
||||
@Log(title = "AI Provider配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public R<Void> edit(@Validated @RequestBody AiProviderConfigBo bo) {
|
||||
return toAjax(aiProviderConfigService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI Provider配置
|
||||
*/
|
||||
@Operation(summary = "批量删除AI Provider配置", description = "根据ID数组批量删除AI Provider配置")
|
||||
@SaCheckPermission("ai:provider:config:remove")
|
||||
@Log(title = "AI Provider配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "配置ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiProviderConfigService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用Provider配置
|
||||
*/
|
||||
@Operation(summary = "启用/禁用Provider配置", description = "启用或禁用AI Provider配置")
|
||||
@SaCheckPermission("ai:provider:config:edit")
|
||||
@Log(title = "AI Provider配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/changeEnabled")
|
||||
public R<Void> changeEnabled(@Parameter(description = "配置ID", required = true)
|
||||
@NotNull(message = "配置ID不能为空")
|
||||
@RequestParam Long id,
|
||||
@Parameter(description = "启用状态(0=禁用 1=启用)", required = true)
|
||||
@NotNull(message = "启用状态不能为空")
|
||||
@RequestParam Integer enabled) {
|
||||
return toAjax(aiProviderConfigService.changeEnabled(id, enabled));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Provider名称查询配置
|
||||
*/
|
||||
@Operation(summary = "根据Provider名称查询配置", description = "根据Provider名称获取配置信息")
|
||||
@SaCheckPermission("ai:provider:config:query")
|
||||
@GetMapping("/name/{providerName}")
|
||||
public R<AiProviderConfigVo> getByProviderName(@Parameter(description = "Provider名称", required = true)
|
||||
@PathVariable String providerName) {
|
||||
return R.ok(aiProviderConfigService.queryByProviderName(providerName));
|
||||
}
|
||||
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiProviderConfigHistoryBo;
|
||||
import org.dromara.ai.domain.vo.AiProviderConfigHistoryVo;
|
||||
import org.dromara.ai.service.AiProviderConfigHistoryService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI Provider配置历史管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI Provider配置历史管理", description = "AI Provider配置历史管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/provider/config/history")
|
||||
public class AdminAiProviderConfigHistoryController extends BaseController {
|
||||
|
||||
private final AiProviderConfigHistoryService aiProviderConfigHistoryService;
|
||||
|
||||
/**
|
||||
* 查询AI Provider配置历史列表
|
||||
*/
|
||||
@Operation(summary = "查询AI Provider配置历史列表", description = "分页查询AI Provider配置历史列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:provider:config:history:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiProviderConfigHistoryVo> list(@Parameter(description = "配置历史查询条件") AiProviderConfigHistoryBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiProviderConfigHistoryService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI Provider配置历史列表
|
||||
*/
|
||||
@Operation(summary = "导出AI Provider配置历史列表", description = "导出AI Provider配置历史到Excel")
|
||||
@SaCheckPermission("ai:provider:config:history:export")
|
||||
@Log(title = "AI Provider配置历史", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "配置历史查询条件") AiProviderConfigHistoryBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiProviderConfigHistoryVo> list = aiProviderConfigHistoryService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI Provider配置历史", AiProviderConfigHistoryVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI Provider配置历史详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI Provider配置历史详情", description = "根据记录ID获取详细信息")
|
||||
@SaCheckPermission("ai:provider:config:history:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiProviderConfigHistoryVo> getInfo(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiProviderConfigHistoryService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置ID查询历史记录列表
|
||||
*/
|
||||
@Operation(summary = "根据配置ID查询历史记录", description = "根据配置ID查询该配置的所有历史记录")
|
||||
@SaCheckPermission("ai:provider:config:history:query")
|
||||
@GetMapping("/config/{configId}")
|
||||
public R<List<AiProviderConfigHistoryVo>> getHistoryByConfigId(@Parameter(description = "配置ID", required = true)
|
||||
@NotNull(message = "配置ID不能为空")
|
||||
@PathVariable Long configId) {
|
||||
return R.ok(aiProviderConfigHistoryService.queryByConfigId(configId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI Provider配置历史
|
||||
*/
|
||||
@Operation(summary = "批量删除AI Provider配置历史", description = "根据ID数组批量删除AI Provider配置历史")
|
||||
@SaCheckPermission("ai:provider:config:history:remove")
|
||||
@Log(title = "AI Provider配置历史", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "记录ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiProviderConfigHistoryService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.monitor.AiMonitorService;
|
||||
import org.dromara.ai.provider.AiProvider;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI提供商管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI提供商管理", description = "AI提供商管理和监控接口")
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/provider")
|
||||
public class AdminAiProviderController extends BaseController {
|
||||
|
||||
private final AiProviderManager providerManager;
|
||||
private final AiMonitorService monitorService;
|
||||
|
||||
/**
|
||||
* 获取所有提供商列表
|
||||
*/
|
||||
@Operation(summary = "提供商列表", description = "获取所有已注册的AI服务提供商信息")
|
||||
@SaCheckPermission("ai:provider:list")
|
||||
@GetMapping("/list")
|
||||
public R<List<Map<String, Object>>> list() {
|
||||
try {
|
||||
List<Map<String, Object>> items = providerManager.getAllProviders().stream().map(p -> Map.of(
|
||||
"name", p.getProviderName(),
|
||||
"type", p.getProviderType().getCode(),
|
||||
"version", p.getVersion(),
|
||||
"available", p.isAvailable(),
|
||||
"statistics", p.getStatistics()
|
||||
)).collect(Collectors.toList());
|
||||
log.debug("获取提供商列表,共{}个提供商", items.size());
|
||||
return R.ok(items);
|
||||
} catch (Exception e) {
|
||||
log.error("获取提供商列表失败", e);
|
||||
return R.fail("获取提供商列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支持的模型列表
|
||||
*/
|
||||
@Operation(summary = "支持的模型列表", description = "获取所有提供商支持的所有模型信息")
|
||||
@SaCheckPermission("ai:provider:list")
|
||||
@GetMapping("/models")
|
||||
public R<List<Object>> models() {
|
||||
try {
|
||||
List<Object> models = providerManager.getAllSupportedModels().stream().map(m -> Map.of(
|
||||
"id", m.getId(),
|
||||
"name", m.getName(),
|
||||
"provider", m.getProvider(),
|
||||
"capabilities", m.getCapabilities(),
|
||||
"type", m.getType() != null ? m.getType().getCode() : null
|
||||
)).collect(Collectors.toList());
|
||||
log.debug("获取模型列表,共{}个模型", models.size());
|
||||
return R.ok(models);
|
||||
} catch (Exception e) {
|
||||
log.error("获取模型列表失败", e);
|
||||
return R.fail("获取模型列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认提供商
|
||||
*/
|
||||
@Operation(summary = "获取默认提供商", description = "获取当前系统默认的AI服务提供商")
|
||||
@SaCheckPermission("ai:provider:query")
|
||||
@GetMapping("/default")
|
||||
public R<Map<String, Object>> getDefault() {
|
||||
try {
|
||||
AiProvider p = providerManager.getDefaultProvider();
|
||||
if (p == null) {
|
||||
log.warn("未设置默认提供商");
|
||||
return R.fail("NO_DEFAULT_PROVIDER");
|
||||
}
|
||||
return R.ok(Map.of("name", p.getProviderName(), "version", p.getVersion(), "type", p.getProviderType().getCode()));
|
||||
} catch (Exception e) {
|
||||
log.error("获取默认提供商失败", e);
|
||||
return R.fail("获取默认提供商失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认提供商
|
||||
*/
|
||||
@Operation(summary = "设置默认提供商", description = "设置系统默认的AI服务提供商")
|
||||
@SaCheckPermission("ai:provider:edit")
|
||||
@PostMapping("/setDefault")
|
||||
public R<Void> setDefault(@Parameter(description = "提供商名称") @RequestParam String name) {
|
||||
try {
|
||||
providerManager.setDefaultProvider(name);
|
||||
log.info("设置默认提供商: {}", name);
|
||||
return R.ok();
|
||||
} catch (Exception e) {
|
||||
log.error("设置默认提供商失败: {}", name, e);
|
||||
return R.fail("设置默认提供商失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统统计信息
|
||||
*/
|
||||
@Operation(summary = "系统统计", description = "获取系统级别的AI使用统计信息")
|
||||
@SaCheckPermission("ai:provider:query")
|
||||
@GetMapping("/stats/system")
|
||||
public R<Map<String, Object>> getSystemStats() {
|
||||
try {
|
||||
Map<String, Object> stats = monitorService.getSystemStats();
|
||||
return R.ok(stats);
|
||||
} catch (Exception e) {
|
||||
log.error("获取系统统计失败", e);
|
||||
return R.fail("获取系统统计失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取各Provider统计信息
|
||||
*/
|
||||
@Operation(summary = "Provider统计", description = "获取所有Provider的使用统计信息")
|
||||
@SaCheckPermission("ai:provider:query")
|
||||
@GetMapping("/stats/providers")
|
||||
public R<List<Map<String, Object>>> getProviderStats() {
|
||||
try {
|
||||
List<Map<String, Object>> stats = monitorService.getAllProviderStats();
|
||||
return R.ok(stats);
|
||||
} catch (Exception e) {
|
||||
log.error("获取Provider统计失败", e);
|
||||
return R.fail("获取Provider统计失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
@Operation(summary = "重置统计", description = "重置所有提供商的统计信息")
|
||||
@SaCheckPermission("ai:provider:edit")
|
||||
@PostMapping("/stats/reset")
|
||||
public R<Void> resetStats() {
|
||||
try {
|
||||
monitorService.resetStats();
|
||||
providerManager.resetAllStatistics();
|
||||
log.info("重置统计信息成功");
|
||||
return R.ok();
|
||||
} catch (Exception e) {
|
||||
log.error("重置统计失败", e);
|
||||
return R.fail("重置统计失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiRatingRecordBo;
|
||||
import org.dromara.ai.domain.vo.AiRatingRecordVo;
|
||||
import org.dromara.ai.service.AiRatingRecordService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI评价记录管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI评价记录管理", description = "AI评价记录管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/rating/record")
|
||||
public class AdminAiRatingRecordController extends BaseController {
|
||||
|
||||
private final AiRatingRecordService aiRatingRecordService;
|
||||
|
||||
/**
|
||||
* 查询AI评价记录列表
|
||||
*/
|
||||
@Operation(summary = "查询AI评价记录列表", description = "分页查询AI评价记录列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:rating:record:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiRatingRecordVo> list(@Parameter(description = "评价记录查询条件") AiRatingRecordBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiRatingRecordService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI评价记录列表
|
||||
*/
|
||||
@Operation(summary = "导出AI评价记录列表", description = "导出AI评价记录到Excel")
|
||||
@SaCheckPermission("ai:rating:record:export")
|
||||
@Log(title = "AI评价记录", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "评价记录查询条件") AiRatingRecordBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiRatingRecordVo> list = aiRatingRecordService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI评价记录", AiRatingRecordVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI评价记录详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI评价记录详情", description = "根据记录ID获取详细信息")
|
||||
@SaCheckPermission("ai:rating:record:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiRatingRecordVo> getInfo(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiRatingRecordService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI评价记录
|
||||
*/
|
||||
@Operation(summary = "批量删除AI评价记录", description = "根据ID数组批量删除AI评价记录")
|
||||
@SaCheckPermission("ai:rating:record:remove")
|
||||
@Log(title = "AI评价记录", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "记录ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiRatingRecordService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平均评分
|
||||
*/
|
||||
@Operation(summary = "获取平均评分", description = "获取指定Provider的平均评分")
|
||||
@SaCheckPermission("ai:rating:record:query")
|
||||
@GetMapping("/average/{providerName}")
|
||||
public R<Map<String, Object>> getAverageRating(@Parameter(description = "Provider名称", required = true)
|
||||
@PathVariable String providerName) {
|
||||
Double averageRating = aiRatingRecordService.getAverageRating(providerName);
|
||||
return R.ok(Map.of("providerName", providerName, "averageRating", averageRating));
|
||||
}
|
||||
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiSensitiveWordBo;
|
||||
import org.dromara.ai.domain.vo.AiSensitiveWordVo;
|
||||
import org.dromara.ai.service.AiSensitiveWordService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI敏感词管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI敏感词管理", description = "AI敏感词管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/sensitive/word")
|
||||
public class AdminAiSensitiveWordController extends BaseController {
|
||||
|
||||
private final AiSensitiveWordService aiSensitiveWordService;
|
||||
|
||||
/**
|
||||
* 查询AI敏感词列表
|
||||
*/
|
||||
@Operation(summary = "查询AI敏感词列表", description = "分页查询AI敏感词列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:sensitive:word:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiSensitiveWordVo> list(@Parameter(description = "敏感词查询条件") AiSensitiveWordBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiSensitiveWordService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI敏感词列表
|
||||
*/
|
||||
@Operation(summary = "导出AI敏感词列表", description = "导出AI敏感词到Excel")
|
||||
@SaCheckPermission("ai:sensitive:word:export")
|
||||
@Log(title = "AI敏感词", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "敏感词查询条件") AiSensitiveWordBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiSensitiveWordVo> list = aiSensitiveWordService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI敏感词", AiSensitiveWordVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI敏感词详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI敏感词详情", description = "根据ID获取详细信息")
|
||||
@SaCheckPermission("ai:sensitive:word:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiSensitiveWordVo> getInfo(@Parameter(description = "敏感词ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiSensitiveWordService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI敏感词
|
||||
*/
|
||||
@Operation(summary = "新增AI敏感词", description = "新增AI敏感词")
|
||||
@SaCheckPermission("ai:sensitive:word:add")
|
||||
@Log(title = "AI敏感词", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<Void> add(@Valid @RequestBody AiSensitiveWordBo bo) {
|
||||
return toAjax(aiSensitiveWordService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI敏感词
|
||||
*/
|
||||
@Operation(summary = "修改AI敏感词", description = "修改AI敏感词")
|
||||
@SaCheckPermission("ai:sensitive:word:edit")
|
||||
@Log(title = "AI敏感词", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public R<Void> edit(@Valid @RequestBody AiSensitiveWordBo bo) {
|
||||
return toAjax(aiSensitiveWordService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI敏感词
|
||||
*/
|
||||
@Operation(summary = "批量删除AI敏感词", description = "根据ID数组批量删除AI敏感词")
|
||||
@SaCheckPermission("ai:sensitive:word:remove")
|
||||
@Log(title = "AI敏感词", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "敏感词ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiSensitiveWordService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用敏感词
|
||||
*/
|
||||
@Operation(summary = "启用/禁用敏感词", description = "启用或禁用敏感词")
|
||||
@SaCheckPermission("ai:sensitive:word:edit")
|
||||
@Log(title = "AI敏感词", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/changeEnabled")
|
||||
public R<Void> changeEnabled(@Parameter(description = "敏感词ID", required = true)
|
||||
@NotNull(message = "敏感词ID不能为空")
|
||||
@RequestParam Long id,
|
||||
@Parameter(description = "启用状态(0=禁用 1=启用)", required = true)
|
||||
@NotNull(message = "启用状态不能为空")
|
||||
@RequestParam Integer enabled) {
|
||||
return toAjax(aiSensitiveWordService.changeEnabled(id, enabled));
|
||||
}
|
||||
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiUsageStatsBo;
|
||||
import org.dromara.ai.domain.vo.AiUsageStatsVo;
|
||||
import org.dromara.ai.service.AiUsageStatsService;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI使用统计管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI使用统计管理", description = "AI使用统计管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/usage/stats")
|
||||
public class AdminAiUsageStatsController extends BaseController {
|
||||
|
||||
private final AiUsageStatsService aiUsageStatsService;
|
||||
|
||||
/**
|
||||
* 查询AI使用统计列表
|
||||
*/
|
||||
@Operation(summary = "查询AI使用统计列表", description = "分页查询AI使用统计列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:usage:stats:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiUsageStatsVo> list(@Parameter(description = "使用统计查询条件") AiUsageStatsBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiUsageStatsService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI使用统计列表
|
||||
*/
|
||||
@Operation(summary = "导出AI使用统计列表", description = "导出AI使用统计到Excel")
|
||||
@SaCheckPermission("ai:usage:stats:export")
|
||||
@Log(title = "AI使用统计", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "使用统计查询条件") AiUsageStatsBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiUsageStatsVo> list = aiUsageStatsService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI使用统计", AiUsageStatsVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI使用统计详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI使用统计详情", description = "根据统计ID获取详细信息")
|
||||
@SaCheckPermission("ai:usage:stats:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiUsageStatsVo> getInfo(@Parameter(description = "统计ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiUsageStatsService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取汇总统计
|
||||
*/
|
||||
@Operation(summary = "获取汇总统计", description = "获取指定时间范围内的汇总统计数据")
|
||||
@SaCheckPermission("ai:usage:stats:query")
|
||||
@GetMapping("/summary")
|
||||
public R<Map<String, Object>> getSummary(
|
||||
@Parameter(description = "开始日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date startDate,
|
||||
@Parameter(description = "结束日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date endDate) {
|
||||
return R.ok(aiUsageStatsService.getSummaryStats(startDate, endDate));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户统计
|
||||
*/
|
||||
@Operation(summary = "获取用户统计", description = "获取指定用户的统计数据")
|
||||
@SaCheckPermission("ai:usage:stats:query")
|
||||
@GetMapping("/user/{userId}")
|
||||
public R<Map<String, Object>> getUserStats(
|
||||
@Parameter(description = "用户ID", required = true) @PathVariable @NotNull Long userId,
|
||||
@Parameter(description = "开始日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date startDate,
|
||||
@Parameter(description = "结束日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date endDate) {
|
||||
return R.ok(aiUsageStatsService.getUserStats(userId, startDate, endDate));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Provider统计
|
||||
*/
|
||||
@Operation(summary = "获取Provider统计", description = "获取指定Provider的统计数据")
|
||||
@SaCheckPermission("ai:usage:stats:query")
|
||||
@GetMapping("/provider/{providerName}")
|
||||
public R<Map<String, Object>> getProviderStats(
|
||||
@Parameter(description = "Provider名称", required = true) @PathVariable @NotNull String providerName,
|
||||
@Parameter(description = "开始日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date startDate,
|
||||
@Parameter(description = "结束日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date endDate) {
|
||||
return R.ok(aiUsageStatsService.getProviderStats(providerName, startDate, endDate));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取趋势数据
|
||||
*/
|
||||
@Operation(summary = "获取趋势数据", description = "获取指定时间范围内的趋势数据(支持按日/周/月分组)")
|
||||
@SaCheckPermission("ai:usage:stats:query")
|
||||
@GetMapping("/trend")
|
||||
public R<List<Map<String, Object>>> getTrend(
|
||||
@Parameter(description = "开始日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date startDate,
|
||||
@Parameter(description = "结束日期") @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(required = false) Date endDate,
|
||||
@Parameter(description = "分组方式(day/week/month)") @RequestParam(defaultValue = "day") String groupBy) {
|
||||
return R.ok(aiUsageStatsService.getTrendData(startDate, endDate, groupBy));
|
||||
}
|
||||
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiUserQuotaBo;
|
||||
import org.dromara.ai.domain.vo.AiUserQuotaVo;
|
||||
import org.dromara.ai.service.AiUserQuotaService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI用户配额管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-AI用户配额管理", description = "AI用户配额管理接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/user/quota")
|
||||
public class AdminAiUserQuotaController extends BaseController {
|
||||
|
||||
private final AiUserQuotaService aiUserQuotaService;
|
||||
|
||||
/**
|
||||
* 查询AI用户配额列表
|
||||
*/
|
||||
@Operation(summary = "查询AI用户配额列表", description = "分页查询AI用户配额列表,支持多条件搜索")
|
||||
@SaCheckPermission("ai:user:quota:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<AiUserQuotaVo> list(@Parameter(description = "配额查询条件") AiUserQuotaBo bo,
|
||||
@Parameter(description = "分页参数") PageQuery pageQuery) {
|
||||
return aiUserQuotaService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出AI用户配额列表
|
||||
*/
|
||||
@Operation(summary = "导出AI用户配额列表", description = "导出AI用户配额到Excel")
|
||||
@SaCheckPermission("ai:user:quota:export")
|
||||
@Log(title = "AI用户配额", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Parameter(description = "配额查询条件") AiUserQuotaBo bo,
|
||||
@Parameter(description = "响应对象") HttpServletResponse response) {
|
||||
List<AiUserQuotaVo> list = aiUserQuotaService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "AI用户配额", AiUserQuotaVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI用户配额详细信息
|
||||
*/
|
||||
@Operation(summary = "获取AI用户配额详情", description = "根据配额ID获取详细信息")
|
||||
@SaCheckPermission("ai:user:quota:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiUserQuotaVo> getInfo(@Parameter(description = "配额ID", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiUserQuotaService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID获取配额信息
|
||||
*/
|
||||
@Operation(summary = "根据用户ID获取配额信息", description = "根据用户ID获取配额信息")
|
||||
@SaCheckPermission("ai:user:quota:query")
|
||||
@GetMapping("/user/{userId}")
|
||||
public R<AiUserQuotaVo> getByUserId(@Parameter(description = "用户ID", required = true)
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
@PathVariable Long userId) {
|
||||
return R.ok(aiUserQuotaService.queryByUserId(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增AI用户配额
|
||||
*/
|
||||
@Operation(summary = "新增AI用户配额", description = "新增AI用户配额")
|
||||
@SaCheckPermission("ai:user:quota:add")
|
||||
@Log(title = "AI用户配额", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public R<Void> add(@Valid @RequestBody AiUserQuotaBo bo) {
|
||||
return toAjax(aiUserQuotaService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改AI用户配额
|
||||
*/
|
||||
@Operation(summary = "修改AI用户配额", description = "修改AI用户配额")
|
||||
@SaCheckPermission("ai:user:quota:edit")
|
||||
@Log(title = "AI用户配额", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public R<Void> edit(@Valid @RequestBody AiUserQuotaBo bo) {
|
||||
return toAjax(aiUserQuotaService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除AI用户配额
|
||||
*/
|
||||
@Operation(summary = "批量删除AI用户配额", description = "根据ID数组批量删除AI用户配额")
|
||||
@SaCheckPermission("ai:user:quota:remove")
|
||||
@Log(title = "AI用户配额", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@Parameter(description = "配额ID数组", required = true)
|
||||
@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiUserQuotaService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置用户配额
|
||||
*/
|
||||
@Operation(summary = "重置用户配额", description = "重置用户的日配额或月配额")
|
||||
@SaCheckPermission("ai:user:quota:edit")
|
||||
@Log(title = "AI用户配额", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/reset/{userId}")
|
||||
public R<Void> resetQuota(@Parameter(description = "用户ID", required = true)
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
@PathVariable Long userId,
|
||||
@Parameter(description = "重置类型(DAILY/MONTHLY)", required = true)
|
||||
@RequestParam String resetType) {
|
||||
return toAjax(aiUserQuotaService.resetQuota(userId, resetType));
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.dromara.ai.controller.admin;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.ai.domain.dto.AiModelInfo;
|
||||
import org.dromara.ai.service.AiModelService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 系统模型管理
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "管理端-系统模型管理", description = "系统模型管理接口")
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/system/model")
|
||||
public class AdminSysModelController extends BaseController {
|
||||
|
||||
private final AiModelService aiModelService;
|
||||
|
||||
/**
|
||||
* 获取模型列表(用于前端下拉选择)
|
||||
*/
|
||||
@Operation(summary = "获取模型列表", description = "获取所有可用模型列表,用于前端下拉选择")
|
||||
@GetMapping("/modelList")
|
||||
public R<List<Map<String, Object>>> modelList() {
|
||||
List<AiModelInfo> models = aiModelService.getAllModels();
|
||||
List<Map<String, Object>> data = models.stream()
|
||||
.filter(m -> Boolean.TRUE.equals(m.getAvailable()))
|
||||
.filter(m -> m.getStatus() == null || m.getStatus() == AiModelInfo.ModelStatus.AVAILABLE)
|
||||
.map(this::toFrontendModel)
|
||||
.collect(Collectors.toList());
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类获取模型列表
|
||||
*/
|
||||
@Operation(summary = "根据分类获取模型列表", description = "根据类型或Provider分类获取模型列表")
|
||||
@GetMapping("/list")
|
||||
public R<List<Map<String, Object>>> listByCategory(
|
||||
@Parameter(description = "分类(类型或Provider)") @RequestParam(required = false) String category) {
|
||||
List<AiModelInfo> models = aiModelService.getAllModels();
|
||||
List<Map<String, Object>> data = models.stream()
|
||||
.filter(m -> {
|
||||
if (category == null || category.isBlank()) return true;
|
||||
boolean byType = m.getType() != null && category.equalsIgnoreCase(m.getType().getCode());
|
||||
boolean byProvider = m.getProvider() != null && category.equalsIgnoreCase(m.getProvider());
|
||||
return byType || byProvider;
|
||||
})
|
||||
.map(this::toFrontendModel)
|
||||
.collect(Collectors.toList());
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为前端模型格式
|
||||
*/
|
||||
private Map<String, Object> toFrontendModel(AiModelInfo m) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("modelName", m.getId());
|
||||
map.put("modelDescribe", Optional.ofNullable(m.getDisplayName()).orElse(Optional.ofNullable(m.getName()).orElse(m.getId())));
|
||||
List<String> caps = Optional.ofNullable(m.getCapabilities()).orElse(List.of())
|
||||
.stream().map(AiModelInfo.ModelCapability::getCode).collect(Collectors.toList());
|
||||
map.put("modelCapability", caps);
|
||||
List<Map<String, String>> abilities = Optional.ofNullable(m.getCapabilities()).orElse(List.of())
|
||||
.stream().map(c -> {
|
||||
Map<String, String> a = new HashMap<>();
|
||||
a.put("name", c.getCode());
|
||||
a.put("description", c.getName());
|
||||
return a;
|
||||
}).collect(Collectors.toList());
|
||||
map.put("modelAbilities", abilities);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.domain.dto.AiChatResponse;
|
||||
import org.dromara.ai.service.AiCodingAssistantService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI代码助手控制器
|
||||
* 提供代码分析和项目脚手架生成功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/ai/assistant")
|
||||
@RequiredArgsConstructor
|
||||
public class AiAssistantController {
|
||||
|
||||
private final AiCodingAssistantService assistantService;
|
||||
|
||||
/**
|
||||
* AI代码分析
|
||||
*
|
||||
* @param code 代码内容
|
||||
* @param model 模型名称(可选)
|
||||
* @param userId 用户ID(可选)
|
||||
* @param params 额外参数(可选)
|
||||
* @return 分析结果
|
||||
*/
|
||||
@PostMapping("/analyze")
|
||||
@Log(title = "AI代码分析", businessType = BusinessType.OTHER)
|
||||
public R<AiChatResponse> analyze(@RequestParam String code,
|
||||
@RequestParam(required = false) String model,
|
||||
@RequestParam(required = false) String userId,
|
||||
@RequestBody(required = false) Map<String, Object> params) {
|
||||
try {
|
||||
AiChatResponse resp = assistantService.analyzeCode(code, model, params, userId);
|
||||
return R.ok(resp);
|
||||
} catch (Exception e) {
|
||||
log.error("AI代码分析失败", e);
|
||||
return R.fail("AI代码分析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AI项目脚手架生成
|
||||
*
|
||||
* @param appType 应用类型(如SpringBoot、Vue等)
|
||||
* @param model 模型名称(可选)
|
||||
* @param userId 用户ID(可选)
|
||||
* @param options 选项参数(可选)
|
||||
* @return 生成结果
|
||||
*/
|
||||
@PostMapping("/scaffold")
|
||||
@Log(title = "AI脚手架生成", businessType = BusinessType.OTHER)
|
||||
public R<AiChatResponse> scaffold(@RequestParam String appType,
|
||||
@RequestParam(required = false) String model,
|
||||
@RequestParam(required = false) String userId,
|
||||
@RequestBody(required = false) Map<String, Object> options) {
|
||||
try {
|
||||
AiChatResponse resp = assistantService.generateScaffold(appType, options, model, userId);
|
||||
return R.ok(resp);
|
||||
} catch (Exception e) {
|
||||
log.error("AI脚手架生成失败", e);
|
||||
return R.fail("AI脚手架生成失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.domain.dto.AiChatRequest;
|
||||
import org.dromara.ai.domain.dto.AiChatResponse;
|
||||
import org.dromara.ai.service.AiChatService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* AI聊天控制器
|
||||
* 提供AI聊天相关的REST API接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/ai/chat")
|
||||
@RequiredArgsConstructor
|
||||
public class AiChatController extends BaseController {
|
||||
|
||||
private final AiChatService aiChatService;
|
||||
private final org.dromara.ai.service.AiChatHistoryService chatHistoryService;
|
||||
|
||||
/**
|
||||
* 同步聊天对话
|
||||
*
|
||||
* @param request 聊天请求
|
||||
* @return 聊天响应
|
||||
*/
|
||||
@PostMapping("/sync")
|
||||
@Log(title = "AI聊天", businessType = BusinessType.OTHER)
|
||||
public R<AiChatResponse> chat(@Validated @RequestBody AiChatRequest request) {
|
||||
log.info("接收到AI聊天请求 - 用户: {}, 模型: {}", request.getUserId(), request.getModel());
|
||||
|
||||
try {
|
||||
AiChatResponse response = aiChatService.chat(request);
|
||||
return R.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("AI聊天失败", e);
|
||||
return R.fail("AI聊天失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步聊天对话
|
||||
*
|
||||
* @param request 聊天请求
|
||||
* @return 异步聊天响应
|
||||
*/
|
||||
@PostMapping("/async")
|
||||
@Log(title = "AI异步聊天", businessType = BusinessType.OTHER)
|
||||
public R<CompletableFuture<AiChatResponse>> chatAsync(@Validated @RequestBody AiChatRequest request) {
|
||||
log.info("接收到AI异步聊天请求 - 用户: {}, 模型: {}", request.getUserId(), request.getModel());
|
||||
|
||||
try {
|
||||
CompletableFuture<AiChatResponse> future = aiChatService.chatAsync(request);
|
||||
return R.ok(future);
|
||||
} catch (Exception e) {
|
||||
log.error("AI异步聊天启动失败", e);
|
||||
return R.fail("AI异步聊天启动失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式聊天对话 (SSE)
|
||||
*
|
||||
* @param request 聊天请求
|
||||
* @return SSE流
|
||||
*/
|
||||
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@Log(title = "AI流式聊天", businessType = BusinessType.OTHER)
|
||||
public SseEmitter chatStream(@Validated @RequestBody AiChatRequest request) {
|
||||
log.info("接收到AI流式聊天请求 - 用户: {}, 模型: {}", request.getUserId(), request.getModel());
|
||||
|
||||
SseEmitter emitter = new SseEmitter(TimeUnit.MINUTES.toMillis(10)); // 10分钟超时
|
||||
|
||||
try {
|
||||
aiChatService.chatStream(request, new AiChatService.StreamCallback() {
|
||||
@Override
|
||||
public void onChunk(String chunk) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("chunk")
|
||||
.data(chunk));
|
||||
} catch (IOException e) {
|
||||
log.error("发送SSE数据块失败", e);
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AiChatResponse response) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("complete")
|
||||
.data(response));
|
||||
emitter.complete();
|
||||
} catch (IOException e) {
|
||||
log.error("发送SSE完成事件失败", e);
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
log.error("AI流式聊天失败", error);
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.name("error")
|
||||
.data("AI流式聊天失败: " + error.getMessage()));
|
||||
} catch (IOException e) {
|
||||
log.error("发送SSE错误事件失败", e);
|
||||
}
|
||||
emitter.completeWithError(error);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI流式聊天启动失败", e);
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式聊天对话 (WebSocket风格的分块响应)
|
||||
*
|
||||
* @param request 聊天请求
|
||||
* @param response HTTP响应
|
||||
*/
|
||||
@PostMapping("/stream-chunks")
|
||||
@Log(title = "AI流式聊天分块", businessType = BusinessType.OTHER)
|
||||
public void chatStreamChunks(@Validated @RequestBody AiChatRequest request,
|
||||
HttpServletResponse response) {
|
||||
log.info("接收到AI流式聊天分块请求 - 用户: {}, 模型: {}", request.getUserId(), request.getModel());
|
||||
|
||||
response.setContentType("text/plain; charset=utf-8");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Connection", "keep-alive");
|
||||
|
||||
try {
|
||||
aiChatService.chatStream(request, new AiChatService.StreamCallback() {
|
||||
@Override
|
||||
public void onChunk(String chunk) {
|
||||
try {
|
||||
response.getWriter().write(chunk);
|
||||
response.getWriter().flush();
|
||||
} catch (IOException e) {
|
||||
log.error("写入响应块失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AiChatResponse chatResponse) {
|
||||
try {
|
||||
response.getWriter().write("\n[DONE]\n");
|
||||
response.getWriter().flush();
|
||||
response.getWriter().close();
|
||||
} catch (IOException e) {
|
||||
log.error("完成响应写入失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
try {
|
||||
response.getWriter().write("\n[ERROR]: " + error.getMessage() + "\n");
|
||||
response.getWriter().flush();
|
||||
response.getWriter().close();
|
||||
} catch (IOException e) {
|
||||
log.error("写入错误响应失败", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("AI流式聊天分块启动失败", e);
|
||||
try {
|
||||
response.getWriter().write("\n[ERROR]: " + e.getMessage() + "\n");
|
||||
response.getWriter().flush();
|
||||
response.getWriter().close();
|
||||
} catch (IOException ioException) {
|
||||
log.error("写入启动错误响应失败", ioException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量聊天对话
|
||||
*
|
||||
* @param requests 聊天请求列表
|
||||
* @return 批量聊天响应
|
||||
*/
|
||||
@PostMapping("/batch")
|
||||
@Log(title = "AI批量聊天", businessType = BusinessType.OTHER)
|
||||
public R<List<AiChatResponse>> chatBatch(@Validated @RequestBody List<AiChatRequest> requests) {
|
||||
log.info("接收到AI批量聊天请求 - 请求数量: {}", requests.size());
|
||||
|
||||
try {
|
||||
List<CompletableFuture<AiChatResponse>> futures = aiChatService.chatBatch(requests);
|
||||
List<AiChatResponse> responses = aiChatService.waitForBatchCompletion(futures);
|
||||
return R.ok(responses);
|
||||
} catch (Exception e) {
|
||||
log.error("AI批量聊天失败", e);
|
||||
return R.fail("AI批量聊天失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天历史
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @param limit 限制数量
|
||||
* @return 聊天历史
|
||||
*/
|
||||
@GetMapping("/history/{sessionId}")
|
||||
public R<List<AiChatRequest.AiMessage>> getChatHistory(
|
||||
@PathVariable String sessionId,
|
||||
@RequestParam(defaultValue = "50") Integer limit) {
|
||||
log.info("获取聊天历史 - 会话: {}, 限制: {}", sessionId, limit);
|
||||
|
||||
try {
|
||||
List<AiChatRequest.AiMessage> history = chatHistoryService.getHistory(sessionId, limit);
|
||||
return R.ok(history);
|
||||
} catch (Exception e) {
|
||||
log.error("获取聊天历史失败", e);
|
||||
return R.fail("获取聊天历史失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除聊天历史
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/history/{sessionId}")
|
||||
@Log(title = "清除聊天历史", businessType = BusinessType.DELETE)
|
||||
public R<Void> clearChatHistory(@PathVariable String sessionId) {
|
||||
log.info("清除聊天历史 - 会话: {}", sessionId);
|
||||
|
||||
try {
|
||||
chatHistoryService.clearHistory(sessionId);
|
||||
return R.ok();
|
||||
} catch (Exception e) {
|
||||
log.error("清除聊天历史失败", e);
|
||||
return R.fail("清除聊天历史失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话统计信息
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @return 统计信息
|
||||
*/
|
||||
@GetMapping("/stats/{sessionId}")
|
||||
public R<org.dromara.ai.service.AiChatHistoryService.SessionStats> getSessionStats(@PathVariable String sessionId) {
|
||||
log.info("获取会话统计 - 会话: {}", sessionId);
|
||||
|
||||
try {
|
||||
org.dromara.ai.service.AiChatHistoryService.SessionStats stats = chatHistoryService.getSessionStats(sessionId);
|
||||
return R.ok(stats);
|
||||
} catch (Exception e) {
|
||||
log.error("获取会话统计失败", e);
|
||||
return R.fail("获取会话统计失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI图像控制器
|
||||
* 提供DALL·E-3图像生成、编辑和变体功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class AiImageController {
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.base-url:https://api.openai.com}")
|
||||
private String openaiBaseUrl;
|
||||
@Value("${ruoyi.ai.external-ai.openai.api-key:}")
|
||||
private String openaiApiKey;
|
||||
|
||||
private final OkHttpClient client = new OkHttpClient();
|
||||
|
||||
/**
|
||||
* DALL·E-3 图像生成
|
||||
*
|
||||
* @param prompt 提示词
|
||||
* @param size 分辨率 (512x512 或 1024x1024)
|
||||
* @return 生成结果
|
||||
*/
|
||||
@Operation(summary = "DALL·E-3 生成", description = "通过OpenAI DALL·E-3模型生成图像")
|
||||
@PostMapping("/dall3")
|
||||
public R<Map<String, Object>> dalle3(
|
||||
@Parameter(description = "提示词") @RequestParam @NotBlank String prompt,
|
||||
@Parameter(description = "分辨率") @RequestParam @Pattern(regexp = "^(512x512|1024x1024)$") String size) {
|
||||
try {
|
||||
log.info("DALL·E-3图像生成请求 - 提示词长度: {}, 分辨率: {}", prompt.length(), size);
|
||||
|
||||
if (openaiApiKey == null || openaiApiKey.isBlank()) {
|
||||
log.warn("OpenAI API Key未配置");
|
||||
return R.fail("OpenAI API Key未配置");
|
||||
}
|
||||
|
||||
String url = normalize(openaiBaseUrl) + "/v1/images/generations";
|
||||
String body = "{\"model\":\"dall-e-3\",\"prompt\":\"" + prompt.replace("\"", "\\\"") + "\",\"size\":\"" + size + "\"}";
|
||||
Request.Builder b = new Request.Builder().url(url)
|
||||
.post(RequestBody.create(MediaType.parse("application/json"), body.getBytes(StandardCharsets.UTF_8)))
|
||||
.header("Authorization", "Bearer " + openaiApiKey);
|
||||
try (Response resp = client.newCall(b.build()).execute()) {
|
||||
if (!resp.isSuccessful()) {
|
||||
String errorBody = resp.body() != null ? resp.body().string() : "";
|
||||
log.error("DALL·E-3 API调用失败: {} - {}", resp.code(), errorBody);
|
||||
return R.fail("OpenAI调用失败: HTTP " + resp.code());
|
||||
}
|
||||
String respBody = resp.body() != null ? resp.body().string() : "{}";
|
||||
log.info("DALL·E-3图像生成成功");
|
||||
return R.ok(Map.of("raw", respBody));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("DALL·E-3图像生成失败", e);
|
||||
return R.fail("OpenAI调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DALL·E-3 图像编辑
|
||||
*
|
||||
* @param prompt 提示词
|
||||
* @param size 分辨率 (512x512 或 1024x1024)
|
||||
* @param image 原图文件
|
||||
* @param mask 遮罩文件(可选)
|
||||
* @return 编辑结果
|
||||
*/
|
||||
@Operation(summary = "DALL·E-3 编辑", description = "上传原图与遮罩进行图像编辑")
|
||||
@PostMapping("/dall3/edit")
|
||||
public R<Map<String, Object>> dalle3Edit(
|
||||
@Parameter(description = "提示词") @RequestParam @NotBlank String prompt,
|
||||
@Parameter(description = "分辨率") @RequestParam @Pattern(regexp = "^(512x512|1024x1024)$") String size,
|
||||
@Parameter(description = "原图") @RequestParam("image") MultipartFile image,
|
||||
@Parameter(description = "遮罩") @RequestParam(value = "mask", required = false) MultipartFile mask) {
|
||||
try {
|
||||
log.info("DALL·E-3图像编辑请求 - 提示词长度: {}, 分辨率: {}, 图片: {}",
|
||||
prompt.length(), size, image.getOriginalFilename());
|
||||
|
||||
if (openaiApiKey == null || openaiApiKey.isBlank()) {
|
||||
log.warn("OpenAI API Key未配置");
|
||||
return R.fail("OpenAI API Key未配置");
|
||||
}
|
||||
|
||||
String url = normalize(openaiBaseUrl) + "/v1/images/edits";
|
||||
MultipartBody.Builder mb = new MultipartBody.Builder().setType(MultipartBody.FORM)
|
||||
.addFormDataPart("model", "dall-e-3")
|
||||
.addFormDataPart("prompt", prompt)
|
||||
.addFormDataPart("size", size)
|
||||
.addFormDataPart("image", image.getOriginalFilename(),
|
||||
RequestBody.create(MediaType.parse("application/octet-stream"), toBytes(image)));
|
||||
if (mask != null && !mask.isEmpty()) {
|
||||
mb.addFormDataPart("mask", mask.getOriginalFilename(),
|
||||
RequestBody.create(MediaType.parse("application/octet-stream"), toBytes(mask)));
|
||||
}
|
||||
Request.Builder b = new Request.Builder().url(url)
|
||||
.post(mb.build())
|
||||
.header("Authorization", "Bearer " + openaiApiKey);
|
||||
try (Response resp = client.newCall(b.build()).execute()) {
|
||||
if (!resp.isSuccessful()) {
|
||||
String errorBody = resp.body() != null ? resp.body().string() : "";
|
||||
log.error("DALL·E-3编辑API调用失败: {} - {}", resp.code(), errorBody);
|
||||
return R.fail("OpenAI调用失败: HTTP " + resp.code());
|
||||
}
|
||||
String respBody = resp.body() != null ? resp.body().string() : "{}";
|
||||
log.info("DALL·E-3图像编辑成功");
|
||||
return R.ok(Map.of("raw", respBody));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("DALL·E-3图像编辑失败", e);
|
||||
return R.fail("OpenAI调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DALL·E-3 图像变体生成
|
||||
*
|
||||
* @param size 分辨率 (512x512 或 1024x1024)
|
||||
* @param image 原图文件
|
||||
* @return 变体生成结果
|
||||
*/
|
||||
@Operation(summary = "DALL·E-3 变体", description = "上传原图生成图像变体")
|
||||
@PostMapping("/dall3/variations")
|
||||
public R<Map<String, Object>> dalle3Variations(
|
||||
@Parameter(description = "分辨率") @RequestParam @Pattern(regexp = "^(512x512|1024x1024)$") String size,
|
||||
@Parameter(description = "原图") @RequestParam("image") MultipartFile image) {
|
||||
try {
|
||||
log.info("DALL·E-3图像变体生成请求 - 分辨率: {}, 图片: {}", size, image.getOriginalFilename());
|
||||
|
||||
if (openaiApiKey == null || openaiApiKey.isBlank()) {
|
||||
log.warn("OpenAI API Key未配置");
|
||||
return R.fail("OpenAI API Key未配置");
|
||||
}
|
||||
|
||||
String url = normalize(openaiBaseUrl) + "/v1/images/variations";
|
||||
MultipartBody.Builder mb = new MultipartBody.Builder().setType(MultipartBody.FORM)
|
||||
.addFormDataPart("model", "dall-e-3")
|
||||
.addFormDataPart("size", size)
|
||||
.addFormDataPart("image", image.getOriginalFilename(),
|
||||
RequestBody.create(MediaType.parse("application/octet-stream"), toBytes(image)));
|
||||
Request.Builder b = new Request.Builder().url(url)
|
||||
.post(mb.build())
|
||||
.header("Authorization", "Bearer " + openaiApiKey);
|
||||
try (Response resp = client.newCall(b.build()).execute()) {
|
||||
if (!resp.isSuccessful()) {
|
||||
String errorBody = resp.body() != null ? resp.body().string() : "";
|
||||
log.error("DALL·E-3变体API调用失败: {} - {}", resp.code(), errorBody);
|
||||
return R.fail("OpenAI调用失败: HTTP " + resp.code());
|
||||
}
|
||||
String respBody = resp.body() != null ? resp.body().string() : "{}";
|
||||
log.info("DALL·E-3图像变体生成成功");
|
||||
return R.ok(Map.of("raw", respBody));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("DALL·E-3图像变体生成失败", e);
|
||||
return R.fail("OpenAI调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URL标准化(去除尾部斜杠)
|
||||
*/
|
||||
private String normalize(String base) {
|
||||
if (base == null || base.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
if (base.endsWith("/")) {
|
||||
return base.substring(0, base.length() - 1);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将MultipartFile转换为字节数组
|
||||
*/
|
||||
private byte[] toBytes(MultipartFile f) {
|
||||
try {
|
||||
return f.getBytes();
|
||||
} catch (Exception e) {
|
||||
log.error("转换文件为字节数组失败: {}", f.getOriginalFilename(), e);
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.painting.PaintingEngineType;
|
||||
import org.dromara.ai.painting.PaintingTask;
|
||||
import org.dromara.ai.service.AiPaintingService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI绘画控制器
|
||||
* 提供统一的AI绘画任务管理和状态查询接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ai/painting")
|
||||
public class AiPaintingController {
|
||||
|
||||
private final AiPaintingService paintingService;
|
||||
|
||||
/**
|
||||
* 创建绘画任务
|
||||
*
|
||||
* @param body 请求体,包含engine(引擎类型)、prompt(提示词)、params(参数)
|
||||
* @return 任务ID
|
||||
*/
|
||||
@Operation(summary = "创建绘画任务", description = "创建一个新的AI绘画任务,支持多种绘画引擎")
|
||||
@PostMapping("/task")
|
||||
public R<Map<String, Object>> createTask(@RequestBody Map<String, Object> body) {
|
||||
try {
|
||||
String engine = String.valueOf(body.getOrDefault("engine", "DALL_E_3"));
|
||||
String prompt = String.valueOf(body.getOrDefault("prompt", ""));
|
||||
Map<String, Object> params = (Map<String, Object>) body.getOrDefault("params", Map.of());
|
||||
|
||||
log.info("创建绘画任务 - 引擎: {}, 提示词长度: {}", engine, prompt.length());
|
||||
|
||||
PaintingEngineType type = PaintingEngineType.valueOf(engine);
|
||||
String taskId = paintingService.createTask(type, prompt, params);
|
||||
|
||||
log.info("绘画任务创建成功 - 任务ID: {}", taskId);
|
||||
return R.ok(Map.of("taskId", taskId));
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("创建绘画任务失败 - 无效的引擎类型", e);
|
||||
return R.fail("无效的引擎类型: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("创建绘画任务失败", e);
|
||||
return R.fail("创建任务失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务状态
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @return 任务状态信息
|
||||
*/
|
||||
@Operation(summary = "任务状态", description = "查询指定绘画任务的当前状态")
|
||||
@GetMapping("/status/{taskId}")
|
||||
public R<PaintingTask> status(
|
||||
@Parameter(description = "任务ID", required = true)
|
||||
@PathVariable("taskId") String taskId) {
|
||||
try {
|
||||
log.debug("查询绘画任务状态 - 任务ID: {}", taskId);
|
||||
PaintingTask task = paintingService.getTask(taskId);
|
||||
if (task == null) {
|
||||
log.warn("绘画任务不存在 - 任务ID: {}", taskId);
|
||||
return R.fail("TASK_NOT_FOUND");
|
||||
}
|
||||
return R.ok(task);
|
||||
} catch (Exception e) {
|
||||
log.error("查询绘画任务状态失败 - 任务ID: {}", taskId, e);
|
||||
return R.fail("查询任务状态失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务进度流(SSE)
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @return SSE事件流
|
||||
*/
|
||||
@Operation(summary = "进度流", description = "通过SSE实时获取绘画任务的进度更新")
|
||||
@GetMapping(value = "/stream/{taskId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter stream(
|
||||
@Parameter(description = "任务ID", required = true)
|
||||
@PathVariable("taskId") String taskId) {
|
||||
try {
|
||||
log.info("创建绘画任务进度流 - 任务ID: {}", taskId);
|
||||
SseEmitter emitter = new SseEmitter(0L);
|
||||
paintingService.addEmitter(taskId, emitter);
|
||||
return emitter;
|
||||
} catch (Exception e) {
|
||||
log.error("创建绘画任务进度流失败 - 任务ID: {}", taskId, e);
|
||||
SseEmitter emitter = new SseEmitter(0L);
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("error").data("创建进度流失败: " + e.getMessage()));
|
||||
emitter.completeWithError(e);
|
||||
} catch (Exception ex) {
|
||||
log.error("发送错误事件失败", ex);
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiPaintingFavoriteBo;
|
||||
import org.dromara.ai.domain.vo.AiPaintingFavoriteVo;
|
||||
import org.dromara.ai.service.AiPaintingFavoriteService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户端AI绘画作品收藏
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "用户端-AI绘画作品收藏", description = "用户端AI绘画作品收藏接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/app/ai/painting/favorite")
|
||||
public class AppAiPaintingFavoriteController extends BaseController {
|
||||
|
||||
private final AiPaintingFavoriteService aiPaintingFavoriteService;
|
||||
|
||||
/**
|
||||
* 查询当前用户的收藏列表
|
||||
*/
|
||||
@Operation(summary = "查询当前用户的收藏列表", description = "查询当前登录用户的绘画作品收藏列表")
|
||||
@GetMapping("/list")
|
||||
public R<List<AiPaintingFavoriteVo>> getMyFavorites() {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
List<AiPaintingFavoriteVo> list = aiPaintingFavoriteService.queryByUserId(userId);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏绘画作品
|
||||
*/
|
||||
@Operation(summary = "收藏绘画作品", description = "收藏指定的绘画作品")
|
||||
@PostMapping
|
||||
public R<Void> addFavorite(@Valid @RequestBody AiPaintingFavoriteBo bo) {
|
||||
// 自动设置用户ID
|
||||
Long userId = LoginHelper.getUserId();
|
||||
bo.setUserId(userId);
|
||||
return toAjax(aiPaintingFavoriteService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消收藏
|
||||
*/
|
||||
@Operation(summary = "取消收藏", description = "取消收藏指定的绘画作品")
|
||||
@DeleteMapping("/{paintingRecordId}")
|
||||
public R<Void> cancelFavorite(@Parameter(description = "绘画记录ID", required = true)
|
||||
@NotNull(message = "绘画记录ID不能为空")
|
||||
@PathVariable Long paintingRecordId) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
return toAjax(aiPaintingFavoriteService.cancelFavorite(userId, paintingRecordId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已收藏
|
||||
*/
|
||||
@Operation(summary = "检查是否已收藏", description = "检查当前用户是否已收藏指定的绘画作品")
|
||||
@GetMapping("/check/{paintingRecordId}")
|
||||
public R<Boolean> checkFavorite(@Parameter(description = "绘画记录ID", required = true)
|
||||
@NotNull(message = "绘画记录ID不能为空")
|
||||
@PathVariable Long paintingRecordId) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
Boolean favorited = aiPaintingFavoriteService.isFavorited(userId, paintingRecordId);
|
||||
return R.ok(favorited);
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.vo.AiPaintingRecordVo;
|
||||
import org.dromara.ai.service.AiPaintingRecordService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户端AI绘画记录
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "用户端-AI绘画记录", description = "用户端AI绘画记录接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/app/ai/painting/record")
|
||||
public class AppAiPaintingRecordController extends BaseController {
|
||||
|
||||
private final AiPaintingRecordService aiPaintingRecordService;
|
||||
|
||||
/**
|
||||
* 生成分享码
|
||||
*/
|
||||
@Operation(summary = "生成分享码", description = "为指定的绘画作品生成分享码")
|
||||
@PostMapping("/{id}/share")
|
||||
public R<Map<String, String>> generateShareCode(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "记录ID不能为空")
|
||||
@PathVariable Long id) {
|
||||
// 验证记录所有权
|
||||
AiPaintingRecordVo record = aiPaintingRecordService.queryById(id);
|
||||
if (record == null) {
|
||||
return R.fail("绘画记录不存在");
|
||||
}
|
||||
Long userId = LoginHelper.getUserId();
|
||||
if (!userId.equals(record.getUserId())) {
|
||||
return R.fail("无权操作该记录");
|
||||
}
|
||||
String shareCode = aiPaintingRecordService.generateShareCode(id);
|
||||
return R.ok(Map.of("shareCode", shareCode));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消分享
|
||||
*/
|
||||
@Operation(summary = "取消分享", description = "取消指定绘画作品的分享")
|
||||
@DeleteMapping("/{id}/share")
|
||||
public R<Void> cancelShare(@Parameter(description = "记录ID", required = true)
|
||||
@NotNull(message = "记录ID不能为空")
|
||||
@PathVariable Long id) {
|
||||
// 验证记录所有权
|
||||
AiPaintingRecordVo record = aiPaintingRecordService.queryById(id);
|
||||
if (record == null) {
|
||||
return R.fail("绘画记录不存在");
|
||||
}
|
||||
Long userId = LoginHelper.getUserId();
|
||||
if (!userId.equals(record.getUserId())) {
|
||||
return R.fail("无权操作该记录");
|
||||
}
|
||||
return toAjax(aiPaintingRecordService.cancelShare(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分享码查看作品(无需登录)
|
||||
*/
|
||||
@Operation(summary = "根据分享码查看作品", description = "根据分享码查看公开分享的绘画作品,无需登录")
|
||||
@SaIgnore
|
||||
@GetMapping("/share/{shareCode}")
|
||||
public R<AiPaintingRecordVo> getByShareCode(@Parameter(description = "分享码", required = true)
|
||||
@NotBlank(message = "分享码不能为空")
|
||||
@PathVariable String shareCode) {
|
||||
AiPaintingRecordVo record = aiPaintingRecordService.queryByShareCode(shareCode);
|
||||
if (record == null) {
|
||||
return R.fail("分享码无效或作品已取消分享");
|
||||
}
|
||||
return R.ok(record);
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiPaintingTemplateBo;
|
||||
import org.dromara.ai.domain.vo.AiPaintingTemplateVo;
|
||||
import org.dromara.ai.service.AiPaintingTemplateService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户端AI绘画参数模板
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "用户端-AI绘画参数模板", description = "用户端AI绘画参数模板接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/app/ai/painting/template")
|
||||
public class AppAiPaintingTemplateController extends BaseController {
|
||||
|
||||
private final AiPaintingTemplateService aiPaintingTemplateService;
|
||||
|
||||
/**
|
||||
* 查询可用的模板列表
|
||||
*/
|
||||
@Operation(summary = "查询可用的模板列表", description = "根据引擎类型查询可用的模板列表(公开模板 + 用户私有模板)")
|
||||
@GetMapping("/list/{engineType}")
|
||||
public R<List<AiPaintingTemplateVo>> getAvailableTemplates(@Parameter(description = "引擎类型", required = true)
|
||||
@PathVariable String engineType) {
|
||||
Long userId = null;
|
||||
try {
|
||||
userId = LoginHelper.getUserId();
|
||||
} catch (Exception e) {
|
||||
// 未登录用户只能看到公开模板
|
||||
}
|
||||
List<AiPaintingTemplateVo> list = aiPaintingTemplateService.queryAvailableTemplates(engineType, userId);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板详情
|
||||
*/
|
||||
@Operation(summary = "获取模板详情", description = "根据模板ID获取详细信息")
|
||||
@GetMapping("/{id}")
|
||||
public R<AiPaintingTemplateVo> getInfo(@Parameter(description = "模板ID", required = true)
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiPaintingTemplateService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户私有模板
|
||||
*/
|
||||
@Operation(summary = "新增用户私有模板", description = "新增用户私有绘画参数模板")
|
||||
@PostMapping
|
||||
public R<Void> add(@Valid @RequestBody AiPaintingTemplateBo bo) {
|
||||
// 自动设置用户ID
|
||||
Long userId = LoginHelper.getUserId();
|
||||
bo.setUserId(userId);
|
||||
bo.setIsPublic(0); // 用户创建的模板默认为私有
|
||||
return toAjax(aiPaintingTemplateService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户私有模板
|
||||
*/
|
||||
@Operation(summary = "修改用户私有模板", description = "修改用户私有绘画参数模板(只能修改自己的模板)")
|
||||
@PutMapping
|
||||
public R<Void> edit(@Valid @RequestBody AiPaintingTemplateBo bo) {
|
||||
// 验证模板所有权
|
||||
Long userId = LoginHelper.getUserId();
|
||||
AiPaintingTemplateVo existing = aiPaintingTemplateService.queryById(bo.getId());
|
||||
if (existing == null || !userId.equals(existing.getUserId())) {
|
||||
return R.fail("无权修改该模板");
|
||||
}
|
||||
bo.setUserId(userId); // 确保用户ID不变
|
||||
return toAjax(aiPaintingTemplateService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户私有模板
|
||||
*/
|
||||
@Operation(summary = "删除用户私有模板", description = "删除用户私有绘画参数模板(只能删除自己的模板)")
|
||||
@DeleteMapping("/{id}")
|
||||
public R<Void> remove(@Parameter(description = "模板ID", required = true)
|
||||
@PathVariable Long id) {
|
||||
// 验证模板所有权
|
||||
Long userId = LoginHelper.getUserId();
|
||||
AiPaintingTemplateVo existing = aiPaintingTemplateService.queryById(id);
|
||||
if (existing == null || !userId.equals(existing.getUserId())) {
|
||||
return R.fail("无权删除该模板");
|
||||
}
|
||||
return toAjax(aiPaintingTemplateService.deleteWithValidByIds(Arrays.asList(id), true));
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.ai.domain.bo.AiRatingRecordBo;
|
||||
import org.dromara.ai.domain.vo.AiRatingRecordVo;
|
||||
import org.dromara.ai.service.AiRatingRecordService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* AI评价记录(用户端)
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "用户端-AI评价记录", description = "AI评价记录接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/app/ai/rating")
|
||||
public class AppAiRatingRecordController extends BaseController {
|
||||
|
||||
private final AiRatingRecordService aiRatingRecordService;
|
||||
|
||||
/**
|
||||
* 提交评价
|
||||
*/
|
||||
@Operation(summary = "提交评价", description = "用户对AI回复进行评价")
|
||||
@Log(title = "AI评价", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/submit")
|
||||
public R<Void> submitRating(@Valid @RequestBody AiRatingRecordBo bo) {
|
||||
// 设置当前用户ID
|
||||
Long userId = LoginHelper.getUserId();
|
||||
bo.setUserId(userId);
|
||||
return toAjax(aiRatingRecordService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询评价记录(根据消息ID)
|
||||
*/
|
||||
@Operation(summary = "查询评价记录", description = "根据消息ID查询当前用户的评价记录")
|
||||
@GetMapping("/{messageId}")
|
||||
public R<AiRatingRecordVo> getRating(@Parameter(description = "消息ID", required = true)
|
||||
@NotNull(message = "消息ID不能为空")
|
||||
@PathVariable String messageId) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
AiRatingRecordVo vo = aiRatingRecordService.queryByMessageId(messageId, userId);
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.ai.domain.vo.AiUserQuotaVo;
|
||||
import org.dromara.ai.service.AiUserQuotaService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 用户端AI用户配额
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Tag(name = "用户端-AI用户配额", description = "用户端AI用户配额接口")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/app/ai/user/quota")
|
||||
public class AppAiUserQuotaController {
|
||||
|
||||
private final AiUserQuotaService aiUserQuotaService;
|
||||
|
||||
/**
|
||||
* 查询当前用户的配额信息
|
||||
*/
|
||||
@Operation(summary = "查询当前用户的配额信息", description = "查询当前登录用户的配额信息")
|
||||
@GetMapping
|
||||
public R<AiUserQuotaVo> getMyQuota() {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
AiUserQuotaVo vo = aiUserQuotaService.queryByUserId(userId);
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.platform.CozeService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Coze平台控制器
|
||||
* 提供Coze平台的Bot对话接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/ai/coze")
|
||||
@RequiredArgsConstructor
|
||||
public class CozeController {
|
||||
|
||||
private final CozeService cozeService;
|
||||
|
||||
/**
|
||||
* Coze流式Bot对话
|
||||
*
|
||||
* @param body 请求体,包含botId(Bot ID)和input(输入内容)
|
||||
* @return SSE事件流
|
||||
*/
|
||||
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@Operation(summary = "Coze流式Bot对话", description = "通过SSE与Coze平台的Bot进行流式对话")
|
||||
public SseEmitter stream(@RequestBody Map<String, Object> body) {
|
||||
String botId = String.valueOf(body.getOrDefault("botId", ""));
|
||||
String input = String.valueOf(body.getOrDefault("input", ""));
|
||||
|
||||
log.info("Coze流式Bot对话请求 - Bot ID: {}, 输入长度: {}", botId, input.length());
|
||||
|
||||
SseEmitter emitter = new SseEmitter(TimeUnit.MINUTES.toMillis(2));
|
||||
try {
|
||||
String raw = cozeService.stream(botId, input);
|
||||
emitter.send(raw);
|
||||
emitter.send("[DONE]");
|
||||
emitter.complete();
|
||||
log.debug("Coze流式Bot对话完成 - Bot ID: {}", botId);
|
||||
} catch (Exception e) {
|
||||
log.error("Coze流式Bot对话失败 - Bot ID: {}", botId, e);
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("error").data("ERROR: " + e.getMessage()));
|
||||
} catch (Exception ignored) {
|
||||
log.debug("发送错误事件失败", ignored);
|
||||
}
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.platform.DifyClientService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* DIFY平台控制器
|
||||
* 提供DIFY平台的应用管理接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/ai/dify")
|
||||
@RequiredArgsConstructor
|
||||
public class DifyController {
|
||||
|
||||
private final DifyClientService difyService;
|
||||
|
||||
/**
|
||||
* 获取应用列表
|
||||
*
|
||||
* @return 应用列表
|
||||
*/
|
||||
@GetMapping("/apps")
|
||||
@Operation(summary = "列表应用", description = "获取DIFY平台的所有应用列表")
|
||||
public R<Map<String, Object>> apps() {
|
||||
try {
|
||||
log.debug("获取DIFY应用列表");
|
||||
String resp = difyService.get("/api/apps");
|
||||
return R.ok(Map.of("raw", resp));
|
||||
} catch (Exception e) {
|
||||
log.error("获取DIFY应用列表失败", e);
|
||||
return R.fail("DIFY调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新应用
|
||||
*
|
||||
* @param body 应用信息
|
||||
* @return 创建结果
|
||||
*/
|
||||
@PostMapping("/apps")
|
||||
@Operation(summary = "创建应用", description = "在DIFY平台创建或更新应用")
|
||||
public R<Map<String, Object>> create(@RequestBody Map<String, Object> body) {
|
||||
try {
|
||||
log.info("创建DIFY应用");
|
||||
String resp = difyService.post("/api/apps", toJson(body));
|
||||
log.info("DIFY应用创建成功");
|
||||
return R.ok(Map.of("raw", resp));
|
||||
} catch (Exception e) {
|
||||
log.error("创建DIFY应用失败", e);
|
||||
return R.fail("DIFY调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map转换为JSON字符串
|
||||
*/
|
||||
private String toJson(Map<String, Object> m) {
|
||||
return org.dromara.common.json.utils.JsonUtils.toJsonString(m);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.platform.FastGptService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* FastGPT平台控制器
|
||||
* 提供FastGPT平台的知识库检索、工作流编排和上下文管理接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/ai/fastgpt")
|
||||
@RequiredArgsConstructor
|
||||
public class FastGptController {
|
||||
|
||||
private final FastGptService fastGptService;
|
||||
|
||||
/**
|
||||
* 知识库检索
|
||||
*
|
||||
* @param body 检索参数
|
||||
* @return 检索结果
|
||||
*/
|
||||
@PostMapping("/knowledge/search")
|
||||
@Operation(summary = "知识库检索", description = "在FastGPT平台的知识库中进行检索")
|
||||
public R<Map<String, Object>> search(@RequestBody Map<String, Object> body) {
|
||||
try {
|
||||
log.debug("FastGPT知识库检索请求");
|
||||
String resp = fastGptService.post("/api/knowledge/search", toJson(body));
|
||||
return R.ok(Map.of("raw", resp));
|
||||
} catch (Exception e) {
|
||||
log.error("FastGPT知识库检索失败", e);
|
||||
return R.fail("FastGPT调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流编排
|
||||
*
|
||||
* @param body 工作流参数
|
||||
* @return 执行结果
|
||||
*/
|
||||
@PostMapping("/workflow/run")
|
||||
@Operation(summary = "工作流编排", description = "运行FastGPT平台的工作流")
|
||||
public R<Map<String, Object>> workflow(@RequestBody Map<String, Object> body) {
|
||||
try {
|
||||
log.info("FastGPT工作流执行请求");
|
||||
String resp = fastGptService.post("/api/workflow/run", toJson(body));
|
||||
log.info("FastGPT工作流执行成功");
|
||||
return R.ok(Map.of("raw", resp));
|
||||
} catch (Exception e) {
|
||||
log.error("FastGPT工作流执行失败", e);
|
||||
return R.fail("FastGPT调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @return 上下文信息
|
||||
*/
|
||||
@GetMapping("/context/{sessionId}")
|
||||
@Operation(summary = "上下文管理", description = "获取指定会话的上下文信息")
|
||||
public R<Map<String, Object>> context(
|
||||
@Parameter(description = "会话ID", required = true)
|
||||
@PathVariable String sessionId) {
|
||||
try {
|
||||
log.debug("获取FastGPT上下文 - 会话ID: {}", sessionId);
|
||||
String resp = fastGptService.get("/api/context/" + sessionId);
|
||||
return R.ok(Map.of("raw", resp));
|
||||
} catch (Exception e) {
|
||||
log.error("获取FastGPT上下文失败 - 会话ID: {}", sessionId, e);
|
||||
return R.fail("FastGPT调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map转换为JSON字符串
|
||||
*/
|
||||
private String toJson(Map<String, Object> m) {
|
||||
return org.dromara.common.json.utils.JsonUtils.toJsonString(m);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.mcp.McpIntegrationService;
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
import org.dromara.ai.mcp.McpToolRegistry;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP工具控制器
|
||||
* 提供MCP(Model Context Protocol)工具的查询和调用接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/ai/mcp")
|
||||
@RequiredArgsConstructor
|
||||
public class McpController {
|
||||
|
||||
private final McpToolRegistry registry;
|
||||
private final McpIntegrationService mcpIntegrationService;
|
||||
|
||||
/**
|
||||
* 获取所有已注册的MCP工具列表
|
||||
*
|
||||
* @return 工具列表
|
||||
*/
|
||||
@GetMapping("/tools")
|
||||
@Operation(summary = "获取工具列表", description = "返回所有已注册的MCP工具及其信息")
|
||||
public R<Map<String, McpTool>> tools() {
|
||||
try {
|
||||
Map<String, McpTool> allTools = registry.all();
|
||||
log.debug("获取MCP工具列表,共{}个工具", allTools.size());
|
||||
return R.ok(allTools);
|
||||
} catch (Exception e) {
|
||||
log.error("获取MCP工具列表失败", e);
|
||||
return R.fail("获取工具列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用指定的MCP工具
|
||||
*
|
||||
* @param name 工具名称
|
||||
* @param input 工具输入参数
|
||||
* @return 工具执行结果
|
||||
*/
|
||||
@PostMapping("/invoke/{name}")
|
||||
@Operation(summary = "调用工具", description = "执行指定的MCP工具并返回结果")
|
||||
public R<Map<String, Object>> invoke(
|
||||
@Parameter(description = "工具名称", required = true)
|
||||
@PathVariable @NotBlank String name,
|
||||
@Parameter(description = "工具输入参数")
|
||||
@RequestBody(required = false) Map<String, Object> input) {
|
||||
try {
|
||||
log.info("调用MCP工具: {}, 输入参数: {}", name, input != null ? input.keySet() : "无");
|
||||
|
||||
Map<String, Object> result = mcpIntegrationService.invokeTool(name, input != null ? input : Map.of());
|
||||
|
||||
if (result.containsKey("error")) {
|
||||
log.warn("MCP工具调用失败: {}, 错误: {}", name, result.get("error"));
|
||||
return R.fail("工具调用失败: " + result.get("error"));
|
||||
}
|
||||
|
||||
log.debug("MCP工具调用成功: {}", name);
|
||||
return R.ok(result);
|
||||
} catch (Exception e) {
|
||||
log.error("调用MCP工具失败: {}", name, e);
|
||||
return R.fail("工具调用失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.tika.Tika;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 多模态控制器
|
||||
* 提供文件解析、OCR识别、图像识别、图生文等功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/multimodal")
|
||||
public class MultimodalController {
|
||||
|
||||
@Value("${ruoyi.ai.ocr.base-url:}")
|
||||
private String ocrBaseUrl;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.api-key:}")
|
||||
private String openaiApiKey;
|
||||
|
||||
@Value("${ruoyi.ai.external-ai.openai.base-url:https://api.openai.com}")
|
||||
private String openaiBaseUrl;
|
||||
|
||||
@Operation(summary = "文件解析")
|
||||
@PostMapping(value = "/extract", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public R<Map<String, Object>> extract(
|
||||
@Parameter(description = "文件") @RequestParam("file") @NotNull MultipartFile file) {
|
||||
try {
|
||||
Tika tika = new Tika();
|
||||
String text = tika.parseToString(file.getInputStream());
|
||||
String summary = summarize(text);
|
||||
return R.ok(Map.of("text", text, "summary", summary));
|
||||
} catch (Exception e) {
|
||||
log.error("文件解析失败", e);
|
||||
return R.fail("解析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "OCR识别")
|
||||
@PostMapping(value = "/ocr", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<byte[]> ocr(
|
||||
@Parameter(description = "图片") @RequestParam("file") @NotNull MultipartFile file) {
|
||||
try {
|
||||
if (ocrBaseUrl == null || ocrBaseUrl.isBlank()) {
|
||||
String msg = "{\"code\":500,\"msg\":\"OCR未配置\",\"data\":null}";
|
||||
return ResponseEntity.status(500).contentType(MediaType.APPLICATION_JSON)
|
||||
.body(msg.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
// TODO: 实现OCR服务调用
|
||||
String msg = "{\"code\":200,\"msg\":\"OK\",\"data\":{\"text\":\"\"}}";
|
||||
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.body(msg.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
log.error("OCR识别失败", e);
|
||||
String msg = "{\"code\":500,\"msg\":\"" + e.getMessage().replace("\"", "\\\"") + "\",\"data\":null}";
|
||||
return ResponseEntity.status(500).contentType(MediaType.APPLICATION_JSON)
|
||||
.body(msg.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "文本分析")
|
||||
@PostMapping("/analyze/text")
|
||||
public R<Map<String, Object>> analyzeText(
|
||||
@Parameter(description = "文本") @RequestParam("text") String text) {
|
||||
try {
|
||||
String t = text == null ? "" : text.trim();
|
||||
String summary = summarize(t);
|
||||
List<String> keywords = Arrays.stream(t.split("\\s+"))
|
||||
.filter(s -> s.length() > 3)
|
||||
.limit(10)
|
||||
.collect(Collectors.toList());
|
||||
String title = t.length() > 16 ? t.substring(0, 16) : t;
|
||||
return R.ok(Map.of("title", title, "summary", summary, "keywords", keywords));
|
||||
} catch (Exception e) {
|
||||
log.error("文本分析失败", e);
|
||||
return R.fail("文本分析失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "图像识别", description = "使用AI模型识别图像内容,返回标签和描述")
|
||||
@PostMapping(value = "/analyze/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public R<Map<String, Object>> analyzeImage(
|
||||
@Parameter(description = "图片") @RequestParam("file") @NotNull MultipartFile file,
|
||||
@Parameter(description = "模型名称") @RequestParam(required = false) String model) {
|
||||
try {
|
||||
// 检查是否配置了OpenAI API Key
|
||||
if (openaiApiKey == null || openaiApiKey.isBlank()) {
|
||||
return R.ok(Map.of("labels", List.of(), "description", "",
|
||||
"msg", "VISION_PROVIDER_NOT_CONFIGURED",
|
||||
"error", "OpenAI API Key未配置"));
|
||||
}
|
||||
|
||||
// 由于当前 AiMessage.content 是 String 类型,Vision API 的多模态支持需要在 Provider 层面实现
|
||||
// 这里提供一个占位实现,提示用户使用支持 Vision 的模型
|
||||
// 实际使用时,需要在 OpenAiProvider 中扩展支持多模态消息格式
|
||||
|
||||
log.warn("图像识别功能需要 Vision API 支持,当前实现为占位,请使用支持 Vision 的模型(如 gpt-4-vision-preview)");
|
||||
|
||||
return R.ok(Map.of("labels", List.of(), "description", "",
|
||||
"msg", "VISION_API_NOT_FULLY_IMPLEMENTED",
|
||||
"hint", "此功能需要 Vision API 支持。当前 AiMessage 接口限制,建议直接使用 OpenAI Vision API 或扩展 Provider 接口"));
|
||||
} catch (Exception e) {
|
||||
log.error("图像识别失败", e);
|
||||
return R.ok(Map.of("labels", List.of(), "description", "",
|
||||
"msg", "VISION_ANALYSIS_ERROR",
|
||||
"error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "图生文", description = "使用AI模型为图片生成描述文本")
|
||||
@PostMapping(value = "/caption", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public R<Map<String, Object>> caption(
|
||||
@Parameter(description = "图片") @RequestParam("file") @NotNull MultipartFile file,
|
||||
@Parameter(description = "模型名称") @RequestParam(required = false) String model) {
|
||||
try {
|
||||
// 检查是否配置了OpenAI API Key
|
||||
if (openaiApiKey == null || openaiApiKey.isBlank()) {
|
||||
return R.ok(Map.of("text", "", "msg", "CAPTION_PROVIDER_NOT_CONFIGURED",
|
||||
"error", "OpenAI API Key未配置"));
|
||||
}
|
||||
|
||||
// 由于当前 AiMessage.content 是 String 类型,Vision API 的多模态支持需要在 Provider 层面实现
|
||||
// 这里提供一个占位实现,提示用户使用支持 Vision 的模型
|
||||
|
||||
log.warn("图生文功能需要 Vision API 支持,当前实现为占位,请使用支持 Vision 的模型(如 gpt-4-vision-preview)");
|
||||
|
||||
return R.ok(Map.of("text", "", "msg", "CAPTION_API_NOT_FULLY_IMPLEMENTED",
|
||||
"hint", "此功能需要 Vision API 支持。当前 AiMessage 接口限制,建议直接使用 OpenAI Vision API 或扩展 Provider 接口"));
|
||||
} catch (Exception e) {
|
||||
log.error("图生文失败", e);
|
||||
return R.ok(Map.of("text", "", "msg", "CAPTION_GENERATION_ERROR",
|
||||
"error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 文本摘要
|
||||
*/
|
||||
private String summarize(String text) {
|
||||
if (text == null) return "";
|
||||
String s = text.trim().replaceAll("\\s+", " ");
|
||||
if (s.length() <= 200) return s;
|
||||
return s.substring(0, 200);
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.xslf.usermodel.XMLSlideShow;
|
||||
import org.apache.poi.xslf.usermodel.XSLFSlide;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PPT控制器
|
||||
* 提供智能PPT生成和导出功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/ppt")
|
||||
public class PptController {
|
||||
|
||||
/**
|
||||
* 生成PPTX文件
|
||||
*
|
||||
* @param content 文本内容(按换行符分割为多页)
|
||||
* @param theme 主题(可选,暂未实现)
|
||||
* @return PPTX文件字节流
|
||||
*/
|
||||
@Operation(summary = "生成PPTX", description = "根据文本内容生成PowerPoint演示文稿,每行内容作为一页")
|
||||
@PostMapping("/generate")
|
||||
public ResponseEntity<byte[]> generate(
|
||||
@Parameter(description = "文本内容") @RequestParam @NotBlank String content,
|
||||
@Parameter(description = "主题") @RequestParam(required = false) String theme) {
|
||||
try {
|
||||
log.info("开始生成PPTX,内容长度: {}", content.length());
|
||||
XMLSlideShow ppt = new XMLSlideShow();
|
||||
try {
|
||||
String[] parts = content.split("\\n+");
|
||||
if (parts.length == 0) parts = new String[]{content};
|
||||
for (String p : parts) {
|
||||
XSLFSlide slide = ppt.createSlide();
|
||||
slide.createTextBox().setText(p);
|
||||
}
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ppt.write(bos);
|
||||
byte[] bytes = bos.toByteArray();
|
||||
log.info("PPTX生成成功,文件大小: {} bytes,共{}页", bytes.length, parts.length);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"generated.pptx\"")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(bytes);
|
||||
} finally {
|
||||
ppt.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("PPTX生成失败", e);
|
||||
String msg = "{\"code\":500,\"msg\":\"" + e.getMessage().replace("\"", "\\\"") + "\",\"data\":null}";
|
||||
return ResponseEntity.status(500)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(msg.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出PNG图片序列
|
||||
*
|
||||
* @param content 文本内容
|
||||
* @param width 图片宽度(可选)
|
||||
* @param height 图片高度(可选)
|
||||
* @return Base64编码的PNG图片列表
|
||||
*/
|
||||
@Operation(summary = "导出PNG序列", description = "将PPT内容导出为PNG图片序列,每页一张图片,返回Base64编码")
|
||||
@PostMapping("/export/pngs")
|
||||
public R<List<String>> exportPngs(
|
||||
@Parameter(description = "文本内容") @RequestParam @NotBlank String content,
|
||||
@Parameter(description = "宽度") @RequestParam(required = false) Integer width,
|
||||
@Parameter(description = "高度") @RequestParam(required = false) Integer height) {
|
||||
try {
|
||||
log.info("开始导出PNG序列,内容长度: {}, 尺寸: {}x{}", content.length(), width, height);
|
||||
XMLSlideShow ppt = new XMLSlideShow();
|
||||
try {
|
||||
String[] parts = content.split("\\n+");
|
||||
if (parts.length == 0) parts = new String[]{content};
|
||||
for (String p : parts) {
|
||||
XSLFSlide slide = ppt.createSlide();
|
||||
slide.createTextBox().setText(p);
|
||||
}
|
||||
Dimension pgsize = ppt.getPageSize();
|
||||
int w = width != null ? width : pgsize.width;
|
||||
int h = height != null ? height : pgsize.height;
|
||||
List<String> imgs = new ArrayList<>();
|
||||
for (XSLFSlide slide : ppt.getSlides()) {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = img.createGraphics();
|
||||
graphics.setPaint(Color.WHITE);
|
||||
graphics.fillRect(0, 0, w, h);
|
||||
slide.draw(graphics);
|
||||
graphics.dispose();
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
javax.imageio.ImageIO.write(img, "png", bos);
|
||||
String b64 = Base64.getEncoder().encodeToString(bos.toByteArray());
|
||||
imgs.add("data:image/png;base64," + b64);
|
||||
}
|
||||
log.info("PNG序列导出成功,共{}张图片", imgs.size());
|
||||
return R.ok(imgs);
|
||||
} finally {
|
||||
ppt.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("PNG序列导出失败", e);
|
||||
return R.fail("导出失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.queue.TaskQueueService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 队列指标控制器
|
||||
* 提供任务队列的监控指标查询接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/ai/queue")
|
||||
@RequiredArgsConstructor
|
||||
public class QueueMetricsController {
|
||||
|
||||
private final TaskQueueService queueService;
|
||||
|
||||
/**
|
||||
* 获取队列指标
|
||||
*
|
||||
* @return 队列指标信息
|
||||
*/
|
||||
@GetMapping("/metrics")
|
||||
@Operation(summary = "队列指标", description = "获取任务队列的实时监控指标,包括队列深度、处理速率等")
|
||||
public R<Map<String, Object>> metrics() {
|
||||
try {
|
||||
Map<String, Object> metrics = queueService.metrics();
|
||||
log.debug("获取队列指标成功");
|
||||
return R.ok(metrics);
|
||||
} catch (Exception e) {
|
||||
log.error("获取队列指标失败", e);
|
||||
return R.fail("获取队列指标失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package org.dromara.ai.controller.app;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.ai.provider.AiProviderManager;
|
||||
import org.dromara.ai.service.SimpleAiService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.knowledge.domain.vo.KnowledgeSearchResultVo;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 简化的AI控制器
|
||||
* 提供基于知识库的AI问答功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/ai")
|
||||
@RequiredArgsConstructor
|
||||
public class SimpleAiController {
|
||||
|
||||
private final SimpleAiService simpleAiService;
|
||||
private final AiProviderManager aiProviderManager;
|
||||
|
||||
/**
|
||||
* 基于知识库的问答
|
||||
*
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param question 用户问题
|
||||
* @param limit 返回结果数量限制
|
||||
* @return AI回答
|
||||
*/
|
||||
@PostMapping("/ask")
|
||||
public R<String> ask(@RequestParam String knowledgeId,
|
||||
@RequestParam String question,
|
||||
@RequestParam(required = false, defaultValue = "5") Integer limit) {
|
||||
String answer = simpleAiService.askWithKnowledge(knowledgeId, question, limit);
|
||||
return R.ok(answer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向量相似度搜索
|
||||
*
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param queryText 查询文本
|
||||
* @param modelName 模型名称
|
||||
* @param topK 返回前K个结果
|
||||
* @param threshold 相似度阈值
|
||||
* @return 搜索结果
|
||||
*/
|
||||
@PostMapping("/search")
|
||||
public R<List<KnowledgeSearchResultVo>> vectorSearch(
|
||||
@RequestParam String knowledgeId,
|
||||
@RequestParam String queryText,
|
||||
@RequestParam(required = false, defaultValue = "default") String modelName,
|
||||
@RequestParam(required = false, defaultValue = "5") Integer topK,
|
||||
@RequestParam(required = false, defaultValue = "0.7") Double threshold) {
|
||||
|
||||
List<KnowledgeSearchResultVo> results = simpleAiService.vectorSearch(
|
||||
knowledgeId, queryText, modelName, topK, threshold
|
||||
);
|
||||
return R.ok(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库统计信息
|
||||
*
|
||||
* @param knowledgeId 知识库ID
|
||||
* @return 统计信息
|
||||
*/
|
||||
@GetMapping("/stats/{knowledgeId}")
|
||||
public R<Map<String, Object>> getKnowledgeStats(@PathVariable String knowledgeId) {
|
||||
Map<String, Object> stats = simpleAiService.getKnowledgeStats(knowledgeId);
|
||||
return R.ok(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模型是否可用
|
||||
*
|
||||
* @param modelName 模型名称
|
||||
* @return 是否可用
|
||||
*/
|
||||
@GetMapping("/model/check/{modelName}")
|
||||
public R<Boolean> checkModel(@PathVariable String modelName) {
|
||||
boolean available = simpleAiService.isModelAvailable(modelName);
|
||||
return R.ok(available);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的模型列表
|
||||
*
|
||||
* @return 模型列表
|
||||
*/
|
||||
@GetMapping("/models")
|
||||
public R<List<String>> getAvailableModels() {
|
||||
List<String> models = simpleAiService.getAvailableModels();
|
||||
return R.ok(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI服务健康检查
|
||||
*
|
||||
* @return 健康状态与Provider统计
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public R<Map<String, Object>> health() {
|
||||
Map<String, Object> stats = aiProviderManager.getAllStatistics();
|
||||
stats.put("status", "UP");
|
||||
return R.ok(stats);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI聊天记录对象 ai_chat_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_chat_record")
|
||||
public class AiChatRecord implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 消息类型(USER/ASSISTANT)
|
||||
*/
|
||||
private String messageType;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 使用的Token数
|
||||
*/
|
||||
private Integer tokensUsed;
|
||||
|
||||
/**
|
||||
* 成本
|
||||
*/
|
||||
private BigDecimal cost;
|
||||
|
||||
/**
|
||||
* 响应时间(毫秒)
|
||||
*/
|
||||
private Integer responseTime;
|
||||
|
||||
/**
|
||||
* 是否流式响应(0否 1是)
|
||||
*/
|
||||
private Integer isStream;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画作品收藏对象 ai_painting_favorite
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_painting_favorite")
|
||||
public class AiPaintingFavorite implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画记录ID
|
||||
*/
|
||||
private Long paintingRecordId;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 收藏时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画历史记录对象 ai_painting_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_painting_record")
|
||||
public class AiPaintingRecord implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(DALL_E_3/MIDJOURNEY/STABLE_DIFFUSION)
|
||||
*/
|
||||
private String engineType;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 绘画参数(JSON字符串)
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 状态(PENDING/RUNNING/SUCCESS/FAILED)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 进度(0-100)
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 生成的图片URL列表(JSON字符串)
|
||||
*/
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 分享码(用于分享作品)
|
||||
*/
|
||||
private String shareCode;
|
||||
|
||||
/**
|
||||
* 是否公开分享(0否 1是)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画参数模板对象 ai_painting_template
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_painting_template")
|
||||
public class AiPaintingTemplate implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 用户ID(为空表示系统模板)
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(DALL_E_3/MIDJOURNEY/STABLE_DIFFUSION)
|
||||
*/
|
||||
private String engineType;
|
||||
|
||||
/**
|
||||
* 绘画参数(JSON字符串)
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 模板描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否公开(0=私有 1=公开)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI Provider配置对象 ai_provider_config
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_provider_config")
|
||||
public class AiProviderConfig implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* Provider类型
|
||||
*/
|
||||
private String providerType;
|
||||
|
||||
/**
|
||||
* 是否启用(0=禁用 1=启用)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 优先级(数字越小优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 权重(用于负载均衡)
|
||||
*/
|
||||
private Integer weight;
|
||||
|
||||
/**
|
||||
* 最大并发数
|
||||
*/
|
||||
private Integer maxConcurrency;
|
||||
|
||||
/**
|
||||
* 超时时间(秒)
|
||||
*/
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private Integer maxRetries;
|
||||
|
||||
/**
|
||||
* 熔断器配置(JSON字符串)
|
||||
*/
|
||||
private String circuitBreakerConfig;
|
||||
|
||||
/**
|
||||
* 限流配置(JSON字符串)
|
||||
*/
|
||||
private String rateLimitConfig;
|
||||
|
||||
/**
|
||||
* API配置(JSON字符串)
|
||||
*/
|
||||
private String apiConfig;
|
||||
|
||||
/**
|
||||
* 模型配置(JSON字符串)
|
||||
*/
|
||||
private String modelConfig;
|
||||
|
||||
/**
|
||||
* 扩展配置(JSON字符串)
|
||||
*/
|
||||
private String extensionConfig;
|
||||
|
||||
/**
|
||||
* 健康检查URL
|
||||
*/
|
||||
private String healthCheckUrl;
|
||||
|
||||
/**
|
||||
* 健康检查间隔(秒)
|
||||
*/
|
||||
private Integer healthCheckInterval;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI Provider配置历史对象 ai_provider_config_history
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_provider_config_history")
|
||||
public class AiProviderConfigHistory implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 配置ID
|
||||
*/
|
||||
private Long configId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 配置数据(JSON字符串)
|
||||
*/
|
||||
private String configData;
|
||||
|
||||
/**
|
||||
* 操作类型(CREATE/UPDATE/DELETE)
|
||||
*/
|
||||
private String operationType;
|
||||
|
||||
/**
|
||||
* 操作时间
|
||||
*/
|
||||
private Date operationTime;
|
||||
|
||||
/**
|
||||
* 操作者
|
||||
*/
|
||||
private String operationBy;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI评价记录对象 ai_rating_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_rating_record")
|
||||
public class AiRatingRecord implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 评分(1-5)
|
||||
*/
|
||||
private Integer rating;
|
||||
|
||||
/**
|
||||
* 反馈内容
|
||||
*/
|
||||
private String feedback;
|
||||
|
||||
/**
|
||||
* 评价维度
|
||||
*/
|
||||
private String dimension;
|
||||
|
||||
/**
|
||||
* 是否匿名(0否 1是)
|
||||
*/
|
||||
private Integer anonymous;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI敏感词对象 ai_sensitive_word
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_sensitive_word")
|
||||
public class AiSensitiveWord implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 敏感词
|
||||
*/
|
||||
private String word;
|
||||
|
||||
/**
|
||||
* 词类型(PROFANITY/HATE_SPEECH/VIOLENCE/SEXUAL_CONTENT/ILLEGAL_ACTIVITY/PERSONAL_INFO/SPAM/MALICIOUS_CODE/POLITICAL_SENSITIVE/COMMERCIAL_AD)
|
||||
*/
|
||||
private String wordType;
|
||||
|
||||
/**
|
||||
* 是否启用(0=禁用 1=启用)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 优先级(数字越大优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI使用统计对象 ai_usage_stats
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_usage_stats")
|
||||
public class AiUsageStats implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 统计日期
|
||||
*/
|
||||
private Date statDate;
|
||||
|
||||
/**
|
||||
* 请求次数
|
||||
*/
|
||||
private Integer requestCount;
|
||||
|
||||
/**
|
||||
* 成功次数
|
||||
*/
|
||||
private Integer successCount;
|
||||
|
||||
/**
|
||||
* 错误次数
|
||||
*/
|
||||
private Integer errorCount;
|
||||
|
||||
/**
|
||||
* 总Token数
|
||||
*/
|
||||
private Long totalTokens;
|
||||
|
||||
/**
|
||||
* 总成本
|
||||
*/
|
||||
private BigDecimal totalCost;
|
||||
|
||||
/**
|
||||
* 平均响应时间(毫秒)
|
||||
*/
|
||||
private Integer avgResponseTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.dromara.ai.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI用户配额对象 ai_user_quota
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@TableName("ai_user_quota")
|
||||
public class AiUserQuota implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 权限级别(GUEST/BASIC/PREMIUM/VIP/ADMIN)
|
||||
*/
|
||||
private String permissionLevel;
|
||||
|
||||
/**
|
||||
* 每日配额限制(0表示无限制)
|
||||
*/
|
||||
private Long dailyQuota;
|
||||
|
||||
/**
|
||||
* 每月配额限制(0表示无限制)
|
||||
*/
|
||||
private Long monthlyQuota;
|
||||
|
||||
/**
|
||||
* 每日已使用配额
|
||||
*/
|
||||
private Long dailyUsed;
|
||||
|
||||
/**
|
||||
* 每月已使用配额
|
||||
*/
|
||||
private Long monthlyUsed;
|
||||
|
||||
/**
|
||||
* 上次重置日配额时间
|
||||
*/
|
||||
private Date lastResetDaily;
|
||||
|
||||
/**
|
||||
* 上次重置月配额时间
|
||||
*/
|
||||
private Date lastResetMonthly;
|
||||
|
||||
/**
|
||||
* 自定义限制(JSON字符串)
|
||||
*/
|
||||
private String customLimits;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiChatRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI聊天记录业务对象 ai_chat_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiChatRecord.class, reverseConvertGenerate = false)
|
||||
public class AiChatRecordBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 消息类型(USER/ASSISTANT)
|
||||
*/
|
||||
private String messageType;
|
||||
|
||||
/**
|
||||
* 消息内容(用于搜索)
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 使用的Token数
|
||||
*/
|
||||
private Integer tokensUsed;
|
||||
|
||||
/**
|
||||
* 成本
|
||||
*/
|
||||
private BigDecimal cost;
|
||||
|
||||
/**
|
||||
* 响应时间(毫秒)
|
||||
*/
|
||||
private Integer responseTime;
|
||||
|
||||
/**
|
||||
* 是否流式响应(0否 1是)
|
||||
*/
|
||||
private Integer isStream;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建时间(开始)
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间(结束)
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiPaintingFavorite;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画作品收藏业务对象 ai_painting_favorite
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiPaintingFavorite.class, reverseConvertGenerate = false)
|
||||
public class AiPaintingFavoriteBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画记录ID
|
||||
*/
|
||||
private Long paintingRecordId;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 收藏时间开始
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 收藏时间结束
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiPaintingRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画历史记录业务对象 ai_painting_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiPaintingRecord.class, reverseConvertGenerate = false)
|
||||
public class AiPaintingRecordBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(DALL_E_3/MIDJOURNEY/STABLE_DIFFUSION)
|
||||
*/
|
||||
private String engineType;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 绘画参数(JSON字符串)
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 状态(PENDING/RUNNING/SUCCESS/FAILED)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 进度(0-100)
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 生成的图片URL列表(JSON字符串)
|
||||
*/
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 创建时间开始
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间结束
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
/**
|
||||
* 分享码(用于分享作品)
|
||||
*/
|
||||
private String shareCode;
|
||||
|
||||
/**
|
||||
* 是否公开分享(0否 1是)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiPaintingTemplate;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画参数模板业务对象 ai_painting_template
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiPaintingTemplate.class, reverseConvertGenerate = false)
|
||||
public class AiPaintingTemplateBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
@NotBlank(message = "模板名称不能为空")
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 用户ID(为空表示系统模板)
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(DALL_E_3/MIDJOURNEY/STABLE_DIFFUSION)
|
||||
*/
|
||||
@NotBlank(message = "引擎类型不能为空")
|
||||
private String engineType;
|
||||
|
||||
/**
|
||||
* 绘画参数(JSON字符串)
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 模板描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否公开(0=私有 1=公开)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 创建时间开始
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间结束
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiProviderConfig;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI Provider配置业务对象 ai_provider_config
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiProviderConfig.class, reverseConvertGenerate = false)
|
||||
public class AiProviderConfigBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
@NotBlank(message = "Provider名称不能为空")
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* Provider类型
|
||||
*/
|
||||
@NotBlank(message = "Provider类型不能为空")
|
||||
private String providerType;
|
||||
|
||||
/**
|
||||
* 是否启用(0=禁用 1=启用)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 优先级(数字越小优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 权重(用于负载均衡)
|
||||
*/
|
||||
private Integer weight;
|
||||
|
||||
/**
|
||||
* 最大并发数
|
||||
*/
|
||||
private Integer maxConcurrency;
|
||||
|
||||
/**
|
||||
* 超时时间(秒)
|
||||
*/
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private Integer maxRetries;
|
||||
|
||||
/**
|
||||
* 熔断器配置(JSON字符串)
|
||||
*/
|
||||
private String circuitBreakerConfig;
|
||||
|
||||
/**
|
||||
* 限流配置(JSON字符串)
|
||||
*/
|
||||
private String rateLimitConfig;
|
||||
|
||||
/**
|
||||
* API配置(JSON字符串)
|
||||
*/
|
||||
private String apiConfig;
|
||||
|
||||
/**
|
||||
* 模型配置(JSON字符串)
|
||||
*/
|
||||
private String modelConfig;
|
||||
|
||||
/**
|
||||
* 扩展配置(JSON字符串)
|
||||
*/
|
||||
private String extensionConfig;
|
||||
|
||||
/**
|
||||
* 健康检查URL
|
||||
*/
|
||||
private String healthCheckUrl;
|
||||
|
||||
/**
|
||||
* 健康检查间隔(秒)
|
||||
*/
|
||||
private Integer healthCheckInterval;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间开始(用于查询)
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间结束(用于查询)
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiProviderConfigHistory;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI Provider配置历史业务对象 ai_provider_config_history
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiProviderConfigHistory.class, reverseConvertGenerate = false)
|
||||
public class AiProviderConfigHistoryBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 配置ID
|
||||
*/
|
||||
private Long configId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 配置数据(JSON字符串)
|
||||
*/
|
||||
private String configData;
|
||||
|
||||
/**
|
||||
* 操作类型(CREATE/UPDATE/DELETE)
|
||||
*/
|
||||
private String operationType;
|
||||
|
||||
/**
|
||||
* 操作时间
|
||||
*/
|
||||
private Date operationTime;
|
||||
|
||||
/**
|
||||
* 操作时间开始
|
||||
*/
|
||||
private Date operationTimeStart;
|
||||
|
||||
/**
|
||||
* 操作时间结束
|
||||
*/
|
||||
private Date operationTimeEnd;
|
||||
|
||||
/**
|
||||
* 操作者
|
||||
*/
|
||||
private String operationBy;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiRatingRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI评价记录业务对象 ai_rating_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiRatingRecord.class, reverseConvertGenerate = false)
|
||||
public class AiRatingRecordBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 评分(1-5)
|
||||
*/
|
||||
private Integer rating;
|
||||
|
||||
/**
|
||||
* 反馈内容
|
||||
*/
|
||||
private String feedback;
|
||||
|
||||
/**
|
||||
* 评价维度
|
||||
*/
|
||||
private String dimension;
|
||||
|
||||
/**
|
||||
* 是否匿名(0否 1是)
|
||||
*/
|
||||
private Integer anonymous;
|
||||
|
||||
/**
|
||||
* 创建时间开始(用于查询)
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间结束(用于查询)
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiSensitiveWord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI敏感词业务对象 ai_sensitive_word
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiSensitiveWord.class, reverseConvertGenerate = false)
|
||||
public class AiSensitiveWordBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 敏感词
|
||||
*/
|
||||
@NotBlank(message = "敏感词不能为空")
|
||||
private String word;
|
||||
|
||||
/**
|
||||
* 词类型(PROFANITY/HATE_SPEECH/VIOLENCE/SEXUAL_CONTENT/ILLEGAL_ACTIVITY/PERSONAL_INFO/SPAM/MALICIOUS_CODE/POLITICAL_SENSITIVE/COMMERCIAL_AD)
|
||||
*/
|
||||
@NotBlank(message = "词类型不能为空")
|
||||
private String wordType;
|
||||
|
||||
/**
|
||||
* 是否启用(0=禁用 1=启用)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 优先级(数字越大优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间开始
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间结束
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiUsageStats;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI使用统计业务对象 ai_usage_stats
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiUsageStats.class, reverseConvertGenerate = false)
|
||||
public class AiUsageStatsBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 统计日期(开始)
|
||||
*/
|
||||
private Date statDateStart;
|
||||
|
||||
/**
|
||||
* 统计日期(结束)
|
||||
*/
|
||||
private Date statDateEnd;
|
||||
|
||||
/**
|
||||
* 请求次数
|
||||
*/
|
||||
private Integer requestCount;
|
||||
|
||||
/**
|
||||
* 成功次数
|
||||
*/
|
||||
private Integer successCount;
|
||||
|
||||
/**
|
||||
* 错误次数
|
||||
*/
|
||||
private Integer errorCount;
|
||||
|
||||
/**
|
||||
* 总Token数
|
||||
*/
|
||||
private Long totalTokens;
|
||||
|
||||
/**
|
||||
* 总成本
|
||||
*/
|
||||
private BigDecimal totalCost;
|
||||
|
||||
/**
|
||||
* 平均响应时间(毫秒)
|
||||
*/
|
||||
private Integer avgResponseTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.dromara.ai.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiUserQuota;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI用户配额业务对象 ai_user_quota
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiUserQuota.class, reverseConvertGenerate = false)
|
||||
public class AiUserQuotaBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 权限级别(GUEST/BASIC/PREMIUM/VIP/ADMIN)
|
||||
*/
|
||||
private String permissionLevel;
|
||||
|
||||
/**
|
||||
* 每日配额限制(0表示无限制)
|
||||
*/
|
||||
private Long dailyQuota;
|
||||
|
||||
/**
|
||||
* 每月配额限制(0表示无限制)
|
||||
*/
|
||||
private Long monthlyQuota;
|
||||
|
||||
/**
|
||||
* 每日已使用配额
|
||||
*/
|
||||
private Long dailyUsed;
|
||||
|
||||
/**
|
||||
* 每月已使用配额
|
||||
*/
|
||||
private Long monthlyUsed;
|
||||
|
||||
/**
|
||||
* 上次重置日配额时间
|
||||
*/
|
||||
private Date lastResetDaily;
|
||||
|
||||
/**
|
||||
* 上次重置月配额时间
|
||||
*/
|
||||
private Date lastResetMonthly;
|
||||
|
||||
/**
|
||||
* 自定义限制(JSON字符串)
|
||||
*/
|
||||
private String customLimits;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间开始
|
||||
*/
|
||||
private Date createTimeStart;
|
||||
|
||||
/**
|
||||
* 创建时间结束
|
||||
*/
|
||||
private Date createTimeEnd;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI聊天请求DTO
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AiChatRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 对话消息列表
|
||||
*/
|
||||
private List<AiMessage> messages;
|
||||
|
||||
/**
|
||||
* 温度参数 (0.0-2.0)
|
||||
*/
|
||||
private Double temperature;
|
||||
|
||||
/**
|
||||
* 最大生成token数
|
||||
*/
|
||||
private Integer maxTokens;
|
||||
|
||||
/**
|
||||
* Top-p参数 (0.0-1.0)
|
||||
*/
|
||||
private Double topP;
|
||||
|
||||
/**
|
||||
* 频率惩罚 (-2.0-2.0)
|
||||
*/
|
||||
private Double frequencyPenalty;
|
||||
|
||||
/**
|
||||
* 存在惩罚 (-2.0-2.0)
|
||||
*/
|
||||
private Double presencePenalty;
|
||||
|
||||
/**
|
||||
* 停止词列表
|
||||
*/
|
||||
private List<String> stop;
|
||||
|
||||
/**
|
||||
* 是否流式输出
|
||||
*/
|
||||
private Boolean stream;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 知识库ID
|
||||
*/
|
||||
private String knowledgeId;
|
||||
|
||||
/**
|
||||
* 是否启用知识库检索
|
||||
*/
|
||||
private Boolean enableKnowledge;
|
||||
|
||||
/**
|
||||
* 知识库检索数量
|
||||
*/
|
||||
private Integer knowledgeLimit;
|
||||
|
||||
/**
|
||||
* 知识库检索阈值
|
||||
*/
|
||||
private Double knowledgeThreshold;
|
||||
|
||||
/**
|
||||
* 扩展参数
|
||||
*/
|
||||
private Map<String, Object> extra;
|
||||
|
||||
private Boolean enableTools;
|
||||
private java.util.List<String> requestedTools;
|
||||
private Map<String, Object> toolContext;
|
||||
|
||||
/**
|
||||
* AI消息
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class AiMessage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 消息角色
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 消息名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 函数调用信息
|
||||
*/
|
||||
private Object functionCall;
|
||||
|
||||
/**
|
||||
* 工具调用信息
|
||||
*/
|
||||
private List<Object> toolCalls;
|
||||
|
||||
/**
|
||||
* 工具调用ID
|
||||
*/
|
||||
private String toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息角色枚举
|
||||
*/
|
||||
public enum MessageRole {
|
||||
SYSTEM("system"),
|
||||
USER("user"),
|
||||
ASSISTANT("assistant"),
|
||||
FUNCTION("function"),
|
||||
TOOL("tool");
|
||||
|
||||
private final String value;
|
||||
|
||||
MessageRole(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI聊天响应DTO
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AiChatResponse implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 对象类型
|
||||
*/
|
||||
private String object;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Long created;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 选择列表
|
||||
*/
|
||||
private List<Choice> choices;
|
||||
|
||||
/**
|
||||
* 使用情况
|
||||
*/
|
||||
private Usage usage;
|
||||
|
||||
/**
|
||||
* 系统指纹
|
||||
*/
|
||||
private String systemFingerprint;
|
||||
|
||||
/**
|
||||
* 提供商名称
|
||||
*/
|
||||
private String provider;
|
||||
|
||||
/**
|
||||
* 响应时间(毫秒)
|
||||
*/
|
||||
private Long responseTime;
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String error;
|
||||
|
||||
/**
|
||||
* 错误代码
|
||||
*/
|
||||
private String errorCode;
|
||||
|
||||
/**
|
||||
* 知识库检索结果
|
||||
*/
|
||||
private List<KnowledgeResult> knowledgeResults;
|
||||
|
||||
/**
|
||||
* 扩展信息
|
||||
*/
|
||||
private Map<String, Object> extra;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 选择项
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Choice implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 索引
|
||||
*/
|
||||
private Integer index;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
private AiChatRequest.AiMessage message;
|
||||
|
||||
/**
|
||||
* 增量消息(流式)
|
||||
*/
|
||||
private AiChatRequest.AiMessage delta;
|
||||
|
||||
/**
|
||||
* 结束原因
|
||||
*/
|
||||
private String finishReason;
|
||||
|
||||
/**
|
||||
* 日志概率
|
||||
*/
|
||||
private Object logprobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用情况
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Usage implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 提示token数
|
||||
*/
|
||||
private Integer promptTokens;
|
||||
|
||||
/**
|
||||
* 完成token数
|
||||
*/
|
||||
private Integer completionTokens;
|
||||
|
||||
/**
|
||||
* 总token数
|
||||
*/
|
||||
private Integer totalTokens;
|
||||
|
||||
/**
|
||||
* 提示token详情
|
||||
*/
|
||||
private Object promptTokensDetails;
|
||||
|
||||
/**
|
||||
* 完成token详情
|
||||
*/
|
||||
private Object completionTokensDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* 知识库检索结果
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class KnowledgeResult implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 文档ID
|
||||
*/
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* 文档标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 文档内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 相似度分数
|
||||
*/
|
||||
private Double score;
|
||||
|
||||
/**
|
||||
* 元数据
|
||||
*/
|
||||
private Map<String, Object> metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束原因枚举
|
||||
*/
|
||||
public enum FinishReason {
|
||||
STOP("stop"),
|
||||
LENGTH("length"),
|
||||
FUNCTION_CALL("function_call"),
|
||||
TOOL_CALLS("tool_calls"),
|
||||
CONTENT_FILTER("content_filter");
|
||||
|
||||
private final String value;
|
||||
|
||||
FinishReason(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AI模型信息DTO
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AiModelInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 模型ID
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 模型显示名称
|
||||
*/
|
||||
private String displayName;
|
||||
|
||||
/**
|
||||
* 模型描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 模型版本
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 提供商类型
|
||||
*/
|
||||
private String provider;
|
||||
|
||||
/**
|
||||
* 模型类型
|
||||
*/
|
||||
private ModelType type;
|
||||
|
||||
/**
|
||||
* 模型能力
|
||||
*/
|
||||
private List<ModelCapability> capabilities;
|
||||
|
||||
/**
|
||||
* 最大上下文长度
|
||||
*/
|
||||
private Integer maxContextLength;
|
||||
|
||||
/**
|
||||
* 最大输出长度
|
||||
*/
|
||||
private Integer maxOutputLength;
|
||||
|
||||
/**
|
||||
* 输入价格(每1K token)
|
||||
*/
|
||||
private Double inputPrice;
|
||||
|
||||
/**
|
||||
* 输出价格(每1K token)
|
||||
*/
|
||||
private Double outputPrice;
|
||||
|
||||
/**
|
||||
* 是否支持流式输出
|
||||
*/
|
||||
private Boolean supportStream;
|
||||
|
||||
/**
|
||||
* 是否支持函数调用
|
||||
*/
|
||||
private Boolean supportFunction;
|
||||
|
||||
/**
|
||||
* 是否支持工具调用
|
||||
*/
|
||||
private Boolean supportTool;
|
||||
|
||||
/**
|
||||
* 是否支持视觉
|
||||
*/
|
||||
private Boolean supportVision;
|
||||
|
||||
/**
|
||||
* 是否支持图像生成
|
||||
*/
|
||||
private Boolean supportImageGeneration;
|
||||
|
||||
/**
|
||||
* 是否支持语音
|
||||
*/
|
||||
private Boolean supportAudio;
|
||||
|
||||
/**
|
||||
* 是否可用
|
||||
*/
|
||||
private Boolean available;
|
||||
|
||||
/**
|
||||
* 模型状态
|
||||
*/
|
||||
private ModelStatus status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime created;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updated;
|
||||
|
||||
/**
|
||||
* 模型参数
|
||||
*/
|
||||
private ModelParameters parameters;
|
||||
|
||||
/**
|
||||
* 扩展信息
|
||||
*/
|
||||
private Map<String, Object> extra;
|
||||
|
||||
/**
|
||||
* 模型类型枚举
|
||||
*/
|
||||
public enum ModelType {
|
||||
/**
|
||||
* 文本生成
|
||||
*/
|
||||
TEXT_GENERATION("text_generation", "文本生成"),
|
||||
|
||||
/**
|
||||
* 聊天对话
|
||||
*/
|
||||
CHAT("chat", "聊天对话"),
|
||||
|
||||
/**
|
||||
* 代码生成
|
||||
*/
|
||||
CODE_GENERATION("code_generation", "代码生成"),
|
||||
|
||||
/**
|
||||
* 图像生成
|
||||
*/
|
||||
IMAGE_GENERATION("image_generation", "图像生成"),
|
||||
|
||||
/**
|
||||
* 语音合成
|
||||
*/
|
||||
TEXT_TO_SPEECH("text_to_speech", "语音合成"),
|
||||
|
||||
/**
|
||||
* 语音识别
|
||||
*/
|
||||
SPEECH_TO_TEXT("speech_to_text", "语音识别"),
|
||||
|
||||
/**
|
||||
* 嵌入向量
|
||||
*/
|
||||
EMBEDDING("embedding", "嵌入向量"),
|
||||
|
||||
/**
|
||||
* 多模态
|
||||
*/
|
||||
MULTIMODAL("multimodal", "多模态");
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
|
||||
ModelType(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型能力枚举
|
||||
*/
|
||||
public enum ModelCapability {
|
||||
/**
|
||||
* 文本理解
|
||||
*/
|
||||
TEXT_UNDERSTANDING("text_understanding", "文本理解"),
|
||||
|
||||
/**
|
||||
* 文本生成
|
||||
*/
|
||||
TEXT_GENERATION("text_generation", "文本生成"),
|
||||
|
||||
/**
|
||||
* 对话聊天
|
||||
*/
|
||||
CONVERSATION("conversation", "对话聊天"),
|
||||
|
||||
/**
|
||||
* 代码理解
|
||||
*/
|
||||
CODE_UNDERSTANDING("code_understanding", "代码理解"),
|
||||
|
||||
/**
|
||||
* 代码生成
|
||||
*/
|
||||
CODE_GENERATION("code_generation", "代码生成"),
|
||||
|
||||
/**
|
||||
* 函数调用
|
||||
*/
|
||||
FUNCTION_CALLING("function_calling", "函数调用"),
|
||||
|
||||
/**
|
||||
* 工具使用
|
||||
*/
|
||||
TOOL_USE("tool_use", "工具使用"),
|
||||
|
||||
/**
|
||||
* 图像理解
|
||||
*/
|
||||
IMAGE_UNDERSTANDING("image_understanding", "图像理解"),
|
||||
|
||||
/**
|
||||
* 图像生成
|
||||
*/
|
||||
IMAGE_GENERATION("image_generation", "图像生成"),
|
||||
|
||||
/**
|
||||
* 语音识别
|
||||
*/
|
||||
SPEECH_RECOGNITION("speech_recognition", "语音识别"),
|
||||
|
||||
/**
|
||||
* 语音合成
|
||||
*/
|
||||
SPEECH_SYNTHESIS("speech_synthesis", "语音合成"),
|
||||
|
||||
/**
|
||||
* 文档分析
|
||||
*/
|
||||
DOCUMENT_ANALYSIS("document_analysis", "文档分析"),
|
||||
|
||||
/**
|
||||
* 知识问答
|
||||
*/
|
||||
KNOWLEDGE_QA("knowledge_qa", "知识问答");
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
|
||||
ModelCapability(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型状态枚举
|
||||
*/
|
||||
public enum ModelStatus {
|
||||
/**
|
||||
* 可用
|
||||
*/
|
||||
AVAILABLE("available", "可用"),
|
||||
|
||||
/**
|
||||
* 不可用
|
||||
*/
|
||||
UNAVAILABLE("unavailable", "不可用"),
|
||||
|
||||
/**
|
||||
* 维护中
|
||||
*/
|
||||
MAINTENANCE("maintenance", "维护中"),
|
||||
|
||||
/**
|
||||
* 已弃用
|
||||
*/
|
||||
DEPRECATED("deprecated", "已弃用");
|
||||
|
||||
private final String code;
|
||||
private final String name;
|
||||
|
||||
ModelStatus(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型参数
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class ModelParameters implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 默认温度
|
||||
*/
|
||||
private Double defaultTemperature;
|
||||
|
||||
/**
|
||||
* 最小温度
|
||||
*/
|
||||
private Double minTemperature;
|
||||
|
||||
/**
|
||||
* 最大温度
|
||||
*/
|
||||
private Double maxTemperature;
|
||||
|
||||
/**
|
||||
* 默认Top-p
|
||||
*/
|
||||
private Double defaultTopP;
|
||||
|
||||
/**
|
||||
* 最小Top-p
|
||||
*/
|
||||
private Double minTopP;
|
||||
|
||||
/**
|
||||
* 最大Top-p
|
||||
*/
|
||||
private Double maxTopP;
|
||||
|
||||
/**
|
||||
* 默认最大token数
|
||||
*/
|
||||
private Integer defaultMaxTokens;
|
||||
|
||||
/**
|
||||
* 最小最大token数
|
||||
*/
|
||||
private Integer minMaxTokens;
|
||||
|
||||
/**
|
||||
* 最大最大token数
|
||||
*/
|
||||
private Integer maxMaxTokens;
|
||||
|
||||
/**
|
||||
* 支持的停止词数量
|
||||
*/
|
||||
private Integer maxStopSequences;
|
||||
|
||||
/**
|
||||
* 其他参数
|
||||
*/
|
||||
private Map<String, Object> others;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package org.dromara.ai.domain.enums;
|
||||
|
||||
/**
|
||||
* AI服务提供商类型枚举
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
public enum AiProviderType {
|
||||
|
||||
/**
|
||||
* OpenAI
|
||||
*/
|
||||
OPENAI("openai", "OpenAI", "OpenAI官方API服务"),
|
||||
|
||||
/**
|
||||
* Azure OpenAI
|
||||
*/
|
||||
AZURE_OPENAI("azure_openai", "Azure OpenAI", "微软Azure OpenAI服务"),
|
||||
|
||||
/**
|
||||
* 百度文心一言
|
||||
*/
|
||||
BAIDU_WENXIN("baidu_wenxin", "百度文心一言", "百度文心一言大模型"),
|
||||
|
||||
/**
|
||||
* 阿里通义千问
|
||||
*/
|
||||
ALIBABA_TONGYI("alibaba_tongyi", "阿里通义千问", "阿里巴巴通义千问大模型"),
|
||||
|
||||
/**
|
||||
* 腾讯混元
|
||||
*/
|
||||
TENCENT_HUNYUAN("tencent_hunyuan", "腾讯混元", "腾讯混元大模型"),
|
||||
|
||||
/**
|
||||
* 讯飞星火
|
||||
*/
|
||||
IFLYTEK_SPARK("iflytek_spark", "讯飞星火", "科大讯飞星火认知大模型"),
|
||||
|
||||
/**
|
||||
* 智谱AI
|
||||
*/
|
||||
ZHIPU_AI("zhipu_ai", "智谱AI", "智谱AI大模型"),
|
||||
|
||||
/**
|
||||
* 月之暗面Kimi
|
||||
*/
|
||||
MOONSHOT_KIMI("moonshot_kimi", "月之暗面Kimi", "月之暗面Kimi大模型"),
|
||||
|
||||
/**
|
||||
* 字节豆包
|
||||
*/
|
||||
BYTEDANCE_DOUBAO("bytedance_doubao", "字节豆包", "字节跳动豆包大模型"),
|
||||
|
||||
/**
|
||||
* 自定义提供商
|
||||
*/
|
||||
CUSTOM("custom", "自定义", "自定义AI服务提供商"),
|
||||
FASTGPT("fastgpt", "FastGPT", "FastGPT平台"),
|
||||
COZE("coze", "Coze", "Coze平台"),
|
||||
DIFY("dify", "Dify", "Dify平台");
|
||||
|
||||
/**
|
||||
* 提供商代码
|
||||
*/
|
||||
private final String code;
|
||||
|
||||
/**
|
||||
* 提供商名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 提供商描述
|
||||
*/
|
||||
private final String description;
|
||||
|
||||
AiProviderType(String code, String name, String description) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据代码获取提供商类型
|
||||
*
|
||||
* @param code 提供商代码
|
||||
* @return 提供商类型
|
||||
*/
|
||||
public static AiProviderType fromCode(String code) {
|
||||
for (AiProviderType type : values()) {
|
||||
if (type.getCode().equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return CUSTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取提供商类型
|
||||
*
|
||||
* @param name 提供商名称
|
||||
* @return 提供商类型
|
||||
*/
|
||||
public static AiProviderType fromName(String name) {
|
||||
for (AiProviderType type : values()) {
|
||||
if (type.getName().equals(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return CUSTOM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.ai.domain.AiChatRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI聊天记录视图对象 ai_chat_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = AiChatRecord.class)
|
||||
public class AiChatRecordVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 消息类型(USER/ASSISTANT)
|
||||
*/
|
||||
private String messageType;
|
||||
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 使用的Token数
|
||||
*/
|
||||
private Integer tokensUsed;
|
||||
|
||||
/**
|
||||
* 成本
|
||||
*/
|
||||
private BigDecimal cost;
|
||||
|
||||
/**
|
||||
* 响应时间(毫秒)
|
||||
*/
|
||||
private Integer responseTime;
|
||||
|
||||
/**
|
||||
* 是否流式响应(0否 1是)
|
||||
*/
|
||||
private Integer isStream;
|
||||
|
||||
/**
|
||||
* 是否流式响应(显示用)
|
||||
*/
|
||||
private String isStreamStr;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiPaintingFavorite;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画作品收藏视图对象 ai_painting_favorite
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiPaintingFavorite.class)
|
||||
public class AiPaintingFavoriteVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画记录ID
|
||||
*/
|
||||
private Long paintingRecordId;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 收藏时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 绘画记录信息(关联查询)
|
||||
*/
|
||||
private AiPaintingRecordVo paintingRecord;
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiPaintingRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画历史记录视图对象 ai_painting_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiPaintingRecord.class)
|
||||
public class AiPaintingRecordVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(DALL_E_3/MIDJOURNEY/STABLE_DIFFUSION)
|
||||
*/
|
||||
private String engineType;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(字符串显示)
|
||||
*/
|
||||
private String engineTypeStr;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 绘画参数(JSON字符串)
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 状态(PENDING/RUNNING/SUCCESS/FAILED)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 状态(字符串显示)
|
||||
*/
|
||||
private String statusStr;
|
||||
|
||||
/**
|
||||
* 进度(0-100)
|
||||
*/
|
||||
private Integer progress;
|
||||
|
||||
/**
|
||||
* 生成的图片URL列表(JSON字符串)
|
||||
*/
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 分享码(用于分享作品)
|
||||
*/
|
||||
private String shareCode;
|
||||
|
||||
/**
|
||||
* 是否公开分享(0否 1是)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 是否公开分享(字符串显示)
|
||||
*/
|
||||
private String isPublicStr;
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiPaintingTemplate;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI绘画参数模板视图对象 ai_painting_template
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiPaintingTemplate.class)
|
||||
public class AiPaintingTemplateVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 模板名称
|
||||
*/
|
||||
private String templateName;
|
||||
|
||||
/**
|
||||
* 用户ID(为空表示系统模板)
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(DALL_E_3/MIDJOURNEY/STABLE_DIFFUSION)
|
||||
*/
|
||||
private String engineType;
|
||||
|
||||
/**
|
||||
* 绘画引擎类型(字符串显示)
|
||||
*/
|
||||
private String engineTypeStr;
|
||||
|
||||
/**
|
||||
* 绘画参数(JSON字符串)
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* 模板描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否公开(0=私有 1=公开)
|
||||
*/
|
||||
private Integer isPublic;
|
||||
|
||||
/**
|
||||
* 是否公开(字符串显示)
|
||||
*/
|
||||
private String isPublicStr;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiProviderConfigHistory;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI Provider配置历史视图对象 ai_provider_config_history
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiProviderConfigHistory.class)
|
||||
public class AiProviderConfigHistoryVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 配置ID
|
||||
*/
|
||||
private Long configId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 配置数据(JSON字符串)
|
||||
*/
|
||||
private String configData;
|
||||
|
||||
/**
|
||||
* 操作类型(CREATE/UPDATE/DELETE)
|
||||
*/
|
||||
private String operationType;
|
||||
|
||||
/**
|
||||
* 操作类型(字符串显示)
|
||||
*/
|
||||
private String operationTypeStr;
|
||||
|
||||
/**
|
||||
* 操作时间
|
||||
*/
|
||||
private Date operationTime;
|
||||
|
||||
/**
|
||||
* 操作者
|
||||
*/
|
||||
private String operationBy;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiProviderConfig;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI Provider配置视图对象 ai_provider_config
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiProviderConfig.class)
|
||||
public class AiProviderConfigVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* Provider类型
|
||||
*/
|
||||
private String providerType;
|
||||
|
||||
/**
|
||||
* 是否启用(0=禁用 1=启用)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 是否启用(字符串显示)
|
||||
*/
|
||||
private String enabledStr;
|
||||
|
||||
/**
|
||||
* 优先级(数字越小优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 权重(用于负载均衡)
|
||||
*/
|
||||
private Integer weight;
|
||||
|
||||
/**
|
||||
* 最大并发数
|
||||
*/
|
||||
private Integer maxConcurrency;
|
||||
|
||||
/**
|
||||
* 超时时间(秒)
|
||||
*/
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private Integer maxRetries;
|
||||
|
||||
/**
|
||||
* 熔断器配置(JSON字符串)
|
||||
*/
|
||||
private String circuitBreakerConfig;
|
||||
|
||||
/**
|
||||
* 限流配置(JSON字符串)
|
||||
*/
|
||||
private String rateLimitConfig;
|
||||
|
||||
/**
|
||||
* API配置(JSON字符串)
|
||||
*/
|
||||
private String apiConfig;
|
||||
|
||||
/**
|
||||
* 模型配置(JSON字符串)
|
||||
*/
|
||||
private String modelConfig;
|
||||
|
||||
/**
|
||||
* 扩展配置(JSON字符串)
|
||||
*/
|
||||
private String extensionConfig;
|
||||
|
||||
/**
|
||||
* 健康检查URL
|
||||
*/
|
||||
private String healthCheckUrl;
|
||||
|
||||
/**
|
||||
* 健康检查间隔(秒)
|
||||
*/
|
||||
private Integer healthCheckInterval;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiRatingRecord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI评价记录视图对象 ai_rating_record
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiRatingRecord.class)
|
||||
public class AiRatingRecordVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 消息ID
|
||||
*/
|
||||
private String messageId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 评分(1-5)
|
||||
*/
|
||||
private Integer rating;
|
||||
|
||||
/**
|
||||
* 反馈内容
|
||||
*/
|
||||
private String feedback;
|
||||
|
||||
/**
|
||||
* 评价维度
|
||||
*/
|
||||
private String dimension;
|
||||
|
||||
/**
|
||||
* 是否匿名(0否 1是)
|
||||
*/
|
||||
private Integer anonymous;
|
||||
|
||||
/**
|
||||
* 是否匿名(字符串显示)
|
||||
*/
|
||||
private String anonymousStr;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiSensitiveWord;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI敏感词视图对象 ai_sensitive_word
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiSensitiveWord.class)
|
||||
public class AiSensitiveWordVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 敏感词
|
||||
*/
|
||||
private String word;
|
||||
|
||||
/**
|
||||
* 词类型(PROFANITY/HATE_SPEECH/VIOLENCE/SEXUAL_CONTENT/ILLEGAL_ACTIVITY/PERSONAL_INFO/SPAM/MALICIOUS_CODE/POLITICAL_SENSITIVE/COMMERCIAL_AD)
|
||||
*/
|
||||
private String wordType;
|
||||
|
||||
/**
|
||||
* 词类型(字符串显示)
|
||||
*/
|
||||
private String wordTypeStr;
|
||||
|
||||
/**
|
||||
* 是否启用(0=禁用 1=启用)
|
||||
*/
|
||||
private Integer enabled;
|
||||
|
||||
/**
|
||||
* 是否启用(字符串显示)
|
||||
*/
|
||||
private String enabledStr;
|
||||
|
||||
/**
|
||||
* 优先级(数字越大优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.ai.domain.AiUsageStats;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI使用统计视图对象 ai_usage_stats
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = AiUsageStats.class)
|
||||
public class AiUsageStatsVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* Provider名称
|
||||
*/
|
||||
private String providerName;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 统计日期
|
||||
*/
|
||||
private Date statDate;
|
||||
|
||||
/**
|
||||
* 请求次数
|
||||
*/
|
||||
private Integer requestCount;
|
||||
|
||||
/**
|
||||
* 成功次数
|
||||
*/
|
||||
private Integer successCount;
|
||||
|
||||
/**
|
||||
* 错误次数
|
||||
*/
|
||||
private Integer errorCount;
|
||||
|
||||
/**
|
||||
* 总Token数
|
||||
*/
|
||||
private Long totalTokens;
|
||||
|
||||
/**
|
||||
* 总成本
|
||||
*/
|
||||
private BigDecimal totalCost;
|
||||
|
||||
/**
|
||||
* 平均响应时间(毫秒)
|
||||
*/
|
||||
private Integer avgResponseTime;
|
||||
|
||||
/**
|
||||
* 成功率(计算字段)
|
||||
*/
|
||||
private Double successRate;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.dromara.ai.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.ai.domain.AiUserQuota;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* AI用户配额视图对象 ai_user_quota
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@AutoMapper(target = AiUserQuota.class)
|
||||
public class AiUserQuotaVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 权限级别(GUEST/BASIC/PREMIUM/VIP/ADMIN)
|
||||
*/
|
||||
private String permissionLevel;
|
||||
|
||||
/**
|
||||
* 权限级别(字符串显示)
|
||||
*/
|
||||
private String permissionLevelStr;
|
||||
|
||||
/**
|
||||
* 每日配额限制(0表示无限制)
|
||||
*/
|
||||
private Long dailyQuota;
|
||||
|
||||
/**
|
||||
* 每月配额限制(0表示无限制)
|
||||
*/
|
||||
private Long monthlyQuota;
|
||||
|
||||
/**
|
||||
* 每日已使用配额
|
||||
*/
|
||||
private Long dailyUsed;
|
||||
|
||||
/**
|
||||
* 每月已使用配额
|
||||
*/
|
||||
private Long monthlyUsed;
|
||||
|
||||
/**
|
||||
* 每日剩余配额
|
||||
*/
|
||||
private Long dailyRemaining;
|
||||
|
||||
/**
|
||||
* 每月剩余配额
|
||||
*/
|
||||
private Long monthlyRemaining;
|
||||
|
||||
/**
|
||||
* 上次重置日配额时间
|
||||
*/
|
||||
private Date lastResetDaily;
|
||||
|
||||
/**
|
||||
* 上次重置月配额时间
|
||||
*/
|
||||
private Date lastResetMonthly;
|
||||
|
||||
/**
|
||||
* 自定义限制(JSON字符串)
|
||||
*/
|
||||
private String customLimits;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
package org.dromara.ai.error;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.domain.dto.AiChatRequest;
|
||||
import org.dromara.ai.domain.dto.AiChatResponse;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* AI错误处理和故障恢复服务
|
||||
* 提供统一的错误处理、重试机制和故障恢复功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AiErrorHandler {
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
// 错误统计
|
||||
private final Map<String, ErrorStatistics> errorStats = new ConcurrentHashMap<>();
|
||||
|
||||
// 熔断器状态
|
||||
private final Map<String, CircuitBreakerState> circuitBreakers = new ConcurrentHashMap<>();
|
||||
|
||||
// 重试配置
|
||||
private final Map<String, RetryConfig> retryConfigs = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 错误类型枚举
|
||||
*/
|
||||
public enum ErrorType {
|
||||
NETWORK_ERROR, // 网络错误
|
||||
TIMEOUT_ERROR, // 超时错误
|
||||
RATE_LIMIT_ERROR, // 速率限制错误
|
||||
QUOTA_EXCEEDED_ERROR, // 配额超限错误
|
||||
AUTHENTICATION_ERROR, // 认证错误
|
||||
AUTHORIZATION_ERROR, // 授权错误
|
||||
VALIDATION_ERROR, // 验证错误
|
||||
SERVICE_UNAVAILABLE, // 服务不可用
|
||||
INTERNAL_ERROR, // 内部错误
|
||||
UNKNOWN_ERROR // 未知错误
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误严重级别
|
||||
*/
|
||||
public enum ErrorSeverity {
|
||||
LOW, // 低级别
|
||||
MEDIUM, // 中级别
|
||||
HIGH, // 高级别
|
||||
CRITICAL // 严重级别
|
||||
}
|
||||
|
||||
/**
|
||||
* 熔断器状态
|
||||
*/
|
||||
public enum CircuitBreakerStatus {
|
||||
CLOSED, // 关闭状态(正常)
|
||||
OPEN, // 开启状态(熔断)
|
||||
HALF_OPEN // 半开状态(试探)
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误信息类
|
||||
*/
|
||||
public static class AiError {
|
||||
private String id;
|
||||
private ErrorType type;
|
||||
private ErrorSeverity severity;
|
||||
private String message;
|
||||
private String details;
|
||||
private String provider;
|
||||
private String model;
|
||||
private String userId;
|
||||
private LocalDateTime occurTime;
|
||||
private Map<String, Object> context;
|
||||
private Throwable cause;
|
||||
|
||||
// Getters and Setters
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public AiError setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public AiError setType(ErrorType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorSeverity getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public AiError setSeverity(ErrorSeverity severity) {
|
||||
this.severity = severity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public AiError setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public AiError setDetails(String details) {
|
||||
this.details = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public AiError setProvider(String provider) {
|
||||
this.provider = provider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public AiError setModel(String model) {
|
||||
this.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public AiError setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LocalDateTime getOccurTime() {
|
||||
return occurTime;
|
||||
}
|
||||
|
||||
public AiError setOccurTime(LocalDateTime occurTime) {
|
||||
this.occurTime = occurTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public AiError setContext(Map<String, Object> context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
public AiError setCause(Throwable cause) {
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误统计类
|
||||
*/
|
||||
public static class ErrorStatistics {
|
||||
private String key;
|
||||
private long totalErrors;
|
||||
private long networkErrors;
|
||||
private long timeoutErrors;
|
||||
private long rateLimitErrors;
|
||||
private long quotaErrors;
|
||||
private long authErrors;
|
||||
private long validationErrors;
|
||||
private long serviceUnavailableErrors;
|
||||
private long internalErrors;
|
||||
private long unknownErrors;
|
||||
private LocalDateTime lastErrorTime;
|
||||
private LocalDateTime firstErrorTime;
|
||||
private double errorRate;
|
||||
|
||||
public ErrorStatistics(String key) {
|
||||
this.key = key;
|
||||
this.firstErrorTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void incrementError(ErrorType type) {
|
||||
totalErrors++;
|
||||
lastErrorTime = LocalDateTime.now();
|
||||
|
||||
switch (type) {
|
||||
case NETWORK_ERROR -> networkErrors++;
|
||||
case TIMEOUT_ERROR -> timeoutErrors++;
|
||||
case RATE_LIMIT_ERROR -> rateLimitErrors++;
|
||||
case QUOTA_EXCEEDED_ERROR -> quotaErrors++;
|
||||
case AUTHENTICATION_ERROR, AUTHORIZATION_ERROR -> authErrors++;
|
||||
case VALIDATION_ERROR -> validationErrors++;
|
||||
case SERVICE_UNAVAILABLE -> serviceUnavailableErrors++;
|
||||
case INTERNAL_ERROR -> internalErrors++;
|
||||
default -> unknownErrors++;
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public long getTotalErrors() {
|
||||
return totalErrors;
|
||||
}
|
||||
|
||||
public long getNetworkErrors() {
|
||||
return networkErrors;
|
||||
}
|
||||
|
||||
public long getTimeoutErrors() {
|
||||
return timeoutErrors;
|
||||
}
|
||||
|
||||
public long getRateLimitErrors() {
|
||||
return rateLimitErrors;
|
||||
}
|
||||
|
||||
public long getQuotaErrors() {
|
||||
return quotaErrors;
|
||||
}
|
||||
|
||||
public long getAuthErrors() {
|
||||
return authErrors;
|
||||
}
|
||||
|
||||
public long getValidationErrors() {
|
||||
return validationErrors;
|
||||
}
|
||||
|
||||
public long getServiceUnavailableErrors() {
|
||||
return serviceUnavailableErrors;
|
||||
}
|
||||
|
||||
public long getInternalErrors() {
|
||||
return internalErrors;
|
||||
}
|
||||
|
||||
public long getUnknownErrors() {
|
||||
return unknownErrors;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastErrorTime() {
|
||||
return lastErrorTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getFirstErrorTime() {
|
||||
return firstErrorTime;
|
||||
}
|
||||
|
||||
public double getErrorRate() {
|
||||
return errorRate;
|
||||
}
|
||||
|
||||
public void setErrorRate(double errorRate) {
|
||||
this.errorRate = errorRate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 熔断器状态类
|
||||
*/
|
||||
public static class CircuitBreakerState {
|
||||
private String key;
|
||||
private CircuitBreakerStatus status;
|
||||
private long failureCount;
|
||||
private long successCount;
|
||||
private LocalDateTime lastFailureTime;
|
||||
private LocalDateTime lastSuccessTime;
|
||||
private LocalDateTime nextRetryTime;
|
||||
private int failureThreshold;
|
||||
private int successThreshold;
|
||||
private long timeoutMs;
|
||||
|
||||
public CircuitBreakerState(String key) {
|
||||
this.key = key;
|
||||
this.status = CircuitBreakerStatus.CLOSED;
|
||||
this.failureThreshold = 5;
|
||||
this.successThreshold = 3;
|
||||
this.timeoutMs = 60000; // 1分钟
|
||||
}
|
||||
|
||||
public void recordSuccess() {
|
||||
successCount++;
|
||||
lastSuccessTime = LocalDateTime.now();
|
||||
|
||||
if (status == CircuitBreakerStatus.HALF_OPEN && successCount >= successThreshold) {
|
||||
status = CircuitBreakerStatus.CLOSED;
|
||||
failureCount = 0;
|
||||
successCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void recordFailure() {
|
||||
failureCount++;
|
||||
lastFailureTime = LocalDateTime.now();
|
||||
|
||||
if (status == CircuitBreakerStatus.CLOSED && failureCount >= failureThreshold) {
|
||||
status = CircuitBreakerStatus.OPEN;
|
||||
nextRetryTime = LocalDateTime.now().plusNanos(timeoutMs * 1_000_000);
|
||||
} else if (status == CircuitBreakerStatus.HALF_OPEN) {
|
||||
status = CircuitBreakerStatus.OPEN;
|
||||
nextRetryTime = LocalDateTime.now().plusNanos(timeoutMs * 1_000_000);
|
||||
successCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canExecute() {
|
||||
if (status == CircuitBreakerStatus.CLOSED) {
|
||||
return true;
|
||||
} else if (status == CircuitBreakerStatus.OPEN) {
|
||||
if (LocalDateTime.now().isAfter(nextRetryTime)) {
|
||||
status = CircuitBreakerStatus.HALF_OPEN;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else { // HALF_OPEN
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public CircuitBreakerStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public long getFailureCount() {
|
||||
return failureCount;
|
||||
}
|
||||
|
||||
public long getSuccessCount() {
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastFailureTime() {
|
||||
return lastFailureTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getLastSuccessTime() {
|
||||
return lastSuccessTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getNextRetryTime() {
|
||||
return nextRetryTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试配置类
|
||||
*/
|
||||
public static class RetryConfig {
|
||||
private int maxAttempts;
|
||||
private long initialDelayMs;
|
||||
private long maxDelayMs;
|
||||
private double backoffMultiplier;
|
||||
private boolean exponentialBackoff;
|
||||
|
||||
public RetryConfig() {
|
||||
this.maxAttempts = 3;
|
||||
this.initialDelayMs = 1000;
|
||||
this.maxDelayMs = 30000;
|
||||
this.backoffMultiplier = 2.0;
|
||||
this.exponentialBackoff = true;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public int getMaxAttempts() {
|
||||
return maxAttempts;
|
||||
}
|
||||
|
||||
public RetryConfig setMaxAttempts(int maxAttempts) {
|
||||
this.maxAttempts = maxAttempts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getInitialDelayMs() {
|
||||
return initialDelayMs;
|
||||
}
|
||||
|
||||
public RetryConfig setInitialDelayMs(long initialDelayMs) {
|
||||
this.initialDelayMs = initialDelayMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getMaxDelayMs() {
|
||||
return maxDelayMs;
|
||||
}
|
||||
|
||||
public RetryConfig setMaxDelayMs(long maxDelayMs) {
|
||||
this.maxDelayMs = maxDelayMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getBackoffMultiplier() {
|
||||
return backoffMultiplier;
|
||||
}
|
||||
|
||||
public RetryConfig setBackoffMultiplier(double backoffMultiplier) {
|
||||
this.backoffMultiplier = backoffMultiplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isExponentialBackoff() {
|
||||
return exponentialBackoff;
|
||||
}
|
||||
|
||||
public RetryConfig setExponentialBackoff(boolean exponentialBackoff) {
|
||||
this.exponentialBackoff = exponentialBackoff;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行带错误处理的操作
|
||||
*
|
||||
* @param key 操作标识
|
||||
* @param operation 操作函数
|
||||
* @param request 请求对象
|
||||
* @return 响应结果
|
||||
*/
|
||||
public AiChatResponse executeWithErrorHandling(String key,
|
||||
Function<AiChatRequest, AiChatResponse> operation,
|
||||
AiChatRequest request) {
|
||||
CircuitBreakerState circuitBreaker = getOrCreateCircuitBreaker(key);
|
||||
|
||||
// 检查熔断器状态
|
||||
if (!circuitBreaker.canExecute()) {
|
||||
AiError error = new AiError()
|
||||
.setType(ErrorType.SERVICE_UNAVAILABLE)
|
||||
.setSeverity(ErrorSeverity.HIGH)
|
||||
.setMessage("服务熔断中,暂时不可用")
|
||||
.setProvider(key)
|
||||
.setUserId(request.getUserId())
|
||||
.setOccurTime(LocalDateTime.now());
|
||||
|
||||
recordError(key, error);
|
||||
return createErrorResponse(error, request);
|
||||
}
|
||||
|
||||
RetryConfig retryConfig = getOrCreateRetryConfig(key);
|
||||
|
||||
for (int attempt = 1; attempt <= retryConfig.getMaxAttempts(); attempt++) {
|
||||
try {
|
||||
AiChatResponse response = operation.apply(request);
|
||||
|
||||
if (response.getSuccess() != null && response.getSuccess()) {
|
||||
circuitBreaker.recordSuccess();
|
||||
return response;
|
||||
} else {
|
||||
// 业务错误
|
||||
AiError error = createErrorFromResponse(response, request);
|
||||
recordError(key, error);
|
||||
|
||||
if (attempt == retryConfig.getMaxAttempts() || !isRetryableError(error.getType())) {
|
||||
circuitBreaker.recordFailure();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
AiError error = createErrorFromException(e, key, request);
|
||||
recordError(key, error);
|
||||
|
||||
if (attempt == retryConfig.getMaxAttempts() || !isRetryableError(error.getType())) {
|
||||
circuitBreaker.recordFailure();
|
||||
return createErrorResponse(error, request);
|
||||
}
|
||||
|
||||
// 等待重试
|
||||
if (attempt < retryConfig.getMaxAttempts()) {
|
||||
try {
|
||||
long delay = calculateRetryDelay(retryConfig, attempt);
|
||||
Thread.sleep(delay);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 所有重试都失败
|
||||
AiError error = new AiError()
|
||||
.setType(ErrorType.INTERNAL_ERROR)
|
||||
.setSeverity(ErrorSeverity.HIGH)
|
||||
.setMessage("所有重试尝试都失败")
|
||||
.setProvider(key)
|
||||
.setUserId(request.getUserId())
|
||||
.setOccurTime(LocalDateTime.now());
|
||||
|
||||
circuitBreaker.recordFailure();
|
||||
return createErrorResponse(error, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误
|
||||
*
|
||||
* @param key 错误标识
|
||||
* @param error 错误信息
|
||||
*/
|
||||
public void recordError(String key, AiError error) {
|
||||
ErrorStatistics stats = errorStats.computeIfAbsent(key, ErrorStatistics::new);
|
||||
stats.incrementError(error.getType());
|
||||
|
||||
log.error("AI错误记录 - Key: {}, Type: {}, Severity: {}, Message: {}",
|
||||
key, error.getType(), error.getSeverity(), error.getMessage());
|
||||
|
||||
// 根据错误严重级别进行不同处理
|
||||
switch (error.getSeverity()) {
|
||||
case CRITICAL -> handleCriticalError(error);
|
||||
case HIGH -> handleHighSeverityError(error);
|
||||
case MEDIUM -> handleMediumSeverityError(error);
|
||||
case LOW -> handleLowSeverityError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误统计
|
||||
*
|
||||
* @param key 统计标识
|
||||
* @return 错误统计
|
||||
*/
|
||||
public ErrorStatistics getErrorStatistics(String key) {
|
||||
return errorStats.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有错误统计
|
||||
*
|
||||
* @return 错误统计映射
|
||||
*/
|
||||
public Map<String, ErrorStatistics> getAllErrorStatistics() {
|
||||
return new ConcurrentHashMap<>(errorStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取熔断器状态
|
||||
*
|
||||
* @param key 熔断器标识
|
||||
* @return 熔断器状态
|
||||
*/
|
||||
public CircuitBreakerState getCircuitBreakerState(String key) {
|
||||
return circuitBreakers.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置熔断器
|
||||
*
|
||||
* @param key 熔断器标识
|
||||
*/
|
||||
public void resetCircuitBreaker(String key) {
|
||||
CircuitBreakerState state = circuitBreakers.get(key);
|
||||
if (state != null) {
|
||||
state.status = CircuitBreakerStatus.CLOSED;
|
||||
state.failureCount = 0;
|
||||
state.successCount = 0;
|
||||
log.info("熔断器已重置: {}", key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置重试配置
|
||||
*
|
||||
* @param key 配置标识
|
||||
* @param config 重试配置
|
||||
*/
|
||||
public void setRetryConfig(String key, RetryConfig config) {
|
||||
retryConfigs.put(key, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理错误统计
|
||||
*/
|
||||
public void clearErrorStatistics() {
|
||||
errorStats.clear();
|
||||
log.info("错误统计已清理");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建熔断器
|
||||
*/
|
||||
private CircuitBreakerState getOrCreateCircuitBreaker(String key) {
|
||||
return circuitBreakers.computeIfAbsent(key, CircuitBreakerState::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建重试配置
|
||||
*/
|
||||
private RetryConfig getOrCreateRetryConfig(String key) {
|
||||
return retryConfigs.computeIfAbsent(key, k -> new RetryConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为可重试错误
|
||||
*/
|
||||
private boolean isRetryableError(ErrorType errorType) {
|
||||
return switch (errorType) {
|
||||
case NETWORK_ERROR, TIMEOUT_ERROR, SERVICE_UNAVAILABLE, RATE_LIMIT_ERROR -> true;
|
||||
case AUTHENTICATION_ERROR, AUTHORIZATION_ERROR, VALIDATION_ERROR, QUOTA_EXCEEDED_ERROR -> false;
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算重试延迟
|
||||
*/
|
||||
private long calculateRetryDelay(RetryConfig config, int attempt) {
|
||||
if (!config.isExponentialBackoff()) {
|
||||
return config.getInitialDelayMs();
|
||||
}
|
||||
|
||||
long delay = (long) (config.getInitialDelayMs() * Math.pow(config.getBackoffMultiplier(), attempt - 1));
|
||||
return Math.min(delay, config.getMaxDelayMs());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从异常创建错误对象
|
||||
*/
|
||||
private AiError createErrorFromException(Exception e, String provider, AiChatRequest request) {
|
||||
ErrorType type = classifyException(e);
|
||||
ErrorSeverity severity = determineSeverity(type);
|
||||
|
||||
return new AiError()
|
||||
.setId(java.util.UUID.randomUUID().toString())
|
||||
.setType(type)
|
||||
.setSeverity(severity)
|
||||
.setMessage(e.getMessage())
|
||||
.setDetails(getStackTrace(e))
|
||||
.setProvider(provider)
|
||||
.setModel(request.getModel())
|
||||
.setUserId(request.getUserId())
|
||||
.setOccurTime(LocalDateTime.now())
|
||||
.setCause(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从响应创建错误对象
|
||||
*/
|
||||
private AiError createErrorFromResponse(AiChatResponse response, AiChatRequest request) {
|
||||
ErrorType type = classifyResponseError(response);
|
||||
ErrorSeverity severity = determineSeverity(type);
|
||||
|
||||
return new AiError()
|
||||
.setId(java.util.UUID.randomUUID().toString())
|
||||
.setType(type)
|
||||
.setSeverity(severity)
|
||||
.setMessage(response.getError())
|
||||
.setDetails(response.getErrorCode())
|
||||
.setProvider(response.getProvider())
|
||||
.setModel(response.getModel())
|
||||
.setUserId(request.getUserId())
|
||||
.setOccurTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类异常类型
|
||||
*/
|
||||
private ErrorType classifyException(Exception e) {
|
||||
String message = e.getMessage();
|
||||
if (message == null) {
|
||||
return ErrorType.UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
message = message.toLowerCase();
|
||||
|
||||
if (message.contains("timeout") || message.contains("timed out")) {
|
||||
return ErrorType.TIMEOUT_ERROR;
|
||||
} else if (message.contains("network") || message.contains("connection")) {
|
||||
return ErrorType.NETWORK_ERROR;
|
||||
} else if (message.contains("rate limit") || message.contains("too many requests")) {
|
||||
return ErrorType.RATE_LIMIT_ERROR;
|
||||
} else if (message.contains("quota") || message.contains("limit exceeded")) {
|
||||
return ErrorType.QUOTA_EXCEEDED_ERROR;
|
||||
} else if (message.contains("unauthorized") || message.contains("authentication")) {
|
||||
return ErrorType.AUTHENTICATION_ERROR;
|
||||
} else if (message.contains("forbidden") || message.contains("access denied")) {
|
||||
return ErrorType.AUTHORIZATION_ERROR;
|
||||
} else if (message.contains("validation") || message.contains("invalid")) {
|
||||
return ErrorType.VALIDATION_ERROR;
|
||||
} else if (message.contains("service unavailable") || message.contains("server error")) {
|
||||
return ErrorType.SERVICE_UNAVAILABLE;
|
||||
} else {
|
||||
return ErrorType.INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类响应错误类型
|
||||
*/
|
||||
private ErrorType classifyResponseError(AiChatResponse response) {
|
||||
String errorCode = response.getErrorCode();
|
||||
String errorMessage = response.getError();
|
||||
|
||||
if (errorCode != null) {
|
||||
return switch (errorCode.toUpperCase()) {
|
||||
case "TIMEOUT", "TIMEOUT_ERROR" -> ErrorType.TIMEOUT_ERROR;
|
||||
case "RATE_LIMIT", "RATE_LIMIT_ERROR" -> ErrorType.RATE_LIMIT_ERROR;
|
||||
case "QUOTA_EXCEEDED" -> ErrorType.QUOTA_EXCEEDED_ERROR;
|
||||
case "AUTH_ERROR", "AUTHENTICATION_ERROR" -> ErrorType.AUTHENTICATION_ERROR;
|
||||
case "AUTHORIZATION_ERROR" -> ErrorType.AUTHORIZATION_ERROR;
|
||||
case "VALIDATION_ERROR" -> ErrorType.VALIDATION_ERROR;
|
||||
case "SERVICE_UNAVAILABLE" -> ErrorType.SERVICE_UNAVAILABLE;
|
||||
case "NETWORK_ERROR" -> ErrorType.NETWORK_ERROR;
|
||||
default -> ErrorType.INTERNAL_ERROR;
|
||||
};
|
||||
}
|
||||
|
||||
if (errorMessage != null) {
|
||||
return classifyException(new RuntimeException(errorMessage));
|
||||
}
|
||||
|
||||
return ErrorType.UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定错误严重级别
|
||||
*/
|
||||
private ErrorSeverity determineSeverity(ErrorType errorType) {
|
||||
return switch (errorType) {
|
||||
case AUTHENTICATION_ERROR, AUTHORIZATION_ERROR -> ErrorSeverity.CRITICAL;
|
||||
case SERVICE_UNAVAILABLE, QUOTA_EXCEEDED_ERROR -> ErrorSeverity.HIGH;
|
||||
case NETWORK_ERROR, TIMEOUT_ERROR, RATE_LIMIT_ERROR -> ErrorSeverity.MEDIUM;
|
||||
case VALIDATION_ERROR -> ErrorSeverity.LOW;
|
||||
default -> ErrorSeverity.MEDIUM;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误响应
|
||||
*/
|
||||
private AiChatResponse createErrorResponse(AiError error, AiChatRequest request) {
|
||||
return new AiChatResponse()
|
||||
.setSuccess(false)
|
||||
.setError(error.getMessage())
|
||||
.setErrorCode(error.getType().name())
|
||||
.setModel(request.getModel())
|
||||
.setProvider(error.getProvider())
|
||||
.setCreateTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常堆栈信息
|
||||
*/
|
||||
private String getStackTrace(Exception e) {
|
||||
java.io.StringWriter sw = new java.io.StringWriter();
|
||||
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理严重错误
|
||||
*/
|
||||
private void handleCriticalError(AiError error) {
|
||||
log.error("严重错误发生: {}", error.getMessage());
|
||||
// 可以发送告警、通知等
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理高级别错误
|
||||
*/
|
||||
private void handleHighSeverityError(AiError error) {
|
||||
log.warn("高级别错误发生: {}", error.getMessage());
|
||||
// 可以记录到特殊日志、发送通知等
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理中级别错误
|
||||
*/
|
||||
private void handleMediumSeverityError(AiError error) {
|
||||
log.warn("中级别错误发生: {}", error.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理低级别错误
|
||||
*/
|
||||
private void handleLowSeverityError(AiError error) {
|
||||
log.debug("低级别错误发生: {}", error.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁资源
|
||||
*/
|
||||
public void destroy() {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
log.info("AI错误处理器已销毁");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.ai.domain.AiChatRecord;
|
||||
import org.dromara.ai.domain.vo.AiChatRecordVo;
|
||||
|
||||
/**
|
||||
* AI聊天记录Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiChatRecordMapper extends BaseMapperPlus<AiChatRecord, AiChatRecordVo> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiPaintingFavorite;
|
||||
import org.dromara.ai.domain.vo.AiPaintingFavoriteVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI绘画作品收藏Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiPaintingFavoriteMapper extends BaseMapperPlus<AiPaintingFavorite, AiPaintingFavoriteVo> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiPaintingRecord;
|
||||
import org.dromara.ai.domain.vo.AiPaintingRecordVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI绘画历史记录Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiPaintingRecordMapper extends BaseMapperPlus<AiPaintingRecord, AiPaintingRecordVo> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiPaintingTemplate;
|
||||
import org.dromara.ai.domain.vo.AiPaintingTemplateVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI绘画参数模板Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiPaintingTemplateMapper extends BaseMapperPlus<AiPaintingTemplate, AiPaintingTemplateVo> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiProviderConfigHistory;
|
||||
import org.dromara.ai.domain.vo.AiProviderConfigHistoryVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI Provider配置历史Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiProviderConfigHistoryMapper extends BaseMapperPlus<AiProviderConfigHistory, AiProviderConfigHistoryVo> {
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiProviderConfig;
|
||||
import org.dromara.ai.domain.vo.AiProviderConfigVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI Provider配置Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiProviderConfigMapper extends BaseMapperPlus<AiProviderConfig, AiProviderConfigVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiRatingRecord;
|
||||
import org.dromara.ai.domain.vo.AiRatingRecordVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI评价记录Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiRatingRecordMapper extends BaseMapperPlus<AiRatingRecord, AiRatingRecordVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiSensitiveWord;
|
||||
import org.dromara.ai.domain.vo.AiSensitiveWordVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI敏感词Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiSensitiveWordMapper extends BaseMapperPlus<AiSensitiveWord, AiSensitiveWordVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.ai.domain.AiUsageStats;
|
||||
import org.dromara.ai.domain.vo.AiUsageStatsVo;
|
||||
|
||||
/**
|
||||
* AI使用统计Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiUsageStatsMapper extends BaseMapperPlus<AiUsageStats, AiUsageStatsVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.dromara.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.dromara.ai.domain.AiUserQuota;
|
||||
import org.dromara.ai.domain.vo.AiUserQuotaVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* AI用户配额Mapper接口
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiUserQuotaMapper extends BaseMapperPlus<AiUserQuota, AiUserQuotaVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.dromara.ai.mcp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.domain.dto.AiChatRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP集成服务
|
||||
* 提供MCP工具与AI聊天请求的集成功能
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class McpIntegrationService {
|
||||
|
||||
private final McpToolRegistry registry;
|
||||
|
||||
/**
|
||||
* 增强消息,添加可用工具信息
|
||||
*
|
||||
* @param request 聊天请求
|
||||
*/
|
||||
public void enrichMessages(AiChatRequest request) {
|
||||
if (!Boolean.TRUE.equals(request.getEnableTools())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> names = request.getRequestedTools() != null
|
||||
? request.getRequestedTools()
|
||||
: new ArrayList<>(registry.all().keySet());
|
||||
|
||||
if (names.isEmpty()) {
|
||||
log.debug("未找到可用的MCP工具");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("可用工具: ").append(String.join(",", names));
|
||||
AiChatRequest.AiMessage sys = new AiChatRequest.AiMessage()
|
||||
.setRole("system")
|
||||
.setContent(sb.toString());
|
||||
|
||||
List<AiChatRequest.AiMessage> msgs = new ArrayList<>();
|
||||
msgs.add(sys);
|
||||
if (request.getMessages() != null) {
|
||||
msgs.addAll(request.getMessages());
|
||||
}
|
||||
request.setMessages(msgs);
|
||||
|
||||
log.debug("MCP工具消息增强完成 - 工具数量: {}", names.size());
|
||||
} catch (Exception e) {
|
||||
log.error("MCP工具消息增强失败", e);
|
||||
// 增强失败不影响主流程,只记录日志
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用MCP工具
|
||||
*
|
||||
* @param name 工具名称
|
||||
* @param input 工具输入参数
|
||||
* @return 工具执行结果
|
||||
*/
|
||||
public Map<String, Object> invokeTool(String name, Map<String, Object> input) {
|
||||
try {
|
||||
log.debug("调用MCP工具: {}, 输入参数: {}", name, input != null ? input.keySet() : "无");
|
||||
|
||||
McpTool tool = registry.get(name);
|
||||
if (tool == null) {
|
||||
log.warn("MCP工具不存在: {}", name);
|
||||
return Map.of("error", "tool_not_found", "toolName", name);
|
||||
}
|
||||
|
||||
Map<String, Object> result = tool.invoke(input != null ? input : Map.of());
|
||||
|
||||
if (result != null && result.containsKey("error")) {
|
||||
log.warn("MCP工具调用失败: {}, 错误: {}", name, result.get("error"));
|
||||
} else {
|
||||
log.debug("MCP工具调用成功: {}", name);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("MCP工具调用异常: {}", name, e);
|
||||
return Map.of("error", "tool_invocation_error", "message", e.getMessage(), "toolName", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.dromara.ai.mcp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP工具接口
|
||||
* 定义MCP工具的标准接口规范
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
public interface McpTool {
|
||||
String name();
|
||||
|
||||
String description();
|
||||
|
||||
Map<String, Object> schema();
|
||||
|
||||
Map<String, Object> invoke(Map<String, Object> input);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.dromara.ai.mcp;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* MCP工具注册表
|
||||
* 管理所有MCP工具的注册、版本控制和动态加载
|
||||
*
|
||||
* @author pengles
|
||||
*/
|
||||
@Component
|
||||
public class McpToolRegistry {
|
||||
private final Map<String, McpTool> tools = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> versions = new ConcurrentHashMap<>();
|
||||
private final Map<String, Supplier<McpTool>> factories = new ConcurrentHashMap<>();
|
||||
|
||||
public void register(McpTool tool) {
|
||||
tools.put(tool.name(), tool);
|
||||
versions.put(tool.name(), "1.0.0");
|
||||
}
|
||||
|
||||
public McpTool get(String name) {
|
||||
return tools.get(name);
|
||||
}
|
||||
|
||||
public Map<String, McpTool> all() {
|
||||
return new HashMap<>(tools);
|
||||
}
|
||||
|
||||
public void unregister(String name) {
|
||||
tools.remove(name);
|
||||
versions.remove(name);
|
||||
factories.remove(name);
|
||||
}
|
||||
|
||||
public void registerFactory(String name, String version, Supplier<McpTool> factory) {
|
||||
factories.put(name, factory);
|
||||
versions.put(name, version);
|
||||
tools.put(name, factory.get());
|
||||
}
|
||||
|
||||
public void reload(String name) {
|
||||
Supplier<McpTool> f = factories.get(name);
|
||||
if (f != null) {
|
||||
tools.put(name, f.get());
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> metadata(String name) {
|
||||
McpTool t = tools.get(name);
|
||||
String v = versions.get(name);
|
||||
if (t == null) return Map.of();
|
||||
return Map.of("name", t.name(), "version", v);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.dromara.ai.mcp.tools;
|
||||
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AlgorithmComputeTool implements McpTool {
|
||||
@Override
|
||||
public String name() {
|
||||
return "algorithm_compute";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "执行简单算法计算";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> schema() {
|
||||
Map<String, Object> s = new HashMap<>();
|
||||
s.put("type", "object");
|
||||
s.put("properties", Map.of("n", Map.of("type", "integer")));
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> invoke(Map<String, Object> input) {
|
||||
int n = Integer.parseInt(String.valueOf(input.getOrDefault("n", 10)));
|
||||
long sum = 0;
|
||||
for (int i = 1; i <= n; i++) sum += i;
|
||||
return Map.of("sum", sum);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.dromara.ai.mcp.tools;
|
||||
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ApiCallTool implements McpTool {
|
||||
@Override
|
||||
public String name() {
|
||||
return "api_call";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "调用HTTP API";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> schema() {
|
||||
Map<String, Object> s = new HashMap<>();
|
||||
s.put("type", "object");
|
||||
s.put("properties", Map.of("url", Map.of("type", "string")));
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> invoke(Map<String, Object> input) {
|
||||
try {
|
||||
String url = String.valueOf(input.get("url"));
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
|
||||
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
return Map.of("status", resp.statusCode(), "body", resp.body());
|
||||
} catch (Exception e) {
|
||||
return Map.of("error", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.dromara.ai.mcp.tools;
|
||||
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DataTransformTool implements McpTool {
|
||||
@Override
|
||||
public String name() {
|
||||
return "data_transform";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "数据格式转换";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> schema() {
|
||||
Map<String, Object> s = new HashMap<>();
|
||||
s.put("type", "object");
|
||||
s.put("properties", Map.of("items", Map.of("type", "array")));
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> invoke(Map<String, Object> input) {
|
||||
Object items = input.get("items");
|
||||
return Map.of("count", items instanceof List ? ((List<?>) items).size() : 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.dromara.ai.mcp.tools;
|
||||
|
||||
import org.apache.tika.Tika;
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FileParseTool implements McpTool {
|
||||
@Override
|
||||
public String name() {
|
||||
return "file_parse";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "解析文件内容";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> schema() {
|
||||
Map<String, Object> s = new HashMap<>();
|
||||
s.put("type", "object");
|
||||
s.put("properties", Map.of("path", Map.of("type", "string")));
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> invoke(Map<String, Object> input) {
|
||||
try {
|
||||
String path = String.valueOf(input.get("path"));
|
||||
Tika tika = new Tika();
|
||||
String text = tika.parseToString(new java.io.File(path));
|
||||
return Map.of("text", text);
|
||||
} catch (Exception e) {
|
||||
return Map.of("error", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.dromara.ai.mcp.tools;
|
||||
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpFetchTool implements McpTool {
|
||||
@Override
|
||||
public String name() {
|
||||
return "http_fetch";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "HTTP抓取";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> schema() {
|
||||
Map<String, Object> s = new HashMap<>();
|
||||
s.put("type", "object");
|
||||
s.put("properties", Map.of("url", Map.of("type", "string")));
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> invoke(Map<String, Object> input) {
|
||||
try {
|
||||
String url = String.valueOf(input.get("url"));
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
|
||||
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
return Map.of("status", resp.statusCode(), "body", resp.body());
|
||||
} catch (Exception e) {
|
||||
return Map.of("error", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.dromara.ai.mcp.tools;
|
||||
|
||||
import org.dromara.ai.mcp.McpTool;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ProjectScaffoldTool implements McpTool {
|
||||
@Override
|
||||
public String name() {
|
||||
return "project_scaffold";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "生成项目脚手架方案";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> schema() {
|
||||
Map<String, Object> s = new HashMap<>();
|
||||
s.put("type", "object");
|
||||
s.put("properties", Map.of("type", Map.of("type", "string")));
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> invoke(Map<String, Object> input) {
|
||||
String type = String.valueOf(input.getOrDefault("type", "webapp"));
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
out.put("plan", "生成 " + type + " 脚手架,包含依赖、目录结构与初始化命令");
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.dromara.ai.model;
|
||||
|
||||
public class ApiResult<T> {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private String code;
|
||||
private T data;
|
||||
|
||||
public static <T> ApiResult<T> ok(T data) {
|
||||
ApiResult<T> r = new ApiResult<>();
|
||||
r.success = true;
|
||||
r.data = data;
|
||||
return r;
|
||||
}
|
||||
|
||||
public static <T> ApiResult<T> fail(String code, String message) {
|
||||
ApiResult<T> r = new ApiResult<>();
|
||||
r.success = false;
|
||||
r.code = code;
|
||||
r.message = message;
|
||||
return r;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public ApiResult<T> setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ApiResult<T> setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ApiResult<T> setCode(String code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ApiResult<T> setData(T data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user