Browse Source

知识库、智能体功能完善

lamphua 4 weeks ago
parent
commit
1f9c526772

+ 6
- 4
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java View File

@@ -48,11 +48,11 @@ public class McpServiceImpl implements IMcpService {
48 48
 
49 49
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
50 50
 
51
-    private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
51
+    private static final String llmServiceUrl = "http://192.168.28.196:8000/v1/chat/completions";
52 52
 
53 53
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
54 54
             ConnectConfig.builder()
55
-                    .uri("http://192.168.28.188:19530")
55
+                    .uri("http://192.168.28.196:19530")
56 56
                     .build());
57 57
 
58 58
     /**
@@ -65,7 +65,9 @@ public class McpServiceImpl implements IMcpService {
65 65
                                            @Param(description = "技术文件地址") String templatePath) throws IOException
66 66
     {
67 67
             try {
68
-                templatePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
68
+                if (templatePath.startsWith("/dev-api"))
69
+                    templatePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
70
+                templatePath = Solon.cfg().getProperty("cmc.profile") + templatePath;
69 71
                 List<String> subTitles = extractSubTitles(templatePath, title);
70 72
                 List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
71 73
                 return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
@@ -144,7 +146,7 @@ public class McpServiceImpl implements IMcpService {
144 146
      */
145 147
     public String generateAnswer(String prompt) throws IOException {
146 148
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
147
-                .model("Qwen2.5-1.5B-Instruct")
149
+                .model("Qwen2.5-7B-Instruct")
148 150
                 .build();
149 151
 
150 152
         List<ChatMessage> messages = new ArrayList<>();

+ 9
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/KnowLedgeController.java View File

@@ -41,6 +41,15 @@ public class KnowLedgeController extends BaseController
41 41
         JSONArray collectionNames = milvusService.getCollectionNames();
42 42
         return success(collectionNames);
43 43
     }
44
+    /**
45
+     * 新建知识库
46
+     */
47
+    @GetMapping("/listByName")
48
+    public AjaxResult listKnowLedgeByCollectionName(String collectionName)
49
+    {
50
+        JSONArray collectionNames = milvusService.listKnowLedgeByCollectionName(collectionName);
51
+        return success(collectionNames);
52
+    }
44 53
 
45 54
     /**
46 55
      * 新建知识库

+ 5
- 3
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java View File

@@ -1,6 +1,7 @@
1 1
 package com.ruoyi.web.llm.controller;
2 2
 
3 3
 import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.common.config.RuoYiConfig;
4 5
 import com.ruoyi.common.core.controller.BaseController;
5 6
 import com.ruoyi.llm.domain.CmcChat;
6 7
 import com.ruoyi.llm.service.ICmcAgentService;
@@ -11,6 +12,7 @@ import dev.langchain4j.model.embedding.EmbeddingModel;
11 12
 import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
12 13
 import io.milvus.client.MilvusServiceClient;
13 14
 import io.milvus.param.ConnectParam;
15
+import org.noear.solon.Solon;
14 16
 import org.noear.solon.ai.chat.ChatModel;
15 17
 import org.noear.solon.ai.chat.ChatResponse;
16 18
 import org.noear.solon.ai.chat.ChatSession;
@@ -52,11 +54,11 @@ public class McpController extends BaseController
52 54
 
53 55
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
54 56
 
55
-    private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
57
+    private static final String llmServiceUrl = "http://192.168.28.196:8000/v1/chat/completions";
56 58
 
57 59
     private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
58 60
             ConnectParam.newBuilder()
59
-                    .withHost("192.168.28.188")
61
+                    .withHost("192.168.28.196")
60 62
                     .withPort(19530)
61 63
                     .build());
62 64
 
@@ -71,7 +73,7 @@ public class McpController extends BaseController
71 73
                 .apiUrl("http://localhost:8080/mcp/sse")
72 74
                 .build();
73 75
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
74
-                .model("Qwen2.5-1.5B-Instruct")
76
+                .model("Qwen2.5-7B-Instruct")
75 77
                 .defaultToolsAdd(clientProvider)
76 78
                 .build();
77 79
 

+ 1
- 1
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java View File

@@ -32,7 +32,7 @@ public class RagController extends BaseController
32 32
     public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question)
33 33
     {
34 34
         List<JSONObject> contexts = langChainMilvusService.retrieveFromMilvus(collectionName, question, 10);
35
-        return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, "http://192.168.28.188:8000/v1/chat/completions");
35
+        return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, "http://192.168.28.196:8000/v1/chat/completions");
36 36
     }
37 37
 
38 38
     /**

+ 2
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java View File

@@ -29,7 +29,7 @@ public class SessionController extends BaseController
29 29
      */
30 30
     @GetMapping("/answer")
31 31
     public Flux<AssistantMessage> answer(String topicId, String question) {
32
-        return langChainMilvusService.generateAnswer(topicId, question, "http://192.168.28.188:8000/v1/chat/completions");
32
+        return langChainMilvusService.generateAnswer(topicId, question, "http://192.168.28.196:8000/v1/chat/completions");
33 33
     }
34 34
 
35 35
     /**
@@ -38,7 +38,7 @@ public class SessionController extends BaseController
38 38
     @GetMapping("/answerWithDocument")
39 39
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws IOException
40 40
     {
41
-        return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question, "http://192.168.28.188:8000/v1/chat/completions");
41
+        return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question, "http://192.168.28.196:8000/v1/chat/completions");
42 42
     }
43 43
 
44 44
 }

+ 5
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/IMilvusService.java View File

@@ -16,6 +16,11 @@ public interface IMilvusService {
16 16
      */
17 17
     public JSONArray getCollectionNames();
18 18
 
19
+    /**
20
+     * 查询知识库Collection
21
+     */
22
+    public JSONArray listKnowLedgeByCollectionName(String collectionName);
23
+
19 24
     /**
20 25
      * 修改知识库Collection
21 26
      */

+ 2
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java View File

@@ -61,7 +61,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
61 61
 
62 62
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
63 63
             ConnectConfig.builder()
64
-                    .uri("http://192.168.28.188:19530")
64
+                    .uri("http://192.168.28.196:19530")
65 65
                     .build());
66 66
 
