From bb94a6b9a9d4090d6b2a55bd5620808bae734aeb Mon Sep 17 00:00:00 2001
From: ct <164434275@qq.com>
Date: Fri, 15 May 2026 09:36:01 +0800
Subject: [PATCH 1/8] =?UTF-8?q?=E6=9A=82=E5=AD=98=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 19 ++
ruoyi-admin/pom.xml | 4 +
.../src/main/resources/application-dev.yml | 45 +++-
.../src/main/resources/application.yml | 2 +-
ruoyi-common/pom.xml | 1 +
ruoyi-common/ruoyi-common-ai/pom.xml | 41 +++
.../common/ai/config/SnailAiConfig.java | 16 ++
...ot.autoconfigure.AutoConfiguration.imports | 1 +
ruoyi-common/ruoyi-common-bom/pom.xml | 7 +
ruoyi-modules/pom.xml | 1 +
ruoyi-modules/ruoyi-ai/pom.xml | 31 +++
.../ai/controller/OpenApiDemoController.java | 251 ++++++++++++++++++
.../java/org/dromara/ai/package-info.java | 1 +
13 files changed, 413 insertions(+), 7 deletions(-)
create mode 100644 ruoyi-common/ruoyi-common-ai/pom.xml
create mode 100644 ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java
create mode 100644 ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
create mode 100644 ruoyi-modules/ruoyi-ai/pom.xml
create mode 100644 ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java
create mode 100644 ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java
diff --git a/pom.xml b/pom.xml
index 02a984b55..81d638836 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,6 +33,7 @@
2.2.7
4.5.0
1.9.0
+ 0.0.1
1.5.0
0.2.0
1.18.42
@@ -278,6 +279,18 @@
${snailjob.version}
+
+ com.aizuda
+ snail-ai-agent-starter
+ ${snailai.version}
+
+
+
+ com.aizuda
+ snail-ai-openapi-starter
+ ${snailai.version}
+
+
org.bouncycastle
@@ -341,6 +354,12 @@
${revision}
+
+ org.dromara
+ ruoyi-ai
+ ${revision}
+
+
org.dromara
ruoyi-gen
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 80ef4b3d5..e29d4a656 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -75,6 +75,10 @@
ruoyi-job
+
+ org.dromara
+ ruoyi-ai
+
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 1b52fab96..f7a9a684e 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -13,7 +13,7 @@ spring.boot.admin.client:
--- # snail-job 配置
snail-job:
- enabled: true
+ enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
@@ -28,6 +28,39 @@ snail-job:
# 客户端ip指定
host:
+--- # snail-ai 配置
+snail-ai:
+ # 启用客户端模式
+ enabled: true
+ # ==================== Server 连接 ====================
+ # Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
+ server:
+ host: 127.0.0.1
+ port: 18888
+ # ==================== 客户端配置 ====================
+ # 本客户端 gRPC 端口(Server 通过此端口分发 Chat 请求)
+ # 应用 ID(在 Server「应用管理」页面创建后获取)
+ app-id: 1
+ # 认证令牌(在 Server「应用管理」页面创建时自动生成)
+ token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b
+ port: 18889
+ # Skill 文件临时目录
+ skill-temp-dir: /tmp/snail-ai-agent/skills
+ # ==================== OpenAPI Client 配置 ====================
+ open-api:
+ # 启用 OpenAPI Client
+ enabled: true
+ # Server HTTP 端口
+ web-port: 18080
+ # 是否使用 HTTPS
+ https: false
+ # API 路径前缀
+ prefix: snail-ai
+ # 超时配置(毫秒)
+ connect-timeout-ms: 5000
+ read-timeout-ms: 60000
+ chat-timeout-ms: 300000
+
--- # 数据源配置
spring:
datasource:
@@ -47,9 +80,9 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
- url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+ url: jdbc:mysql://8.136.96.136:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
- password: root
+ password: mysql_5NrZmE
# # 从库数据源
# slave:
# lazy: true
@@ -96,13 +129,13 @@ spring:
spring.data:
redis:
# 地址
- host: localhost
+ host: 8.136.96.136
# 端口,默认为6379
port: 6379
# 数据库索引
- database: 0
+ database: 11
# redis 密码必须配置
- password: ruoyi123
+ password: redis_JsjKmr
# 连接超时时间
timeout: 10s
# 是否开启ssl
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index b8848f036..324f4ab19 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -224,7 +224,7 @@ message:
--- # warm-flow工作流配置
warm-flow:
# 是否开启工作流,默认true
- enabled: true
+ enabled: false
# 是否开启设计器ui
ui: true
# 是否显示流程图顶部文字
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index f6a30c47f..c88e1fd11 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -37,6 +37,7 @@
ruoyi-common-encrypt
ruoyi-common-push
ruoyi-common-mqtt
+ ruoyi-common-ai
diff --git a/ruoyi-common/ruoyi-common-ai/pom.xml b/ruoyi-common/ruoyi-common-ai/pom.xml
new file mode 100644
index 000000000..f1eb0712b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ai/pom.xml
@@ -0,0 +1,41 @@
+
+
+
+ ruoyi-common
+ org.dromara
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-ai
+
+
+ ruoyi-common-ai AI公共模块
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ com.aizuda
+ snail-ai-agent-starter
+
+
+
+ com.aizuda
+ snail-ai-openapi-starter
+
+
+
+ org.dromara
+ ruoyi-common-core
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java b/ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java
new file mode 100644
index 000000000..5ade5cbdd
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ai/src/main/java/org/dromara/common/ai/config/SnailAiConfig.java
@@ -0,0 +1,16 @@
+package org.dromara.common.ai.config;
+
+import com.aizuda.snail.ai.agent.starter.EnableSnailAiAgent;
+import com.aizuda.snail.ai.openapi.client.starter.EnableSnailAiOpenApi;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ * Snail AI 自动配置
+ */
+@AutoConfiguration
+@ConditionalOnProperty(prefix = "snail-ai", name = "enabled", havingValue = "true")
+@EnableSnailAiAgent
+@EnableSnailAiOpenApi
+public class SnailAiConfig {
+}
diff --git a/ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..32af79519
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.ai.config.SnailAiConfig
diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml
index b60c35f40..0526082c4 100644
--- a/ruoyi-common/ruoyi-common-bom/pom.xml
+++ b/ruoyi-common/ruoyi-common-bom/pom.xml
@@ -159,6 +159,13 @@
${revision}
+
+
+ org.dromara
+ ruoyi-common-ai
+ ${revision}
+
+
diff --git a/ruoyi-modules/pom.xml b/ruoyi-modules/pom.xml
index 971929a54..4a79d2500 100644
--- a/ruoyi-modules/pom.xml
+++ b/ruoyi-modules/pom.xml
@@ -21,6 +21,7 @@
ruoyi-job
ruoyi-system
ruoyi-workflow
+ ruoyi-ai
diff --git a/ruoyi-modules/ruoyi-ai/pom.xml b/ruoyi-modules/ruoyi-ai/pom.xml
new file mode 100644
index 000000000..1baf5cb8a
--- /dev/null
+++ b/ruoyi-modules/ruoyi-ai/pom.xml
@@ -0,0 +1,31 @@
+
+
+
+ org.dromara
+ ruoyi-modules
+ ${revision}
+
+ 4.0.0
+ jar
+ ruoyi-ai
+
+
+ ai模块
+
+
+
+
+
+ org.dromara
+ ruoyi-common-ai
+
+
+ org.dromara
+ ruoyi-common-satoken
+
+
+
+
+
diff --git a/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java
new file mode 100644
index 000000000..5b62e5ed5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java
@@ -0,0 +1,251 @@
+package org.dromara.ai.controller;
+
+import com.aizuda.snail.ai.common.execption.SnailAiException;
+import com.aizuda.snail.ai.common.model.PageResult;
+import com.aizuda.snail.ai.common.model.Result;
+import com.aizuda.snail.ai.common.openapi.dto.*;
+import com.aizuda.snail.ai.openapi.client.core.api.OpenApiAgentClient;
+import com.aizuda.snail.ai.openapi.client.core.api.OpenApiChatClient;
+import com.aizuda.snail.ai.openapi.client.core.api.OpenApiConversationClient;
+import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient;
+import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * OpenAPI 使用示例 Controller
+ *
+ * 演示如何使用 OpenAPI Client 调用 Snail AI 服务端接口
+ *
+ * @author opensnail
+ * @date 2026-04-25
+ */
+@Slf4j
+@RestController
+@RequestMapping("/snail-ai")
+@RequiredArgsConstructor
+@Tag(name = "OpenAPI Demo", description = "OpenAPI 客户端使用示例")
+public class OpenApiDemoController {
+
+ private final OpenApiAgentClient agentClient;
+ private final OpenApiChatClient chatClient;
+ private final OpenApiConversationClient conversationClient;
+ private final OpenApiUserClient userClient;
+
+ // ==================== User 相关接口 ====================
+
+ @PostMapping("/user/register")
+ @Operation(summary = "注册当前登录用户", description = "使用当前系统登录用户自动注册,返回 openId")
+ public Result registerCurrentUser() {
+ OpenApiUserVO user = ensureOpenApiUser();
+ return Result.ok(user);
+ }
+
+ @GetMapping("/user")
+ @Operation(summary = "获取当前登录用户的 OpenAPI 信息", description = "自动解析当前用户 openId 并查询详情")
+ public Result getUser() {
+ String openId = ensureOpenId();
+ OpenApiUserQueryRequest request = new OpenApiUserQueryRequest();
+ request.setOpenId(openId);
+ return userClient.getUser(request);
+ }
+
+ // ==================== Agent 相关接口 ====================
+
+ @GetMapping("/agents")
+ @Operation(summary = "获取所有 Agent 列表", description = "查询当前用户可访问的所有智能体")
+ public Result> listAgents() {
+ return agentClient.listAgents();
+ }
+
+ @GetMapping("/agent/{agentId}")
+ @Operation(summary = "获取 Agent 详情", description = "根据 ID 查询智能体详细信息")
+ public Result getAgent(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId) {
+ OpenApiAgentIdentityRequest request = new OpenApiAgentIdentityRequest();
+ request.setAgentId(agentId);
+ return agentClient.getAgent(request);
+ }
+
+ // ==================== Conversation 相关接口 ====================
+
+ @PostMapping("/agent/{agentId}/conversation")
+ @Operation(summary = "创建会话", description = "为指定 Agent 创建一个新的对话会话")
+ public Result createConversation(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId,
+ @Parameter(description = "创建会话请求")
+ @RequestBody OpenApiCreateConversationRequest request) {
+ request.setAgentId(agentId);
+ request.setOpenId(ensureOpenId());
+ return conversationClient.createConversation(request);
+ }
+
+ @GetMapping("/agent/{agentId}/conversations")
+ @Operation(summary = "获取会话列表", description = "查询指定 Agent 的所有会话(分页)")
+ public PageResult> listConversations(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId,
+ @Parameter(description = "页码", example = "1")
+ @RequestParam(defaultValue = "1") int page,
+ @Parameter(description = "每页数量", example = "10")
+ @RequestParam(defaultValue = "10") int size) {
+ OpenApiConversationQueryRequest request = new OpenApiConversationQueryRequest();
+ request.setAgentId(agentId);
+ request.setOpenId(ensureOpenId());
+ request.setPage(page);
+ request.setSize(size);
+ return conversationClient.listConversations(request);
+ }
+
+ @GetMapping("/agent/{agentId}/conversation/{conversationId}/messages")
+ @Operation(summary = "获取会话消息", description = "查询指定会话的所有消息记录")
+ public Result> getMessages(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId,
+ @Parameter(description = "会话 ID", required = true, example = "conv-123")
+ @PathVariable String conversationId) {
+ OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
+ request.setAgentId(agentId);
+ request.setConversationId(conversationId);
+ request.setOpenId(ensureOpenId());
+ return conversationClient.getMessages(request);
+ }
+
+ @DeleteMapping("/agent/{agentId}/conversation/{conversationId}")
+ @Operation(summary = "删除会话", description = "删除指定的对话会话")
+ public Result deleteConversation(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId,
+ @Parameter(description = "会话 ID", required = true, example = "conv-123")
+ @PathVariable String conversationId) {
+ OpenApiConversationIdentityRequest request = new OpenApiConversationIdentityRequest();
+ request.setAgentId(agentId);
+ request.setConversationId(conversationId);
+ request.setOpenId(ensureOpenId());
+ return conversationClient.deleteConversation(request);
+ }
+
+ // ==================== Chat 相关接口 ====================
+
+ @PostMapping("/agent/{agentId}/chat/sync")
+ @Operation(summary = "同步对话", description = "发送消息并等待 AI 回复(非流式)")
+ public Result chatSync(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId,
+ @Parameter(description = "对话请求")
+ @RequestBody OpenApiChatRequest request) {
+ request.setAgentId(agentId);
+ request.setOpenId(ensureOpenId());
+ log.info("Sync chat request: agentId={}, content={}", agentId, request.getContent());
+ return chatClient.chatSync(request);
+ }
+
+ @GetMapping("/agent/{agentId}/chat/stream")
+ @Operation(summary = "流式对话", description = "发送消息并以 SSE 流式接收 AI 回复")
+ public SseEmitter chatStream(
+ @Parameter(description = "Agent ID", required = true, example = "1")
+ @PathVariable Long agentId,
+ @Parameter(description = "用户消息", required = true, example = "你好")
+ @RequestParam String content,
+ @Parameter(description = "会话 ID(可选)", example = "conv-123")
+ @RequestParam(required = false) String conversationId) {
+
+ SseEmitter emitter = new SseEmitter(300000L); // 5 分钟超时
+
+ OpenApiChatRequest request = new OpenApiChatRequest();
+ request.setAgentId(agentId);
+ request.setOpenId(ensureOpenId());
+ request.setContent(content);
+ request.setConversationId(conversationId);
+
+ log.info("Stream chat request: agentId={}, content={}", agentId, content);
+ try {
+ chatClient.chatStream(request, new SseEventListener() {
+ @Override
+ public void onText(String text) {
+ try {
+ emitter.send(SseEmitter.event()
+ .name("text")
+ .data(text));
+ } catch (IOException e) {
+ log.error("Failed to send SSE text", e);
+ emitter.completeWithError(e);
+ }
+ }
+
+ @Override
+ public void onThinking(String thinking) {
+ try {
+ emitter.send(SseEmitter.event()
+ .name("thinking")
+ .data(thinking));
+ } catch (IOException e) {
+ log.error("Failed to send SSE thinking", e);
+ }
+ }
+
+ @Override
+ public void onComplete(String data) {
+ try {
+ emitter.send(SseEmitter.event()
+ .name("done")
+ .data(data));
+ emitter.complete();
+ log.info("Stream chat completed");
+ } catch (IOException e) {
+ log.error("Failed to send SSE completion", e);
+ emitter.completeWithError(e);
+ }
+ }
+
+ @Override
+ public void onError(String errorMessage) {
+ log.error("Stream chat error: {}", errorMessage);
+ try {
+ emitter.send(SseEmitter.event()
+ .name("error")
+ .data(errorMessage));
+ } catch (IOException e) {
+ log.error("Failed to send SSE error", e);
+ }
+ emitter.completeWithError(new SnailAiException(errorMessage));
+ }
+ });
+ } catch (Exception e) {
+ log.error("Stream chat exception", e);
+ emitter.completeWithError(e);
+ }
+
+ return emitter;
+ }
+
+ private String ensureOpenId() {
+ return ensureOpenApiUser().getOpenId();
+ }
+
+ private OpenApiUserVO ensureOpenApiUser() {
+ Long userId = LoginHelper.getUserId();
+ String username = LoginHelper.getLoginUser().getNickname();
+
+ OpenApiUserRegisterRequest registerRequest = new OpenApiUserRegisterRequest();
+ registerRequest.setExternalId(String.valueOf(userId));
+ registerRequest.setNickname(username);
+ Result registerResult = userClient.register(registerRequest);
+ if (registerResult == null || registerResult.getData() == null) {
+ throw new SnailAiException("注册 OpenAPI 用户失败,返回为空");
+ }
+ OpenApiUserVO user = registerResult.getData();
+ return user;
+ }
+}
diff --git a/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java
new file mode 100644
index 000000000..d97a8cbe5
--- /dev/null
+++ b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/package-info.java
@@ -0,0 +1 @@
+package org.dromara.ai;
From 4eacdc54ca09f121571bb8d56a74ff61183def88 Mon Sep 17 00:00:00 2001
From: ct <164434275@qq.com>
Date: Mon, 18 May 2026 16:53:21 +0800
Subject: [PATCH 2/8] =?UTF-8?q?snail-ai=E6=B5=8B=E8=AF=95=E7=89=88?=
=?UTF-8?q?=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/resources/application-dev.yml | 2 +
.../src/main/resources/application-prod.yml | 35 ++++++++
.../ai/controller/OpenApiDemoController.java | 88 ++++++++++++++-----
3 files changed, 102 insertions(+), 23 deletions(-)
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index f7a9a684e..e9a59c6af 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -32,6 +32,8 @@ snail-job:
snail-ai:
# 启用客户端模式
enabled: true
+ # 聊天发送模式: stream(流式) / sync(同步)
+ chat-mode: stream
# ==================== Server 连接 ====================
# Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
server:
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index d77ddf57c..4a78dcb3b 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -31,6 +31,41 @@ snail-job:
# 客户端ip指定
host:
+--- # snail-ai 配置
+snail-ai:
+ # 启用客户端模式
+ enabled: true
+ # 聊天发送模式: stream(流式) / sync(同步)
+ chat-mode: stream
+ # ==================== Server 连接 ====================
+ # Server 端 gRPC 地址(即 snail-ai-starter 的 snail-ai.server.grpc-port)
+ server:
+ host: 127.0.0.1
+ port: 18888
+ # ==================== 客户端配置 ====================
+ # 本客户端 gRPC 端口(Server 通过此端口分发 Chat 请求)
+ # 应用 ID(在 Server「应用管理」页面创建后获取)
+ app-id: 1
+ # 认证令牌(在 Server「应用管理」页面创建时自动生成)
+ token: SAI_ce6fbc820c50456baecc7cdcf2a14b1b
+ port: 18889
+ # Skill 文件临时目录
+ skill-temp-dir: /tmp/snail-ai-agent/skills
+ # ==================== OpenAPI Client 配置 ====================
+ open-api:
+ # 启用 OpenAPI Client
+ enabled: true
+ # Server HTTP 端口
+ web-port: 18080
+ # 是否使用 HTTPS
+ https: false
+ # API 路径前缀
+ prefix: snail-ai
+ # 超时配置(毫秒)
+ connect-timeout-ms: 5000
+ read-timeout-ms: 60000
+ chat-timeout-ms: 300000
+
--- # 数据源配置
spring:
datasource:
diff --git a/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java
index 5b62e5ed5..f429ea797 100644
--- a/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java
+++ b/ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/OpenApiDemoController.java
@@ -12,14 +12,20 @@ import com.aizuda.snail.ai.openapi.client.core.listener.SseEventListener;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.satoken.utils.LoginHelper;
+import org.springframework.http.MediaType;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
-import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* OpenAPI 使用示例 Controller
@@ -40,6 +46,8 @@ public class OpenApiDemoController {
private final OpenApiChatClient chatClient;
private final OpenApiConversationClient conversationClient;
private final OpenApiUserClient userClient;
+ @Value("${snail-ai.chat-mode:stream}")
+ private String chatMode;
// ==================== User 相关接口 ====================
@@ -138,6 +146,13 @@ public class OpenApiDemoController {
// ==================== Chat 相关接口 ====================
+ @GetMapping("/chat/mode")
+ @Operation(summary = "获取聊天发送模式", description = "返回 stream(流式) 或 sync(同步)")
+ public Result