瀏覽代碼

智能体页面修改

lamphua 16 小時之前
父節點
當前提交
e837ca4aea

+ 3
- 0
oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/oa/CmcDeviceApprovalController.java 查看文件

@@ -145,6 +145,9 @@ public class CmcDeviceApprovalController extends BaseController
145 145
             cmcDeviceApproval.setDispatchTime(new Date());
146 146
         }
147 147
         if (formDataJson.getJSONArray("modifyDevices").size() > 0 && formDataJson.getString("managerComment") == null) {
148
+            cmcDeviceApproval.setDispatcher(getLoginUser().getUserId());
149
+            cmcDeviceApproval.setDispatchComment(formDataJson.getString("dispatchComment"));
150
+            cmcDeviceApproval.setDispatchTime(new Date());
148 151
             String deviceString = formDataJson.getString("modifyDevices").substring(1, formDataJson.getString("modifyDevices").length() - 1);
149 152
             cmcDeviceApproval.setModifyDevices(deviceString);
150 153
             if (!formDataJson.getString("projectId").equals("")) {

+ 18
- 2
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcAgentController.java 查看文件

@@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.PathVariable;
15 15
 import org.springframework.web.bind.annotation.RequestBody;
16 16
 import org.springframework.web.bind.annotation.RequestMapping;
17 17
 import org.springframework.web.bind.annotation.RestController;
18
+
19
+import com.alibaba.fastjson2.JSONObject;
18 20
 import com.ruoyi.common.annotation.Log;
19 21
 import com.ruoyi.common.core.controller.BaseController;
20 22
 import com.ruoyi.common.core.domain.AjaxResult;
@@ -27,7 +29,7 @@ import org.springframework.web.multipart.MultipartFile;
27 29
 
28 30
 /**
29 31
  * 智能体Controller
30
- * 
32
+ *
31 33
  * @author ruoyi
32 34
  * @date 2025-07-17
33 35
  */
@@ -79,6 +81,20 @@ public class CmcAgentController extends BaseController
79 81
         return success(ChatMessage.ofAssistant(cmcAgentService.getOpening(agentName)));
80 82
     }
81 83
 
84
+    /**
85
+     * 保存目录到Word文件
86
+     */
87
+    @PostMapping("/writeTitles")
88
+    public AjaxResult writeTitles(@RequestBody JSONObject data) throws IOException
89
+    {
90
+        JSONObject result = cmcAgentService.writeTitles(data);
91
+        if (result.getIntValue("code") == 200) {
92
+            return AjaxResult.success(result.getString("message"));
93
+        } else {
94
+            return AjaxResult.error(result.getString("message"));
95
+        }
96
+    }
97
+
82 98
     /**
83 99
      * 上传单文件
84 100
      * @return
@@ -143,7 +159,7 @@ public class CmcAgentController extends BaseController
143 159
      * 删除智能体
144 160
      */
145 161
     @Log(title = "智能体", businessType = BusinessType.DELETE)
146
-	@DeleteMapping("/{agentIds}")
162
+    @DeleteMapping("/{agentIds}")
147 163
     public AjaxResult remove(@PathVariable Integer[] agentIds)
148 164
     {
149 165
         return success(cmcAgentService.deleteCmcAgentByAgentIds(agentIds));

+ 16
- 8
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcAgentService.java 查看文件

@@ -9,15 +9,15 @@ import java.util.List;
9 9
 
10 10
 /**
11 11
  * 智能体Service接口
12
- * 
12
+ *
13 13
  * @author ruoyi
14 14
  * @date 2025-07-17
15 15
  */
16
-public interface ICmcAgentService 
16
+public interface ICmcAgentService
17 17
 {
18 18
     /**
19 19
      * 查询智能体
20
-     * 
20
+     *
21 21
      * @param agentId 智能体主键
22 22
      * @return 智能体
23 23
      */
@@ -25,7 +25,7 @@ public interface ICmcAgentService
25 25
 
26 26
     /**
27 27
      * 查询智能体列表
28
-     * 
28
+     *
29 29
      * @param cmcAgent 智能体
30 30
      * @return 智能体集合
31 31
      */
@@ -72,7 +72,7 @@ public interface ICmcAgentService
72 72
 
73 73
     /**
74 74
      * 新增智能体
75
-     * 
75
+     *
76 76
      * @param cmcAgent 智能体
77 77
      * @return 结果
78 78
      */
@@ -80,7 +80,7 @@ public interface ICmcAgentService
80 80
 
81 81
     /**
82 82
      * 修改智能体
83
-     * 
83
+     *
84 84
      * @param cmcAgent 智能体
85 85
      * @return 结果
86 86
      */
@@ -88,7 +88,7 @@ public interface ICmcAgentService
88 88
 
89 89
     /**
90 90
      * 批量删除智能体
91
-     * 
91
+     *
92 92
      * @param agentIds 需要删除的智能体主键集合
93 93
      * @return 结果
94 94
      */
@@ -96,9 +96,17 @@ public interface ICmcAgentService
96 96
 
97 97
     /**
98 98
      * 删除智能体信息
99
-     * 
99
+     *
100 100
      * @param agentId 智能体主键
101 101
      * @return 结果
102 102
      */
103 103
     public int deleteCmcAgentByAgentId(Integer agentId);
104
+
105
+    /**
106
+     * 保存目录到Word文件
107
+     *
108
+     * @param data 目录数据
109
+     * @return 结果
110
+     */
111
+    public JSONObject writeTitles(JSONObject data) throws IOException;
104 112
 }

+ 360
- 142
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java 查看文件

@@ -1,17 +1,20 @@
1 1
 package com.ruoyi.llm.service.impl;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONObject;
4
+import com.alibaba.fastjson2.JSONArray;
4 5
 import com.ruoyi.common.config.RuoYiConfig;
5 6
 import com.ruoyi.common.utils.DateUtils;
6 7
 import com.ruoyi.common.utils.SecurityUtils;
7 8
 import com.ruoyi.common.utils.SnowFlake;
8 9
 import com.ruoyi.llm.domain.CmcAgent;
9 10
 import com.ruoyi.llm.domain.CmcChat;
11
+import com.ruoyi.llm.domain.CmcTopic;
10 12
 import com.ruoyi.llm.domain.CmcDocument;
11 13
 import com.ruoyi.llm.mapper.CmcAgentMapper;
12 14
 import com.ruoyi.llm.mapper.CmcChatMapper;
13 15
 import com.ruoyi.llm.mapper.CmcDocumentMapper;
14 16
 import com.ruoyi.llm.service.ICmcAgentService;
17
+import com.ruoyi.llm.service.ICmcTopicService;
15 18
 import dev.langchain4j.data.document.Document;
16 19
 import dev.langchain4j.data.document.parser.TextDocumentParser;
17 20
 import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
@@ -41,10 +44,8 @@ import org.noear.solon.ai.chat.ChatModel;
41 44
 import org.noear.solon.ai.chat.ChatResponse;
42 45
 import org.noear.solon.ai.chat.ChatSession;
43 46
 import org.noear.solon.ai.chat.message.ChatMessage;
47
+import org.noear.solon.ai.chat.prompt.Prompt;
44 48
 import org.noear.solon.ai.chat.session.InMemoryChatSession;
45
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber;
46
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
47
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
48 49
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrGeneral;
49 50
 import org.springframework.beans.factory.annotation.Autowired;
50 51
 import org.springframework.beans.factory.annotation.Value;
@@ -78,6 +79,9 @@ public class CmcAgentServiceImpl implements ICmcAgentService
78 79
     @Autowired
79 80
     private CmcChatMapper cmcChatMapper;
80 81
 
82
+    @Autowired
83
+    private ICmcTopicService cmcTopicService;
84
+
81 85
     private String processValue = "";
82 86
 
83 87
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
@@ -95,10 +99,10 @@ public class CmcAgentServiceImpl implements ICmcAgentService
95 99
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
96 100
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
97 101
         }
98
-//        milvusClient = new MilvusClientV2(
99
-//                ConnectConfig.builder()
100
-//                        .uri(milvusServiceUrl)
101
-//                        .build());
102
+        milvusClient = new MilvusClientV2(
103
+                ConnectConfig.builder()
104
+                        .uri(milvusServiceUrl)
105
+                        .build());
102 106
     }
