| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.controller; |
| | | |
| | | import com.ruoyi.ai.assistant.ApproveTodoAgent; |
| | | import com.ruoyi.ai.assistant.ApproveTodoIntentExecutor; |
| | | import com.ruoyi.ai.assistant.FileAnalyzeAgent; |
| | | import com.ruoyi.ai.bean.ChatForm; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.ai.service.AiChatSessionService; |
| | | import com.ruoyi.ai.service.AiFileTextExtractor; |
| | | import com.ruoyi.ai.store.MongoChatMemoryStore; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.framework.web.controller.BaseController; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import dev.langchain4j.data.message.AiMessage; |
| | | import dev.langchain4j.data.message.UserMessage; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import org.springframework.web.bind.annotation.DeleteMapping; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | 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.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import reactor.core.publisher.Flux; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.List; |
| | | import java.util.NoSuchElementException; |
| | | import java.util.UUID; |
| | | |
| | | @Tag(name = "åååå
¬å©æ") |
| | | @RestController |
| | | @RequestMapping("/xiaozhi") |
| | | public class XiaozhiController extends BaseController { |
| | | |
| | | private static final String FILE_ANALYZE_MEMORY_PREFIX = "file-analyze::"; |
| | | |
| | | private final ApproveTodoAgent approveTodoAgent; |
| | | private final ApproveTodoIntentExecutor approveTodoIntentExecutor; |
| | | private final FileAnalyzeAgent fileAnalyzeAgent; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | private final MongoChatMemoryStore mongoChatMemoryStore; |
| | | private final AiFileTextExtractor aiFileTextExtractor; |
| | | private final AiChatSessionService aiChatSessionService; |
| | | |
| | | public XiaozhiController(ApproveTodoAgent approveTodoAgent, |
| | | ApproveTodoIntentExecutor approveTodoIntentExecutor, |
| | | FileAnalyzeAgent fileAnalyzeAgent, |
| | | AiSessionUserContext aiSessionUserContext, |
| | | MongoChatMemoryStore mongoChatMemoryStore, |
| | | AiFileTextExtractor aiFileTextExtractor, |
| | | AiChatSessionService aiChatSessionService) { |
| | | this.approveTodoAgent = approveTodoAgent; |
| | | this.approveTodoIntentExecutor = approveTodoIntentExecutor; |
| | | this.fileAnalyzeAgent = fileAnalyzeAgent; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | this.mongoChatMemoryStore = mongoChatMemoryStore; |
| | | this.aiFileTextExtractor = aiFileTextExtractor; |
| | | this.aiChatSessionService = aiChatSessionService; |
| | | } |
| | | |
| | | @Operation(summary = "对è¯") |
| | | @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> chat(@RequestBody ChatForm chatForm) { |
| | | if (!StringUtils.hasText(chatForm.getMemoryId())) { |
| | | return Flux.just("memoryIdä¸è½ä¸ºç©º"); |
| | | } |
| | | if (!StringUtils.hasText(chatForm.getMessage())) { |
| | | return Flux.just("messageä¸è½ä¸ºç©º"); |
| | | } |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | String memoryId = chatForm.getMemoryId(); |
| | | String userMessage = chatForm.getMessage(); |
| | | |
| | | aiSessionUserContext.bind(memoryId, loginUser); |
| | | aiChatSessionService.touchSession(memoryId, loginUser, userMessage); |
| | | |
| | | String directResponse = approveTodoIntentExecutor.tryExecute(memoryId, userMessage); |
| | | if (StringUtils.isNotEmpty(directResponse)) { |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(directResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | return approveTodoAgent.chat(memoryId, userMessage) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "ä¸ä¼ æä»¶åæ") |
| | | @PostMapping(value = "/analyze-file", consumes = "multipart/form-data", produces = "text/stream;charset=utf-8") |
| | | public Flux<String> analyzeFile(@RequestParam("file") MultipartFile file, |
| | | @RequestParam(value = "message", required = false) String message, |
| | | @RequestParam(value = "memoryId", required = false) String memoryId) { |
| | | String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString(); |
| | | String finalMemoryId = rawMemoryId.startsWith(FILE_ANALYZE_MEMORY_PREFIX) |
| | | ? rawMemoryId |
| | | : FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId; |
| | | |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | aiSessionUserContext.bind(finalMemoryId, loginUser); |
| | | |
| | | String finalMessage = StringUtils.hasText(message) ? message : "请åæè¿ä¸ªæä»¶çæ ¸å¿å
容并ç»åºæ»ç»"; |
| | | String fileText; |
| | | try { |
| | | fileText = aiFileTextExtractor.extractText(file); |
| | | } catch (IllegalArgumentException ex) { |
| | | return Flux.just(ex.getMessage()); |
| | | } catch (IOException ex) { |
| | | return Flux.just("æä»¶è¯»å失败"); |
| | | } |
| | | |
| | | if (!StringUtils.hasText(fileText)) { |
| | | return Flux.just("æªæåå°æææä»¶å
容"); |
| | | } |
| | | |
| | | String limitedContent = fileText.length() > 12000 ? fileText.substring(0, 12000) : fileText; |
| | | String userPrompt = "ä½ å°åæç¨æ·ä¸ä¼ çæä»¶å
容ã\n" |
| | | + "æä»¶å: " + file.getOriginalFilename() + "\n" |
| | | + "ç¨æ·é®é¢: " + finalMessage + "\n" |
| | | + "æä»¶å
容å¦ä¸:\n" |
| | | + limitedContent; |
| | | |
| | | aiChatSessionService.touchSession(finalMemoryId, loginUser, "æä»¶åæ: " + finalMessage); |
| | | |
| | | return Flux.defer(() -> fileAnalyzeAgent.chat(finalMemoryId, userPrompt)) |
| | | .onErrorResume(NoSuchElementException.class, ex -> { |
| | | // DashScope å¨å岿¶æ¯å
¼å®¹åºæ¯ä¸å¯è½æ NoSuchElementExceptionï¼æ¸
空åéè¯ä¸æ¬¡ |
| | | mongoChatMemoryStore.deleteMessages(finalMemoryId); |
| | | return fileAnalyzeAgent.chat(finalMemoryId, userPrompt); |
| | | }) |
| | | .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) |
| | | .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); |
| | | } |
| | | |
| | | @Operation(summary = "ä¼è¯å表") |
| | | @GetMapping("/history/sessions") |
| | | public AjaxResult listSessions() { |
| | | return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "ä¼è¯æ¶æ¯") |
| | | @GetMapping("/history/messages/{memoryId}") |
| | | public AjaxResult listMessages(@PathVariable String memoryId) { |
| | | return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | |
| | | @Operation(summary = "å é¤ä¼è¯") |
| | | @DeleteMapping("/history/{memoryId}") |
| | | public AjaxResult deleteSession(@PathVariable String memoryId) { |
| | | aiSessionUserContext.remove(memoryId); |
| | | return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser())); |
| | | } |
| | | } |