From 363b2a625ac7eb728f194d30c45bdc019d235741 Mon Sep 17 00:00:00 2001 From: ageer Date: Tue, 27 Feb 2024 23:10:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- .../com/xmzs/system/service/ChatService.java | 27 -- .../com/xmzs/system/service/SseService.java | 49 --- .../system/service/impl/ChatServiceImpl.java | 104 ----- .../system/service/impl/SseServiceImpl.java | 380 ------------------ 5 files changed, 5 insertions(+), 563 deletions(-) delete mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ChatService.java delete mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/SseService.java delete mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatServiceImpl.java delete mode 100644 ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java diff --git a/README.md b/README.md index b171206c..86f9b909 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,11 @@ ## 语音克隆 -
- -
+ + + ## 私有知识库管理(开发中)
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ChatService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ChatService.java deleted file mode 100644 index f12c1d39..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/ChatService.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.xmzs.system.service; - -import com.xmzs.system.domain.bo.ChatMessageBo; - -/** - * @author hncboy - * @date 2023/3/22 19:41 - * 聊天相关业务接口 - */ -public interface ChatService { - - - /** - * 根据消耗的tokens扣除余额 - * - * @param chatMessageBo - * @return 结果 - */ - - void deductToken(ChatMessageBo chatMessageBo); - - /** - * 扣除用户的余额 - * - */ - void deductUserBalance(Long userId, Double numberCost); -} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/SseService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/SseService.java deleted file mode 100644 index 185d0053..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/SseService.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.xmzs.system.service; - - -import com.xmzs.common.chat.domain.request.ChatRequest; -import com.xmzs.common.chat.domain.request.Dall3Request; -import com.xmzs.common.chat.entity.images.Item; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.util.List; - -/** - * 描述: - * - * @author https:www.unfbx.com - * @date 2023-04-08 - */ -public interface SseService { - - /** - * 客户端发送消息到服务端 - * @param chatRequest - */ - SseEmitter sseChat(ChatRequest chatRequest); - - /** - * 绘画接口 - * @param request - */ - List dall3(Dall3Request request); - - - /** - * mj绘画接口 - */ - void mjTask(); - - /** - * 中转接口 - */ - SseEmitter transitChat(ChatRequest chatRequest); - - /** - * azure 聊天接口 - * - * @param chatRequest - * @return - */ - SseEmitter azureChat(ChatRequest chatRequest); -} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatServiceImpl.java deleted file mode 100644 index cb745d63..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/ChatServiceImpl.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.xmzs.system.service.impl; - - -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; -import com.xmzs.common.chat.entity.chat.BaseChatCompletion; -import com.xmzs.common.chat.entity.chat.ChatCompletion; -import com.xmzs.common.core.exception.ServiceException; -import com.xmzs.system.domain.ChatToken; -import com.xmzs.system.domain.SysUser; -import com.xmzs.system.domain.bo.ChatMessageBo; -import com.xmzs.system.mapper.SysUserMapper; -import com.xmzs.system.service.ChatService; -import com.xmzs.system.service.IChatMessageService; -import com.xmzs.system.service.IChatTokenService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -/** - * @author hncboy - * @date 2023/3/22 19:41 - * 聊天相关业务实现类 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class ChatServiceImpl implements ChatService { - - private final SysUserMapper sysUserMapper; - - - private final IChatMessageService chatMessageService; - - private final IChatTokenService chatTokenService; - - - /** - * 根据消耗的tokens扣除余额 - * - * @param chatMessageBo - * - */ - public void deductToken(ChatMessageBo chatMessageBo) { - // 计算总token数 - ChatToken chatToken = chatTokenService.queryByUserId(chatMessageBo.getUserId(), chatMessageBo.getModelName()); - if(chatToken == null){ - chatToken = new ChatToken(); - chatToken.setToken(0); - } - int totalTokens = chatToken.getToken()+ chatMessageBo.getTotalTokens(); - // 如果总token数大于等于1000,进行费用扣除 - if (totalTokens >= 1000) { - // 计算费用 - int token1 = totalTokens / 1000; - int token2 = totalTokens % 1000; - if(token2 > 0){ - // 保存剩余tokens - chatToken.setToken(token2); - chatTokenService.editToken(chatToken); - }else { - chatTokenService.resetToken(chatMessageBo.getUserId(), chatMessageBo.getModelName()); - } - chatMessageBo.setDeductCost(token1 * ChatCompletion.getModelCost(chatMessageBo.getModelName())); - // 扣除用户余额 - deductUserBalance(chatMessageBo.getUserId(), chatMessageBo.getDeductCost()); - } else { - chatMessageBo.setDeductCost(0d); - chatMessageBo.setRemark("不满1kToken,计入下一次!"); - chatToken.setToken(totalTokens); - chatToken.setModelName(chatMessageBo.getModelName()); - chatToken.setUserId(chatMessageBo.getUserId()); - chatTokenService.editToken(chatToken); - } - - // 保存消息记录 - chatMessageService.insertByBo(chatMessageBo); - - } - - /** - * 从用户余额中扣除指定费用 - * - * @param userId 用户ID - * @param numberCost 要扣除的费用 - */ - @Override - public void deductUserBalance(Long userId, Double numberCost) { - SysUser sysUser = sysUserMapper.selectById(userId); - if (sysUser == null) { - return; - } - - Double userBalance = sysUser.getUserBalance(); - if (userBalance < numberCost) { - throw new ServiceException("余额不足,请联系管理员充值!"); - } - - sysUserMapper.update(null, - new LambdaUpdateWrapper() - .set(SysUser::getUserBalance, Math.max(userBalance - numberCost, 0)) - .eq(SysUser::getUserId, userId)); - } - -} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java deleted file mode 100644 index 158cf3c4..00000000 --- a/ruoyi-modules/ruoyi-system/src/main/java/com/xmzs/system/service/impl/SseServiceImpl.java +++ /dev/null @@ -1,380 +0,0 @@ -package com.xmzs.system.service.impl; - - -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.json.JSONUtil; - -import com.azure.ai.openai.OpenAIClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.ai.openai.models.*; -import com.azure.core.credential.AzureKeyCredential; -import com.azure.core.util.IterableStream; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.xmzs.common.chat.config.LocalCache; -import com.xmzs.common.chat.constant.OpenAIConst; -import com.xmzs.common.chat.domain.request.ChatRequest; -import com.xmzs.common.chat.domain.request.Dall3Request; -import com.xmzs.common.chat.entity.chat.*; -import com.xmzs.common.chat.entity.images.Image; -import com.xmzs.common.chat.entity.images.ImageResponse; -import com.xmzs.common.chat.entity.images.Item; -import com.xmzs.common.chat.entity.images.ResponseFormat; -import com.xmzs.common.chat.openai.OpenAiStreamClient; -import com.xmzs.common.chat.utils.TikTokensUtil; -import com.xmzs.common.core.domain.model.LoginUser; -import com.xmzs.common.core.exception.ServiceException; -import com.xmzs.common.core.exception.base.BaseException; - -import com.xmzs.common.core.utils.StringUtils; -import com.xmzs.common.satoken.utils.LoginHelper; -import com.xmzs.common.translation.annotation.Translation; -import com.xmzs.system.domain.SysUser; -import com.xmzs.system.domain.bo.ChatMessageBo; -import com.xmzs.system.listener.SSEEventSourceListener; -import com.xmzs.system.mapper.SysUserMapper; -import com.xmzs.system.service.ChatService; -import com.xmzs.system.service.IChatMessageService; - -import com.xmzs.system.service.SseService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import com.azure.ai.openai.models.ImageGenerationOptions; -import com.azure.core.models.ResponseError; -import com.azure.ai.openai.OpenAIClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.ai.openai.models.ImageGenerationData; -import com.azure.ai.openai.models.ImageGenerationOptions; -import com.azure.ai.openai.models.ImageGenerations; -import com.azure.core.credential.AzureKeyCredential; -/** - * 描述: - * - * @author https:www.unfbx.com - * @date 2023-04-08 - */ -@Service -@Slf4j -@RequiredArgsConstructor -public class SseServiceImpl implements SseService { - private final OpenAiStreamClient openAiStreamClient; - - private final ChatService chatService; - - private final SysUserMapper sysUserMapper; - - private final IChatMessageService chatMessageService; - - @Value("${transit.apiKey}") - private String API_KEY; - - @Value("${transit.apiHost}") - private String API_HOST; - - private static final String DONE_SIGNAL = "[DONE]"; - - @Override - @Transactional - public SseEmitter sseChat(ChatRequest chatRequest) { - LocalCache.CACHE.put("userId",getUserId()); - SseEmitter sseEmitter = new SseEmitter(0L); - SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter); - checkUserGrade(sseEmitter, chatRequest.getModel()); - // 获取对话消息列表 - List msgList = chatRequest.getMessages(); - // 图文识别上下文信息 - List contentList = chatRequest.getContent(); - // 图文识别模型 - if (ChatCompletion.Model.GPT_4_VISION_PREVIEW.getName().equals(chatRequest.getModel())) { - MessagePicture message = MessagePicture.builder().role(Message.Role.USER.getName()).content(contentList).build(); - ChatCompletionWithPicture chatCompletion = ChatCompletionWithPicture - .builder() - .messages(Collections.singletonList(message)) - .model(chatRequest.getModel()) - .temperature(chatRequest.getTemperature()) - .topP(chatRequest.getTop_p()) - .stream(true) - .build(); - openAiStreamClient.streamChatCompletion(chatCompletion, openAIEventSourceListener); - // 扣除图文对话费用 - chatService.deductUserBalance(getUserId(),OpenAIConst.GPT4_COST); - - String text = contentList.get(contentList.size() - 1).getText(); - // 保存消息记录 - ChatMessageBo chatMessageBo = new ChatMessageBo(); - chatMessageBo.setUserId(getUserId()); - chatMessageBo.setModelName(chatRequest.getModel()); - chatMessageBo.setContent(text); - chatMessageBo.setDeductCost(OpenAIConst.GPT4_COST); - chatMessageBo.setTotalTokens(0); - chatMessageService.insertByBo(chatMessageBo); - } else { - ChatCompletion completion = ChatCompletion - .builder() - .messages(msgList) - .model(chatRequest.getModel()) - .temperature(chatRequest.getTemperature()) - .topP(chatRequest.getTop_p()) - .stream(true) - .build(); - openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener); - Message message = msgList.get(msgList.size() - 1); - // 扣除余额 - int tokens = TikTokensUtil.tokens(chatRequest.getModel(), msgList); - ChatMessageBo chatMessageBo = new ChatMessageBo(); - chatMessageBo.setUserId(getUserId()); - chatMessageBo.setModelName(chatRequest.getModel()); - chatMessageBo.setContent(message.getContent()); - chatMessageBo.setTotalTokens(tokens); - chatService.deductToken(chatMessageBo); - } - return sseEmitter; - } - - /** - * dall-e-3绘画接口 - * - * @param request - * @return - */ - public List dall3(Dall3Request request) { - checkUserGrade(null,""); - // DALL3 绘图模型 - Image image = Image.builder() - .responseFormat(ResponseFormat.URL.getName()) - .model(Image.Model.DALL_E_3.getName()) - .prompt(request.getPrompt()) - .n(1) - .quality(request.getQuality()) - .size(request.getSize()) - .style(request.getStyle()) - .build(); - ImageResponse imageResponse = openAiStreamClient.genImages(image); - - // 扣除费用 - if(Objects.equals(request.getSize(), "1792x1024") || Objects.equals(request.getSize(), "1024x1792")){ - chatService.deductUserBalance(getUserId(),OpenAIConst.DALL3_HD_COST); - }else { - chatService.deductUserBalance(getUserId(),OpenAIConst.DALL3_COST); - } - // 保存扣费记录 - ChatMessageBo chatMessageBo = new ChatMessageBo(); - chatMessageBo.setUserId(getUserId()); - chatMessageBo.setModelName(Image.Model.DALL_E_3.getName()); - chatMessageBo.setContent(request.getPrompt()); - chatMessageBo.setDeductCost(OpenAIConst.GPT4_COST); - chatMessageBo.setTotalTokens(0); - chatMessageService.insertByBo(chatMessageBo); - return imageResponse.getData(); - } - - @Override - public void mjTask() { - // 检验是否是免费用户 - checkUserGrade(null,""); - chatService.deductUserBalance(getUserId(),OpenAIConst.MJ_COST); - // 保存扣费记录 - ChatMessageBo chatMessageBo = new ChatMessageBo(); - chatMessageBo.setUserId(getUserId()); - chatMessageBo.setModelName("mj"); - chatMessageBo.setContent("mj绘图"); - chatMessageBo.setDeductCost(OpenAIConst.GPT4_COST); - chatMessageBo.setTotalTokens(0); - chatMessageService.insertByBo(chatMessageBo); - } - - /** - * 中转接口 - * - * @param chatRequest - * @return - */ - @Override - public SseEmitter transitChat(ChatRequest chatRequest) { - // 获取对话消息列表 - List msgList = chatRequest.getMessages(); - Message message = msgList.get(msgList.size() - 1); - SseEmitter emitter = new SseEmitter(0L); - checkUserGrade(emitter, chatRequest.getModel()); - ChatCompletion completion = ChatCompletion - .builder() - .messages(chatRequest.getMessages()) - .model(chatRequest.getModel()) - .temperature(chatRequest.getTemperature()) - .topP(chatRequest.getTop_p()) - .stream(true) - .build(); - // 启动一个新的线程来处理数据流 - new Thread(() -> { - // 启动一个新的线程来处理数据流 - try { - ObjectMapper mapper = new ObjectMapper(); - String requestBody = mapper.writeValueAsString(completion); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(API_HOST + "v1/chat/completions")) - .header("Authorization", "Bearer " + API_KEY) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(requestBody)) - .build(); - // 发送请求并获取响应体作为InputStream - HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofInputStream()); - // 使用正确的字符编码将InputStream包装为InputStreamReader,然后创建BufferedReader - BufferedReader reader = new BufferedReader(new InputStreamReader(response.body())); - String line; - while ((line = reader.readLine()) != null) { - if (line.startsWith("data: ")) { - String data = line.replace("data: ", ""); - emitter.send(data, MediaType.TEXT_PLAIN); - if (data.equals(DONE_SIGNAL)) { - //成功响应 - emitter.complete(); - } - } - } - // 关闭资源 - reader.close(); - } catch (Exception e) { - emitter.complete(); - throw new ServiceException("调用中转接口失败:"+e.getMessage()); - } - }).start(); - chatService.deductUserBalance(getUserId(),OpenAIConst.GPT4_COST); - // 保存消息记录 - ChatMessageBo chatMessageBo = new ChatMessageBo(); - chatMessageBo.setUserId(getUserId()); - chatMessageBo.setModelName(chatRequest.getModel()); - chatMessageBo.setContent(message.getContent()); - chatMessageBo.setDeductCost(OpenAIConst.GPT4_COST); - chatMessageBo.setTotalTokens(0); - chatMessageService.insertByBo(chatMessageBo); - return emitter; - } - - public static void main(String[] args) { - String azureOpenaiKey = "-"; - String endpoint = "-"; - String deploymentOrModelName = "-"; - - OpenAIClient client = new OpenAIClientBuilder() - .endpoint(endpoint) - .credential(new AzureKeyCredential(azureOpenaiKey)) - .buildClient(); - - ImageGenerationOptions imageGenerationOptions = new ImageGenerationOptions( - "A drawing of the Seattle skyline in the style of Van Gogh"); - ImageGenerations images = client.getImageGenerations(deploymentOrModelName, imageGenerationOptions); - - for (ImageGenerationData imageGenerationData : images.getData()) { - System.out.printf( - "Image location URL that provides temporary access to download the generated image is %s.%n", - imageGenerationData.getUrl()); - } - } - - public SseEmitter azureChat(ChatRequest chatRequest) { - String azureOpenaiKey = "-"; - String endpoint = "-"; - String deploymentOrModelId = "-"; - OpenAIClient client = new OpenAIClientBuilder() - .endpoint(endpoint) - .credential(new AzureKeyCredential(azureOpenaiKey)) - .buildClient(); - final SseEmitter emitter = new SseEmitter(); - // 使用线程池异步执行 - ExecutorService service = Executors.newSingleThreadExecutor(); - service.execute(() -> { - try { - // 获取对话消息列表 - List chatMessages = chatRequest.getMessages(); - List messages = new ArrayList<>(); - chatMessages.forEach( - e->{ - ChatRequestMessage chatMessage; - if(Message.Role.SYSTEM.getName().equals(e.getRole())){ - chatMessage = new ChatRequestSystemMessage(e.getContent()); - }else { - chatMessage = new ChatRequestUserMessage(e.getContent()); - } - messages.add(chatMessage); - } - ); - // 获取流式响应 - IterableStream chatCompletionsStream = client.getChatCompletionsStream(deploymentOrModelId, new ChatCompletionsOptions(messages)); - - // 遍历流式响应并发送到客户端 - for (ChatCompletions chatCompletion : chatCompletionsStream) { - - if(CollectionUtil.isEmpty(chatCompletion.getChoices())){ - continue; - } - log.info("json ======{}", JSONUtil.toJsonStr(chatCompletion)); - emitter.send(chatCompletion); - } - emitter.complete(); - } catch (Exception e) { - emitter.completeWithError(e); - } - }); - return emitter; - } - - /** - * 判断用户是否付费 - */ - public void checkUserGrade(SseEmitter emitter, String model) { - SysUser sysUser = sysUserMapper.selectById(getUserId()); - if(StringUtils.isEmpty(model)){ - if("0".equals(sysUser.getUserGrade())){ - throw new ServiceException("免费用户暂时不支持此模型,请切换gpt-3.5-turbo模型或者点击《进入市场选购您的商品》充值后使用!",500); - } - } - // TODO 添加枚举 - if ("0".equals(sysUser.getUserGrade()) && !ChatCompletion.Model.GPT_3_5_TURBO.getName().equals(model)) { - // 创建并发送一个名为 "error" 的事件,带有错误消息和状态码 - SseEmitter.SseEventBuilder event = SseEmitter.event() - .name("error") // 客户端将监听这个事件名 - .data("免费用户暂时不支持此模型,请切换gpt-3.5-turbo模型或者点击《进入市场选购您的商品》充值后使用!"); - try { - emitter.send(event); - } catch (IOException e) { - throw new RuntimeException(e); - } - emitter.complete(); - } - } - - /** - * 获取用户Id - * - * @return - */ - public Long getUserId(){ - LoginUser loginUser = LoginHelper.getLoginUser(); - if (loginUser == null) { - throw new BaseException("用户未登录!"); - } - return loginUser.getUserId(); - } - -}