103 107
 
104 108
     @PreDestroy
@@ -189,7 +193,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
189 193
         cmcChat.setInput("招标文件地址:" + prefixPath + "/" + file.getOriginalFilename());
190 194
         cmcChat.setUserId(SecurityUtils.getUserId());
191 195
         cmcChatMapper.insertCmcChat(cmcChat);
192
-        if (agentName.contains("技术")) {
196
+        if (agentName.contains("技术标书")) {
193 197
             String[] filenameSplit = file.getOriginalFilename().split("\\.");
194 198
             String outputFilename = prefixPath + "/" + file.getOriginalFilename()
195 199
                     .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
@@ -197,36 +201,65 @@ public class CmcAgentServiceImpl implements ICmcAgentService
197 201
                 outputFilename = outputFilename.replace(".doc", ".docx");
198 202
             if (file.getOriginalFilename().endsWith(".pdf"))
199 203
                 outputFilename = outputFilename.replace(".pdf", ".docx");
200
-            Path outputFilePath = Paths.get(RuoYiConfig.getProfile() + outputFilename);
201
-            Files.deleteIfExists(outputFilePath);
202
-            InputStream fileInputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
203
-            try (XWPFDocument doc = new XWPFDocument(fileInputStream)) {
204
-                // 保存文档到本地文件系统
205
-                try (FileOutputStream out = new FileOutputStream(RuoYiConfig.getProfile() + outputFilename)) {
206
-                    doc.write(out);
207
-                }
208
-            }
209
-            String question = "工作大纲/工作范围/招标范围/服务范围";
210
-            String chapters = generateAnswerWithDocument(profilePath + "/" + file.getOriginalFilename(),
211
-                    RuoYiConfig.getProfile() + outputFilename, question);
212
-
213
-            // 生成详细目录结构(包含二三级标题)
214
-            String detailedDirectory = generateDetailedDirectory(profilePath + "/" + file.getOriginalFilename());
215
-
204
+            
205
+            // 分割文档
206
+            List<TextSegment> segments = splitDocument(transferFile, 300, 50);
207
+            
216 208
             // 分析项目概况
217
-            String projectOverview = analyzeProjectOverview(profilePath + "/" + file.getOriginalFilename());
209
+            String projectOverview = analyzeProjectOverview(profilePath + "/" + file.getOriginalFilename(), segments);
210
+            jsonObject.put("projectOverview", projectOverview);
218 211
 
219 212
             // 分析评分要求
220
-            String scoringRequirements = analyzeScoringRequirements(profilePath + "/" + file.getOriginalFilename());
213
+            String scoringRequirements = analyzeScoringRequirements(profilePath + "/" + file.getOriginalFilename(), segments, agentName);
214
+            jsonObject.put("scoringRequirements", scoringRequirements);
215
+
216
+            // 生成详细目录结构(包含二三级标题)并写入文件
217
+            JSONArray detailedDirectoryTree = generateDetailedDirectory(file.getOriginalFilename(), agentName);
218
+            String detailedDirectory = buildDirectoryText(detailedDirectoryTree);
219
+            jsonObject.put("detailedDirectory", detailedDirectoryTree);
220
+            jsonObject.put("directoryText", detailedDirectory);
221 221
 
222
-            message = "好的,我已经收到您上传的招标文件。以下为根据招标文件生成的章节大纲:\n\n"+ chapters + "\n\n" +
222
+            message = "好的,我已经收到您上传的招标文件。以下为根据招标文件生成的章节大纲:\n\n"+ detailedDirectory + "\n\n" +
223 223
                     "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
224 224
                     "思考时间可能较长,请耐心等待!\n";
225 225
 
226
-            // 将分析结果添加到返回对象中
227
-            jsonObject.put("detailedDirectory", detailedDirectory);
228
-            jsonObject.put("projectOverview", projectOverview);
229
-            jsonObject.put("scoringRequirements", scoringRequirements);
226
+            // 返回输出文件名,以便前端在保存目录时使用
227
+            jsonObject.put("filename", file.getOriginalFilename()
228
+                    .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName));
229
+            
230
+            // 根据 agentName 查询 agentId
231
+            CmcAgent queryAgent = new CmcAgent();
232
+            queryAgent.setAgentName(agentName);
233
+            List<CmcAgent> agentList = cmcAgentMapper.selectCmcAgentList(queryAgent);
234
+            Integer agentId = null;
235
+            if (!agentList.isEmpty()) {
236
+                agentId = agentList.get(0).getAgentId();
237
+            }
238
+            
239
+            // 去除文件名后缀作为 topic 名称
240
+            String originalFilename = file.getOriginalFilename();
241
+            String topic = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
242
+            
243
+            // 创建 topic
244
+            CmcTopic cmcTopic = new CmcTopic();
245
+            cmcTopic.setTopicId(new SnowFlake().generateId());
246
+            cmcTopic.setAgentId(agentId);
247
+            cmcTopic.setTopic(topic);
248
+            cmcTopicService.insertCmcTopic(cmcTopic);
249
+            String topicId = cmcTopic.getTopicId();
250
+            
251
+            // 合并 projectOverview、scoringRequirements、detailedDirectory 作为 output
252
+            StringBuilder output = new StringBuilder();
253
+            output.append("【项目概况】\n").append(projectOverview).append("\n\n");
254
+            output.append("【评分要求】\n").append(scoringRequirements).append("\n\n");
255
+            output.append("【详细目录】\n").append(detailedDirectory);
256
+            
257
+            // 更新 cmc_chat 表
258
+            cmcChat.setTopicId(topicId);
259
+            cmcChat.setOutput(output.toString());
260
+            cmcChat.setOutputTime(new Date());
261
+            cmcChatMapper.updateCmcChat(cmcChat);
262
+            jsonObject.put("topicId", topicId);
230 263
         }
