Просмотр исходного кода

合同内项目信息路由跳转问题

lamphua 2 дней назад
Родитель
Сommit
2987409869

+ 2
- 2
oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java Просмотреть файл

@@ -89,7 +89,7 @@ public class SysUserController extends BaseController
89 89
             List<Long> userPostList = postService.selectPostListByUserId(sysUser.getUserId());
90 90
             if (userPostList.size() > 0) {
91 91
                 for (Long post : userPostList)
92
-                    postName.append(postService.selectPostById(post).getPostName()).append("");
92
+                    postName.append(postService.selectPostById(post).getPostName()).append("/");
93 93
                 sysUser.setPostNames(postName.substring(0, postName.length() - 1));
94 94
             }
95 95
         }
@@ -108,7 +108,7 @@ public class SysUserController extends BaseController
108 108
             StringBuilder postName = new StringBuilder();
109 109
             List<Long> userPostList = postService.selectPostListByUserId(sysUser.getUserId());
110 110
             for (Long post : userPostList)
111
-                postName.append(postService.selectPostById(post).getPostName()).append("");
111
+                postName.append(postService.selectPostById(post).getPostName()).append("/");
112 112
             if (!postName.toString().equals(""))
113 113
                 sysUser.setPostNames(postName.substring(0, postName.length() - 1));
114 114
             sysUser.setUserId(sysUser.getUserId() - 1);

+ 6
- 0
oa-back/ruoyi-agent/pom.xml Просмотреть файл

@@ -83,6 +83,12 @@
83 83
             <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
84 84
             <version>0.35.0</version>
85 85
         </dependency>
86
+        <!-- MySQL 驱动依赖 -->
87
+        <dependency>
88
+            <groupId>mysql</groupId>
89
+            <artifactId>mysql-connector-java</artifactId>
90
+            <version>8.0.33</version>
91
+        </dependency>
86 92
     </dependencies>
87 93
 
88 94
     <dependencyManagement>

+ 42
- 32
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java Просмотреть файл

@@ -1,5 +1,6 @@
1 1
 package com.ruoyi.agent.service.impl;
2 2
 
3
+import com.alibaba.fastjson2.JSONArray;
3 4
 import com.alibaba.fastjson2.JSONObject;
4 5
 import com.ruoyi.agent.service.IMcpService;
5 6
 import dev.langchain4j.data.document.Document;
@@ -26,7 +27,6 @@ import org.apache.poi.extractor.POITextExtractor;
26 27
 import org.apache.poi.extractor.ExtractorFactory;
27 28
 import org.apache.poi.xwpf.usermodel.*;
28 29
 import org.apache.xmlbeans.XmlCursor;
29
-import org.noear.solon.Solon;
30 30
 import org.noear.solon.ai.annotation.ToolMapping;
31 31
 import org.noear.solon.ai.chat.ChatModel;
32 32
 import org.noear.solon.ai.chat.ChatResponse;
@@ -40,6 +40,7 @@ import org.noear.solon.annotation.Param;
40 40
 import org.springframework.beans.factory.annotation.Value;
41 41
 import org.springframework.stereotype.Service;
42 42
 
43
+import javax.annotation.PostConstruct;
43 44
 import java.io.*;
44 45
 import java.sql.*;
45 46
 import java.util.*;
