初始化代码

This commit is contained in:
lpd
2026-01-20 11:37:55 +08:00
parent 423975df96
commit 7994712f20
2880 changed files with 0 additions and 206191 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -1,263 +0,0 @@
<?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/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>lyzsys-module-ai</artifactId>
<name>${project.artifactId}</name>
<description>
ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维导图等功能。
目前已接入各种模型,不限于:
国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek
国外OpenAI、Ollama、Midjourney、StableDiffusion、Suno
</description>
<properties>
<spring-ai.version>1.1.2</spring-ai.version>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
<alibaba-ai.version>1.1.0.0-RC2</alibaba-ai.version>
<tinyflow.version>1.2.6</tinyflow.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-module-system</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-module-infra</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Job 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-job</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-test</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-excel</artifactId>
</dependency>
<!-- Spring AI Model 模型接入 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-azure-openai</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-stability-ai</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- 智谱 GLM -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-minimax</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- 通义千问 -->
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${alibaba-ai.version}</version>
</dependency>
<dependency>
<!-- 文心一言 -->
<groupId>org.springaicommunity</groupId>
<artifactId>qianfan-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<!-- 月之暗面 -->
<groupId>org.springaicommunity</groupId>
<artifactId>moonshot-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 向量存储https://db-engines.com/en/ranking/vector+dbms -->
<dependency>
<!-- Qdranthttps://qdrant.tech/ -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<!-- Redishttps://redis.io/docs/latest/develop/get-started/vector-database/ -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>lyzsys-spring-boot-starter-redis</artifactId>
</dependency>
<dependency>
<!-- Milvushttps://milvus.io/ -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<!-- 解决和 logback 的日志冲突 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- Tika负责内容的解析 -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
<version>${spring-ai.version}</version>
<!-- TODO 芋艿boot 项目里,不引入 cloud 依赖!!!另外,这样也是为了解决启动报错的问题! -->
<exclusions>
<exclusion>
<artifactId>spring-cloud-function-context</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
<exclusion>
<artifactId>spring-cloud-function-core</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- MCP 相关 -->
<!--
特殊说明:不能使用 spring-ai-starter-mcp-server-webflux 或 spring-ai-starter-mcp-client-webflux
原因:项目使用了 SpringMVC而不是 WebFlux。引入上述 2 个,会导致 SSE Server 失效。
-->
<dependency>
<!-- 服务端 -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>${spring-ai.version}</version>
<exclusions>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- 客户端 -->
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- TinyFlowAI 工作流 -->
<dependency>
<groupId>dev.tinyflow</groupId>
<artifactId>tinyflow-java-core</artifactId>
<version>${tinyflow.version}</version>
<exclusions>
<exclusion>
<groupId>com.jfinal</groupId>
<artifactId>enjoy</artifactId>
</exclusion>
<exclusion>
<!-- 解决 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1318/ 问题 -->
<groupId>com.agentsflex</groupId>
<artifactId>agents-flex-store-elasticsearch</artifactId>
</exclusion>
<exclusion>
<!-- 解决 https://t.zsxq.com/pCBZC 问题 -->
<groupId>com.agentsflex</groupId>
<artifactId>agents-flex-search-engine-es</artifactId>
</exclusion>
<exclusion>
<!-- TODO @芋艿:暂时移除 groovy和 iot 冲突 -->
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
<!-- 解决和 logback 的日志冲突 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -1,118 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.chat.AiChatConversationDO;
import cn.iocoder.lyzsys.module.ai.service.chat.AiChatConversationService;
import cn.iocoder.lyzsys.module.ai.service.chat.AiChatMessageService;
import com.fhs.core.trans.anno.TransMethodResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
import static cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.lyzsys.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - AI 聊天对话")
@RestController
@RequestMapping("/ai/chat/conversation")
@Validated
public class AiChatConversationController {
@Resource
private AiChatConversationService chatConversationService;
@Resource
private AiChatMessageService chatMessageService;
@PostMapping("/create-my")
@Operation(summary = "创建【我的】聊天对话")
public CommonResult<Long> createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) {
return success(chatConversationService.createChatConversationMy(createReqVO, getLoginUserId()));
}
@PutMapping("/update-my")
@Operation(summary = "更新【我的】聊天对话")
public CommonResult<Boolean> updateChatConversationMy(@RequestBody @Valid AiChatConversationUpdateMyReqVO updateReqVO) {
chatConversationService.updateChatConversationMy(updateReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/my-list")
@Operation(summary = "获得【我的】聊天对话列表")
@TransMethodResult
public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() {
List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
}
@GetMapping("/get-my")
@Operation(summary = "获得【我的】聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
@TransMethodResult
public CommonResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
conversation = null;
}
return success(BeanUtils.toBean(conversation, AiChatConversationRespVO.class));
}
@DeleteMapping("/delete-my")
@Operation(summary = "删除聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
public CommonResult<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
chatConversationService.deleteChatConversationMy(id, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete-by-unpinned")
@Operation(summary = "删除未置顶的聊天对话")
public CommonResult<Boolean> deleteChatConversationMyByUnpinned() {
chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId());
return success(true);
}
// ========== 对话管理 ==========
@GetMapping("/page")
@Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
@TransMethodResult
public CommonResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 拼接关联数据
Map<Long, Integer> messageCountMap = chatMessageService.getChatMessageCountMap(
convertList(pageResult.getList(), AiChatConversationDO::getId));
return success(BeanUtils.toBean(pageResult, AiChatConversationRespVO.class,
conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0))));
}
@Operation(summary = "管理员删除对话")
@DeleteMapping("/delete-by-admin")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:delete')")
public CommonResult<Boolean> deleteChatConversationByAdmin(@RequestParam("id") Long id) {
chatConversationService.deleteChatConversationByAdmin(id);
return success(true);
}
}