231 264
         else if (agentName.contains("检查")) {
232 265
             message = generateAnswerWithDocumentContent(profilePath + "/" + file.getOriginalFilename());
@@ -472,7 +505,6 @@ public class CmcAgentServiceImpl implements ICmcAgentService
472 505
             EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
473 506
                     .queryEmbedding(queryEmbedding)
474 507
                     .minScore(0.7) // 降低阈值以获取更多相关内容
475
-                    .maxResults(5) // 限制结果数量
476 508
                     .build();
477 509
 
478 510
             List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
@@ -585,18 +617,20 @@ public class CmcAgentServiceImpl implements ICmcAgentService
585 617
     }
586 618
 
587 619
     /**
588
-     * 调用LLM生成回答
620
+     * 调用LLM生成目录结构
589 621
      */
590
-    public String generateAnswerWithDocument(String uploadFilePath, String templatePath, String question) throws IOException {
622
+    public JSONArray generateDetailedDirectory(String fileName, String agentName) throws IOException {
623
+        String prefixPath = "/upload/agent/" + agentName;
591 624
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
592
-        File profilePath = new File(uploadFilePath);
625
+        File profilePath = new File(new File( RuoYiConfig.getProfile() + prefixPath) + "/" + fileName);
593 626
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
594 627
         List<TextSegment> segments = splitDocument(profilePath, 300, 50);
595 628
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
596 629
         embeddingStore.addAll(embeddings, segments);
597
-        Embedding queryEmbedding = embeddingModel.embed(question).content();
630
+        Embedding queryEmbedding = embeddingModel.embed("工作大纲/工作范围/招标范围/服务范围").content();
598 631
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
599 632
                 .queryEmbedding(queryEmbedding)
633
+                .maxResults(5)
600 634
                 .minScore(0.7)
601 635
                 .build();
602 636
         List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
@@ -607,7 +641,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
607 641
         }
608 642
         //根据招标文件工作大纲要求生成二级标题
609 643
         sb.append("请基于上述招标文件中提到的")
610
-                .append(question)
644
+                .append("工作大纲/工作范围/招标范围/服务范围")
611 645
                 .append(",先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
612 646
                 .append("6.1 XX\n" +
613 647
                         "6.2 XX\n" +
@@ -615,117 +649,118 @@ public class CmcAgentServiceImpl implements ICmcAgentService
615 649
                         "......\n" +
616 650
                         "6.n-1 XX\n" +
617 651
                         "6.n XX\n");
618
-        return writeChapters(sb.toString(), templatePath);
652
+        String directoryContent = writeChapters(sb.toString(), fileName, agentName);
653
+        return parseDirectoryToTree(directoryContent);
619 654
     }
620 655
 
621 656
     /**
622
-     * 生成详细目录结构(包含二三级标题)
657
+     * 将目录文本解析为树结构
623 658
      */
624
-    public String generateDetailedDirectory(String uploadFilePath) throws IOException {
625
-        processValue = "生成详细目录结构中: 0%";
626
-        StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
627
-        File profilePath = new File(uploadFilePath);
628
-        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
629
-        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
630
-        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
631
-        embeddingStore.addAll(embeddings, segments);
659
+    private JSONArray parseDirectoryToTree(String directoryContent) {
660
+        JSONArray tree = new JSONArray();
661
+        Map<String, JSONObject> level1Map = new LinkedHashMap<>();
662
+        Map<String, JSONObject> level2Map = new LinkedHashMap<>();
663
+        Map<String, JSONObject> level3Map = new LinkedHashMap<>();
664
+
665
+        String[] lines = directoryContent.split("\n");
666
+        for (String line : lines) {
667
+            line = line.trim().replace("*", "").replace("#", "").replace("-", "");
668
+            if (!line.contains("6.") || line.isEmpty()) {
669
+                continue;
670
+            }
632 671
 
633
-        // 搜索与目录相关的内容
634
-        String directoryQuery = "目录结构、章节划分、文件结构";
635
-        Embedding queryEmbedding = embeddingModel.embed(directoryQuery).content();
636
-        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
637
-                .queryEmbedding(queryEmbedding)
638
-                .minScore(0.7)
639
-                .build();
640
-        List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
641
-        results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
672
+            String[] parts = line.split(" ", 2);
673
+            if (parts.length < 2) {
674
+                continue;
675
+            }
642 676
 
643
-        for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
644
-            String content = embeddingMatch.embedded().toString();
645
-            sb.append(content).append("\n\n");
677
+            String number = parts[0].trim();
678
+            String title = parts[1].trim();
679
+            int dotCount = number.split("\\.").length - 1;
680
+
681
+            if (dotCount == 1) {
682
+                JSONObject level1 = new JSONObject();
683
+                level1.put("title", number + " " + title);
684
+                level1.put("children", new JSONArray());
685
+                level1Map.put(number, level1);
686
+            } else if (dotCount == 2) {
687
+                JSONObject level2 = new JSONObject();
688
+                level2.put("title", number + " " + title);
689
+                level2.put("children", new JSONArray());
690
+                level2Map.put(number, level2);
691
+
692
+                String parentNumber = number.substring(0, number.lastIndexOf("."));
693
+                if (level1Map.containsKey(parentNumber)) {
694
+                    level1Map.get(parentNumber).getJSONArray("children").add(level2);
695
+                }
696
+            } else if (dotCount == 3) {
697
+                JSONObject level3 = new JSONObject();
698
+                level3.put("title", number + " " + title);
699
+                level3Map.put(number, level3);
700
+
701
+                String parentNumber = number.substring(0, number.lastIndexOf("."));
702
+                if (level2Map.containsKey(parentNumber)) {
703
+                    level2Map.get(parentNumber).getJSONArray("children").add(level3);
704
+                }
705
+            }
706
+        }
707
+
708
+        for (Map.Entry<String, JSONObject> entry : level1Map.entrySet()) {
709
+            tree.add(entry.getValue());
646 710
         }
647 711
 
648
-        // 生成详细目录结构
649
-        sb.append("请基于上述招标文件内容,生成详细的目录结构,包含一级、二级和三级标题,严格按以下格式输出:\n")
650
-                .append("1. 一级标题\n")
651
-                .append("  1.1 二级标题\n")
652
-                .append("    1.1.1 三级标题\n")
653
-                .append("    1.1.2 三级标题\n")
654
-                .append("  1.2 二级标题\n")
655
-                .append("2. 一级标题\n")
656
-                .append("  2.1 二级标题\n")
657
-                .append("    2.1.1 三级标题\n")
658
-                .append("......\n");
659
-
660
-        processValue = "生成详细目录结构中: 50%";
661
-        String directory = generateAnswer(sb.toString());
662
-        processValue = "生成详细目录结构中: 100%";
663
-        return directory;
712
+        return tree;
664 713
     }
665 714
 
666 715
     /**
667
-     * 分析项目概况
716
+     * 将目录树结构转换为文本格式
668 717
      */
669
-    public String analyzeProjectOverview(String uploadFilePath) throws IOException {
670
-        processValue = "分析项目概况中: 0%";
671
-        StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
672
-        File profilePath = new File(uploadFilePath);
673
-        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
674
-        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
675
-        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
676
-        embeddingStore.addAll(embeddings, segments);
677
-
678
-        // 搜索与项目概况相关的内容
679
-        String overviewQuery = "项目概况、项目基本信息、项目背景、项目目标、项目范围";
680
-        Embedding queryEmbedding = embeddingModel.embed(overviewQuery).content();
681
-        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
682
-                .queryEmbedding(queryEmbedding)
683
-                .minScore(0.7)
684
-                .build();
685
-        List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
686
-        results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
687
-
688
-        for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
689
-            String content = embeddingMatch.embedded().toString();
690
-            sb.append(content).append("\n\n");
718
+    private String buildDirectoryText(JSONArray tree) {
719
+        StringBuilder sb = new StringBuilder();
720
+        for (int i = 0; i < tree.size(); i++) {
721
+            JSONObject level1 = tree.getJSONObject(i);
722
+            sb.append(level1.getString("title")).append("\n");
723
+            if (level1.containsKey("children")) {
724
+                JSONArray children = level1.getJSONArray("children");
725
+                for (int j = 0; j < children.size(); j++) {
726
+                    JSONObject level2 = children.getJSONObject(j);
727
+                    sb.append(level2.getString("title")).append("\n");
728
+                    if (level2.containsKey("children")) {
729
+                        JSONArray grandchildren = level2.getJSONArray("children");
730
+                        for (int k = 0; k < grandchildren.size(); k++) {
731
+                            JSONObject level3 = grandchildren.getJSONObject(k);
732
+                            sb.append(level3.getString("title")).append("\n");
733
+                        }
734
+                    }
735
+                }
736
+            }
691 737
         }
692
-
693
-        // 分析项目概况
694
-        sb.append("请基于上述招标文件内容,分析项目概况,包括但不限于以下内容:\n")
695
-                .append("1. 项目名称\n")
696
-                .append("2. 项目类型\n")
697
-                .append("3. 项目预算\n")
698
-                .append("4. 项目周期\n")
699
-                .append("5. 项目地点\n")
700
-                .append("6. 招标人\n")
701
-                .append("7. 项目背景\n")
702
-                .append("8. 项目目标\n")
703
-                .append("9. 项目范围\n")
704
-                .append("请以清晰的结构输出分析结果。\n");
705
-
706
-        processValue = "分析项目概况中: 50%";
707
-        String overview = generateAnswer(sb.toString());
708
-        processValue = "分析项目概况中: 100%";
709
-        return overview;
738
+        return sb.toString();
710 739
     }
711 740
 
712 741
     /**
713
-     * 分析评分要求
742
+     * 分析招标文件内容(公共方法)
743
+     * @param uploadFilePath 文件路径
744
+     * @param query 查询关键词
745
+     * @param analysisPrompt 分析提示语
746
+     * @param progressStartMsg 进度开始消息
747
+     * @param progressFoundMsg 进度找到消息
748
+     * @param progressEndMsg 进度结束消息
749
+     * @return 分析结果
714 750
      */
715
-    public String analyzeScoringRequirements(String uploadFilePath) throws IOException {
716
-        processValue = "分析评分要求中: 0%";
751
+    private String analyzeDocumentContent(List<TextSegment> segments, String query, String analysisPrompt,
752
+                                         String progressStartMsg, String progressFoundMsg, String progressEndMsg) throws IOException {
753
+        processValue = progressStartMsg;
717 754
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
718
-        File profilePath = new File(uploadFilePath);
719 755
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
720
-        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
721 756
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
722 757
         embeddingStore.addAll(embeddings, segments);
723 758
 
724
-        // 搜索与评分要求相关的内容
725
-        String scoringQuery = "评分要求、评分标准、评标办法、打分规则";
726
-        Embedding queryEmbedding = embeddingModel.embed(scoringQuery).content();
759
+        // 搜索相关内容
760
+        Embedding queryEmbedding = embeddingModel.embed(query).content();
727 761
         EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
728 762
                 .queryEmbedding(queryEmbedding)
763
+                .maxResults(5)
729 764
                 .minScore(0.7)
730 765
                 .build();
731 766
         List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
@@ -736,26 +771,50 @@ public class CmcAgentServiceImpl implements ICmcAgentService
736 771
             sb.append(content).append("\n\n");
737 772
         }
738 773
 
739
-        // 分析评分要求
740
-        sb.append("请基于上述招标文件内容,分析评分要求,包括但不限于以下内容:\n")
741
-                .append("1. 评分项及权重\n")
742
-                .append("2. 技术方案评分标准\n")
743
-                .append("3. 商务方案评分标准\n")
744
-                .append("4. 服务方案评分标准\n")
745
-                .append("5. 其他评分项\n")
746
-                .append("请以清晰的结构输出分析结果,包括各项的具体分值和评分细则。\n");
747
-
748
-        processValue = "分析评分要求中: 50%";
749
-        String scoring = generateAnswer(sb.toString());
750
-        processValue = "分析评分要求中: 100%";
751
-        return scoring;
774
+        // 添加分析提示
775
+        sb.append(analysisPrompt);
776
+
777
+        processValue = progressFoundMsg;
778
+        String result = generateAnswer(sb.toString());
779
+        processValue = progressEndMsg;
780
+        return result;
781
+    }
782
+
783
+    /**
784
+     * 分析项目概况
785
+     */
786
+    public String analyzeProjectOverview(String uploadFilePath, List<TextSegment> segments) throws IOException {
787
+        String query = uploadFilePath.split("/")[uploadFilePath.split("/").length - 1] + "项目概况";
788
+        String analysisPrompt = "请基于上述招标文件内容,分析项目概况。\n" +
789
+                "请以清晰的结构输出分析结果。\n";
790
+        return analyzeDocumentContent(segments, query, analysisPrompt,
791
+                "分析项目概况中: 0%",
792
+                "已查到项目概况: 50%",
793
+                "分析项目概况中: 100%");
794
+    }
795
+
796
+    /**
797
+     * 分析评分要求
798
+     */
799
+    public String analyzeScoringRequirements(String uploadFilePath, List<TextSegment> segments, String agentName) throws IOException {
800
+        String query = "评分要求、评分标准、评标办法、打分规则";
801
+        if (agentName.contains("技术"))
802
+            query = "技术部分评分要求、评分标准、评标办法、打分规则";
803
+        else if (agentName.contains("商务"))
804
+            query = "商务部分评分要求、评分标准、评标办法、打分规则";
805
+        String analysisPrompt = "请基于上述招标文件内容,分析评分要求。\n" +
806
+                "请以清晰的结构输出分析结果,包括各项的具体分值和评分细则。\n";
807
+        return analyzeDocumentContent(segments, query, analysisPrompt,
808
+                "分析评分要求中: 0%",
809
+                "已查到评分要求: 50%",
810
+                "分析评分要求中: 100%");
752 811
     }
753 812
 
754 813
     /**
755 814
      * 编排章节
756 815
      * @return
757 816
      */
758
-    public String writeChapters(String prompt, String templatePath) throws IOException {
817
+    public String writeChapters(String prompt, String fileName, String agentName) throws IOException {
759 818
         String chapters2 = generateAnswer(prompt);
760 819
 
761 820
         //根据技术文档知识库生成二级标题下三级标题
@@ -801,7 +860,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
801 860
                 "6.n.1 XX\n" +
802 861
                 "6.n 2 XX\n";
803 862
         String content = generateAnswer(sb);
804
-        writeTitles(content, templatePath);
863
+        writeTitles(content, fileName, agentName);
805 864
         return content;
806 865
     }
807 866
 
@@ -812,14 +871,17 @@ public class CmcAgentServiceImpl implements ICmcAgentService
812 871
     public String generateAnswer(String prompt) throws IOException {
813 872
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
814 873
                 .model("Qwen")
874
+                .timeout(java.time.Duration.ofSeconds(120))
815 875
                 .build();
816 876
 
817 877
         List<ChatMessage> messages = new ArrayList<>();
818 878
         messages.add(ChatMessage.ofUser(prompt));
819 879
         ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
820
-        ChatResponse response = chatModel.prompt(chatSession).call();
821 880
 
822
-        return response.lastChoice().getMessage().getContent();
881
+        Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
882
+        ChatResponse response = chatModel.prompt(prompt1).call();
883
+        String content = response.lastChoice().getMessage().getContent();
884
+        return content;
823 885
     }
824 886
 
825 887
     /**
@@ -893,13 +955,27 @@ public class CmcAgentServiceImpl implements ICmcAgentService
893 955
      * 写入章节大纲
894 956
      * @return
895 957
      */
896
-    public void writeTitles(String content, String templatePath) throws IOException {
958
+    public void writeTitles(String content, String fileName, String agentName) throws IOException {
897 959
         List<String> chapters = new ArrayList<>();
898 960
         String[] contentLines = content.split("\n");
899 961
         for (String line : contentLines) {
900 962
             if (line.contains("6."))
901 963
                 chapters.add(line.replace("*", "").replace("#", "").replace("-", ""));
902 964
         }
965
+        String prefixPath = "/upload/agent/" + agentName;
966
+        String[] filenameSplit = fileName.split("\\.");
967
+        String outputFilename = prefixPath + "/" + fileName
968
+                .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
969
+        String templatePath = RuoYiConfig.getProfile() + outputFilename;
970
+        Path outputFilePath = Paths.get(templatePath);
971
+        Files.deleteIfExists(outputFilePath);
972
+        InputStream inputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
973
+        try (XWPFDocument doc = new XWPFDocument(inputStream)) {
974
+            // 保存文档到本地文件系统
975
+            try (FileOutputStream out = new FileOutputStream(templatePath)) {
976
+                doc.write(out);
977
+            }
978
+        }
903 979
         File file = new File(templatePath);
904 980
         FileInputStream fileInputStream = new FileInputStream(file);
905 981
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
@@ -1010,6 +1086,50 @@ public class CmcAgentServiceImpl implements ICmcAgentService
1010 1086
     /**
1011 1087
      * 获取最低级别子标题列表
1012 1088
      */
1089
+    /**
1090
+     * 保存目录到Word文件
1091
+     */
1092
+    @Override
1093
+    public JSONObject writeTitles(JSONObject data) throws IOException {
1094
+        JSONObject result = new JSONObject();
1095
+        try {
1096
+            String filename = data.getString("filename");
1097
+            String agentName = data.getString("agentName");
1098
+            JSONArray titlesArray = data.getJSONArray("titles");
1099
+
1100
+            // 构建目录字符串
1101
+            StringBuilder titlesContent = new StringBuilder();
1102
+            buildTitlesContent(titlesArray, titlesContent);
1103
+
1104
+            // 调用现有的writeTitles方法写入Word文件
1105
+            writeTitles(titlesContent.toString(), filename, agentName);
1106
+
1107
+            result.put("code", 200);
1108
+            result.put("message", "目录保存成功");
1109
+        } catch (Exception e) {
1110
+            e.printStackTrace();
1111
+            result.put("code", 500);
1112
+            result.put("message", "目录保存失败: " + e.getMessage());
1113
+        }
1114
+        return result;
1115
+    }
1116
+
1117
+    /**
1118
+     * 构建目录内容字符串
1119
+     */
1120
+    private void buildTitlesContent(JSONArray titlesArray, StringBuilder content) {
1121
+        for (int i = 0; i < titlesArray.size(); i++) {
1122
+            JSONObject titleObj = titlesArray.getJSONObject(i);
1123
+            String title = titleObj.getString("title");
1124
+            content.append(title).append("\n");
1125
+
1126
+            if (titleObj.containsKey("children")) {
1127
+                JSONArray children = titleObj.getJSONArray("children");
1128
+                buildTitlesContent(children, content);
1129
+            }
1130
+        }
1131
+    }
1132
+
1013 1133
     public List<String> extractSubTitles(String filename, String question) throws IOException {
1014 1134
         List<String> subTitles = new ArrayList<>();
1015 1135
         InputStream fileInputStream = new FileInputStream(filename);
@@ -1152,6 +1272,96 @@ public class CmcAgentServiceImpl implements ICmcAgentService
1152 1272
         }
1153 1273
     }
1154 1274
 
1275
+    /**
1276
+     * 从 DOCX 文档中提取表格内容
1277
+     */
1278
+    private List<TextSegment> extractTablesFromDocx(XWPFDocument document) {
1279
+        List<TextSegment> tableSegments = new ArrayList<>();
1280
+        List<XWPFTable> tables = document.getTables();
1281
+
1282
+        for (int i = 0; i < tables.size(); i++) {
1283
+            XWPFTable table = tables.get(i);
1284
+            StringBuilder tableContent = new StringBuilder();
1285
+
1286
+            // 添加表格标识
1287
+            tableContent.append("[表格 ").append(i + 1).append("]\n");
1288
+
1289
+            // 提取表格内容
1290
+            for (XWPFTableRow row : table.getRows()) {
1291
+                StringBuilder rowContent = new StringBuilder();
1292
+                for (XWPFTableCell cell : row.getTableCells()) {
1293
+                    String cellText = cell.getText().trim();
1294
+                    if (!cellText.isEmpty()) {
1295
+                        rowContent.append(cellText).append(" | ");
1296
+                    }
1297
+                }
1298
+                if (rowContent.length() > 0) {
1299
+                    // 移除末尾的分隔符
1300
+                    if (rowContent.length() >= 3) {
1301
+                        rowContent.setLength(rowContent.length() - 3);
1302
+                    }
1303
+                    tableContent.append(rowContent).append("\n");
1304
+                }
1305
+            }
1306
+
1307
+            // 只有当表格内容有实际内容时才添加
1308
+            if (tableContent.length() > 10) {
1309
+                TextSegment segment = TextSegment.from(tableContent.toString());
1310
+                tableSegments.add(segment);
1311
+            }
1312
+        }
1313
+
1314
+        return tableSegments;
1315
+    }
1316
+
1317
+    /**
1318
+     * 从 DOC 文档中提取表格内容
1319
+     */
1320
+    private List<TextSegment> extractTablesFromDoc(HWPFDocument document) {
1321
+        List<TextSegment> tableSegments = new ArrayList<>();
1322
+        Range range = document.getRange();
1323
+
1324
+        int tableCount = 0;
1325
+        StringBuilder tableContent = new StringBuilder();
1326
+        boolean inTable = false;
1327
+
1328
+        for (int i = 0; i < range.numParagraphs(); i++) {
1329
+            Paragraph paragraph = range.getParagraph(i);
1330
+            String text = paragraph.text().trim();
1331
+
1332
+            // 检测表格段落
1333
+            if (paragraph.isInTable()) {
1334
+                if (!inTable) {
1335
+                    // 开始新表格
1336
+                    tableCount++;
1337
+                    tableContent = new StringBuilder();
1338
+                    tableContent.append("[表格 ").append(tableCount).append("]\n");
1339
+                    inTable = true;
1340
+                }
1341
+
1342
+                // 添加表格行内容
1343
+                if (!text.isEmpty()) {
1344
+                    tableContent.append(text).append("\n");
1345
+                }
1346
+            } else if (inTable) {
1347
+                // 表格结束
1348
+                inTable = false;
1349
+                if (tableContent.length() > 10) {
1350
+                    TextSegment segment = TextSegment.from(tableContent.toString());
1351
+                    tableSegments.add(segment);
1352
+                }
1353
+            }
1354
+        }
1355
+
1356
+        // 处理最后一个表格
1357
+        if (inTable && tableContent.length() > 10) {
1358
+            TextSegment segment = TextSegment.from(tableContent.toString());
1359
+            tableSegments.add(segment);
1360
+        }
1361
+
1362
+        return tableSegments;
1363
+    }
1364
+
1155 1365
     /**
1156 1366
      * 按三级标题分割DOC内容
1157 1367
      */
@@ -1243,6 +1453,10 @@ public class CmcAgentServiceImpl implements ICmcAgentService
1243 1453
                         splitDocxByLevel3(currentLevel2Content.toString(), xwpfDocument, segments);
1244 1454
                     }
1245 1455
                 }
1456
+
1457
+                // 提取文档中的所有表格
1458
+                List<TextSegment> tableSegments = extractTablesFromDocx(xwpfDocument);
1459
+                segments.addAll(tableSegments);
1246 1460
             }
1247 1461
             return segments;
1248 1462
         }
@@ -1289,6 +1503,10 @@ public class CmcAgentServiceImpl implements ICmcAgentService
1289 1503
                         splitDocByLevel3(currentLevel2Content.toString(), hwpfDocument, segments);
1290 1504
                     }