@@ -50,24 +51,39 @@ public class McpServiceImpl implements IMcpService {
50 51
 
51 52
     private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
52 53
 
53
-    @Value("${llmService.url}")
54
-    private static String llmServiceUrl;
54
+    @Value("${cmc.llmService.url}")
55
+    private String llmServiceUrl;
55 56
 
56
-    @Value("${lmilvusService.url}")
57
-    private static String milvusServiceUrl;
57
+    @Value("${cmc.milvusService.url}")
58
+    private String milvusServiceUrl;
58 59
 
59
-    @Value("${mysqlService.jdbcUrl}")
60
-    private static String url ;
60
+    @Value("${cmc.mysqlService.jdbcUrl}")
61
+    private String url ;
61 62
 
62
-    @Value("${mysqlService.username}")
63
-    private static String user;
63
+    @Value("${cmc.mysqlService.username}")
64
+    private String user;
64 65
 
65
-    @Value("${mysqlService.password}")
66
-    private static String password;
66
+    @Value("${cmc.mysqlService.password}")
67
+    private String password;
67 68
 
68 69
     @Value("${cmc.profile}")
69
-    private static String profile;
70
+    private String profile;
70 71
 
72
+    private MilvusClientV2 milvusClient;
73
+    
74
+    /**
75
+     * 初始化 Milvus 客户端
76
+     */
77
+    @PostConstruct
78
+    public void initMilvusClient() {
79
+        if (milvusServiceUrl == null || milvusServiceUrl.trim().isEmpty()) {
80
+            throw new IllegalStateException("Milvus service URL 未配置");
81
+        }
82
+//        this.milvusClient = new MilvusClientV2(
83
+//                ConnectConfig.builder()
84
+//                        .uri(milvusServiceUrl)
85
+//                        .build());
86
+    }
71 87
     /**
72 88
      * 调用LLM+RAG(外部文件+知识库)生成回答
73 89
      */
@@ -92,34 +108,32 @@ public class McpServiceImpl implements IMcpService {
92 108
     /**
93 109
      * 查询OA数据生成回答
94 110
      */
95
-    @ToolMapping(description = "数据查询")
96
-    public AssistantMessage SQLQuery(@Param(description = "sql语句") String sqlString) throws Exception
111
+    @ToolMapping(description = "执行SQL查询")
112
+    public JSONArray SQLQuery(@Param(description = "sql语句") String sqlString)
97 113
     {
98
-            try {
99
-                List<Map<String, Object>> data = executeQuery(sqlString, 100.0);
100
-                return ChatMessage.ofAssistant(generateAnswer(sqlString));
101
-            } catch (IOException e) {
102
-                throw new RuntimeException(e);
103
-            }
114
+            sqlString = sqlString.replace("[","").replace("]","");
115
+            return executeQuery(sqlString);
104 116
     }
105 117
 
106
-    public static List<Map<String, Object>> executeQuery(String sql, Object... params) {
107
-        List<Map<String, Object>> result = new ArrayList<>();
118
+    public JSONArray executeQuery(String sql) {
119
+        JSONArray result = new JSONArray();
120
+
121
+        try {
122
+            // 显式加载 MySQL 驱动
123
+            Class.forName("com.mysql.cj.jdbc.Driver");
124
+        } catch (ClassNotFoundException e) {
125
+            throw new RuntimeException("MySQL 驱动未找到", e);
126
+        }
108 127
 
109 128
         try (Connection conn = DriverManager.getConnection(url, user, password);
110 129
              PreparedStatement stmt = conn.prepareStatement(sql)) {
111 130
 
112
-            // 设置参数
113
-            for (int i = 0; i < params.length; i++) {
114
-                stmt.setObject(i + 1, params[i]);
115
-            }
116
-
117 131
             ResultSet rs = stmt.executeQuery();
118 132
             ResultSetMetaData metaData = rs.getMetaData();
119 133
             int columnCount = metaData.getColumnCount();
120 134
 
121 135
             while (rs.next()) {
122
-                Map<String, Object> row = new HashMap<>();
136
+                JSONObject row = new JSONObject();
123 137
                 for (int i = 1; i <= columnCount; i++) {
124 138
                     String columnName = metaData.getColumnLabel(i);
125 139
                     Object value = rs.getObject(i);
@@ -362,10 +376,6 @@ public class McpServiceImpl implements IMcpService {
362 376
      * @return
363 377
      */
364 378
     private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
365
-        MilvusClientV2 milvusClient = new MilvusClientV2(
366
-                ConnectConfig.builder()
367
-                        .uri(milvusServiceUrl)
368
-                        .build());
369 379
         List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
370 380
 
371 381
         //  加载集合

+ 13
- 0
oa-back/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java Просмотреть файл

@@ -192,6 +192,9 @@ public class SysUser extends BaseEntity
192 192
     })
193 193
     private SysDept dept;
194 194
 
195
+    @Excel(name = "备注")
196
+    private String remark;
197
+
195 198
     private CmcPostSalary salary;
196 199
 
197 200
     /** 角色对象 */
@@ -692,6 +695,16 @@ public class SysUser extends BaseEntity
692 695
     {
693 696
         return telephone;
694 697
     }
698
+    public String getRemark()
699
+    {
700
+        return remark;
701
+    }
702
+
703
+    public void setRemark(String remark)
704
+    {
705
+        this.remark = remark;
706
+    }
707
+
695 708
 
696 709
     @Override
697 710
     public String toString() {

+ 1
- 4
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java Просмотреть файл

@@ -27,16 +27,13 @@ public class RagController extends BaseController
27 27
     @Autowired
28 28
     private ILangChainMilvusService langChainMilvusService;
29 29
 
30
-    @Value("${llmService.url}")
31
-    private String llmServiceUrl;
32
-
33 30
     /**
34 31
      * 调用LLM+RAG(知识库)生成回答
35 32
      */
36 33
     @GetMapping("/answer")
37 34
     public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question) throws IOException {
38 35
         List<JSONObject> contexts = langChainMilvusService.retrieveFromMilvus(collectionName, question, 10);
39
-        return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, llmServiceUrl);
36
+        return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts);
40 37
     }
41 38
 
42 39
     /**

+ 86
- 5
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java Просмотреть файл

@@ -1,8 +1,20 @@
1 1
 package com.ruoyi.web.llm.controller;
2 2
 
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.common.config.RuoYiConfig;
3 5
 import com.ruoyi.common.core.controller.BaseController;
6
+import com.ruoyi.llm.domain.CmcChat;
7
+import com.ruoyi.llm.service.ICmcChatService;
4 8
 import com.ruoyi.web.llm.service.ILangChainMilvusService;
9
+import org.noear.solon.ai.chat.ChatModel;
10
+import org.noear.solon.ai.chat.ChatResponse;
11
+import org.noear.solon.ai.chat.ChatSession;
5 12
 import org.noear.solon.ai.chat.message.AssistantMessage;
13
+import org.noear.solon.ai.chat.message.ChatMessage;
14
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
15
+import org.noear.solon.ai.mcp.McpChannel;
16
+import org.noear.solon.ai.mcp.client.McpClientProvider;
17
+import org.reactivestreams.Publisher;
6 18
 import org.springframework.beans.factory.annotation.Autowired;
7 19
 import org.springframework.beans.factory.annotation.Value;
8 20
 import org.springframework.web.bind.annotation.GetMapping;
@@ -10,11 +22,13 @@ import org.springframework.web.bind.annotation.RequestMapping;
10 22
 import org.springframework.web.bind.annotation.RestController;
11 23
 import reactor.core.publisher.Flux;
12 24
 
13
-import java.io.IOException;
25
+import java.io.*;
26
+import java.util.ArrayList;
27
+import java.util.List;
14 28
 
15 29
 /**
16 30
  * session对话Controller
17
- * 
31
+ *
18 32
  * @author cmc
19 33
  * @date 2025-04-08
20 34
  */
@@ -22,6 +36,9 @@ import java.io.IOException;
22 36
 @RequestMapping("/llm/session")
23 37
 public class SessionController extends BaseController
24 38
 {
39
+    @Autowired
40
+    private ICmcChatService cmcChatService;
41
+
25 42
     @Autowired
26 43
     private ILangChainMilvusService langChainMilvusService;
27 44
 
@@ -32,8 +49,72 @@ public class SessionController extends BaseController
32 49
      * 生成回答
33 50
      */
34 51
     @GetMapping("/answer")
35
-    public Flux<AssistantMessage> answer(String topicId, String question) {
36
-        return langChainMilvusService.generateAnswer(topicId, question, llmServiceUrl);
52
+    public Flux<AssistantMessage> answer(String topicId, String question) throws IOException {
53
+        McpClientProvider clientProvider = McpClientProvider.builder()
54
+                .channel(McpChannel.SSE)
55
+                .url("http://localhost:8080/mcp/sse")
56
+                .build();
57
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
58
+                .model("Qwen2.5-7B-Instruct")
59
+                .defaultToolsAdd(clientProvider)
60
+                .build();
61
+
62
+        List<ChatMessage> messages = new ArrayList<>();
63
+        CmcChat cmcChat = new CmcChat();
64
+        cmcChat.setTopicId(topicId);
65
+        List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
66
+        for (CmcChat chat : cmcChatList) {
67
+            messages.add(ChatMessage.ofUser(chat.getInput()));
68
+            messages.add(ChatMessage.ofAssistant(chat.getOutput()));
69
+        }
70
+        StringBuilder sqlContent = new StringBuilder();
71
+        File sqlStructure = new File(RuoYiConfig.getProfile() + "/cmc_oa.sql");
72
+        try (BufferedReader br = new BufferedReader(new FileReader(sqlStructure))) {
73
+            String line;
74
+
75
+            while ((line = br.readLine()) != null) {
76
+                sqlContent.append(line).append("\n");
77
+            }
78
+        } catch (IOException e) {
79
+            System.err.println("读取文件时出错: " + e.getMessage());
80
+        }
81
+
82
+        String prompt = "你是一个MySQL专家。根据以下OA系统表结构信息:  \n" +
83
+                sqlContent + " \n" +
84
+                "用户查询:  \n"+
85
+                question  + " \n"+
86
+                "你可以使用SQLQuery工具来查询OA系统数据。  \n" +
87
+                "SQLQuery工具功能:执行SQL查询语句,获取OA系统数据  \n" +
88
+                "工具参数:sqlString(需要执行的SQL语句,格式为[SQL语句])  \n" +
89
+                "要求:  \n" +
90
+                "1. 根据用户查询需求,决定是否需要查询数据  \n" +
91
+                "2. 如果需要查询数据,调用SQLQuery工具并传入正确的SQL语句  \n" +
92
+                "3. 生成标准MYSQL查询语句,根据语义和字段类型使用COUNT/SUM/AVG等聚合函数  \n" +
93
+                "4. 如果单表字段不满足查询需求,根据OA系统表结构信息进行多张表连接查询  \n" +
94
+                "5. 给生成的字段取一个简短的中文名称  \n" +
95
+                "6. 输出格式:使用[]包含sql文本,例如:[select 1 from dual]\n";
96
+        messages.add(ChatMessage.ofUser(prompt));
97
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
98
+        ChatResponse response = chatModel.prompt(chatSession).call();
99
+        String resultContent = response.lastChoice().getMessage().getResultContent();
100
+
101
+        if (resultContent.contains("<tool_call>")) {
102
+            resultContent = resultContent.split("<tool_call>\n")[1];
103
+            String content = resultContent.replace("\n</tool_call>", "");
104
+            JSONObject jsonObject = JSONObject.parseObject(content);
105
+            String name = jsonObject.getString("name");
106
+            JSONObject arguments = jsonObject.getJSONObject("arguments");
107
+            resultContent = clientProvider.callToolAsText(name, arguments).getContent();
108
+            resultContent = "根据查询结果:\n" +
109
+                    resultContent + "回答问题:\n" +
110
+                    question + " \n";
111
+            return langChainMilvusService.generateAnswer(topicId, resultContent);
112
+        } else {
113
+            // 不需要调用工具,直接返回原始响应,避免重复调用大模型
114
+            // 创建一个包含原始响应内容的 AssistantMessage 并返回 Flux
115
+            AssistantMessage assistantMessage = ChatMessage.ofAssistant(resultContent);
116
+            return Flux.just(assistantMessage);
117
+        }
37 118
     }
38 119
 
39 120
     /**
@@ -42,7 +123,7 @@ public class SessionController extends BaseController
42 123
     @GetMapping("/answerWithDocument")
43 124
     public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws Exception
44 125
     {
45
-        return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question, llmServiceUrl);
126
+        return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question);
46 127
     }
47 128
 
48 129
 }

+ 4
- 5
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java Просмотреть файл

@@ -5,7 +5,6 @@ import org.noear.solon.ai.chat.message.AssistantMessage;
5 5
 import org.springframework.web.multipart.MultipartFile;
6 6
 import reactor.core.publisher.Flux;
7 7
 
8
-import java.io.FileNotFoundException;
9 8
 import java.io.IOException;
10 9
 import java.util.List;
11 10
 
@@ -40,25 +39,25 @@ public interface ILangChainMilvusService {
40 39
      * 调用LLM生成回答
41 40
      * @return
42 41
      */
43
-    public Flux<AssistantMessage> generateAnswer(String topicId, String question, String llmServiceUrl);
42
+    public Flux<AssistantMessage> generateAnswer(String topicId, String question);
44 43
 
45 44
     /**
46 45
      * 调用LLM+RAG(知识库)生成回答
47 46
      * @return
48 47
      */
49
-    public Flux<AssistantMessage> generateAnswerWithCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl);
48
+    public Flux<AssistantMessage> generateAnswerWithCollection(String topicId, String question, List<JSONObject> contexts);
50 49
 
51 50
     /**
52 51
      * 调用LLM+RAG(外部文件)生成回答
53 52
      * @return
54 53
      */
55
-    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question, String llmServiceUrl) throws Exception;
54
+    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question) throws Exception;
56 55
 
57 56
     /**
58 57
      * 调用LLM+RAG(外部文件+知识库)生成回答
59 58
      * @return
60 59
      */
61
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question,  List<JSONObject> requests, String llmServiceUrl) throws Exception;
60
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question,  List<JSONObject> requests) throws Exception;
62 61
 
63 62
     /**
64 63
      * 获取二级标题下三级标题列表

+ 27
- 17
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java Просмотреть файл

@@ -49,6 +49,7 @@ import org.springframework.stereotype.Service;
49 49
 import org.springframework.web.multipart.MultipartFile;
50 50
 import reactor.core.publisher.Flux;
51 51
 
52
+import javax.annotation.PostConstruct;
52 53
 import java.io.*;
53 54
 import java.util.*;
54 55
 
@@ -71,6 +72,19 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
71 72
     @Value("${milvusService.url}")
72 73
     private String milvusServiceUrl;
73 74
 
75
+    private MilvusClientV2 milvusClient;
76
+
77
+    @PostConstruct
78
+    public void initMilvusClient() {
79
+        if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
80
+            throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
81
+        }
82
+//        milvusClient = new MilvusClientV2(
83
+//                ConnectConfig.builder()
84
+//                        .uri(milvusServiceUrl)
85
+//                        .build());
86
+    }
87
+
74 88
     /**
75 89
      * 上传多文件
76 90
      *
@@ -87,20 +101,20 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
87 101
     @Override
88 102
     public int insertLangchainEmbeddingDocument(MultipartFile[] fileList, String collectionName)
89 103
     {
90
-        MilvusClientV2 milvusClient = new MilvusClientV2(
91
-                ConnectConfig.builder()
92
-                        .uri(llmServiceUrl)
93
-                        .build());
94 104
         processValue = "";
95 105
         int successfullyInsertedFiles = 0;
96 106
 
97 107
         for (int i = 0; i < fileList.length; i++) {
98 108
             MultipartFile file = fileList[i];
99 109
             try {
100
-                File profilePath = new File( RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName);
110
+                // 构建上传目录路径
111
+                String uploadDir = RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName;
112
+                File profilePath = new File(uploadDir);
113
+                
114
+                // 确保目录存在,创建目录并设置权限
101 115
                 if (!profilePath.exists())
102 116
                     profilePath.mkdirs();
103
-                File transferFile = new File( profilePath + "/" + file.getOriginalFilename());
117
+                File transferFile = new File(profilePath, file.getOriginalFilename());
104 118
                 if (!transferFile.exists())
105 119
                     file.transferTo(transferFile);
106 120
                 List<TextSegment> segments = splitDocument(transferFile);
@@ -213,7 +227,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
213 227
      * @return
214 228
      */
215 229
     @Override
216
-    public Flux<AssistantMessage> generateAnswer(String topicId, String prompt, String llmServiceUrl) {
230
+    public Flux<AssistantMessage> generateAnswer(String topicId, String prompt) {
217 231
         ChatModel chatModel = ChatModel.of(llmServiceUrl)
218 232
 
219 233
                 .model("Qwen2.5-7B-Instruct")
@@ -241,7 +255,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
241 255
      * @return
242 256
      */
243 257
     @Override
244
-    public Flux<AssistantMessage> generateAnswerWithCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) {
258
+    public Flux<AssistantMessage> generateAnswerWithCollection(String topicId, String question, List<JSONObject> contexts) {
245 259
         StringBuilder sb = new StringBuilder();
246 260
         sb.append("问题: ").append(question).append("\n\n");
247 261
         sb.append("根据以下上下文回答问题:\n\n");
@@ -251,14 +265,14 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
251 265
                     .append("上下文").append(": ")
252 266
                     .append(context.getString("content")).append("\n\n");
253 267
         }
254
-        return generateAnswer(topicId, sb.toString(), llmServiceUrl);
268
+        return generateAnswer(topicId, sb.toString());
255 269
     }
256 270
 
257 271
     /**
258 272
      * 调用LLM生成回答
259 273
      */
260 274
     @Override
261
-    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question, String llmServiceUrl) throws Exception {
275
+    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question) throws Exception {
262 276
         CmcDocument cmcDocument = new CmcDocument();
263 277
         cmcDocument.setChatId(chatId);
264 278
         List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
@@ -284,14 +298,14 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
284 298
                         .append(contexts).append("\n\n");
285 299
             }
286 300
         }
