浏览代码

知识库、智能体功能完善

lamphua 4 周前
父节点
当前提交
1f9c526772

+ 6
- 4
llm-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java 查看文件

48
 
48
 
49
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
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
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
53
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
54
             ConnectConfig.builder()
54
             ConnectConfig.builder()
55
-                    .uri("http://192.168.28.188:19530")
55
+                    .uri("http://192.168.28.196:19530")
56
                     .build());
56
                     .build());
57
 
57
 
58
     /**
58
     /**
65
                                            @Param(description = "技术文件地址") String templatePath) throws IOException
65
                                            @Param(description = "技术文件地址") String templatePath) throws IOException
66
     {
66
     {
67
             try {
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
                 List<String> subTitles = extractSubTitles(templatePath, title);
71
                 List<String> subTitles = extractSubTitles(templatePath, title);
70
                 List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
72
                 List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
71
                 return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
73
                 return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
144
      */
146
      */
145
     public String generateAnswer(String prompt) throws IOException {
147
     public String generateAnswer(String prompt) throws IOException {
146
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
148
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
147
-                .model("Qwen2.5-1.5B-Instruct")
149
+                .model("Qwen2.5-7B-Instruct")
148
                 .build();
150
                 .build();
149
 
151
 
150
         List<ChatMessage> messages = new ArrayList<>();
152
         List<ChatMessage> messages = new ArrayList<>();

+ 9
- 0
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/KnowLedgeController.java 查看文件

41
         JSONArray collectionNames = milvusService.getCollectionNames();
41
         JSONArray collectionNames = milvusService.getCollectionNames();
42
         return success(collectionNames);
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 查看文件

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

+ 1
- 1
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java 查看文件

32
     public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question)
32
     public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question)
