29 Commits

Author SHA1 Message Date
ageerle
9c2586ab43 Update README.md 2025-05-09 17:24:55 +08:00
ageerle
1bddf5df3b feat: 兼容多平台模型 2025-05-09 16:45:41 +08:00
ageer
57b2f833f8 feat: 修复向量库初始化失败导致程序启动失败 2025-05-08 23:56:40 +08:00
ageer
7907cbeb7f feat: 修复向量库初始化失败导致程序启动失败 2025-05-08 19:31:46 +08:00
ageer
9cfcdd2b9b feat: 代码优化 2025-05-08 19:00:48 +08:00
ageerle
980df20752 feat: 支持milvus、qdrant向量库 2025-05-08 16:09:02 +08:00
ageerle
aa92d232bb feat: Weaviate操作向量库功能优化 2025-05-08 10:41:01 +08:00
ageer
81c0bb5738 feat: Weaviate改为langchain4j方式调用 2025-05-07 22:53:21 +08:00
ageerle
1a645c6e10 feat: 接入langchain4j操作向量库 2025-05-07 17:33:22 +08:00
ageer
731f6ceb6e feat: sql脚本 2025-05-05 15:10:53 +08:00
ageer
8e21245348 feat: sql脚本 2025-05-05 15:04:02 +08:00
ageer
936e157e4a feat: sql脚本 2025-05-05 15:02:57 +08:00
ageer
256f72c487 feat: 支付模块引用 2025-05-03 15:37:56 +08:00
ageer
009aa5f1a5 feat: 添加接口说明 2025-05-03 15:27:46 +08:00
ageer
d22d2cb708 fix(接口文档): 修复接口文档/v3/api-docs无法正常访问 2025-05-03 14:31:20 +08:00
ageer
59104028b6 feat: 会话管理 2025-05-03 10:45:29 +08:00
ageer
c2f6a8321a feat: 会话管理 2025-05-03 10:41:50 +08:00
ageer
5476a4b0b7 fix: 公众号登录功能同步 2025-04-30 19:34:09 +08:00
ageerle
cd490aa0e5 feat: 新增wechat模块 2025-04-30 11:29:23 +08:00
ageerle
b1ff44df4b feat: sse对话样式优化 2025-04-29 11:44:47 +08:00
ageerle
dc6d00f0fc feat: 查询gpts无需登录 2025-04-29 11:00:34 +08:00
ageerle
990da8da6f feat: 增加工具调用超时时间配置 2025-04-24 17:09:52 +08:00
ageerle
8fc7ad0359 feat: mcp开启后才执行工具调用 2025-04-24 10:31:58 +08:00
ageer
8a89d9eb9c fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 2025-04-23 23:10:31 +08:00
ageer
abfa9ae97b fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 2025-04-23 23:09:56 +08:00
ageer
bde2e8ff8e fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 2025-04-23 23:09:22 +08:00
ageer
4d8eb1850f feat: 附件上传逻辑调整 2025-04-22 21:43:40 +08:00
ageer
5aaa3a5662 fix: 1. 修复No static resource tool/gen/list. 2. 修复新增数据库校验异常 2025-04-22 21:24:00 +08:00
ageerle
8fec96f5b2 feat: 默认关闭mcp功能 2025-04-22 18:01:40 +08:00
101 changed files with 2513 additions and 994 deletions

View File

@@ -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)
### 附:技术讨论群 ### 附:技术讨论群

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 227 KiB

12
pom.xml
View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,12 @@ public class ChatRequest {
*/ */
private Long userId; private Long userId;
/**
* 会话id
*/
private Long sessionId;
/** /**
* 应用ID * 应用ID
*/ */

View File

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

View File

@@ -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 {
/** /**
* 文档基本信息 * 文档基本信息

View File

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

View File

@@ -1 +1 @@
org.ruoyi.common.doc.config.SwaggerConfig org.ruoyi.common.doc.config.SpringDocConfig

View File

@@ -34,6 +34,11 @@ public class ChatMessage extends BaseEntity {
*/ */
private Long userId; private Long userId;
/**
* 会话id
*/
private Long sessionId;
/** /**
* 消息内容 * 消息内容
*/ */

View File

@@ -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;
/** /**
* 密钥 * 密钥
*/ */

View File

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

View File

@@ -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;
/** /**
* 对话角色 * 对话角色
*/ */

View File

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

View File

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

View File

@@ -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;
/** /**
* 消息内容 * 消息内容
*/ */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()));
}
/** /**
* 导出套餐管理列表 * 导出套餐管理列表
*/ */

View File

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

View File

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

View File

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

View File

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

View File

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