287
-        return generateAnswer(topicId, sb.toString(), llmServiceUrl);
301
+        return generateAnswer(topicId, sb.toString());
288 302
     }
289 303
 
290 304
     /**
291 305
      * 调用LLM生成回答
292 306
      */
293 307
     @Override
294
-    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) throws Exception {
308
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question, List<JSONObject> contexts) throws Exception {
295 309
         StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
296 310
         CmcChat cmcChat = new CmcChat();
297 311
         cmcChat.setTopicId(topicId);
@@ -328,7 +342,7 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
328 342
 //                    .append("段落格式").append(": ")
329 343
 //                    .append(context.getString("content")).append("\n\n");
330 344
 //        }
331
-        return generateAnswer(topicId, sb.toString(), llmServiceUrl);
345
+        return generateAnswer(topicId, sb.toString());
332 346
     }
333 347
 
334 348
     /**
@@ -374,10 +388,6 @@ public class LangChainMilvusServiceImpl implements ILangChainMilvusService
374 388
      * @return
375 389
      */
376 390
     private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
377
-        MilvusClientV2 milvusClient = new MilvusClientV2(
378
-                ConnectConfig.builder()
379
-                        .uri(milvusServiceUrl)
380
-                        .build());
381 391
         List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
382 392
 
383 393
         //  加载集合

