mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-16 13:23:42 +00:00
Compare commits
29 Commits
v2.0.3
...
9c2586ab43
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c2586ab43 | ||
|
|
1bddf5df3b | ||
|
|
57b2f833f8 | ||
|
|
7907cbeb7f | ||
|
|
9cfcdd2b9b | ||
|
|
980df20752 | ||
|
|
aa92d232bb | ||
|
|
81c0bb5738 | ||
|
|
1a645c6e10 | ||
|
|
731f6ceb6e | ||
|
|
8e21245348 | ||
|
|
936e157e4a | ||
|
|
256f72c487 | ||
|
|
009aa5f1a5 | ||
|
|
d22d2cb708 | ||
|
|
59104028b6 | ||
|
|
c2f6a8321a | ||
|
|
5476a4b0b7 | ||
|
|
cd490aa0e5 | ||
|
|
b1ff44df4b | ||
|
|
dc6d00f0fc | ||
|
|
990da8da6f | ||
|
|
8fc7ad0359 | ||
|
|
8a89d9eb9c | ||
|
|
abfa9ae97b | ||
|
|
bde2e8ff8e | ||
|
|
4d8eb1850f | ||
|
|
5aaa3a5662 | ||
|
|
8fec96f5b2 |
35
README.md
35
README.md
@@ -63,18 +63,19 @@
|
|||||||
用户名: admin 密码:admin123
|
用户名: admin 密码:admin123
|
||||||
|
|
||||||
### 源码地址
|
### 源码地址
|
||||||
[1]github
|
|
||||||
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
|
|
||||||
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
|
|
||||||
- 前端服务-小程序端: https://github.com/ageerle/ruoyi-uniapp
|
|
||||||
- 后端服务:https://github.com/ageerle/ruoyi-ai
|
|
||||||
|
|
||||||
[2]gitee
|
[1]gitee
|
||||||
- 前端服务-用户端: https://gitee.com/ageerle/ruoyi-web
|
- 前端服务-用户端: https://gitee.com/ageerle/ruoyi-web
|
||||||
- 前端服务-管理端: https://gitee.com/ageerle/ruoyi-admin
|
- 前端服务-管理端: https://gitee.com/ageerle/ruoyi-admin
|
||||||
- 前端服务-小程序端: https://gitee.com/ageerle/ruoyi-uniapp
|
- 前端服务-小程序端: https://gitee.com/ageerle/ruoyi-uniapp
|
||||||
- 后端服务:https://gitee.com/ageerle/ruoyi-ai
|
- 后端服务:https://gitee.com/ageerle/ruoyi-ai
|
||||||
|
|
||||||
|
[2]github
|
||||||
|
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
|
||||||
|
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
|
||||||
|
- 前端服务-小程序端: https://github.com/ageerle/ruoyi-uniapp
|
||||||
|
- 后端服务:https://github.com/ageerle/ruoyi-ai
|
||||||
|
|
||||||
[3]gitcode
|
[3]gitcode
|
||||||
- 前端服务-用户端:https://gitcode.com/ageerle/ruoyi-web
|
- 前端服务-用户端:https://gitcode.com/ageerle/ruoyi-web
|
||||||
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
|
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
|
|
||||||
### 配套文档
|
### 配套文档
|
||||||
- 配套文档: https://doc.pandarobot.chat
|
- 配套文档: https://doc.pandarobot.chat
|
||||||
- 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
|
- 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
|
||||||
|
|
||||||
### 核心功能
|
### 核心功能
|
||||||
1. 全套开源系统:提供完整的前端应用、后台管理以及小程序应用,基于MIT协议,开箱即用。
|
1. 全套开源系统:提供完整的前端应用、后台管理以及小程序应用,基于MIT协议,开箱即用。
|
||||||
@@ -96,7 +97,16 @@
|
|||||||
|
|
||||||
### 项目演示
|
### 项目演示
|
||||||
|
|
||||||
#### mcp支持(需要切换dev分支 下周发布正式版)
|
#### mcp支持
|
||||||
|
|
||||||
|
### 如何使用
|
||||||
|
1. ruoyi-admin\src\main\resources\application.yml中mcp.client.enabled改为true
|
||||||
|
2. application.yml中配置openai api-key(用于推理使用那个工具,并构建工具所需参数)
|
||||||
|
3. 启动[ruoyi-mcp-server]
|
||||||
|
4. [mcp-server.json]中配置fileSystem.command(npx本地安装路径)
|
||||||
|
5. 指定fileSystem操作目录(本地必须存在指定的目录)
|
||||||
|
6. 配置search1api.env.SEARCH1API_KEY 申请地址:https://www.search1api.com/
|
||||||
|
7. 详情教程:https://blog.csdn.net/weixin_42416319/article/details/147385808
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
||||||
<img src="image/mcp-01.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
<img src="image/mcp-01.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||||
<img src="image/mcp-02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
<img src="image/mcp-02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||||
@@ -215,9 +225,12 @@
|
|||||||
|
|
||||||
### 开发计划
|
### 开发计划
|
||||||
|
|
||||||
- 流程编排
|
| 主题 | 方向 | 时间节点 |
|
||||||
|
| --- |-----------------------------------|--------|
|
||||||
|
| 前端简化版 | 与element-plus-x框架合作,推出基于该框架的前端简化版 | 2025.5 |
|
||||||
|
| agent2agent | Agent2Agent协议支持 | 2025.6 |
|
||||||
|
| 流程编排 | 通过可视化界面和灵活的配置方式,快速构建AI应用 | 2025.7 |
|
||||||
|
|
||||||
通过流程编排功能,用户可以将不同的模型按照业务逻辑进行有序连接。这将解决单一模型能力不足的问题,充分发挥多个模型的协同作用,从而更好地满足企业的复杂业务需求。
|
|
||||||
|
|
||||||
- 感谢
|
- 感谢
|
||||||
|
|
||||||
@@ -264,6 +277,8 @@
|
|||||||
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/master/LICENSE.txt
|
[license-url]: https://github.com/ageerle/ruoyi-ai/blob/master/LICENSE.txt
|
||||||
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
|
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
|
||||||
|
|
||||||
|
## 🌿 第三方生态
|
||||||
|
- [PPIO 派欧云:一键调用高性价比的开源模型 API 和 GPU 容器](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai)
|
||||||
|
|
||||||
### 附:技术讨论群
|
### 附:技术讨论群
|
||||||
|
|
||||||
|
|||||||
BIN
image/mcp-01.png
BIN
image/mcp-01.png
Binary file not shown.
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 227 KiB |
12
pom.xml
12
pom.xml
@@ -20,7 +20,7 @@
|
|||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<mysql.version>8.0.33</mysql.version>
|
<mysql.version>8.0.33</mysql.version>
|
||||||
<mybatis.version>3.5.16</mybatis.version>
|
<mybatis.version>3.5.16</mybatis.version>
|
||||||
<springdoc.version>2.1.0</springdoc.version>
|
<springdoc.version>2.8.5</springdoc.version>
|
||||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||||
<poi.version>5.2.3</poi.version>
|
<poi.version>5.2.3</poi.version>
|
||||||
<easyexcel.version>3.2.1</easyexcel.version>
|
<easyexcel.version>3.2.1</easyexcel.version>
|
||||||
@@ -337,11 +337,11 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- <dependency>-->
|
<dependency>
|
||||||
<!-- <groupId>org.ruoyi</groupId>-->
|
<groupId>org.ruoyi</groupId>
|
||||||
<!-- <artifactId>ruoyi-demo</artifactId>-->
|
<artifactId>ruoyi-wechat</artifactId>
|
||||||
<!-- <version>${revision}</version>-->
|
<version>${revision}</version>
|
||||||
<!-- </dependency>-->
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|||||||
@@ -42,12 +42,6 @@
|
|||||||
<artifactId>mssql-jdbc</artifactId>
|
<artifactId>mssql-jdbc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- demo模块 -->
|
|
||||||
<!-- <dependency>-->
|
|
||||||
<!-- <groupId>org.ruoyi</groupId>-->
|
|
||||||
<!-- <artifactId>ruoyi-demo</artifactId>-->
|
|
||||||
<!-- </dependency>-->
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ruoyi</groupId>
|
<groupId>org.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-system</artifactId>
|
<artifactId>ruoyi-system</artifactId>
|
||||||
@@ -58,6 +52,16 @@
|
|||||||
<artifactId>ruoyi-chat</artifactId>
|
<artifactId>ruoyi-chat</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-generator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-wechat</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.ruoyi.controller;
|
package org.ruoyi.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ spring:
|
|||||||
master:
|
master:
|
||||||
type: ${spring.datasource.type}
|
type: ${spring.datasource.type}
|
||||||
driverClassName: com.mysql.cj.jdbc.Driver
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
url: jdbc:mysql://43.139.70.230:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
url: jdbc:mysql://127.0.0.1:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
|
||||||
username: ry-vue
|
username: ry-vue
|
||||||
password: xx
|
password: xx
|
||||||
|
|
||||||
|
|||||||
@@ -215,11 +215,16 @@ mybatis-encryptor:
|
|||||||
publicKey:
|
publicKey:
|
||||||
privateKey:
|
privateKey:
|
||||||
|
|
||||||
# Swagger配置
|
springdoc:
|
||||||
swagger:
|
api-docs:
|
||||||
|
# 是否开启接口文档
|
||||||
|
enabled: true
|
||||||
|
# swagger-ui:
|
||||||
|
# # 持久化认证数据
|
||||||
|
# persistAuthorization: true
|
||||||
info:
|
info:
|
||||||
# 标题
|
# 标题
|
||||||
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
|
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
|
||||||
# 描述
|
# 描述
|
||||||
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
|
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
|
||||||
# 版本
|
# 版本
|
||||||
@@ -236,24 +241,10 @@ swagger:
|
|||||||
type: APIKEY
|
type: APIKEY
|
||||||
in: HEADER
|
in: HEADER
|
||||||
name: ${sa-token.token-name}
|
name: ${sa-token.token-name}
|
||||||
|
|
||||||
springdoc:
|
|
||||||
api-docs:
|
|
||||||
# 是否开启接口文档
|
|
||||||
enabled: true
|
|
||||||
swagger-ui:
|
|
||||||
# 持久化认证数据
|
|
||||||
persistAuthorization: true
|
|
||||||
#这里定义了两个分组,可定义多个,也可以不定义
|
#这里定义了两个分组,可定义多个,也可以不定义
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: 1.演示模块
|
- group: 1.系统模块
|
||||||
packages-to-scan: org.ruoyi.demo
|
|
||||||
- group: 2.通用模块
|
|
||||||
packages-to-scan: org.ruoyi.web
|
|
||||||
- group: 3.系统模块
|
|
||||||
packages-to-scan: org.ruoyi.system
|
packages-to-scan: org.ruoyi.system
|
||||||
- group: 4.代码生成模块
|
|
||||||
packages-to-scan: org.ruoyi.generator
|
|
||||||
|
|
||||||
# 防止XSS攻击
|
# 防止XSS攻击
|
||||||
xss:
|
xss:
|
||||||
@@ -323,11 +314,11 @@ wechat:
|
|||||||
spring:
|
spring:
|
||||||
ai:
|
ai:
|
||||||
openai:
|
openai:
|
||||||
api-key: sk-xxx
|
api-key: sk-xx
|
||||||
base-url: https://api.pandarobot.chat/
|
base-url: https://api.pandarobot.chat/
|
||||||
mcp:
|
mcp:
|
||||||
client:
|
client:
|
||||||
enabled: true
|
enabled: false
|
||||||
name: ruoyi-ai-mcp
|
name: ruoyi-ai-mcp
|
||||||
sse:
|
sse:
|
||||||
connections:
|
connections:
|
||||||
@@ -335,4 +326,5 @@ spring:
|
|||||||
url: http://127.0.0.1:8081
|
url: http://127.0.0.1:8081
|
||||||
stdio:
|
stdio:
|
||||||
servers-configuration: classpath:mcp-server.json
|
servers-configuration: classpath:mcp-server.json
|
||||||
|
request-timeout: 300s
|
||||||
|
|
||||||
|
|||||||
BIN
ruoyi-admin/src/main/resources/static/favicon.ico
Normal file
BIN
ruoyi-admin/src/main/resources/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
@@ -159,12 +159,20 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 微信模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.ruoyi</groupId>
|
<groupId>org.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-chat</artifactId>
|
<artifactId>ruoyi-common-wechat</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 支付模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-common-pay</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ public class OpenAIConst {
|
|||||||
|
|
||||||
public final static String OPENAI_HOST = "https://api.openai.com/";
|
public final static String OPENAI_HOST = "https://api.openai.com/";
|
||||||
|
|
||||||
|
public final static String apiUrl = "v1/chat/completions";
|
||||||
|
|
||||||
public final static int SUCCEED_CODE = 200;
|
public final static int SUCCEED_CODE = 200;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ public class OpenAiStreamClient {
|
|||||||
*/
|
*/
|
||||||
private String apiHost;
|
private String apiHost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义url 兼容多个平台
|
||||||
|
*/
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义的okHttpClient
|
* 自定义的okHttpClient
|
||||||
* 如果不自定义 ,就是用sdk默认的OkHttpClient实例
|
* 如果不自定义 ,就是用sdk默认的OkHttpClient实例
|
||||||
@@ -112,6 +117,11 @@ public class OpenAiStreamClient {
|
|||||||
}
|
}
|
||||||
apiHost = builder.apiHost;
|
apiHost = builder.apiHost;
|
||||||
|
|
||||||
|
if (StrUtil.isBlank(builder.apiUrl)) {
|
||||||
|
builder.apiUrl = OpenAIConst.apiUrl;
|
||||||
|
}
|
||||||
|
apiUrl = builder.apiUrl;
|
||||||
|
|
||||||
if (Objects.isNull(builder.keyStrategy)) {
|
if (Objects.isNull(builder.keyStrategy)) {
|
||||||
builder.keyStrategy = new KeyRandomStrategy();
|
builder.keyStrategy = new KeyRandomStrategy();
|
||||||
}
|
}
|
||||||
@@ -180,7 +190,7 @@ public class OpenAiStreamClient {
|
|||||||
ObjectMapper mapper = new ObjectMapper();
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
String requestBody = mapper.writeValueAsString(chatCompletion);
|
String requestBody = mapper.writeValueAsString(chatCompletion);
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(this.apiHost + "v1/chat/completions")
|
.url(this.apiHost + apiUrl)
|
||||||
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
|
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
|
||||||
.build();
|
.build();
|
||||||
factory.newEventSource(request, eventSourceListener);
|
factory.newEventSource(request, eventSourceListener);
|
||||||
@@ -611,6 +621,8 @@ public class OpenAiStreamClient {
|
|||||||
*/
|
*/
|
||||||
private String apiHost;
|
private String apiHost;
|
||||||
|
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义OkhttpClient
|
* 自定义OkhttpClient
|
||||||
*/
|
*/
|
||||||
@@ -645,6 +657,16 @@ public class OpenAiStreamClient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param val 自定义请求后缀
|
||||||
|
* @return Builder
|
||||||
|
* @see OpenAIConst
|
||||||
|
*/
|
||||||
|
public Builder apiUrl(String val) {
|
||||||
|
apiUrl = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder keyStrategy(KeyStrategyFunction val) {
|
public Builder keyStrategy(KeyStrategyFunction val) {
|
||||||
keyStrategy = val;
|
keyStrategy = val;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ public class ChatRequest {
|
|||||||
*/
|
*/
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话id
|
||||||
|
*/
|
||||||
|
private Long sessionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用ID
|
* 应用ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import io.swagger.v3.oas.models.info.Info;
|
|||||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.ruoyi.common.core.utils.StringUtils;
|
import org.ruoyi.common.core.utils.StringUtils;
|
||||||
import org.ruoyi.common.doc.config.properties.SwaggerProperties;
|
import org.ruoyi.common.doc.config.properties.SpringDocProperties;
|
||||||
import org.ruoyi.common.doc.handler.OpenApiHandler;
|
import org.ruoyi.common.doc.handler.OpenApiHandler;
|
||||||
import org.springdoc.core.configuration.SpringDocConfiguration;
|
import org.springdoc.core.configuration.SpringDocConfiguration;
|
||||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||||
@@ -36,26 +36,26 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@AutoConfiguration(before = SpringDocConfiguration.class)
|
@AutoConfiguration(before = SpringDocConfiguration.class)
|
||||||
@EnableConfigurationProperties(SwaggerProperties.class)
|
@EnableConfigurationProperties(SpringDocProperties.class)
|
||||||
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
|
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
public class SwaggerConfig {
|
public class SpringDocConfig {
|
||||||
|
|
||||||
private final ServerProperties serverProperties;
|
private final ServerProperties serverProperties;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(OpenAPI.class)
|
@ConditionalOnMissingBean(OpenAPI.class)
|
||||||
public OpenAPI openApi(SwaggerProperties swaggerProperties) {
|
public OpenAPI openApi(SpringDocProperties properties) {
|
||||||
OpenAPI openApi = new OpenAPI();
|
OpenAPI openApi = new OpenAPI();
|
||||||
// 文档基本信息
|
// 文档基本信息
|
||||||
SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo();
|
SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
|
||||||
Info info = convertInfo(infoProperties);
|
Info info = convertInfo(infoProperties);
|
||||||
openApi.info(info);
|
openApi.info(info);
|
||||||
// 扩展文档信息
|
// 扩展文档信息
|
||||||
openApi.externalDocs(swaggerProperties.getExternalDocs());
|
openApi.externalDocs(properties.getExternalDocs());
|
||||||
openApi.tags(swaggerProperties.getTags());
|
openApi.tags(properties.getTags());
|
||||||
openApi.paths(swaggerProperties.getPaths());
|
openApi.paths(properties.getPaths());
|
||||||
openApi.components(swaggerProperties.getComponents());
|
openApi.components(properties.getComponents());
|
||||||
Set<String> keySet = swaggerProperties.getComponents().getSecuritySchemes().keySet();
|
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||||
List<SecurityRequirement> list = new ArrayList<>();
|
List<SecurityRequirement> list = new ArrayList<>();
|
||||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||||
keySet.forEach(securityRequirement::addList);
|
keySet.forEach(securityRequirement::addList);
|
||||||
@@ -65,7 +65,7 @@ public class SwaggerConfig {
|
|||||||
return openApi;
|
return openApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) {
|
private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
|
||||||
Info info = new Info();
|
Info info = new Info();
|
||||||
info.setTitle(infoProperties.getTitle());
|
info.setTitle(infoProperties.getTitle());
|
||||||
info.setDescription(infoProperties.getDescription());
|
info.setDescription(infoProperties.getDescription());
|
||||||
@@ -18,8 +18,8 @@ import java.util.List;
|
|||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ConfigurationProperties(prefix = "swagger")
|
@ConfigurationProperties(prefix = "springdoc")
|
||||||
public class SwaggerProperties {
|
public class SpringDocProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文档基本信息
|
* 文档基本信息
|
||||||
@@ -11,6 +11,7 @@ import io.swagger.v3.oas.models.Paths;
|
|||||||
import io.swagger.v3.oas.models.tags.Tag;
|
import io.swagger.v3.oas.models.tags.Tag;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.ruoyi.common.core.utils.StreamUtils;
|
||||||
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
|
||||||
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
|
||||||
import org.springdoc.core.properties.SpringDocConfigProperties;
|
import org.springdoc.core.properties.SpringDocConfigProperties;
|
||||||
@@ -158,7 +159,7 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
if (springdocTags.containsKey(handlerMethod)) {
|
if (springdocTags.containsKey(handlerMethod)) {
|
||||||
Tag tag = springdocTags.get(handlerMethod);
|
io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
|
||||||
tagsStr.add(tag.getName());
|
tagsStr.add(tag.getName());
|
||||||
if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
|
if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
|
||||||
openAPI.addTagsItem(tag);
|
openAPI.addTagsItem(tag);
|
||||||
@@ -182,7 +183,7 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
if (javadocProvider.isPresent()) {
|
if (javadocProvider.isPresent()) {
|
||||||
String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
|
String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
|
||||||
if (StringUtils.isNotBlank(description)) {
|
if (StringUtils.isNotBlank(description)) {
|
||||||
Tag tag = new Tag();
|
io.swagger.v3.oas.models.tags.Tag tag = new io.swagger.v3.oas.models.tags.Tag();
|
||||||
|
|
||||||
// 自定义部分 修改使用java注释当tag名
|
// 自定义部分 修改使用java注释当tag名
|
||||||
List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
|
List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
|
||||||
@@ -203,7 +204,7 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
|
|
||||||
if (!CollectionUtils.isEmpty(tags)) {
|
if (!CollectionUtils.isEmpty(tags)) {
|
||||||
// Existing tags
|
// Existing tags
|
||||||
List<Tag> openApiTags = openAPI.getTags();
|
List<io.swagger.v3.oas.models.tags.Tag> openApiTags = openAPI.getTags();
|
||||||
if (!CollectionUtils.isEmpty(openApiTags))
|
if (!CollectionUtils.isEmpty(openApiTags))
|
||||||
tags.addAll(openApiTags);
|
tags.addAll(openApiTags);
|
||||||
openAPI.setTags(new ArrayList<>(tags));
|
openAPI.setTags(new ArrayList<>(tags));
|
||||||
@@ -222,7 +223,7 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
|
private void buildTagsFromMethod(Method method, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr, Locale locale) {
|
||||||
// method tags
|
// method tags
|
||||||
Set<Tags> tagsSet = AnnotatedElementUtils
|
Set<Tags> tagsSet = AnnotatedElementUtils
|
||||||
.findAllMergedAnnotations(method, Tags.class);
|
.findAllMergedAnnotations(method, Tags.class);
|
||||||
@@ -230,14 +231,14 @@ public class OpenApiHandler extends OpenAPIService {
|
|||||||
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
|
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
|
||||||
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
|
||||||
if (!CollectionUtils.isEmpty(methodTags)) {
|
if (!CollectionUtils.isEmpty(methodTags)) {
|
||||||
tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
|
tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
|
||||||
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
|
||||||
addTags(allTags, tags, locale);
|
addTags(allTags, tags, locale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
|
private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<io.swagger.v3.oas.models.tags.Tag> tags, Locale locale) {
|
||||||
Optional<Set<Tag>> optionalTagSet = AnnotationsUtils
|
Optional<Set<io.swagger.v3.oas.models.tags.Tag>> optionalTagSet = AnnotationsUtils
|
||||||
.getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
|
.getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
|
||||||
optionalTagSet.ifPresent(tagsSet -> {
|
optionalTagSet.ifPresent(tagsSet -> {
|
||||||
tagsSet.forEach(tag -> {
|
tagsSet.forEach(tag -> {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
org.ruoyi.common.doc.config.SwaggerConfig
|
org.ruoyi.common.doc.config.SpringDocConfig
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ public class ChatMessage extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话id
|
||||||
|
*/
|
||||||
|
private Long sessionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息内容
|
* 消息内容
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.ruoyi.domain;
|
package org.ruoyi.domain;
|
||||||
|
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -68,6 +69,12 @@ public class ChatModel extends BaseEntity {
|
|||||||
*/
|
*/
|
||||||
private String apiHost;
|
private String apiHost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求地址后缀 - 兼容多平台
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "请求地址后缀")
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密钥
|
* 密钥
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.ruoyi.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.ruoyi.core.domain.BaseEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理对象 chat_session
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("chat_session")
|
||||||
|
public class ChatSession extends BaseEntity {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(value = "id")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话标题
|
||||||
|
*/
|
||||||
|
private String sessionTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话内容
|
||||||
|
*/
|
||||||
|
private String sessionContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,6 +40,11 @@ public class ChatMessageBo extends BaseEntity {
|
|||||||
@NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class })
|
@NotBlank(message = "消息内容不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话id
|
||||||
|
*/
|
||||||
|
private Long sessionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对话角色
|
* 对话角色
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.ruoyi.domain.bo;
|
package org.ruoyi.domain.bo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import org.ruoyi.common.core.validate.AddGroup;
|
import org.ruoyi.common.core.validate.AddGroup;
|
||||||
import org.ruoyi.common.core.validate.EditGroup;
|
import org.ruoyi.common.core.validate.EditGroup;
|
||||||
import org.ruoyi.domain.ChatModel;
|
import org.ruoyi.domain.ChatModel;
|
||||||
@@ -79,6 +80,12 @@ public class ChatModelBo extends BaseEntity {
|
|||||||
@NotBlank(message = "密钥不能为空", groups = { AddGroup.class, EditGroup.class })
|
@NotBlank(message = "密钥不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求地址后缀 - 兼容多平台
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "请求地址后缀")
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.ruoyi.domain.bo;
|
||||||
|
|
||||||
|
import org.ruoyi.common.core.validate.AddGroup;
|
||||||
|
import org.ruoyi.common.core.validate.EditGroup;
|
||||||
|
import org.ruoyi.core.domain.BaseEntity;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import org.ruoyi.domain.ChatSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理业务对象 chat_session
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@AutoMapper(target = ChatSession.class, reverseConvertGenerate = false)
|
||||||
|
public class ChatSessionBo extends BaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
@NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话标题
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "会话标题不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String sessionTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话内容
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "会话内容不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String sessionContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -41,6 +41,11 @@ public class ChatMessageVo implements Serializable {
|
|||||||
@ExcelProperty(value = "用户id")
|
@ExcelProperty(value = "用户id")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话id
|
||||||
|
*/
|
||||||
|
private Long sessionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息内容
|
* 消息内容
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -88,11 +88,16 @@ public class ChatModelVo implements Serializable {
|
|||||||
@ExcelProperty(value = "密钥")
|
@ExcelProperty(value = "密钥")
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求地址后缀 - 兼容多平台
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "请求地址后缀")
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "备注")
|
@ExcelProperty(value = "备注")
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.ruoyi.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.ruoyi.domain.ChatSession;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理视图对象 chat_session
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AutoMapper(target = ChatSession.class)
|
||||||
|
public class ChatSessionVo implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话标题
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "会话标题")
|
||||||
|
private String sessionTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话内容
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "会话内容")
|
||||||
|
private String sessionContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.ruoyi.mapper;
|
||||||
|
|
||||||
|
import org.ruoyi.core.mapper.BaseMapperPlus;
|
||||||
|
import org.ruoyi.domain.ChatSession;
|
||||||
|
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理Mapper接口
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
public interface ChatSessionMapper extends BaseMapperPlus<ChatSession, ChatSessionVo> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.ruoyi.service;
|
||||||
|
|
||||||
|
import org.ruoyi.core.page.PageQuery;
|
||||||
|
import org.ruoyi.core.page.TableDataInfo;
|
||||||
|
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||||
|
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理Service接口
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
public interface IChatSessionService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理
|
||||||
|
*/
|
||||||
|
ChatSessionVo queryById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<ChatSessionVo> queryPageList(ChatSessionBo bo, PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理列表
|
||||||
|
*/
|
||||||
|
List<ChatSessionVo> queryList(ChatSessionBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增会话管理
|
||||||
|
*/
|
||||||
|
Boolean insertByBo(ChatSessionBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会话管理
|
||||||
|
*/
|
||||||
|
Boolean updateByBo(ChatSessionBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并批量删除会话管理信息
|
||||||
|
*/
|
||||||
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.ruoyi.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信聊天管理Service接口
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-04-08
|
||||||
|
*/
|
||||||
|
public interface IChatVxService {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信应用回复
|
||||||
|
* @param prompt 提示词
|
||||||
|
* @return 回复内容
|
||||||
|
*/
|
||||||
|
String chat(String prompt);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package org.ruoyi.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.ruoyi.common.core.utils.MapstructUtils;
|
||||||
|
import org.ruoyi.common.core.utils.StringUtils;
|
||||||
|
import org.ruoyi.core.page.TableDataInfo;
|
||||||
|
import org.ruoyi.core.page.PageQuery;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.ruoyi.domain.ChatSession;
|
||||||
|
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||||
|
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||||
|
import org.ruoyi.mapper.ChatSessionMapper;
|
||||||
|
import org.ruoyi.service.IChatSessionService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理Service业务层处理
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class ChatSessionServiceImpl implements IChatSessionService {
|
||||||
|
|
||||||
|
private final ChatSessionMapper baseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ChatSessionVo queryById(Long id){
|
||||||
|
return baseMapper.selectVoById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<ChatSessionVo> queryPageList(ChatSessionBo bo, PageQuery pageQuery) {
|
||||||
|
LambdaQueryWrapper<ChatSession> lqw = buildQueryWrapper(bo);
|
||||||
|
Page<ChatSessionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||||
|
return TableDataInfo.build(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理列表
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<ChatSessionVo> queryList(ChatSessionBo bo) {
|
||||||
|
LambdaQueryWrapper<ChatSession> lqw = buildQueryWrapper(bo);
|
||||||
|
return baseMapper.selectVoList(lqw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapper<ChatSession> buildQueryWrapper(ChatSessionBo bo) {
|
||||||
|
Map<String, Object> params = bo.getParams();
|
||||||
|
LambdaQueryWrapper<ChatSession> lqw = Wrappers.lambdaQuery();
|
||||||
|
lqw.eq(bo.getUserId() != null, ChatSession::getUserId, bo.getUserId());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getSessionTitle()), ChatSession::getSessionTitle, bo.getSessionTitle());
|
||||||
|
lqw.eq(StringUtils.isNotBlank(bo.getSessionContent()), ChatSession::getSessionContent, bo.getSessionContent());
|
||||||
|
return lqw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增会话管理
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean insertByBo(ChatSessionBo bo) {
|
||||||
|
ChatSession add = MapstructUtils.convert(bo, ChatSession.class);
|
||||||
|
validEntityBeforeSave(add);
|
||||||
|
boolean flag = baseMapper.insert(add) > 0;
|
||||||
|
if (flag) {
|
||||||
|
bo.setId(add.getId());
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会话管理
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean updateByBo(ChatSessionBo bo) {
|
||||||
|
ChatSession update = MapstructUtils.convert(bo, ChatSession.class);
|
||||||
|
validEntityBeforeSave(update);
|
||||||
|
return baseMapper.updateById(update) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存前的数据校验
|
||||||
|
*/
|
||||||
|
private void validEntityBeforeSave(ChatSession entity){
|
||||||
|
//TODO 做一些数据校验,如唯一约束
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除会话管理
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||||
|
if(isValid){
|
||||||
|
//TODO 做一些业务上的校验,判断是否需要校验
|
||||||
|
}
|
||||||
|
return baseMapper.deleteBatchIds(ids) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.ruoyi.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||||
|
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||||
|
import org.ruoyi.common.chat.entity.chat.Message;
|
||||||
|
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||||
|
import org.ruoyi.service.IChatVxService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChatVxServiceImpl implements IChatVxService {
|
||||||
|
|
||||||
|
private final OpenAiStreamClient openAiStreamClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chat(String prompt) {
|
||||||
|
List<Message> messageList = new ArrayList<>();
|
||||||
|
Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
|
||||||
|
messageList.add(message);
|
||||||
|
ChatCompletion chatCompletion = ChatCompletion
|
||||||
|
.builder()
|
||||||
|
.messages(messageList)
|
||||||
|
.model("gpt-4o-mini")
|
||||||
|
.stream(false)
|
||||||
|
.build();
|
||||||
|
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||||
|
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,8 +16,21 @@
|
|||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<langchain4j.version>1.0.0-beta4</langchain4j.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-bom</artifactId>
|
||||||
|
<version>${langchain4j.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
<!-- pdf解析器 -->
|
<!-- pdf解析器 -->
|
||||||
@@ -34,17 +47,60 @@
|
|||||||
<version>1.0.79</version>
|
<version>1.0.79</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- milvus java sdk -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.milvus</groupId>
|
<groupId>dev.langchain4j</groupId>
|
||||||
<artifactId>milvus-sdk-java</artifactId>
|
<artifactId>langchain4j</artifactId>
|
||||||
<version>2.3.2</version>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-weaviate</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.weaviate</groupId>
|
<groupId>dev.langchain4j</groupId>
|
||||||
<artifactId>client</artifactId>
|
<artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId>
|
||||||
<version>4.0.0</version>
|
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>weaviate</artifactId>
|
||||||
|
<version>1.19.6</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-open-ai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-ollama</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-milvus</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>milvus</artifactId>
|
||||||
|
<version>1.19.6</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.langchain4j</groupId>
|
||||||
|
<artifactId>langchain4j-qdrant</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>qdrant</artifactId>
|
||||||
|
<version>1.19.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import jakarta.validation.constraints.*;
|
|||||||
public class KnowledgeInfoBo extends BaseEntity {
|
public class KnowledgeInfoBo extends BaseEntity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* 主键
|
||||||
*/
|
*/
|
||||||
@NotNull(message = "不能为空", groups = { EditGroup.class })
|
@NotNull(message = "不能为空", groups = { EditGroup.class })
|
||||||
private Long id;
|
private Long id;
|
||||||
@@ -29,13 +29,13 @@ public class KnowledgeInfoBo extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 知识库ID
|
* 知识库ID
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "知识库ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
@NotBlank(message = "知识库ID不能为空", groups = {EditGroup.class })
|
||||||
private String kid;
|
private String kid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户ID
|
* 用户ID
|
||||||
*/
|
*/
|
||||||
@NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
@NotNull(message = "用户ID不能为空", groups = {EditGroup.class })
|
||||||
private Long uid;
|
private Long uid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,25 +53,21 @@ public class KnowledgeInfoBo extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 描述
|
* 描述
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "描述不能为空", groups = { AddGroup.class, EditGroup.class })
|
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识分隔符
|
* 知识分隔符
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "知识分隔符不能为空", groups = { AddGroup.class, EditGroup.class })
|
|
||||||
private String knowledgeSeparator;
|
private String knowledgeSeparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提问分隔符
|
* 提问分隔符
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "提问分隔符不能为空", groups = { AddGroup.class, EditGroup.class })
|
|
||||||
private String questionSeparator;
|
private String questionSeparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重叠字符数
|
* 重叠字符数
|
||||||
*/
|
*/
|
||||||
@NotNull(message = "重叠字符数不能为空", groups = { AddGroup.class, EditGroup.class })
|
|
||||||
private Long overlapChar;
|
private Long overlapChar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,8 +97,6 @@ public class KnowledgeInfoBo extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 备注
|
* 备注
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
|
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.ruoyi.domain.bo;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询向量所需参数
|
||||||
|
* @author ageer
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class QueryVectorBo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询内容
|
||||||
|
*/
|
||||||
|
private String query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库kid
|
||||||
|
*/
|
||||||
|
private String kid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询向量返回条数
|
||||||
|
*/
|
||||||
|
private Integer maxResults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求key
|
||||||
|
*/
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求地址
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.ruoyi.domain.bo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存向量所需参数
|
||||||
|
* @author ageer
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class StoreEmbeddingBo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切分文本块列表
|
||||||
|
*/
|
||||||
|
private List<String> chunkList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识库kid
|
||||||
|
*/
|
||||||
|
private String kid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档id
|
||||||
|
*/
|
||||||
|
private String docId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 知识块id列表
|
||||||
|
*/
|
||||||
|
private List<String> fids;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求key
|
||||||
|
*/
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求地址
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.ruoyi.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface EmbeddingService {
|
|
||||||
|
|
||||||
void storeEmbeddings(List<String> chunkList, String kid, String docId,List<String> fidList);
|
|
||||||
|
|
||||||
void removeByDocId(String kid,String docId);
|
|
||||||
|
|
||||||
void removeByKid(String kid);
|
|
||||||
|
|
||||||
List<Double> getQueryVector(String query, String kid);
|
|
||||||
|
|
||||||
void createSchema(String kid);
|
|
||||||
|
|
||||||
void removeByKidAndFid(String kid, String fid);
|
|
||||||
|
|
||||||
void saveFragment(String kid, String docId, String fid, String content);
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,26 @@
|
|||||||
package org.ruoyi.service;
|
package org.ruoyi.service;
|
||||||
|
|
||||||
|
import org.ruoyi.domain.bo.QueryVectorBo;
|
||||||
|
import org.ruoyi.domain.bo.StoreEmbeddingBo;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向量存储
|
* 向量库管理
|
||||||
|
* @author ageer
|
||||||
*/
|
*/
|
||||||
public interface VectorStoreService {
|
public interface VectorStoreService {
|
||||||
|
|
||||||
void storeEmbeddings(List<String> chunkList, List<List<Double>> vectorList, String kid, String docId, List<String> fidList);
|
void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo);
|
||||||
|
|
||||||
void removeByDocId(String kid, String docId);
|
void removeByDocId(String kid,String docId);
|
||||||
|
|
||||||
void removeByKid(String kid);
|
void removeByKid(String kid);
|
||||||
|
|
||||||
List<String> nearest(List<Double> queryVector, String kid);
|
List<String> getQueryVector(QueryVectorBo queryVectorBo);
|
||||||
|
|
||||||
List<String> nearest(String query, String kid);
|
void createSchema(String kid,String modelName);
|
||||||
|
|
||||||
void newSchema(String kid);
|
|
||||||
|
|
||||||
void removeByKidAndFid(String kid, String fid);
|
void removeByKidAndFid(String kid, String fid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.ruoyi.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文本向量化
|
|
||||||
*/
|
|
||||||
public interface VectorizationService {
|
|
||||||
|
|
||||||
List<List<Double>> batchVectorization(List<String> chunkList, String kid);
|
|
||||||
|
|
||||||
List<Double> singleVectorization(String chunk, String kid);
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package org.ruoyi.service.impl;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.ruoyi.service.EmbeddingService;
|
|
||||||
import org.ruoyi.service.VectorStoreService;
|
|
||||||
import org.ruoyi.service.VectorizationService;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class EmbeddingServiceImpl implements EmbeddingService {
|
|
||||||
|
|
||||||
private final VectorStoreService vectorStore;
|
|
||||||
private final VectorizationService vectorization;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存向量数据库
|
|
||||||
* @param chunkList 文档按行切分的片段
|
|
||||||
* @param kid 知识库ID
|
|
||||||
* @param docId 文档ID
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void storeEmbeddings(List<String> chunkList, String kid, String docId,List<String> fidList) {
|
|
||||||
List<List<Double>> vectorList = vectorization.batchVectorization(chunkList, kid);
|
|
||||||
vectorStore.storeEmbeddings(chunkList,vectorList,kid,docId,fidList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeByDocId(String kid,String docId) {
|
|
||||||
vectorStore.removeByDocId(kid,docId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeByKid(String kid) {
|
|
||||||
vectorStore.removeByKid(kid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Double> getQueryVector(String query, String kid) {
|
|
||||||
return vectorization.singleVectorization(query,kid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createSchema(String kid) {
|
|
||||||
vectorStore.newSchema(kid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeByKidAndFid(String kid, String fid) {
|
|
||||||
vectorStore.removeByKidAndFid(kid,fid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveFragment(String kid, String docId, String fid, String content) {
|
|
||||||
List<String> chunkList = new ArrayList<>();
|
|
||||||
List<String> fidList = new ArrayList<>();
|
|
||||||
chunkList.add(content);
|
|
||||||
fidList.add(fid);
|
|
||||||
storeEmbeddings(chunkList,kid,docId,fidList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package org.ruoyi.service.impl;
|
||||||
|
|
||||||
|
import dev.langchain4j.data.embedding.Embedding;
|
||||||
|
import dev.langchain4j.data.segment.TextSegment;
|
||||||
|
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||||
|
import dev.langchain4j.model.ollama.OllamaEmbeddingModel;
|
||||||
|
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
|
||||||
|
import dev.langchain4j.model.output.Response;
|
||||||
|
import dev.langchain4j.store.embedding.EmbeddingMatch;
|
||||||
|
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
|
||||||
|
import dev.langchain4j.store.embedding.EmbeddingStore;
|
||||||
|
import dev.langchain4j.store.embedding.filter.Filter;
|
||||||
|
import dev.langchain4j.store.embedding.filter.comparison.IsEqualTo;
|
||||||
|
import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
|
||||||
|
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
|
||||||
|
import dev.langchain4j.store.embedding.weaviate.WeaviateEmbeddingStore;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.core.service.ConfigService;
|
||||||
|
import org.ruoyi.domain.bo.QueryVectorBo;
|
||||||
|
import org.ruoyi.domain.bo.StoreEmbeddingBo;
|
||||||
|
import org.ruoyi.service.VectorStoreService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import static dev.langchain4j.model.openai.OpenAiEmbeddingModelName.TEXT_EMBEDDING_3_SMALL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向量库管理
|
||||||
|
* @author ageer
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class VectorStoreServiceImpl implements VectorStoreService {
|
||||||
|
|
||||||
|
private final ConfigService configService;
|
||||||
|
|
||||||
|
Map<String,EmbeddingStore<TextSegment>> storeMap;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createSchema(String kid,String modelName) {
|
||||||
|
EmbeddingStore<TextSegment> embeddingStore = WeaviateEmbeddingStore.builder().build();
|
||||||
|
switch (modelName) {
|
||||||
|
case "weaviate" -> {
|
||||||
|
String protocol = configService.getConfigValue("weaviate", "protocol");
|
||||||
|
String host = configService.getConfigValue("weaviate", "host");
|
||||||
|
String className = configService.getConfigValue("weaviate", "classname");
|
||||||
|
embeddingStore = WeaviateEmbeddingStore.builder()
|
||||||
|
.scheme(protocol)
|
||||||
|
.host(host)
|
||||||
|
.objectClass(className + kid)
|
||||||
|
.scheme(protocol)
|
||||||
|
.avoidDups(true)
|
||||||
|
.consistencyLevel("ALL")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
case "milvus" -> {
|
||||||
|
String uri = configService.getConfigValue("milvus", "host");
|
||||||
|
String collection = configService.getConfigValue("milvus", "collection");
|
||||||
|
String dimension = configService.getConfigValue("milvus", "dimension");
|
||||||
|
embeddingStore = MilvusEmbeddingStore.builder()
|
||||||
|
.uri(uri)
|
||||||
|
.collectionName(collection + kid)
|
||||||
|
.dimension(Integer.parseInt(dimension))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
case "qdrant" -> {
|
||||||
|
String host = configService.getConfigValue("qdrant", "host");
|
||||||
|
String port = configService.getConfigValue("qdrant", "port");
|
||||||
|
String collectionName = configService.getConfigValue("qdrant", "collectionName");
|
||||||
|
embeddingStore = QdrantEmbeddingStore.builder()
|
||||||
|
.host(host)
|
||||||
|
.port(Integer.parseInt(port))
|
||||||
|
.collectionName(collectionName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storeMap.put(kid,embeddingStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeEmbeddings(StoreEmbeddingBo storeEmbeddingBo) {
|
||||||
|
EmbeddingStore<TextSegment> store = storeMap.get(storeEmbeddingBo.getKid());
|
||||||
|
EmbeddingModel embeddingModel = getEmbeddingModel(storeEmbeddingBo.getModelName(),
|
||||||
|
storeEmbeddingBo.getApiKey(), storeEmbeddingBo.getBaseUrl());
|
||||||
|
for (int i = 0; i < storeEmbeddingBo.getChunkList().size(); i++) {
|
||||||
|
Map<String, Object> dataSchema = new HashMap<>();
|
||||||
|
dataSchema.put("kid", storeEmbeddingBo.getKid());
|
||||||
|
dataSchema.put("docId", storeEmbeddingBo.getKid());
|
||||||
|
dataSchema.put("fid", storeEmbeddingBo.getFids().get(i));
|
||||||
|
Response<Embedding> response = embeddingModel.embed(storeEmbeddingBo.getChunkList().get(i));
|
||||||
|
Embedding embedding = response.content();
|
||||||
|
TextSegment segment = TextSegment.from(storeEmbeddingBo.getChunkList().get(i));
|
||||||
|
segment.metadata().putAll(dataSchema);
|
||||||
|
|
||||||
|
store.add(embedding,segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getQueryVector(QueryVectorBo queryVectorBo) {
|
||||||
|
EmbeddingStore<TextSegment> store = storeMap.get(queryVectorBo.getKid());
|
||||||
|
|
||||||
|
EmbeddingModel embeddingModel = getEmbeddingModel(queryVectorBo.getModelName(),
|
||||||
|
queryVectorBo.getApiKey(), queryVectorBo.getBaseUrl());
|
||||||
|
Filter simpleFilter = new IsEqualTo("kid", queryVectorBo.getKid());
|
||||||
|
Embedding queryEmbedding = embeddingModel.embed(queryVectorBo.getQuery()).content();
|
||||||
|
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
|
||||||
|
.queryEmbedding(queryEmbedding)
|
||||||
|
.maxResults(queryVectorBo.getMaxResults())
|
||||||
|
// 添加过滤条件
|
||||||
|
.filter(simpleFilter)
|
||||||
|
.build();
|
||||||
|
List<EmbeddingMatch<TextSegment>> matches = store.search(embeddingSearchRequest).matches();
|
||||||
|
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
|
||||||
|
matches.forEach(embeddingMatch -> results.add(embeddingMatch.embedded().text()));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeByKid(String kid) {
|
||||||
|
EmbeddingStore<TextSegment> store = storeMap.get(kid);
|
||||||
|
|
||||||
|
// 根据条件删除向量数据
|
||||||
|
Filter simpleFilter = new IsEqualTo("kid", kid);
|
||||||
|
store.removeAll(simpleFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeByDocId(String kid, String docId) {
|
||||||
|
EmbeddingStore<TextSegment> store = storeMap.get(kid);
|
||||||
|
// 根据条件删除向量数据
|
||||||
|
Filter simpleFilterByDocId = new IsEqualTo("docId", docId);
|
||||||
|
store.removeAll(simpleFilterByDocId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeByKidAndFid(String kid, String fid) {
|
||||||
|
EmbeddingStore<TextSegment> store = storeMap.get(kid);
|
||||||
|
// 根据条件删除向量数据
|
||||||
|
Filter simpleFilterByKid = new IsEqualTo("kid", kid);
|
||||||
|
Filter simpleFilterFid = new IsEqualTo("fid", fid);
|
||||||
|
Filter simpleFilterByAnd = Filter.and(simpleFilterFid, simpleFilterByKid);
|
||||||
|
store.removeAll(simpleFilterByAnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取向量模型
|
||||||
|
*/
|
||||||
|
public EmbeddingModel getEmbeddingModel(String modelName,String apiKey,String baseUrl) {
|
||||||
|
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder().build();
|
||||||
|
if(TEXT_EMBEDDING_3_SMALL.toString().equals(modelName)) {
|
||||||
|
embeddingModel = OpenAiEmbeddingModel.builder()
|
||||||
|
.apiKey(apiKey)
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.modelName(TEXT_EMBEDDING_3_SMALL)
|
||||||
|
.build();
|
||||||
|
// TODO 添加枚举
|
||||||
|
}else if("quentinz/bge-large-zh-v1.5".equals(modelName)) {
|
||||||
|
embeddingModel = OllamaEmbeddingModel.builder()
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.modelName(modelName)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return embeddingModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,402 +0,0 @@
|
|||||||
package org.ruoyi.service.impl;
|
|
||||||
|
|
||||||
import cn.hutool.core.lang.UUID;
|
|
||||||
import cn.hutool.json.JSONObject;
|
|
||||||
import com.google.gson.internal.LinkedTreeMap;
|
|
||||||
import io.weaviate.client.Config;
|
|
||||||
import io.weaviate.client.WeaviateClient;
|
|
||||||
import io.weaviate.client.base.Result;
|
|
||||||
import io.weaviate.client.v1.data.model.WeaviateObject;
|
|
||||||
import io.weaviate.client.v1.data.replication.model.ConsistencyLevel;
|
|
||||||
import io.weaviate.client.v1.filters.Operator;
|
|
||||||
import io.weaviate.client.v1.filters.WhereFilter;
|
|
||||||
import io.weaviate.client.v1.graphql.model.GraphQLResponse;
|
|
||||||
import io.weaviate.client.v1.graphql.query.argument.NearTextArgument;
|
|
||||||
import io.weaviate.client.v1.graphql.query.argument.NearVectorArgument;
|
|
||||||
import io.weaviate.client.v1.graphql.query.fields.Field;
|
|
||||||
import io.weaviate.client.v1.misc.model.Meta;
|
|
||||||
import io.weaviate.client.v1.misc.model.ReplicationConfig;
|
|
||||||
import io.weaviate.client.v1.misc.model.ShardingConfig;
|
|
||||||
import io.weaviate.client.v1.misc.model.VectorIndexConfig;
|
|
||||||
import io.weaviate.client.v1.schema.model.DataType;
|
|
||||||
import io.weaviate.client.v1.schema.model.Property;
|
|
||||||
import io.weaviate.client.v1.schema.model.Schema;
|
|
||||||
import io.weaviate.client.v1.schema.model.WeaviateClass;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.ruoyi.common.core.service.ConfigService;
|
|
||||||
import org.ruoyi.domain.vo.KnowledgeInfoVo;
|
|
||||||
import org.ruoyi.service.IKnowledgeInfoService;
|
|
||||||
import org.ruoyi.service.VectorStoreService;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class WeaviateVectorStoreImpl implements VectorStoreService {
|
|
||||||
|
|
||||||
private volatile String protocol;
|
|
||||||
private volatile String host;
|
|
||||||
private volatile String className;
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
@Resource
|
|
||||||
private IKnowledgeInfoService knowledgeInfoService;
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
@Resource
|
|
||||||
private ConfigService configService;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void loadConfig() {
|
|
||||||
this.protocol = configService.getConfigValue("weaviate", "protocol");
|
|
||||||
this.host = configService.getConfigValue("weaviate", "host");
|
|
||||||
this.className = configService.getConfigValue("weaviate", "classname");
|
|
||||||
}
|
|
||||||
|
|
||||||
public WeaviateClient getClient() {
|
|
||||||
Config config = new Config(protocol, host);
|
|
||||||
WeaviateClient client = new WeaviateClient(config);
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result<Meta> getMeta() {
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Result<Meta> meta = client.misc().metaGetter().run();
|
|
||||||
if (meta.getError() == null) {
|
|
||||||
System.out.printf("meta.hostname: %s\n", meta.getResult().getHostname());
|
|
||||||
System.out.printf("meta.version: %s\n", meta.getResult().getVersion());
|
|
||||||
System.out.printf("meta.modules: %s\n", meta.getResult().getModules());
|
|
||||||
} else {
|
|
||||||
System.out.printf("Error: %s\n", meta.getError().getMessages());
|
|
||||||
}
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result<Schema> getSchemas() {
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Result<Schema> result = client.schema().getter().run();
|
|
||||||
if (result.hasErrors()) {
|
|
||||||
System.out.println(result.getError());
|
|
||||||
} else {
|
|
||||||
System.out.println(result.getResult());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Result<Boolean> createSchema(String kid) {
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
|
|
||||||
VectorIndexConfig vectorIndexConfig = VectorIndexConfig.builder()
|
|
||||||
.distance("cosine")
|
|
||||||
.cleanupIntervalSeconds(300)
|
|
||||||
.efConstruction(128)
|
|
||||||
.maxConnections(64)
|
|
||||||
.vectorCacheMaxObjects(500000L)
|
|
||||||
.ef(-1)
|
|
||||||
.skip(false)
|
|
||||||
.dynamicEfFactor(8)
|
|
||||||
.dynamicEfMax(500)
|
|
||||||
.dynamicEfMin(100)
|
|
||||||
.flatSearchCutoff(40000)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ShardingConfig shardingConfig = ShardingConfig.builder()
|
|
||||||
.desiredCount(3)
|
|
||||||
.desiredVirtualCount(128)
|
|
||||||
.function("murmur3")
|
|
||||||
.key("_id")
|
|
||||||
.strategy("hash")
|
|
||||||
.virtualPerPhysical(128)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ReplicationConfig replicationConfig = ReplicationConfig.builder()
|
|
||||||
.factor(1)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
JSONObject classModuleConfigValue = new JSONObject();
|
|
||||||
classModuleConfigValue.put("vectorizeClassName", false);
|
|
||||||
JSONObject classModuleConfig = new JSONObject();
|
|
||||||
classModuleConfig.put("text2vec-transformers", classModuleConfigValue);
|
|
||||||
|
|
||||||
JSONObject propertyModuleConfigValueSkipTrue = new JSONObject();
|
|
||||||
propertyModuleConfigValueSkipTrue.put("vectorizePropertyName", false);
|
|
||||||
propertyModuleConfigValueSkipTrue.put("skip", true);
|
|
||||||
JSONObject propertyModuleConfigSkipTrue = new JSONObject();
|
|
||||||
propertyModuleConfigSkipTrue.put("text2vec-transformers", propertyModuleConfigValueSkipTrue);
|
|
||||||
|
|
||||||
JSONObject propertyModuleConfigValueSkipFalse = new JSONObject();
|
|
||||||
propertyModuleConfigValueSkipFalse.put("vectorizePropertyName", false);
|
|
||||||
propertyModuleConfigValueSkipFalse.put("skip", false);
|
|
||||||
JSONObject propertyModuleConfigSkipFalse = new JSONObject();
|
|
||||||
propertyModuleConfigSkipFalse.put("text2vec-transformers", propertyModuleConfigValueSkipFalse);
|
|
||||||
|
|
||||||
WeaviateClass clazz = WeaviateClass.builder()
|
|
||||||
.className(className + kid)
|
|
||||||
.description("local knowledge")
|
|
||||||
.vectorIndexType("hnsw")
|
|
||||||
.vectorizer("text2vec-transformers")
|
|
||||||
.shardingConfig(shardingConfig)
|
|
||||||
.vectorIndexConfig(vectorIndexConfig)
|
|
||||||
.replicationConfig(replicationConfig)
|
|
||||||
.moduleConfig(classModuleConfig)
|
|
||||||
.properties(new ArrayList() {
|
|
||||||
{
|
|
||||||
add(Property.builder()
|
|
||||||
.dataType(new ArrayList() {
|
|
||||||
{
|
|
||||||
add(DataType.TEXT);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.name("content")
|
|
||||||
.description("The content of the local knowledge,for search")
|
|
||||||
.moduleConfig(propertyModuleConfigSkipFalse)
|
|
||||||
.build());
|
|
||||||
add(Property.builder()
|
|
||||||
.dataType(new ArrayList() {
|
|
||||||
{
|
|
||||||
add(DataType.TEXT);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.name("kid")
|
|
||||||
.description("The knowledge id of the local knowledge,for search")
|
|
||||||
.moduleConfig(propertyModuleConfigSkipTrue)
|
|
||||||
.build());
|
|
||||||
add(Property.builder()
|
|
||||||
.dataType(new ArrayList() {
|
|
||||||
{
|
|
||||||
add(DataType.TEXT);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.name("docId")
|
|
||||||
.description("The doc id of the local knowledge,for search")
|
|
||||||
.moduleConfig(propertyModuleConfigSkipTrue)
|
|
||||||
.build());
|
|
||||||
add(Property.builder()
|
|
||||||
.dataType(new ArrayList() {
|
|
||||||
{
|
|
||||||
add(DataType.TEXT);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.name("fid")
|
|
||||||
.description("The fragment id of the local knowledge,for search")
|
|
||||||
.moduleConfig(propertyModuleConfigSkipTrue)
|
|
||||||
.build());
|
|
||||||
add(Property.builder()
|
|
||||||
.dataType(new ArrayList() {
|
|
||||||
{
|
|
||||||
add(DataType.TEXT);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.name("uuid")
|
|
||||||
.description("The uuid id of the local knowledge fragment(same with id properties),for search")
|
|
||||||
.moduleConfig(propertyModuleConfigSkipTrue)
|
|
||||||
.build());
|
|
||||||
} })
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Result<Boolean> result = client.schema().classCreator().withClass(clazz).run();
|
|
||||||
if (result.hasErrors()) {
|
|
||||||
System.out.println(result.getError());
|
|
||||||
}
|
|
||||||
System.out.println(result.getResult());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void newSchema(String kid) {
|
|
||||||
createSchema(kid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeByKidAndFid(String kid, String fid) {
|
|
||||||
List<String> resultList = new ArrayList<>();
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Field fieldId = Field.builder().name("uuid").build();
|
|
||||||
WhereFilter where = WhereFilter.builder()
|
|
||||||
.path(new String[]{"fid"})
|
|
||||||
.operator(Operator.Equal)
|
|
||||||
.valueString(fid)
|
|
||||||
.build();
|
|
||||||
Result<GraphQLResponse> result = client.graphQL().get()
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withFields(fieldId)
|
|
||||||
.withWhere(where)
|
|
||||||
.run();
|
|
||||||
LinkedTreeMap<String, Object> t = (LinkedTreeMap<String, Object>) result.getResult().getData();
|
|
||||||
LinkedTreeMap<String, ArrayList<LinkedTreeMap>> l = (LinkedTreeMap<String, ArrayList<LinkedTreeMap>>) t.get("Get");
|
|
||||||
ArrayList<LinkedTreeMap> m = l.get(className + kid);
|
|
||||||
for (LinkedTreeMap linkedTreeMap : m) {
|
|
||||||
String uuid = linkedTreeMap.get("uuid").toString();
|
|
||||||
resultList.add(uuid);
|
|
||||||
}
|
|
||||||
for (String uuid : resultList) {
|
|
||||||
Result<Boolean> deleteResult = client.data().deleter()
|
|
||||||
.withID(uuid)
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withConsistencyLevel(ConsistencyLevel.ALL) // default QUORUM
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storeEmbeddings(List<String> chunkList, List<List<Double>> vectorList, String kid, String docId, List<String> fidList) {
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
|
|
||||||
for (int i = 0; i < Math.min(chunkList.size(), vectorList.size()); i++) {
|
|
||||||
List<Double> vector = vectorList.get(i);
|
|
||||||
Float[] vf = vector.stream().map(Double::floatValue).toArray(Float[]::new);
|
|
||||||
|
|
||||||
Map<String, Object> dataSchema = new HashMap<>();
|
|
||||||
dataSchema.put("content", chunkList.get(i));
|
|
||||||
dataSchema.put("kid", kid);
|
|
||||||
dataSchema.put("docId", docId);
|
|
||||||
dataSchema.put("fid", fidList.get(i));
|
|
||||||
String uuid = UUID.randomUUID().toString();
|
|
||||||
dataSchema.put("uuid", uuid);
|
|
||||||
|
|
||||||
Result<WeaviateObject> result = client.data().creator()
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withID(uuid)
|
|
||||||
.withVector(vf)
|
|
||||||
.withProperties(dataSchema)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeByDocId(String kid, String docId) {
|
|
||||||
List<String> resultList = new ArrayList<>();
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Field fieldId = Field.builder().name("uuid").build();
|
|
||||||
WhereFilter where = WhereFilter.builder()
|
|
||||||
.path(new String[]{"docId"})
|
|
||||||
.operator(Operator.Equal)
|
|
||||||
.valueString(docId)
|
|
||||||
.build();
|
|
||||||
Result<GraphQLResponse> result = client.graphQL().get()
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withFields(fieldId)
|
|
||||||
.withWhere(where)
|
|
||||||
.run();
|
|
||||||
LinkedTreeMap<String, Object> t = (LinkedTreeMap<String, Object>) result.getResult().getData();
|
|
||||||
LinkedTreeMap<String, ArrayList<LinkedTreeMap>> l = (LinkedTreeMap<String, ArrayList<LinkedTreeMap>>) t.get("Get");
|
|
||||||
ArrayList<LinkedTreeMap> m = l.get(className + kid);
|
|
||||||
for (LinkedTreeMap linkedTreeMap : m) {
|
|
||||||
String uuid = linkedTreeMap.get("uuid").toString();
|
|
||||||
resultList.add(uuid);
|
|
||||||
}
|
|
||||||
for (String uuid : resultList) {
|
|
||||||
Result<Boolean> deleteResult = client.data().deleter()
|
|
||||||
.withID(uuid)
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withConsistencyLevel(ConsistencyLevel.ALL) // default QUORUM
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeByKid(String kid) {
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Result<Boolean> result = client.schema().classDeleter().withClassName(className + kid).run();
|
|
||||||
if (result.hasErrors()) {
|
|
||||||
System.out.println("删除schema失败" + result.getError());
|
|
||||||
} else {
|
|
||||||
System.out.println("删除schema成功" + result.getResult());
|
|
||||||
}
|
|
||||||
log.info("drop schema by kid, result = {}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> nearest(List<Double> queryVector, String kid) {
|
|
||||||
if (StringUtils.isBlank(kid)) {
|
|
||||||
return new ArrayList<String>();
|
|
||||||
}
|
|
||||||
List<String> resultList = new ArrayList<>();
|
|
||||||
Float[] vf = new Float[queryVector.size()];
|
|
||||||
for (int j = 0; j < queryVector.size(); j++) {
|
|
||||||
Double value = queryVector.get(j);
|
|
||||||
vf[j] = value.floatValue();
|
|
||||||
}
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Field contentField = Field.builder().name("content").build();
|
|
||||||
Field _additional = Field.builder()
|
|
||||||
.name("_additional")
|
|
||||||
.fields(new Field[]{
|
|
||||||
Field.builder().name("distance").build()
|
|
||||||
}).build();
|
|
||||||
NearVectorArgument nearVector = NearVectorArgument.builder()
|
|
||||||
.vector(vf)
|
|
||||||
.distance(1.6f) // certainty = 1f - distance /2f
|
|
||||||
.build();
|
|
||||||
KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoService.queryById(Long.valueOf(kid));
|
|
||||||
Result<GraphQLResponse> result = client.graphQL().get()
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withFields(contentField, _additional)
|
|
||||||
.withNearVector(nearVector)
|
|
||||||
.withLimit(knowledgeInfoVo.getRetrieveLimit())
|
|
||||||
.run();
|
|
||||||
LinkedTreeMap<String, Object> t = (LinkedTreeMap<String, Object>) result.getResult().getData();
|
|
||||||
LinkedTreeMap<String, ArrayList<LinkedTreeMap>> l = (LinkedTreeMap<String, ArrayList<LinkedTreeMap>>) t.get("Get");
|
|
||||||
ArrayList<LinkedTreeMap> m = l.get(className + kid);
|
|
||||||
for (LinkedTreeMap linkedTreeMap : m) {
|
|
||||||
String content = linkedTreeMap.get("content").toString();
|
|
||||||
resultList.add(content);
|
|
||||||
}
|
|
||||||
return resultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> nearest(String query, String kid) {
|
|
||||||
if (StringUtils.isBlank(kid)) {
|
|
||||||
return new ArrayList<String>();
|
|
||||||
}
|
|
||||||
List<String> resultList = new ArrayList<>();
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Field contentField = Field.builder().name("content").build();
|
|
||||||
Field _additional = Field.builder()
|
|
||||||
.name("_additional")
|
|
||||||
.fields(new Field[]{
|
|
||||||
Field.builder().name("distance").build()
|
|
||||||
}).build();
|
|
||||||
NearTextArgument nearText = client.graphQL().arguments().nearTextArgBuilder()
|
|
||||||
.concepts(new String[]{query})
|
|
||||||
.distance(1.6f) // certainty = 1f - distance /2f
|
|
||||||
.build();
|
|
||||||
KnowledgeInfoVo knowledgeInfoVo = knowledgeInfoService.queryById(Long.valueOf(kid));
|
|
||||||
Result<GraphQLResponse> result = client.graphQL().get()
|
|
||||||
.withClassName(className + kid)
|
|
||||||
.withFields(contentField, _additional)
|
|
||||||
.withNearText(nearText)
|
|
||||||
.withLimit(knowledgeInfoVo.getRetrieveLimit())
|
|
||||||
.run();
|
|
||||||
LinkedTreeMap<String, Object> t = (LinkedTreeMap<String, Object>) result.getResult().getData();
|
|
||||||
LinkedTreeMap<String, ArrayList<LinkedTreeMap>> l = (LinkedTreeMap<String, ArrayList<LinkedTreeMap>>) t.get("Get");
|
|
||||||
ArrayList<LinkedTreeMap> m = l.get(className + kid);
|
|
||||||
for (LinkedTreeMap linkedTreeMap : m) {
|
|
||||||
String content = linkedTreeMap.get("content").toString();
|
|
||||||
resultList.add(content);
|
|
||||||
}
|
|
||||||
return resultList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result<Boolean> deleteSchema(String kid) {
|
|
||||||
WeaviateClient client = getClient();
|
|
||||||
Result<Boolean> result = client.schema().classDeleter().withClassName(className + kid).run();
|
|
||||||
if (result.hasErrors()) {
|
|
||||||
System.out.println(result.getError());
|
|
||||||
} else {
|
|
||||||
System.out.println(result.getResult());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<module>ruoyi-chat</module>
|
<module>ruoyi-chat</module>
|
||||||
<module>ruoyi-system</module>
|
<module>ruoyi-system</module>
|
||||||
<module>ruoyi-generator</module>
|
<module>ruoyi-generator</module>
|
||||||
|
<module>ruoyi-wechat</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -32,11 +32,12 @@ public class ChatConfig {
|
|||||||
public OpenAiStreamClient openAiStreamClient() {
|
public OpenAiStreamClient openAiStreamClient() {
|
||||||
String apiHost = configService.getConfigValue("chat", "apiHost");
|
String apiHost = configService.getConfigValue("chat", "apiHost");
|
||||||
String apiKey = configService.getConfigValue("chat", "apiKey");
|
String apiKey = configService.getConfigValue("chat", "apiKey");
|
||||||
openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey);
|
String url = configService.getConfigValue("chat", "apiUrl");
|
||||||
|
openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey,url);
|
||||||
return openAiStreamClient;
|
return openAiStreamClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey,String url) {
|
||||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
||||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||||
@@ -47,6 +48,7 @@ public class ChatConfig {
|
|||||||
.build();
|
.build();
|
||||||
return OpenAiStreamClient.builder()
|
return OpenAiStreamClient.builder()
|
||||||
.apiHost(apiHost)
|
.apiHost(apiHost)
|
||||||
|
.apiUrl(url)
|
||||||
.apiKey(Collections.singletonList(apiKey))
|
.apiKey(Collections.singletonList(apiKey))
|
||||||
.keyStrategy(new KeyRandomStrategy())
|
.keyStrategy(new KeyRandomStrategy())
|
||||||
.okHttpClient(okHttpClient)
|
.okHttpClient(okHttpClient)
|
||||||
|
|||||||
@@ -75,12 +75,19 @@ public class ChatConfigController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 新增配置信息
|
* 新增配置信息
|
||||||
*/
|
*/
|
||||||
@SaCheckPermission("system:config:add")
|
@SaCheckPermission("system:config:edit")
|
||||||
@Log(title = "配置信息", businessType = BusinessType.INSERT)
|
@Log(title = "新增或者修改配置信息", businessType = BusinessType.UPDATE)
|
||||||
@RepeatSubmit()
|
@RepeatSubmit()
|
||||||
@PostMapping()
|
@PostMapping("/saveOrUpdate")
|
||||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatConfigBo bo) {
|
public R<Void> saveOrUpdate(@RequestBody List<ChatConfigBo> boList) {
|
||||||
return toAjax(chatConfigService.insertByBo(bo));
|
for (ChatConfigBo chatConfigBo : boList) {
|
||||||
|
if(chatConfigBo.getId() == null){
|
||||||
|
chatConfigService.insertByBo(chatConfigBo);
|
||||||
|
}else {
|
||||||
|
chatConfigService.updateByBo(chatConfigBo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toAjax(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ public class ChatGptsController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 查询应用管理列表
|
* 查询应用管理列表
|
||||||
*/
|
*/
|
||||||
@SaCheckPermission("system:gpts:list")
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo<ChatGptsVo> list(ChatGptsBo bo, PageQuery pageQuery) {
|
public TableDataInfo<ChatGptsVo> list(ChatGptsBo bo, PageQuery pageQuery) {
|
||||||
return chatGptsService.queryPageList(bo, pageQuery);
|
return chatGptsService.queryPageList(bo, pageQuery);
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ public class ChatPackagePlanController extends BaseController {
|
|||||||
return chatPackagePlanService.queryPageList(bo, pageQuery);
|
return chatPackagePlanService.queryPageList(bo, pageQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询套餐列表-不分页
|
||||||
|
*/
|
||||||
|
@GetMapping("/listPlan")
|
||||||
|
public R<List<ChatPackagePlanVo>> listPlan() {
|
||||||
|
return R.ok(chatPackagePlanService.queryList(new ChatPackagePlanBo()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出套餐管理列表
|
* 导出套餐管理列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package org.ruoyi.chat.controller.chat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.constraints.*;
|
||||||
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||||
|
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||||
|
import org.ruoyi.core.page.TableDataInfo;
|
||||||
|
import org.ruoyi.domain.bo.ChatSessionBo;
|
||||||
|
import org.ruoyi.domain.vo.ChatSessionVo;
|
||||||
|
import org.ruoyi.service.IChatSessionService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.ruoyi.common.log.annotation.Log;
|
||||||
|
import org.ruoyi.common.web.core.BaseController;
|
||||||
|
import org.ruoyi.core.page.PageQuery;
|
||||||
|
import org.ruoyi.common.core.domain.R;
|
||||||
|
import org.ruoyi.common.core.validate.AddGroup;
|
||||||
|
import org.ruoyi.common.core.validate.EditGroup;
|
||||||
|
import org.ruoyi.common.log.enums.BusinessType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话管理
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/session")
|
||||||
|
public class ChatSessionController extends BaseController {
|
||||||
|
|
||||||
|
private final IChatSessionService chatSessionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会话管理列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:session:list")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public TableDataInfo<ChatSessionVo> list(ChatSessionBo bo, PageQuery pageQuery) {
|
||||||
|
return chatSessionService.queryPageList(bo, pageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出会话管理列表
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:session:export")
|
||||||
|
@Log(title = "会话管理", businessType = BusinessType.EXPORT)
|
||||||
|
@PostMapping("/export")
|
||||||
|
public void export(ChatSessionBo bo, HttpServletResponse response) {
|
||||||
|
List<ChatSessionVo> list = chatSessionService.queryList(bo);
|
||||||
|
ExcelUtil.exportExcel(list, "会话管理", ChatSessionVo.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话管理详细信息
|
||||||
|
*
|
||||||
|
* @param id 主键
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:session:query")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public R<ChatSessionVo> getInfo(@NotNull(message = "主键不能为空")
|
||||||
|
@PathVariable Long id) {
|
||||||
|
return R.ok(chatSessionService.queryById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增会话管理
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:session:add")
|
||||||
|
@Log(title = "会话管理", businessType = BusinessType.INSERT)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PostMapping()
|
||||||
|
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatSessionBo bo) {
|
||||||
|
return toAjax(chatSessionService.insertByBo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会话管理
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:session:edit")
|
||||||
|
@Log(title = "会话管理", businessType = BusinessType.UPDATE)
|
||||||
|
@RepeatSubmit()
|
||||||
|
@PutMapping()
|
||||||
|
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ChatSessionBo bo) {
|
||||||
|
return toAjax(chatSessionService.updateByBo(bo));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会话管理
|
||||||
|
*
|
||||||
|
* @param ids 主键串
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("system:session:remove")
|
||||||
|
@Log(title = "会话管理", businessType = BusinessType.DELETE)
|
||||||
|
@DeleteMapping("/{ids}")
|
||||||
|
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||||
|
@PathVariable Long[] ids) {
|
||||||
|
return toAjax(chatSessionService.deleteWithValidByIds(List.of(ids), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,10 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author ageer
|
* 知识库管理
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
*/
|
*/
|
||||||
@Validated
|
@Validated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘画(换脸)任务查询
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
@Api(tags = "任务查询")
|
@Api(tags = "任务查询")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/mj")
|
@RequestMapping("/mj")
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ import org.ruoyi.common.core.utils.OkHttpUtil;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 描述:文生视频
|
* 文生视频
|
||||||
*
|
*
|
||||||
* @author ageerle@163.com
|
* @author ageerle
|
||||||
* date 2024/6/27
|
* @date 2025-05-03
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/luma")
|
@RequestMapping("/luma")
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘画任务提交
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-05-03
|
||||||
|
*/
|
||||||
@Api(tags = "任务提交")
|
@Api(tags = "任务提交")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/mj/submit")
|
@RequestMapping("/mj/submit")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user