1291 1505
                 }
1506
+
1507
+                // 提取文档中的所有表格
1508
+                List<TextSegment> tableSegments = extractTablesFromDoc(hwpfDocument);
1509
+                segments.addAll(tableSegments);
1292 1510
             }
1293 1511
             return segments;
1294 1512
         }

+ 14
- 2
oa-ui/src/api/llm/agent.js 查看文件

@@ -2,7 +2,7 @@
2 2
  * @Author: wrh
3 3
  * @Date: 2025-07-17 18:06:24
4 4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-09-02 16:43:25
5
+ * @LastEditTime: 2026-04-24 13:52:03
6 6
  */
7 7
 import request from '@/utils/request'
8 8
 
@@ -65,11 +65,14 @@ export function uploadFile(file, agentName) {
65 65
 }
66 66
 
67 67
 // 上传单文件
68
-export function uploadModifyFile(topicId, file, agentName) {
68
+export function uploadModifyFile(topicId, file, agentName, chapterNumber) {
69 69
   const formData = new FormData()
70 70
   formData.append('topicId', topicId)
71 71
   formData.append('file', file)
72 72
   formData.append('agentName', agentName)
73
+  if (chapterNumber) {
74
+    formData.append('chapterNumber', chapterNumber)
75
+  }
73 76
   return request({
74 77
     url: '/llm/agent/modifyFile',
75 78
     method: 'post',
@@ -113,3 +116,12 @@ export function opening(agentName) {
113 116
     params: { agentName }
114 117
   })
115 118
 }
119
+
120
+// 保存目录到Word文件
121
+export function writeTitles(data) {
122
+  return request({
123
+    url: '/llm/agent/writeTitles',
124
+    method: 'post',
125
+    data: data
126
+  })
127
+}

+ 861
- 156
oa-ui/src/views/llm/agent/AgentDetail.vue
文件差異過大導致無法顯示
查看文件


+ 3
- 1
oa-ui/src/views/llm/agent/index.vue 查看文件

@@ -310,7 +310,9 @@ export default {
310 310
 }
311 311
 
312 312
 .left-panel {
313
-  width: 400px;
313
+  width: 25%;
314
+  min-width: 260px;
315
+  max-width: 320px;
314 316
   background: white;
315 317
   border-radius: 8px;
316 318
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

+ 1
- 1
oa-ui/src/views/llm/knowledge/index.vue 查看文件

@@ -1277,7 +1277,7 @@ export default {
1277 1277
 }
1278 1278
 
1279 1279
 .left-panel {
1280
-  width: 400px;
1280
+  width: 250px;
1281 1281
   background: white;
1282 1282
   border-radius: 8px;
1283 1283
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

Loading…
取消
儲存