diff --git a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml index 7e7279eb84..7d387eb8e6 100644 --- a/yudao-framework/yudao-spring-boot-starter-protection/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-protection/pom.xml @@ -35,13 +35,6 @@ lock4j-redisson-spring-boot-starter true - - - - cn.iocoder.boot - yudao-spring-boot-starter-test - test - diff --git a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java b/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java deleted file mode 100644 index c9a3dfff40..0000000000 --- a/yudao-framework/yudao-spring-boot-starter-protection/src/test/java/cn/iocoder/yudao/framework/signature/core/ApiSignatureTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.framework.signature.core; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.crypto.digest.DigestUtil; -import cn.iocoder.yudao.framework.signature.core.annotation.ApiSignature; -import cn.iocoder.yudao.framework.signature.core.aop.ApiSignatureAspect; -import cn.iocoder.yudao.framework.signature.core.redis.ApiSignatureRedisDAO; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -/** - * {@link ApiSignatureTest} 的单元测试 - */ -@ExtendWith(MockitoExtension.class) -public class ApiSignatureTest { - - @InjectMocks - private ApiSignatureAspect apiSignatureAspect; - - @Mock - private ApiSignatureRedisDAO signatureRedisDAO; - - @Test - public void testSignatureGet() throws IOException { - // 搞一个签名 - Long timestamp = System.currentTimeMillis(); - String nonce = IdUtil.randomUUID(); - String appId = "xxxxxx"; - String appSecret = "yyyyyy"; - String signString = "k1=v1&v1=k1testappId=xxxxxx&nonce=" + nonce + "×tamp=" + timestamp + "yyyyyy"; - String sign = DigestUtil.sha256Hex(signString); - - // 准备参数 - ApiSignature apiSignature = mock(ApiSignature.class); - when(apiSignature.appId()).thenReturn("appId"); - when(apiSignature.timestamp()).thenReturn("timestamp"); - when(apiSignature.nonce()).thenReturn("nonce"); - when(apiSignature.sign()).thenReturn("sign"); - when(apiSignature.timeout()).thenReturn(60); - when(apiSignature.timeUnit()).thenReturn(TimeUnit.SECONDS); - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getHeader(eq("appId"))).thenReturn(appId); - when(request.getHeader(eq("timestamp"))).thenReturn(String.valueOf(timestamp)); - when(request.getHeader(eq("nonce"))).thenReturn(nonce); - when(request.getHeader(eq("sign"))).thenReturn(sign); - when(request.getParameterMap()).thenReturn(MapUtil.builder() - .put("v1", new String[]{"k1"}).put("k1", new String[]{"v1"}).build()); - when(request.getContentType()).thenReturn("application/json"); - when(request.getReader()).thenReturn(new BufferedReader(new StringReader("test"))); - // mock 方法 - when(signatureRedisDAO.getAppSecret(eq(appId))).thenReturn(appSecret); - - // 调用 - boolean result = apiSignatureAspect.verifySignature(apiSignature, request); - // 断言结果 - assertTrue(result); - // 断言调用 - verify(signatureRedisDAO).setNonce(eq(nonce), eq(120), eq(TimeUnit.SECONDS)); - } - -} diff --git a/yudao-module-ai/pom.xml b/yudao-module-ai/pom.xml deleted file mode 100644 index 7135100d79..0000000000 --- a/yudao-module-ai/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - cn.iocoder.boot - yudao - ${revision} - - 4.0.0 - - yudao-module-ai-api - yudao-module-ai-biz - yudao-spring-boot-starter-ai - - pom - yudao-module-ai - - ${project.artifactId} - - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。 - 目前已接入各种模型,不限于: - 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek - 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno - - - \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-api/pom.xml b/yudao-module-ai/yudao-module-ai-api/pom.xml deleted file mode 100644 index 41fe56efc8..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/pom.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - cn.iocoder.boot - yudao-module-ai - ${revision} - - 4.0.0 - yudao-module-ai-api - jar - - ${project.artifactId} - - ai 模块 API,暴露给其它模块调用 - - - - - cn.iocoder.boot - yudao-common - - - - - org.springframework.boot - spring-boot-starter-validation - true - - - \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/api/package-info.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/api/package-info.java deleted file mode 100644 index 4f94f23f8b..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/api/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 占位,没有特别的作用 - */ -package cn.iocoder.yudao.module.ai.api; \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java deleted file mode 100644 index 19cbc8f8f2..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleEnum.java +++ /dev/null @@ -1,64 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums; - -import cn.iocoder.yudao.framework.common.core.IntArrayValuable; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; - -/** - * AI 内置聊天角色的枚举 - * - * @author xiaoxin - */ -@AllArgsConstructor -@Getter -public enum AiChatRoleEnum implements IntArrayValuable { - - AI_WRITE_ROLE(1, "写作助手", """ - 你是一位出色的写作助手,能够帮助用户生成创意和灵感,并在用户提供场景和提示词时生成对应的回复。你的任务包括: - 1. 撰写建议:根据用户提供的主题或问题,提供详细的写作建议、情节发展方向、角色设定以及背景描写,确保内容结构清晰、有逻辑。 - 2. 回复生成:根据用户提供的场景和提示词,生成合适的对话或文字回复,确保语气和风格符合场景需求。 - 除此之外不需要除了正文内容外的其他回复,如标题、开头、任何解释性语句或道歉。 - """), - - AI_MIND_MAP_ROLE(2, "脑图助手", """ - 你是一位非常优秀的思维导图助手,你会把用户的所有提问都总结成思维导图,然后以 Markdown 格式输出。markdown 只需要输出一级标题,二级标题,三级标题,四级标题,最多输出四级,除此之外不要输出任何其他 markdown 标记。下面是一个合格的例子: - # Geek-AI 助手 - ## 完整的开源系统 - ### 前端开源 - ### 后端开源 - ## 支持各种大模型 - ### OpenAI - ### Azure - ### 文心一言 - ### 通义千问 - ## 集成多种收费方式 - ### 支付宝 - ### 微信 - 除此之外不要任何解释性语句。 - """); - - // TODO @xin:这个 role 是不是删除掉好点哈。= = 目前主要是没做角色枚举。这里多了 role 反倒容易误解哈 - /** - * 角色 - */ - private final Integer role; - /** - * 角色名 - */ - private final String name; - - /** - * 角色设定 - */ - private final String systemMessage; - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiChatRoleEnum::getRole).toArray(); - - @Override - public int[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java deleted file mode 100644 index 73782a2cbb..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/DictTypeConstants.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums; - -/** - * AI 字典类型的枚举类 - * - * @author xiaoxin - */ -public interface DictTypeConstants { - - // ========== AI Write ========== - String AI_WRITE_FORMAT = "ai_write_format"; // 写作格式 - String AI_WRITE_LENGTH = "ai_write_length"; // 写作长度 - String AI_WRITE_LANGUAGE = "ai_write_language"; // 写作语言 - String AI_WRITE_TONE = "ai_write_tone"; // 写作语气 - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java deleted file mode 100644 index ddfb489f35..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums; - -import cn.iocoder.yudao.framework.common.exception.ErrorCode; - -/** - * AI 错误码枚举类 - * - * ai 系统,使用 1-040-000-000 段 - */ -public interface ErrorCodeConstants { - - // ========== API 密钥 1-040-000-000 ========== - ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在"); - ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!"); - ErrorCode API_KEY_MIDJOURNEY_NOT_FOUND = new ErrorCode(1_040_000_900, "Midjourney 模型不存在"); - ErrorCode API_KEY_SUNO_NOT_FOUND = new ErrorCode(1_040_000_901, "Suno 模型不存在"); - ErrorCode API_KEY_IMAGE_NODE_FOUND = new ErrorCode(1_040_000_902, "平台({}) 图片模型未配置"); - - // ========== API 聊天模型 1-040-001-000 ========== - ErrorCode CHAT_MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!"); - ErrorCode CHAT_MODEL_DISABLE = new ErrorCode(1_040_001_001, "模型({})已禁用!"); - ErrorCode CHAT_MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认聊天模型"); - - // ========== API 聊天模型 1-040-002-000 ========== - ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在"); - ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!"); - - // ========== API 聊天会话 1-040-003-000 ========== - - ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!"); - ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整"); - - // ========== API 聊天消息 1-040-004-000 ========== - - ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); - ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对话生成异常!"); - - // ========== API 绘画 1-040-005-000 ========== - - ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!"); - ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}"); - ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}"); - ErrorCode IMAGE_FAIL = new ErrorCode(1_022_005_002, "图片绘画失败! {}"); - - // ========== API 音乐 1-040-006-000 ========== - ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_022_006_000, "音乐不存在!"); - - - // ========== API 写作 1-022-007-000 ========== - ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_022_007_000, "作文不存在!"); - ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_022_07_001, "写作生成异常!"); - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java deleted file mode 100644 index cf80761503..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums.image; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * AI 绘画状态的枚举 - * - * @author fansili - */ -@AllArgsConstructor -@Getter -public enum AiImageStatusEnum { - - IN_PROGRESS(10, "进行中"), - SUCCESS(20, "已完成"), - FAIL(30, "已失败"); - - /** - * 状态 - */ - private final Integer status; - /** - * 状态名 - */ - private final String name; - - public static AiImageStatusEnum valueOfStatus(Integer status) { - for (AiImageStatusEnum statusEnum : AiImageStatusEnum.values()) { - if (statusEnum.getStatus().equals(status)) { - return statusEnum; - } - } - throw new IllegalArgumentException("未知会话状态: " + status); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java deleted file mode 100644 index 651731b60f..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums.music; - -import cn.iocoder.yudao.framework.common.core.IntArrayValuable; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; - -/** - * AI 音乐生成模式的枚举 - * - * @author xiaoxin - */ -@AllArgsConstructor -@Getter -public enum AiMusicGenerateModeEnum implements IntArrayValuable { - - DESCRIPTION(1, "描述模式"), - LYRIC(2, "歌词模式"); - - /** - * 模式 - */ - private final Integer mode; - /** - * 模式名 - */ - private final String name; - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicGenerateModeEnum::getMode).toArray(); - - @Override - public int[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java deleted file mode 100644 index f1298cf564..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums.music; - -import cn.iocoder.yudao.framework.common.core.IntArrayValuable; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; - -/** - * AI 音乐状态的枚举 - * - * @author xiaoxin - */ -@AllArgsConstructor -@Getter -public enum AiMusicStatusEnum implements IntArrayValuable { - - IN_PROGRESS(10, "进行中"), - SUCCESS(20, "已完成"), - FAIL(30, "已失败"); - - /** - * 状态 - */ - private final Integer status; - - /** - * 状态名 - */ - private final String name; - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiMusicStatusEnum::getStatus).toArray(); - - @Override - public int[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java deleted file mode 100644 index 49d825be85..0000000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/write/AiWriteTypeEnum.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums.write; - -import cn.iocoder.yudao.framework.common.core.IntArrayValuable; -import lombok.AllArgsConstructor; -import lombok.Getter; - -import java.util.Arrays; - -/** - * AI 写作类型的枚举 - * - * @author xiaoxin - */ -@AllArgsConstructor -@Getter -public enum AiWriteTypeEnum implements IntArrayValuable { - - WRITING(1, "撰写", "请撰写一篇关于 [{}] 的文章。文章的内容格式:{},语气:{},语言:{},长度:{}。请确保涵盖主要内容,不需要除了正文内容外的其他回复,如标题、额外的解释或道歉。"), - REPLY(2, "回复", "请针对如下内容:[{}] 做个回复。回复内容参考:[{}], 回复格式:{},语气:{},语言:{},长度:{}。不需要除了正文内容外的其他回复,如标题、开头、额外的解释或道歉。"); - - /** - * 类型 - */ - private final Integer type; - /** - * 类型名 - */ - private final String name; - - /** - * 模版 - */ - private final String prompt; - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AiWriteTypeEnum::getType).toArray(); - - @Override - public int[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/pom.xml b/yudao-module-ai/yudao-module-ai-biz/pom.xml deleted file mode 100644 index 7c529f118b..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - cn.iocoder.boot - yudao-module-ai - ${revision} - - 4.0.0 - yudao-module-ai-biz - - ${project.artifactId} - - ai 模块下,接入 LLM 大模型,支持聊天、绘图、音乐、写作、思维脑图等功能。 - 目前已接入各种模型,不限于: - 国内:通义千问、文心一言、讯飞星火、智谱 GLM、DeepSeek - 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno - - - - - cn.iocoder.boot - yudao-module-ai-api - ${revision} - - - - - cn.iocoder.boot - yudao-spring-boot-starter-ai - ${revision} - - - - cn.iocoder.boot - yudao-spring-boot-starter-biz-tenant - - - - - cn.iocoder.boot - yudao-spring-boot-starter-security - - - - - cn.iocoder.boot - yudao-spring-boot-starter-mybatis - - - - - cn.iocoder.boot - yudao-spring-boot-starter-job - - - - - cn.iocoder.boot - yudao-spring-boot-starter-test - - - \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java deleted file mode 100644 index 5142cde443..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java +++ /dev/null @@ -1,114 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; -import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService; -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.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.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 createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) { - return success(chatConversationService.createChatConversationMy(createReqVO, getLoginUserId())); - } - - @PutMapping("/update-my") - @Operation(summary = "更新【我的】聊天对话") - public CommonResult updateChatConversationMy(@RequestBody @Valid AiChatConversationUpdateMyReqVO updateReqVO) { - chatConversationService.updateChatConversationMy(updateReqVO, getLoginUserId()); - return success(true); - } - - @GetMapping("/my-list") - @Operation(summary = "获得【我的】聊天对话列表") - public CommonResult> getChatConversationMyList() { - List list = chatConversationService.getChatConversationListByUserId(getLoginUserId()); - return success(BeanUtils.toBean(list, AiChatConversationRespVO.class)); - } - - @GetMapping("/get-my") - @Operation(summary = "获得【我的】聊天对话") - @Parameter(name = "id", required = true, description = "对话编号", example = "1024") - public CommonResult 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 deleteChatConversationMy(@RequestParam("id") Long id) { - chatConversationService.deleteChatConversationMy(id, getLoginUserId()); - return success(true); - } - - @DeleteMapping("/delete-by-unpinned") - @Operation(summary = "删除未置顶的聊天对话") - public CommonResult deleteChatConversationMyByUnpinned() { - chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId()); - return success(true); - } - - // ========== 对话管理 ========== - - @GetMapping("/page") - @Operation(summary = "获得对话分页", description = "用于【对话管理】菜单") - @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')") - public CommonResult> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) { - PageResult pageResult = chatConversationService.getChatConversationPage(pageReqVO); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty()); - } - // 拼接关联数据 - Map 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 deleteChatConversationByAdmin(@RequestParam("id") Long id) { - chatConversationService.deleteChatConversationByAdmin(id); - return success(true); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http deleted file mode 100644 index e75e0d3335..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http +++ /dev/null @@ -1,29 +0,0 @@ -### 发送消息(段式) -POST {{baseUrl}}/ai/chat/message/send -Content-Type: application/json -Authorization: {{token}} -tenant-id: {{adminTenentId}} - -{ - "conversationId": "1781604279872581724", - "content": "你是 OpenAI 么?" -} - -### 发送消息(流式) -POST {{baseUrl}}/ai/chat/message/send-stream -Content-Type: application/json -Authorization: {{token}} -tenant-id: {{adminTenentId}} - -{ - "conversationId": "1781604279872581724", - "content": "1+1=?" -} - -### 获得指定对话的消息列表 -GET {{baseUrl}}/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649 -Authorization: {{token}} - -### 删除消息 -DELETE {{baseUrl}}/ai/chat/message/delete?id=50 -Authorization: {{token}} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java deleted file mode 100644 index 357dbec5ed..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java +++ /dev/null @@ -1,120 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; -import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService; -import cn.iocoder.yudao.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.annotation.security.PermitAll; -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.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.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; - - @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢") - @PostMapping("/send") - public CommonResult sendMessage(@Valid @RequestBody AiChatMessageSendReqVO sendReqVO) { - return success(chatMessageService.sendMessage(sendReqVO, getLoginUserId())); - } - - @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") - @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 - public Flux> 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> getChatMessageListByConversationId( - @RequestParam("conversationId") Long conversationId) { - AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId); - if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) { - return success(Collections.emptyList()); - } - List messageList = chatMessageService.getChatMessageListByConversationId(conversationId); - return success(BeanUtils.toBean(messageList, AiChatMessageRespVO.class)); - } - - @Operation(summary = "删除消息") - @DeleteMapping("/delete") - @Parameter(name = "id", required = true, description = "消息编号", example = "1024") - public CommonResult 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 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> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) { - PageResult pageResult = chatMessageService.getChatMessagePage(pageReqVO); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty()); - } - // 拼接数据 - Map 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 deleteChatMessageByAdmin(@RequestParam("id") Long id) { - chatMessageService.deleteChatMessageByAdmin(id); - return success(true); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java deleted file mode 100644 index c13200b6ae..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.iocoder.yudao.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; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java deleted file mode 100644 index 967e866ea7..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation; - -import cn.iocoder.yudao.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.yudao.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; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java deleted file mode 100644 index 66eb24db5f..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java +++ /dev/null @@ -1,71 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation; - -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.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 = AiChatModelDO.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; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java deleted file mode 100644 index f9ce64bae3..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.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 = "一个快乐的程序员") - 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; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessagePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessagePageReqVO.java deleted file mode 100644 index 7ccb6aa0b6..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessagePageReqVO.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message; - -import cn.iocoder.yudao.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.yudao.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; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java deleted file mode 100644 index 9b358df6f2..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - AI 聊天消息 Response VO") -@Data -public class AiChatMessageRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - private Long conversationId; - - @Schema(description = "回复消息编号", example = "1024") - private Long replyId; - - @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role") - private String type; // 参见 MessageType 枚举类 - - @Schema(description = "用户编号", example = "4096") - private Long userId; - - @Schema(description = "角色编号", example = "888") - private Long roleId; - - @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo") - private String model; - - @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123") - private Long modelId; - - @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") - private String content; - - @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean useContext; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51") - private LocalDateTime createTime; - - // ========== 仅在【对话管理】时加载 ========== - - @Schema(description = "角色名字", example = "小黄") - private String roleName; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java deleted file mode 100644 index 89a84bcbd2..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendReqVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Data; -import lombok.experimental.Accessors; - -@Schema(description = "管理后台 - AI 聊天消息发送 Request VO") -@Data -public class AiChatMessageSendReqVO { - - @Schema(description = "聊天对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "聊天对话编号不能为空") - private Long conversationId; - - @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "帮我写个 Java 算法") - @NotEmpty(message = "聊天内容不能为空") - private String content; - - @Schema(description = "是否携带上下文", example = "true") - private Boolean useContext; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java deleted file mode 100644 index 58ba056595..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - AI 聊天消息发送 Response VO") -@Data -public class AiChatMessageSendRespVO { - - @Schema(description = "发送消息", requiredMode = Schema.RequiredMode.REQUIRED) - private Message send; - - @Schema(description = "接收消息", requiredMode = Schema.RequiredMode.REQUIRED) - private Message receive; - - @Schema(description = "消息") - @Data - public static class Message { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Long id; - - @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role") - private String type; // 参见 MessageType 枚举类 - - @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊") - private String content; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.http deleted file mode 100644 index 9047610c0f..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.http +++ /dev/null @@ -1,42 +0,0 @@ -### 生成图片:OpenAI(DALL) -POST {{baseUrl}}/ai/image/draw -Content-Type: application/json -Authorization: {{token}} - -{ - "platform": "OpenAI", - "prompt": "可爱的小喵星人", - "model": "dall-e-3", - "height": "1024", - "width": "1024", - "options": { - "style": "vivid" - } -} - -### 生成图片:StableDiffusion -POST {{baseUrl}}/ai/image/draw -Content-Type: application/json -Authorization: {{token}} - -{ - "platform": "StableDiffusion", - "prompt": "中国长城", - "model": "stable-diffusion-v1-6", - "height": "1024", - "width": "1024", - "style": "vivid" -} - -### 生成图片:生成图片(Midjourney) -POST {{baseUrl}}/ai/image/midjourney/imagine -Content-Type: application/json -Authorization: {{token}} - -{ - "prompt": "中国旗袍", - "model": "midjourney", - "width": "1", - "height": "1", - "version": "6.0" -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java deleted file mode 100644 index de12ee1e05..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java +++ /dev/null @@ -1,134 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image; - -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; -import cn.iocoder.yudao.module.ai.service.image.AiImageService; -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.annotation.security.PermitAll; -import jakarta.validation.Valid; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - AI 绘画") -@RestController -@RequestMapping("/ai/image") -@Slf4j -public class AiImageController { - - @Resource - private AiImageService imageService; - - @GetMapping("/my-page") - @Operation(summary = "获取【我的】绘图分页") - public CommonResult> getImagePageMy(@Validated PageParam pageReqVO) { - PageResult pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO); - return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); - } - - @GetMapping("/get-my") - @Operation(summary = "获取【我的】绘图记录") - @Parameter(name = "id", required = true, description = "绘画编号", example = "1024") - public CommonResult getImageMy(@RequestParam("id") Long id) { - AiImageDO image = imageService.getImage(id); - if (image == null || ObjUtil.notEqual(getLoginUserId(), image.getUserId())) { - return success(null); - } - return success(BeanUtils.toBean(image, AiImageRespVO.class)); - } - - @GetMapping("/my-list-by-ids") - @Operation(summary = "获取【我的】绘图记录列表") - @Parameter(name = "ids", required = true, description = "绘画编号数组", example = "1024,2048") - public CommonResult> getImageListMyByIds(@RequestParam("ids") List ids) { - List imageList = imageService.getImageList(ids); - imageList.removeIf(item -> !ObjUtil.equal(getLoginUserId(), item.getUserId())); - return success(BeanUtils.toBean(imageList, AiImageRespVO.class)); - } - - @Operation(summary = "生成图片") - @PostMapping("/draw") - public CommonResult drawImage(@Valid @RequestBody AiImageDrawReqVO drawReqVO) { - return success(imageService.drawImage(getLoginUserId(), drawReqVO)); - } - - @Operation(summary = "删除【我的】绘画记录") - @DeleteMapping("/delete-my") - @Parameter(name = "id", required = true, description = "绘画编号", example = "1024") - public CommonResult deleteImageMy(@RequestParam("id") Long id) { - imageService.deleteImageMy(id, getLoginUserId()); - return success(true); - } - - // ================ midjourney 专属 ================ - - @Operation(summary = "【Midjourney】生成图片") - @PostMapping("/midjourney/imagine") - public CommonResult midjourneyImagine(@Valid @RequestBody AiMidjourneyImagineReqVO reqVO) { - Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO); - return success(imageId); - } - - @Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调") - @PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错 - @PermitAll - public CommonResult midjourneyNotify(@Valid @RequestBody MidjourneyApi.Notify notify) { - imageService.midjourneyNotify(notify); - return success(true); - } - - @Operation(summary = "【Midjourney】Action 操作(二次生成图片)", description = "例如说:放大、缩小、U1、U2 等") - @PostMapping("/midjourney/action") - public CommonResult midjourneyAction(@Valid @RequestBody AiMidjourneyActionReqVO reqVO) { - Long imageId = imageService.midjourneyAction(getLoginUserId(), reqVO); - return success(imageId); - } - - // ================ 绘图管理 ================ - - @GetMapping("/page") - @Operation(summary = "获得绘画分页") - @PreAuthorize("@ss.hasPermission('ai:image:query')") - public CommonResult> getImagePage(@Valid AiImagePageReqVO pageReqVO) { - PageResult pageResult = imageService.getImagePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); - } - - @PutMapping("/update") - @Operation(summary = "更新绘画") - @PreAuthorize("@ss.hasPermission('ai:image:update')") - public CommonResult updateImage(@Valid @RequestBody AiImageUpdateReqVO updateReqVO) { - imageService.updateImage(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除绘画") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('ai:image:delete')") - public CommonResult deleteImage(@RequestParam("id") Long id) { - imageService.deleteImage(id); - return success(true); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java deleted file mode 100644 index a38935ef71..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Data; -import org.springframework.ai.openai.OpenAiImageOptions; -import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; - -import java.util.Map; - -@Schema(description = "管理后台 - AI 绘画 Request VO") -@Data -public class AiImageDrawReqVO { - - @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") - private String platform; // 参见 AiPlatformEnum 枚举 - - @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城") - @NotEmpty(message = "提示词不能为空") - @Size(max = 1200, message = "提示词最大 1200") - private String prompt; - - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6") - @NotEmpty(message = "模型不能为空") - private String model; - - /** - * 1. dall-e-2 模型:256x256、512x512、1024x1024 - * 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792 - */ - @Schema(description = "图片高度") - @NotNull(message = "图片高度不能为空") - private Integer height; - - @Schema(description = "图片宽度") - @NotNull(message = "图片宽度不能为空") - private Integer width; - - // ========== 各平台绘画的拓展参数 ========== - - /** - * 绘制参数,不同 platform 的不同参数 - * - * 1. {@link OpenAiImageOptions} - * 2. {@link StabilityAiImageOptions} - */ - @Schema(description = "绘制参数") - private Map options; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java deleted file mode 100644 index bdf329c618..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - AI 绘画分页 Request VO") -@Data -public class AiImagePageReqVO extends PageParam { - - @Schema(description = "用户编号", example = "28987") - private Long userId; - - @Schema(description = "平台", example = "OpenAI") - private String platform; - - @Schema(description = "绘画状态", example = "1") - private Integer status; - - @Schema(description = "是否发布", example = "1") - private Boolean publicStatus; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java deleted file mode 100644 index f73d05aaa7..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java +++ /dev/null @@ -1,60 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; - -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -@Schema(description = "管理后台 - AI 绘画 Response VO") -@Data -public class AiImageRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long id; - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long userId; - - @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") - private String platform; // 参见 AiPlatformEnum 枚举 - - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6") - private String model; - - @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "南极的小企鹅") - private String prompt; - - @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Integer width; - - @Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - private Integer height; - - @Schema(description = "绘画状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer status; - - @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "public") - private Boolean publicStatus; - - @Schema(description = "图片地址", example = "https://www.iocoder.cn/1.png") - private String picUrl; - - @Schema(description = "绘画错误信息", example = "图片错误信息") - private String errorMessage; - - @Schema(description = "绘制参数") - private Map options; - - @Schema(description = "mj buttons 按钮") - private List buttons; - - @Schema(description = "完成时间") - private LocalDateTime finishTime; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageUpdateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageUpdateReqVO.java deleted file mode 100644 index 45df01015b..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageUpdateReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 绘画修改 Request VO") -@Data -public class AiImageUpdateReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") - @NotNull(message = "编号不能为空") - private Long id; - - @Schema(description = "是否发布", example = "true") - private Boolean publicStatus; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyActionReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyActionReqVO.java deleted file mode 100644 index 28803a051c..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyActionReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 绘图操作(Midjourney) Request VO") -@Data -public class AiMidjourneyActionReqVO { - - @Schema(description = "图片编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "图片编号不能为空") - private Long id; - - @Schema(description = "操作按钮编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "MJ::JOB::variation::4::06aa3e66-0e97-49cc-8201-e0295d883de4") - @NotEmpty(message = "操作按钮编号不能为空") - private String customId; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyImagineReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyImagineReqVO.java deleted file mode 100644 index b90882639d..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiMidjourneyImagineReqVO.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 绘画生成(Midjourney) Request VO") -@Data -public class AiMidjourneyImagineReqVO { - - @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙") - @NotEmpty(message = "提示词不能为空!") - private String prompt; - - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "midjourney") - @NotEmpty(message = "模型不能为空") - private String model; // 参考 MidjourneyApi.ModelEnum - - @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "图片宽度不能为空") - private Integer width; - - @Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "图片高度不能为空") - private Integer height; - - @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6.0") - @NotEmpty(message = "版本号不能为空") - private String version; - - @Schema(description = "参考图", example = "https://www.iocoder.cn/x.png") - private String referImageUrl; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java deleted file mode 100644 index 0151802654..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.mindmap; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; -import cn.iocoder.yudao.module.ai.service.mindmap.AiMindMapService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.validation.Valid; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Flux; - -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - AI 思维导图") -@RestController -@RequestMapping("/ai/mind-map") -public class AiMindMapController { - - @Resource - private AiMindMapService mindMapService; - - @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - @Operation(summary = "脑图生成(流式)", description = "流式返回,响应较快") - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 - public Flux> generateMindMap(@RequestBody @Valid AiMindMapGenerateReqVO generateReqVO) { - return mindMapService.generateMindMap(generateReqVO, getLoginUserId()); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapGenerateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapGenerateReqVO.java deleted file mode 100644 index 08404bb0f1..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/vo/AiMindMapGenerateReqVO.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -@Schema(description = "管理后台 - AI 思维导图生成 Request VO") -@Data -public class AiMindMapGenerateReqVO { - - @Schema(description = "思维导图内容提示", example = "Java 学习路线") - @NotBlank(message = "思维导图内容提示不能为空") - private String prompt; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiApiKeyController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiApiKeyController.java deleted file mode 100644 index 2bc190051d..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiApiKeyController.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -@Tag(name = "管理后台 - AI API 密钥") -@RestController -@RequestMapping("/ai/api-key") -@Validated -public class AiApiKeyController { - - @Resource - private AiApiKeyService apiKeyService; - - @PostMapping("/create") - @Operation(summary = "创建 API 密钥") - @PreAuthorize("@ss.hasPermission('ai:api-key:create')") - public CommonResult createApiKey(@Valid @RequestBody AiApiKeySaveReqVO createReqVO) { - return success(apiKeyService.createApiKey(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新 API 密钥") - @PreAuthorize("@ss.hasPermission('ai:api-key:update')") - public CommonResult updateApiKey(@Valid @RequestBody AiApiKeySaveReqVO updateReqVO) { - apiKeyService.updateApiKey(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除 API 密钥") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('ai:api-key:delete')") - public CommonResult deleteApiKey(@RequestParam("id") Long id) { - apiKeyService.deleteApiKey(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得 API 密钥") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('ai:api-key:query')") - public CommonResult getApiKey(@RequestParam("id") Long id) { - AiApiKeyDO apiKey = apiKeyService.getApiKey(id); - return success(BeanUtils.toBean(apiKey, AiApiKeyRespVO.class)); - } - - @GetMapping("/page") - @Operation(summary = "获得 API 密钥分页") - @PreAuthorize("@ss.hasPermission('ai:api-key:query')") - public CommonResult> getApiKeyPage(@Valid AiApiKeyPageReqVO pageReqVO) { - PageResult pageResult = apiKeyService.getApiKeyPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiApiKeyRespVO.class)); - } - - @GetMapping("/simple-list") - @Operation(summary = "获得 API 密钥分页列表") - public CommonResult> getApiKeySimpleList() { - List list = apiKeyService.getApiKeyList(); - return success(convertList(list, key -> new AiChatModelRespVO().setId(key.getId()).setName(key.getName()))); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatModelController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatModelController.java deleted file mode 100644 index 08a53b286b..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatModelController.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; -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 static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; - -@Tag(name = "管理后台 - AI 聊天模型") -@RestController -@RequestMapping("/ai/chat-model") -@Validated -public class AiChatModelController { - - @Resource - private AiChatModelService chatModelService; - - @PostMapping("/create") - @Operation(summary = "创建聊天模型") - @PreAuthorize("@ss.hasPermission('ai:chat-model:create')") - public CommonResult createChatModel(@Valid @RequestBody AiChatModelSaveReqVO createReqVO) { - return success(chatModelService.createChatModel(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新聊天模型") - @PreAuthorize("@ss.hasPermission('ai:chat-model:update')") - public CommonResult updateChatModel(@Valid @RequestBody AiChatModelSaveReqVO updateReqVO) { - chatModelService.updateChatModel(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除聊天模型") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('ai:chat-model:delete')") - public CommonResult deleteChatModel(@RequestParam("id") Long id) { - chatModelService.deleteChatModel(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得聊天模型") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('ai:chat-model:query')") - public CommonResult getChatModel(@RequestParam("id") Long id) { - AiChatModelDO chatModel = chatModelService.getChatModel(id); - return success(BeanUtils.toBean(chatModel, AiChatModelRespVO.class)); - } - - @GetMapping("/page") - @Operation(summary = "获得聊天模型分页") - @PreAuthorize("@ss.hasPermission('ai:chat-model:query')") - public CommonResult> getChatModelPage(@Valid AiChatModelPageReqVO pageReqVO) { - PageResult pageResult = chatModelService.getChatModelPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiChatModelRespVO.class)); - } - - @GetMapping("/simple-list") - @Operation(summary = "获得聊天模型列表") - @Parameter(name = "status", description = "状态", required = true, example = "1") - public CommonResult> getChatModelSimpleList(@RequestParam("status") Integer status) { - List list = chatModelService.getChatModelListByStatus(status); - return success(convertList(list, model -> new AiChatModelRespVO().setId(model.getId()) - .setName(model.getName()).setModel(model.getModel()))); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java deleted file mode 100644 index 02f698b944..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java +++ /dev/null @@ -1,124 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model; - -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.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 org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - AI 聊天角色") -@RestController -@RequestMapping("/ai/chat-role") -@Validated -public class AiChatRoleController { - - @Resource - private AiChatRoleService chatRoleService; - - @GetMapping("/my-page") - @Operation(summary = "获得【我的】聊天角色分页") - public CommonResult> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) { - PageResult pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId()); - return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class)); - } - - @GetMapping("/get-my") - @Operation(summary = "获得【我的】聊天角色") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public CommonResult getChatRoleMy(@RequestParam("id") Long id) { - AiChatRoleDO chatRole = chatRoleService.getChatRole(id); - if (ObjUtil.notEqual(chatRole.getUserId(), getLoginUserId())) { - return success(null); - } - return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class)); - } - - @PostMapping("/create-my") - @Operation(summary = "创建【我的】聊天角色") - public CommonResult createChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO createReqVO) { - return success(chatRoleService.createChatRoleMy(createReqVO, getLoginUserId())); - } - - @PutMapping("/update-my") - @Operation(summary = "更新【我的】聊天角色") - public CommonResult updateChatRoleMy(@Valid @RequestBody AiChatRoleSaveMyReqVO updateReqVO) { - chatRoleService.updateChatRoleMy(updateReqVO, getLoginUserId()); - return success(true); - } - - @DeleteMapping("/delete-my") - @Operation(summary = "删除【我的】聊天角色") - @Parameter(name = "id", description = "编号", required = true) - public CommonResult deleteChatRoleMy(@RequestParam("id") Long id) { - chatRoleService.deleteChatRoleMy(id, getLoginUserId()); - return success(true); - } - - @GetMapping("/category-list") - @Operation(summary = "获得聊天角色的分类列表") - public CommonResult> getChatRoleCategoryList() { - return success(chatRoleService.getChatRoleCategoryList()); - } - - // ========== 角色管理 ========== - - @PostMapping("/create") - @Operation(summary = "创建聊天角色") - @PreAuthorize("@ss.hasPermission('ai:chat-role:create')") - public CommonResult createChatRole(@Valid @RequestBody AiChatRoleSaveReqVO createReqVO) { - return success(chatRoleService.createChatRole(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新聊天角色") - @PreAuthorize("@ss.hasPermission('ai:chat-role:update')") - public CommonResult updateChatRole(@Valid @RequestBody AiChatRoleSaveReqVO updateReqVO) { - chatRoleService.updateChatRole(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除聊天角色") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('ai:chat-role:delete')") - public CommonResult deleteChatRole(@RequestParam("id") Long id) { - chatRoleService.deleteChatRole(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得聊天角色") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('ai:chat-role:query')") - public CommonResult getChatRole(@RequestParam("id") Long id) { - AiChatRoleDO chatRole = chatRoleService.getChatRole(id); - return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class)); - } - - @GetMapping("/page") - @Operation(summary = "获得聊天角色分页") - @PreAuthorize("@ss.hasPermission('ai:chat-role:query')") - public CommonResult> getChatRolePage(@Valid AiChatRolePageReqVO pageReqVO) { - PageResult pageResult = chatRoleService.getChatRolePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeyPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeyPageReqVO.java deleted file mode 100644 index 0636962447..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeyPageReqVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey; - -import lombok.*; -import java.util.*; -import io.swagger.v3.oas.annotations.media.Schema; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import org.springframework.format.annotation.DateTimeFormat; -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - AI API 密钥分页 Request VO") -@Data -public class AiApiKeyPageReqVO extends PageParam { - - @Schema(description = "名称", example = "文心一言") - private String name; - - @Schema(description = "平台", example = "OpenAI") - private String platform; - - @Schema(description = "状态", example = "1") - private Integer status; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeyRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeyRespVO.java deleted file mode 100644 index 55d6d802b5..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeyRespVO.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -@Schema(description = "管理后台 - AI API 密钥 Response VO") -@Data -public class AiApiKeyRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538") - private Long id; - - @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "文心一言") - private String name; - - @Schema(description = "密钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC") - private String apiKey; - - @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") - private String platform; - - @Schema(description = "自定义 API 地址", example = "https://aip.baidubce.com") - private String url; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer status; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeySaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeySaveReqVO.java deleted file mode 100644 index 8fbc8fde78..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/apikey/AiApiKeySaveReqVO.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import java.util.*; -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - AI API 密钥新增/修改 Request VO") -@Data -public class AiApiKeySaveReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538") - private Long id; - - @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "文心一言") - @NotEmpty(message = "名称不能为空") - private String name; - - @Schema(description = "密钥", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC") - @NotEmpty(message = "密钥不能为空") - private String apiKey; - - @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") - @NotEmpty(message = "平台不能为空") - private String platform; - - @Schema(description = "自定义 API 地址", example = "https://aip.baidubce.com") - private String url; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "状态不能为空") - private Integer status; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelPageReqVO.java deleted file mode 100644 index ce2f83b4b1..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelPageReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; -import cn.iocoder.yudao.framework.common.pojo.PageParam; - -@Schema(description = "管理后台 - API 聊天模型分页 Request VO") -@Data -public class AiChatModelPageReqVO extends PageParam { - - @Schema(description = "模型名字", example = "张三") - private String name; - - @Schema(description = "模型标识", example = "gpt-3.5-turbo-0125") - private String model; - - @Schema(description = "模型平台", example = "OpenAI") - private String platform; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelRespVO.java deleted file mode 100644 index 681dabe687..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelRespVO.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - AI 聊天模型 Response VO") -@Data -public class AiChatModelRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2630") - private Long id; - - @Schema(description = "API 秘钥编号", example = "22042") - private Long keyId; - - @Schema(description = "模型名字", example = "张三") - private String name; - - @Schema(description = "模型标识", example = "gpt-3.5-turbo-0125") - private String model; - - @Schema(description = "模型平台", example = "OpenAI") - private String platform; - - @Schema(description = "排序", example = "1") - private Integer sort; - - @Schema(description = "状态", example = "2") - private Integer status; - - @Schema(description = "温度参数", example = "1") - private Double temperature; - - @Schema(description = "单条回复的最大 Token 数量", example = "4096") - private Integer maxTokens; - - @Schema(description = "上下文的最大 Message 数量", example = "8192") - private Integer maxContexts; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelSaveReqVO.java deleted file mode 100644 index 4fad5a1fc1..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatModel/AiChatModelSaveReqVO.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - API 聊天模型新增/修改 Request VO") -@Data -public class AiChatModelSaveReqVO { - - @Schema(description = "编号", example = "2630") - private Long id; - - @Schema(description = "API 秘钥编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22042") - @NotNull(message = "API 秘钥编号不能为空") - private Long keyId; - - @Schema(description = "模型名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") - @NotEmpty(message = "模型名字不能为空") - private String name; - - @Schema(description = "模型标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125") - @NotEmpty(message = "模型标识不能为空") - private String model; - - @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") - @NotEmpty(message = "模型平台不能为空") - private String platform; - - @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "排序不能为空") - private Integer sort; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @InEnum(CommonStatusEnum.class) - @NotNull(message = "状态不能为空") - private Integer status; - - @Schema(description = "温度参数", example = "1") - private Double temperature; - - @Schema(description = "单条回复的最大 Token 数量", example = "4096") - private Integer maxTokens; - - @Schema(description = "上下文的最大 Message 数量", example = "8192") - private Integer maxContexts; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRolePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRolePageReqVO.java deleted file mode 100644 index 0a9d08de59..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRolePageReqVO.java +++ /dev/null @@ -1,20 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; -import cn.iocoder.yudao.framework.common.pojo.PageParam; - -@Schema(description = "管理后台 - AI 聊天角色分页 Request VO") -@Data -public class AiChatRolePageReqVO extends PageParam { - - @Schema(description = "角色名称", example = "李四") - private String name; - - @Schema(description = "角色类别", example = "创作") - private String category; - - @Schema(description = "是否公开", example = "1") - private Boolean publicStatus; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java deleted file mode 100644 index eb34da2748..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleRespVO.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole; - -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -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 AiChatRoleRespVO implements VO { - - @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32746") - private Long id; - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9442") - private Long userId; - - @Schema(description = "模型编号", example = "17640") - @Trans(type = TransType.SIMPLE, target = AiChatModelDO.class, fields = {"name", "model"}, refs = {"modelName", "model"}) - private Long modelId; - @Schema(description = "模型名字", example = "张三") - private String modelName; - @Schema(description = "模型标识", example = "gpt-3.5-turbo-0125") - private String model; - - @Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - private String name; - - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") - private String avatar; - - @Schema(description = "角色类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "创作") - private String category; - - @Schema(description = "角色排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer sort; - - @Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对") - private String description; - - @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED) - private String systemMessage; - - @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Boolean publicStatus; - - @Schema(description = "状态", example = "1") - private Integer status; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveMyReqVO.java deleted file mode 100644 index 4673901d38..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveMyReqVO.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; -import org.hibernate.validator.constraints.URL; - -@Schema(description = "管理后台 - AI 聊天角色新增/修改【我的】 Request VO") -@Data -public class AiChatRoleSaveMyReqVO { - - @Schema(description = "角色编号", example = "32746") - private Long id; - - @Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - @NotEmpty(message = "角色名称不能为空") - private String name; - - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") - @NotEmpty(message = "角色头像不能为空") - @URL(message = "角色头像必须是 URL 格式") - private String avatar; - - @Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对") - @NotEmpty(message = "角色描述不能为空") - private String description; - - @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED, example = "现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题") - @NotEmpty(message = "角色设定不能为空") - private String systemMessage; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveReqVO.java deleted file mode 100644 index bdda027ef2..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/vo/chatRole/AiChatRoleSaveReqVO.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; -import jakarta.validation.constraints.*; -import org.hibernate.validator.constraints.URL; - -@Schema(description = "管理后台 - AI 聊天角色新增/修改 Request VO") -@Data -public class AiChatRoleSaveReqVO { - - @Schema(description = "角色编号", example = "32746") - private Long id; - - @Schema(description = "模型编号", example = "17640") - private Long modelId; - - @Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - @NotEmpty(message = "角色名称不能为空") - private String name; - - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") - @NotEmpty(message = "角色头像不能为空") - @URL(message = "角色头像必须是 URL 格式") - private String avatar; - - @Schema(description = "角色类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "创作") - @NotEmpty(message = "角色类别不能为空") - private String category; - - @Schema(description = "角色排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "角色排序不能为空") - private Integer sort; - - @Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对") - @NotEmpty(message = "角色描述不能为空") - private String description; - - @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED, example = "现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题") - @NotEmpty(message = "角色设定不能为空") - private String systemMessage; - - @Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "是否公开不能为空") - private Boolean publicStatus; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "状态不能为空") - @InEnum(CommonStatusEnum.class) - private Integer status; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http deleted file mode 100644 index ae68c82ea6..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.http +++ /dev/null @@ -1,26 +0,0 @@ -### 生成音乐:Suno + 歌词模式 -POST {{baseUrl}}/ai/music/generate -Content-Type: application/json -Authorization: {{token}} - -{ - "platform": "Suno", - "generateMode": 2, - "prompt": "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。", - "model": "chirp-v3.5", - "tags": ["Happy"], - "title": "Happy Song" -} - -### 生成音乐:Suno + 描述模式 -POST {{baseUrl}}/ai/music/generate -Content-Type: application/json -Authorization: {{token}} - -{ - "platform": "Suno", - "generateMode": 1, - "model": "chirp-v3.5", - "prompt": "happy music", - "makeInstrumental": false -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java deleted file mode 100644 index 6c09e4b30b..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music; - -import cn.hutool.core.util.ObjUtil; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; -import cn.iocoder.yudao.module.ai.service.music.AiMusicService; -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.web.bind.annotation.*; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - AI 音乐") -@RestController -@RequestMapping("/ai/music") -public class AiMusicController { - - @Resource - private AiMusicService musicService; - - @GetMapping("/my-page") - @Operation(summary = "获得【我的】音乐分页") - public CommonResult> getMusicMyPage(@Valid AiMusicPageReqVO pageReqVO) { - PageResult pageResult = musicService.getMusicMyPage(pageReqVO, getLoginUserId()); - return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); - } - - @PostMapping("/generate") - @Operation(summary = "音乐生成") - public CommonResult> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { - return success(musicService.generateMusic(getLoginUserId(), reqVO)); - } - - @Operation(summary = "删除【我的】音乐记录") - @DeleteMapping("/delete-my") - @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") - public CommonResult deleteMusicMy(@RequestParam("id") Long id) { - musicService.deleteMusicMy(id, getLoginUserId()); - return success(true); - } - - @GetMapping("/get-my") - @Operation(summary = "获取【我的】音乐") - @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") - public CommonResult getMusicMy(@RequestParam("id") Long id) { - AiMusicDO music = musicService.getMusic(id); - if (music == null || ObjUtil.notEqual(getLoginUserId(), music.getUserId())) { - return success(null); - } - return success(BeanUtils.toBean(music, AiMusicRespVO.class)); - } - - @PostMapping("/update-my") - @Operation(summary = "修改【我的】音乐 目前只支持修改标题") - @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") - public CommonResult updateMy(AiMusicUpdateMyReqVO updateReqVO) { - musicService.updateMyMusic(updateReqVO, getLoginUserId()); - return success(true); - } - - // ================ 音乐管理 ================ - - @GetMapping("/page") - @Operation(summary = "获得音乐分页") - @PreAuthorize("@ss.hasPermission('ai:music:query')") - public CommonResult> getMusicPage(@Valid AiMusicPageReqVO pageReqVO) { - PageResult pageResult = musicService.getMusicPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除音乐") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('ai:music:delete')") - public CommonResult deleteMusic(@RequestParam("id") Long id) { - musicService.deleteMusic(id); - return success(true); - } - - @PutMapping("/update") - @Operation(summary = "更新音乐") - @PreAuthorize("@ss.hasPermission('ai:music:update')") - public CommonResult updateMusic(@Valid @RequestBody AiMusicUpdateReqVO updateReqVO) { - musicService.updateMusic(updateReqVO); - return success(true); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java deleted file mode 100644 index 678edae3d2..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicPageReqVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; -import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - AI 音乐分页 Request VO") -@Data -public class AiMusicPageReqVO extends PageParam { - - @Schema(description = "用户编号", example = "12212") - private Long userId; - - @Schema(description = "音乐名称", example = "夜空中最亮的星") - private String title; - - @Schema(description = "音乐状态", example = "20") - @InEnum(AiMusicStatusEnum.class) - private Integer status; - - @Schema(description = "生成模式", example = "1") - @InEnum(AiMusicGenerateModeEnum.class) - private Integer generateMode; - - @Schema(description = "是否发布", example = "true") - private Boolean publicStatus; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java deleted file mode 100644 index 05044a4e7c..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; - -@Schema(description = "管理后台 - AI 音乐 Response VO") -@Data -public class AiMusicRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") - private Long id; - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12212") - private Long userId; - - @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星") - private String title; - - @Schema(description = "歌词", example = "oh~卖糕的") - private String lyric; - - @Schema(description = "图片地址", example = "https://www.iocoder.cn") - private String imageUrl; - - @Schema(description = "音频地址", example = "https://www.iocoder.cn") - private String audioUrl; - - @Schema(description = "视频地址", example = "https://www.iocoder.cn") - private String videoUrl; - - @Schema(description = "音乐状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer status; - - @Schema(description = "描述词", example = "一首轻快的歌曲") - private String gptDescriptionPrompt; - - @Schema(description = "提示词", example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") - private String prompt; - - @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") - private String platform; - - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") - private String model; - - @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer generateMode; - - @Schema(description = "音乐风格标签") - private List tags; - - @Schema(description = "音乐时长", example = "[\"pop\",\"jazz\",\"punk\"]") - private Double duration; - - @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean publicStatus; - - @Schema(description = "任务编号", example = "11369") - private String taskId; - - @Schema(description = "错误信息") - private String errorMessage; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateMyReqVO.java deleted file mode 100644 index 4576701156..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateMyReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 修改我的音乐 Request VO") -@Data -public class AiMusicUpdateMyReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") - @NotNull(message = "编号不能为空") - private Long id; - - @Schema(description = "音乐名称", example = "夜空中最亮的星") - private String title; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java deleted file mode 100644 index 447bc9765e..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 音乐修改 Request VO") -@Data -public class AiMusicUpdateReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") - @NotNull(message = "编号不能为空") - private Long id; - - @Schema(description = "是否发布", example = "true") - private Boolean publicStatus; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java deleted file mode 100644 index f72d2b54a5..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.util.List; - -@Schema(description = "管理后台 - AI 音乐生成 Request VO") -@Data -public class AiSunoGenerateReqVO { - - @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "Suno") - @NotBlank(message = "平台不能为空") - private String platform; // 参见 AiPlatformEnum 枚举 - - /** - * 1. 描述模式:描述词 + 是否纯音乐 + 模型 - * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 - */ - @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @NotNull(message = "生成模式不能为空") - private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 - - @Schema(description = "用于生成音乐音频的歌词提示", - example = """ - 1.描述模式:创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。 - 2.歌词模式: - [Verse] - 阳光下奔跑 多么欢快 - 假期就要来 心都飞起来 - 朋友在一旁 笑声又灿烂 - 无忧无虑的 每一天甜蜜 - [Chorus] - 马上放假了 快来庆祝 - 一起去旅行 快去冒险 - 日子太短暂 别再等待 - 马上放假了 梦想起飞 - """) - private String prompt; - - @Schema(description = "是否纯音乐", example = "true") - private Boolean makeInstrumental; - - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") - @NotEmpty(message = "模型不能为空") - private String model; - - @Schema(description = "音乐风格", example = "[\"pop\",\"jazz\",\"punk\"]") - private List tags; - - @Schema(description = "音乐/歌曲名称", example = "夜空中最亮的星") - private String title; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java deleted file mode 100644 index d27204d21e..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.write; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; -import cn.iocoder.yudao.module.ai.service.write.AiWriteService; -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.annotation.security.PermitAll; -import jakarta.validation.Valid; -import org.springframework.http.MediaType; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; -import reactor.core.publisher.Flux; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - AI 写作") -@RestController -@RequestMapping("/ai/write") -public class AiWriteController { - - @Resource - private AiWriteService writeService; - - @PostMapping(value = "/generate-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - @Operation(summary = "写作生成(流式)", description = "流式返回,响应较快") - @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 - public Flux> generateWriteContent(@RequestBody @Valid AiWriteGenerateReqVO generateReqVO) { - return writeService.generateWriteContent(generateReqVO, getLoginUserId()); - } - - // ================ 写作管理 ================ - - @DeleteMapping("/delete") - @Operation(summary = "删除写作") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('ai:write:delete')") - public CommonResult deleteWrite(@RequestParam("id") Long id) { - writeService.deleteWrite(id); - return success(true); - } - - @GetMapping("/page") - @Operation(summary = "获得写作分页") - @PreAuthorize("@ss.hasPermission('ai:write:query')") - public CommonResult> getWritePage(@Valid AiWritePageReqVO pageReqVO) { - PageResult pageResult = writeService.getWritePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, AiWriteRespVO.class)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java deleted file mode 100644 index 21c60420da..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteGenerateReqVO.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.write.vo; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 写作生成 Request VO") -@Data -public class AiWriteGenerateReqVO { - - @Schema(description = "写作类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @InEnum(value = AiWriteTypeEnum.class, message = "写作类型必须是 {value}") - private Integer type; - - @Schema(description = "写作内容提示", example = "1.撰写:田忌赛马;2.回复:不批") - private String prompt; - - @Schema(description = "原文", example = "领导我要辞职") - private String originalContent; - - @Schema(description = "长度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "长度不能为空") - private Integer length; - - @Schema(description = "格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "格式不能为空") - private Integer format; - - @Schema(description = "语气", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "语气不能为空") - private Integer tone; - - @Schema(description = "语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "语言不能为空") - private Integer language; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java deleted file mode 100644 index 047380e422..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWritePageReqVO.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.write.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - AI 写作分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class AiWritePageReqVO extends PageParam { - - @Schema(description = "用户编号", example = "28404") - private Long userId; - - @Schema(description = "写作类型", example = "1") - private Integer type; - - @Schema(description = "平台", example = "TongYi") - private String platform; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java deleted file mode 100644 index 4160de9ad2..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/vo/AiWriteRespVO.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.write.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - AI 写作 Response VO") -@Data -public class AiWriteRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5311") - private Long id; - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28404") - private Long userId; - - @Schema(description = "写作类型", example = "1") - private Integer type; - - @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "TongYi") - private String platform; - - @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen") - private String model; - - @Schema(description = "生成内容提示", requiredMode = Schema.RequiredMode.REQUIRED, example = "撰写:田忌赛马") - private String prompt; - - @Schema(description = "生成的内容", example = "你非常不错") - private String generatedContent; - - @Schema(description = "原文", example = "真的么?") - private String originalContent; - - @Schema(description = "长度提示词", example = "1") - private Integer length; - - @Schema(description = "格式提示词", example = "2") - private Integer format; - - @Schema(description = "语气提示词", example = "3") - private Integer tone; - - @Schema(description = "语言提示词", example = "4") - private Integer language; - - @Schema(description = "错误信息") - private String errorMessage; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java deleted file mode 100644 index 05b1ce233c..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/app/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * TODO 芋艿:站位,无特殊作用 - */ -package cn.iocoder.yudao.module.ai.controller.app; \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java deleted file mode 100644 index 68dfddfa3e..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 提供 RESTful API 给前端: - * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 - * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 - */ -package cn.iocoder.yudao.module.ai.controller; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java deleted file mode 100644 index 0b7eb02336..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java +++ /dev/null @@ -1,99 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.chat; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -import java.time.LocalDateTime; - -/** - * AI Chat 对话 DO - * - * 用户每次发起 Chat 聊天时,会创建一个 {@link AiChatConversationDO} 对象,将它的消息关联在一起 - * - * @author fansili - * @since 2024/4/14 17:35 - */ -@TableName("ai_chat_conversation") -@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AiChatConversationDO extends BaseDO { - - public static final String TITLE_DEFAULT = "新对话"; - - /** - * ID 编号,自增 - */ - @TableId - private Long id; - - /** - * 用户编号 - * - * 关联 AdminUserDO 的 userId 字段 - */ - private Long userId; - - /** - * 对话标题 - * - * 默认由系统自动生成,可用户手动修改 - */ - private String title; - /** - * 是否置顶 - */ - private Boolean pinned; - /** - * 置顶时间 - */ - private LocalDateTime pinnedTime; - - /** - * 角色编号 - * - * 关联 {@link AiChatRoleDO#getId()} - */ - private Long roleId; - - /** - * 模型编号 - * - * 关联 {@link AiChatModelDO#getId()} 字段 - */ - private Long modelId; - /** - * 模型标志 - */ - private String model; - - // ========== 对话配置 ========== - - /** - * 角色设定 - */ - private String systemMessage; - /** - * 温度参数 - * - * 用于调整生成回复的随机性和多样性程度:较低的温度值会使输出更收敛于高频词汇,较高的则增加多样性 - */ - private Double temperature; - /** - * 单条回复的最大 Token 数量 - */ - private Integer maxTokens; - /** - * 上下文的最大 Message 数量 - */ - private Integer maxContexts; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java deleted file mode 100644 index 973c593ce3..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.chat; - -import com.baomidou.mybatisplus.annotation.TableId; -import org.springframework.ai.chat.messages.MessageType; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * AI Chat 消息 DO - * - * @since 2024/4/14 17:35 - * @since 2024/4/14 17:35 - */ -@TableName("ai_chat_message") -@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AiChatMessageDO extends BaseDO { - - /** - * 编号,作为每条聊天记录的唯一标识符 - */ - @TableId - private Long id; - - /** - * 对话编号 - * - * 关联 {@link AiChatConversationDO#getId()} 字段 - */ - private Long conversationId; - /** - * 回复消息编号 - * - * 关联 {@link #id} 字段 - * - * 大模型回复的消息编号,用于“问答”的关联 - */ - private Long replyId; - - /** - * 消息类型 - * - * 也等价于 OpenAPI 的 role 字段 - * - * 枚举 {@link MessageType} - */ - private String type; - /** - * 用户编号 - * - * 关联 AdminUserDO 的 userId 字段 - */ - private Long userId; - /** - * 角色编号 - * - * 关联 {@link AiChatRoleDO#getId()} 字段 - */ - private Long roleId; - - /** - * 模型标志 - */ - private String model; - /** - * 模型编号 - * - * 关联 {@link AiChatModelDO#getId()} 字段 - */ - private Long modelId; - - /** - * 聊天内容 - */ - private String content; - - /** - * 是否携带上下文 - */ - private Boolean useContext; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java deleted file mode 100644 index a894caee66..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java +++ /dev/null @@ -1,119 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.image; - -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; -import org.springframework.ai.openai.OpenAiImageOptions; -import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -/** - * AI 绘画 DO - * - * @author fansili - */ -@TableName(value = "ai_image", autoResultMap = true) -@Data -public class AiImageDO extends BaseDO { - - /** - * 编号 - */ - @TableId(type = IdType.AUTO) - private Long id; - - /** - * 用户编号 - * - * 关联 {@link AdminUserRespDTO#getId()} - */ - private Long userId; - - /** - * 提示词 - */ - private String prompt; - - /** - * 平台 - * - * 枚举 {@link cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum} - */ - private String platform; - /** - * 模型 - * - * 冗余 {@link AiChatModelDO#getModel()} - */ - private String model; - - /** - * 图片宽度 - */ - private Integer width; - /** - * 图片高度 - */ - private Integer height; - - /** - * 生成状态 - * - * 枚举 {@link AiImageStatusEnum} - */ - private Integer status; - - /** - * 完成时间 - */ - private LocalDateTime finishTime; - - /** - * 绘画错误信息 - */ - private String errorMessage; - - /** - * 图片地址 - */ - private String picUrl; - /** - * 是否公开 - */ - private Boolean publicStatus; - - /** - * 绘制参数,不同 platform 的不同参数 - * - * 1. {@link OpenAiImageOptions} - * 2. {@link StabilityAiImageOptions} - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private Map options; - - /** - * mj buttons 按钮 - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List buttons; - - /** - * 任务编号 - * - * 1. midjourney proxy:关联的 task id - */ - private String taskId; - -} - diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java deleted file mode 100644 index 0442a52d75..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/mindmap/AiMindMapDO.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.mindmap; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -/** - * AI 思维导图 DO - * - * @author xiaoxin - */ -@TableName(value = "ai_mind_map") -@Data -public class AiMindMapDO extends BaseDO { - - /** - * 编号 - */ - @TableId(type = IdType.AUTO) - private Long id; - - /** - * 用户编号 - *

- * 关联 AdminUserDO 的 userId 字段 - */ - private Long userId; - - /** - * 平台 - *

- * 枚举 {@link AiPlatformEnum} - */ - private String platform; - /** - * 模型 - */ - private String model; - - /** - * 生成内容提示 - */ - private String prompt; - - /** - * 生成的内容 - */ - private String generatedContent; - - /** - * 错误信息 - */ - private String errorMessage; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java deleted file mode 100644 index e251d55c85..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiApiKeyDO.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.model; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * AI API 秘钥 DO - * - * @author 芋道源码 - */ -@TableName("ai_api_key") -@KeySequence("ai_chat_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AiApiKeyDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 名称 - */ - private String name; - /** - * 密钥 - */ - private String apiKey; - /** - * 平台 - * - * 枚举 {@link AiPlatformEnum} - */ - private String platform; - /** - * API 地址 - */ - private String url; - /** - * 状态 - * - * 枚举 {@link CommonStatusEnum} - */ - private Integer status; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java deleted file mode 100644 index 7197f8b58f..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.model; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * AI 聊天模型 DO - * - * 默认聊天模型:{@link #status} 为开启,并且 {@link #sort} 排序第一 - * - * @author fansili - * @since 2024/4/24 19:39 - */ -@TableName("ai_chat_model") -@KeySequence("ai_chat_model_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AiChatModelDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * API 秘钥编号 - * - * 关联 {@link AiApiKeyDO#getId()} - */ - private Long keyId; - /** - * 模型名称 - */ - private String name; - /** - * 模型标志 - */ - private String model; - /** - * 平台 - * - * 枚举 {@link AiPlatformEnum} - */ - private String platform; - - /** - * 排序值 - */ - private Integer sort; - /** - * 状态 - * - * 枚举 {@link CommonStatusEnum} - */ - private Integer status; - - // ========== 对话配置 ========== - - /** - * 温度参数 - * - * 用于调整生成回复的随机性和多样性程度:较低的温度值会使输出更收敛于高频词汇,较高的则增加多样性 - */ - private Double temperature; - /** - * 单条回复的最大 Token 数量 - */ - private Integer maxTokens; - /** - * 上下文的最大 Message 数量 - */ - private Integer maxContexts; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java deleted file mode 100644 index 28f6cda43d..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatRoleDO.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.model; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.*; -import lombok.*; - -/** - * AI 聊天角色 DO - * - * @author fansili - * @since 2024/4/24 19:39 - */ -@TableName(value = "ai_chat_role", autoResultMap = true) -@KeySequence("ai_chat_role_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AiChatRoleDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 角色名称 - */ - private String name; - /** - * 角色头像 - */ - private String avatar; - /** - * 角色分类 - */ - private String category; - /** - * 角色描述 - */ - private String description; - /** - * 角色设定 - */ - private String systemMessage; - - /** - * 用户编号 - * - * 关联 AdminUserDO 的 userId 字段 - */ - private Long userId; - - /** - * 模型编号 - * - * 关联 {@link AiChatModelDO#getId()} 字段 - */ - private Long modelId; - - /** - * 是否公开 - * - * 1. true - 公开;由管理员在【角色管理】所创建 - * 2. false - 私有;由个人在【我的角色】所创建 - */ - private Boolean publicStatus; - - /** - * 排序值 - */ - private Integer sort; - /** - * 状态 - * - * 枚举 {@link CommonStatusEnum} - */ - private Integer status; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java deleted file mode 100644 index 8a6cbe8288..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java +++ /dev/null @@ -1,117 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.music; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; -import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import lombok.Data; - -import java.util.List; - -/** - * AI 音乐 DO - * - * @author xiaoxin - */ -@TableName(value = "ai_music", autoResultMap = true) -@Data -public class AiMusicDO extends BaseDO { - - /** - * 编号 - */ - @TableId(type = IdType.AUTO) - private Long id; - - /** - * 用户编号 - *

- * 关联 AdminUserDO 的 userId 字段 - */ - private Long userId; - - /** - * 音乐名称 - */ - private String title; - - /** - * 歌词 - */ - private String lyric; - - /** - * 图片地址 - */ - private String imageUrl; - /** - * 音频地址 - */ - private String audioUrl; - /** - * 视频地址 - */ - private String videoUrl; - - /** - * 音乐状态 - *

- * 枚举 {@link AiMusicStatusEnum} - */ - private Integer status; - - /** - * 生成模式 - *

- * 枚举 {@link AiMusicGenerateModeEnum} - */ - private Integer generateMode; - - /** - * 描述词 - */ - private String description; - - /** - * 平台 - *

- * 枚举 {@link AiPlatformEnum} - */ - private String platform; - /** - * 模型 - */ - private String model; - - /** - * 音乐风格标签 - */ - @TableField(typeHandler = JacksonTypeHandler.class) - private List tags; - - /** - * 音乐时长 - */ - private Double duration; - - /** - * 是否公开 - */ - private Boolean publicStatus; - - /** - * 任务编号 - */ - private String taskId; - - /** - * 错误信息 - */ - private String errorMessage; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java deleted file mode 100644 index 752876f2a6..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/write/AiWriteDO.java +++ /dev/null @@ -1,95 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.dataobject.write; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -/** - * AI 写作 DO - * - * @author xiaoxin - */ -@TableName("ai_write") -@Data -public class AiWriteDO extends BaseDO { - - /** - * 编号 - */ - @TableId(type = IdType.AUTO) - private Long id; - - /** - * 用户编号 - * - * 关联 AdminUserDO 的 userId 字段 - */ - private Long userId; - - /** - * 写作类型 - *

- * 枚举 {@link AiWriteTypeEnum} - */ - private Integer type; - - /** - * 平台 - * - * 枚举 {@link AiPlatformEnum} - */ - private String platform; - /** - * 模型 - */ - private String model; - - /** - * 生成内容提示 - */ - private String prompt; - - /** - * 生成的内容 - */ - private String generatedContent; - /** - * 原文 - */ - private String originalContent; - - /** - * 长度提示词 - * - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LENGTH} - */ - private Integer length; - /** - * 格式提示词 - * - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_FORMAT} - */ - private Integer format; - /** - * 语气提示词 - * - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_TONE} - */ - private Integer tone; - /** - * 语言提示词 - * - * 字典:{@link cn.iocoder.yudao.module.ai.enums.DictTypeConstants#AI_WRITE_LANGUAGE} - */ - private Integer language; - - /** - * 错误信息 - */ - private String errorMessage; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java deleted file mode 100644 index ce9da2f24d..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.chat; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -/** - * AI 聊天对话 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface AiChatConversationMapper extends BaseMapperX { - - default List selectListByUserId(Long userId) { - return selectList(AiChatConversationDO::getUserId, userId); - } - - default List selectListByUserIdAndPinned(Long userId, boolean pinned) { - return selectList(new LambdaQueryWrapperX() - .eq(AiChatConversationDO::getUserId, userId) - .eq(AiChatConversationDO::getPinned, pinned)); - } - - default PageResult selectChatConversationPage(AiChatConversationPageReqVO pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eqIfPresent(AiChatConversationDO::getUserId, pageReqVO.getUserId()) - .likeIfPresent(AiChatConversationDO::getTitle, pageReqVO.getTitle()) - .betweenIfPresent(AiChatConversationDO::getCreateTime, pageReqVO.getCreateTime()) - .orderByDesc(AiChatConversationDO::getId)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java deleted file mode 100644 index 5020f3944e..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.chat; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import org.apache.ibatis.annotations.Mapper; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * AI 聊天对话 Mapper - * - * @author fansili - */ -@Mapper -public interface AiChatMessageMapper extends BaseMapperX { - - default List selectListByConversationId(Long conversationId) { - return selectList(new LambdaQueryWrapperX() - .eq(AiChatMessageDO::getConversationId, conversationId) - .orderByAsc(AiChatMessageDO::getId)); - } - - default Map selectCountMapByConversationId(Collection conversationIds) { - // SQL count 查询 - List> result = selectMaps(new QueryWrapper() - .select("COUNT(id) AS count, conversation_id AS conversationId") - .in("conversation_id", conversationIds) - .groupBy("conversation_id")); - if (CollUtil.isEmpty(result)) { - return Collections.emptyMap(); - } - // 转换数据 - return CollectionUtils.convertMap(result, - record -> MapUtil.getLong(record, "conversationId"), - record -> MapUtil.getInt(record, "count" )); - } - - default PageResult selectPage(AiChatMessagePageReqVO pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eqIfPresent(AiChatMessageDO::getConversationId, pageReqVO.getConversationId()) - .eqIfPresent(AiChatMessageDO::getUserId, pageReqVO.getUserId()) - .likeIfPresent(AiChatMessageDO::getContent, pageReqVO.getContent()) - .betweenIfPresent(AiChatMessageDO::getCreateTime, pageReqVO.getCreateTime()) - .orderByDesc(AiChatMessageDO::getId)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java deleted file mode 100644 index fd6e4b398c..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.image; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -/** - * AI 绘图 Mapper - * - * @author fansili - */ -@Mapper -public interface AiImageMapper extends BaseMapperX { - - default AiImageDO selectByTaskId(String taskId) { - return this.selectOne(AiImageDO::getTaskId, taskId); - } - - default PageResult selectPage(AiImagePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(AiImageDO::getUserId, reqVO.getUserId()) - .eqIfPresent(AiImageDO::getPlatform, reqVO.getPlatform()) - .eqIfPresent(AiImageDO::getStatus, reqVO.getStatus()) - .eqIfPresent(AiImageDO::getPublicStatus, reqVO.getPublicStatus()) - .betweenIfPresent(AiImageDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(AiImageDO::getId)); - } - - default PageResult selectPage(Long userId, PageParam pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eq(AiImageDO::getUserId, userId) - .orderByDesc(AiImageDO::getId)); - } - - default List selectListByStatusAndPlatform(Integer status, String platform) { - return selectList(AiImageDO::getStatus, status, - AiImageDO::getPlatform, platform); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java deleted file mode 100644 index ff25e89ffe..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/mindmap/AiMindMapMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.mindmap; - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * AI 思维导图 Mapper - * - * @author xiaoxin - */ -@Mapper -public interface AiMindMapMapper extends BaseMapperX { -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java deleted file mode 100644 index 0a2efe36f8..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiApiKeyMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.model; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * AI API 密钥 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface AiApiKeyMapper extends BaseMapperX { - - default PageResult selectPage(AiApiKeyPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiApiKeyDO::getName, reqVO.getName()) - .eqIfPresent(AiApiKeyDO::getPlatform, reqVO.getPlatform()) - .eqIfPresent(AiApiKeyDO::getStatus, reqVO.getStatus()) - .orderByDesc(AiApiKeyDO::getId)); - } - - default AiApiKeyDO selectFirstByPlatformAndStatus(String platform, Integer status) { - return selectOne(new QueryWrapperX() - .eq("platform", platform) - .eq("status", status) - .limitN(1) - .orderByAsc("id")); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatModelMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatModelMapper.java deleted file mode 100644 index a3136fa9f6..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatModelMapper.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.model; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.Collection; -import java.util.List; - -/** - * API 聊天模型 Mapper - * - * @author fansili - */ -@Mapper -public interface AiChatModelMapper extends BaseMapperX { - - default AiChatModelDO selectFirstByStatus(Integer status) { - return selectOne(new QueryWrapperX() - .eq("status", status) - .limitN(1) - .orderByAsc("sort")); - } - - default PageResult selectPage(AiChatModelPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiChatModelDO::getName, reqVO.getName()) - .eqIfPresent(AiChatModelDO::getModel, reqVO.getModel()) - .eqIfPresent(AiChatModelDO::getPlatform, reqVO.getPlatform()) - .orderByAsc(AiChatModelDO::getSort)); - } - - default List selectList(Integer status) { - return selectList(new LambdaQueryWrapperX() - .eq(AiChatModelDO::getStatus, status) - .orderByAsc(AiChatModelDO::getSort)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java deleted file mode 100644 index ed91edf3fa..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/model/AiChatRoleMapper.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.model; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -/** - * AI 聊天角色 Mapper - * - * @author fansili - */ -@Mapper -public interface AiChatRoleMapper extends BaseMapperX { - - default PageResult selectPage(AiChatRolePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiChatRoleDO::getName, reqVO.getName()) - .eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory()) - .eqIfPresent(AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus()) - .orderByAsc(AiChatRoleDO::getSort)); - } - - default PageResult selectPageByMy(AiChatRolePageReqVO reqVO, Long userId) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AiChatRoleDO::getName, reqVO.getName()) - .eqIfPresent(AiChatRoleDO::getCategory, reqVO.getCategory()) - // 情况一:公开 - .eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiChatRoleDO::getPublicStatus, reqVO.getPublicStatus()) - // 情况二:私有 - .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiChatRoleDO::getUserId, userId) - .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiChatRoleDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) - .orderByAsc(AiChatRoleDO::getSort)); - } - - default List selectListGroupByCategory(Integer status) { - return selectList(new LambdaQueryWrapperX() - .select(AiChatRoleDO::getCategory) - .eq(AiChatRoleDO::getStatus, status) - .groupBy(AiChatRoleDO::getCategory)); - } - - default List selectListByName(String name) { - return selectList(new LambdaQueryWrapperX() - .likeIfPresent(AiChatRoleDO::getName, name) - .orderByAsc(AiChatRoleDO::getSort)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java deleted file mode 100644 index 025f5e0185..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.music; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -/** - * AI 音乐 Mapper - * - * @author xiaoxin - */ -@Mapper -public interface AiMusicMapper extends BaseMapperX { - - default List selectListByStatus(Integer status) { - return selectList(AiMusicDO::getStatus, status); - } - - default PageResult selectPage(AiMusicPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(AiMusicDO::getUserId, reqVO.getUserId()) - .eqIfPresent(AiMusicDO::getTitle, reqVO.getTitle()) - .eqIfPresent(AiMusicDO::getStatus, reqVO.getStatus()) - .eqIfPresent(AiMusicDO::getGenerateMode, reqVO.getGenerateMode()) - .betweenIfPresent(AiMusicDO::getCreateTime, reqVO.getCreateTime()) - .eqIfPresent(AiMusicDO::getPublicStatus, reqVO.getPublicStatus()) - .orderByDesc(AiMusicDO::getId)); - } - - default PageResult selectPageByMy(AiMusicPageReqVO reqVO, Long userId) { - return selectPage(reqVO, new LambdaQueryWrapperX() - // 情况一:公开 - .eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiMusicDO::getPublicStatus, reqVO.getPublicStatus()) - // 情况二:私有 - .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiMusicDO::getUserId, userId) - .orderByAsc(AiMusicDO::getId)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java deleted file mode 100644 index c4983eb444..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/write/AiWriteMapper.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql.write; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * AI 写作 Mapper - * - * @author xiaoxin - */ -@Mapper -public interface AiWriteMapper extends BaseMapperX { - - default PageResult selectPage(AiWritePageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(AiWriteDO::getUserId, reqVO.getUserId()) - .eqIfPresent(AiWriteDO::getType, reqVO.getType()) - .eqIfPresent(AiWriteDO::getPlatform, reqVO.getPlatform()) - .betweenIfPresent(AiWriteDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(AiWriteDO::getId)); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/package-info.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/package-info.java deleted file mode 100644 index 6a619234cb..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * 属于 ai 模块的 framework 封装 - * - * @author 芋道源码 - */ -package cn.iocoder.yudao.module.crm.framework; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/config/AiWebConfiguration.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/config/AiWebConfiguration.java deleted file mode 100644 index 2eb265d588..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/config/AiWebConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.ai.framework.web.config; - -import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; -import org.springdoc.core.models.GroupedOpenApi; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * ai 模块的 web 组件的 Configuration - * - * @author 芋道源码 - */ -@Configuration(proxyBeanMethods = false) -public class AiWebConfiguration { - - /** - * ai 模块的 API 分组 - */ - @Bean - public GroupedOpenApi aiGroupedOpenApi() { - return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("ai"); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java deleted file mode 100644 index 09de7263c5..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/framework/web/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * crm 模块的 web 拓展封装 - */ -package cn.iocoder.yudao.module.crm.framework.web; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/image/AiMidjourneySyncJob.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/image/AiMidjourneySyncJob.java deleted file mode 100644 index 822e1e401e..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/image/AiMidjourneySyncJob.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.ai.job.image; - -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.module.ai.service.image.AiImageService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -/** - * Midjourney 同步 Job:定时拉去 midjourney 绘制状态 - * - * @author fansili - */ -@Component -@Slf4j -public class AiMidjourneySyncJob implements JobHandler { - - @Resource - private AiImageService imageService; - - @Override - public String execute(String param) { - Integer count = imageService.midjourneySync(); - log.info("[execute][同步 Midjourney ({}) 个]", count); - return String.format("同步 Midjourney %s 个", count); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/music/AiSunoSyncJob.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/music/AiSunoSyncJob.java deleted file mode 100644 index 6d85ec27f2..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/music/AiSunoSyncJob.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.ai.job.music; - -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.module.ai.service.music.AiMusicService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - - -/** - * 同步 Suno 任务状态以及回写对应的音乐信息 Job - * - * @author xiaoxin - */ -@Component -@Slf4j -public class AiSunoSyncJob implements JobHandler { - - @Resource - private AiMusicService musicService; - - @Override - public String execute(String param) { - Integer count = musicService.syncMusic(); - log.info("[execute][同步 Suno ({}) 个]", count); - return String.format("同步 Suno %s 个", count); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java deleted file mode 100644 index bce6d435dd..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.chat; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; - -import java.util.List; - -/** - * AI 聊天对话 Service 接口 - * - * @author fansili - */ -public interface AiChatConversationService { - - /** - * 创建【我的】聊天对话 - * - * @param createReqVO 创建信息 - * @param userId 用户编号 - * @return 编号 - */ - Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId); - - /** - * 更新【我的】聊天对话 - * - * @param updateReqVO 更新信息 - * @param userId 用户编号 - */ - void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId); - - /** - * 获得【我的】聊天对话列表 - * - * @param userId 用户编号 - * @return 聊天对话列表 - */ - List getChatConversationListByUserId(Long userId); - - /** - * 获得聊天对话 - * - * @param id 编号 - * @return 聊天对话 - */ - AiChatConversationDO getChatConversation(Long id); - - /** - * 删除【我的】聊天对话 - * - * @param id 编号 - * @param userId 用户编号 - */ - void deleteChatConversationMy(Long id, Long userId); - - /** - * 【管理员】删除聊天对话 - * - * @param id 编号 - */ - void deleteChatConversationByAdmin(Long id); - - /** - * 校验聊天对话是否存在 - * - * @param id 编号 - * @return 聊天对话 - */ - AiChatConversationDO validateChatConversationExists(Long id); - - /** - * 删除【我的】 + 非置顶的聊天对话 - * - * @param userId 用户编号 - */ - void deleteChatConversationMyByUnpinned(Long userId); - - /** - * 获得聊天对话的分页列表 - * - * @param pageReqVO 分页查询 - * @return 聊天对话的分页列表 - */ - PageResult getChatConversationPage(AiChatConversationPageReqVO pageReqVO); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java deleted file mode 100644 index 83dcd8dff7..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java +++ /dev/null @@ -1,157 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.chat; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; -import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.time.LocalDateTime; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_MODEL_ERROR; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; - -/** - * AI 聊天对话 Service 实现类 - * - * @author fansili - */ -@Service -@Validated -@Slf4j -public class AiChatConversationServiceImpl implements AiChatConversationService { - - @Resource - private AiChatConversationMapper chatConversationMapper; - - @Resource - private AiChatModelService chatModalService; - @Resource - private AiChatRoleService chatRoleService; - - @Override - public Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId) { - // 1.1 获得 AiChatRoleDO 聊天角色 - AiChatRoleDO role = createReqVO.getRoleId() != null ? chatRoleService.validateChatRole(createReqVO.getRoleId()) : null; - // 1.2 获得 AiChatModelDO 聊天模型 - AiChatModelDO model = role != null && role.getModelId() != null ? chatModalService.validateChatModel(role.getModelId()) - : chatModalService.getRequiredDefaultChatModel(); - Assert.notNull(model, "必须找到默认模型"); - validateChatModel(model); - - // 2. 创建 AiChatConversationDO 聊天对话 - AiChatConversationDO conversation = new AiChatConversationDO().setUserId(userId).setPinned(false) - .setModelId(model.getId()).setModel(model.getModel()) - .setTemperature(model.getTemperature()).setMaxTokens(model.getMaxTokens()).setMaxContexts(model.getMaxContexts()); - if (role != null) { - conversation.setTitle(role.getName()).setRoleId(role.getId()).setSystemMessage(role.getSystemMessage()); - } else { - conversation.setTitle(AiChatConversationDO.TITLE_DEFAULT); - } - chatConversationMapper.insert(conversation); - return conversation.getId(); - } - - @Override - public void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId) { - // 1.1 校验对话是否存在 - AiChatConversationDO conversation = validateChatConversationExists(updateReqVO.getId()); - if (ObjUtil.notEqual(conversation.getUserId(), userId)) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } - // 1.2 校验模型是否存在(修改模型的情况) - AiChatModelDO model = null; - if (updateReqVO.getModelId() != null) { - model = chatModalService.validateChatModel(updateReqVO.getModelId()); - } - - // 2. 更新对话信息 - AiChatConversationDO updateObj = BeanUtils.toBean(updateReqVO, AiChatConversationDO.class); - if (Boolean.TRUE.equals(updateReqVO.getPinned())) { - updateObj.setPinnedTime(LocalDateTime.now()); - } - if (model != null) { - updateObj.setModel(model.getModel()); - } - chatConversationMapper.updateById(updateObj); - } - - @Override - public List getChatConversationListByUserId(Long userId) { - return chatConversationMapper.selectListByUserId(userId); - } - - @Override - public AiChatConversationDO getChatConversation(Long id) { - return chatConversationMapper.selectById(id); - } - - @Override - public void deleteChatConversationMy(Long id, Long userId) { - // 1. 校验对话是否存在 - AiChatConversationDO conversation = validateChatConversationExists(id); - if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), userId)) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } - // 2. 执行删除 - chatConversationMapper.deleteById(id); - } - - @Override - public void deleteChatConversationByAdmin(Long id) { - // 1. 校验对话是否存在 - AiChatConversationDO conversation = validateChatConversationExists(id); - if (conversation == null) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } - // 2. 执行删除 - chatConversationMapper.deleteById(id); - } - - private void validateChatModel(AiChatModelDO model) { - if (ObjectUtil.isAllNotEmpty(model.getTemperature(), model.getMaxTokens(), model.getMaxContexts())) { - return; - } - throw exception(CHAT_CONVERSATION_MODEL_ERROR); - } - - public AiChatConversationDO validateChatConversationExists(Long id) { - AiChatConversationDO conversation = chatConversationMapper.selectById(id); - if (conversation == null) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } - return conversation; - } - - @Override - public void deleteChatConversationMyByUnpinned(Long userId) { - List list = chatConversationMapper.selectListByUserIdAndPinned(userId, false); - if (CollUtil.isEmpty(list)) { - return; - } - chatConversationMapper.deleteBatchIds(convertList(list, AiChatConversationDO::getId)); - } - - @Override - public PageResult getChatConversationPage(AiChatConversationPageReqVO pageReqVO) { - return chatConversationMapper.selectChatConversationPage(pageReqVO); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java deleted file mode 100644 index f572bddd97..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java +++ /dev/null @@ -1,87 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.chat; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import reactor.core.publisher.Flux; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * AI 聊天消息 Service 接口 - * - * @author fansili - */ -public interface AiChatMessageService { - - /** - * 发送消息 - * - * @param sendReqVO 发送信息 - * @param userId 用户编号 - * @return 发送结果 - */ - AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId); - - /** - * 发送消息 - * - * @param sendReqVO 发送信息 - * @param userId 用户编号 - * @return 发送结果 - */ - Flux> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId); - - /** - * 获得指定对话的消息列表 - * - * @param conversationId 对话编号 - * @return 消息列表 - */ - List getChatMessageListByConversationId(Long conversationId); - - /** - * 删除消息 - * - * @param id 消息编号 - * @param userId 用户编号 - */ - void deleteChatMessage(Long id, Long userId); - - /** - * 删除指定对话的消息 - * - * @param conversationId 对话编号 - * @param userId 用户编号 - */ - void deleteChatMessageByConversationId(Long conversationId, Long userId); - - /** - * 【管理员】删除消息 - * - * @param id 消息编号 - */ - void deleteChatMessageByAdmin(Long id); - - /** - * 获得聊天对话的消息数量 Map - * - * @param conversationIds 对话编号数组 - * @return 消息数量 Map - */ - Map getChatMessageCountMap(Collection conversationIds); - - /** - * 获得聊天消息的分页 - * - * @param pageReqVO 分页查询 - * @return 聊天消息的分页 - */ - PageResult getChatMessagePage(AiChatMessagePageReqVO pageReqVO); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java deleted file mode 100644 index 72fa06a79c..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ /dev/null @@ -1,259 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.chat; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.util.AiUtils; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; -import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.messages.*; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import reactor.core.publisher.Flux; - -import java.time.LocalDateTime; -import java.util.*; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_NOT_EXIST; - -/** - * AI 聊天消息 Service 实现类 - * - * @author fansili - */ -@Service -@Slf4j -public class AiChatMessageServiceImpl implements AiChatMessageService { - - @Resource - private AiChatMessageMapper chatMessageMapper; - - @Resource - private AiChatConversationService chatConversationService; - @Resource - private AiChatModelService chatModalService; - @Resource - private AiApiKeyService apiKeyService; - - @Transactional(rollbackFor = Exception.class) - public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { - // 1.1 校验对话存在 - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); - if (ObjUtil.notEqual(conversation.getUserId(), userId)) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } - List historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); - // 1.2 校验模型 - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); - ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); - - // 2. 插入 user 发送消息 - AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); - - // 3.1 插入 assistant 接收消息 - AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); - - // 3.2 创建 chat 需要的 Prompt - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); - ChatResponse chatResponse = chatModel.call(prompt); - - // 3.3 段式返回 - String newContent = chatResponse.getResult().getOutput().getContent(); - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(newContent)); - return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent)); - } - - @Override - public Flux> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { - // 1.1 校验对话存在 - AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); - if (ObjUtil.notEqual(conversation.getUserId(), userId)) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); - } - List historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); - // 1.2 校验模型 - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); - - // 2. 插入 user 发送消息 - AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); - - // 3.1 插入 assistant 接收消息 - AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); - - // 3.2 构建 Prompt,并进行调用 - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); - Flux streamResponse = chatModel.stream(prompt); - - // 3.3 流式返回 - // TODO 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 - StringBuffer contentBuffer = new StringBuffer(); - return streamResponse.map(chunk -> { - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; - newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 - contentBuffer.append(newContent); - // 响应结果 - return success(new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent))); - }).doOnComplete(() -> { - // 忽略租户,因为 Flux 异步无法透传租户 - TenantUtils.executeIgnore(() -> - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()))); - }).doOnError(throwable -> { - log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); - // 忽略租户,因为 Flux 异步无法透传租户 - TenantUtils.executeIgnore(() -> - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage()))); - }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.CHAT_STREAM_ERROR))); - } - - private Prompt buildPrompt(AiChatConversationDO conversation, List messages, - AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { - // 1. 构建 Prompt Message 列表 - List chatMessages = new ArrayList<>(); - // 1.1 system context 角色设定 - if (StrUtil.isNotBlank(conversation.getSystemMessage())) { - chatMessages.add(new SystemMessage(conversation.getSystemMessage())); - } - // 1.2 history message 历史消息 - List contextMessages = filterContextMessages(messages, conversation, sendReqVO); - contextMessages.forEach(message -> chatMessages.add(AiUtils.buildMessage(message.getType(), message.getContent()))); - // 1.3 user message 新发送消息 - chatMessages.add(new UserMessage(sendReqVO.getContent())); - - // 2. 构建 ChatOptions 对象 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), - conversation.getTemperature(), conversation.getMaxTokens()); - return new Prompt(chatMessages, chatOptions); - } - - /** - * 从历史消息中,获得倒序的 n 组消息作为消息上下文 - * - * n 组:指的是 user + assistant 形成一组 - * - * @param messages 消息列表 - * @param conversation 对话 - * @param sendReqVO 发送请求 - * @return 消息上下文 - */ - private List filterContextMessages(List messages, - AiChatConversationDO conversation, - AiChatMessageSendReqVO sendReqVO) { - if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { - return Collections.emptyList(); - } - List contextMessages = new ArrayList<>(conversation.getMaxContexts() * 2); - for (int i = messages.size() - 1; i >= 0; i--) { - AiChatMessageDO assistantMessage = CollUtil.get(messages, i); - if (assistantMessage == null || assistantMessage.getReplyId() == null) { - continue; - } - AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); - if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) - || StrUtil.isEmpty(assistantMessage.getContent())) { - continue; - } - // 由于后续要 reverse 反转,所以先添加 assistantMessage - contextMessages.add(assistantMessage); - contextMessages.add(userMessage); - // 超过最大上下文,结束 - if (contextMessages.size() >= conversation.getMaxContexts() * 2) { - break; - } - } - Collections.reverse(contextMessages); - return contextMessages; - } - - private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, - AiChatModelDO model, Long userId, Long roleId, - MessageType messageType, String content, Boolean useContext) { - AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) - .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) - .setType(messageType.getValue()).setContent(content).setUseContext(useContext); - message.setCreateTime(LocalDateTime.now()); - chatMessageMapper.insert(message); - return message; - } - - @Override - public List getChatMessageListByConversationId(Long conversationId) { - return chatMessageMapper.selectListByConversationId(conversationId); - } - - @Override - public void deleteChatMessage(Long id, Long userId) { - // 1. 校验消息存在 - AiChatMessageDO message = chatMessageMapper.selectById(id); - if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) { - throw exception(CHAT_MESSAGE_NOT_EXIST); - } - // 2. 执行删除 - chatMessageMapper.deleteById(id); - } - - @Override - public void deleteChatMessageByConversationId(Long conversationId, Long userId) { - // 1. 校验消息存在 - List messages = chatMessageMapper.selectListByConversationId(conversationId); - if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { - throw exception(CHAT_MESSAGE_NOT_EXIST); - } - // 2. 执行删除 - chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); - } - - @Override - public void deleteChatMessageByAdmin(Long id) { - // 1. 校验消息存在 - AiChatMessageDO message = chatMessageMapper.selectById(id); - if (message == null) { - throw exception(CHAT_MESSAGE_NOT_EXIST); - } - // 2. 执行删除 - chatMessageMapper.deleteById(id); - } - - @Override - public Map getChatMessageCountMap(Collection conversationIds) { - return chatMessageMapper.selectCountMapByConversationId(conversationIds); - } - - @Override - public PageResult getChatMessagePage(AiChatMessagePageReqVO pageReqVO) { - return chatMessageMapper.selectPage(pageReqVO); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java deleted file mode 100644 index 716c7ea8af..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java +++ /dev/null @@ -1,121 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.image; - -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; -import jakarta.validation.Valid; - -import java.util.List; - -/** - * AI 绘图 Service 接口 - * - * @author fansili - */ -public interface AiImageService { - - /** - * 获取【我的】绘图分页 - * - * @param userId 用户编号 - * @param pageReqVO 分页条件 - * @return 绘图分页 - */ - PageResult getImagePageMy(Long userId, PageParam pageReqVO); - - /** - * 获得绘图记录 - * - * @param id 绘图编号 - * @return 绘图记录 - */ - AiImageDO getImage(Long id); - - /** - * 获得绘图列表 - * - * @param ids 绘图编号数组 - * @return 绘图记录列表 - */ - List getImageList(List ids); - - /** - * 绘制图片 - * - * @param userId 用户编号 - * @param drawReqVO 绘制请求 - * @return 绘画编号 - */ - Long drawImage(Long userId, AiImageDrawReqVO drawReqVO); - - /** - * 删除【我的】绘画记录 - * - * @param id 绘画编号 - * @param userId 用户编号 - */ - void deleteImageMy(Long id, Long userId); - - /** - * 获得绘画分页 - * - * @param pageReqVO 分页查询 - * @return 绘画分页 - */ - PageResult getImagePage(AiImagePageReqVO pageReqVO); - - /** - * 更新绘画 - * - * @param updateReqVO 更新信息 - */ - void updateImage(@Valid AiImageUpdateReqVO updateReqVO); - - /** - * 删除绘画 - * - * @param id 编号 - */ - void deleteImage(Long id); - - // ================ midjourney 专属 ================ - - /** - * 【Midjourney】生成图片 - * - * @param userId 用户编号 - * @param reqVO 绘制请求 - * @return 绘画编号 - */ - Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO); - - /** - * 【Midjourney】同步图片进展 - * - * @return 同步成功数量 - */ - Integer midjourneySync(); - - /** - * 【Midjourney】通知图片进展 - * - * @param notify 通知 - */ - void midjourneyNotify(MidjourneyApi.Notify notify); - - /** - * 【Midjourney】Action 操作(放大、缩小、U1、U2...) - * - * @param userId 用户编号 - * @param reqVO 绘制请求 - * @return 绘画编号 - */ - Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java deleted file mode 100644 index 3a8ff83460..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java +++ /dev/null @@ -1,350 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.image; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.hutool.http.HttpUtil; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageUpdateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; -import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; -import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.infra.api.file.FileApi; -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesOptions; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.image.ImageOptions; -import org.springframework.ai.image.ImagePrompt; -import org.springframework.ai.image.ImageResponse; -import org.springframework.ai.openai.OpenAiImageOptions; -import org.springframework.ai.qianfan.QianFanImageOptions; -import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; - -/** - * AI 绘画 Service 实现类 - * - * @author fansili - */ -@Service -@Slf4j -public class AiImageServiceImpl implements AiImageService { - - @Resource - private AiImageMapper imageMapper; - - @Resource - private FileApi fileApi; - - @Resource - private AiApiKeyService apiKeyService; - - @Override - public PageResult getImagePageMy(Long userId, PageParam pageReqVO) { - return imageMapper.selectPage(userId, pageReqVO); - } - - @Override - public AiImageDO getImage(Long id) { - return imageMapper.selectById(id); - } - - @Override - public List getImageList(List ids) { - if (CollUtil.isEmpty(ids)) { - return Collections.emptyList(); - } - return imageMapper.selectBatchIds(ids); - } - - @Override - public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) { - // 1. 保存数据库 - AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) - .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); - imageMapper.insert(image); - // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果 - getSelf().executeDrawImage(image, drawReqVO); - return image.getId(); - } - - @Async - public void executeDrawImage(AiImageDO image, AiImageDrawReqVO req) { - try { - // 1.1 构建请求 - ImageOptions request = buildImageOptions(req); - // 1.2 执行请求 - ImageModel imageModel = apiKeyService.getImageModel(AiPlatformEnum.validatePlatform(req.getPlatform())); - ImageResponse response = imageModel.call(new ImagePrompt(req.getPrompt(), request)); - - // 2. 上传到文件服务 - String b64Json = response.getResult().getOutput().getB64Json(); - byte[] fileContent = StrUtil.isNotEmpty(b64Json) ? Base64.decode(b64Json) - : HttpUtil.downloadBytes(response.getResult().getOutput().getUrl()); - String filePath = fileApi.createFile(fileContent); - - // 3. 更新数据库 - imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(AiImageStatusEnum.SUCCESS.getStatus()) - .setPicUrl(filePath).setFinishTime(LocalDateTime.now())); - } catch (Exception ex) { - log.error("[doDall][image({}) 生成异常]", image, ex); - imageMapper.updateById(new AiImageDO().setId(image.getId()) - .setStatus(AiImageStatusEnum.FAIL.getStatus()) - .setErrorMessage(ex.getMessage()).setFinishTime(LocalDateTime.now())); - } - } - - private static ImageOptions buildImageOptions(AiImageDrawReqVO draw) { - if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) { - // https://platform.openai.com/docs/api-reference/images/create - return OpenAiImageOptions.builder().withModel(draw.getModel()) - .withHeight(draw.getHeight()).withWidth(draw.getWidth()) - .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格 - .withResponseFormat("b64_json") - .build(); - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { - // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage - // https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage - return StabilityAiImageOptions.builder().withModel(draw.getModel()) - .withHeight(draw.getHeight()).withWidth(draw.getWidth()) - .withSeed(Long.valueOf(draw.getOptions().get("seed"))) - .withCfgScale(Float.valueOf(draw.getOptions().get("scale"))) - .withSteps(Integer.valueOf(draw.getOptions().get("steps"))) - .withSampler(String.valueOf(draw.getOptions().get("sampler"))) - .withStylePreset(String.valueOf(draw.getOptions().get("stylePreset"))) - .withClipGuidancePreset(String.valueOf(draw.getOptions().get("clipGuidancePreset"))) - .build(); - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) { - return TongYiImagesOptions.builder() - .withModel(draw.getModel()).withN(1) - .withHeight(draw.getHeight()).withWidth(draw.getWidth()) - .build(); - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) { - return QianFanImageOptions.builder() - .withModel(draw.getModel()).withN(1) - .withHeight(draw.getHeight()).withWidth(draw.getWidth()) - .build(); - } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) { - return ZhiPuAiImageOptions.builder() - .withModel(draw.getModel()) - .build(); - } - throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform()); - } - - @Override - public void deleteImageMy(Long id, Long userId) { - // 1. 校验是否存在 - AiImageDO image = validateImageExists(id); - if (ObjUtil.notEqual(image.getUserId(), userId)) { - throw exception(IMAGE_NOT_EXISTS); - } - // 2. 删除记录 - imageMapper.deleteById(id); - } - - @Override - public PageResult getImagePage(AiImagePageReqVO pageReqVO) { - return imageMapper.selectPage(pageReqVO); - } - - @Override - public void updateImage(AiImageUpdateReqVO updateReqVO) { - // 1. 校验存在 - validateImageExists(updateReqVO.getId()); - // 2. 更新发布状态 - imageMapper.updateById(BeanUtils.toBean(updateReqVO, AiImageDO.class)); - } - - @Override - public void deleteImage(Long id) { - // 1. 校验存在 - validateImageExists(id); - // 2. 删除 - imageMapper.deleteById(id); - } - - private AiImageDO validateImageExists(Long id) { - AiImageDO image = imageMapper.selectById(id); - if (image == null) { - throw exception(IMAGE_NOT_EXISTS); - } - return image; - } - - // ================ midjourney 专属 ================ - - @Override - @Transactional(rollbackFor = Exception.class) - public Long midjourneyImagine(Long userId, AiMidjourneyImagineReqVO reqVO) { - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi(); - // 1. 保存数据库 - AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) - .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) - .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()); - imageMapper.insert(image); - - // 2. 调用 Midjourney Proxy 提交任务 - List base64Array = StrUtil.isBlank(reqVO.getReferImageUrl()) ? null : - Collections.singletonList("data:image/jpeg;base64,".concat(Base64.encode(HttpUtil.downloadBytes(reqVO.getReferImageUrl())))); - MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest( - base64Array, reqVO.getPrompt(),null, - MidjourneyApi.ImagineRequest.buildState(reqVO.getWidth(), - reqVO.getHeight(), reqVO.getVersion(), reqVO.getModel())); - MidjourneyApi.SubmitResponse imagineResponse = midjourneyApi.imagine(imagineRequest); - - // 3. 情况一【失败】:抛出业务异常 - if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) { - String description = imagineResponse.description().contains("quota_not_enough") ? - "账户余额不足" : imagineResponse.description(); - throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); - } - - // 4. 情况二【成功】:更新 taskId 和参数 - imageMapper.updateById(new AiImageDO().setId(image.getId()) - .setTaskId(imagineResponse.result()).setOptions(BeanUtil.beanToMap(reqVO))); - return image.getId(); - } - - @Override - public Integer midjourneySync() { - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi(); - // 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image - List imageList = imageMapper.selectListByStatusAndPlatform( - AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform()); - if (CollUtil.isEmpty(imageList)) { - return 0; - } - // 1.2 调用 Midjourney Proxy 获取任务进展 - List taskList = midjourneyApi.getTaskList(convertSet(imageList, AiImageDO::getTaskId)); - Map taskMap = convertMap(taskList, MidjourneyApi.Notify::id); - - // 2. 逐个处理,更新进展 - int count = 0; - for (AiImageDO image : imageList) { - MidjourneyApi.Notify notify = taskMap.get(image.getTaskId()); - if (notify == null) { - log.error("[midjourneySync][image({}) 查询不到进展]", image); - continue; - } - count++; - updateMidjourneyStatus(image, notify); - } - return count; - } - - @Override - public void midjourneyNotify(MidjourneyApi.Notify notify) { - // 1. 校验 image 存在 - AiImageDO image = imageMapper.selectByTaskId(notify.id()); - if (image == null) { - log.warn("[midjourneyNotify][回调任务({}) 不存在]", notify.id()); - return; - } - // 2. 更新状态 - updateMidjourneyStatus(image, notify); - } - - private void updateMidjourneyStatus(AiImageDO image, MidjourneyApi.Notify notify) { - // 1. 转换状态 - Integer status = null; - LocalDateTime finishTime = null; - if (StrUtil.isNotBlank(notify.status())) { - MidjourneyApi.TaskStatusEnum taskStatusEnum = MidjourneyApi.TaskStatusEnum.valueOf(notify.status()); - if (MidjourneyApi.TaskStatusEnum.SUCCESS == taskStatusEnum) { - status = AiImageStatusEnum.SUCCESS.getStatus(); - finishTime = LocalDateTime.now(); - } else if (MidjourneyApi.TaskStatusEnum.FAILURE == taskStatusEnum) { - status = AiImageStatusEnum.FAIL.getStatus(); - finishTime = LocalDateTime.now(); - } - } - - // 2. 上传图片 - String picUrl = null; - if (StrUtil.isNotBlank(notify.imageUrl())) { - try { - picUrl = fileApi.createFile(HttpUtil.downloadBytes(notify.imageUrl())); - } catch (Exception e) { - picUrl = notify.imageUrl(); - log.warn("[updateMidjourneyStatus][图片({}) 地址({}) 上传失败]", image.getId(), notify.imageUrl(), e); - } - } - - // 3. 更新 image 状态 - imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(status) - .setPicUrl(picUrl).setButtons(notify.buttons()).setErrorMessage(notify.failReason()) - .setFinishTime(finishTime)); - } - - @Override - public Long midjourneyAction(Long userId, AiMidjourneyActionReqVO reqVO) { - MidjourneyApi midjourneyApi = apiKeyService.getMidjourneyApi(); - // 1.1 检查 image - AiImageDO image = validateImageExists(reqVO.getId()); - if (ObjUtil.notEqual(userId, image.getUserId())) { - throw exception(IMAGE_NOT_EXISTS); - } - // 1.2 检查 customId - MidjourneyApi.Button button = CollUtil.findOne(image.getButtons(), - buttonX -> buttonX.customId().equals(reqVO.getCustomId())); - if (button == null) { - throw exception(IMAGE_CUSTOM_ID_NOT_EXISTS); - } - - // 2. 调用 Midjourney Proxy 提交任务 - MidjourneyApi.SubmitResponse actionResponse = midjourneyApi.action( - new MidjourneyApi.ActionRequest(button.customId(), image.getTaskId(), null)); - if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(actionResponse.code())) { - String description = actionResponse.description().contains("quota_not_enough") ? - "账户余额不足" : actionResponse.description(); - throw exception(IMAGE_MIDJOURNEY_SUBMIT_FAIL, description); - } - - // 3. 新增 image 记录 - AiImageDO newImage = new AiImageDO().setUserId(image.getUserId()).setPublicStatus(false).setPrompt(image.getPrompt()) - .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) - .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()) - .setModel(image.getModel()).setWidth(image.getWidth()).setHeight(image.getHeight()) - .setOptions(image.getOptions()).setTaskId(actionResponse.result()); - imageMapper.insert(newImage); - return newImage.getId(); - } - - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private AiImageServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java deleted file mode 100644 index 2eb1f1b1a3..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.mindmap; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; -import reactor.core.publisher.Flux; - -/** - * AI 思维导图 Service 接口 - * - * @author xiaoxin - */ -public interface AiMindMapService { - - /** - * 生成思维导图内容 - * - * @param generateReqVO 请求参数 - * @param userId 用户编号 - * @return 生成结果 - */ - Flux> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java deleted file mode 100644 index 72be20c547..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.mindmap; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.util.AiUtils; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.ai.controller.admin.mindmap.vo.AiMindMapGenerateReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.mindmap.AiMindMapDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.module.ai.dal.mysql.mindmap.AiMindMapMapper; -import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum; -import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; -import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.SystemMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.stereotype.Service; -import reactor.core.publisher.Flux; - -import java.util.ArrayList; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -/** - * AI 思维导图 Service 实现类 - * - * @author xiaoxin - */ -@Service -@Slf4j -public class AiMindMapServiceImpl implements AiMindMapService { - - @Resource - private AiApiKeyService apiKeyService; - @Resource - private AiChatModelService chatModalService; - @Resource - private AiChatRoleService chatRoleService; - - @Resource - private AiMindMapMapper mindMapMapper; - - @Override - public Flux> generateMindMap(AiMindMapGenerateReqVO generateReqVO, Long userId) { - // 1. 获取脑图模型。尝试获取思维导图助手角色,如果没有则使用默认模型 - AiChatRoleDO role = CollUtil.getFirst( - chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_MIND_MAP_ROLE.getName())); - // 1.1 获取脑图执行模型 - AiChatModelDO model = getModel(role); - // 1.2 获取角色设定消息 - String systemMessage = role != null && StrUtil.isNotBlank(role.getSystemMessage()) - ? role.getSystemMessage() : AiChatRoleEnum.AI_MIND_MAP_ROLE.getSystemMessage(); - // 1.3 校验平台 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - ChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); - - // 2. 插入思维导图信息 - AiMindMapDO mindMapDO = BeanUtils.toBean(generateReqVO, AiMindMapDO.class, - mindMap -> mindMap.setUserId(userId).setModel(model.getModel()).setPlatform(platform.getPlatform())); - mindMapMapper.insert(mindMapDO); - - // 3.1 构建 Prompt,并进行调用 - Prompt prompt = buildPrompt(generateReqVO, model, systemMessage); - Flux streamResponse = chatModel.stream(prompt); - - // 3.2 流式返回 - StringBuffer contentBuffer = new StringBuffer(); - return streamResponse.map(chunk -> { - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; - newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 - contentBuffer.append(newContent); - // 响应结果 - return success(newContent); - }).doOnComplete(() -> { - // 忽略租户,因为 Flux 异步无法透传租户 - TenantUtils.executeIgnore(() -> - mindMapMapper.updateById(new AiMindMapDO().setId(mindMapDO.getId()).setGeneratedContent(contentBuffer.toString()))); - }).doOnError(throwable -> { - log.error("[generateWriteContent][generateReqVO({}) 发生异常]", generateReqVO, throwable); - // 忽略租户,因为 Flux 异步无法透传租户 - TenantUtils.executeIgnore(() -> - mindMapMapper.updateById(new AiMindMapDO().setId(mindMapDO.getId()).setErrorMessage(throwable.getMessage()))); - }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); - - } - - private Prompt buildPrompt(AiMindMapGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) { - // 1. 构建 message 列表 - List chatMessages = buildMessages(generateReqVO, systemMessage); - // 2. 构建 options 对象 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); - return new Prompt(chatMessages, options); - } - - private static List buildMessages(AiMindMapGenerateReqVO generateReqVO, String systemMessage) { - List chatMessages = new ArrayList<>(); - // 1. 角色设定 - if (StrUtil.isNotBlank(systemMessage)) { - chatMessages.add(new SystemMessage(systemMessage)); - } - // 2. 用户输入 - chatMessages.add(new UserMessage(generateReqVO.getPrompt())); - return chatMessages; - } - - private AiChatModelDO getModel(AiChatRoleDO role) { - AiChatModelDO model = null; - if (role != null && role.getModelId() != null) { - model = chatModalService.getChatModel(role.getModelId()); - } - if (model != null) { - model = chatModalService.getRequiredDefaultChatModel(); - } - Assert.notNull(model, "[AI] 获取不到模型"); - return model; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java deleted file mode 100644 index fe8fdd194e..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyService.java +++ /dev/null @@ -1,114 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; -import jakarta.validation.Valid; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.image.ImageModel; - -import java.util.List; - -/** - * AI API 密钥 Service 接口 - * - * @author 芋道源码 - */ -public interface AiApiKeyService { - - /** - * 创建 API 密钥 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createApiKey(@Valid AiApiKeySaveReqVO createReqVO); - - /** - * 更新 API 密钥 - * - * @param updateReqVO 更新信息 - */ - void updateApiKey(@Valid AiApiKeySaveReqVO updateReqVO); - - /** - * 删除 API 密钥 - * - * @param id 编号 - */ - void deleteApiKey(Long id); - - /** - * 获得 API 密钥 - * - * @param id 编号 - * @return API 密钥 - */ - AiApiKeyDO getApiKey(Long id); - - /** - * 校验 API 密钥 - * - * @param id 比那好 - * @return API 密钥 - */ - AiApiKeyDO validateApiKey(Long id); - - /** - * 获得 API 密钥分页 - * - * @param pageReqVO 分页查询 - * @return API 密钥分页 - */ - PageResult getApiKeyPage(AiApiKeyPageReqVO pageReqVO); - - /** - * 获得 API 密钥列表 - * - * @return API 密钥列表 - */ - List getApiKeyList(); - - // ========== 与 spring-ai 集成 ========== - - /** - * 获得 ChatModel 对象 - * - * @param id 编号 - * @return ChatModel 对象 - */ - ChatModel getChatModel(Long id); - - /** - * 获得 ImageModel 对象 - * - * TODO 可优化点:目前默认获取 platform 对应的第一个开启的配置用于绘画;后续可以支持配置选择 - * - * @param platform 平台 - * @return ImageModel 对象 - */ - ImageModel getImageModel(AiPlatformEnum platform); - - /** - * 获得 MidjourneyApi 对象 - * - * TODO 可优化点:目前默认获取 Midjourney 对应的第一个开启的配置用于绘画;后续可以支持配置选择 - * - * @return MidjourneyApi 对象 - */ - MidjourneyApi getMidjourneyApi(); - - /** - * 获得 SunoApi 对象 - * - * TODO 可优化点:目前默认获取 Suno 对应的第一个开启的配置用于音乐;后续可以支持配置选择 - * - * @return SunoApi 对象 - */ - SunoApi getSunoApi(); - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java deleted file mode 100644 index 590b10a4c8..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; -import jakarta.annotation.Resource; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.image.ImageModel; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; - -/** - * AI API 密钥 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -public class AiApiKeyServiceImpl implements AiApiKeyService { - - @Resource - private AiApiKeyMapper apiKeyMapper; - - @Resource - private AiModelFactory modelFactory; - - @Override - public Long createApiKey(AiApiKeySaveReqVO createReqVO) { - // 插入 - AiApiKeyDO apiKey = BeanUtils.toBean(createReqVO, AiApiKeyDO.class); - apiKeyMapper.insert(apiKey); - // 返回 - return apiKey.getId(); - } - - @Override - public void updateApiKey(AiApiKeySaveReqVO updateReqVO) { - // 校验存在 - validateApiKeyExists(updateReqVO.getId()); - // 更新 - AiApiKeyDO updateObj = BeanUtils.toBean(updateReqVO, AiApiKeyDO.class); - apiKeyMapper.updateById(updateObj); - } - - @Override - public void deleteApiKey(Long id) { - // 校验存在 - validateApiKeyExists(id); - // 删除 - apiKeyMapper.deleteById(id); - } - - private AiApiKeyDO validateApiKeyExists(Long id) { - AiApiKeyDO apiKey = apiKeyMapper.selectById(id); - if (apiKey == null) { - throw exception(API_KEY_NOT_EXISTS); - } - return apiKey; - } - - @Override - public AiApiKeyDO getApiKey(Long id) { - return apiKeyMapper.selectById(id); - } - - @Override - public AiApiKeyDO validateApiKey(Long id) { - AiApiKeyDO apiKey = validateApiKeyExists(id); - if (CommonStatusEnum.isDisable(apiKey.getStatus())) { - throw exception(API_KEY_DISABLE); - } - return apiKey; - } - - @Override - public PageResult getApiKeyPage(AiApiKeyPageReqVO pageReqVO) { - return apiKeyMapper.selectPage(pageReqVO); - } - - @Override - public List getApiKeyList() { - return apiKeyMapper.selectList(); - } - - // ========== 与 spring-ai 集成 ========== - - @Override - public ChatModel getChatModel(Long id) { - AiApiKeyDO apiKey = validateApiKey(id); - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); - return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public ImageModel getImageModel(AiPlatformEnum platform) { - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); - if (apiKey == null) { - throw exception(API_KEY_IMAGE_NODE_FOUND, platform.getName()); - } - return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public MidjourneyApi getMidjourneyApi() { - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus( - AiPlatformEnum.MIDJOURNEY.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); - if (apiKey == null) { - throw exception(API_KEY_MIDJOURNEY_NOT_FOUND); - } - return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); - } - - @Override - public SunoApi getSunoApi() { - AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus( - AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); - if (apiKey == null) { - throw exception(API_KEY_SUNO_NOT_FOUND); - } - return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); - } -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelService.java deleted file mode 100644 index f83ac73c91..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelService.java +++ /dev/null @@ -1,92 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import jakarta.validation.Valid; - -import java.util.Collection; -import java.util.List; - -import java.util.Set; - -/** - * AI 聊天模型 Service 接口 - * - * @author fansili - * @since 2024/4/24 19:42 - */ -public interface AiChatModelService { - - /** - * 创建聊天模型 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createChatModel(@Valid AiChatModelSaveReqVO createReqVO); - - /** - * 更新聊天模型 - * - * @param updateReqVO 更新信息 - */ - void updateChatModel(@Valid AiChatModelSaveReqVO updateReqVO); - - /** - * 删除聊天模型 - * - * @param id 编号 - */ - void deleteChatModel(Long id); - - /** - * 获得聊天模型 - * - * @param id 编号 - * @return 聊天模型 - */ - AiChatModelDO getChatModel(Long id); - - /** - * 获得默认的聊天模型 - * - * 如果获取不到,则抛出 {@link cn.iocoder.yudao.framework.common.exception.ServiceException} 业务异常 - * - * @return 聊天模型 - */ - AiChatModelDO getRequiredDefaultChatModel(); - - /** - * 获得聊天模型分页 - * - * @param pageReqVO 分页查询 - * @return 聊天模型分页 - */ - PageResult getChatModelPage(AiChatModelPageReqVO pageReqVO); - - /** - * 校验聊天模型 - * - * @param id 编号 - * @return 聊天模型 - */ - AiChatModelDO validateChatModel(Long id); - - /** - * 获得聊天模型列表 - * - * @param status 状态 - * @return 聊天模型列表 - */ - List getChatModelListByStatus(Integer status); - - /** - * 获得聊天模型列表 - * - * @param ids 编号数组 - * @return 模型列表 - */ - List getChatModelList(Collection ids); -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java deleted file mode 100644 index 4b11602f54..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java +++ /dev/null @@ -1,114 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatModelMapper; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; - -/** - * AI 聊天模型 Service 实现类 - * - * @author fansili - */ -@Service -@Validated -public class AiChatModelServiceImpl implements AiChatModelService { - - @Resource - private AiApiKeyService apiKeyService; - - @Resource - private AiChatModelMapper chatModelMapper; - - @Override - public Long createChatModel(AiChatModelSaveReqVO createReqVO) { - // 1. 校验 - AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); - apiKeyService.validateApiKey(createReqVO.getKeyId()); - - // 2. 插入 - AiChatModelDO chatModel = BeanUtils.toBean(createReqVO, AiChatModelDO.class); - chatModelMapper.insert(chatModel); - return chatModel.getId(); - } - - @Override - public void updateChatModel(AiChatModelSaveReqVO updateReqVO) { - // 1. 校验 - validateChatModelExists(updateReqVO.getId()); - AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); - apiKeyService.validateApiKey(updateReqVO.getKeyId()); - - // 2. 更新 - AiChatModelDO updateObj = BeanUtils.toBean(updateReqVO, AiChatModelDO.class); - chatModelMapper.updateById(updateObj); - } - - @Override - public void deleteChatModel(Long id) { - // 校验存在 - validateChatModelExists(id); - // 删除 - chatModelMapper.deleteById(id); - } - - private AiChatModelDO validateChatModelExists(Long id) { - AiChatModelDO model = chatModelMapper.selectById(id); - if (chatModelMapper.selectById(id) == null) { - throw exception(CHAT_MODEL_NOT_EXISTS); - } - return model; - } - - @Override - public AiChatModelDO getChatModel(Long id) { - return chatModelMapper.selectById(id); - } - - @Override - public AiChatModelDO getRequiredDefaultChatModel() { - AiChatModelDO model = chatModelMapper.selectFirstByStatus(CommonStatusEnum.ENABLE.getStatus()); - if (model == null) { - throw exception(CHAT_MODEL_DEFAULT_NOT_EXISTS); - } - return model; - } - - @Override - public PageResult getChatModelPage(AiChatModelPageReqVO pageReqVO) { - return chatModelMapper.selectPage(pageReqVO); - } - - @Override - public AiChatModelDO validateChatModel(Long id) { - AiChatModelDO model = validateChatModelExists(id); - if (CommonStatusEnum.isDisable(model.getStatus())) { - throw exception(CHAT_MODEL_DISABLE); - } - return model; - } - - @Override - public List getChatModelListByStatus(Integer status) { - return chatModelMapper.selectList(status); - } - - @Override - public List getChatModelList(Collection ids) { - return chatModelMapper.selectBatchIds(ids); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java deleted file mode 100644 index 81c8d259b6..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java +++ /dev/null @@ -1,129 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import jakarta.validation.Valid; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; - -/** - * AI 聊天角色 Service 接口 - * - * @author fansili - */ -public interface AiChatRoleService { - - /** - * 创建聊天角色 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createChatRole(@Valid AiChatRoleSaveReqVO createReqVO); - - /** - * 创建【我的】聊天角色 - * - * @param createReqVO 创建信息 - * @param userId 用户编号 - * @return 编号 - */ - Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId); - - /** - * 更新聊天角色 - * - * @param updateReqVO 更新信息 - */ - void updateChatRole(@Valid AiChatRoleSaveReqVO updateReqVO); - - /** - * 创建【我的】聊天角色 - * - * @param updateReqVO 更新信息 - * @param userId 用户编号 - */ - void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId); - - /** - * 删除聊天角色 - * - * @param id 编号 - */ - void deleteChatRole(Long id); - - /** - * 删除【我的】聊天角色 - * - * @param id 编号 - * @param userId 用户编号 - */ - void deleteChatRoleMy(Long id, Long userId); - - /** - * 获得聊天角色 - * - * @param id 编号 - * @return AI 聊天角色 - */ - AiChatRoleDO getChatRole(Long id); - - /** - * 获得聊天角色列表 - * - * @param ids 编号数组 - * @return 聊天角色列表 - */ - List getChatRoleList(Collection ids); - - default Map getChatRoleMap(Collection ids) { - return convertMap(getChatRoleList(ids), AiChatRoleDO::getId); - } - - /** - * 校验聊天角色是否合法 - * - * @param id 角色编号 - */ - AiChatRoleDO validateChatRole(Long id); - - /** - * 获得聊天角色分页 - * - * @param pageReqVO 分页查询 - * @return 聊天角色分页 - */ - PageResult getChatRolePage(AiChatRolePageReqVO pageReqVO); - - /** - * 获得【我的】聊天角色分页 - * - * @param pageReqVO 分页查询 - * @param userId 用户编号 - * @return 聊天角色分页 - */ - PageResult getChatRoleMyPage(AiChatRolePageReqVO pageReqVO, Long userId); - - /** - * 获得聊天角色的分类列表 - * - * @return 分类列表 - */ - List getChatRoleCategoryList(); - - /** - * 根据名字获得聊天角色 - * - * @param name 名字 - * @return 聊天角色列表 - */ - List getChatRoleListByName(String name); - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java deleted file mode 100644 index 2cf4d46d1c..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java +++ /dev/null @@ -1,146 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.model; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatRoleMapper; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; - -/** - * AI 聊天角色 Service 实现类 - * - * @author fansili - */ -@Service -@Slf4j -public class AiChatRoleServiceImpl implements AiChatRoleService { - - @Resource - private AiChatRoleMapper chatRoleMapper; - - @Override - public Long createChatRole(AiChatRoleSaveReqVO createReqVO) { - AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class); - chatRoleMapper.insert(chatRole); - return chatRole.getId(); - } - - @Override - public Long createChatRoleMy(AiChatRoleSaveMyReqVO createReqVO, Long userId) { - AiChatRoleDO chatRole = BeanUtils.toBean(createReqVO, AiChatRoleDO.class).setUserId(userId) - .setStatus(CommonStatusEnum.ENABLE.getStatus()).setPublicStatus(false); - chatRoleMapper.insert(chatRole); - return chatRole.getId(); - } - - @Override - public void updateChatRole(AiChatRoleSaveReqVO updateReqVO) { - // 校验存在 - validateChatRoleExists(updateReqVO.getId()); - // 更新 - AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class); - chatRoleMapper.updateById(updateObj); - } - - @Override - public void updateChatRoleMy(AiChatRoleSaveMyReqVO updateReqVO, Long userId) { - // 校验存在 - AiChatRoleDO chatRole = validateChatRoleExists(updateReqVO.getId()); - if (ObjectUtil.notEqual(chatRole.getUserId(), userId)) { - throw exception(CHAT_ROLE_NOT_EXISTS); - } - - // 更新 - AiChatRoleDO updateObj = BeanUtils.toBean(updateReqVO, AiChatRoleDO.class); - chatRoleMapper.updateById(updateObj); - } - - @Override - public void deleteChatRole(Long id) { - // 校验存在 - validateChatRoleExists(id); - // 删除 - chatRoleMapper.deleteById(id); - } - - @Override - public void deleteChatRoleMy(Long id, Long userId) { - // 校验存在 - AiChatRoleDO chatRole = validateChatRoleExists(id); - if (ObjectUtil.notEqual(chatRole.getUserId(), userId)) { - throw exception(CHAT_ROLE_NOT_EXISTS); - } - // 删除 - chatRoleMapper.deleteById(id); - } - - private AiChatRoleDO validateChatRoleExists(Long id) { - AiChatRoleDO chatRole = chatRoleMapper.selectById(id); - if (chatRole == null) { - throw exception(CHAT_ROLE_NOT_EXISTS); - } - return chatRole; - } - - @Override - public AiChatRoleDO getChatRole(Long id) { - return chatRoleMapper.selectById(id); - } - - @Override - public List getChatRoleList(Collection ids) { - if (CollUtil.isEmpty(ids)) { - return Collections.emptyList(); - } - return chatRoleMapper.selectBatchIds(ids); - } - - @Override - public AiChatRoleDO validateChatRole(Long id) { - AiChatRoleDO chatRole = validateChatRoleExists(id); - if (CommonStatusEnum.isDisable(chatRole.getStatus())) { - throw exception(CHAT_ROLE_DISABLE, chatRole.getName()); - } - return chatRole; - } - - @Override - public PageResult getChatRolePage(AiChatRolePageReqVO pageReqVO) { - return chatRoleMapper.selectPage(pageReqVO); - } - - @Override - public PageResult getChatRoleMyPage(AiChatRolePageReqVO pageReqVO, Long userId) { - return chatRoleMapper.selectPageByMy(pageReqVO, userId); - } - - @Override - public List getChatRoleCategoryList() { - List list = chatRoleMapper.selectListGroupByCategory(CommonStatusEnum.ENABLE.getStatus()); - return convertList(list, AiChatRoleDO::getCategory, role -> role != null && StrUtil.isNotBlank(role.getCategory())); - } - - @Override - public List getChatRoleListByName(String name) { - return chatRoleMapper.selectListByName(name); - } - -} - diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java deleted file mode 100644 index 49f8332dec..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java +++ /dev/null @@ -1,87 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.music; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; -import jakarta.validation.Valid; - -import java.util.List; - -/** - * AI 音乐 Service 接口 - * - * @author xiaoxin - */ -public interface AiMusicService { - - /** - * 音乐生成 - * - * @param userId 用户编号 - * @param reqVO 请求参数 - * @return 生成的音乐ID - */ - List generateMusic(Long userId, AiSunoGenerateReqVO reqVO); - - /** - * 同步音乐任务 - * - * @return 同步数量 - */ - Integer syncMusic(); - - /** - * 更新音乐发布状态 - * - * @param updateReqVO 更新信息 - */ - void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO); - - /** - * 更新我的音乐 - * - * @param updateReqVO 更新信息 - */ - void updateMyMusic(@Valid AiMusicUpdateMyReqVO updateReqVO, Long userId); - - /** - * 删除AI 音乐 - * - * @param id 编号 - */ - void deleteMusic(Long id); - - /** - * 删除【我的】音乐记录 - * - * @param id 音乐编号 - * @param userId 用户编号 - */ - void deleteMusicMy(Long id, Long userId); - - /** - * 获得AI 音乐 - * - * @param id 音乐编号 - * @return 音乐内容 - */ - AiMusicDO getMusic(Long id); - - /** - * 获得音乐分页 - * - * @param pageReqVO 分页查询 - * @return 音乐分页 - */ - PageResult getMusicPage(AiMusicPageReqVO pageReqVO); - - /** - * 获得【我的】音乐分页 - * - * @param pageReqVO 分页查询 - * @param userId 用户编号 - * @return 音乐分页 - */ - PageResult getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java deleted file mode 100644 index 3f10ec8402..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java +++ /dev/null @@ -1,218 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.music; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.StrPool; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpUtil; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdateMyReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; -import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; -import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; -import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.infra.api.file.FileApi; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.IMAGE_NOT_EXISTS; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS; - -/** - * AI 音乐 Service 实现类 - * - * @author xiaoxin - */ -@Service -@Slf4j -public class AiMusicServiceImpl implements AiMusicService { - - @Resource - private AiApiKeyService apiKeyService; - - @Resource - private AiMusicMapper musicMapper; - - @Resource - private FileApi fileApi; - - @Override - @Transactional(rollbackFor = Exception.class) - public List generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { - // 1. 调用 Suno 生成音乐 - SunoApi sunoApi = apiKeyService.getSunoApi(); - List musicDataList; - if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { - // 1.1 描述模式 - SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( - reqVO.getPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental()); - musicDataList = sunoApi.generate(generateRequest); - } else if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { - // 1.2 歌词模式 - SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( - reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); - musicDataList = sunoApi.customGenerate(generateRequest); - } else { - throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); - } - - // 2. 插入数据库 - if (CollUtil.isEmpty(musicDataList)) { - return Collections.emptyList(); - } - List musicList = buildMusicDOList(musicDataList); - musicList.forEach(music -> music.setUserId(userId).setPlatform(reqVO.getPlatform()).setGenerateMode(reqVO.getGenerateMode())); - musicMapper.insertBatch(musicList); - return convertList(musicList, AiMusicDO::getId); - } - - @Override - public Integer syncMusic() { - List streamingTask = musicMapper.selectListByStatus(AiMusicStatusEnum.IN_PROGRESS.getStatus()); - if (CollUtil.isEmpty(streamingTask)) { - return 0; - } - log.info("[syncMusic][Suno 开始同步, 共 ({}) 个任务]", streamingTask.size()); - - // GET 请求,为避免参数过长,分批次处理 - SunoApi sunoApi = apiKeyService.getSunoApi(); - CollUtil.split(streamingTask, 36).forEach(chunkList -> { - Map taskIdMap = convertMap(chunkList, AiMusicDO::getTaskId, AiMusicDO::getId); - List musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); - if (CollUtil.isEmpty(musicTaskList)) { - log.warn("Suno 任务同步失败, 任务ID: [{}]", taskIdMap.keySet()); - return; - } - // 更新进度 - List updateMusicList = buildMusicDOList(musicTaskList); - updateMusicList.forEach(music -> music.setId(taskIdMap.get(music.getTaskId()))); - musicMapper.updateBatch(updateMusicList); - }); - return streamingTask.size(); - } - - @Override - public void updateMusic(AiMusicUpdateReqVO updateReqVO) { - // 校验存在 - validateMusicExists(updateReqVO.getId()); - // 更新 - musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setPublicStatus(updateReqVO.getPublicStatus())); - } - - @Override - public void updateMyMusic(AiMusicUpdateMyReqVO updateReqVO, Long userId) { - // 校验音乐是否存在 - AiMusicDO musicDO = validateMusicExists(updateReqVO.getId()); - if (ObjUtil.notEqual(musicDO.getUserId(), userId)) { - throw exception(MUSIC_NOT_EXISTS); - } - // 更新 - musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle())); - } - - @Override - public void deleteMusic(Long id) { - // 校验存在 - validateMusicExists(id); - // 删除 - musicMapper.deleteById(id); - } - - @Override - public void deleteMusicMy(Long id, Long userId) { - // 1. 校验是否存在 - AiMusicDO music = validateMusicExists(id); - if (ObjUtil.notEqual(music.getUserId(), userId)) { - throw exception(IMAGE_NOT_EXISTS); - } - // 2. 删除记录 - musicMapper.deleteById(id); - } - - @Override - public AiMusicDO getMusic(Long id) { - return musicMapper.selectById(id); - } - - @Override - public PageResult getMusicPage(AiMusicPageReqVO pageReqVO) { - return musicMapper.selectPage(pageReqVO); - } - - @Override - public PageResult getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId) { - return musicMapper.selectPageByMy(pageReqVO, userId); - } - - /** - * 构建 AiMusicDO 集合 - * - * @param musicList suno 音乐任务列表 - * @return AiMusicDO 集合 - */ - private List buildMusicDOList(List musicList) { - return convertList(musicList, musicData -> { - Integer status = Objects.equals("complete", musicData.status()) ? AiMusicStatusEnum.SUCCESS.getStatus() - : Objects.equals("error", musicData.status()) ? AiMusicStatusEnum.FAIL.getStatus() - : AiMusicStatusEnum.IN_PROGRESS.getStatus(); - return new AiMusicDO() - .setTaskId(musicData.id()).setModel(musicData.modelName()) - .setDescription(musicData.gptDescriptionPrompt()) - .setAudioUrl(downloadFile(status, musicData.audioUrl())) - .setVideoUrl(downloadFile(status, musicData.videoUrl())) - .setImageUrl(downloadFile(status, musicData.imageUrl())) - .setTitle(musicData.title()).setDuration(musicData.duration()) - .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) - .setErrorMessage(musicData.errorMessage()) - .setStatus(status); - }); - } - - /** - * 音乐生成好后,将音频文件上传到文件服务器 - * - * @param status 音乐状态 - * @param url 音频文件地址 - * @return 内部文件地址 - */ - private String downloadFile(Integer status, String url) { - if (StrUtil.isBlank(url) || ObjectUtil.notEqual(status, AiMusicStatusEnum.SUCCESS.getStatus())) { - return url; - } - try { - byte[] bytes = HttpUtil.downloadBytes(url); - return fileApi.createFile(bytes); - } catch (Exception e) { - log.error("[downloadFile][url({}) 下载失败]", url, e); - return url; - } - } - - /** - * 校验音乐是否存在 - * - * @param id 音乐编号 - * @return 音乐信息 - */ - private AiMusicDO validateMusicExists(Long id) { - AiMusicDO music = musicMapper.selectById(id); - if (music == null) { - throw exception(MUSIC_NOT_EXISTS); - } - return music; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java deleted file mode 100644 index f2dd489ff9..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.write; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; -import reactor.core.publisher.Flux; - -/** - * AI 写作 Service 接口 - * - * @author xiaoxin - */ -public interface AiWriteService { - - /** - * 生成写作内容 - * - * @param generateReqVO 作文生成请求参数 - * @param userId 用户编号 - * @return 生成结果 - */ - Flux> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId); - - /** - * 删除写作 - * - * @param id 编号 - */ - void deleteWrite(Long id); - - /** - * 获得写作分页 - * - * @param pageReqVO 分页查询 - * @return AI 写作分页 - */ - PageResult getWritePage(AiWritePageReqVO pageReqVO); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java deleted file mode 100644 index 2fae31d590..0000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java +++ /dev/null @@ -1,177 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.write; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.util.AiUtils; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWriteGenerateReqVO; -import cn.iocoder.yudao.module.ai.controller.admin.write.vo.AiWritePageReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.write.AiWriteDO; -import cn.iocoder.yudao.module.ai.dal.mysql.write.AiWriteMapper; -import cn.iocoder.yudao.module.ai.enums.AiChatRoleEnum; -import cn.iocoder.yudao.module.ai.enums.DictTypeConstants; -import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; -import cn.iocoder.yudao.module.ai.enums.write.AiWriteTypeEnum; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; -import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; -import cn.iocoder.yudao.module.system.api.dict.DictDataApi; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.SystemMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.stereotype.Service; -import reactor.core.publisher.Flux; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.WRITE_NOT_EXISTS; - -/** - * AI 写作 Service 实现类 - * - * @author xiaoxin - */ -@Service -@Slf4j -public class AiWriteServiceImpl implements AiWriteService { - - @Resource - private AiApiKeyService apiKeyService; - @Resource - private AiChatModelService chatModalService; - @Resource - private AiChatRoleService chatRoleService; - - @Resource - private DictDataApi dictDataApi; - - @Resource - private AiWriteMapper writeMapper; - - @Override - public Flux> generateWriteContent(AiWriteGenerateReqVO generateReqVO, Long userId) { - // 1 获取写作模型。尝试获取写作助手角色,没有则使用默认模型 - AiChatRoleDO writeRole = CollUtil.getFirst( - chatRoleService.getChatRoleListByName(AiChatRoleEnum.AI_WRITE_ROLE.getName())); - // 1.1 获取写作执行模型 - AiChatModelDO model = getModel(writeRole); - // 1.2 获取角色设定消息 - String systemMessage = Objects.nonNull(writeRole) && StrUtil.isNotBlank(writeRole.getSystemMessage()) - ? writeRole.getSystemMessage() : AiChatRoleEnum.AI_WRITE_ROLE.getSystemMessage(); - // 1.3 校验平台 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - StreamingChatModel chatModel = apiKeyService.getChatModel(model.getKeyId()); - - // 2. 插入写作信息 - AiWriteDO writeDO = BeanUtils.toBean(generateReqVO, AiWriteDO.class, - write -> write.setUserId(userId).setPlatform(platform.getPlatform()).setModel(model.getModel())); - writeMapper.insert(writeDO); - - // 3.1 构建 Prompt,并进行调用 - Prompt prompt = buildPrompt(generateReqVO, model, systemMessage); - Flux streamResponse = chatModel.stream(prompt); - - // 3.2 流式返回 - StringBuffer contentBuffer = new StringBuffer(); - return streamResponse.map(chunk -> { - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; - newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 - contentBuffer.append(newContent); - // 响应结果 - return success(newContent); - }).doOnComplete(() -> { - // 忽略租户,因为 Flux 异步无法透传租户 - TenantUtils.executeIgnore(() -> - writeMapper.updateById(new AiWriteDO().setId(writeDO.getId()).setGeneratedContent(contentBuffer.toString()))); - }).doOnError(throwable -> { - log.error("[generateWriteContent][generateReqVO({}) 发生异常]", generateReqVO, throwable); - // 忽略租户,因为 Flux 异步无法透传租户 - TenantUtils.executeIgnore(() -> - writeMapper.updateById(new AiWriteDO().setId(writeDO.getId()).setErrorMessage(throwable.getMessage()))); - }).onErrorResume(error -> Flux.just(error(ErrorCodeConstants.WRITE_STREAM_ERROR))); - } - - private AiChatModelDO getModel(AiChatRoleDO writeRole) { - AiChatModelDO model = null; - if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) { - model = chatModalService.getChatModel(writeRole.getModelId()); - } - if (Objects.isNull(model)) { - model = chatModalService.getRequiredDefaultChatModel(); - } - Assert.notNull(model, "[AI] 获取不到模型"); - return model; - } - - private Prompt buildPrompt(AiWriteGenerateReqVO generateReqVO, AiChatModelDO model, String systemMessage) { - // 1. 构建 message 列表 - List chatMessages = buildMessages(generateReqVO, systemMessage); - // 2. 构建 options 对象 - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - ChatOptions options = AiUtils.buildChatOptions(platform, model.getModel(), model.getTemperature(), model.getMaxTokens()); - return new Prompt(chatMessages, options); - } - - private List buildMessages(AiWriteGenerateReqVO generateReqVO, String systemMessage) { - List chatMessages = new ArrayList<>(); - if (StrUtil.isNotBlank(systemMessage)) { - // 1.1 角色设定 - chatMessages.add(new SystemMessage(systemMessage)); - } - // 1.2 用户输入 - chatMessages.add(new UserMessage(buildUserMessage(generateReqVO))); - return chatMessages; - } - - private String buildUserMessage(AiWriteGenerateReqVO generateReqVO) { - String format = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_FORMAT, generateReqVO.getFormat()); - String tone = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_TONE, generateReqVO.getTone()); - String language = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LANGUAGE, generateReqVO.getLanguage()); - String length = dictDataApi.getDictDataLabel(DictTypeConstants.AI_WRITE_LENGTH, generateReqVO.getLength()); - // 格式化 prompt - String prompt = generateReqVO.getPrompt(); - if (Objects.equals(generateReqVO.getType(), AiWriteTypeEnum.WRITING.getType())) { - return StrUtil.format(AiWriteTypeEnum.WRITING.getPrompt(), prompt, format, tone, language, length); - } else { - return StrUtil.format(AiWriteTypeEnum.REPLY.getPrompt(), generateReqVO.getOriginalContent(), prompt, format, tone, language, length); - } - } - - @Override - public void deleteWrite(Long id) { - // 校验存在 - validateWriteExists(id); - // 删除 - writeMapper.deleteById(id); - } - - private void validateWriteExists(Long id) { - if (writeMapper.selectById(id) == null) { - throw exception(WRITE_NOT_EXISTS); - } - } - - @Override - public PageResult getWritePage(AiWritePageReqVO pageReqVO) { - return writeMapper.selectPage(pageReqVO); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml deleted file mode 100644 index 4aa6273cf3..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - cn.iocoder.boot - yudao-module-ai - ${revision} - - 4.0.0 - yudao-spring-boot-starter-ai - jar - - ${project.artifactId} - AI 大模型拓展,接入国内外大模型 - - 1.0.0-M1 - - - - - org.springframework.ai - spring-ai-zhipuai-spring-boot-starter - ${spring-ai.version} - - - - org.springframework.ai - spring-ai-openai-spring-boot-starter - ${spring-ai.version} - - - org.springframework.ai - spring-ai-ollama-spring-boot-starter - ${spring-ai.version} - - - org.springframework.ai - spring-ai-stability-ai-spring-boot-starter - ${spring-ai.version} - - - - cn.iocoder.boot - yudao-common - - - - - group.springframework.ai - spring-ai-qianfan-spring-boot-starter - 1.1.0 - - - - - - com.alibaba - dashscope-sdk-java - 2.14.0 - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java deleted file mode 100644 index 05a3172945..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ /dev/null @@ -1,76 +0,0 @@ -package cn.iocoder.yudao.framework.ai.config; - -import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactory; -import cn.iocoder.yudao.framework.ai.core.factory.AiModelFactoryImpl; -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; -import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -/** - * 芋道 AI 自动配置 - * - * @author fansili - */ -@AutoConfiguration -@EnableConfigurationProperties(YudaoAiProperties.class) -@Slf4j -@Import(TongYiAutoConfiguration.class) -public class YudaoAiAutoConfiguration { - - @Bean - public AiModelFactory aiModelFactory() { - return new AiModelFactoryImpl(); - } - - // ========== 各种 AI Client 创建 ========== - - @Bean - @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true") - public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) { - YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepSeek(); - DeepSeekChatOptions options = DeepSeekChatOptions.builder() - .model(properties.getModel()) - .temperature(properties.getTemperature()) - .maxTokens(properties.getMaxTokens()) - .topP(properties.getTopP()) - .build(); - return new DeepSeekChatModel(properties.getApiKey(), options); - } - - @Bean - @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") - public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { - YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); - XingHuoChatOptions options = XingHuoChatOptions.builder() - .model(properties.getModel()) - .temperature(properties.getTemperature()) - .maxTokens(properties.getMaxTokens()) - .topK(properties.getTopK()) - .build(); - return new XingHuoChatModel(properties.getAppKey(), properties.getSecretKey(), options); - } - - @Bean - @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") - public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { - YudaoAiProperties.MidjourneyProperties config = yudaoAiProperties.getMidjourney(); - return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl()); - } - - @Bean - @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") - public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { - return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); - } - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java deleted file mode 100644 index 82c74b0c64..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ /dev/null @@ -1,84 +0,0 @@ -package cn.iocoder.yudao.framework.ai.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * 芋道 AI 配置类 - * - * @author fansili - * @since 1.0 - */ -@ConfigurationProperties(prefix = "yudao.ai") -@Data -public class YudaoAiProperties { - - /** - * DeepSeek - */ - private DeepSeekProperties deepSeek; - - /** - * 讯飞星火 - */ - private XingHuoProperties xinghuo; - - /** - * Midjourney 绘图 - */ - private MidjourneyProperties midjourney; - - /** - * Suno 音乐 - */ - private SunoProperties suno; - - @Data - public static class XingHuoProperties { - - private String enable; - private String appId; - private String appKey; - private String secretKey; - - private String model; - private Float temperature; - private Integer maxTokens; - private Integer topK; - - } - - @Data - public static class DeepSeekProperties { - - private String enable; - private String apiKey; - - private String model; - private Float temperature; - private Integer maxTokens; - private Float topP; - - } - - @Data - public static class MidjourneyProperties { - - private String enable; - private String baseUrl; - - private String apiKey; - private String notifyUrl; - - } - - @Data - public static class SunoProperties { - - private boolean enable = false; - - private String baseUrl; - - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java deleted file mode 100644 index 5961181688..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * AI 模型平台 - * - * @author fansili - */ -@Getter -@AllArgsConstructor -public enum AiPlatformEnum { - - // ========== 国内平台 ========== - - TONG_YI("TongYi", "通义千问"), // 阿里 - YI_YAN("YiYan", "文心一言"), // 百度 - DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek - ZHI_PU("ZhiPu", "智谱"), // 智谱 AI - XING_HUO("XingHuo", "星火"), // 讯飞 - - // ========== 国外平台 ========== - - OPENAI("OpenAI", "OpenAI"), - OLLAMA("Ollama", "Ollama"), - - STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI - MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney - SUNO("Suno", "Suno"), // Suno AI - - ; - - /** - * 平台 - */ - private final String platform; - /** - * 平台名 - */ - private final String name; - - public static AiPlatformEnum validatePlatform(String platform) { - for (AiPlatformEnum platformEnum : AiPlatformEnum.values()) { - if (platformEnum.getPlatform().equals(platform)) { - return platformEnum; - } - } - throw new IllegalArgumentException("非法平台: " + platform); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java deleted file mode 100644 index b6d7b3dd08..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactory.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.factory; - -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.image.ImageModel; - -/** - * AI Model 模型工厂的接口类 - * - * @author fansili - */ -public interface AiModelFactory { - - /** - * 基于指定配置,获得 ChatModel 对象 - * - * 如果不存在,则进行创建 - * - * @param platform 平台 - * @param apiKey API KEY - * @param url API URL - * @return ChatModel 对象 - */ - ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); - - /** - * 基于默认配置,获得 ChatModel 对象 - * - * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 - * - * @param platform 平台 - * @return ChatModel 对象 - */ - ChatModel getDefaultChatModel(AiPlatformEnum platform); - - /** - * 基于默认配置,获得 ImageModel 对象 - * - * 默认配置,指的是在 application.yaml 配置文件中的 spring.ai 相关的配置 - * - * @param platform 平台 - * @return ImageModel 对象 - */ - ImageModel getDefaultImageModel(AiPlatformEnum platform); - - /** - * 基于指定配置,获得 ImageModel 对象 - * - * 如果不存在,则进行创建 - * - * @param platform 平台 - * @param apiKey API KEY - * @param url API URL - * @return ImageModel 对象 - */ - ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url); - - /** - * 基于指定配置,获得 MidjourneyApi 对象 - * - * 如果不存在,则进行创建 - * - * @param apiKey API KEY - * @param url API URL - * @return MidjourneyApi 对象 - */ - MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url); - - /** - * 基于指定配置,获得 SunoApi 对象 - * - * 如果不存在,则进行创建 - * - * @param apiKey API KEY - * @param url API URL - * @return SunoApi 对象 - */ - SunoApi getOrCreateSunoApi(String apiKey, String url); - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java deleted file mode 100644 index a5df282468..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiModelFactoryImpl.java +++ /dev/null @@ -1,294 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.factory; - -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Singleton; -import cn.hutool.core.lang.func.Func0; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.ai.config.YudaoAiAutoConfiguration; -import cn.iocoder.yudao.framework.ai.config.YudaoAiProperties; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatModel; -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; -import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; -import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties; -import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel; -import com.alibaba.cloud.ai.tongyi.chat.TongYiChatProperties; -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesModel; -import com.alibaba.cloud.ai.tongyi.image.TongYiImagesProperties; -import com.alibaba.dashscope.aigc.generation.Generation; -import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis; -import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration; -import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration; -import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration; -import org.springframework.ai.autoconfigure.qianfan.QianFanChatProperties; -import org.springframework.ai.autoconfigure.qianfan.QianFanConnectionProperties; -import org.springframework.ai.autoconfigure.qianfan.QianFanImageProperties; -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration; -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiChatProperties; -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiConnectionProperties; -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiImageProperties; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.model.function.FunctionCallbackContext; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiImageModel; -import org.springframework.ai.openai.api.ApiUtils; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.ai.qianfan.QianFanChatModel; -import org.springframework.ai.qianfan.QianFanImageModel; -import org.springframework.ai.qianfan.api.QianFanApi; -import org.springframework.ai.qianfan.api.QianFanImageApi; -import org.springframework.ai.stabilityai.StabilityAiImageModel; -import org.springframework.ai.stabilityai.api.StabilityAiApi; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiImageModel; -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClient; - -import java.util.List; - -/** - * AI Model 模型工厂的实现类 - * - * @author 芋道源码 - */ -public class AiModelFactoryImpl implements AiModelFactory { - - @Override - public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) { - String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url); - return Singleton.get(cacheKey, (Func0) () -> { - //noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return buildTongYiChatModel(apiKey); - case YI_YAN: - return buildYiYanChatModel(apiKey); - case DEEP_SEEK: - return buildDeepSeekChatModel(apiKey); - case ZHI_PU: - return buildZhiPuChatModel(apiKey, url); - case XING_HUO: - return buildXingHuoChatModel(apiKey); - case OPENAI: - return buildOpenAiChatModel(apiKey, url); - case OLLAMA: - return buildOllamaChatModel(url); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - }); - } - - @Override - public ChatModel getDefaultChatModel(AiPlatformEnum platform) { - //noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return SpringUtil.getBean(TongYiChatModel.class); - case YI_YAN: - return SpringUtil.getBean(QianFanChatModel.class); - case DEEP_SEEK: - return SpringUtil.getBean(DeepSeekChatModel.class); - case ZHI_PU: - return SpringUtil.getBean(ZhiPuAiChatModel.class); - case XING_HUO: - return SpringUtil.getBean(XingHuoChatModel.class); - case OPENAI: - return SpringUtil.getBean(OpenAiChatModel.class); - case OLLAMA: - return SpringUtil.getBean(OllamaChatModel.class); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - } - - @Override - public ImageModel getDefaultImageModel(AiPlatformEnum platform) { - //noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return SpringUtil.getBean(TongYiImagesModel.class); - case YI_YAN: - return SpringUtil.getBean(QianFanImageModel.class); - case ZHI_PU: - return SpringUtil.getBean(ZhiPuAiImageModel.class); - case OPENAI: - return SpringUtil.getBean(OpenAiImageModel.class); - case STABLE_DIFFUSION: - return SpringUtil.getBean(StabilityAiImageModel.class); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - } - - @Override - public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) { - //noinspection EnhancedSwitchMigration - switch (platform) { - case TONG_YI: - return buildTongYiImagesModel(apiKey); - case YI_YAN: - return buildQianFanImageModel(apiKey); - case ZHI_PU: - return buildZhiPuAiImageModel(apiKey, url); - case OPENAI: - return buildOpenAiImageModel(apiKey, url); - case STABLE_DIFFUSION: - return buildStabilityAiImageModel(apiKey, url); - default: - throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); - } - } - - @Override - public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) { - String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey, url); - return Singleton.get(cacheKey, (Func0) () -> { - YudaoAiProperties.MidjourneyProperties properties = SpringUtil.getBean(YudaoAiProperties.class).getMidjourney(); - return new MidjourneyApi(url, apiKey, properties.getNotifyUrl()); - }); - } - - @Override - public SunoApi getOrCreateSunoApi(String apiKey, String url) { - String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url); - return Singleton.get(cacheKey, (Func0) () -> new SunoApi(url)); - } - - private static String buildClientCacheKey(Class clazz, Object... params) { - if (ArrayUtil.isEmpty(params)) { - return clazz.getName(); - } - return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_")); - } - - // ========== 各种创建 spring-ai 客户端的方法 ========== - - /** - * 可参考 {@link TongYiAutoConfiguration#tongYiChatClient(Generation, TongYiChatProperties, TongYiConnectionProperties)} - */ - private static TongYiChatModel buildTongYiChatModel(String key) { - com.alibaba.dashscope.aigc.generation.Generation generation = SpringUtil.getBean(Generation.class); - TongYiChatProperties chatOptions = SpringUtil.getBean(TongYiChatProperties.class); - // TODO @芋艿:貌似 apiKey 是全局唯一的???得测试下 - // TODO @芋艿:貌似阿里云不是增量返回的 - // 该 issue 进行跟进中 https://github.com/alibaba/spring-cloud-alibaba/issues/3790 - TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties(); - connectionProperties.setApiKey(key); - return new TongYiAutoConfiguration().tongYiChatClient(generation, chatOptions, connectionProperties); - } - - private static TongYiImagesModel buildTongYiImagesModel(String key) { - ImageSynthesis imageSynthesis = SpringUtil.getBean(ImageSynthesis.class); - TongYiImagesProperties imagesOptions = SpringUtil.getBean(TongYiImagesProperties.class); - TongYiConnectionProperties connectionProperties = new TongYiConnectionProperties(); - connectionProperties.setApiKey(key); - return new TongYiAutoConfiguration().tongYiImagesClient(imageSynthesis, imagesOptions, connectionProperties); - } - - /** - * 可参考 {@link QianFanAutoConfiguration#qianFanChatModel(QianFanConnectionProperties, QianFanChatProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} - */ - private static QianFanChatModel buildYiYanChatModel(String key) { - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); - String appKey = keys.get(0); - String secretKey = keys.get(1); - QianFanApi qianFanApi = new QianFanApi(appKey, secretKey); - return new QianFanChatModel(qianFanApi); - } - - /** - * 可参考 {@link QianFanAutoConfiguration#qianFanImageModel(QianFanConnectionProperties, QianFanImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} - */ - private QianFanImageModel buildQianFanImageModel(String key) { - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); - String appKey = keys.get(0); - String secretKey = keys.get(1); - QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey); - return new QianFanImageModel(qianFanApi); - } - - /** - * 可参考 {@link YudaoAiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)} - */ - private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { - return new DeepSeekChatModel(apiKey); - } - - /** - * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiChatModel( - * ZhiPuAiConnectionProperties, ZhiPuAiChatProperties, RestClient.Builder, List, FunctionCallbackContext, RetryTemplate, ResponseErrorHandler)} - */ - private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { - url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); - ZhiPuAiApi zhiPuAiApi = new ZhiPuAiApi(url, apiKey); - return new ZhiPuAiChatModel(zhiPuAiApi); - } - - /** - * 可参考 {@link ZhiPuAiAutoConfiguration#zhiPuAiImageModel( - * ZhiPuAiConnectionProperties, ZhiPuAiImageProperties, RestClient.Builder, RetryTemplate, ResponseErrorHandler)} - */ - private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { - url = StrUtil.blankToDefault(url, ZhiPuAiConnectionProperties.DEFAULT_BASE_URL); - ZhiPuAiImageApi zhiPuAiApi = new ZhiPuAiImageApi(url, apiKey, RestClient.builder()); - return new ZhiPuAiImageModel(zhiPuAiApi); - } - - /** - * 可参考 {@link YudaoAiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)} - */ - private static XingHuoChatModel buildXingHuoChatModel(String key) { - List keys = StrUtil.split(key, '|'); - Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式"); - String appKey = keys.get(1); - String secretKey = keys.get(2); - return new XingHuoChatModel(appKey, secretKey); - } - - /** - * 可参考 {@link OpenAiAutoConfiguration} - */ - private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { - url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL); - OpenAiApi openAiApi = new OpenAiApi(url, openAiToken); - return new OpenAiChatModel(openAiApi); - } - - /** - * 可参考 {@link OpenAiAutoConfiguration} - */ - private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { - url = StrUtil.blankToDefault(url, ApiUtils.DEFAULT_BASE_URL); - OpenAiImageApi openAiApi = new OpenAiImageApi(url, openAiToken, RestClient.builder()); - return new OpenAiImageModel(openAiApi); - } - - /** - * 可参考 {@link OllamaAutoConfiguration} - */ - private static OllamaChatModel buildOllamaChatModel(String url) { - OllamaApi ollamaApi = new OllamaApi(url); - return new OllamaChatModel(ollamaApi); - } - - private StabilityAiImageModel buildStabilityAiImageModel(String apiKey, String url) { - url = StrUtil.blankToDefault(url, StabilityAiApi.DEFAULT_BASE_URL); - StabilityAiApi stabilityAiApi = new StabilityAiApi(apiKey, StabilityAiApi.DEFAULT_IMAGE_MODEL, url); - return new StabilityAiImageModel(stabilityAiApi); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java deleted file mode 100644 index 1437404e85..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatModel.java +++ /dev/null @@ -1,165 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.deepseek; - -import cn.hutool.core.lang.Assert; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata; -import org.springframework.ai.retry.RetryUtils; -import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; -import reactor.core.publisher.Flux; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.ai.core.model.deepseek.DeepSeekChatOptions.MODEL_DEFAULT; - -/** - * DeepSeek {@link ChatModel} 实现类 - * - * @author fansili - */ -@Slf4j -public class DeepSeekChatModel implements ChatModel { - - private static final String BASE_URL = "https://api.deepseek.com"; - - private final DeepSeekChatOptions defaultOptions; - private final RetryTemplate retryTemplate; - - /** - * DeepSeek 兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本 - * - * 不过要注意,DeepSeek 没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它 - */ - private final OpenAiApi openAiApi; - - public DeepSeekChatModel(String apiKey) { - this(apiKey, DeepSeekChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build()); - } - - public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options) { - this(apiKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); - } - - public DeepSeekChatModel(String apiKey, DeepSeekChatOptions options, RetryTemplate retryTemplate) { - Assert.notEmpty(apiKey, "apiKey 不能为空"); - Assert.notNull(options, "options 不能为空"); - Assert.notNull(retryTemplate, "retryTemplate 不能为空"); - this.openAiApi = new OpenAiApi(BASE_URL, apiKey); - this.defaultOptions = options; - this.retryTemplate = retryTemplate; - } - - @Override - public ChatResponse call(Prompt prompt) { - OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false); - return this.retryTemplate.execute(ctx -> { - // 1.1 发起调用 - ResponseEntity completionEntity = openAiApi.chatCompletionEntity(request); - // 1.2 校验结果 - OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody(); - if (chatCompletion == null) { - log.warn("No chat completion returned for prompt: {}", prompt); - return new ChatResponse(List.of()); - } - List choices = chatCompletion.choices(); - if (choices == null) { - log.warn("No choices returned for prompt: {}", prompt); - return new ChatResponse(List.of()); - } - - // 2. 转换 ChatResponse 返回 - List generations = choices.stream().map(choice -> { - Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice)); - if (choice.finishReason() != null) { - generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null)); - } - return generation; - }).toList(); - return new ChatResponse(generations, - OpenAiChatResponseMetadata.from(completionEntity.getBody())); - }); - } - - private Map toMap(String id, OpenAiApi.ChatCompletion.Choice choice) { - Map map = new HashMap<>(); - OpenAiApi.ChatCompletionMessage message = choice.message(); - if (message.role() != null) { - map.put("role", message.role().name()); - } - if (choice.finishReason() != null) { - map.put("finishReason", choice.finishReason().name()); - } - map.put("id", id); - return map; - } - - @Override - public Flux stream(Prompt prompt) { - OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true); - return this.retryTemplate.execute(ctx -> { - // 1. 发起调用 - Flux response = this.openAiApi.chatCompletionStream(request); - return response.map(chatCompletion -> { - String id = chatCompletion.id(); - // 2. 转换 ChatResponse 返回 - List generations = chatCompletion.choices().stream().map(choice -> { - String finish = (choice.finishReason() != null ? choice.finishReason().name() : ""); - String role = (choice.delta().role() != null ? choice.delta().role().name() : ""); - if (choice.finishReason() == OpenAiApi.ChatCompletionFinishReason.STOP) { - // 兜底处理 DeepSeek 返回 STOP 时,role 为空的情况 - role = OpenAiApi.ChatCompletionMessage.Role.ASSISTANT.name(); - } - Generation generation = new Generation(choice.delta().content(), - Map.of("id", id, "role", role, "finishReason", finish)); - if (choice.finishReason() != null) { - generation = generation.withGenerationMetadata( - ChatGenerationMetadata.from(choice.finishReason().name(), null)); - } - return generation; - }).toList(); - return new ChatResponse(generations); - }); - }); - } - - OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { - // 1. 构建 ChatCompletionMessage 对象 - List chatCompletionMessages = prompt.getInstructions().stream().map(m -> - new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList(); - OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream); - - // 2.1 补充 prompt 内置的 options - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, OpenAiChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class); - } else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } - } - // 2.2 补充默认 options - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class); - } - return request; - } - - @Override - public ChatOptions getDefaultOptions() { - return DeepSeekChatOptions.fromOptions(defaultOptions); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatOptions.java deleted file mode 100644 index e07e3f0865..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/deepseek/DeepSeekChatOptions.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.deepseek; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.ai.chat.prompt.ChatOptions; - -/** - * DeepSeek {@link ChatOptions} 实现类 - * - * 参考文档:快速开始 - * - * @author fansili - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class DeepSeekChatOptions implements ChatOptions { - - public static final String MODEL_DEFAULT = "deepseek-chat"; - - /** - * 模型 - */ - private String model; - /** - * 温度 - */ - private Float temperature; - /** - * 最大 Token - */ - private Integer maxTokens; - /** - * topP - */ - private Float topP; - - @Override - public Integer getTopK() { - return null; - } - - public static DeepSeekChatOptions fromOptions(DeepSeekChatOptions fromOptions) { - return DeepSeekChatOptions.builder() - .model(fromOptions.getModel()) - .temperature(fromOptions.getTemperature()) - .maxTokens(fromOptions.getMaxTokens()) - .topP(fromOptions.getTopP()) - .build(); - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java deleted file mode 100644 index 55091c78d4..0000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java +++ /dev/null @@ -1,348 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.midjourney.api; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.openai.api.ApiUtils; -import org.springframework.http.HttpRequest; -import org.springframework.http.HttpStatusCode; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * Midjourney API - * - * @author fansili - * @since 1.0 - */ -@Slf4j -public class MidjourneyApi { - - private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); - - private final Function>> EXCEPTION_FUNCTION = - reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { - HttpRequest request = response.request(); - log.error("[midjourney-api] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]", - request.getMethod(), request.getURI(), reqParam, responseBody); - sink.error(new IllegalStateException("[midjourney-api] 调用失败!")); - }); - - private final WebClient webClient; - - /** - * 回调地址 - */ - private final String notifyUrl; - - public MidjourneyApi(String baseUrl, String apiKey, String notifyUrl) { - this.webClient = WebClient.builder() - .baseUrl(baseUrl) - .defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey)) - .build(); - this.notifyUrl = notifyUrl; - } - - /** - * imagine - 根据提示词提交绘画任务 - * - * @param request 请求 - * @return 提交结果 - */ - public SubmitResponse imagine(ImagineRequest request) { - if (StrUtil.isEmpty(request.getNotifyHook())) { - request.setNotifyHook(notifyUrl); - } - String response = post("/submit/imagine", request); - return JsonUtils.parseObject(response, SubmitResponse.class); - } - - /** - * action - 放大、缩小、U1、U2... - * - * @param request 请求 - * @return 提交结果 - */ - public SubmitResponse action(ActionRequest request) { - if (StrUtil.isEmpty(request.getNotifyHook())) { - request.setNotifyHook(notifyUrl); - } - String response = post("/submit/action", request); - return JsonUtils.parseObject(response, SubmitResponse.class); - } - - /** - * 批量查询 task 任务 - * - * @param ids 任务编号数组 - * @return task 任务 - */ - public List getTaskList(Collection ids) { - String res = post("/task/list-by-condition", ImmutableMap.of("ids", ids)); - return JsonUtils.parseArray(res, Notify.class); - } - - private String post(String uri, Object body) { - return webClient.post() - .uri(uri) - .body(Mono.just(JsonUtils.toJsonString(body)), String.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) - .bodyToMono(String.class) - .block(); - } - - // ========== record 结构 ========== - - /** - * Imagine 请求(生成图片) - */ - @Data - public static final class ImagineRequest { - - /** - * 垫图(参考图) base64 数组 - */ - private List base64Array; - /** - * 提示词 - */ - private String prompt; - /** - * 通知地址 - */ - private String notifyHook; - /** - * 自定义参数 - */ - private String state; - - public ImagineRequest(List base64Array, String prompt, String notifyHook, String state) { - this.base64Array = base64Array; - this.prompt = prompt; - this.notifyHook = notifyHook; - this.state = state; - } - - public static String buildState(Integer width, Integer height, String version, String model) { - StringBuilder params = new StringBuilder(); - // --ar 来设置尺寸 - params.append(String.format(" --ar %s:%s ", width, height)); - // --niji 模型 - if (ModelEnum.NIJI.getModel().equals(model)) { - params.append(String.format(" --niji %s ", version)); - } else { - params.append(String.format(" --v %s ", version)); - } - return params.toString(); - } - - } - - /** - * Action 请求 - */ - @Data - public static final class ActionRequest { - - private String customId; - private String taskId; - private String notifyHook; - - public ActionRequest(String taskId, String customId, String notifyHook) { - this.customId = customId; - this.taskId = taskId; - this.notifyHook = notifyHook; - } - - } - - /** - * Submit 统一返回 - * - * @param code 状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误) - * @param description 描述 - * @param properties 扩展字段 - * @param result 任务ID - */ - public record SubmitResponse(String code, - String description, - Map properties, - String result) { - } - - /** - * 通知 request - * - * @param id job id - * @param action 任务类型 {@link TaskActionEnum} - * @param status 任务状态 {@link TaskStatusEnum} - * @param prompt 提示词 - * @param promptEn 提示词-英文 - * @param description 任务描述 - * @param state 自定义参数 - * @param submitTime 提交时间 - * @param startTime 开始执行时间 - * @param finishTime 结束时间 - * @param imageUrl 图片url - * @param progress 任务进度 - * @param failReason 失败原因 - * @param buttons 任务完成后的可执行按钮 - */ - public record Notify(String id, - String action, - String status, - - String prompt, - String promptEn, - - String description, - String state, - - Long submitTime, - Long startTime, - Long finishTime, - - String imageUrl, - String progress, - String failReason, - List