View File

@@ -1,66 +0,0 @@
### 发送消息(段式)
POST {{baseUrl}}/ai/chat/message/send
Content-Type: application/json
Authorization: {{token}}
tenant-id: {{adminTenantId}}
{
"conversationId": "1781604279872581724",
"content": "你是 OpenAI 么?"
}
### 发送消息(流式)
POST {{baseUrl}}/ai/chat/message/send-stream
Content-Type: application/json
Authorization: {{token}}
tenant-id: {{adminTenantId}}
{
"conversationId": "1781604279872581724",
"content": "1+1=?"
}
### 发送消息(流式)【带文件】
POST {{baseUrl}}/ai/chat/message/send-stream
Content-Type: application/json
Authorization: {{token}}
tenant-id: {{adminTenantId}}
{
"conversationId": "1781604279872581797",
"content": "图片里有什么?",
"attachmentUrls": ["http://test.lyzsys.iocoder.cn/1755531278.jpeg"]
}
### 发送消息(流式)【追问带文件】
POST {{baseUrl}}/ai/chat/message/send-stream
Content-Type: application/json
Authorization: {{token}}
tenant-id: {{adminTenantId}}
{
"conversationId": "1781604279872581799",
"content": "说下图片里,有哪些字?",
"useContext": true
}
### 发送消息(流式)【联网搜索】
POST {{baseUrl}}/ai/chat/message/send-stream
Content-Type: application/json
Authorization: {{token}}
tenant-id: {{adminTenantId}}
{
"conversationId": "1781604279872581799",
"content": "今天是周几?",
"useSearch": true
}
### 获得指定对话的消息列表
GET {{baseUrl}}/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581799
Authorization: {{token}}
tenant-id: {{adminTenantId}}
### 删除消息
DELETE {{baseUrl}}/ai/chat/message/delete?id=50
Authorization: {{token}}

View File

@@ -1,157 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.lyzsys.framework.common.pojo.CommonResult;
import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.collection.MapUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
import cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.chat.AiChatConversationDO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.chat.AiChatMessageDO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.model.AiChatRoleDO;
import cn.iocoder.lyzsys.module.ai.service.chat.AiChatConversationService;
import cn.iocoder.lyzsys.module.ai.service.chat.AiChatMessageService;
import cn.iocoder.lyzsys.module.ai.service.knowledge.AiKnowledgeDocumentService;
import cn.iocoder.lyzsys.module.ai.service.knowledge.AiKnowledgeSegmentService;
import cn.iocoder.lyzsys.module.ai.service.model.AiChatRoleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.lyzsys.framework.common.pojo.CommonResult.success;
import static cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.lyzsys.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 聊天消息")
@RestController
@RequestMapping("/ai/chat/message")
@Slf4j
public class AiChatMessageController {
@Resource
private AiChatMessageService chatMessageService;
@Resource
private AiChatConversationService chatConversationService;
@Resource
private AiChatRoleService chatRoleService;
@Resource
private AiKnowledgeSegmentService knowledgeSegmentService;
@Resource
private AiKnowledgeDocumentService knowledgeDocumentService;
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
@PostMapping("/send")
public CommonResult<AiChatMessageSendRespVO> sendMessage(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
return success(chatMessageService.sendMessage(sendReqVO, getLoginUserId()));
}
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) {
return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
}
@Operation(summary = "获得指定对话的消息列表")
@GetMapping("/list-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
public CommonResult<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
@RequestParam("conversationId") Long conversationId) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
return success(Collections.emptyList());
}
// 1. 获取消息列表
List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
if (CollUtil.isEmpty(messageList)) {
return success(Collections.emptyList());
}
// 2. 拼接数据,主要是知识库段落信息
Map<Long, AiKnowledgeSegmentDO> segmentMap = knowledgeSegmentService.getKnowledgeSegmentMap(convertListByFlatMap(messageList,
message -> CollUtil.isEmpty(message.getSegmentIds()) ? null : message.getSegmentIds().stream()));
Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap(
convertList(segmentMap.values(), AiKnowledgeSegmentDO::getDocumentId));
List<AiChatMessageRespVO> messageVOList = BeanUtils.toBean(messageList, AiChatMessageRespVO.class);
for (int i = 0; i < messageList.size(); i++) {
AiChatMessageDO message = messageList.get(i);
if (CollUtil.isEmpty(message.getSegmentIds())) {
continue;
}
// 设置知识库段落信息
messageVOList.get(i).setSegments(convertList(message.getSegmentIds(), segmentId -> {
AiKnowledgeSegmentDO segment = segmentMap.get(segmentId);
if (segment == null) {
return null;
}
AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId());
if (document == null) {
return null;
}
return new AiChatMessageRespVO.KnowledgeSegment().setId(segment.getId()).setContent(segment.getContent())
.setDocumentId(segment.getDocumentId()).setDocumentName(document.getName());
}));
}
return success(messageVOList);
}
@Operation(summary = "删除消息")
@DeleteMapping("/delete")
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
public CommonResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
chatMessageService.deleteChatMessage(id, getLoginUserId());
return success(true);
}
@Operation(summary = "删除指定对话的消息")
@DeleteMapping("/delete-by-conversation-id")
@Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
return success(true);
}
// ========== 对话管理 ==========
@GetMapping("/page")
@Operation(summary = "获得消息分页", description = "用于【对话管理】菜单")
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) {
PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 拼接数据
Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(
convertSet(pageResult.getList(), AiChatMessageDO::getRoleId));
return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class,
respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(),
role -> respVO.setRoleName(role.getName()))));
}
@Operation(summary = "管理员删除消息")
@DeleteMapping("/delete-by-admin")
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
@PreAuthorize("@ss.hasPermission('ai:chat-message:delete')")
public CommonResult<Boolean> deleteChatMessageByAdmin(@RequestParam("id") Long id) {
chatMessageService.deleteChatMessageByAdmin(id);
return success(true);
}
}