67 67
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
@@ -155,7 +155,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
155 155
     public Flux<AssistantMessage> generateAnswer(String topicId, String prompt, String llmServiceUrl) {
156 156
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
157 157
                 
158
-                .model("Qwen2.5-1.5B-Instruct")
158
+                .model("Qwen2.5-7B-Instruct")
159 159
                 .build();
160 160
 
161 161
         List<ChatMessage> messages = new ArrayList<>();

+ 39
- 1
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java View File

@@ -24,7 +24,7 @@ public class MilvusServiceImpl implements IMilvusService {
24 24
 
25 25
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
26 26
             ConnectConfig.builder()
27
-                    .uri("http://192.168.28.188:19530")
27
+                    .uri("http://192.168.28.196:19530")
28 28
                     .build());
29 29
 
30 30
     /**
@@ -88,6 +88,7 @@ public class MilvusServiceImpl implements IMilvusService {
88 88
     /**
89 89
      * 查询知识库Collection
90 90
      */
91
+    @Override
91 92
     public JSONArray getCollectionNames() {
92 93
         JSONArray jsonArray = new JSONArray();
93 94
         ListCollectionsResp listResponse = milvusClient.listCollections();
@@ -112,9 +113,42 @@ public class MilvusServiceImpl implements IMilvusService {
112 113
         return jsonArray;
113 114
     }
114 115
 
116
+    /**
117
+     * 根据名称查询知识库Collection
118
+     * 返回指定名称的集合信息
119
+     */
120
+    @Override
121
+    public JSONArray listKnowLedgeByCollectionName(String collectionName) {
122
+        JSONArray jsonArray = new JSONArray();
123
+        ListCollectionsResp listResponse = milvusClient.listCollections();
124
+        
125
+        if (listResponse != null) {
126
+            List<String> allCollectionNames = listResponse.getCollectionNames();
127
+            for (String name : allCollectionNames) {
128
+                JSONObject jsonObject = new JSONObject();
129
+                DescribeCollectionReq request = DescribeCollectionReq.builder()
130
+                        .collectionName(name)
131
+                        .build();
132
+                DescribeCollectionResp describeResponse = milvusClient.describeCollection(request);
133
+                if (describeResponse.getDescription().contains(collectionName)) {
134
+                    jsonObject.put("collectionId", describeResponse.getCollectionID());
135
+                    jsonObject.put("collectionName", name);
136
+                    SimpleDateFormat beijingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
137
+                    beijingFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
138
+                    String beijingTime = beijingFormat.format(describeResponse.getCreateUtcTime());
139
+                    jsonObject.put("createdTime", beijingTime);
140
+                    jsonObject.put("description", describeResponse.getDescription());
141
+                    jsonArray.add(jsonObject);
142
+                }
143
+            }
144
+        }
145
+        return jsonArray;
146
+    }
147
+
115 148
     /**
116 149
      * 修改知识库Collection
117 150
      */
151
+    @Override
118 152
     public void collectionRename(String collectionName, String newCollectionName) {
119 153
         RenameCollectionReq renameCollectionReq = RenameCollectionReq.builder()
120 154
                 .collectionName(collectionName)
@@ -127,6 +161,7 @@ public class MilvusServiceImpl implements IMilvusService {
127 161
     /**
128 162
      * 删除知识库Collection
129 163
      */
164
+    @Override
130 165
     public void deleteCollectionName(String collectionName) {
131 166
         DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
132 167
                 .collectionName(collectionName)
@@ -137,6 +172,7 @@ public class MilvusServiceImpl implements IMilvusService {
137 172
     /**
138 173
      * 查询知识库文件
139 174
      */
175
+    @Override
140 176
     public List<String> listDocument(String collectionName, String fileType) {
141 177
         List<String> documentList = new ArrayList<>();
142 178
         loadCollectionName(collectionName);
@@ -168,6 +204,7 @@ public class MilvusServiceImpl implements IMilvusService {
168 204
     /**
169 205
      * 删除知识库文件
170 206
      */
207
+    @Override
171 208
     public void removeDocument(String collectionName, String fileName) {
172 209
         loadCollectionName(collectionName);
173 210
         DeleteReq deleteReq = DeleteReq.builder()
@@ -180,6 +217,7 @@ public class MilvusServiceImpl implements IMilvusService {
180 217
     /**
181 218
      * 删除知识库所有文件
182 219
      */
220
+    @Override
183 221
     public void removeAllDocument(String collectionName) {
184 222
         loadCollectionName(collectionName);
185 223
         DeleteReq deleteReq = DeleteReq.builder()

+ 169
- 41
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java View File

@@ -74,11 +74,11 @@ public class CmcAgentServiceImpl implements ICmcAgentService
74 74
 
75 75
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
76 76
 
77
-    private static final String llmServiceUrl = "http://192.168.28.188:8000/v1/chat/completions";
77
+    private static final String llmServiceUrl = "http://192.168.28.196:8000/v1/chat/completions";
78 78
 
79 79
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
80 80
             ConnectConfig.builder()
81
-                    .uri("http://192.168.28.188:19530")
81
+                    .uri("http://192.168.28.196:19530")
82 82
                     .build());
83 83
 
84 84
     /**
@@ -116,6 +116,8 @@ public class CmcAgentServiceImpl implements ICmcAgentService
116 116
         String content = "";
117 117
         if (agentName.contains("技术"))
118 118
             content = "我是投标文件写作助手,我将助您完成技术文件部分撰写。请上传招标询价文件,分析后将依招标服务要求,运用技术方案知识库,为您提供参考。";
119
+        else if (agentName.contains("检查"))
120
+            content = "我是文档检查助手,我将助您完成错别字检查。请上传文件。";
119 121
         return content;
120 122
     }
121 123
 
@@ -142,20 +144,24 @@ public class CmcAgentServiceImpl implements ICmcAgentService
142 144
         cmcDocument.setDocumentId(new SnowFlake().generateId());
143 145
         cmcDocument.setChatId(chatId);
144 146
         cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
147
+        if (file.getOriginalFilename().endsWith(".pdf"))
148
+            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename().replace(".pdf", ".docx"));
145 149
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
146 150
         String message = "";
151
+        CmcChat cmcChat = new CmcChat();
152
+        cmcChat.setChatId(jsonObject.getString("chatId"));
153
+        cmcChat.setInputTime(new Date());
154
+        cmcChat.setInput("招标文件地址:" + prefixPath + "/" + file.getOriginalFilename());
155
+        cmcChat.setUserId(SecurityUtils.getUserId());
156
+        cmcChatMapper.insertCmcChat(cmcChat);
147 157
         if (agentName.contains("技术")) {
148
-            CmcChat cmcChat = new CmcChat();
149
-            cmcChat.setChatId(jsonObject.getString("chatId"));
150
-            cmcChat.setInputTime(new Date());
151
-            cmcChat.setInput("招标文件地址:" + prefixPath + "/" + file.getOriginalFilename());
152
-            cmcChat.setUserId(SecurityUtils.getUserId());
153
-            cmcChatMapper.insertCmcChat(cmcChat);
154 158
             String[] filenameSplit = file.getOriginalFilename().split("\\.");
155 159
             String outputFilename = prefixPath + "/" + file.getOriginalFilename()
156 160
                     .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
157 161
             if (file.getOriginalFilename().endsWith(".doc"))
158 162
                 outputFilename = outputFilename.replace(".doc", ".docx");
163
+            if (file.getOriginalFilename().endsWith(".pdf"))
164
+                outputFilename = outputFilename.replace(".pdf", ".docx");
159 165
             Path outputFilePath = Paths.get(RuoYiConfig.getProfile() + outputFilename);
160 166
             Files.deleteIfExists(outputFilePath);
161 167
             InputStream fileInputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
@@ -165,12 +171,16 @@ public class CmcAgentServiceImpl implements ICmcAgentService
165 171
                     doc.write(out);
166 172
                 }
167 173
             }
174
+            String question = "工作大纲/工作范围/招标范围/服务范围";
168 175
             String chapters = generateAnswerWithDocument(profilePath + "/" + file.getOriginalFilename(),
169
-                    RuoYiConfig.getProfile() + outputFilename, "工作大纲");
170
-            message = "好的,我已经收到您上传的招标文件。\n\n"+ chapters + "\n\n" +
176
+                    RuoYiConfig.getProfile() + outputFilename, question);
177
+            message = "好的,我已经收到您上传的招标文件。以下为根据招标文件生成的章节大纲:\n\n"+ chapters + "\n\n" +
171 178
                     "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
172 179
                     "思考时间可能较长,请耐心等待!\n";
173 180
         }
181
+        else if (agentName.contains("检查")) {
182
+            message = generateAnswerWithDocumentContent(profilePath + "/" + file.getOriginalFilename());
183
+        }
174 184
         jsonObject.put("assistantMessage", message);
175 185
         return jsonObject;
176 186
     }
@@ -203,6 +213,8 @@ public class CmcAgentServiceImpl implements ICmcAgentService
203 213
         cmcDocument.setDocumentId(new SnowFlake().generateId());
204 214
         cmcDocument.setChatId(chatId);
205 215
         cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
216
+        if (file.getOriginalFilename().endsWith(".pdf"))
217
+            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename().replace(".pdf", ".docx"));
206 218
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
207 219
         String message = "";
208 220
         if (agentName.contains("技术")) {
@@ -303,6 +315,22 @@ public class CmcAgentServiceImpl implements ICmcAgentService
303 315
         return cmcAgentMapper.deleteCmcAgentByAgentId(agentId);
304 316
     }
305 317
 
318
+    /**
319
+     * 调用LLM生成回答 - 检查文档内容
320
+     */
321
+    public String generateAnswerWithDocumentContent(String documentPath) throws IOException {
322
+        File profilePath = new File(documentPath);
323
+        List<TextSegment> segments = splitDocument(profilePath, 10000, 0);
324
+        StringBuilder sb = new StringBuilder();
325
+        for (TextSegment segment : segments) {
326
+            String prompt = "文档内容如下,帮我检查文档中用字、语言表达是否准确:\n" + segment.text();
327
+            sb.append("片段").append(segments.indexOf(segment) + 1).append(":\n").append(generateAnswer(prompt));
328
+            processValue = "检查中: " + Double.parseDouble(String.format("%.2f%n", (double) (segments.indexOf(segment) + 1)  / segments.size() * 100)) + "%";
329
+        }
330
+        sb.append("综合以上各片段检查内容修改建议,给出完整修改意见");
331
+        return generateAnswer(sb.toString());
332
+    }
333
+
306 334
     /**
307 335
      * 调用LLM生成回答 - 针对技术文件章节内容生成
308 336
      */
@@ -311,7 +339,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
311 339
 
312 340
         // 构建招标文件内容的嵌入向量存储
313 341
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
314
-        List<TextSegment> segments = splitDocument(profilePath);
342
+        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
315 343
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
316 344
         embeddingStore.addAll(embeddings, segments);
317 345
 
@@ -504,7 +532,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
504 532
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
505 533
         File profilePath = new File(uploadFilePath);
506 534
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
507
-        List<TextSegment> segments = splitDocument(profilePath);
535
+        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
508 536
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
509 537
         embeddingStore.addAll(embeddings, segments);
510 538
         Embedding queryEmbedding = embeddingModel.embed(question).content();
@@ -519,10 +547,15 @@ public class CmcAgentServiceImpl implements ICmcAgentService
519 547
             sb.append(requests).append("\n\n");
520 548
         }
521 549
         //根据招标文件工作大纲要求生成二级标题
522
-        sb.append("请基于上述招标文件中提到的工作大纲内容,先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
550
+        sb.append("请基于上述招标文件中提到的")
551
+                .append(question)
552
+                .append(",先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
523 553
                 .append("6.1 XX\n" +
524 554
                 "6.2 XX\n" +
525
-                "6.3 XX");
555
+                "6.3 XX\n" +
556
+                "......\n" +
557
+                "6.n-1 XX\n" +
558
+                "6.n XX\n");
526 559
         return writeChapters(sb.toString(), templatePath);
527 560
     }
528 561
 
@@ -569,7 +602,11 @@ public class CmcAgentServiceImpl implements ICmcAgentService
569 602
                 "6.2.2 XX\n" +
570 603
                 "6.3 XX\n" +
571 604
                 "6.3.1 XX\n" +
572
-                "6.3.2 XX\n";
605
+                "6.3.2 XX\n" +
606
+                "...... \n" +
607
+                "6.n XX\n" +
608
+                "6.n.1 XX\n" +
609
+                "6.n 2 XX\n";
573 610
         String content = generateAnswer(sb);
574 611
         writeTitles(content, templatePath);
575 612
         return content;
@@ -581,7 +618,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
581 618
      */
582 619
     public String generateAnswer(String prompt) throws IOException {
583 620
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
584
-                .model("Qwen2.5-1.5B-Instruct")
621
+                .model("Qwen2.5-7B-Instruct")
585 622
                 .build();
586 623
 
587 624
         List<ChatMessage> messages = new ArrayList<>();
@@ -658,19 +695,24 @@ public class CmcAgentServiceImpl implements ICmcAgentService
658 695
         File file = new File(templatePath);
659 696
         FileInputStream fileInputStream = new FileInputStream(file);
660 697
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
661
-           for (int i = 0; i < chapters.size(); i++) {
662
-               XWPFParagraph contentParagraph = document.createParagraph();
663
-               int dotNum = chapters.get(i).split("\\.").length - 1;
664
-               contentParagraph.setStyle(String.valueOf(dotNum + 2));
665
-
666
-               XWPFRun run = contentParagraph.createRun();
667
-               if (chapters.get(i).split(" ").length > 1)
668
-                   run.setText(chapters.get(i).split(" ")[1]);
669
-               if (dotNum > 1)
670
-                   run.addBreak();
671
-               if (i < chapters.size() - 1 && chapters.get(i + 1).indexOf(".") == chapters.get(i + 1).lastIndexOf("."))
672
-                   run.addBreak(BreakType.PAGE);
673
-           }
698
+            String otherTitles = "";
699
+            for (int i = 0; i < chapters.size(); i++) {
700
+                if (otherTitles.equals("") && chapters.get(i).split(" ")[1].startsWith("其它"))
701
+                    otherTitles = chapters.get(i).split(" ")[0];
702
+                if (!otherTitles.equals("") && chapters.get(i).split(" ")[0].startsWith(otherTitles) && !chapters.get(i).split(" ")[0].equals(otherTitles))
703
+                    continue;
704
+                XWPFParagraph contentParagraph = document.createParagraph();
705
+                int dotNum = chapters.get(i).split("\\.").length - 1;
706
+                contentParagraph.setStyle(String.valueOf(dotNum + 2));
707
+
708
+                XWPFRun run = contentParagraph.createRun();
709
+                if (chapters.get(i).split(" ").length > 1)
710
+                    run.setText(chapters.get(i).split(" ")[1]);
711
+                if (dotNum > 1)
712
+                    run.addBreak();
713
+                if (i < chapters.size() - 1 && chapters.get(i + 1).indexOf(".") == chapters.get(i + 1).lastIndexOf("."))
714
+                    run.addBreak(BreakType.PAGE);
715
+            }
674 716
 
675 717
             try (FileOutputStream out = new FileOutputStream(templatePath)) {
676 718
                 document.write(out);
@@ -821,7 +863,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
821 863
     /**
822 864
      * 分割文档
823 865
      */
824
-    private List<TextSegment> splitDocument(File transferFile) throws IOException {
866
+    private List<TextSegment> splitDocument(File transferFile, int maxSegmentSizeInChars, int maxOverlapSizeInChars) throws IOException {
825 867
         // 加载文档
826 868
         Document document;
827 869
         InputStream fileInputStream = new FileInputStream(transferFile);
@@ -844,7 +886,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
844 886
         else {
845 887
             throw new UnsupportedOperationException("不支持文件类型: " + filename);
846 888
         }
847
-        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
889
+        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(maxSegmentSizeInChars,  maxOverlapSizeInChars);
848 890
         return splitter.split(document);
849 891
     }
850 892
 
@@ -872,17 +914,26 @@ public class CmcAgentServiceImpl implements ICmcAgentService
872 914
 
873 915
             // 将内容解析为块(普通段落/表格/标题/列表项等),然后倒序插入,确保顺序正确
874 916
             class Block {
875
-                String type; // para | table | h4 | list1 | list2 | imageText
917
+                String type; // para | table | h4 | list1 | list2 | imageText | boldOnly | numberedBold
876 918
                 String text;
877 919
                 List<String> table;
878 920
                 Integer h4Index;       // 预计算的四级标题编号
879 921
                 Integer list1Index;    // 预计算的一级列表编号 1),2),3)
880 922
                 Integer list2Index;    // 预计算的二级列表编号 (1),(2),(3)
923
+                String boldText;      // 加粗文本部分
924
+                String suffixText;    // 后缀文本(如冒号及后续内容)
925
+                String numberPrefix;  // 数字前缀,如 "1. "
881 926
                 Block(String t, String x) { type = t; text = x; }
882 927
                 Block(List<String> tbl) { type = "table"; table = new ArrayList<>(tbl); }
883 928
                 Block(String t, String x, Integer h4Idx, Integer l1Idx, Integer l2Idx) {
884 929
                     type = t; text = x; h4Index = h4Idx; list1Index = l1Idx; list2Index = l2Idx;
885 930
                 }
931
+                Block(String t, String bold, String suffix) {
932
+                    type = t; boldText = bold; suffixText = suffix;
933
+                }
934
+                Block(String t, String numPrefix, String bold, String suffix) {
935
+                    type = t; numberPrefix = numPrefix; boldText = bold; suffixText = suffix;
936
+                }
886 937
             }
887 938
             List<Block> blocks = new ArrayList<>();
888 939
 
@@ -890,16 +941,25 @@ public class CmcAgentServiceImpl implements ICmcAgentService
890 941
             int h4Counter = 0;
891 942
             int list1Counter = 0;
892 943
             int list2Counter = 0;
944
+            String previousLine = ""; // 记录上一行内容
893 945
 
894 946
             for (String raw : lines) {
895 947
                 String line = raw.trim();
896 948
                 if (line.isEmpty()) {
897 949
                     blocks.add(new Block("para", "")); // 空段落
950
+                    previousLine = line;
898 951
                     continue;
899 952
                 }
900 953
 
954
+                // 检查上一行是否以中文冒号结尾,如果是则重置列表编号
955
+                if (previousLine.endsWith(":") || previousLine.endsWith(":")) {
956
+                    list1Counter = 0;
957
+                    list2Counter = 0;
958
+                }
959
+
901 960
                 if (line.startsWith("#")) {
902 961
                     // 跳过前三级标题
962
+                    previousLine = line;
903 963
                     continue;
904 964
                 } else if (line.startsWith("####")) {
905 965
                     // 四级标题,编号采用预计算,保证倒序插入后视觉顺序仍递增
@@ -930,7 +990,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService
930 990
                     tableLines.add(line);
931 991
                 } else if (line.startsWith("- **")) {
932 992
                     // 一级加粗列表项,编号为 1),2),3) —— 预计算编号
933
-                    String listItem = line.replaceFirst("- \\\\*\\\\*", "").replace("**", "");
993
+                    String listItem = line.replaceFirst("- \\*\\*", "").replace("**", "");
934 994
                     listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
935 995
                     list1Counter++;
936 996
                     // 一级列表开始时,重置二级列表编号
@@ -939,20 +999,49 @@ public class CmcAgentServiceImpl implements ICmcAgentService
939 999
                     blocks.add(b);
940 1000
                 } else if (line.startsWith("  - **")) {
941 1001
                     // 二级加粗列表项,编号为 (1),(2),(3) —— 预计算编号
942
-                    String listItem = line.replaceFirst("  - \\\\*\\\\*", "").replace("**", "");
1002
+                    String listItem = line.replaceFirst("  - \\*\\*", "").replace("**", "");
943 1003
                     listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
944 1004
                     list2Counter++;
945 1005
                     Block b = new Block("list2", listItem, null, null, list2Counter);
946 1006
                     blocks.add(b);
947
-                } else if (line.startsWith("![") && line.contains("](") && line.contains(")")) {
948
-                    // 图片 markdown,转成说明文本
949
-                    String imageText = extractImageDescription(line);
950
-                    blocks.add(new Block("imageText", imageText));
951
-                } else if (line.contains("http") && (line.contains(".png") || line.contains(".jpg") || line.contains(".jpeg") || line.contains(".gif"))) {
952
-                    blocks.add(new Block("imageText", "图片链接: " + line));
953 1007
                 } else {
954
-                    blocks.add(new Block("para", line));
1008
+                    // 匹配格式:1. **文本**(数字前缀+加粗)
1009
+                    Pattern numberedBoldPattern = Pattern.compile("^(\\d+\\.\\s+)\\*\\*([^*]+)\\*\\*(.*)$");
1010
+                    java.util.regex.Matcher nbm = numberedBoldPattern.matcher(line);
1011
+                    if (nbm.matches()) {
1012
+                        String numPrefix = nbm.group(1);
1013
+                        String bold = nbm.group(2);
1014
+                        String suf = nbm.group(3);
1015
+                        if (suf == null) suf = "";
1016
+                        blocks.add(new Block("numberedBold", numPrefix, bold, suf));
1017
+                        previousLine = line;
1018
+                        continue;
1019
+                    }
1020
+                    // 匹配格式:**文本**
1021
+                    Pattern boldOnlyPattern = Pattern.compile("^\\*\\*([^*]+)\\*\\*(.*)$");
1022
+                    java.util.regex.Matcher boldMatcher = boldOnlyPattern.matcher(line);
1023
+                    if (boldMatcher.matches()) {
1024
+                        String boldText = boldMatcher.group(1);      // "数据校验"
1025
+                        String suffix = boldMatcher.group(2);        // ":"或后续文本,可能为空
1026
+                        if (suffix == null) suffix = "";
1027
+                        blocks.add(new Block("boldOnly", boldText, suffix));
1028
+                        previousLine = line;
1029
+                        continue;
1030
+                    }
1031
+                    // 如果不是 boldOnly 格式,继续后续处理
1032
+                    if (line.startsWith("![") && line.contains("](") && line.contains(")")) {
1033
+                        // 图片 markdown,转成说明文本
1034
+                        String imageText = extractImageDescription(line);
1035
+                        blocks.add(new Block("imageText", imageText));
1036
+                    } else if (line.contains("http") && (line.contains(".png") || line.contains(".jpg") || line.contains(".jpeg") || line.contains(".gif"))) {
1037
+                        blocks.add(new Block("imageText", "图片链接: " + line));
1038
+                    } else {
1039
+                        blocks.add(new Block("para", line));
1040
+                    }
955 1041
                 }
1042
+                
1043
+                // 更新上一行记录
1044
+                previousLine = line;
956 1045
             }
957 1046
             // 收尾:如果文件最后是表格仍未输出
958 1047
             if (inTable && !tableLines.isEmpty()) {
@@ -998,6 +1087,45 @@ public class CmcAgentServiceImpl implements ICmcAgentService
998 1087
                         run.setFontSize(12);
999 1088
                         break;
1000 1089
                     }
1090
+                    case "numberedBold": {
1091
+                        XWPFParagraph p = document.insertNewParagraph(cursor);
1092
+                        // 数字前缀(不加粗)
1093
+                        if (b.numberPrefix != null) {
1094
+                            XWPFRun r1 = p.createRun();
1095
+                            r1.setText(b.numberPrefix);
1096
+                            r1.setFontSize(12);
1097
+                        }
1098
+                        // 加粗文本
1099
+                        XWPFRun r2 = p.createRun();
1100
+                        r2.setText(b.boldText != null ? b.boldText : "");
1101
+                        r2.setFontSize(12);
1102
+                        r2.setBold(true);
1103
+                        // 后缀(不加粗)
1104
+                        if (b.suffixText != null && !b.suffixText.isEmpty()) {
1105
+                            XWPFRun r3 = p.createRun();
1106
+                            r3.setText(b.suffixText);
1107
+                            r3.setFontSize(12);
1108
+                        }
1109
+                        break;
1110
+                    }
1111
+                    case "boldOnly": {
1112
+                        // 处理格式:**文本** 或 **文本**: 转换为 Word 中的部分加粗
1113
+                        XWPFParagraph p = document.insertNewParagraph(cursor);
1114
+                        
1115
+                        // 加粗文本部分
1116
+                        XWPFRun run1 = p.createRun();
1117
+                        run1.setText(b.boldText);
1118
+                        run1.setFontSize(12);
1119
+                        run1.setBold(true);
1120
+                        
1121
+                        // 后缀部分(不加粗,如冒号及后续内容,如果有的话)
1122
+                        if (b.suffixText != null && !b.suffixText.isEmpty()) {
1123
+                            XWPFRun run2 = p.createRun();
1124
+                            run2.setText(b.suffixText);
1125
+                            run2.setFontSize(12);
1126
+                        }
1127
+                        break;
1128
+                    }
1001 1129
                     case "imageText": {
1002 1130
                         XWPFParagraph p = document.insertNewParagraph(cursor);
1003 1131
                         XWPFRun run = p.createRun();

+ 119
- 9
llm-back/vllm_server.py View File

@@ -1,117 +1,227 @@
1 1
 from vllm import LLM, SamplingParams
2
+
2 3
 from fastapi import FastAPI, Request
4
+
3 5
 from fastapi.responses import StreamingResponse
6
+
4 7
 import uvicorn
8
+
5 9
 import time
10
+
6 11
 import json
7 12
 
13
+
14
+
8 15
 def create_vllm_server(
16
+
9 17
     model: str,
18
+
10 19
     served_model_name: str,
20
+
21
+    tensor_parallel_size: int,
22
+
11 23
     host: str,
24
+
12 25
     port: int,
13
-    tensor_parallel_size: int,
26
+
14 27
     top_p: float,
28
+
15 29
     temperature: float,
30
+
16 31
     max_tokens: int,
32
+
17 33
     gpu_memory_utilization: float,
34
+
18 35
     dtype: str,
36
+
19 37
 ) -> FastAPI:
38
+
20 39
     # 只初始化 LLM
40
+
21 41
     llm = LLM(
42
+
22 43
         model=model,
44
+
23 45
         tensor_parallel_size=tensor_parallel_size,
46
+
24 47
         gpu_memory_utilization=gpu_memory_utilization,
48
+
25 49
         dtype=dtype,
50
+
26 51
     )
27 52
 
53
+
54
+
28 55
     sampling_params = SamplingParams(
56
+
29 57
         temperature=temperature,
58
+
30 59
         top_p=top_p,
60
+
31 61
         max_tokens=max_tokens,
62
+
32 63
     )
33 64
 
65
+
66
+
34 67
     app = FastAPI()
35 68
 
69
+
70
+
36 71
     @app.post("/v1/chat/completions")
72
+
37 73
     async def chat_completions(request: Request):
74
+
38 75
         try:
76
+
39 77
             data = await request.json()
78
+
40 79
             messages = data["messages"]
80
+
41 81
             tools = data.get("tools")  # 支持 tools 参数
82
+
42 83
             created_time = time.time()
84
+
43 85
             request_id = f"chatcmpl-{int(time.time())}"
44 86
 
87
+
88
+
45 89
             # 调用 llm.chat(),传入 tools
90
+
46 91
             outputs = llm.chat(
92
+
47 93
                 messages=messages,
94
+
48 95
                 sampling_params=sampling_params,
96
+
49 97
                 tools=tools,
98
+
50 99
             )
100
+
51 101
             if data.get("stream"):
102
+
52 103
                 def generate():
104
+
53 105
                     full_text = ""
106
+
54 107
                     for output in outputs:
108
+
55 109
                         new_text = output.outputs[0].text[len(full_text):]
110
+
56 111
                         full_text = output.outputs[0].text
112
+
57 113
                         response_data = {
114
+
58 115
                             "id": request_id,
116
+
59 117
                             "model": served_model_name,
118
+
60 119
                             "created": created_time,
120
+
61 121
                             "choices": [{
122
+
62 123
                                 "index": 0,
124
+
63 125
                                 "delta": {"content": new_text},
126
+
64 127
                                 "finish_reason": output.outputs[0].finish_reason,
128
+
65 129
                             }],
130
+
66 131
                         }
132
+
67 133
                         yield f"data: {json.dumps(response_data)}\n\n"
134
+
68 135
                     yield "data: [DONE]\n\n"
69 136
 
137
+
138
+
70 139
                 return StreamingResponse(generate(), media_type="text/event-stream")
140
+
71 141
             else:
142
+
72 143
                 return {
144
+
73 145
                     "id": request_id,
146
+
74 147
                     "model": served_model_name,
148
+
75 149
                     "created": created_time,
150
+
76 151
                     "choices": [{
152
+
77 153
                         "index": 0,
154
+
78 155
                         "message": {
156
+
79 157
                             "role": "assistant",
158
+
80 159
                             "content": outputs[0].outputs[0].text,
160
+
81 161
                         },
162
+
82 163
                         "finish_reason": outputs[0].outputs[0].finish_reason,
164
+
83 165
                     }],
166
+
84 167
                 }
85 168
 
169
+
170
+
86 171
         except Exception as e:
172
+
87 173
             return {"error": str(e)}, 400
88 174
 
175
+
176
+
89 177
     return app
90 178
 
179
+
180
+
91 181
 if __name__ == "__main__":
182
+
92 183
     # 配置参数
184
+
93 185
     CONFIG = {
94
-        "model": "/mnt/d/Qwen/Qwen2.5-1.5B-Instruct",
95
-        "served_model_name": "Qwen2.5-1.5B-Instruct",
96
-        # "model": "/mnt/d/Deepseek/DeepSeek-R1-Distill-Qwen-1.5B",
97
-        # "served_model_name": "DeepSeek-R1-Distill-Qwen-1.5B",
98
-        "host": "172.25.231.226",
186
+
187
+        "model": "/home/Qwen/Qwen2.5-7B-Instruct",
188
+
189
+        "served_model_name": "Qwen2.5-7B-Instruct",
190
+
191
+        "host": "192.168.28.196",
192
+
99 193
         "port": 8000,
100
-        "tensor_parallel_size": 1,
194
+
195
+        "tensor_parallel_size": 4,
196
+
101 197
         "top_p": 0.9,
198
+
102 199
         "temperature": 0.7,
200
+
103 201
         "max_tokens": 8192,
202
+
104 203
         "gpu_memory_utilization": 0.9,
204
+
105 205
         "dtype": "float16"
206
+
106 207
     }
107
-    
208
+
108 209
     # 创建应用
210
+
109 211
     app = create_vllm_server(**CONFIG)
212
+
110 213
     
214
+
111 215
     # 启动服务器
216
+
112 217
     uvicorn.run(
218
+
113 219
         app,
220
+
114 221
         host=CONFIG["host"],
222
+
115 223
         port=CONFIG["port"],
224
+
116 225
         workers= 1,
117
-    )
226
+
227
+    )

+ 10
- 2
llm-ui/src/api/llm/knowLedge.js View File

@@ -7,11 +7,19 @@
7 7
 import request from '@/utils/request'
8 8
 
9 9
 // 查询知识库列表
10
-export function listKnowledge(query) {
10
+export function listKnowledge() {
11 11
   return request({
12 12
     url: '/llm/knowledge/list',
13 13
     method: 'get',
14
-    params: query
14
+  })
15
+}
16
+
17
+// 根据collectionName查询知识库列表
18
+export function listKnowLedgeByCollectionName(collectionName) {
19
+  return request({
20
+    url: '/llm/knowledge/listByName',
21
+    method: 'get',
22
+    params: collectionName
15 23
   })
16 24
 }
17 25
 

+ 20
- 8
llm-ui/src/views/llm/agent/AgentDetail.vue View File

@@ -2,13 +2,13 @@
2 2
  * @Author: wrh
3 3
  * @Date: 2025-01-01 00:00:00
4 4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-09-11 15:58:35
5
+ * @LastEditTime: 2025-11-03 11:28:40
6 6
 -->
7 7
 <template>
8 8
   <div class="agent-detail-container" v-loading="loading">
9 9
     <div v-if="agentInfo" class="detail-content">
10 10
       <!-- 智能体头部信息 -->
11
-      <div class="agent-header">
11
+      <!-- <div class="agent-header">
12 12
         <div class="agent-basic-info">
13 13
           <h2 class="agent-title">{{ agentInfo.agentName }}</h2>
14 14
           <p class="agent-description">{{ agentInfo.description || '暂无描述' }}</p>
@@ -21,7 +21,7 @@
21 21
             </span>
22 22
           </div>
23 23
         </div>
24
-      </div>
24
+      </div> -->
25 25
 
26 26
       <!-- 对话区域 -->
27 27
       <div class="chat-section">
@@ -81,7 +81,7 @@
81 81
               </div>
82 82
               <div class="message-content">
83 83
                 <div class="message-upload-area">
84
-                  <p class="upload-tip">请上传您需要分析的招标文件(单个文件):</p>
84
+                  <p class="upload-tip"> {{ selectDocumentTip }}</p>
85 85
                   <el-upload class="inline-upload" :action="uploadAction" :multiple="false" :auto-upload="false"
86 86
                     :file-list="chatFileList" :on-change="handleChatFileChange" :before-upload="beforeUpload"
87 87
                     :show-file-list="true" :limit="1">
@@ -89,7 +89,7 @@
89 89
                       <el-icon>
90 90
                         <Upload />
91 91
                       </el-icon>
92
-                      选择招标文件
92
+                      {{ selectDocument }}
93 93
                     </el-button>
94 94
                   </el-upload>
95 95
                   <div v-if="chatFileList.length > 0" class="chat-upload-actions">
@@ -249,6 +249,8 @@ const percentage = ref(0);
249 249
 const progress = ref("");
250 250
 const percentageDisplay = ref(false);
251 251
 const techUploadDisplay = ref(false)
252
+const selectDocument = ref('')
253
+const selectDocumentTip = ref('')
252 254
 
253 255
 // 聊天内文件上传相关
254 256
 const chatFileList = ref([]) // 聊天内的文件列表
@@ -284,6 +286,14 @@ const loadAgentDetail = async (agentId) => {
284 286
     if (agentInfo.value?.agentName) {
285 287
       const res = await opening(agentInfo.value.agentName)
286 288
       openingMessage.value = res.data.resultContent;
289
+      if (agentInfo.value.agentName.includes('技术文件')) {
290
+        selectDocument.value = '选择招标文件'
291
+        selectDocumentTip.value = '请上传您需要分析的招标文件(单个文件):'
292
+      }
293
+      else if (agentInfo.value.agentName.includes('文档检查')) {
294
+        selectDocument.value = '选择检查文件'
295
+        selectDocumentTip.value = '请上传您需要检查的文件(单个文件):'
296
+      }
287 297
     }
288 298
     // 查询当前智能体的话题列表
289 299
     await loadTopic()
@@ -422,6 +432,7 @@ const handleDeleteTopic = async (topic) => {
422 432
     // 重新加载话题列表
423 433
     await loadTopic()
424 434
 
435
+    techUploadDisplay.value = false;
425 436
     ElMessage.success('话题删除成功')
426 437
   } catch (error) {
427 438
     if (error !== 'cancel') {
@@ -609,7 +620,8 @@ const submitChatUpload = async () => {
609 620
           isHtml: true // 标记这是HTML内容
610 621
         }
611 622
         chatMessages.value.push(uploadMessage);
612
-        techUploadDisplay.value = true;
623
+        if (agentInfo.value.agentName.includes('技术文件'))
624
+          techUploadDisplay.value = true;
613 625
         // ElMessage.success('文件上传成功');
614 626
         let topicRes = await addTopic({ agentId: props.agentId, topic: fileName });
615 627
         const topicId = topicRes.msg;
@@ -748,7 +760,7 @@ const startNewChat = () => {
748 760
       messagesContainer.value.scrollTop = 0
749 761
     }
750 762
   })
751
-
763
+  techUploadDisplay.value = false
752 764
   ElMessage.success('已切换到新对话模式')
753 765
 }
754 766
 
@@ -862,7 +874,7 @@ const formatContentLinks = (content) => {
862 874
   border: 1px solid #e4e4e4;
863 875
   overflow: hidden;
864 876
   display: flex;
865
-  height: calc(100vh - 200px);
877
+  height: calc(100vh - 150px);
866 878
 
867 879
   // 话题列表样式
868 880
   .topic-list {

+ 309
- 186
llm-ui/src/views/llm/agent/index.vue View File

@@ -1,108 +1,143 @@
1 1
 <!--
2 2
  * @Author: ysh
3 3
  * @Date: 2025-07-17 18:16:50
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-07-28 14:46:01
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-11-13 16:24:00
6 6
 -->
7 7
 <template>
8
-  <div class="agent-container">
9
-    <!-- 左侧智能体列表 -->
10
-    <div class="agent-list">
11
-      <div class="list-header">
12
-        <h3>智能体列表</h3>
13
-        <el-button type="primary" size="small" @click="openAddDialog">
14
-          <el-icon>
15
-            <Plus />
16
-          </el-icon>
17
-          新增
18
-        </el-button>
19
-      </div>
8
+  <div class="app-container">
9
+    <div class="main-content">
10
+      <!-- 左侧智能体列表 -->
11
+      <div class="left-panel">
12
+        <div class="panel-header">
13
+          <h3>智能体列表</h3>
14
+          <span class="agent-count">{{ agentList.length }} 个智能体</span>
15
+          <el-button type="primary" size="small" @click="openAddDialog">
16
+            <el-icon>
17
+              <Plus />
18
+            </el-icon>
19
+            新增智能体
20
+          </el-button>
21
+        </div>
20 22
 
21
-      <!-- 搜索框 -->
22
-      <div class="search-box">
23
-        <el-input v-model="queryParams.agentName" placeholder="请输入智能体名称" clearable @clear="handleQuery"
24
-          @keyup.enter="handleQuery">
25
-          <template #append>
26
-            <el-button @click="handleQuery">
27
-              <el-icon>
28
-                <Search />
29
-              </el-icon>
30
-            </el-button>
31
-          </template>
32
-        </el-input>
33
-      </div>
23
+        <!-- 搜索框 -->
24
+        <div class="search-box">
25
+          <el-input v-model="queryParams.agentName" placeholder="请输入智能体名称" clearable @clear="handleQuery"
26
+            @keyup.enter="handleQuery">
27
+            <template #append>
28
+              <el-button @click="handleQuery">
29
+                <el-icon>
30
+                  <Search />
31
+                </el-icon>
32
+              </el-button>
33
+            </template>
34
+          </el-input>
35
+        </div>
34 36
 
35
-      <!-- 智能体列表 -->
36
-      <div class="list-content" v-loading="loading">
37
-        <div v-for="agent in agentList" :key="agent.agentId" class="agent-item"
38
-          :class="{ 'active': selectedAgentId === agent.agentId }" @click="selectAgent(agent)">
39
-          <div class="agent-info">
40
-            <div class="agent-name">{{ agent.agentName }}</div>
41
-            <div class="agent-desc">{{ agent.description || '暂无描述' }}</div>
42
-            <div class="agent-meta">
43
-              <span class="create-time">{{ agent.createTime }}</span>
37
+        <!-- 智能体列表 -->
38
+        <div class="agent-cards" v-loading="loading">
39
+          <div v-for="agent in agentList" :key="agent.agentId" class="agent-card"
40
+            :class="{ 'active': selectedAgentId === agent.agentId }" @click="selectAgent(agent)">
41
+            <div class="card-header">
42
+              <div class="card-title">
43
+                <el-icon class="folder-icon">
44
+                  <Folder />
45
+                </el-icon>
46
+                <span class="title-text">{{ agent.agentName }}</span>
47
+              </div>
48
+              <div class="card-actions">
49
+                <el-dropdown @command="handleCommand" trigger="click">
50
+                  <el-button type="text" size="small">
51
+                    <el-icon>
52
+                      <MoreFilled />
53
+                    </el-icon>
54
+                  </el-button>
55
+                  <template #dropdown>
56
+                    <el-dropdown-menu>
57
+                      <el-dropdown-item :command="{ action: 'edit', agent }">编辑</el-dropdown-item>
58
+                      <el-dropdown-item :command="{ action: 'delete', agent }" divided>删除</el-dropdown-item>
59
+                    </el-dropdown-menu>
60
+                  </template>
61
+                </el-dropdown>
62
+              </div>
63
+            </div>
64
+            <div class="card-content">
65
+              <p class="description">{{ agent.description || '暂无描述' }}</p>
66
+              <div class="meta-info">
67
+                <span class="create-time">{{ agent.createTime }}</span>
68
+              </div>
44 69
             </div>
45 70
           </div>
46
-          <div class="agent-actions">
47
-            <el-dropdown @command="handleCommand" trigger="click">
48
-              <el-icon class="action-icon">
49
-                <More />
50
-              </el-icon>
51
-              <template #dropdown>
52
-                <el-dropdown-menu>
53
-                  <el-dropdown-item :command="{ action: 'edit', agent }">编辑</el-dropdown-item>
54
-                  <el-dropdown-item :command="{ action: 'delete', agent }" divided>删除</el-dropdown-item>
55
-                </el-dropdown-menu>
56
-              </template>
57
-            </el-dropdown>
58
-          </div>
59
-        </div>
60 71
 
61
-        <!-- 空状态 -->
62
-        <div v-if="agentList.length === 0 && !loading" class="empty-state">
63
-          <el-empty description="暂无智能体数据">
64
-            <el-button type="primary" @click="dialogVisible = true">创建智能体</el-button>
65
-          </el-empty>
72
+
73
+          <!-- 空状态 -->
74
+          <div v-if="agentList.length === 0 && !loading" class="empty-state">
75
+            <el-icon class="empty-icon">
76
+              <FolderOpened />
77
+            </el-icon>
78
+            <p>暂无智能体</p>
79
+            <el-button type="primary" @click="openAddDialog">创建第一个智能体</el-button>
80
+          </div>
66 81
         </div>
67 82
       </div>
68 83
 
69
-      <!-- 分页 -->
70
-      <div class="pagination-wrapper" v-if="total > 0">
71
-        <el-pagination v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize"
72
-          :total="total" :page-sizes="[10, 20, 50]" layout="sizes, prev, pager, next" small @size-change="handleQuery"
73
-          @current-change="handleQuery" />
84
+      <!-- 右侧详细内容 -->
85
+      <div class="agent-detail">
86
+        <AgentDetail :agent-id="selectedAgentId" />
74 87
       </div>
75
-    </div>
76 88
 
77
-    <!-- 右侧详细内容 -->
78
-    <div class="agent-detail">
79
-      <AgentDetail :agent-id="selectedAgentId" />
89
+      <el-dialog
90
+        v-model="agentDialogVisible"
91
+        :title="dialogTitle"
92
+        width="500px"
93
+        @close="resetForm">
94
+        <el-form
95
+          :model="form"
96
+          :rules="rules"
97
+          label-width="80px">
98
+          <el-form-item label="智能体名称" prop="agentName">
99
+            <el-input v-model="form.agentName" placeholder="请输入智能体名称" maxlength="20" show-word-limit />
100
+          </el-form-item>
101
+          <el-form-item label="描述" prop="description">
102
+            <el-input v-model="form.description" type="textarea" placeholder="请输入描述" :rows="2" maxlength="200" show-word-limit />
103
+          </el-form-item>
104
+          <el-form-item label="角色" prop="role">
105
+            <el-input v-model="form.role" placeholder="请输入角色" maxlength="50" show-word-limit />
106
+          </el-form-item>
107
+          <el-form-item label="初始提示词" prop="initPrompt">
108
+            <el-input v-model="form.initPrompt" type="textarea" placeholder="请输入初始提示词" :rows="4" maxlength="1000" show-word-limit />
109
+          </el-form-item>
110
+          <el-form-item label="模型" prop="modelName">
111
+            <el-select v-model="form.modelName" placeholder="请选择模型" clearable>
112
+              <el-option
113
+                v-for="model in modelList"
114
+                :key="model.name"
115
+                :label="model.name"
116
+                :value="model.name">
117
+              </el-option>
118
+            </el-select>
119
+          </el-form-item>
120
+        </el-form>
121
+        <template #footer>
122
+          <span class="dialog-footer">
123
+            <el-button @click="closeDialog">取消</el-button>
124
+            <el-button type="primary" @click="submitForm">确定</el-button>
125
+          </span>
126
+        </template>
127
+      </el-dialog>
80 128
     </div>
81
-
82
-    <el-dialog v-model="dialogVisible" :title="isEdit ? '编辑智能体' : '新增智能体'" width="30%" @close="resetForm">
83
-      <el-form :model="form" :rules="rules" label-width="120px">
84
-        <el-form-item label="智能体名称" prop="agentName">
85
-          <el-input v-model="form.agentName" placeholder="请输入智能体名称" />
86
-        </el-form-item>
87
-        <el-form-item label="智能体描述" prop="description">
88
-          <el-input type="textarea" v-model="form.description" placeholder="请输入智能体描述" />
89
-        </el-form-item>
90
-      </el-form>
91
-      <template #footer>
92
-        <el-button @click="() => { dialogVisible = false; resetForm(); }">取消</el-button>
93
-        <el-button type="primary" @click="handleSubmit">{{ isEdit ? '保存' : '确定' }}</el-button>
94
-      </template>
95
-    </el-dialog>
96 129
   </div>
97 130
 </template>
98 131
 
99 132
 <script setup>
100 133
 import { ref, reactive, onMounted } from 'vue'
101 134
 import { ElMessage, ElMessageBox } from 'element-plus'
135
+import { Search, Plus, MoreFilled, Folder, FolderOpened } from '@element-plus/icons-vue'
102 136
 import { listAgent, addAgent, updateAgent, delAgent, opening } from '@/api/llm/agent'
103 137
 import { answer } from '@/api/llm/mcp'
104 138
 import AgentDetail from './AgentDetail.vue'
105 139
 
140
+const open = ref(false);
106 141
 // 响应式数据
107 142
 const loading = ref(false)
108 143
 const agentList = ref([])
@@ -158,33 +193,62 @@ const resetForm = () => {
158 193
   editAgentId.value = null
159 194
   form.agentName = ''
160 195
   form.description = ''
196
+  form.avatar = '';
197
+  form.role = '';
198
+  form.initPrompt = '';
199
+  form.modelName = '';
161 200
 }
162 201
 
163
-const openAddDialog = () => {
164
-  resetForm()
165
-  dialogVisible.value = true
166
-}
202
+// 新增智能体对话框控制变量
203
+const agentDialogVisible = ref(false);
204
+const dialogTitle = ref('');
205
+const isModifyAgent = ref(false);
167 206
 
168
-const handleSubmit = () => {
169
-  if (isEdit.value) {
170
-    updateAgent({ agentId: editAgentId.value, ...form }).then(res => {
171
-      if (res.code === 200) {
172
-        ElMessage.success('编辑成功')
173
-        dialogVisible.value = false
174
-        isEdit.value = false
175
-        editAgentId.value = null
176
-        getList()
177
-      }
178
-    })
179
-  } else {
180
-    addAgent(form).then(res => {
181
-      if (res.code === 200) {
182
-        ElMessage.success('新增成功')
183
-        dialogVisible.value = false
184
-        getList()
207
+// 打开新增智能体对话框
208
+const openAddDialog = () => {
209
+  agentDialogVisible.value = true;
210
+  dialogTitle.value = "添加智能体";
211
+  isModifyAgent.value = false;
212
+  // 重置表单
213
+  resetForm();
214
+};
215
+
216
+// 关闭对话框
217
+const closeDialog = () => {
218
+  agentDialogVisible.value = false;
219
+  resetForm();
220
+};
221
+
222
+// 提交表单
223
+const submitForm = () => {
224
+  const formRef = { value: { validate: (callback) => callback(true) } };
225
+  formRef.value.validate((valid) => {
226
+    if (valid) {
227
+      loading.value = true
228
+      const agentForm = { ...form }
229
+      if (isModifyAgent.value) {
230
+        updateAgent(agentForm).then(() => {
231
+          ElMessage.success('修改成功')
232
+          loading.value = false
233
+          agentDialogVisible.value = false
234
+          getList()
235
+        }).catch(() => {
236
+          loading.value = false
237
+          ElMessage.error('修改失败')
238
+        })
239
+      } else {
240
+        addAgent(agentForm).then(() => {
241
+          ElMessage.success('新增成功')
242
+          loading.value = false
243
+          agentDialogVisible.value = false
244
+          getList()
245
+        }).catch(() => {
246
+          loading.value = false
247
+          ElMessage.error('新增失败')
248
+        })
185 249
       }
186
-    })
187
-  }
250
+    }
251
+  })
188 252
 }
189 253
 
190 254
 // 操作菜单处理
@@ -234,114 +298,181 @@ onMounted(() => {
234 298
 </script>
235 299
 
236 300
 <style lang="scss" scoped>
237
-.agent-container {
301
+.app-container {
302
+  height: 100vh;
303
+  display: flex;
304
+  flex-direction: column;
305
+  background: #f5f7fa;
306
+}
307
+
308
+.main-content {
309
+  flex: 1;
238 310
   display: flex;
239
-  height: calc(100vh - 120px);
240
-  background: #f5f5f5;
311
+  gap: 16px;
312
+  padding: 16px;
313
+  overflow: hidden;
241 314
 }
242 315
 
243
-.agent-list {
244
-  width: 300px;
316
+.left-panel {
317
+  width: 400px;
245 318
   background: white;
246
-  border-right: 1px solid #e4e4e4;
319
+  border-radius: 8px;
320
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
247 321
   display: flex;
248 322
   flex-direction: column;
323
+  overflow: hidden;
324
+}
249 325
 
250
-  .list-header {
251
-    padding: 16px;
252
-    border-bottom: 1px solid #e4e4e4;
253
-    display: flex;
254
-    justify-content: space-between;
255
-    align-items: center;
326
+.right-panel {
327
+  flex: 1;
328
+  background: white;
329
+  border-radius: 8px;
330
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
331
+  display: flex;
332
+  flex-direction: column;
333
+  overflow: hidden;
334
+}
256 335
 
257
-    h3 {
258
-      margin: 0;
259
-      font-size: 16px;
260
-      font-weight: 500;
261
-    }
336
+.panel-header {
337
+  padding: 20px 24px;
338
+  border-bottom: 1px solid #e4e7ed;
339
+  display: flex;
340
+  justify-content: space-between;
341
+  align-items: center;
342
+  background: #fafbfc;
343
+
344
+  h3 {
345
+    margin: 0;
346
+    font-size: 16px;
347
+    font-weight: 600;
348
+    color: #303133;
349
+  }
350
+
351
+  .agent-count {
352
+    font-size: 12px;
353
+    color: #909399;
354
+    background: #f0f2f5;
355
+    padding: 4px 8px;
356
+    border-radius: 4px;
262 357
   }
263 358
 
264 359
   .search-box {
265 360
     padding: 16px;
266 361
     border-bottom: 1px solid #e4e4e4;
267 362
   }
363
+}
364
+
365
+.agent-cards {
366
+  flex: 1;
367
+  padding: 16px;
368
+  overflow-y: auto;
369
+  display: flex;
370
+  flex-direction: column;
371
+  gap: 12px;
372
+}
373
+
374
+.agent-card {
375
+  background: white;
376
+  border: 1px solid #e4e7ed;
377
+  border-radius: 8px;
378
+  padding: 16px;
379
+  cursor: pointer;
380
+  transition: all 0.3s ease;
381
+  position: relative;
382
+
383
+  &:hover {
384
+    border-color: #409eff;
385
+    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
386
+    transform: translateY(-2px);
387
+  }
268 388
 
269
-  .list-content {
270
-    flex: 1;
271
-    overflow-y: auto;
389
+  &.active {
390
+    border-color: #409eff;
391
+    background: #f0f9ff;
392
+    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
393
+  }
272 394
 
273
-    .agent-item {
274
-      padding: 12px 16px;
275
-      border-bottom: 1px solid #f0f0f0;
276
-      cursor: pointer;
277
-      transition: all 0.2s;
395
+  .card-header {
396
+    display: flex;
397
+    justify-content: space-between;
398
+    align-items: flex-start;
399
+    margin-bottom: 12px;
400
+
401
+    .card-title {
278 402
       display: flex;
279
-      justify-content: space-between;
280 403
       align-items: center;
404
+      gap: 8px;
405
+      flex: 1;
281 406
 
282
-      &:hover {
283
-        background: #f8f9fa;
407
+      .folder-icon {
408
+        color: #409eff;
409
+        font-size: 18px;
284 410
       }
285 411
 
286
-      &.active {
287
-        background: #e6f7ff;
288
-        border-right: 3px solid #1890ff;
412
+      .title-text {
413
+        font-weight: 600;
414
+        color: #303133;
415
+        font-size: 14px;
416
+        line-height: 1.4;
289 417
       }
418
+    }
290 419
 
291
-      .agent-info {
292
-        flex: 1;
293
-
294
-        .agent-name {
295
-          font-size: 14px;
296
-          font-weight: 500;
297
-          color: #333;
298
-          margin-bottom: 4px;
299
-        }
420
+    .card-actions {
421
+      opacity: 0;
422
+      transition: opacity 0.3s ease;
423
+    }
424
+  }
300 425
 
301
-        .agent-desc {
302
-          font-size: 12px;
303
-          color: #666;
304
-          margin-bottom: 4px;
305
-          overflow: hidden;
306
-          text-overflow: ellipsis;
307
-          white-space: nowrap;
308
-        }
426
+  &:hover .card-actions {
427
+    opacity: 1;
428
+  }
309 429
 
310
-        .agent-meta {
311
-          font-size: 11px;
312
-          color: #999;
430
+  .card-content {
431
+    .description {
432
+      color: #606266;
433
+      font-size: 13px;
434
+      line-height: 1.5;
435
+      margin: 0 0 12px 0;
436
+      display: -webkit-box;
437
+      -webkit-line-clamp: 2;
438
+      -webkit-box-orient: vertical;
439
+      overflow: hidden;
440
+    }
313 441
 
314
-          .create-time {
315
-            margin-right: 8px;
316
-          }
317
-        }
318
-      }
442
+    .meta-info {
443
+      display: flex;
444
+      justify-content: space-between;
445
+      align-items: center;
446
+      font-size: 12px;
447
+      color: #909399;
319 448
 
320
-      .agent-actions {
321
-        .action-icon {
322
-          color: #999;
323
-          font-size: 16px;
324
-          padding: 4px;
325
-          border-radius: 4px;
326
-
327
-          &:hover {
328
-            background: #f0f0f0;
329
-            color: #666;
330
-          }
331
-        }
449
+      .create-time {
450
+        background: #f0f2f5;
451
+        padding: 2px 6px;
452
+        border-radius: 3px;
332 453
       }
333 454
     }
455
+  }
456
+}
334 457
 
335
-    .empty-state {
336
-      padding: 40px 20px;
337
-      text-align: center;
338
-    }
458
+.empty-state {
459
+  display: flex;
460
+  flex-direction: column;
461
+  align-items: center;
462
+  justify-content: center;
463
+  padding: 60px 20px;
464
+  text-align: center;
465
+  color: #909399;
466
+
467
+  .empty-icon {
468
+    font-size: 48px;
469
+    margin-bottom: 16px;
470
+    opacity: 0.5;
339 471
   }
340 472
 
341
-  .pagination-wrapper {
342
-    padding: 16px;
343
-    border-top: 1px solid #e4e4e4;
344
-    background: white;
473
+  p {
474
+    margin: 0 0 16px 0;
475
+    font-size: 14px;
345 476
   }
346 477
 }
347 478
 
@@ -349,12 +480,4 @@ onMounted(() => {
349 480
   flex: 1;
350 481
   background: white;
351 482
 }
352
-
353
-:deep(.el-pagination) {
354
-  justify-content: center;
355
-
356
-  .el-pagination__sizes {
357
-    margin-right: 8px;
358
-  }
359
-}
360 483
 </style>

+ 69
- 55
llm-ui/src/views/llm/knowledge/index.vue View File

@@ -1,25 +1,5 @@
1 1
 <template>
2 2
   <div class="app-container">
3
-    <!-- 顶部搜索和操作栏 -->
4
-    <div class="top-toolbar">
5
-      <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
6
-        <el-form-item label="知识库名称" prop="collectionName">
7
-          <el-input v-model="queryParams.collectionName" placeholder="请输入知识库名称" clearable @keyup.enter="handleQuery" />
8
-        </el-form-item>
9
-        <el-form-item>
10
-          <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
11
-          <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
12
-        </el-form-item>
13
-      </el-form>
14
-
15
-      <div class="toolbar-actions">
16
-        <el-button type="primary" plain :icon="Plus" @click="handleAdd" v-hasPermi="['llm:knowledge:add']">
17
-          新增知识库
18
-        </el-button>
19
-        <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
20
-      </div>
21
-    </div>
22
-
23 3
     <!-- 主要内容区域 -->
24 4
     <div class="main-content">
25 5
       <!-- 左侧知识库列表 -->
@@ -27,6 +7,26 @@
27 7
         <div class="panel-header">
28 8
           <h3>知识库列表</h3>
29 9
           <span class="knowledge-count">{{ knowledgeList.length }} 个知识库</span>
10
+          <el-button type="primary" size="small" @click="handleAdd" v-hasPermi="['llm:knowledge:add']">
11
+            <el-icon>
12
+              <Plus />
13
+            </el-icon>
14
+            新增知识库
15
+          </el-button>
16
+        </div>
17
+
18
+        <!-- 搜索框 -->
19
+        <div class="search-box">
20
+          <el-input v-model="queryParams.collectionName" placeholder="请输入知识库名称" clearable @clear="handleQuery"
21
+            @keyup.enter="handleQuery">
22
+            <template #append>
23
+              <el-button @click="handleQuery">
24
+                <el-icon>
25
+                  <Search />
26
+                </el-icon>
27
+              </el-button>
28
+            </template>
29
+          </el-input>
30 30
         </div>
31 31
 
32 32
         <div class="knowledge-cards" v-loading="loading">
@@ -69,13 +69,13 @@
69 69
             </div>
70 70
           </div>
71 71
 
72
-          <!-- 空状态 -->
72
+          <!-- 空状态 - 无论是否搜索,当列表为空且未加载时都显示创建按钮 -->
73 73
           <div v-if="knowledgeList.length === 0 && !loading" class="empty-state">
74 74
             <el-icon class="empty-icon">
75 75
               <FolderOpened />
76 76
             </el-icon>
77
-            <p>暂无知识库</p>
78
-            <el-button type="primary" @click="handleAdd">创建第一个知识库</el-button>
77
+            <p>{{ queryParams.collectionName ? '搜索结果为空' : '暂无知识库' }}</p>
78
+            <el-button type="primary" @click="handleAdd">{{ queryParams.collectionName ? '创建知识库' : '创建第一个知识库' }}</el-button>
79 79
           </div>
80 80
         </div>
81 81
       </div>
@@ -333,7 +333,7 @@
333 333
 <script setup>
334 334
 import { ref, reactive, getCurrentInstance, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
335 335
 import { Search, Refresh, Plus, Edit, Delete, Upload, UploadFilled, Folder, MoreFilled, FolderOpened, Document, ChatRound, User, ChatDotRound, Promotion } from '@element-plus/icons-vue'
336
-import { listKnowledge, addKnowledge, updateKnowledge, delKnowledge, insertKnowledgeFile, listKnowledgeDocument, deleteKnowledgeFile } from "@/api/llm/knowLedge";
336
+import { listKnowledge, listKnowLedgeByCollectionName, addKnowledge, updateKnowledge, delKnowledge, insertKnowledgeFile, listKnowledgeDocument, deleteKnowledgeFile } from "@/api/llm/knowLedge";
337 337
 import { getAnswer, getAnswerStream,getContextFile } from '@/api/llm/rag';
338 338
 import { getToken } from "@/utils/auth";
339 339
 
@@ -418,25 +418,46 @@ const uploadRules = reactive({
418 418
 /** 查询知识库列表 */
419 419
 function getList() {
420 420
   loading.value = true;
421
-  listKnowledge().then(response => {
422
-    // 确保返回的数据是数组格式
423
-    if (Array.isArray(response.data)) {
424
-      knowledgeList.value = response.data;
425
-      knowledgeList.value.map(item => {
426
-        listKnowledgeDocument(item.collectionName).then(response => {
427
-          item.fileCount = response.data.length;
421
+  // 将查询参数传递给API调用
422
+  if (queryParams.collectionName == '') {
423
+    listKnowledge().then(response => {
424
+      // 确保返回的数据是数组格式
425
+      if (Array.isArray(response.data)) {
426
+        knowledgeList.value = response.data;
427
+        knowledgeList.value.map(item => {
428
+          listKnowledgeDocument(item.collectionName).then(response => {
429
+            item.fileCount = response.data.length;
430
+          })
428 431
         })
429
-      })
430
-    } else {
431
-      knowledgeList.value = [];
432
-    }
433
-    loading.value = false;
434
-  }).catch(error => {
435
-    loading.value = false;
436
-    proxy.$modal.msgError("获取知识库列表失败:" + error.message);
437
-  });
432
+      } else {
433
+        knowledgeList.value = [];
434
+      }
435
+      loading.value = false;
436
+    }).catch(error => {
437
+      loading.value = false;
438
+      proxy.$modal.msgError("获取知识库列表失败:" + error.message);
439
+    });
440
+  }
441
+  else {
442
+    listKnowLedgeByCollectionName({collectionName: queryParams.collectionName}).then(response => {
443
+      // 确保返回的数据是数组格式
444
+      if (Array.isArray(response.data)) {
445
+        knowledgeList.value = response.data;
446
+        knowledgeList.value.map(item => {
447
+          listKnowledgeDocument(item.collectionName).then(response => {
448
+            item.fileCount = response.data.length;
449
+          })
450
+        })
451
+      } else {
452
+        knowledgeList.value = [];
453
+      }
454
+      loading.value = false;
455
+    }).catch(error => {
456
+      loading.value = false;
457
+      proxy.$modal.msgError("获取知识库列表失败:" + error.message);
458
+    });
459
+  }
438 460
 }
439
-
440 461
 /** 选择知识库 */
441 462
 function selectKnowledge(knowledge) {
442 463
   // 如果正在生成回答,先停止
@@ -832,6 +853,8 @@ function reset() {
832 853
 
833 854
 /** 搜索按钮操作 */
834 855
 function handleQuery() {
856
+  // 重置页码
857
+  queryParams.pageNum = 1;
835 858
   getList();
836 859
 }
837 860
 
@@ -1006,20 +1029,6 @@ onUnmounted(() => {
1006 1029
   background: #f5f7fa;
1007 1030
 }
1008 1031
 
1009
-.top-toolbar {
1010
-  background: white;
1011
-  padding: 16px 24px;
1012
-  border-bottom: 1px solid #e4e7ed;
1013
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
1014
-
1015
-  .toolbar-actions {
1016
-    margin-top: 12px;
1017
-    display: flex;
1018
-    gap: 8px;
1019
-    align-items: center;
1020
-  }
1021
-}
1022
-
1023 1032
 .main-content {
1024 1033
   flex: 1;
1025 1034
   display: flex;
@@ -1071,6 +1080,11 @@ onUnmounted(() => {
1071 1080
     border-radius: 4px;
1072 1081
   }
1073 1082
 
1083
+  .search-box {
1084
+    padding: 16px;
1085
+    border-bottom: 1px solid #e4e4e4;
1086
+  }
1087
+  
1074 1088
   .header-actions {
1075 1089
     display: flex;
1076 1090
     gap: 8px;

Loading…
Cancel
Save