Compare commits
19 Commits
f24ff5bbdd
...
v2.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15c306eca2 | ||
|
|
620ea1fc76 | ||
|
|
c5c375dc6d | ||
|
|
1b793e822a | ||
|
|
6281840f36 | ||
|
|
f5daa7eb78 | ||
|
|
52cb563383 | ||
|
|
69efc3261e | ||
|
|
fb19fb29cc | ||
|
|
9f257cf712 | ||
|
|
4af26fe4b4 | ||
|
|
ab2a118ee9 | ||
|
|
9cd97a4dc5 | ||
|
|
788b372e32 | ||
|
|
744f9b6c7f | ||
|
|
761d954ef1 | ||
|
|
d1006f50ad | ||
|
|
b50c15755d | ||
|
|
09abc0b5af |
125
README.md
@@ -36,48 +36,75 @@
|
||||
|
||||
## 目录
|
||||
|
||||
- [系统体验](#系统体验)
|
||||
- [源码地址](#源码地址)
|
||||
- [特色功能](#特色功能)
|
||||
- [配套文档](#项目文档)
|
||||
- [核心功能](#核心功能)
|
||||
- [项目演示](#项目演示)
|
||||
- [后台管理](#后台管理)
|
||||
- [管理端](#管理端)
|
||||
- [用户端](#用户端)
|
||||
- [小程序端](#小程序端)
|
||||
- [开发前的配置要求](#开发前的配置要求)
|
||||
- [文件目录说明](#文件目录说明)
|
||||
- [使用到的框架](#使用到的框架)
|
||||
- [开发环境](#开发环境)
|
||||
- [项目结构](#项目结构)
|
||||
- [ruoyi-ai](#ruoyi-ai)
|
||||
- [注意事项](#注意事项)
|
||||
- [vben模板](#vben模板)
|
||||
- [贡献者](#贡献者)
|
||||
- [如何参与开源项目](#如何参与开源项目)
|
||||
- [版本控制](#版本控制)
|
||||
- [作者](#作者)
|
||||
- [鸣谢](#鸣谢)
|
||||
- [技术讨论群](#技术讨论群)
|
||||
|
||||
### 系统体验
|
||||
- 用户端:https://web.pandarobot.chat
|
||||
- 管理端:https://admin.pandarobot.chat
|
||||
|
||||
用户名: admin 密码:admin123
|
||||
|
||||
### 源码地址
|
||||
- 项目文档: https://doc.pandarobot.chat
|
||||
- 前端-后台管理: https://github.com/ageerle/ruoyi-admin
|
||||
- 前端-用户端: https://github.com/ageerle/ruoyi-web
|
||||
- 小程序端: https://github.com/ageerle/ruoyi-uniapp
|
||||
- 演示地址: https://web.pandarobot.chat
|
||||
- 后台管理: https://admin.pandarobot.chat
|
||||
- 用户名: 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
|
||||
|
||||
### gitcode源码地址
|
||||
- https://gitcode.com/ageerle/ruoyi-ai
|
||||
- https://gitcode.com/ageerle/ruoyi-web
|
||||
- https://gitcode.com/ageerle/ruoyi-admin
|
||||
- https://gitcode.com/ageerle/ruoyi-uniapp
|
||||
[2]gitee
|
||||
- 前端服务-用户端: https://gitee.com/ageerle/ruoyi-web
|
||||
- 前端服务-管理端: https://gitee.com/ageerle/ruoyi-admin
|
||||
- 前端服务-小程序端: https://gitee.com/ageerle/ruoyi-uniapp
|
||||
- 后端服务:https://gitee.com/ageerle/ruoyi-ai
|
||||
|
||||
### 特色功能
|
||||
[3]gitcode
|
||||
- 前端服务-用户端:https://gitcode.com/ageerle/ruoyi-web
|
||||
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
|
||||
- 前端服务-小程序端: https://gitcode.com/ageerle/ruoyi-uniapp
|
||||
- 后端服务:https://gitcode.com/ageerle/ruoyi-ai
|
||||
|
||||
### 配套文档
|
||||
- 配套文档: https://doc.pandarobot.chat
|
||||
- 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
|
||||
|
||||
### 核心功能
|
||||
1. 全套开源系统:提供完整的前端应用、后台管理以及小程序应用,基于MIT协议,开箱即用。
|
||||
2. 本地RAG方案:集成Milvus/Weaviate向量库、本地向量化模型与Ollama,实现本地化RAG
|
||||
2. 本地RAG方案:集成Milvus/Weaviate向量库、本地向量化模型与Ollama,实现本地化RAG。
|
||||
3. 丰富插件功能:支持联网、SQL查询插件及Text2API插件,扩展系统能力与应用场景。
|
||||
4. 内置SSE、websocket等网络协议,支持对接多种大语言模型,同时还集成了MidJourney和DALLE AI绘画功能
|
||||
5. 强大的多媒体功能:支持AI翻译、PPT制作、语音克隆和翻唱等
|
||||
6. 扩展功能:支持将大模型接入个人或企业微信
|
||||
7. 支付功能:支持易支付、微信支付等多种支付方式
|
||||
4. 内置SSE、websocket等网络协议,支持对接多种大语言模型,同时还集成了MidJourney和DALLE AI绘画功能。
|
||||
5. 强大的多媒体功能:支持AI翻译、PPT制作、语音克隆和翻唱等。
|
||||
6. 扩展功能:支持将大模型接入个人或企业微信。
|
||||
7. 支付功能:支持易支付、微信支付等多种支付方式。
|
||||
|
||||
### 项目演示
|
||||
|
||||
#### 后台管理
|
||||
#### mcp支持(需要切换dev分支 下周发布正式版)
|
||||
<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-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-03.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-04.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);"/>
|
||||
</div>
|
||||
|
||||
#### 管理端
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
||||
<img src="image/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/03.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);"/>
|
||||
@@ -100,7 +127,7 @@
|
||||
<img src="image/07.png" alt="drawing" style="width: 320px; height: 600px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||
</div>
|
||||
|
||||
### 开发前的配置要求
|
||||
### 开发环境
|
||||
|
||||
1. jdk 17
|
||||
2. mysql 5.7、8.0
|
||||
@@ -108,8 +135,14 @@
|
||||
4. maven 3.8+
|
||||
5. nodejs 20+ & pnpm
|
||||
|
||||
### 文件目录说明
|
||||
RuoYi-AI
|
||||
- 附-部署配套视频:https://www.bilibili.com/video/BV1jDXkYWEba
|
||||
|
||||
<div>
|
||||
<img src="image/教程搭建.png" alt="drawing" width="600px" height="300px"/>
|
||||
</div>
|
||||
|
||||
### 项目结构
|
||||
- RuoYi-AI
|
||||
|
||||
```
|
||||
├─ ruoyi-admin // 管理模块
|
||||
@@ -158,6 +191,14 @@ RuoYi-AI
|
||||
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
- vben模板
|
||||
|
||||
|
||||
Q:vben5 的模板默认是没有的吗?
|
||||
|
||||
A:vben模板是收费的 请联系vben-vue-plus作者获取。
|
||||
|
||||
### 版本控制
|
||||
|
||||
该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
|
||||
@@ -170,17 +211,10 @@ RuoYi-AI
|
||||
|
||||
### 项目现状
|
||||
|
||||
目前,项目还处于早期阶段,距离成熟还有很长的路要走。由于个人精力有限,项目的发展速度受到了一定的限制。为了加快项目的进度,我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者,还是刚刚入门的小白,我都热烈欢迎你们提交Pull Request(PR)。即使代码修改得很少,或者存在一些错误,都没有关系。我会认真审核每一位贡献者的代码,并和大家一起完善项目。
|
||||
目前,项目还处于早期阶段,距离成熟还有很长的路要走。由于个人精力有限,项目的发展速度受到了一定的限制。为了加快项目的进度,我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者,还是刚刚入门的小白,我都热烈欢迎你们提交Pull Request(PR)👏👏👏。即使代码修改得很少,或者存在一些错误,都没有关系。我会认真审核每一位贡献者的代码,并和大家一起完善项目⛽️⛽️⛽️。
|
||||
|
||||
### 开发计划
|
||||
|
||||
- 智能体管理
|
||||
|
||||
通过设置提示词、插件、知识库等,用户可以快速构建一个AI应用。这将极大地简化AI应用的开发流程,降低开发门槛,使更多企业能够轻松地利用AI技术。
|
||||
<div>
|
||||
<img src="image/13.png" alt="drawing" width="600px" height="300px"/>
|
||||
</div>
|
||||
|
||||
- 流程编排
|
||||
|
||||
通过流程编排功能,用户可以将不同的模型按照业务逻辑进行有序连接。这将解决单一模型能力不足的问题,充分发挥多个模型的协同作用,从而更好地满足企业的复杂业务需求。
|
||||
@@ -193,7 +227,7 @@ RuoYi-AI
|
||||
|
||||
#### 如何参与开源项目
|
||||
|
||||
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献都是**非常感谢**的。
|
||||
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献,我们都非常感谢!🙏
|
||||
|
||||
1. Fork 这个项目
|
||||
2. 创建你的功能分支 (`git checkout -b feature/dev`)
|
||||
@@ -231,4 +265,23 @@ RuoYi-AI
|
||||
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
|
||||
|
||||
|
||||
### 附:技术讨论群
|
||||
|
||||
#### 全面开放,欢迎加入
|
||||
🏠 wx:ruoyi-ai(加人备注:ruoyi-ai)
|
||||
|
||||
🏠 qq:1603234088 (加人备注:ruoyi-ai)
|
||||
|
||||
👏👏👏 ruoyi-ai官方交流1群(qq区):1034554687
|
||||
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
||||
<img src="image/QQ区-官方交流1群.png" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||
</div>
|
||||
|
||||
👏👏👏 ruoyi-ai官方交流4群(微信区):
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
|
||||
<img src="image/WX区-官方交流4群.jpg" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
image/QQ区-官方交流1群.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
image/WX区-官方交流4群.jpg
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
image/mcp-01.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
image/mcp-02.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
image/mcp-03.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
image/mcp-04.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
image/qq-msg.png
Normal file
|
After Width: | Height: | Size: 391 KiB |
BIN
image/wx-msg.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
image/wx-msg2.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
image/教程搭建.png
Normal file
|
After Width: | Height: | Size: 341 KiB |
@@ -63,6 +63,8 @@ public class AuthController {
|
||||
body.getUsername(), body.getPassword(),
|
||||
body.getCode(), body.getUuid());
|
||||
loginVo.setToken(token);
|
||||
// 兼容后台管理登录
|
||||
loginVo.setAccess_token(token);
|
||||
loginVo.setUserInfo(LoginHelper.getLoginUser());
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
@@ -323,16 +323,16 @@ wechat:
|
||||
spring:
|
||||
ai:
|
||||
openai:
|
||||
api-key: sk-xX
|
||||
api-key: sk-xxx
|
||||
base-url: https://api.pandarobot.chat/
|
||||
ollama:
|
||||
base-url: http://localhost:11434
|
||||
mcp:
|
||||
client:
|
||||
enabled: true
|
||||
name: call-mcp-server
|
||||
name: ruoyi-ai-mcp
|
||||
sse:
|
||||
connections:
|
||||
server1:
|
||||
url: http://127.0.0.1:6040
|
||||
server:
|
||||
url: http://127.0.0.1:8081
|
||||
stdio:
|
||||
servers-configuration: classpath:mcp-server.json
|
||||
|
||||
|
||||
22
ruoyi-admin/src/main/resources/mcp-server.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"fileSystem": {
|
||||
"command": "C:\\Program Files\\nodejs\\npx.cmd",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"D:\\"
|
||||
]
|
||||
},
|
||||
"search1api": {
|
||||
"command": "C:\\Program Files\\nodejs\\npx.cmd",
|
||||
"args": [
|
||||
"-y",
|
||||
"search1api-mcp"
|
||||
],
|
||||
"env": {
|
||||
"SEARCH1API_KEY": "xx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public class ChatRequest {
|
||||
*/
|
||||
private Boolean search = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* 是否开启mcp
|
||||
*/
|
||||
private Boolean isMcp = Boolean.FALSE;
|
||||
|
||||
/**
|
||||
* 知识库id
|
||||
*/
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>call-mcp-server</artifactId>
|
||||
<name>Archetype - call-mcp-server</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CallMcpServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CallMcpServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver.cofing;
|
||||
|
||||
import io.modelcontextprotocol.client.McpClient;
|
||||
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Configuration
|
||||
public class McpClientCfg implements McpSyncClientCustomizer {
|
||||
|
||||
|
||||
@Override
|
||||
public void customize(String name, McpClient.SyncSpec spec) {
|
||||
// do nothing
|
||||
spec.requestTimeout(Duration.ofSeconds(30));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver.view;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.InMemoryChatMemory;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
|
||||
/**
|
||||
* @author jianzhang
|
||||
* 2025/03/18/下午8:00
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/dashscope/chat-client")
|
||||
public class ChatController {
|
||||
|
||||
private final ChatClient chatClient;
|
||||
|
||||
private final ChatMemory chatMemory = new InMemoryChatMemory();
|
||||
|
||||
public ChatController(ChatClient.Builder chatClientBuilder,ToolCallbackProvider tools) {
|
||||
this.chatClient = chatClientBuilder
|
||||
.defaultTools(tools)
|
||||
.defaultOptions(
|
||||
OpenAiChatOptions.builder().model("gpt-4o-mini").build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/generate_stream", method = RequestMethod.GET)
|
||||
public Flux<ChatResponse> generateStream(HttpServletResponse response, @RequestParam("id") String id, @RequestParam("prompt") String prompt) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
|
||||
|
||||
|
||||
Flux<ChatResponse> chatResponseFlux = this.chatClient.prompt(prompt)
|
||||
.advisors(messageChatMemoryAdvisor)
|
||||
.stream()
|
||||
.chatResponse();
|
||||
|
||||
Flux<String> content = this.chatClient.prompt(prompt)
|
||||
.advisors(messageChatMemoryAdvisor)
|
||||
.stream()
|
||||
.content();
|
||||
|
||||
content.subscribe(
|
||||
content1 -> System.out.println("chatResponse"+content1)
|
||||
);
|
||||
return chatResponseFlux;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/advisor/chat/{id}/{prompt}")
|
||||
public Flux<String> advisorChat(
|
||||
HttpServletResponse response,
|
||||
@PathVariable String id,
|
||||
@PathVariable String prompt) {
|
||||
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, id, 10);
|
||||
return this.chatClient.prompt(prompt)
|
||||
.advisors(messageChatMemoryAdvisor).stream().content();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.ruoyi.rocket.callmcpserver.view;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author jianzhang
|
||||
* 2025/03/18/下午8:00
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String chat(Model model) {
|
||||
//model.addAttribute("name", "User");
|
||||
// 返回视图名称,对应 templates/index.html
|
||||
return "index";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
server:
|
||||
port: 9999
|
||||
spring:
|
||||
ai:
|
||||
openai:
|
||||
api-key: sk-xXe1WMPjhlVb1aiI1b4c6c8934D8463f9e4b67Ed8718B772
|
||||
base-url: https://api.pandarobot.chat/
|
||||
mcp:
|
||||
client:
|
||||
enabled: true
|
||||
name: call-mcp-server
|
||||
sse:
|
||||
connections:
|
||||
server1:
|
||||
url: http://127.0.0.1:6040
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"fileSystem": {
|
||||
"command": "D:\\software\\nodeJs\\npx.cmd",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"D:\\software\\sqlite"
|
||||
]
|
||||
},
|
||||
"sqlLite": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"mcp-server-sqlite",
|
||||
"--db-path",
|
||||
"D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db"
|
||||
]
|
||||
},
|
||||
"fetch": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"mcp-server-fetch"
|
||||
]
|
||||
},
|
||||
"baidu-map": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"run",
|
||||
"--with",
|
||||
"mcp[cli]",
|
||||
"mcp",
|
||||
"run",
|
||||
"D:\\work-space-python\\python-baidu-map\\baidu_map_mcp_server\\map.py"
|
||||
],
|
||||
"env": {
|
||||
"BAIDU_MAPS_API_KEY": "{百度地图API-KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"fileSystem": {
|
||||
"command": "D:\\software\\nodeJs\\npx.cmd",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"D:\\software\\sqlite"
|
||||
]
|
||||
},
|
||||
"sqlLite": {
|
||||
"command": "D:\\Program Files\\python3.12.3\\Scripts\\uvx.exe",
|
||||
"args": [
|
||||
"mcp-server-sqlite",
|
||||
"--db-path",
|
||||
"D:\\work-space-study\\spring-ai-mcp-demo\\mcp-client\\src\\main\\resources\\test.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI 对话助手</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen">
|
||||
<div class="container mx-auto p-4 max-w-3xl">
|
||||
<!-- 标题 -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800">AI 对话助手</h1>
|
||||
<p class="text-gray-600 mt-2">基于 Spring AI 的流式对话系统 By AhuCodingBeast</p>
|
||||
</div>
|
||||
|
||||
<!-- 聊天容器 -->
|
||||
<div id="chat-container" class="bg-white rounded-xl shadow-lg p-4 mb-4 h-[500px] overflow-y-auto space-y-4">
|
||||
<!-- 初始欢迎消息 -->
|
||||
<div class="ai-message flex items-start gap-3">
|
||||
<div class="bg-green-100 p-3 rounded-lg max-w-[85%]">
|
||||
<span class="text-gray-800">您好!我是AI助手,有什么可以帮您?</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="message-input"
|
||||
class="flex-1 border border-gray-300 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="输入您的问题...">
|
||||
<button id="send-button"
|
||||
class="bg-blue-500 text-white px-6 py-3 rounded-xl hover:bg-blue-600 transition-colors flex items-center">
|
||||
<span>发送</span>
|
||||
<svg id="loading-spinner" class="hidden w-4 h-4 ml-2 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const messageInput = document.getElementById('message-input');
|
||||
const sendButton = document.getElementById('send-button');
|
||||
const loadingSpinner = document.getElementById('loading-spinner');
|
||||
|
||||
// 发送消息处理
|
||||
function handleSend() {
|
||||
const message = messageInput.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
// 添加用户消息
|
||||
addMessage(message, 'user');
|
||||
messageInput.value = '';
|
||||
|
||||
// 构建API URL
|
||||
const apiUrl = new URL('http://localhost:9999/dashscope/chat-client/generate_stream');
|
||||
apiUrl.searchParams.append('id', '01');
|
||||
apiUrl.searchParams.append('prompt', message);
|
||||
|
||||
// 显示加载状态
|
||||
sendButton.disabled = true;
|
||||
loadingSpinner.classList.remove('hidden');
|
||||
|
||||
// 创建EventSource连接
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
let aiMessageElement = null;
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log(data);
|
||||
const content = data.result?.output?.text || '';
|
||||
const finishReason = data.result?.metadata?.finishReason;
|
||||
|
||||
// 创建消息容器(如果不存在)
|
||||
if (!aiMessageElement) {
|
||||
aiMessageElement = addMessage('', 'ai');
|
||||
}
|
||||
|
||||
// 追加内容
|
||||
if (content) {
|
||||
aiMessageElement.querySelector('.message-content').textContent += content;
|
||||
autoScroll();
|
||||
}
|
||||
|
||||
// 处理结束
|
||||
if (finishReason === 'STOP') {
|
||||
eventSource.close();
|
||||
sendButton.disabled = false;
|
||||
loadingSpinner.classList.add('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('连接错误:', error);
|
||||
eventSource.close();
|
||||
sendButton.disabled = false;
|
||||
loadingSpinner.classList.add('hidden');
|
||||
addMessage('对话连接异常,请重试', 'ai', true);
|
||||
};
|
||||
}
|
||||
|
||||
// 添加消息到容器
|
||||
function addMessage(content, type, isError = false) {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `${type}-message flex items-start gap-3`;
|
||||
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = `p-3 rounded-lg max-w-[85%] ${
|
||||
type === 'user'
|
||||
? 'bg-blue-500 text-white ml-auto'
|
||||
: `bg-green-100 ${isError ? 'text-red-500' : 'text-gray-800'}`
|
||||
}`;
|
||||
|
||||
const contentSpan = document.createElement('span');
|
||||
contentSpan.className = 'message-content';
|
||||
contentSpan.textContent = content;
|
||||
|
||||
bubble.appendChild(contentSpan);
|
||||
messageDiv.appendChild(bubble);
|
||||
chatContainer.appendChild(messageDiv);
|
||||
|
||||
autoScroll();
|
||||
return bubble;
|
||||
}
|
||||
|
||||
// 自动滚动到底部
|
||||
function autoScroll() {
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
// 事件监听
|
||||
sendButton.addEventListener('click', handleSend);
|
||||
messageInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,31 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-mcp-server</artifactId>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>ruoyi-mcp-serve</name>
|
||||
<description>ruoyi-mcp-serve</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-ai.version>1.0.0-M7</spring-ai.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -34,31 +47,30 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.ruoyi</groupId>-->
|
||||
<!-- <artifactId>ruoyi-system-api</artifactId>-->
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <groupId>org.ruoyi</groupId>-->
|
||||
<!-- <artifactId>ruoyi-common-translation</artifactId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.ruoyi.mcp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class McpServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(McpServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package org.ruoyi.mcp.config;
|
||||
|
||||
import org.ruoyi.mcp.service.McpCustomService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class McpServerConfig implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
public ToolCallbackProvider openLibraryTools(McpCustomService mcpService) {
|
||||
return MethodToolCallbackProvider.builder().toolObjects(mcpService).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public List<McpServerFeatures.SyncResourceRegistration> resourceRegistrations() {
|
||||
|
||||
// Create a resource registration for system information
|
||||
var systemInfoResource = new McpSchema.Resource(
|
||||
"system://info",
|
||||
"System Information",
|
||||
"Provides basic system information including Java version, OS, etc.",
|
||||
"application/json", null
|
||||
);
|
||||
|
||||
var resourceRegistration = new McpServerFeatures.SyncResourceRegistration(systemInfoResource, (request) -> {
|
||||
try {
|
||||
var systemInfo = Map.of(
|
||||
"javaVersion", System.getProperty("java.version"),
|
||||
"osName", System.getProperty("os.name"),
|
||||
"osVersion", System.getProperty("os.version"),
|
||||
"osArch", System.getProperty("os.arch"),
|
||||
"processors", Runtime.getRuntime().availableProcessors(),
|
||||
"timestamp", System.currentTimeMillis());
|
||||
|
||||
String jsonContent = new ObjectMapper().writeValueAsString(systemInfo);
|
||||
|
||||
return new McpSchema.ReadResourceResult(
|
||||
List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent)));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to generate system info", e);
|
||||
}
|
||||
});
|
||||
|
||||
return List.of(resourceRegistration);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() {
|
||||
|
||||
var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt",
|
||||
List.of(new McpSchema.PromptArgument("name", "The name to greet", true)));
|
||||
|
||||
var promptRegistration = new McpServerFeatures.SyncPromptRegistration(prompt, getPromptRequest -> {
|
||||
|
||||
String nameArgument = (String) getPromptRequest.arguments().get("name");
|
||||
if (nameArgument == null) {
|
||||
nameArgument = "friend";
|
||||
}
|
||||
|
||||
var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER,
|
||||
new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?"));
|
||||
|
||||
return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage));
|
||||
});
|
||||
|
||||
return List.of(promptRegistration);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public Consumer<List<McpSchema.Root>> rootsChangeConsumer() {
|
||||
return roots -> {
|
||||
System.out.println("rootsChange");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.ruoyi.mcp.service;
|
||||
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
public class McpCustomService {
|
||||
|
||||
public record User(String userName, String userBalance) {
|
||||
}
|
||||
|
||||
@Tool(description = "根据用户名称查询用户信息")
|
||||
public User getUserBalance(String username) {
|
||||
return new User("admin","99.99");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.ruoyi.mcpserve;
|
||||
|
||||
import org.ruoyi.mcpserve.service.ToolService;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class RuoyiMcpServeApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RuoyiMcpServeApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ToolCallbackProvider systemTools(ToolService toolService) {
|
||||
return MethodToolCallbackProvider.builder().toolObjects(toolService).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.ruoyi.mcpserve.service;
|
||||
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
public class ToolService {
|
||||
|
||||
@Tool(description = "获取一个指定前缀的随机数")
|
||||
public String add(@ToolParam(description = "字符前缀") String prefix) {
|
||||
// 定义日期格式
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMdd");
|
||||
//根据当前时间获取yyMMdd格式的时间字符串
|
||||
String format = LocalDate.now().format(formatter);
|
||||
//生成随机数
|
||||
String replace = prefix + UUID.randomUUID().toString().replace("-", "");
|
||||
return format + replace;
|
||||
}
|
||||
|
||||
@Tool(description = "获取当前时间")
|
||||
public LocalDateTime getCurrentTime() {
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
server:
|
||||
port: 6040
|
||||
port: 8081
|
||||
spring:
|
||||
application:
|
||||
name: mcp-server
|
||||
ai:
|
||||
mcp:
|
||||
server:
|
||||
name: webmvc-mcp-server
|
||||
name: ruoyi-mcp-serve
|
||||
version: 1.0.0
|
||||
type: SYNC
|
||||
sse-message-endpoint: /mcp/messages
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-ai.version>1.0.0-M7</spring-ai.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -23,7 +24,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bom</artifactId>
|
||||
<version>1.0.0-M6</version>
|
||||
<version>${spring-ai.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -32,6 +33,13 @@
|
||||
|
||||
<!-- 对话基础模块 -->
|
||||
<dependencies>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>io.modelcontextprotocol.sdk</groupId>-->
|
||||
<!-- <artifactId>mcp-spring-webflux</artifactId>-->
|
||||
<!-- <version>0.8.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-chat</artifactId>
|
||||
@@ -55,17 +63,12 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
|
||||
<artifactId>spring-ai-starter-mcp-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.ai</groupId>-->
|
||||
<!-- <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -65,7 +65,6 @@ public class ChatModelBo extends BaseEntity {
|
||||
/**
|
||||
* 系统提示词
|
||||
*/
|
||||
@NotBlank(message = "系统提示词不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String systemPrompt;
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,7 @@ public class KnowledgeFragment extends BaseEntity {
|
||||
/**
|
||||
* 片段索引下标
|
||||
*/
|
||||
private Long idx;
|
||||
private Integer idx;
|
||||
|
||||
/**
|
||||
* 文档内容
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.ruoyi.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Data
|
||||
public class KnowledgeInfoUploadBo {
|
||||
|
||||
private String kid;
|
||||
|
||||
private MultipartFile file;
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import org.ruoyi.domain.bo.KnowledgeAttachBo;
|
||||
import org.ruoyi.domain.vo.KnowledgeAttachVo;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -46,4 +47,17 @@ public interface IKnowledgeAttachService {
|
||||
* 校验并批量删除知识库附件信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 删除知识附件
|
||||
*/
|
||||
void removeKnowledgeAttach(String docId);
|
||||
|
||||
/**
|
||||
* 翻译文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @param targetLanguage 目标语音
|
||||
*/
|
||||
String translationByFile(MultipartFile file, String targetLanguage);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.ruoyi.service;
|
||||
|
||||
|
||||
import org.ruoyi.domain.bo.KnowledgeInfoBo;
|
||||
import org.ruoyi.domain.bo.KnowledgeInfoUploadBo;
|
||||
import org.ruoyi.domain.vo.KnowledgeInfoVo;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
@@ -46,4 +47,19 @@ public interface IKnowledgeInfoService {
|
||||
* 校验并批量删除知识库信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 新增知识库
|
||||
*/
|
||||
void saveOne(KnowledgeInfoBo bo);
|
||||
|
||||
/**
|
||||
* 删除知识库
|
||||
*/
|
||||
void removeKnowledge(String id);
|
||||
|
||||
/**
|
||||
* 上传附件
|
||||
*/
|
||||
void upload(KnowledgeInfoUploadBo bo);
|
||||
}
|
||||
|
||||
@@ -9,13 +9,16 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.domain.vo.KnowledgeAttachVo;
|
||||
import org.ruoyi.mapper.KnowledgeFragmentMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.ruoyi.domain.bo.KnowledgeAttachBo;
|
||||
|
||||
import org.ruoyi.domain.KnowledgeAttach;
|
||||
import org.ruoyi.mapper.KnowledgeAttachMapper;
|
||||
import org.ruoyi.service.IKnowledgeAttachService;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
@@ -31,6 +34,7 @@ import java.util.Collection;
|
||||
public class KnowledgeAttachServiceImpl implements IKnowledgeAttachService {
|
||||
|
||||
private final KnowledgeAttachMapper baseMapper;
|
||||
private final KnowledgeFragmentMapper fragmentMapper;
|
||||
|
||||
/**
|
||||
* 查询知识库附件
|
||||
@@ -111,4 +115,64 @@ public class KnowledgeAttachServiceImpl implements IKnowledgeAttachService {
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeKnowledgeAttach(String docId) {
|
||||
Map<String,Object> map = new HashMap<>();
|
||||
map.put("doc_id",docId);
|
||||
baseMapper.deleteByMap(map);
|
||||
fragmentMapper.deleteByMap(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String translationByFile(MultipartFile file, String targetLanguage) {
|
||||
/*String fileName = file.getOriginalFilename();
|
||||
String docType = fileName.substring(fileName.lastIndexOf(".")+1);
|
||||
String content = "";
|
||||
ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(docType);
|
||||
try {
|
||||
content = resourceLoader.getContent(file.getInputStream());
|
||||
} catch (IOException e) {
|
||||
throw new BaseException("该文件类型暂不支持!");
|
||||
}
|
||||
// 翻译模型固定为gpt-4o-mini
|
||||
String model = "gpt-4o-mini";
|
||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
chatMessageBo.setUserId(getUserId());
|
||||
chatMessageBo.setModelName(model);
|
||||
chatMessageBo.setContent(content);
|
||||
chatMessageBo.setDeductCost(0.01);
|
||||
chatMessageBo.setTotalTokens(0);
|
||||
OpenAiStreamClient openAiStreamClient = chatConfig.getOpenAiStreamClient();
|
||||
List<Message> messageList = new ArrayList<>();
|
||||
Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content("你是一位精通各国语言的翻译大师\n" +
|
||||
"\n" +
|
||||
"请将用户输入词语翻译成{" + targetLanguage + "}\n" +
|
||||
"\n" +
|
||||
"==示例输出==\n" +
|
||||
"**原文** : <这里显示要翻译的原文信息>\n" +
|
||||
"**翻译** : <这里显示翻译之后的结果>\n" +
|
||||
"**总结** : <这里是对关键信息一个总结>\n" +
|
||||
"**提取的关键信息** : <这里返回关键信息>\n" +
|
||||
"==示例结束==\n" +
|
||||
"\n" +
|
||||
"注意:请严格按示例进行输出,返回markdown格式").build();
|
||||
messageList.add(sysMessage);
|
||||
Message message = Message.builder().role(Message.Role.USER).content(content).build();
|
||||
messageList.add(message);
|
||||
ChatCompletionResponse chatCompletionResponse = null;
|
||||
try {
|
||||
ChatCompletion chatCompletion = ChatCompletion
|
||||
.builder()
|
||||
.messages(messageList)
|
||||
.model(model)
|
||||
.stream(false)
|
||||
.build();
|
||||
chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||
}catch (Exception e) {
|
||||
throw new BaseException("调用大模型失败,请检查密钥是否正确!");
|
||||
}
|
||||
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();*/
|
||||
return "接口开发中!";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
package org.ruoyi.service.impl;
|
||||
|
||||
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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.domain.vo.KnowledgeInfoVo;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.ruoyi.domain.bo.KnowledgeInfoBo;
|
||||
import org.ruoyi.domain.KnowledgeInfo;
|
||||
import org.ruoyi.mapper.KnowledgeInfoMapper;
|
||||
import org.ruoyi.service.IKnowledgeInfoService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 知识库Service业务层处理
|
||||
*
|
||||
* @author ageerle
|
||||
* @date 2025-04-08
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class KnowledgeInfoServiceImpl implements IKnowledgeInfoService {
|
||||
|
||||
private final KnowledgeInfoMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询知识库
|
||||
*/
|
||||
@Override
|
||||
public KnowledgeInfoVo queryById(Long id){
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询知识库列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<KnowledgeInfoVo> queryPageList(KnowledgeInfoBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
|
||||
Page<KnowledgeInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询知识库列表
|
||||
*/
|
||||
@Override
|
||||
public List<KnowledgeInfoVo> queryList(KnowledgeInfoBo bo) {
|
||||
LambdaQueryWrapper<KnowledgeInfo> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<KnowledgeInfo> buildQueryWrapper(KnowledgeInfoBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<KnowledgeInfo> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeInfo::getKid, bo.getKid());
|
||||
lqw.eq(bo.getUid() != null, KnowledgeInfo::getUid, bo.getUid());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getKname()), KnowledgeInfo::getKname, bo.getKname());
|
||||
lqw.eq(bo.getShare() != null, KnowledgeInfo::getShare, bo.getShare());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getDescription()), KnowledgeInfo::getDescription, bo.getDescription());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getKnowledgeSeparator()), KnowledgeInfo::getKnowledgeSeparator, bo.getKnowledgeSeparator());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getQuestionSeparator()), KnowledgeInfo::getQuestionSeparator, bo.getQuestionSeparator());
|
||||
lqw.eq(bo.getOverlapChar() != null, KnowledgeInfo::getOverlapChar, bo.getOverlapChar());
|
||||
lqw.eq(bo.getRetrieveLimit() != null, KnowledgeInfo::getRetrieveLimit, bo.getRetrieveLimit());
|
||||
lqw.eq(bo.getTextBlockSize() != null, KnowledgeInfo::getTextBlockSize, bo.getTextBlockSize());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getVector()), KnowledgeInfo::getVector, bo.getVector());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getVectorModel()), KnowledgeInfo::getVectorModel, bo.getVectorModel());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增知识库
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(KnowledgeInfoBo bo) {
|
||||
KnowledgeInfo add = MapstructUtils.convert(bo, KnowledgeInfo.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改知识库
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(KnowledgeInfoBo bo) {
|
||||
KnowledgeInfo update = MapstructUtils.convert(bo, KnowledgeInfo.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(KnowledgeInfo entity){
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除知识库
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
@@ -46,4 +46,10 @@ public interface IChatConfigService {
|
||||
* 校验并批量删除配置信息信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
|
||||
/**
|
||||
* 查询系统参数
|
||||
*/
|
||||
List<ChatConfigVo> getSysConfigValue(String category);
|
||||
}
|
||||
|
||||
@@ -129,4 +129,18 @@ public class ChatConfigServiceImpl implements ConfigService, IChatConfigService
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置类型和配置key获取值
|
||||
*
|
||||
* @param category
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<ChatConfigVo> getSysConfigValue(String category) {
|
||||
ChatConfigBo bo = new ChatConfigBo();
|
||||
bo.setCategory(category);
|
||||
LambdaQueryWrapper<ChatConfig> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ChatConfig {
|
||||
return openAiStreamClient;
|
||||
}
|
||||
|
||||
public OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
||||
public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
@@ -31,11 +32,14 @@ import org.ruoyi.common.log.enums.BusinessType;
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/system/chatConfig")
|
||||
@RequestMapping("/chat/config")
|
||||
public class ChatConfigController extends BaseController {
|
||||
|
||||
private final IChatConfigService chatConfigService;
|
||||
|
||||
|
||||
private final ConfigService configService;
|
||||
|
||||
/**
|
||||
* 查询配置信息列表
|
||||
*/
|
||||
@@ -102,4 +106,24 @@ public class ChatConfigController extends BaseController {
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(chatConfigService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数键名查询系统参数值
|
||||
*
|
||||
* @param configKey 参数Key
|
||||
*/
|
||||
@GetMapping(value = "/configKey/{configKey}")
|
||||
public R<String> getConfigKey(@PathVariable String configKey) {
|
||||
return R.ok(configService.getConfigValue("sys",configKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询系统参数
|
||||
*
|
||||
*/
|
||||
@GetMapping(value = "/sysConfigKey")
|
||||
public R<List<ChatConfigVo>> getSysConfigKey() {
|
||||
return R.ok(chatConfigService.getSysConfigValue("sys"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.ruoyi.chat.controller.chat;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.domain.bo.ChatAppStoreBo;
|
||||
import org.ruoyi.domain.vo.ChatAppStoreVo;
|
||||
import org.ruoyi.service.IChatAppStoreService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 应用商店
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2024-03-19
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/system/store")
|
||||
public class ChatStoreController extends BaseController {
|
||||
|
||||
private final IChatAppStoreService appStoreService;
|
||||
|
||||
/**
|
||||
* 应用商店
|
||||
*/
|
||||
@GetMapping("/appList")
|
||||
public R<List<ChatAppStoreVo>> appList(ChatAppStoreBo bo) {
|
||||
return R.ok(appStoreService.queryList(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏应用
|
||||
*/
|
||||
@PostMapping("/copyApp")
|
||||
public R<String> copyApp() {
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.ruoyi.chat.controller.knowledge;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.common.core.domain.R;
|
||||
import org.ruoyi.common.core.validate.AddGroup;
|
||||
import org.ruoyi.common.excel.utils.ExcelUtil;
|
||||
import org.ruoyi.common.log.annotation.Log;
|
||||
import org.ruoyi.common.log.enums.BusinessType;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.common.web.core.BaseController;
|
||||
import org.ruoyi.core.page.PageQuery;
|
||||
import org.ruoyi.core.page.TableDataInfo;
|
||||
import org.ruoyi.domain.bo.KnowledgeAttachBo;
|
||||
import org.ruoyi.domain.bo.KnowledgeFragmentBo;
|
||||
import org.ruoyi.domain.bo.KnowledgeInfoBo;
|
||||
import org.ruoyi.domain.bo.KnowledgeInfoUploadBo;
|
||||
import org.ruoyi.domain.vo.KnowledgeAttachVo;
|
||||
import org.ruoyi.domain.vo.KnowledgeFragmentVo;
|
||||
import org.ruoyi.domain.vo.KnowledgeInfoVo;
|
||||
import org.ruoyi.service.IKnowledgeAttachService;
|
||||
import org.ruoyi.service.IKnowledgeFragmentService;
|
||||
import org.ruoyi.service.IKnowledgeInfoService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/knowledge")
|
||||
public class KnowledgeController extends BaseController {
|
||||
|
||||
private final IKnowledgeInfoService knowledgeInfoService;
|
||||
|
||||
private final IKnowledgeAttachService attachService;
|
||||
|
||||
private final IKnowledgeFragmentService fragmentService;
|
||||
|
||||
/**
|
||||
* 根据用户信息查询本地知识库
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) {
|
||||
if (!StpUtil.isLogin()) {
|
||||
throw new SecurityException("请先去登录!");
|
||||
}
|
||||
bo.setUid(LoginHelper.getUserId());
|
||||
return knowledgeInfoService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增知识库
|
||||
*/
|
||||
@Log(title = "知识库", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/save")
|
||||
public R<Void> save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) {
|
||||
knowledgeInfoService.saveOne(bo);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库
|
||||
*/
|
||||
@PostMapping("/remove/{id}")
|
||||
public R<String> remove(@PathVariable String id) {
|
||||
knowledgeInfoService.removeKnowledge(id);
|
||||
return R.ok("删除知识库成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改知识库
|
||||
*/
|
||||
@Log(title = "知识库", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/edit")
|
||||
public R<Void> edit(@RequestBody KnowledgeInfoBo bo) {
|
||||
return toAjax(knowledgeInfoService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出知识库列表
|
||||
*/
|
||||
@Log(title = "知识库", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(KnowledgeInfoBo bo, HttpServletResponse response) {
|
||||
List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "知识库", KnowledgeInfoVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询知识附件信息
|
||||
*/
|
||||
@GetMapping("/detail/{kid}")
|
||||
public TableDataInfo<KnowledgeAttachVo> attach(KnowledgeAttachBo bo, PageQuery pageQuery, @PathVariable String kid) {
|
||||
bo.setKid(kid);
|
||||
return attachService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传知识库附件
|
||||
*/
|
||||
@PostMapping(value = "/attach/upload")
|
||||
public R<String> upload(KnowledgeInfoUploadBo bo) {
|
||||
knowledgeInfoService.upload(bo);
|
||||
return R.ok("上传知识库附件成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库附件详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@GetMapping("attach/info/{id}")
|
||||
public R<KnowledgeAttachVo> getAttachInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(attachService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库附件
|
||||
*/
|
||||
@PostMapping("attach/remove/{kid}")
|
||||
public R<Void> removeAttach(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable String kid) {
|
||||
attachService.removeKnowledgeAttach(kid);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询知识片段
|
||||
*/
|
||||
@GetMapping("/fragment/list/{docId}")
|
||||
public TableDataInfo<KnowledgeFragmentVo> fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) {
|
||||
bo.setDocId(docId);
|
||||
return fragmentService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件翻译
|
||||
*/
|
||||
@PostMapping("/translationByFile")
|
||||
@ResponseBody
|
||||
public String translationByFile(@RequestParam("file") MultipartFile file, String targetLanguage) {
|
||||
return attachService.translationByFile(file, targetLanguage);
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,4 @@ public interface IChatService {
|
||||
SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter);
|
||||
|
||||
|
||||
/**
|
||||
* 客户端发送消息到服务端
|
||||
* @param chatRequest 请求对象
|
||||
*/
|
||||
void mcpChat(ChatRequest chatRequest,SseEmitter emitter);
|
||||
}
|
||||
|
||||
@@ -55,11 +55,4 @@ public interface ISseService {
|
||||
*/
|
||||
String wxCpChat(String prompt);
|
||||
|
||||
/**
|
||||
* 联网查询
|
||||
*
|
||||
* @param prompt 提示词
|
||||
* @return 查询内容
|
||||
*/
|
||||
String webSearch (String prompt);
|
||||
}
|
||||
|
||||
@@ -54,8 +54,14 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
|
||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||
|
||||
Object userId = LocalCache.CACHE.get("userId");
|
||||
if(userId!=null){
|
||||
chatMessageBo.setUserId((Long) userId);
|
||||
}else {
|
||||
chatMessageBo.setUserId(getUserId());
|
||||
}
|
||||
// 计算总token数
|
||||
ChatToken chatToken = chatTokenService.queryByUserId(getUserId(), modelName);
|
||||
ChatToken chatToken = chatTokenService.queryByUserId(chatMessageBo.getUserId(), modelName);
|
||||
if (chatToken == null) {
|
||||
chatToken = new ChatToken();
|
||||
chatToken.setToken(0);
|
||||
@@ -69,17 +75,17 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
if (token2 > 0) {
|
||||
// 保存剩余tokens
|
||||
chatToken.setModelName(modelName);
|
||||
chatToken.setUserId(getUserId());
|
||||
chatToken.setUserId(chatMessageBo.getUserId());
|
||||
chatToken.setToken(token2);
|
||||
chatTokenService.editToken(chatToken);
|
||||
} else {
|
||||
chatTokenService.resetToken(getUserId(), modelName);
|
||||
chatTokenService.resetToken(chatMessageBo.getUserId(), modelName);
|
||||
}
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||
double cost = chatModelVo.getModelPrice();
|
||||
if (BillingType.TIMES.getCode().equals(chatModelVo.getModelType())) {
|
||||
// 按次数扣费
|
||||
deductUserBalance(getUserId(), cost);
|
||||
deductUserBalance(chatMessageBo.getUserId(), cost);
|
||||
chatMessageBo.setDeductCost(cost);
|
||||
}else {
|
||||
// 按token扣费
|
||||
@@ -89,7 +95,7 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
}
|
||||
chatMessageBo.setContent(chatRequest.getPrompt());
|
||||
} else {
|
||||
deductUserBalance(getUserId(), 0.0);
|
||||
deductUserBalance(chatMessageBo.getUserId(), 0.0);
|
||||
chatMessageBo.setDeductCost(0d);
|
||||
chatMessageBo.setRemark("不满1kToken,计入下一次!");
|
||||
chatToken.setToken(totalTokens);
|
||||
@@ -97,12 +103,6 @@ public class ChatCostServiceImpl implements IChatCostService {
|
||||
chatToken.setUserId(chatMessageBo.getUserId());
|
||||
chatTokenService.editToken(chatToken);
|
||||
}
|
||||
Object userId = LocalCache.CACHE.get("userId");
|
||||
if(userId!=null){
|
||||
chatMessageBo.setUserId((Long) userId);
|
||||
}else {
|
||||
chatMessageBo.setUserId(getUserId());
|
||||
}
|
||||
// 保存消息记录
|
||||
chatMessageService.insertByBo(chatMessageBo);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
import io.modelcontextprotocol.client.McpSyncClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.config.ChatConfig;
|
||||
import org.ruoyi.chat.listener.SSEEventSourceListener;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
import org.springframework.ai.chat.memory.ChatMemory;
|
||||
import org.springframework.ai.chat.memory.InMemoryChatMemory;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -24,55 +24,42 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class OpenAIServiceImpl implements IChatService {
|
||||
|
||||
private final ChatClient chatClient;
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
private final ChatMemory chatMemory = new InMemoryChatMemory();
|
||||
private OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
private final ChatClient chatClient;
|
||||
|
||||
public OpenAIServiceImpl(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
|
||||
public OpenAIServiceImpl(ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpSyncClients) {
|
||||
this.chatClient = chatClientBuilder
|
||||
.defaultTools(tools)
|
||||
.defaultOptions(
|
||||
OpenAiChatOptions.builder()
|
||||
.model("gpt-4o-mini")
|
||||
.temperature(0.4)
|
||||
.build())
|
||||
OpenAiChatOptions.builder().model("gpt-4o-mini").build())
|
||||
.defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey());
|
||||
String toolString = mcpChat(chatRequest.getPrompt());
|
||||
Message userMessage = Message.builder().content("工具返回信息:"+toolString).role(Message.Role.USER).build();
|
||||
List<Message> messages = chatRequest.getMessages();
|
||||
messages.add(userMessage);
|
||||
SSEEventSourceListener listener = new SSEEventSourceListener(emitter);
|
||||
ChatCompletion completion = ChatCompletion
|
||||
.builder()
|
||||
.messages(messages)
|
||||
.model(chatRequest.getModel())
|
||||
.stream(true)
|
||||
.build();
|
||||
openAiStreamClient.streamChatCompletion(completion, listener);
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mcpChat(ChatRequest chatRequest, SseEmitter emitter) {
|
||||
List<Message> msgList = chatRequest.getMessages();
|
||||
// 添加记忆
|
||||
for (int i = 0; i < msgList.size(); i++) {
|
||||
org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString());
|
||||
chatMemory.add(String.valueOf(i), springAiMessage);
|
||||
}
|
||||
var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId().toString(), 10);
|
||||
|
||||
Flux<String> content = chatClient
|
||||
.prompt(chatRequest.getPrompt())
|
||||
.advisors(messageChatMemoryAdvisor)
|
||||
.stream().content();
|
||||
|
||||
content.publishOn(Schedulers.boundedElastic())
|
||||
.doOnNext(text -> {
|
||||
try {
|
||||
emitter.send(text);
|
||||
} catch (IOException e) {
|
||||
emitter.completeWithError(e);
|
||||
}
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.error("Error in SSE stream: ", error);
|
||||
emitter.completeWithError(error);
|
||||
})
|
||||
.doOnComplete(emitter::complete)
|
||||
.subscribe();
|
||||
public String mcpChat(String prompt){
|
||||
return this.chatClient.prompt(prompt).call().content();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,9 +71,9 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
@Override
|
||||
public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) {
|
||||
SseEmitter sseEmitter = new SseEmitter();
|
||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||
try {
|
||||
// 构建消息列表增加联网、知识库等内容
|
||||
// 构建消息列表
|
||||
buildChatMessageList(chatRequest);
|
||||
if (!StpUtil.isLogin()) {
|
||||
// 未登录用户限制对话次数
|
||||
@@ -144,7 +144,9 @@ public class SseServiceImpl implements ISseService {
|
||||
String sysPrompt = chatModelVo.getSystemPrompt();
|
||||
if(StringUtils.isEmpty(sysPrompt)){
|
||||
sysPrompt ="你是一个由RuoYI-AI开发的人工智能助手,名字叫熊猫助手。你擅长中英文对话,能够理解并处理各种问题,提供安全、有帮助、准确的回答。" +
|
||||
"当前时间:"+ DateUtils.getDate();
|
||||
"当前时间:"+ DateUtils.getDate()+
|
||||
"#注意:回复之前注意结合上下文和工具返回内容。";
|
||||
|
||||
}
|
||||
// 设置系统默认提示词
|
||||
Message sysMessage = Message.builder().content(sysPrompt).role(Message.Role.SYSTEM).build();
|
||||
|
||||