From ea5a55deffa6d33048a1f7e03b71424c8add5e31 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 08 五月 2026 14:51:52 +0800
Subject: [PATCH] feat(ai): 实现采购多文件分析附件存储与历史回显功能
---
src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java | 172 ++++++++++++++++++++-
doc/20260508_采购多文件分析附件存储与历史回显联调说明.md | 149 ++++++++++++++++++
src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java | 16 ++
src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java | 23 ++
src/main/resources/application-dev.yml | 14 +
src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java | 17 +
src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java | 73 ++++++++
7 files changed, 448 insertions(+), 16 deletions(-)
diff --git "a/doc/20260508_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\351\231\204\344\273\266\345\255\230\345\202\250\344\270\216\345\216\206\345\217\262\345\233\236\346\230\276\350\201\224\350\260\203\350\257\264\346\230\216.md" "b/doc/20260508_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\351\231\204\344\273\266\345\255\230\345\202\250\344\270\216\345\216\206\345\217\262\345\233\236\346\230\276\350\201\224\350\260\203\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..c883a22
--- /dev/null
+++ "b/doc/20260508_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\351\231\204\344\273\266\345\255\230\345\202\250\344\270\216\345\216\206\345\217\262\345\233\236\346\230\276\350\201\224\350\260\203\350\257\264\346\230\216.md"
@@ -0,0 +1,149 @@
+# 閲囪喘澶氭枃浠跺垎鏋愰檮浠跺瓨鍌ㄤ笌鍘嗗彶鍥炴樉鑱旇皟璇存槑
+
+> 鏇存柊鏃堕棿锛�2026-05-08
+> 閫傜敤鑼冨洿锛氶噰璐櫤鑳戒綋澶氭枃浠跺垎鏋� + 鍘嗗彶浼氳瘽闄勪欢鍥炴樉
+
+## 1. 鍙樻洿鑳屾櫙
+
+鍚庣宸茶ˉ榻愪互涓嬭兘鍔涳細
+
+1. `POST /purchase-ai/analyze-files` 涓婁紶鏃跺厛瀛橀檮浠跺埌鏈嶅姟鍣紙鍏叡璁块棶锛夈��
+2. 鎸夋枃浠剁被鍨嬭繑鍥炲彲璁块棶璺緞锛�
+ - 鍥剧墖 / PDF锛氫紭鍏堥瑙堣矾寰�
+ - 鍏跺畠鏂囦欢锛氫紭鍏堜笅杞借矾寰�
+3. 鐢ㄦ埛鎻愰棶涓庨檮浠惰矾寰勫垎寮�瀛樺叆 Mongo銆�
+4. 鍘嗗彶娑堟伅鎺ュ彛鍙寜鈥滄秷鎭淮搴︹�濆洖浼犻檮浠惰矾寰勶紝鍓嶇鍙洿鎺ュ洖鏄鹃檮浠跺崱鐗囥��
+
+---
+
+## 2. 鎺ュ彛琛屼负
+
+### 2.1 澶氭枃浠跺垎鏋愭帴鍙�
+
+```http
+POST /purchase-ai/analyze-files
+Content-Type: multipart/form-data
+```
+
+璇锋眰鍙傛暟锛�
+
+- `files`: `MultipartFile[]`锛堝繀濉級
+- `message`: `string`锛堝彲閫夛級
+- `memoryId`: `string`锛堝彲閫夛級
+
+鍚庣澶勭悊娴佺▼锛�
+
+1. 璋冪敤 `StorageBlobService.upload(files, true)` 涓婁紶骞朵繚瀛橀檮浠躲��
+2. 鐢熸垚闄勪欢璁块棶璺緞锛堥瑙� / 涓嬭浇锛夈��
+3. 灏嗏�滄湰娆℃彁闂� + 鏈闄勪欢璺緞鍒楄〃鈥濆啓鍏� Mongo銆�
+4. 缁х画鎵ц鍘熸湁鏂囦欢瑙f瀽鍜� AI 鍒嗘瀽娴佺▼銆�
+
+鍙兘閿欒锛堟祦寮忔枃鏈級锛�
+
+- `鏂囦欢涓婁紶澶辫触`
+- `浼氳瘽鏂囦欢淇℃伅淇濆瓨澶辫触`
+
+### 2.2 鍘嗗彶娑堟伅鎺ュ彛锛堝墠绔噸鐐癸級
+
+```http
+GET /purchase-ai/history/messages/{memoryId}
+```
+
+娑堟伅瀵硅薄鏂板鍙�夊瓧娈� `filePaths`锛堜粎鐢ㄦ埛娑堟伅鍙兘鏈夊�硷級锛�
+
+```json
+{
+ "role": "user | assistant | system | tool | unknown",
+ "content": "娑堟伅鏂囨湰",
+ "filePaths": [
+ "/common/preview/xxx?publicKey=...",
+ "/common/download/yyy?publicKey=..."
+ ]
+}
+```
+
+璇存槑锛�
+
+1. `filePaths` 鍙兘缂哄け鎴栦负绌猴紙鑰佷細璇� / 鏅�氬璇濓級銆�
+2. 鍗曟潯鐢ㄦ埛娑堟伅鍙兘鍖呭惈澶氫釜闄勪欢璺緞銆�
+3. 璇ュ瓧娈靛凡鎸夋秷鎭淮搴﹀榻愶紝鍙洿鎺ョ敤浜庡巻鍙插洖鏄俱��
+
+---
+
+## 3. 鍓嶇鏀归�犲缓璁紙鎸夊綋鍓嶅疄鐜拌惤鍦帮級
+
+### 3.1 鍘嗗彶鍝嶅簲妯″瀷
+
+寤鸿鍦ㄥ巻鍙叉秷鎭師濮嬬粨鏋勪腑淇濈暀 `filePaths`锛�
+
+```ts
+type AiHistoryMessage = {
+ role: string;
+ content: string;
+ filePaths?: string[];
+};
+```
+
+### 3.2 UI 娑堟伅妯″瀷鏄犲皠
+
+鑻ュ墠绔〉闈㈢敤鐨勬槸 `localUploadFiles` 娓叉煋闄勪欢鍗$墖锛堝 `AIChatSidebar` 褰撳墠瀹炵幇锛夛紝鍘嗗彶娑堟伅闇�鍋氫竴娆℃槧灏勶細
+
+```ts
+type LocalUploadFileItem = {
+ previewId: string;
+ name: string;
+ size: number;
+ type: string;
+ isImage: boolean;
+ previewUrl: string;
+ rawFile: null;
+};
+```
+
+鏄犲皠瑙勫垯寤鸿锛�
+
+1. 浠� `role === 'user'` 涓� `filePaths?.length > 0` 鏃剁敓鎴� `localUploadFiles`銆�
+2. `previewId` 鐢� `${memoryId}-${messageIndex}-${fileIndex}` 鐢熸垚绋冲畾鍊笺��
+3. `name` 鍙粠 URL 璺緞瑙f瀽锛涜В鏋愬け璐ョ敤 `file-{n}`銆�
+4. `isImage` 鍙寜鎵╁睍鍚嶅垽鏂紙`png/jpg/jpeg/gif/webp/bmp/svg`锛夈��
+5. 鍥剧墖鐨� `previewUrl` 鐩存帴浣跨敤璺緞锛涢潪鍥剧墖鍙疆绌哄苟璧板浘鏍囧睍绀恒��
+
+### 3.3 娑堟伅娓叉煋
+
+瀵逛簬鐢ㄦ埛娑堟伅锛�
+
+1. 姝e父娓叉煋 `content`銆�
+2. 鑻ュ瓨鍦� `localUploadFiles`锛堟垨鐩存帴浣跨敤 `filePaths`锛夛紝鍦ㄦ秷鎭笅鏂规覆鏌撻檮浠跺垪琛�/鍗$墖銆�
+3. 閾炬帴鐩存帴浣跨敤鍚庣杩斿洖璺緞锛屼笉鍐嶆嫾鎺ユ垨浜屾鏀瑰啓銆�
+
+> 鍚庣宸插畬鎴愨�滈瑙�/涓嬭浇璺緞鈥濋�夋嫨锛屽墠绔彧璐熻矗灞曠ず涓庢墦寮�銆�
+
+### 3.4 鍏煎瑕佹眰
+
+- `filePaths` 缂哄け锛氫笉鎶ラ敊锛屼笉娓叉煋闄勪欢鍖哄煙銆�
+- 鑰佷細璇濓細缁х画鎸� `role/content` 灞曠ず锛屼笉褰卞搷鍘嗗彶璁板綍鏌ョ湅銆�
+- 澶氶檮浠讹細淇濇寔椤哄簭灞曠ず锛岄伩鍏嶆墦涔辩敤鎴蜂笂浼犻『搴忋��
+
+---
+
+## 4. Mongo 瀛楁璇存槑锛堝悗绔凡瀹炵幇锛�
+
+`chat_messages` 鏂囨。鏂板/浣跨敤瀛楁锛�
+
+- `analyzeUserQuestions: string[]`
+- `analyzeFilePaths: string[]`锛堝吋瀹规棫瀛楁锛�
+- `analyzeFilePathGroups: string[][]`锛堟帹鑽愶紝鎸夋彁闂垎缁勶級
+
+鍘嗗彶娑堟伅鍥炰紶鏃讹紝鍚庣浼樺厛璇诲彇 `analyzeFilePathGroups`锛屽苟鍏煎 `analyzeFilePaths`銆�
+
+---
+
+## 5. 鑱旇皟楠屾敹娓呭崟
+
+1. 鏂板缓浼氳瘽锛屼笂浼� 1 寮犲浘鐗� + 1 涓� Excel锛屽彂閫佸垎鏋愯姹傘��
+2. 璋冪敤 `GET /purchase-ai/history/messages/{memoryId}`锛岀‘璁ょ敤鎴锋秷鎭惈 `filePaths` 涓旀暟閲忔纭��
+3. 鍒锋柊椤甸潰鍚庨噸鏂拌繘鍏ュ悓涓�浼氳瘽锛岀‘璁ら檮浠跺崱鐗囧彲鍥炴樉銆�
+4. 鍒嗗埆楠岃瘉锛�
+ - 鍥剧墖/PDF 鍙瑙堣闂�
+ - 鍏跺畠鏂囦欢鍙笅杞借闂�
+5. 楠岃瘉鑰佷細璇濓紙鏃� `filePaths`锛夊彲姝e父灞曠ず锛岄〉闈笉鎶ラ敊銆�
diff --git a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
index 2bb0625..08dc357 100644
--- a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
+++ b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
@@ -11,8 +11,10 @@
import com.ruoyi.ai.service.AiChatSessionService;
import com.ruoyi.ai.service.AiFileTextExtractor;
import com.ruoyi.ai.store.MongoChatMemoryStore;
+import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.mapper.SupplierManageMapper;
import com.ruoyi.basic.pojo.SupplierManage;
+import com.ruoyi.basic.service.StorageBlobService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;
@@ -50,10 +52,14 @@
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
+import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Base64;
+import java.util.Arrays;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -63,9 +69,11 @@
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
+import java.nio.file.Files;
@Tag(name = "閲囪喘鏅鸿兘浣�")
@RestController
@@ -87,6 +95,7 @@
private final IPurchaseLedgerService purchaseLedgerService;
private final IPaymentRegistrationService paymentRegistrationService;
private final PurchaseReturnOrdersService purchaseReturnOrdersService;
+ private final StorageBlobService storageBlobService;
private final SupplierManageMapper supplierManageMapper;
private final StreamingChatLanguageModel purchaseVisionStreamingChatModel;
@@ -96,12 +105,13 @@
MongoChatMemoryStore mongoChatMemoryStore,
AiChatSessionService aiChatSessionService,
AiFileTextExtractor aiFileTextExtractor,
- ObjectMapper objectMapper,
- IPurchaseLedgerService purchaseLedgerService,
- IPaymentRegistrationService paymentRegistrationService,
- PurchaseReturnOrdersService purchaseReturnOrdersService,
- SupplierManageMapper supplierManageMapper,
- @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) {
+ ObjectMapper objectMapper,
+ IPurchaseLedgerService purchaseLedgerService,
+ IPaymentRegistrationService paymentRegistrationService,
+ PurchaseReturnOrdersService purchaseReturnOrdersService,
+ StorageBlobService storageBlobService,
+ SupplierManageMapper supplierManageMapper,
+ @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) {
this.purchaseAgent = purchaseAgent;
this.purchaseIntentExecutor = purchaseIntentExecutor;
this.aiSessionUserContext = aiSessionUserContext;
@@ -112,6 +122,7 @@
this.purchaseLedgerService = purchaseLedgerService;
this.paymentRegistrationService = paymentRegistrationService;
this.purchaseReturnOrdersService = purchaseReturnOrdersService;
+ this.storageBlobService = storageBlobService;
this.supplierManageMapper = supplierManageMapper;
this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel;
}
@@ -172,6 +183,19 @@
? message
: "璇峰垎鏋愯繖浜涢噰璐枃浠讹紝鎻愬彇鍙敤浜庝笟鍔″鐞嗙殑鏁版嵁锛屽苟鏁寸悊鎴愬緟瀹㈡埛纭鐨勬牸寮�";
+ List<String> filePaths;
+ try {
+ List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true);
+ filePaths = resolveFileAccessPaths(uploadedFiles);
+ } catch (Exception ex) {
+ return Flux.just("鏂囦欢涓婁紶澶辫触");
+ }
+ try {
+ mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths);
+ } catch (Exception ex) {
+ return Flux.just("浼氳瘽鏂囦欢淇℃伅淇濆瓨澶辫触");
+ }
+
String fileContent;
try {
fileContent = buildMultiFileContent(files);
@@ -189,7 +213,7 @@
aiChatSessionService.touchSession(finalMemoryId, loginUser, "閲囪喘澶氭枃浠跺垎鏋�: " + finalMessage);
if (containsImageFile(files)) {
- return chatWithPurchaseVisionModel(finalMemoryId, userPrompt, files)
+ return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files)
.doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
.doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
}
@@ -282,8 +306,121 @@
return false;
}
- private Flux<String> chatWithPurchaseVisionModel(String memoryId, String userPrompt, MultipartFile[] files) {
+ private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) {
+ if (StringUtils.isEmpty(uploadedFiles)) {
+ return Collections.emptyList();
+ }
+ List<String> filePaths = new ArrayList<>();
+ for (StorageBlobVO uploadedFile : uploadedFiles) {
+ if (uploadedFile == null) {
+ continue;
+ }
+ String selectedPath;
+ if (shouldUsePreviewPath(uploadedFile)) {
+ selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL())
+ ? uploadedFile.getPreviewURL()
+ : uploadedFile.getDownloadURL();
+ } else {
+ selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL())
+ ? uploadedFile.getDownloadURL()
+ : uploadedFile.getPreviewURL();
+ }
+ if (StringUtils.hasText(selectedPath)) {
+ filePaths.add(selectedPath);
+ }
+ }
+ return filePaths;
+ }
+
+ private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) {
+ String contentType = uploadedFile.getContentType();
+ if (StringUtils.hasText(contentType)) {
+ String normalized = contentType.toLowerCase(Locale.ROOT);
+ if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) {
+ return true;
+ }
+ }
+ String filename = uploadedFile.getOriginalFilename();
+ if (!StringUtils.hasText(filename) || !filename.contains(".")) {
+ return false;
+ }
+ String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT);
+ return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf");
+ }
+
+ private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException {
+ List<MultipartFile> copies = new ArrayList<>();
+ for (MultipartFile file : files) {
+ copies.add(new InMemoryMultipartFile(
+ file.getName(),
+ file.getOriginalFilename(),
+ file.getContentType(),
+ file.getBytes()
+ ));
+ }
+ return copies;
+ }
+
+ private static final class InMemoryMultipartFile implements MultipartFile {
+ private final String name;
+ private final String originalFilename;
+ private final String contentType;
+ private final byte[] bytes;
+
+ private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
+ this.name = name;
+ this.originalFilename = originalFilename;
+ this.contentType = contentType;
+ this.bytes = bytes == null ? new byte[0] : bytes;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getOriginalFilename() {
+ return originalFilename;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return bytes.length == 0;
+ }
+
+ @Override
+ public long getSize() {
+ return bytes.length;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return bytes.clone();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public void transferTo(File dest) throws IOException, IllegalStateException {
+ Files.write(dest.toPath(), bytes);
+ }
+ }
+
+ private Flux<String> chatWithPurchaseVisionModel(String memoryId,
+ String userMessage,
+ String userPrompt,
+ MultipartFile[] files) {
return Flux.create(sink -> {
+ StringBuilder assistantReply = new StringBuilder();
try {
List<Content> contents = new ArrayList<>();
contents.add(TextContent.from(userPrompt));
@@ -302,14 +439,21 @@
SystemMessage.from("浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝浠庢枃鏈拰鍥剧墖涓瘑鍒噰璐彴璐︺�侀噰璐骇鍝佹槑缁嗐�佷粯娆炬垨閫�璐т俊鎭紝鍙緭鍑哄悎娉� JSON銆�"),
UserMessage.from(contents)
);
+ safeAppendMessages(memoryId, List.of(UserMessage.from("閲囪喘澶氭枃浠跺垎鏋�: " + userMessage)));
purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
- sink.next(partialResponse);
+ if (partialResponse != null) {
+ assistantReply.append(partialResponse);
+ sink.next(partialResponse);
+ }
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
+ if (StringUtils.hasText(assistantReply.toString())) {
+ safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString())));
+ }
sink.complete();
}
@@ -325,6 +469,16 @@
});
}
+ private void safeAppendMessages(String memoryId, List<ChatMessage> messages) {
+ if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) {
+ return;
+ }
+ try {
+ mongoChatMemoryStore.appendMessages(memoryId, messages);
+ } catch (Exception ignored) {
+ }
+ }
+
private String resolveImageMimeType(MultipartFile file) {
String contentType = file.getContentType();
if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) {
diff --git a/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java b/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
index 9242b3e..081bf12 100644
--- a/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
+++ b/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
@@ -1,15 +1,28 @@
package com.ruoyi.ai.dto;
-import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import java.util.List;
+
@Data
@NoArgsConstructor
-@AllArgsConstructor
public class AiChatMessageDto {
private String role;
private String content;
+
+ private List<String> filePaths;
+
+ public AiChatMessageDto(String role, String content) {
+ this.role = role;
+ this.content = content;
+ }
+
+ public AiChatMessageDto(String role, String content, List<String> filePaths) {
+ this.role = role;
+ this.content = content;
+ this.filePaths = filePaths;
+ }
}
diff --git a/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java b/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
index 0a239d8..e2944cb 100644
--- a/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
+++ b/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
@@ -9,6 +9,7 @@
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
+import java.util.List;
@Data
@AllArgsConstructor
@@ -24,6 +25,21 @@
private String content;
+ /**
+ * 澶氭枃浠跺垎鏋愮敤鎴锋彁闂俊鎭紙涓庢枃浠惰矾寰勫垎寮�瀛樺偍锛�
+ */
+ private List<String> analyzeUserQuestions;
+
+ /**
+ * 澶氭枃浠跺垎鏋愪笂浼犳枃浠惰矾寰勶紙鍥剧墖鍜� pdf 浣跨敤棰勮鍦板潃锛屽叾浠栦娇鐢ㄤ笅杞藉湴鍧�锛�
+ */
+ private List<String> analyzeFilePaths;
+
+ /**
+ * 澶氭枃浠跺垎鏋愭瘡娆℃彁闂搴旂殑鏂囦欢璺緞鍒嗙粍
+ */
+ private List<List<String>> analyzeFilePathGroups;
+
private Date createTime;
private Date updateTime;
diff --git a/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java b/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
index 6d8c945..0ea436d 100644
--- a/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
+++ b/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
@@ -114,7 +114,10 @@
return new LinkedList<>();
}
List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId);
- return messages.stream().map(this::convertMessage).collect(Collectors.toList());
+ List<AiChatMessageDto> messageDtos = messages.stream().map(this::convertMessage).collect(Collectors.toList());
+ List<List<String>> analyzeFilePathGroups = mongoChatMemoryStore.getAnalyzeFilePathGroups(memoryId);
+ attachAnalyzeFilePaths(messageDtos, analyzeFilePathGroups);
+ return messageDtos;
}
@Override
@@ -188,4 +191,22 @@
}
return new AiChatMessageDto("unknown", String.valueOf(message));
}
+
+ private void attachAnalyzeFilePaths(List<AiChatMessageDto> messages,
+ List<List<String>> analyzeFilePathGroups) {
+ if (StringUtils.isEmpty(messages) || StringUtils.isEmpty(analyzeFilePathGroups)) {
+ return;
+ }
+ int analyzeIndex = 0;
+ for (AiChatMessageDto message : messages) {
+ if (!"user".equals(message.getRole()) || analyzeIndex >= analyzeFilePathGroups.size()) {
+ continue;
+ }
+ List<String> filePaths = analyzeFilePathGroups.get(analyzeIndex);
+ if (!StringUtils.isEmpty(filePaths)) {
+ message.setFilePaths(filePaths);
+ }
+ analyzeIndex++;
+ }
+ }
}
diff --git a/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java b/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
index e88f0e9..e6bcadb 100644
--- a/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
+++ b/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
@@ -11,6 +11,8 @@
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.LinkedList;
@@ -24,8 +26,7 @@
@Override
public List<ChatMessage> getMessages(Object memoryId) {
- Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
- ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);
+ ChatMessages chatMessages = findChatMessages(memoryId);
if (chatMessages == null || chatMessages.getContent() == null) {
return new LinkedList<>();
}
@@ -56,7 +57,75 @@
updateMessages(memoryId, messages);
}
+ public void appendAnalyzeFileContext(Object memoryId, String userQuestion, List<String> filePaths) {
+ String memoryIdValue = memoryIdString(memoryId);
+ if (!StringUtils.hasText(memoryIdValue)) {
+ return;
+ }
+ List<String> validFilePaths = new LinkedList<>();
+ if (!CollectionUtils.isEmpty(filePaths)) {
+ for (String filePath : filePaths) {
+ if (StringUtils.hasText(filePath)) {
+ validFilePaths.add(filePath);
+ }
+ }
+ }
+ if (!StringUtils.hasText(userQuestion) && validFilePaths.isEmpty()) {
+ return;
+ }
+ Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue));
+ Update update = new Update();
+ update.set("memoryId", memoryIdValue);
+ update.set("updateTime", new Date());
+ update.setOnInsert("createTime", new Date());
+ if (StringUtils.hasText(userQuestion)) {
+ update.push("analyzeUserQuestions", userQuestion);
+ }
+ if (!validFilePaths.isEmpty()) {
+ update.push("analyzeFilePaths").each(validFilePaths.toArray());
+ update.push("analyzeFilePathGroups", validFilePaths);
+ }
+ mongoTemplate.upsert(query, update, ChatMessages.class);
+ }
+
+ public List<String> getAnalyzeUserQuestions(Object memoryId) {
+ ChatMessages chatMessages = findChatMessages(memoryId);
+ if (chatMessages == null || CollectionUtils.isEmpty(chatMessages.getAnalyzeUserQuestions())) {
+ return new LinkedList<>();
+ }
+ return new LinkedList<>(chatMessages.getAnalyzeUserQuestions());
+ }
+
+ public List<List<String>> getAnalyzeFilePathGroups(Object memoryId) {
+ ChatMessages chatMessages = findChatMessages(memoryId);
+ if (chatMessages == null) {
+ return new LinkedList<>();
+ }
+ if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePathGroups())) {
+ if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePaths())) {
+ return new LinkedList<>();
+ }
+ List<List<String>> fallback = new LinkedList<>();
+ fallback.add(new LinkedList<>(chatMessages.getAnalyzeFilePaths()));
+ return fallback;
+ }
+ List<List<String>> groups = new LinkedList<>();
+ for (List<String> group : chatMessages.getAnalyzeFilePathGroups()) {
+ if (CollectionUtils.isEmpty(group)) {
+ groups.add(new LinkedList<>());
+ } else {
+ groups.add(new LinkedList<>(group));
+ }
+ }
+ return groups;
+ }
+
private String memoryIdString(Object memoryId) {
return memoryId == null ? "" : memoryId.toString();
}
+
+ private ChatMessages findChatMessages(Object memoryId) {
+ Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
+ return mongoTemplate.findOne(query, ChatMessages.class);
+ }
}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index ef003c9..334e5d7 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -254,8 +254,18 @@
# 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
allowOverwrite: false
+# 鏂囦欢涓婁紶閰嶇疆
file:
- temp-dir: D:/ruoyi/temp/uploads # 涓存椂鐩綍
- upload-dir: D:/ruoyi/prod/uploads # 姝e紡鐩綍
+ temp-dir: D:/ruoyi/temp/uploads # 涓存椂鐩綍 鍚庢湡鍒犻櫎
+ upload-dir: D:/ruoyi/prod/uploads # 姝e紡鐩綍 鍚庢湡鍒犻櫎
+ path: D:/ruoyi/prod/uploads # 涓婁紶鐩綍
+ urlPrefix: /common # 閾炬帴鍓嶇紑
+ domain: http://127.0.0.1:7005 # 鍩熷悕鍓嶇紑
+ expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
+ useLimit: 10 # 浣跨敤娆℃暟
+ compress: true # 鏄惁鍘嬬缉
+ needCompressSize: 10MB # 鍘嬬缉闃堝��
+ compressQuality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
knowledge:
one: D:\鏂扮枂澶х綏绱犱紒涓氫骇鍝佷綋绯昏鏄庢枃妗�.md
+
--
Gitblit v1.9.3