View File

@@ -1,16 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - AI 聊天对话创建【我的】 Request VO")
@Data
public class AiChatConversationCreateMyReqVO {
@Schema(description = "聊天角色编号", example = "666")
private Long roleId;
@Schema(description = "知识库编号", example = "1204")
private Long knowledgeId;
}

View File

@@ -1,26 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation;
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - AI 聊天对话的分页 Request VO")
@Data
public class AiChatConversationPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "对话标题", example = "你好")
private String title;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -1,71 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.model.AiModelDO;
import cn.iocoder.lyzsys.module.ai.dal.dataobject.model.AiChatRoleDO;
import com.fhs.core.trans.anno.Trans;
import com.fhs.core.trans.constant.TransType;
import com.fhs.core.trans.vo.VO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - AI 聊天对话 Response VO")
@Data
public class AiChatConversationRespVO implements VO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long userId;
@Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
private String title;
@Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean pinned;
@Schema(description = "角色编号", example = "1")
@Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"})
private Long roleId;
@Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Trans(type = TransType.SIMPLE, target = AiModelDO.class, fields = "name", ref = "modelName")
private Long modelId;
@Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922")
private String model;
@Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
private String modelName;
@Schema(description = "角色设定", example = "一个快乐的程序员")
private String systemMessage;
@Schema(description = "温度参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
private Double temperature;
@Schema(description = "单条回复的最大 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
private Integer maxTokens;
@Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer maxContexts;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
// ========== 关联 role 信息 ==========
@Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png")
private String roleAvatar;
@Schema(description = "角色名字", example = "小黄")
private String roleName;
// ========== 仅在【对话管理】时加载 ==========
@Schema(description = "消息数量", example = "20")
private Integer messageCount;
}

View File

@@ -1,39 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.conversation;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - AI 聊天对话更新【我的】 Request VO")
@Data
public class AiChatConversationUpdateMyReqVO {
@Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "对话编号不能为空")
private Long id;
@Schema(description = "对话标题", example = "我是一个标题")
private String title;
@Schema(description = "是否置顶", example = "true")
private Boolean pinned;
@Schema(description = "模型编号", example = "1")
private Long modelId;
@Schema(description = "知识库编号", example = "1")
private Long knowledgeId;
@Schema(description = "角色设定", example = "一个快乐的程序员")
private String systemMessage;
@Schema(description = "温度参数", example = "0.8")
private Double temperature;
@Schema(description = "单条回复的最大 Token 数量", example = "4096")
private Integer maxTokens;
@Schema(description = "上下文的最大 Message 数量", example = "10")
private Integer maxContexts;
}

View File

@@ -1,29 +0,0 @@
package cn.iocoder.lyzsys.module.ai.controller.admin.chat.vo.message;
import cn.iocoder.lyzsys.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.lyzsys.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - AI 聊天消息的分页 Request VO")
@Data
public class AiChatMessagePageReqVO extends PageParam {
@Schema(description = "对话编号", example = "2048")
private Long conversationId;
@Schema(description = "用户编号", example = "1024")
private Long userId;
@Schema(description = "消息内容", example = "你好")
private String content;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

Some files were not shown because too many files have changed in this diff Show More