33
     {
33
     {
34
         List<JSONObject> contexts = langChainMilvusService.retrieveFromMilvus(collectionName, question, 10);
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 查看文件

29
      */
29
      */
30
     @GetMapping("/answer")
30
     @GetMapping("/answer")
31
     public Flux<AssistantMessage> answer(String topicId, String question) {
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
     @GetMapping("/answerWithDocument")
38
     @GetMapping("/answerWithDocument")
39
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws IOException
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 查看文件

16
      */
16
      */
17
     public JSONArray getCollectionNames();
17
     public JSONArray getCollectionNames();
18
 
18
 
19
+    /**
20
+     * 查询知识库Collection
21
+     */
22
+    public JSONArray listKnowLedgeByCollectionName(String collectionName);
23
+
19
     /**
24
     /**
20
      * 修改知识库Collection
25
      * 修改知识库Collection
21
      */
26
      */

+ 2
- 2
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java 查看文件

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

+ 39
- 1
llm-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java 查看文件

24
 
24
 
25
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
25
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
26
             ConnectConfig.builder()
26
             ConnectConfig.builder()
27
-                    .uri("http://192.168.28.188:19530")
27
+                    .uri("http://192.168.28.196:19530")
28
                     .build());
28
                     .build());
29
 
29
 
30
     /**
30
     /**
88
     /**
88
     /**
89
      * 查询知识库Collection
89
      * 查询知识库Collection
90
      */
90
      */
91
+    @Override
91
     public JSONArray getCollectionNames() {
92
     public JSONArray getCollectionNames() {
92
         JSONArray jsonArray = new JSONArray();
93
         JSONArray jsonArray = new JSONArray();
93
         ListCollectionsResp listResponse = milvusClient.listCollections();
94
         ListCollectionsResp listResponse = milvusClient.listCollections();
112
         return jsonArray;
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
      * 修改知识库Collection
149
      * 修改知识库Collection
117
      */
150
      */
151
+    @Override
118
     public void collectionRename(String collectionName, String newCollectionName) {
152
     public void collectionRename(String collectionName, String newCollectionName) {
119
         RenameCollectionReq renameCollectionReq = RenameCollectionReq.builder()
153
         RenameCollectionReq renameCollectionReq = RenameCollectionReq.builder()
120
                 .collectionName(collectionName)
154
                 .collectionName(collectionName)
127
     /**
161
     /**
128
      * 删除知识库Collection
162
      * 删除知识库Collection
129
      */
163
      */
164
+    @Override
130
     public void deleteCollectionName(String collectionName) {
165
     public void deleteCollectionName(String collectionName) {
131
         DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
166
         DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
132
                 .collectionName(collectionName)
167
                 .collectionName(collectionName)
137
     /**
172
     /**
138
      * 查询知识库文件
173
      * 查询知识库文件
139
      */
174
      */
175
+    @Override
140
     public List<String> listDocument(String collectionName, String fileType) {
176
     public List<String> listDocument(String collectionName, String fileType) {
141
         List<String> documentList = new ArrayList<>();
177
         List<String> documentList = new ArrayList<>();
142
         loadCollectionName(collectionName);
178
         loadCollectionName(collectionName);
168
     /**
204
     /**
169
      * 删除知识库文件
205
      * 删除知识库文件
170
      */
206
      */
207
+    @Override
171
     public void removeDocument(String collectionName, String fileName) {
208
     public void removeDocument(String collectionName, String fileName) {
172
         loadCollectionName(collectionName);
209
         loadCollectionName(collectionName);
173
         DeleteReq deleteReq = DeleteReq.builder()
210
         DeleteReq deleteReq = DeleteReq.builder()
180
     /**
217
     /**
181
      * 删除知识库所有文件
218
      * 删除知识库所有文件
182
      */
219
      */
220
+    @Override
183
     public void removeAllDocument(String collectionName) {
221
     public void removeAllDocument(String collectionName) {
184
         loadCollectionName(collectionName);
222
         loadCollectionName(collectionName);
185
         DeleteReq deleteReq = DeleteReq.builder()
223
         DeleteReq deleteReq = DeleteReq.builder()

+ 169
- 41
llm-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java 查看文件

74
 
74
 
75
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
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
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
79
     private static final MilvusClientV2 milvusClient = new MilvusClientV2(
80
             ConnectConfig.builder()
80
             ConnectConfig.builder()
81
-                    .uri("http://192.168.28.188:19530")
81
+                    .uri("http://192.168.28.196:19530")
82
                     .build());
82
                     .build());
83
 
83
 
84
     /**
84
     /**
116
         String content = "";
116
         String content = "";
117
         if (agentName.contains("技术"))
117
         if (agentName.contains("技术"))
118
             content = "我是投标文件写作助手,我将助您完成技术文件部分撰写。请上传招标询价文件,分析后将依招标服务要求,运用技术方案知识库,为您提供参考。";
118
             content = "我是投标文件写作助手,我将助您完成技术文件部分撰写。请上传招标询价文件,分析后将依招标服务要求,运用技术方案知识库,为您提供参考。";
119
+        else if (agentName.contains("检查"))
120
+            content = "我是文档检查助手,我将助您完成错别字检查。请上传文件。";
119
         return content;
121
         return content;
120
     }
122
     }
121
 
123
 
142
         cmcDocument.setDocumentId(new SnowFlake().generateId());
144
         cmcDocument.setDocumentId(new SnowFlake().generateId());
143
         cmcDocument.setChatId(chatId);
145
         cmcDocument.setChatId(chatId);
144
         cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
146
         cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
147
+        if (file.getOriginalFilename().endsWith(".pdf"))
148
+            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename().replace(".pdf", ".docx"));
145
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
149
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
146
         String message = "";
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
         if (agentName.contains("技术")) {
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
             String[] filenameSplit = file.getOriginalFilename().split("\\.");
158
             String[] filenameSplit = file.getOriginalFilename().split("\\.");
155
             String outputFilename = prefixPath + "/" + file.getOriginalFilename()
159
             String outputFilename = prefixPath + "/" + file.getOriginalFilename()
156
                     .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
160
                     .replace(filenameSplit[filenameSplit.length - 2], filenameSplit[filenameSplit.length - 2] + "_" + agentName);
157
             if (file.getOriginalFilename().endsWith(".doc"))
161
             if (file.getOriginalFilename().endsWith(".doc"))
158
                 outputFilename = outputFilename.replace(".doc", ".docx");
162
                 outputFilename = outputFilename.replace(".doc", ".docx");
163
+            if (file.getOriginalFilename().endsWith(".pdf"))
164
+                outputFilename = outputFilename.replace(".pdf", ".docx");
159
             Path outputFilePath = Paths.get(RuoYiConfig.getProfile() + outputFilename);
165
             Path outputFilePath = Paths.get(RuoYiConfig.getProfile() + outputFilename);
160
             Files.deleteIfExists(outputFilePath);
166
             Files.deleteIfExists(outputFilePath);
161
             InputStream fileInputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
167
             InputStream fileInputStream = new FileInputStream(RuoYiConfig.getProfile() + "/upload/agent/template/technical.docx");
165
                     doc.write(out);
171
                     doc.write(out);
166
                 }
172
                 }
167
             }
173
             }
174
+            String question = "工作大纲/工作范围/招标范围/服务范围";
168
             String chapters = generateAnswerWithDocument(profilePath + "/" + file.getOriginalFilename(),
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
                     "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
178
                     "若您对章节标题有异议,请打开" + "【<a href='/profile" + outputFilename + "'> 技术文件 " + "</a>】" + "进行修改,后续将根据修改后的章节标题,帮您生成对应章节内容。\n\n" +
172
                     "思考时间可能较长,请耐心等待!\n";
179
                     "思考时间可能较长,请耐心等待!\n";
173
         }
180
         }
181
+        else if (agentName.contains("检查")) {
182
+            message = generateAnswerWithDocumentContent(profilePath + "/" + file.getOriginalFilename());
183
+        }
174
         jsonObject.put("assistantMessage", message);
184
         jsonObject.put("assistantMessage", message);
175
         return jsonObject;
185
         return jsonObject;
176
     }
186
     }
203
         cmcDocument.setDocumentId(new SnowFlake().generateId());
213
         cmcDocument.setDocumentId(new SnowFlake().generateId());
204
         cmcDocument.setChatId(chatId);
214
         cmcDocument.setChatId(chatId);
205
         cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
215
         cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
216
+        if (file.getOriginalFilename().endsWith(".pdf"))
217
+            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename().replace(".pdf", ".docx"));
206
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
218
         cmcDocumentMapper.insertCmcDocument(cmcDocument);
207
         String message = "";
219
         String message = "";
208
         if (agentName.contains("技术")) {
220
         if (agentName.contains("技术")) {
303
         return cmcAgentMapper.deleteCmcAgentByAgentId(agentId);
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
      * 调用LLM生成回答 - 针对技术文件章节内容生成
335
      * 调用LLM生成回答 - 针对技术文件章节内容生成
308
      */
336
      */
311
 
339
 
312
         // 构建招标文件内容的嵌入向量存储
340
         // 构建招标文件内容的嵌入向量存储
313
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
341
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
314
-        List<TextSegment> segments = splitDocument(profilePath);
342
+        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
315
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
343
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
316
         embeddingStore.addAll(embeddings, segments);
344
         embeddingStore.addAll(embeddings, segments);
317
 
345
 
504
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
532
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
505
         File profilePath = new File(uploadFilePath);
533
         File profilePath = new File(uploadFilePath);
506
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
534
         InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
507
-        List<TextSegment> segments = splitDocument(profilePath);
535
+        List<TextSegment> segments = splitDocument(profilePath, 300, 50);
508
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
536
         List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
509
         embeddingStore.addAll(embeddings, segments);
537
         embeddingStore.addAll(embeddings, segments);
510
         Embedding queryEmbedding = embeddingModel.embed(question).content();
538
         Embedding queryEmbedding = embeddingModel.embed(question).content();
519
             sb.append(requests).append("\n\n");
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
                 .append("6.1 XX\n" +
553
                 .append("6.1 XX\n" +
524
                 "6.2 XX\n" +
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
         return writeChapters(sb.toString(), templatePath);
559
         return writeChapters(sb.toString(), templatePath);
527
     }
560
     }
528
 
561
 
569
                 "6.2.2 XX\n" +
602
                 "6.2.2 XX\n" +
570
                 "6.3 XX\n" +
603
                 "6.3 XX\n" +
571
                 "6.3.1 XX\n" +
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
         String content = generateAnswer(sb);
610
         String content = generateAnswer(sb);
574
         writeTitles(content, templatePath);
611
         writeTitles(content, templatePath);
575
         return content;
612
         return content;
581
      */
618
      */
582
     public String generateAnswer(String prompt) throws IOException {
619
     public String generateAnswer(String prompt) throws IOException {
583
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
620
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
584
-                .model("Qwen2.5-1.5B-Instruct")
621
+                .model("Qwen2.5-7B-Instruct")
585
                 .build();
622
                 .build();
586
 
623
 
587
         List<ChatMessage> messages = new ArrayList<>();
624
         List<ChatMessage> messages = new ArrayList<>();
658
         File file = new File(templatePath);
695
         File file = new File(templatePath);
659
         FileInputStream fileInputStream = new FileInputStream(file);
696
         FileInputStream fileInputStream = new FileInputStream(file);
660
         try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
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
             try (FileOutputStream out = new FileOutputStream(templatePath)) {
717
             try (FileOutputStream out = new FileOutputStream(templatePath)) {
676
                 document.write(out);
718
                 document.write(out);
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
         Document document;
868
         Document document;
827
         InputStream fileInputStream = new FileInputStream(transferFile);
869
         InputStream fileInputStream = new FileInputStream(transferFile);
844
         else {
886
         else {
845
             throw new UnsupportedOperationException("不支持文件类型: " + filename);
887
             throw new UnsupportedOperationException("不支持文件类型: " + filename);
846
         }
888
         }
847
-        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
889
+        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(maxSegmentSizeInChars,  maxOverlapSizeInChars);
848
         return splitter.split(document);
890
         return splitter.split(document);
849
     }
891
     }
850
 
892
 
872
 
914
 
873
             // 将内容解析为块(普通段落/表格/标题/列表项等),然后倒序插入,确保顺序正确
915
             // 将内容解析为块(普通段落/表格/标题/列表项等),然后倒序插入,确保顺序正确
874
             class Block {
916
             class Block {
875
-                String type; // para | table | h4 | list1 | list2 | imageText
917
+                String type; // para | table | h4 | list1 | list2 | imageText | boldOnly | numberedBold
876
                 String text;
918
                 String text;
877
                 List<String> table;
919
                 List<String> table;
878
                 Integer h4Index;       // 预计算的四级标题编号
920
                 Integer h4Index;       // 预计算的四级标题编号
879
                 Integer list1Index;    // 预计算的一级列表编号 1),2),3)
921
                 Integer list1Index;    // 预计算的一级列表编号 1),2),3)
880
                 Integer list2Index;    // 预计算的二级列表编号 (1),(2),(3)
922
                 Integer list2Index;    // 预计算的二级列表编号 (1),(2),(3)
923
+                String boldText;      // 加粗文本部分
924
+                String suffixText;    // 后缀文本(如冒号及后续内容)
925
+                String numberPrefix;  // 数字前缀,如 "1. "
881
                 Block(String t, String x) { type = t; text = x; }
926
                 Block(String t, String x) { type = t; text = x; }
882
                 Block(List<String> tbl) { type = "table"; table = new ArrayList<>(tbl); }
927
                 Block(List<String> tbl) { type = "table"; table = new ArrayList<>(tbl); }
883
                 Block(String t, String x, Integer h4Idx, Integer l1Idx, Integer l2Idx) {
928
                 Block(String t, String x, Integer h4Idx, Integer l1Idx, Integer l2Idx) {
884
                     type = t; text = x; h4Index = h4Idx; list1Index = l1Idx; list2Index = l2Idx;
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
             List<Block> blocks = new ArrayList<>();
938
             List<Block> blocks = new ArrayList<>();
888
 
939
 
890
             int h4Counter = 0;
941
             int h4Counter = 0;
891
             int list1Counter = 0;
942
             int list1Counter = 0;
892
             int list2Counter = 0;
943
             int list2Counter = 0;
944
+            String previousLine = ""; // 记录上一行内容
893
 
945
 
894
             for (String raw : lines) {
946
             for (String raw : lines) {
895
                 String line = raw.trim();
947
                 String line = raw.trim();
896
                 if (line.isEmpty()) {
948
                 if (line.isEmpty()) {
897
                     blocks.add(new Block("para", "")); // 空段落
949
                     blocks.add(new Block("para", "")); // 空段落
950
+                    previousLine = line;
898
                     continue;
951
                     continue;
899
                 }
952
                 }
900
 
953
 
954
+                // 检查上一行是否以中文冒号结尾,如果是则重置列表编号
955
+                if (previousLine.endsWith(":") || previousLine.endsWith(":")) {
956
+                    list1Counter = 0;
957
+                    list2Counter = 0;
958
+                }
959
+
901
                 if (line.startsWith("#")) {
960
                 if (line.startsWith("#")) {
902
                     // 跳过前三级标题
961
                     // 跳过前三级标题
962
+                    previousLine = line;
903
                     continue;
963
                     continue;
904
                 } else if (line.startsWith("####")) {
964
                 } else if (line.startsWith("####")) {
905
                     // 四级标题,编号采用预计算,保证倒序插入后视觉顺序仍递增
965
                     // 四级标题,编号采用预计算,保证倒序插入后视觉顺序仍递增
930
                     tableLines.add(line);
990
                     tableLines.add(line);
931
                 } else if (line.startsWith("- **")) {
991
                 } else if (line.startsWith("- **")) {
932
                     // 一级加粗列表项,编号为 1),2),3) —— 预计算编号
992
                     // 一级加粗列表项,编号为 1),2),3) —— 预计算编号
933
-                    String listItem = line.replaceFirst("- \\\\*\\\\*", "").replace("**", "");
993
+                    String listItem = line.replaceFirst("- \\*\\*", "").replace("**", "");
934
                     listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
994
                     listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
935
                     list1Counter++;
995
                     list1Counter++;
936
                     // 一级列表开始时,重置二级列表编号
996
                     // 一级列表开始时,重置二级列表编号
939
                     blocks.add(b);
999
                     blocks.add(b);
940
                 } else if (line.startsWith("  - **")) {
1000
                 } else if (line.startsWith("  - **")) {
941
                     // 二级加粗列表项,编号为 (1),(2),(3) —— 预计算编号
1001
                     // 二级加粗列表项,编号为 (1),(2),(3) —— 预计算编号
942
-                    String listItem = line.replaceFirst("  - \\\\*\\\\*", "").replace("**", "");
1002
+                    String listItem = line.replaceFirst("  - \\*\\*", "").replace("**", "");
943
                     listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
1003
                     listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
944
                     list2Counter++;
1004
                     list2Counter++;
945
                     Block b = new Block("list2", listItem, null, null, list2Counter);
1005
                     Block b = new Block("list2", listItem, null, null, list2Counter);
946
                     blocks.add(b);
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
                 } else {
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
             if (inTable && !tableLines.isEmpty()) {
1047
             if (inTable && !tableLines.isEmpty()) {
998
                         run.setFontSize(12);
1087
                         run.setFontSize(12);
999
                         break;
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
                     case "imageText": {
1129
                     case "imageText": {
1002
                         XWPFParagraph p = document.insertNewParagraph(cursor);
1130
                         XWPFParagraph p = document.insertNewParagraph(cursor);
1003
                         XWPFRun run = p.createRun();
1131
                         XWPFRun run = p.createRun();

+ 119
- 9
llm-back/vllm_server.py 查看文件

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

+ 10
- 2
llm-ui/src/api/llm/knowLedge.js 查看文件

7
 import request from '@/utils/request'
7
 import request from '@/utils/request'
8
 
8
 
9
 // 查询知识库列表
9
 // 查询知识库列表
10
-export function listKnowledge(query) {
10
+export function listKnowledge() {
11
   return request({
11
   return request({
12
     url: '/llm/knowledge/list',
12
     url: '/llm/knowledge/list',
13
     method: 'get',
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 查看文件

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

+ 309
- 186
llm-ui/src/views/llm/agent/index.vue 查看文件

1
 <!--
1
 <!--
2
  * @Author: ysh
2
  * @Author: ysh
3
  * @Date: 2025-07-17 18:16:50
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
 <template>
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
             </div>
69
             </div>
45
           </div>
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
         </div>
81
         </div>
67
       </div>
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
       </div>
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
     </div>
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
   </div>
129
   </div>
97
 </template>
130
 </template>
98
 
131
 
99
 <script setup>
132
 <script setup>
100
 import { ref, reactive, onMounted } from 'vue'
133
 import { ref, reactive, onMounted } from 'vue'
101
 import { ElMessage, ElMessageBox } from 'element-plus'
134
 import { ElMessage, ElMessageBox } from 'element-plus'
135
+import { Search, Plus, MoreFilled, Folder, FolderOpened } from '@element-plus/icons-vue'
102
 import { listAgent, addAgent, updateAgent, delAgent, opening } from '@/api/llm/agent'
136
 import { listAgent, addAgent, updateAgent, delAgent, opening } from '@/api/llm/agent'
103
 import { answer } from '@/api/llm/mcp'
137
 import { answer } from '@/api/llm/mcp'
104
 import AgentDetail from './AgentDetail.vue'
138
 import AgentDetail from './AgentDetail.vue'
105
 
139
 
140
+const open = ref(false);
106
 // 响应式数据
141
 // 响应式数据
107
 const loading = ref(false)
142
 const loading = ref(false)
108
 const agentList = ref([])
143
 const agentList = ref([])
158
   editAgentId.value = null
193
   editAgentId.value = null
159
   form.agentName = ''
194
   form.agentName = ''
160
   form.description = ''
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
 </script>
298
 </script>
235
 
299
 
236
 <style lang="scss" scoped>
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
   display: flex;
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
   background: white;
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
   display: flex;
321
   display: flex;
248
   flex-direction: column;
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
   .search-box {
359
   .search-box {
265
     padding: 16px;
360
     padding: 16px;
266
     border-bottom: 1px solid #e4e4e4;
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
       display: flex;
402
       display: flex;
279
-      justify-content: space-between;
280
       align-items: center;
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
   flex: 1;
480
   flex: 1;
350
   background: white;
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
 </style>
483
 </style>

+ 69
- 55
llm-ui/src/views/llm/knowledge/index.vue 查看文件

1
 <template>
1
 <template>
2
   <div class="app-container">
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
     <div class="main-content">
4
     <div class="main-content">
25
       <!-- 左侧知识库列表 -->
5
       <!-- 左侧知识库列表 -->
27
         <div class="panel-header">
7
         <div class="panel-header">
28
           <h3>知识库列表</h3>
8
           <h3>知识库列表</h3>
29
           <span class="knowledge-count">{{ knowledgeList.length }} 个知识库</span>
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
         </div>
30
         </div>
31
 
31
 
32
         <div class="knowledge-cards" v-loading="loading">
32
         <div class="knowledge-cards" v-loading="loading">
69
             </div>
69
             </div>
70
           </div>
70
           </div>
71
 
71
 
72
-          <!-- 空状态 -->
72
+          <!-- 空状态 - 无论是否搜索,当列表为空且未加载时都显示创建按钮 -->
73
           <div v-if="knowledgeList.length === 0 && !loading" class="empty-state">
73
           <div v-if="knowledgeList.length === 0 && !loading" class="empty-state">
74
             <el-icon class="empty-icon">
74
             <el-icon class="empty-icon">
75
               <FolderOpened />
75
               <FolderOpened />
76
             </el-icon>
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
           </div>
79
           </div>
80
         </div>
80
         </div>
81
       </div>
81
       </div>
333
 <script setup>
333
 <script setup>
334
 import { ref, reactive, getCurrentInstance, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
334
 import { ref, reactive, getCurrentInstance, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
335
 import { Search, Refresh, Plus, Edit, Delete, Upload, UploadFilled, Folder, MoreFilled, FolderOpened, Document, ChatRound, User, ChatDotRound, Promotion } from '@element-plus/icons-vue'
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
 import { getAnswer, getAnswerStream,getContextFile } from '@/api/llm/rag';
337
 import { getAnswer, getAnswerStream,getContextFile } from '@/api/llm/rag';
338
 import { getToken } from "@/utils/auth";
338
 import { getToken } from "@/utils/auth";
339
 
339
 
418
 /** 查询知识库列表 */
418
 /** 查询知识库列表 */
419
 function getList() {
419
 function getList() {
420
   loading.value = true;
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
 function selectKnowledge(knowledge) {
462
 function selectKnowledge(knowledge) {
442
   // 如果正在生成回答,先停止
463
   // 如果正在生成回答,先停止
832
 
853
 
833
 /** 搜索按钮操作 */
854
 /** 搜索按钮操作 */
834
 function handleQuery() {
855
 function handleQuery() {
856
+  // 重置页码
857
+  queryParams.pageNum = 1;
835
   getList();
858
   getList();
836
 }
859
 }
837
 
860
 
1006
   background: #f5f7fa;
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
 .main-content {
1032
 .main-content {
1024
   flex: 1;
1033
   flex: 1;
1025
   display: flex;
1034
   display: flex;
1071
     border-radius: 4px;
1080
     border-radius: 4px;
1072
   }
1081
   }
1073
 
1082
 
1083
+  .search-box {
1084
+    padding: 16px;
1085
+    border-bottom: 1px solid #e4e4e4;
1086
+  }
1087
+  
1074
   .header-actions {
1088
   .header-actions {
1075
     display: flex;
1089
     display: flex;
1076
     gap: 8px;
1090
     gap: 8px;

正在加载...
取消
保存