+ 14
- 32
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java Просмотреть файл

@@ -16,6 +16,7 @@ import io.milvus.v2.service.vector.response.QueryResp;
16 16
 import org.springframework.beans.factory.annotation.Value;
17 17
 import org.springframework.stereotype.Service;
18 18
 
19
+import javax.annotation.PostConstruct;
19 20
 import java.text.SimpleDateFormat;
20 21
 import java.util.*;
21 22
 import java.util.stream.Collectors;
@@ -26,15 +27,24 @@ public class MilvusServiceImpl implements IMilvusService {
26 27
     @Value("${milvusService.url}")
27 28
     private String milvusServiceUrl;
28 29
 
30
+    private MilvusClientV2 milvusClient;
31
+
32
+    @PostConstruct
33
+    public void initMilvusClient() {
34
+        if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
35
+            throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
36
+        }
37
+//        milvusClient = new MilvusClientV2(
38
+//                ConnectConfig.builder()
39
+//                        .uri(milvusServiceUrl)
40
+//                        .build());
41
+    }
42
+
29 43
     /**
30 44
      * 新建知识库Collection(含Schema、Field、Index)
31 45
      */
32 46
     @Override
33 47
     public void createCollection(String collectionName, String description, int dimension) {
34
-        MilvusClientV2 milvusClient = new MilvusClientV2(
35
-                ConnectConfig.builder()
36
-                        .uri(milvusServiceUrl)
37
-                        .build());
38 48
         CreateCollectionReq.CollectionSchema schema = MilvusClientV2.CreateSchema();
39 49
 
40 50
         schema.addField(AddFieldReq.builder()
@@ -93,10 +103,6 @@ public class MilvusServiceImpl implements IMilvusService {
93 103
      */
94 104
     @Override
95 105
     public JSONArray getCollectionNames() {
96
-        MilvusClientV2 milvusClient = new MilvusClientV2(
97
-                ConnectConfig.builder()
98
-                        .uri(milvusServiceUrl)
99
-                        .build());
100 106
         JSONArray jsonArray = new JSONArray();
101 107
         ListCollectionsResp listResponse = milvusClient.listCollections();
102 108
         if (listResponse != null) {
@@ -126,10 +132,6 @@ public class MilvusServiceImpl implements IMilvusService {
126 132
      */
127 133
     @Override
128 134
     public JSONArray listKnowLedgeByCollectionName(String collectionName) {
129
-        MilvusClientV2 milvusClient = new MilvusClientV2(
130
-                ConnectConfig.builder()
131
-                        .uri(milvusServiceUrl)
132
-                        .build());
133 135
         JSONArray jsonArray = new JSONArray();
134 136
         ListCollectionsResp listResponse = milvusClient.listCollections();
135 137
 
@@ -161,10 +163,6 @@ public class MilvusServiceImpl implements IMilvusService {
161 163
      */
162 164
     @Override
163 165
     public void collectionRename(String collectionName, String newCollectionName) {
164
-        MilvusClientV2 milvusClient = new MilvusClientV2(
165
-                ConnectConfig.builder()
166
-                        .uri(milvusServiceUrl)
167
-                        .build());
168 166
         RenameCollectionReq renameCollectionReq = RenameCollectionReq.builder()
169 167
                 .collectionName(collectionName)
170 168
                 .newCollectionName(newCollectionName)
@@ -178,10 +176,6 @@ public class MilvusServiceImpl implements IMilvusService {
178 176
      */
179 177
     @Override
180 178
     public void deleteCollectionName(String collectionName) {
181
-        MilvusClientV2 milvusClient = new MilvusClientV2(
182
-                ConnectConfig.builder()
183
-                        .uri(milvusServiceUrl)
184
-                        .build());
185 179
         DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
186 180
                 .collectionName(collectionName)
187 181
                 .build();
@@ -193,10 +187,6 @@ public class MilvusServiceImpl implements IMilvusService {
193 187
      */
194 188
     @Override
195 189
     public List<String> listDocument(String collectionName, String fileType) {
196
-        MilvusClientV2 milvusClient = new MilvusClientV2(
197
-                ConnectConfig.builder()
198
-                        .uri(milvusServiceUrl)
199
-                        .build());
200 190
         List<String> documentList = new ArrayList<>();
201 191
         LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
202 192
                 .collectionName(collectionName)
@@ -233,10 +223,6 @@ public class MilvusServiceImpl implements IMilvusService {
233 223
      */
234 224
     @Override
235 225
     public void removeDocument(String collectionName, String fileName) {
236
-        MilvusClientV2 milvusClient = new MilvusClientV2(
237
-                ConnectConfig.builder()
238
-                        .uri(milvusServiceUrl)
239
-                        .build());
240 226
         LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
241 227
                 .collectionName(collectionName)
242 228
                 .build();
@@ -253,10 +239,6 @@ public class MilvusServiceImpl implements IMilvusService {
253 239
      */
254 240
     @Override
255 241
     public void removeAllDocument(String collectionName) {
256
-        MilvusClientV2 milvusClient = new MilvusClientV2(
257
-                ConnectConfig.builder()
258
-                        .uri(milvusServiceUrl)
259
-                        .build());
260 242
         LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
261 243
                 .collectionName(collectionName)
262 244
                 .build();

+ 0
- 1
oa-back/ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java Просмотреть файл

@@ -1,6 +1,5 @@
1 1
 package com.ruoyi.quartz.task;
2 2
 
3
-import com.ruoyi.common.utils.spring.SpringUtils;
4 3
 import com.ruoyi.system.service.ISysLogininforService;
5 4
 import com.ruoyi.system.service.ISysOperLogService;
6 5
 import org.springframework.beans.factory.annotation.Autowired;

+ 19
- 3
oa-ui/src/views/contractStatistic/components/projectStatistic.vue Просмотреть файл

@@ -1,8 +1,8 @@
1 1
 <!--
2 2
  * @Author: ysh
3 3
  * @Date: 2024-12-17 10:32:27
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2024-12-19 16:25:46
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2026-01-08 14:56:17
6 6
 -->
7 7
 <template>
8 8
   <div>
@@ -85,9 +85,25 @@ export default {
85 85
     rowClickFn(row) {
86 86
       this.$router.replace({ query: { ...this.$route.query, projectId: row.projectId } });
87 87
       this.drawerOpen = true;
88
+      // 找到当前tab并修改当前tab显示的标题
89
+      let currentView = this.$store.state.tagsView.visitedViews.find(item => item.path === this.$route.path);
90
+      if (currentView != null) {
91
+        currentView.title = row.projectName;
92
+      }
88 93
     },
89 94
     closeDrawerFn(){
90
-      this.$router.replace({ path: '/statistics/contractStatistic' }); 
95
+      this.drawerOpen = false;
96
+      // 只需要删除 query 中的 projectId参数,保留其他参数
97
+      const { projectId, ...otherQuery } = this.$route.query;
98
+      this.$router.replace({ 
99
+        path: this.$route.path, 
100
+        query: otherQuery 
101
+      });
102
+      // 找到当前tab并修改当前tab显示的标题为"合同统计"
103
+      let currentView = this.$store.state.tagsView.visitedViews.find(item => item.path === this.$route.path);
104
+      if (currentView != null) {
105
+        currentView.title = '合同统计';
106
+      }
91 107
     }
92 108
   },
93 109
 }

+ 22
- 3
oa-ui/src/views/flowable/form/business/contractForm.vue Просмотреть файл

@@ -1,8 +1,8 @@
1 1
 <!--
2 2
  * @Author: ysh
3 3
  * @Date: 2024-05-10 15:31:57
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-08-15 09:38:15
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2026-01-08 14:57:18
6 6
 -->
7 7
 <template>
8 8
   <div class="app-container">
@@ -392,7 +392,7 @@
392 392
     <el-dialog :visible.sync="subOpen" title="选择分包合同" :size="'70%'" append-to-body>
393 393
       <choose-subcontract @choose="confirmSubContract"></choose-subcontract>
394 394
     </el-dialog>
395
-    <el-drawer :visible.sync="drawerOpen" title="" :size="'70%'" append-to-body>
395
+    <el-drawer :visible.sync="drawerOpen" title="" :size="'70%'" append-to-body @close="closeDrawerFn">
396 396
       <projectInfo :needReturn="false"></projectInfo>
397 397
     </el-drawer>
398 398
     <el-drawer :visible.sync="subInfoOpen" title="" :size="'55%'" append-to-body>
@@ -1217,6 +1217,25 @@ export default {
1217 1217
     clickProjectFn(row) {
1218 1218
       this.$router.replace({ query: { ...this.$route.query, projectId: row.projectId } });
1219 1219
       this.drawerOpen = true;
1220
+      // 找到当前tab并修改当前tab显示的标题
1221
+      let currentView = this.$store.state.tagsView.visitedViews.find(item => item.path === this.$route.path);
1222
+      if (currentView != null) {
1223
+        currentView.title = row.projectName;
1224
+      }
1225
+    },
1226
+    closeDrawerFn() {
1227
+      this.drawerOpen = false;
1228
+      // 只需要删除 query 中的 projectId参数,保留其他参数
1229
+      const { projectId, ...otherQuery } = this.$route.query;
1230
+      this.$router.replace({ 
1231
+        path: this.$route.path, 
1232
+        query: otherQuery 
1233
+      });
1234
+      // 找到当前tab并修改当前tab显示的标题为"承接合同"
1235
+      let currentView = this.$store.state.tagsView.visitedViews.find(item => item.path === this.$route.path);
1236
+      if (currentView != null) {
1237
+        currentView.title = '承接合同';
1238
+      }
1220 1239
     },
1221 1240
     clickSubFn(row) {
1222 1241
       this.subContractId.formId = row.subContractId

+ 8
- 3
oa-ui/src/views/llm/agent/AgentDetail.vue Просмотреть файл

@@ -531,9 +531,14 @@ export default {
531 531
 
532 532
     // 滚动到底部
533 533
     scrollToBottom() {
534
-      if (this.messagesContainer) {
535
-        this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight
536
-      }
534
+      // 确保在 DOM 更新后执行滚动操作
535
+      this.$nextTick(() => {
536
+        // 直接从 $refs 中获取滚动容器,确保每次都能获取到最新的 DOM 元素
537
+        const container = this.$refs.messagesContainer;
538
+        if (container) {
539
+          container.scrollTop = container.scrollHeight;
540
+        }
541
+      });
537 542
     },
538 543
 
539 544
     // 聊天内文件上传相关方法

+ 4
- 1
oa-ui/src/views/llm/chat/index.vue Просмотреть файл

@@ -675,7 +675,10 @@ export default {
675 675
     scrollToBottom() {
676 676
       if (this.$refs.messageScrollbar) {
677 677
         const scrollbar = this.$refs.messageScrollbar;
678
-        scrollbar.scrollTop = scrollbar.scrollHeight;
678
+        // Element UI的el-scrollbar组件内部的滚动容器是wrap DOM元素
679
+        if (scrollbar && scrollbar.wrap) {
680
+          scrollbar.wrap.scrollTop = scrollbar.wrap.scrollHeight;
681
+        }
679 682
       }
680 683
     },
681 684
 

+ 7
- 4
oa-ui/src/views/llm/knowledge/index.vue Просмотреть файл

@@ -716,10 +716,13 @@ export default {
716 716
 
717 717
     /** 滚动到底部 */
718 718
     scrollToBottom() {
719
-      const container = this.$refs.chatMessagesRef;
720
-      if (container) {
721
-        container.scrollTop = container.scrollHeight;
722
-      }
719
+      // 确保在 DOM 更新后执行滚动操作
720
+      this.$nextTick(() => {
721
+        const container = this.$refs.chatMessagesRef;
722
+        if (container) {
723
+          container.scrollTop = container.scrollHeight;
724
+        }
725
+      });
723 726
     },
724 727
 
725 728
     /** 处理Enter键 */

+ 43
- 8
oa-ui/src/views/oa/project/info.vue Просмотреть файл

@@ -288,8 +288,8 @@
288 288
                 <el-input disabled type="textarea" v-model="projectComment.jyComment"></el-input>
289 289
                 <div class="sign mt10">
290 290
                   <div class="mr20">签名:<span class="auditor">{{ projectComment.jyUser ? projectComment.jyUser.nickName :
291
-        ''
292
-                      }}</span>
291
+                    ''
292
+                  }}</span>
293 293
                   </div>
294 294
                   <div class="ml20"><span>审核时间:{{ projectComment.jyApprovalTime }}</span></div>
295 295
                 </div>
@@ -304,7 +304,7 @@
304 304
                 <el-input disabled type="textarea" v-model="projectComment.manageComment"></el-input>
305 305
                 <div class="sign mt10">
306 306
                   <div class="mr20">签名:<span class="auditor">{{ projectComment.managerUser ?
307
-        projectComment.managerUser.nickName : '' }}</span>
307
+                    projectComment.managerUser.nickName : '' }}</span>
308 308
                   </div>
309 309
                   <div class="ml20"><span>审核时间:{{ projectComment.manageApprovalTime }}</span></div>
310 310
                 </div>
@@ -345,7 +345,7 @@
345 345
               </template>
346 346
               <el-select v-model="devices" multiple disabled style="width:100%">
347 347
                 <el-option v-for="item in deviceList" :key="item.deviceId" :label="item.name + '【' + (item.brand != null ? item.brand : '') + (item.series != null ? '-' + item.series + '】' : '')
348
-        + (item.code != null ? '(设备编号:' + item.code + ')' : '')" :value="item.deviceId">
348
+                  + (item.code != null ? '(设备编号:' + item.code + ')' : '')" :value="item.deviceId">
349 349
                 </el-option>
350 350
               </el-select>
351 351
             </el-descriptions-item>
@@ -355,8 +355,9 @@
355 355
                 使用车辆
356 356
               </template>
357 357
               <el-select v-model="cars" multiple disabled style="width:100%">
358
-                <el-option v-for="item in carList" :label="item.licensePlate + (item.brand ? item.brand : '') + (item.series ? item.series : '')" :value="item.carId"
359
-                  :key="item.carId">
358
+                <el-option v-for="item in carList"
359
+                  :label="item.licensePlate + (item.brand ? item.brand : '') + (item.series ? item.series : '')"
360
+                  :value="item.carId" :key="item.carId">
360 361
                 </el-option>
361 362
               </el-select>
362 363
             </el-descriptions-item>
@@ -572,8 +573,6 @@ export default {
572 573
     }
573 574
   },
574 575
   beforeCreate() {
575
-    // 可有可无,在面包屑中展示
576
-    this.$route.meta.title = this.$route.query.projectName
577 576
     // 找到当前tab并修改当前tab显示的标题
578 577
     let currentView = this.$store.getters.visitedViews[this.$store.getters.visitedViews.findIndex(
579 578
       (item) => item.path === this.$route.path
@@ -599,6 +598,31 @@ export default {
599 598
       this.goBack();
600 599
     }
601 600
   },
601
+  watch: {
602
+    '$route.query.projectId'(newProjectId, oldProjectId) {
603
+      if (newProjectId && newProjectId !== oldProjectId) {
604
+        // 重置数据
605
+        this.project = {};
606
+        this.deptLeaderList = [];
607
+        this.projectComment = {};
608
+        this.projectChangeList = [];
609
+        this.workList = [];
610
+        this.progressList = [];
611
+        this.actualList = [];
612
+        
613
+        // 更新任务表单ID
614
+        this.taskForm.formId = newProjectId;
615
+        
616
+        // 重新加载数据
617
+        this.getProjectInfo(newProjectId);
618
+        this.getProjectWorkList(newProjectId);
619
+        this.getProjectCommentList(newProjectId);
620
+        this.getProjectProgressList(newProjectId);
621
+        this.getActualList(newProjectId);
622
+        this.getProjectChangeList(newProjectId);
623
+      }
624
+    }
625
+  },
602 626
   data() {
603 627
     return {
604 628
       baseUrl: process.env.VUE_APP_BASE_API,
@@ -657,6 +681,17 @@ export default {
657 681
           if (this.project.projectLeader == this.$store.state.user.id) {
658 682
             this.isProjectLeader = true;
659 683
           }
684
+          // 更新标签页标题
685
+          if (this.project.projectNumber && this.project.projectName) {
686
+            const newTitle = `【${this.project.projectNumber}】${this.project.projectName}`;
687
+            this.$store.dispatch('tagsView/updateVisitedView', {
688
+              ...this.$route,
689
+              meta: {
690
+                ...this.$route.meta,
691
+                title: newTitle
692
+              }
693
+            });
694
+          }
660 695
           let deptArr = [];
661 696
           if (this.project.undertakingDept && this.project.undertakingDept.trim() !== '') {
662 697
             deptArr = (this.project.undertakingDept.split(',')).map(Number)

Загрузка…
Отмена
Сохранить