Browse Source

迁移大模型

lamphua 3 weeks ago
parent
commit
eec8dc8894
65 changed files with 10663 additions and 31 deletions
  1. 24
    2
      oa-back/pom.xml
  2. 9
    2
      oa-back/ruoyi-admin/pom.xml
  3. 0
    1
      oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/oa/CmcDepositController.java
  4. 164
    0
      oa-back/ruoyi-agent/pom.xml
  5. 19
    0
      oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/RuoYiAgentApplication.java
  6. 9
    0
      oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/IMcpService.java
  7. 73
    0
      oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/McpServerConfig.java
  8. 369
    0
      oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java
  9. 5
    0
      oa-back/ruoyi-agent/src/main/resources/application.yml
  10. 81
    0
      oa-back/ruoyi-llm/pom.xml
  11. 151
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcAgentController.java
  12. 101
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcChatController.java
  13. 110
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcDocumentController.java
  14. 131
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcTopicController.java
  15. 125
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/KnowLedgeController.java
  16. 113
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java
  17. 47
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/RagController.java
  18. 44
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/SessionController.java
  19. 67
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java
  20. 48
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/IMilvusService.java
  21. 435
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/LangChainMilvusServiceImpl.java
  22. 248
    0
      oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java
  23. 36
    0
      oa-back/ruoyi-system/pom.xml
  24. 67
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcAgent.java
  25. 126
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcChat.java
  26. 65
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcDocument.java
  27. 66
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcTopic.java
  28. 62
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcAgentMapper.java
  29. 62
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcChatMapper.java
  30. 62
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcDocumentMapper.java
  31. 62
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcTopicMapper.java
  32. 104
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcAgentService.java
  33. 62
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcChatService.java
  34. 73
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcDocumentService.java
  35. 62
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcTopicService.java
  36. 1222
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java
  37. 94
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcChatServiceImpl.java
  38. 129
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcDocumentServiceImpl.java
  39. 96
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcTopicServiceImpl.java
  40. 2
    0
      oa-back/ruoyi-system/src/main/java/com/ruoyi/system/domain/TreeSelectNew.java
  41. 69
    0
      oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcAgentMapper.xml
  42. 83
    0
      oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcChatMapper.xml
  43. 63
    0
      oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcDocumentMapper.xml
  44. 68
    0
      oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcTopicMapper.xml
  45. 114
    0
      oa-ui/src/api/llm/agent.js
  46. 50
    0
      oa-ui/src/api/llm/chat.js
  47. 67
    0
      oa-ui/src/api/llm/document.js
  48. 94
    0
      oa-ui/src/api/llm/knowLedge.js
  49. 16
    0
      oa-ui/src/api/llm/mcp.js
  50. 189
    0
      oa-ui/src/api/llm/rag.js
  51. 25
    0
      oa-ui/src/api/llm/session.js
  52. 44
    0
      oa-ui/src/api/llm/topic.js
  53. 1
    0
      oa-ui/src/assets/icons/svg/robot.svg
  54. 2
    2
      oa-ui/src/views/flowable/form/budget/adjust/components/InnerStaffCost.vue
  55. 2
    2
      oa-ui/src/views/flowable/form/budget/adjust/components/OuterStaffCost.vue
  56. 4
    4
      oa-ui/src/views/flowable/form/budget/adjust/newBudgetInfo.vue
  57. 7
    7
      oa-ui/src/views/flowable/form/oa/recruitForm.vue
  58. 1301
    0
      oa-ui/src/views/llm/agent/AgentDetail.vue
  59. 479
    0
      oa-ui/src/views/llm/agent/index.vue
  60. 1327
    0
      oa-ui/src/views/llm/chat/index.vue
  61. 1815
    0
      oa-ui/src/views/llm/knowledge/index.vue
  62. 1
    1
      oa-ui/src/views/oa/brand/brandProject.vue
  63. 3
    4
      oa-ui/src/views/oa/project/invest.vue
  64. 10
    2
      oa-ui/src/views/oa/recruit/index.vue
  65. 4
    4
      oa-ui/src/views/oa/recruit/recruitTab.vue

+ 24
- 2
oa-back/pom.xml View File

@@ -23,11 +23,11 @@
23 23
         <swagger.version>3.0.0</swagger.version>
24 24
         <kaptcha.version>2.3.3</kaptcha.version>
25 25
         <pagehelper.boot.version>1.4.7</pagehelper.boot.version>
26
-        <fastjson.version>2.0.43</fastjson.version>
26
+        <fastjson.version>2.0.53</fastjson.version>
27 27
         <oshi.version>6.4.8</oshi.version>
28 28
         <commons.io.version>2.13.0</commons.io.version>
29 29
         <commons.collections.version>3.2.2</commons.collections.version>
30
-        <poi.version>4.1.2</poi.version>
30
+        <poi.version>5.2.5</poi.version>
31 31
         <velocity.version>2.3</velocity.version>
32 32
         <jwt.version>0.9.1</jwt.version>
33 33
         <flowable.version>6.8.0</flowable.version>
@@ -171,6 +171,26 @@
171 171
                 <version>${cmc.version}</version>
172 172
             </dependency>
173 173
 
174
+            <!-- 大模型-->
175
+            <dependency>
176
+                <groupId>com.ruoyi</groupId>
177
+                <artifactId>ruoyi-llm</artifactId>
178
+                <version>${cmc.version}</version>
179
+            </dependency>
180
+
181
+            <!-- 智能体-->
182
+            <dependency>
183
+                <groupId>com.ruoyi</groupId>
184
+                <artifactId>ruoyi-agent</artifactId>
185
+                <version>${cmc.version}</version>
186
+            </dependency>
187
+
188
+            <dependency>
189
+                <groupId>com.google.protobuf</groupId>
190
+                <artifactId>protobuf-java</artifactId>
191
+                <version>3.25.5</version>
192
+            </dependency>
193
+
174 194
             <dependency>
175 195
                 <groupId>com.ruoyi</groupId>
176 196
                 <artifactId>ruoyi-flowable</artifactId>
@@ -198,6 +218,8 @@
198 218
 
199 219
     <modules>
200 220
         <module>ruoyi-admin</module>
221
+        <module>ruoyi-llm</module>
222
+        <module>ruoyi-agent</module>
201 223
         <module>ruoyi-framework</module>
202 224
         <module>ruoyi-system</module>
203 225
         <module>ruoyi-quartz</module>

+ 9
- 2
oa-back/ruoyi-admin/pom.xml View File

@@ -39,8 +39,9 @@
39 39
 
40 40
          <!-- Mysql驱动包 -->
41 41
         <dependency>
42
-            <groupId>mysql</groupId>
43
-            <artifactId>mysql-connector-java</artifactId>
42
+            <groupId>com.mysql</groupId>
43
+            <artifactId>mysql-connector-j</artifactId>
44
+            <version>8.0.33</version>
44 45
         </dependency>
45 46
 
46 47
         <!-- 核心模块-->
@@ -67,6 +68,12 @@
67 68
             <artifactId>ruoyi-flowable</artifactId>
68 69
         </dependency>
69 70
 
71
+        <!-- 大模型-->
72
+        <dependency>
73
+            <groupId>com.ruoyi</groupId>
74
+            <artifactId>ruoyi-llm</artifactId>
75
+        </dependency>
76
+
70 77
     </dependencies>
71 78
 
72 79
     <build>

+ 0
- 1
oa-back/ruoyi-admin/src/main/java/com/ruoyi/web/controller/oa/CmcDepositController.java View File

@@ -4,7 +4,6 @@ import java.util.Date;
4 4
 import java.util.List;
5 5
 import javax.servlet.http.HttpServletResponse;
6 6
 
7
-import liquibase.pro.packaged.D;
8 7
 import org.springframework.beans.factory.annotation.Autowired;
9 8
 import org.springframework.web.bind.annotation.GetMapping;
10 9
 import org.springframework.web.bind.annotation.PostMapping;

+ 164
- 0
oa-back/ruoyi-agent/pom.xml View File

@@ -0,0 +1,164 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <parent>
6
+        <artifactId>ruoyi</artifactId>
7
+        <groupId>com.ruoyi</groupId>
8
+        <version>3.8.7</version>
9
+    </parent>
10
+    <modelVersion>4.0.0</modelVersion>
11
+    <packaging>jar</packaging>
12
+
13
+    <artifactId>ruoyi-agent</artifactId>
14
+
15
+    <description>
16
+        cmc智能体
17
+    </description>
18
+
19
+    <properties>
20
+        <solon.version>3.5.1</solon.version>
21
+    </properties>
22
+
23
+    <dependencies>
24
+
25
+        <dependency>
26
+            <groupId>org.noear</groupId>
27
+            <artifactId>solon-lib</artifactId>
28
+        </dependency>
29
+
30
+        <dependency>
31
+            <groupId>org.noear</groupId>
32
+            <artifactId>solon-web-servlet</artifactId>
33
+        </dependency>
34
+
35
+        <!-- Spring Boot Web -->
36
+        <dependency>
37
+            <groupId>org.springframework.boot</groupId>
38
+            <artifactId>spring-boot-starter-web</artifactId>
39
+        </dependency>
40
+
41
+        <!-- Solon模型上下文协议 -->
42
+        <dependency>
43
+            <groupId>org.noear</groupId>
44
+            <artifactId>solon-ai-mcp</artifactId>
45
+        </dependency>
46
+
47
+        <dependency>
48
+            <groupId>org.apache.poi</groupId>
49
+            <artifactId>poi</artifactId>
50
+            <version>5.2.5</version>
51
+        </dependency>
52
+        <dependency>
53
+            <groupId>org.apache.poi</groupId>
54
+            <artifactId>poi-ooxml</artifactId>
55
+            <version>5.2.5</version>
56
+        </dependency>
57
+        <dependency>
58
+            <groupId>org.apache.poi</groupId>
59
+            <artifactId>poi-scratchpad</artifactId>
60
+            <version>5.2.5</version>
61
+        </dependency>
62
+        <dependency>
63
+            <groupId>com.alibaba.fastjson2</groupId>
64
+            <artifactId>fastjson2</artifactId>
65
+        </dependency>
66
+        <dependency>
67
+            <groupId>dev.langchain4j</groupId>
68
+            <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
69
+            <version>0.35.0</version>
70
+        </dependency>
71
+        <dependency>
72
+            <groupId>io.milvus</groupId>
73
+            <artifactId>milvus-sdk-java</artifactId>
74
+            <version>2.6.2</version>
75
+        </dependency>
76
+        <dependency>
77
+            <groupId>dev.langchain4j</groupId>
78
+            <artifactId>langchain4j</artifactId>
79
+            <version>0.35.0</version>
80
+        </dependency>
81
+        <dependency>
82
+            <groupId>dev.langchain4j</groupId>
83
+            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
84
+            <version>0.35.0</version>
85
+        </dependency>
86
+    </dependencies>
87
+
88
+    <dependencyManagement>
89
+        <dependencies>
90
+            <dependency>
91
+                <groupId>org.noear</groupId>
92
+                <artifactId>solon-parent</artifactId>
93
+                <version>${solon.version}</version>
94
+                <type>pom</type>
95
+                <scope>import</scope>
96
+            </dependency>
97
+        </dependencies>
98
+    </dependencyManagement>
99
+
100
+    <build>
101
+        <finalName>${project.name}</finalName>
102
+        <plugins>
103
+            <plugin>
104
+                <groupId>org.apache.maven.plugins</groupId>
105
+                <artifactId>maven-compiler-plugin</artifactId>
106
+                <version>3.11.0</version>
107
+                <configuration>
108
+                    <compilerArgument>-parameters</compilerArgument>
109
+                    <source>8</source>
110
+                    <target>8</target>
111
+                    <encoding>UTF-8</encoding>
112
+                </configuration>
113
+            </plugin>
114
+
115
+            <plugin>
116
+                <groupId>org.apache.maven.plugins</groupId>
117
+                <artifactId>maven-assembly-plugin</artifactId>
118
+                <configuration>
119
+                    <finalName>${project.name}</finalName>
120
+                    <appendAssemblyId>false</appendAssemblyId>
121
+                    <descriptorRefs>
122
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
123
+                    </descriptorRefs>
124
+                    <archive>
125
+                        <manifest>
126
+                            <mainClass>webapp.HelloApp</mainClass>
127
+                        </manifest>
128
+                    </archive>
129
+                </configuration>
130
+                <executions>
131
+                    <execution>
132
+                        <id>make-assembly</id>
133
+                        <phase>package</phase>
134
+                        <goals>
135
+                            <goal>single</goal>
136
+                        </goals>
137
+                    </execution>
138
+                </executions>
139
+            </plugin>
140
+        </plugins>
141
+    </build>
142
+
143
+
144
+    <repositories>
145
+        <repository>
146
+            <id>sonatype-snapshots</id>
147
+            <name>Sonatype Snapshots</name>
148
+            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
149
+            <releases>
150
+                <enabled>false</enabled>
151
+            </releases>
152
+        </repository>
153
+    </repositories>
154
+    <pluginRepositories>
155
+        <pluginRepository>
156
+            <id>sonatype-snapshots</id>
157
+            <name>Sonatype Snapshots</name>
158
+            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
159
+            <releases>
160
+                <enabled>false</enabled>
161
+            </releases>
162
+        </pluginRepository>
163
+    </pluginRepositories>
164
+</project>

+ 19
- 0
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/RuoYiAgentApplication.java View File

@@ -0,0 +1,19 @@
1
+package com.ruoyi.agent;
2
+
3
+import org.springframework.boot.SpringApplication;
4
+import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+/**
7
+ * 启动程序
8
+ * 
9
+ * @author ruoyi
10
+ */
11
+@SpringBootApplication
12
+public class RuoYiAgentApplication
13
+{
14
+    public static void main(String[] args)
15
+    {
16
+        SpringApplication.run(RuoYiAgentApplication.class, args);
17
+        System.out.println("智能体启动成功 \n");
18
+    }
19
+}

+ 9
- 0
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/IMcpService.java View File

@@ -0,0 +1,9 @@
1
+package com.ruoyi.agent.service;
2
+
3
+/**
4
+ * (通过此接口,自动收集 McpServerEndpoint 组件类)
5
+ *
6
+ * @author noear 2025/5/6 created
7
+ */
8
+public interface IMcpService {
9
+}

+ 73
- 0
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/McpServerConfig.java View File

@@ -0,0 +1,73 @@
1
+package com.ruoyi.agent.service;
2
+
3
+import org.noear.solon.Solon;
4
+import org.noear.solon.ai.chat.tool.MethodToolProvider;
5
+import org.noear.solon.ai.mcp.server.McpServerEndpointProvider;
6
+import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
7
+import org.noear.solon.ai.mcp.server.prompt.MethodPromptProvider;
8
+import org.noear.solon.ai.mcp.server.resource.MethodResourceProvider;
9
+import org.noear.solon.web.servlet.SolonServletFilter;
10
+import org.springframework.aop.support.AopUtils;
11
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
12
+import org.springframework.context.annotation.Bean;
13
+import org.springframework.context.annotation.Configuration;
14
+import org.springframework.core.annotation.AnnotationUtils;
15
+
16
+import javax.annotation.PostConstruct;
17
+import javax.annotation.PreDestroy;
18
+import java.util.List;
19
+
20
+/**
21
+ * 这个类独立一个目录,可以让 Solon 扫描范围最小化
22
+ * */
23
+@Configuration
24
+public class McpServerConfig {
25
+    @PostConstruct
26
+    public void start() {
27
+        Solon.start(McpServerConfig.class, new String[]{"--cfg=application.yml"});
28
+    }
29
+
30
+    @PreDestroy
31
+    public void stop() {
32
+        if (Solon.app() != null) {
33
+            Solon.stopBlock(false, Solon.cfg().stopDelay());
34
+        }
35
+    }
36
+
37
+    @Bean
38
+    public McpServerConfig init(List<IMcpService> serverEndpoints) {
39
+        //提取实现容器里 IMcpService 接口的 bean ,并注册为服务端点
40
+        for (IMcpService serverEndpoint : serverEndpoints) {
41
+            Class<?> serverEndpointClz = AopUtils.getTargetClass(serverEndpoint);
42
+            McpServerEndpoint anno = AnnotationUtils.findAnnotation(serverEndpointClz, McpServerEndpoint.class);
43
+
44
+            if (anno == null) {
45
+                continue;
46
+            }
47
+
48
+            McpServerEndpointProvider serverEndpointProvider = McpServerEndpointProvider.builder()
49
+                    .from(serverEndpointClz, anno)
50
+                    .build();
51
+
52
+            serverEndpointProvider.addTool(new MethodToolProvider(serverEndpointClz, serverEndpoint));
53
+            serverEndpointProvider.addResource(new MethodResourceProvider(serverEndpointClz, serverEndpoint));
54
+            serverEndpointProvider.addPrompt(new MethodPromptProvider(serverEndpointClz, serverEndpoint));
55
+
56
+            serverEndpointProvider.postStart();
57
+
58
+            //可以再把 serverEndpointProvider 手动转入 SpringBoot 容器
59
+        }
60
+
61
+        //为了能让这个 init 能正常运行
62
+        return this;
63
+    }
64
+
65
+    @Bean
66
+    public FilterRegistrationBean mcpServerFilter() {
67
+        FilterRegistrationBean<SolonServletFilter> filter = new FilterRegistrationBean<>();
68
+        filter.setName("SolonFilter");
69
+        filter.addUrlPatterns("/mcp/*");
70
+        filter.setFilter(new SolonServletFilter());
71
+        return filter;
72
+    }
73
+}

+ 369
- 0
oa-back/ruoyi-agent/src/main/java/com/ruoyi/agent/service/impl/McpServiceImpl.java View File

@@ -0,0 +1,369 @@
1
+package com.ruoyi.agent.service.impl;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.agent.service.IMcpService;
5
+import dev.langchain4j.data.document.Document;
6
+import dev.langchain4j.data.document.parser.TextDocumentParser;
7
+import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
8
+import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
9
+import dev.langchain4j.data.embedding.Embedding;
10
+import dev.langchain4j.data.segment.TextSegment;
11
+import dev.langchain4j.model.embedding.EmbeddingModel;
12
+import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
13
+import dev.langchain4j.store.embedding.EmbeddingMatch;
14
+import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
15
+import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
16
+import io.milvus.v2.client.ConnectConfig;
17
+import io.milvus.v2.client.MilvusClientV2;
18
+import io.milvus.v2.common.IndexParam;
19
+import io.milvus.v2.service.collection.request.LoadCollectionReq;
20
+import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
21
+import io.milvus.v2.service.vector.request.SearchReq;
22
+import io.milvus.v2.service.vector.request.data.BaseVector;
23
+import io.milvus.v2.service.vector.request.data.FloatVec;
24
+import io.milvus.v2.service.vector.response.SearchResp;
25
+import org.apache.poi.extractor.POITextExtractor;
26
+import org.apache.poi.extractor.ExtractorFactory;
27
+import org.apache.poi.xwpf.usermodel.*;
28
+import org.apache.xmlbeans.XmlCursor;
29
+import org.noear.solon.Solon;
30
+import org.noear.solon.ai.annotation.ToolMapping;
31
+import org.noear.solon.ai.chat.ChatModel;
32
+import org.noear.solon.ai.chat.ChatResponse;
33
+import org.noear.solon.ai.chat.ChatSession;
34
+import org.noear.solon.ai.chat.message.AssistantMessage;
35
+import org.noear.solon.ai.chat.message.ChatMessage;
36
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
37
+import org.noear.solon.ai.mcp.McpChannel;
38
+import org.noear.solon.ai.mcp.server.annotation.McpServerEndpoint;
39
+import org.noear.solon.annotation.Param;
40
+import org.springframework.stereotype.Service;
41
+
42
+import java.io.*;
43
+import java.util.*;
44
+
45
+@Service
46
+@McpServerEndpoint(channel = McpChannel.SSE, mcpEndpoint = "/mcp/sse")
47
+public class McpServiceImpl implements IMcpService {
48
+
49
+    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
50
+
51
+    private static final String llmServiceUrl = "http://192.168.28.196:8000/v1/chat/completions";
52
+
53
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
54
+            ConnectConfig.builder()
55
+                    .uri("http://192.168.28.196:19530")
56
+                    .build());
57
+
58
+    /**
59
+     * 调用LLM+RAG(外部文件+知识库)生成回答
60
+     */
61
+    @ToolMapping(description = "章节撰写")
62
+    public AssistantMessage writeParagraph(@Param(description = "知识库名称") String collectionName,
63
+                                           @Param(description = "智能体名称") String agentName,
64
+                                           @Param(description = "章节名称") String title,
65
+                                           @Param(description = "技术文件地址") String templatePath) throws IOException
66
+    {
67
+            try {
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;
71
+                List<String> subTitles = extractSubTitles(templatePath, title);
72
+                List<JSONObject> contexts = retrieveFromMilvus(collectionName, title, 10);
73
+                return generateAnswerWithDocumentAndCollection(agentName, templatePath, subTitles, contexts);
74
+            } catch (IOException e) {
75
+                throw new RuntimeException(e);
76
+            }
77
+    }
78
+
79
+    /**
80
+     * 从Milvus检索相关文档
81
+     * @return
82
+     */
83
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
84
+        List<JSONObject> resultList = new ArrayList<>();
85
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
86
+        searchResultList.forEach(searchResult -> {
87
+            JSONObject result = new JSONObject();
88
+            result.put("file_name", searchResult.get(0).getEntity().get("file_name"));
89
+            result.put("content", searchResult.get(0).getEntity().get("content"));
90
+            resultList.add(result);
91
+        });
92
+        return resultList;
93
+    }
94
+
95
+    /**
96
+     * 调用LLM生成回答
97
+     */
98
+    public AssistantMessage generateAnswerWithDocumentAndCollection(String agentName, String templatePath, List<String> titles, List<JSONObject> contexts) throws IOException {
99
+        StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
100
+        String filename = templatePath.replace("_" + agentName, "");
101
+        File profilePath = new File(filename);
102
+        if (!profilePath.exists()) {
103
+            filename = filename.replace(".docx", ".doc");
104
+            profilePath = new File(filename);
105
+        }
106
+        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
107
+        List<TextSegment> segments = splitDocument(profilePath);
108
+        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
109
+        embeddingStore.addAll(embeddings, segments);
110
+        StringBuilder content = new StringBuilder();
111
+        for (String title : titles) {
112
+            Embedding queryEmbedding = embeddingModel.embed(title).content();
113
+            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
114
+                    .queryEmbedding(queryEmbedding)
115
+                    .minScore(0.7)
116
+                    .build();
117
+            List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
118
+            results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
119
+            for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
120
+                String requests = embeddingMatch.embedded().toString();
121
+                sb.append(requests).append("\n\n");
122
+
123
+            }
124
+            sb.append("针对本项目招标文件内容,补全以下章节部分:\n\n").append(title);
125
+            content.append(generateAnswer(sb.toString()));
126
+        }
127
+        String absolutePath = templatePath.replace("/dev-api/profile", Solon.cfg().getProperty("cmc.profile"));
128
+        writeContent(content.toString(), titles, absolutePath);
129
+        for (JSONObject context : contexts) {
130
+            sb.append("文件").append(": ")
131
+                    .append(context.getString("file_name")).append("\n\n")
132
+                    .append("段落格式").append(": ")
133
+                    .append(context.getString("content")).append("\n\n");
134
+        }
135
+        content.append( "招标文件分析完成,章节内容已写入【<a href='")
136
+                .append(templatePath.replace(Solon.cfg().getProperty("cmc.profile"), "/dev-api/profile"))
137
+                .append("'> 技术文件" + "</a>】,请查阅\n\n")
138
+                .append("如需修改,请输入技术文件已有内容的章节标题\n\n")
139
+                .append(extractTitles(templatePath));
140
+        return ChatMessage.ofAssistant(content.toString());
141
+    }
142
+
143
+    /**
144
+     * 调用LLM生成回答
145
+     * @return
146
+     */
147
+    public String generateAnswer(String prompt) throws IOException {
148
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
149
+                .model("Qwen2.5-7B-Instruct")
150
+                .build();
151
+
152
+        List<ChatMessage> messages = new ArrayList<>();
153
+        messages.add(ChatMessage.ofUser(prompt));
154
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
155
+        chatSession.addMessage(messages);
156
+        ChatResponse response = chatModel.prompt(chatSession).call();
157
+
158
+        return response.lastChoice().getMessage().getContent();
159
+    }
160
+
161
+    /**
162
+     * 写入章节内容
163
+     * @return
164
+     */
165
+    public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
166
+        String[] contentLines = content.split("\n");
167
+        Map<String, String> map = new HashMap<>();
168
+        File file = new File(absolutePath);
169
+        FileInputStream fileInputStream = new FileInputStream(file);
170
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
171
+            for (int i = 0; i < titles.size(); i++) {
172
+                int startIndex = Arrays.asList(contentLines).indexOf(titles.get(i));
173
+                StringBuilder text = new StringBuilder();
174
+                if (startIndex >= 0) {
175
+                    if (i < titles.size() - 1) {
176
+                        int endIndex = Arrays.asList(contentLines).indexOf(titles.get(i + 1));
177
+                        for (int c = startIndex + 1; c < endIndex; c++) {
178
+                            text.append(contentLines[c]).append("\n\n");
179
+                        }
180
+                    } else {
181
+                        if (startIndex + 1 < contentLines.length) {
182
+                            for (int c = startIndex + 1; c < contentLines.length; c++) {
183
+                                text.append(contentLines[c]).append("\n\n");
184
+                            }
185
+                        }
186
+                    }
187
+                }
188
+                else
189
+                    text.append(content);
190
+                map.put(titles.get(i), text.toString());
191
+            }
192
+
193
+            List<Integer> positions = new ArrayList<>();
194
+            List<String> contents = new ArrayList<>();
195
+            List<XWPFParagraph> paragraphs = document.getParagraphs();
196
+
197
+            for (int i = 0; i < paragraphs.size(); i++) {
198
+                XWPFParagraph paragraph = paragraphs.get(i);
199
+                for (String title : titles) {
200
+                    if (paragraph.getText().contains(title)) {
201
+                        positions.add(i);
202
+                        contents.add(map.get(title));
203
+                    }
204
+                }
205
+            }
206
+
207
+            for (int i = positions.size() - 1; i >= 0; i--) {
208
+                int insertPos = positions.get(i) + 1;
209
+                XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
210
+                XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
211
+                contentParagraph.setStyle("1");
212
+                XWPFRun run = contentParagraph.createRun();
213
+                run.setText(contents.get(i));
214
+            }
215
+
216
+            try (FileOutputStream out = new FileOutputStream(absolutePath)) {
217
+                document.write(out);
218
+            }
219
+        }
220
+    }
221
+
222
+    /**
223
+     * 获取最低级别子标题列表
224
+     */
225
+    public List<String> extractSubTitles(String filename, String question) throws IOException {
226
+        List<String> subTitles = new ArrayList<>();
227
+        InputStream fileInputStream = new FileInputStream(filename);
228
+
229
+        boolean foundParent = false;
230
+        int parentLevel = -1;
231
+        // 用于跟踪当前路径
232
+        List<XWPFParagraph> currentPath = new ArrayList<>();
233
+
234
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
235
+
236
+            for (XWPFParagraph  paragraph : document.getParagraphs()) {
237
+                String text = paragraph.getText();
238
+                int level = Integer.parseInt(paragraph.getStyle());
239
+
240
+                // 维护当前路径
241
+                while (!currentPath.isEmpty() && Integer.parseInt(currentPath.get(currentPath.size() - 1).getStyle()) >= level) {
242
+                    currentPath.remove(currentPath.size() - 1);
243
+                }
244
+                currentPath.add(paragraph);
245
+                if (foundParent) {
246
+                    if (level > parentLevel) { // 是子标题
247
+                        if (isLeafNode(document, paragraph, level)) {
248
+                            subTitles.add(text);
249
+                        }
250
+                    } else {
251
+                        // 遇到同级或更高级别的标题,停止搜索
252
+                        break;
253
+                    }
254
+                } else if (text.equals(question)) {
255
+                    foundParent = true;
256
+                    parentLevel = level;
257
+                }
258
+            }
259
+
260
+
261
+            return subTitles;
262
+
263
+        }
264
+    }
265
+
266
+    // 检查一个标题是否是叶子节点(没有更低级别的子标题)
267
+    private boolean isLeafNode(XWPFDocument doc, XWPFParagraph p, int level) {
268
+        int index = doc.getPosOfParagraph(p);
269
+        if (index == -1 || index >= doc.getParagraphs().size()-1) {
270
+            return true;
271
+        }
272
+
273
+        // 检查后续段落
274
+        XWPFParagraph nextP = doc.getParagraphs().get(index + 1);
275
+        int nextLevel = Integer.parseInt(nextP.getStyle());
276
+        // 遇到同级或更高级别标题,是叶子节点
277
+        return nextLevel <= level; // 存在更低级别的标题,不是叶子节点
278
+
279
+    }
280
+
281
+    /**
282
+     * 获取二、三级标题列表
283
+     */
284
+    public String extractTitles(String filename) throws IOException {
285
+        StringBuilder subTitles = new StringBuilder();
286
+        InputStream fileInputStream = new FileInputStream(filename);
287
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
288
+            for (XWPFParagraph paragraph : document.getParagraphs()) {
289
+                String text = paragraph.getText().trim();
290
+                if (paragraph.getStyle() != null) {
291
+                    // 判断主标题
292
+                    if (paragraph.getStyle().equals("3") || paragraph.getStyle().equals("4") ) {
293
+                        subTitles.append(text).append("\n");
294
+                    }
295
+                }
296
+            }
297
+        }
298
+        return subTitles.toString();
299
+    }
300
+
301
+    /**
302
+     * 检索知识库
303
+     * @return
304
+     */
305
+    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
306
+        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
307
+
308
+        //  加载集合
309
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
310
+                .collectionName(collectionName)
311
+                .build();
312
+        milvusClient.loadCollection(loadCollectionReq);
313
+
314
+        // 构建SearchParam
315
+        Map<String, Object> searchParams = new HashMap<>();
316
+        searchParams.put("nprobe", 8);
317
+        SearchReq searchReq = SearchReq.builder()
318
+                .collectionName(collectionName)
319
+                .data(queryVector)
320
+                .topK(topK)
321
+                .outputFields(Arrays.asList("file_name", "file_type", "content"))
322
+                .annsField("embedding")
323
+                .metricType(IndexParam.MetricType.COSINE)
324
+                .searchParams(searchParams)
325
+                .build();
326
+
327
+        SearchResp searchResp = milvusClient.search(searchReq);
328
+        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();
329
+
330
+        // 释放集合
331
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
332
+                .collectionName(collectionName)
333
+                .build();
334
+        milvusClient.releaseCollection(releaseCollectionReq);
335
+
336
+        return searchResultList;
337
+    }
338
+
339
+    /**
340
+     * 检索知识库
341
+     */
342
+    private List<TextSegment> splitDocument(File transferFile) throws IOException {
343
+        // 加载文档
344
+        Document document;
345
+        InputStream fileInputStream = new FileInputStream(transferFile);
346
+        String filename = transferFile.getName().toLowerCase();
347
+        if (filename.endsWith(".doc") || filename.endsWith(".docx")) {
348
+            try (POITextExtractor extractor = ExtractorFactory.createExtractor(fileInputStream)) {
349
+                String text = extractor.getText();
350
+                document = Document.from(text);
351
+            }
352
+            catch (IOException e) {
353
+                throw new RuntimeException(e);
354
+            }
355
+        }
356
+        else if (filename.endsWith(".pdf")) {
357
+            document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
358
+        }
359
+        else if (filename.endsWith(".txt")) {
360
+            document = new TextDocumentParser().parse(fileInputStream);
361
+        }
362
+        else {
363
+            throw new UnsupportedOperationException("不支持文件类型: " + filename);
364
+        }
365
+        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
366
+        return splitter.split(document);
367
+    }
368
+
369
+}

+ 5
- 0
oa-back/ruoyi-agent/src/main/resources/application.yml View File

@@ -0,0 +1,5 @@
1
+# 项目相关配置
2
+cmc:
3
+  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
4
+  #  profile: /home/cmc/projects/cmc-llm
5
+  profile: E:/home/cmc/projects/cmc-llm

+ 81
- 0
oa-back/ruoyi-llm/pom.xml View File

@@ -0,0 +1,81 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <parent>
6
+        <artifactId>ruoyi</artifactId>
7
+        <groupId>com.ruoyi</groupId>
8
+        <version>3.8.7</version>
9
+    </parent>
10
+    <modelVersion>4.0.0</modelVersion>
11
+
12
+    <artifactId>ruoyi-llm</artifactId>
13
+
14
+    <description>
15
+        cmc大模型
16
+    </description>
17
+
18
+    <dependencies>
19
+
20
+        <dependency>
21
+            <groupId>com.ruoyi</groupId>
22
+            <artifactId>ruoyi-common</artifactId>
23
+        </dependency>
24
+
25
+        <dependency>
26
+            <groupId>com.ruoyi</groupId>
27
+            <artifactId>ruoyi-framework</artifactId>
28
+        </dependency>
29
+
30
+        <!-- 向量数据库-->
31
+        <dependency>
32
+            <groupId>io.milvus</groupId>
33
+            <artifactId>milvus-sdk-java</artifactId>
34
+            <version>2.6.2</version>
35
+        </dependency>
36
+
37
+        <!-- LangChain4j -->
38
+        <dependency>
39
+            <groupId>dev.langchain4j</groupId>
40
+            <artifactId>langchain4j</artifactId>
41
+            <version>0.35.0</version>
42
+        </dependency>
43
+
44
+        <!-- LangChain4j Milvus 集成 -->
45
+        <dependency>
46
+            <groupId>dev.langchain4j</groupId>
47
+            <artifactId>langchain4j-milvus</artifactId>
48
+            <version>0.35.0</version>
49
+        </dependency>
50
+
51
+        <!-- LangChain4j embedding 集成 -->
52
+        <dependency>
53
+            <groupId>dev.langchain4j</groupId>
54
+            <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
55
+            <version>0.35.0</version>
56
+        </dependency>
57
+
58
+        <!-- LangChain4j rag 集成 -->
59
+        <dependency>
60
+            <groupId>dev.langchain4j</groupId>
61
+            <artifactId>langchain4j-easy-rag</artifactId>
62
+            <version>0.35.0</version>
63
+        </dependency>
64
+
65
+        <!-- LangChain4j pdfParser 集成 -->
66
+        <dependency>
67
+            <groupId>dev.langchain4j</groupId>
68
+            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
69
+            <version>0.35.0</version>
70
+        </dependency>
71
+
72
+        <!-- Solon模型上下文协议 -->
73
+        <dependency>
74
+            <groupId>org.noear</groupId>
75
+            <artifactId>solon-ai-mcp</artifactId>
76
+            <version>3.5.1</version>
77
+        </dependency>
78
+
79
+    </dependencies>
80
+
81
+</project>

+ 151
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcAgentController.java View File

@@ -0,0 +1,151 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import java.io.IOException;
4
+import java.util.Date;
5
+import java.util.List;
6
+import javax.servlet.http.HttpServletResponse;
7
+
8
+import org.noear.solon.ai.chat.message.AssistantMessage;
9
+import org.springframework.beans.factory.annotation.Autowired;
10
+import org.springframework.web.bind.annotation.GetMapping;
11
+import org.springframework.web.bind.annotation.PostMapping;
12
+import org.springframework.web.bind.annotation.PutMapping;
13
+import org.springframework.web.bind.annotation.DeleteMapping;
14
+import org.springframework.web.bind.annotation.PathVariable;
15
+import org.springframework.web.bind.annotation.RequestBody;
16
+import org.springframework.web.bind.annotation.RequestMapping;
17
+import org.springframework.web.bind.annotation.RestController;
18
+import com.ruoyi.common.annotation.Log;
19
+import com.ruoyi.common.core.controller.BaseController;
20
+import com.ruoyi.common.core.domain.AjaxResult;
21
+import com.ruoyi.common.enums.BusinessType;
22
+import com.ruoyi.llm.domain.CmcAgent;
23
+import com.ruoyi.llm.service.ICmcAgentService;
24
+import com.ruoyi.common.utils.poi.ExcelUtil;
25
+import com.ruoyi.common.core.page.TableDataInfo;
26
+import org.springframework.web.multipart.MultipartFile;
27
+
28
+/**
29
+ * 智能体Controller
30
+ * 
31
+ * @author ruoyi
32
+ * @date 2025-07-17
33
+ */
34
+@RestController
35
+@RequestMapping("/llm/agent")
36
+public class CmcAgentController extends BaseController
37
+{
38
+    @Autowired
39
+    private ICmcAgentService cmcAgentService;
40
+
41
+    /**
42
+     * 查询智能体列表
43
+     */
44
+    @GetMapping("/list")
45
+    public TableDataInfo list(CmcAgent cmcAgent)
46
+    {
47
+        startPage();
48
+        List<CmcAgent> list = cmcAgentService.selectCmcAgentList(cmcAgent);
49
+        return getDataTable(list);
50
+    }
51
+
52
+    /**
53
+     * 导出智能体列表
54
+     */
55
+    @Log(title = "智能体", businessType = BusinessType.EXPORT)
56
+    @PostMapping("/export")
57
+    public void export(HttpServletResponse response, CmcAgent cmcAgent)
58
+    {
59
+        List<CmcAgent> list = cmcAgentService.selectCmcAgentList(cmcAgent);
60
+        ExcelUtil<CmcAgent> util = new ExcelUtil<CmcAgent>(CmcAgent.class);
61
+        util.exportExcel(response, list, "智能体数据");
62
+    }
63
+
64
+    /**
65
+     * 获取智能体详细信息
66
+     */
67
+    @GetMapping(value = "/{agentId}")
68
+    public AjaxResult getInfo(@PathVariable("agentId") Integer agentId)
69
+    {
70
+        return success(cmcAgentService.selectCmcAgentByAgentId(agentId));
71
+    }
72
+
73
+    /**
74
+     * 获取开场白
75
+     * @return
76
+     */
77
+    @GetMapping("/opening")
78
+    public AjaxResult opening(String agentName) {
79
+        return success(new AssistantMessage(cmcAgentService.getOpening(agentName)));
80
+    }
81
+
82
+    /**
83
+     * 上传单文件
84
+     * @return
85
+     */
86
+    @PostMapping("/upload")
87
+    public AjaxResult upload(MultipartFile file, String agentName) throws IOException
88
+    {
89
+        return success(cmcAgentService.uploadDocument(file, agentName));
90
+    }
91
+
92
+    /**
93
+     * 上传修改文件
94
+     * @return
95
+     */
96
+    @PostMapping("/modifyFile")
97
+    public AjaxResult uploadModifyFile(MultipartFile file, String agentName) throws IOException
98
+    {
99
+        return success(cmcAgentService.uploadModifyFile(file, agentName));
100
+    }
101
+
102
+    /**
103
+     * 上传多文件
104
+     * @return
105
+     */
106
+    @PostMapping("/uploadList")
107
+    public AjaxResult uploadList(MultipartFile[] fileList, String agentName) throws IOException
108
+    {
109
+        return success(cmcAgentService.uploadDocumentList(fileList, agentName));
110
+    }
111
+
112
+    /**
113
+     * 获取上传进度
114
+     */
115
+    @GetMapping(value = "/getProcess")
116
+    public AjaxResult getProcess() {
117
+        return success(cmcAgentService.getProcess());
118
+    }
119
+
120
+    /**
121
+     * 新增智能体
122
+     */
123
+    @Log(title = "智能体", businessType = BusinessType.INSERT)
124
+    @PostMapping
125
+    public AjaxResult add(@RequestBody CmcAgent cmcAgent)
126
+    {
127
+        cmcAgent.setCreateTime(new Date());
128
+        cmcAgent.setCreateBy(getLoginUser().getUserId().toString());
129
+        return toAjax(cmcAgentService.insertCmcAgent(cmcAgent));
130
+    }
131
+
132
+    /**
133
+     * 修改智能体
134
+     */
135
+    @Log(title = "智能体", businessType = BusinessType.UPDATE)
136
+    @PutMapping
137
+    public AjaxResult edit(@RequestBody CmcAgent cmcAgent)
138
+    {
139
+        return toAjax(cmcAgentService.updateCmcAgent(cmcAgent));
140
+    }
141
+
142
+    /**
143
+     * 删除智能体
144
+     */
145
+    @Log(title = "智能体", businessType = BusinessType.DELETE)
146
+	@DeleteMapping("/{agentIds}")
147
+    public AjaxResult remove(@PathVariable Integer[] agentIds)
148
+    {
149
+        return success(cmcAgentService.deleteCmcAgentByAgentIds(agentIds));
150
+    }
151
+}

+ 101
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcChatController.java View File

@@ -0,0 +1,101 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import java.util.List;
4
+import javax.servlet.http.HttpServletResponse;
5
+
6
+import com.ruoyi.common.utils.SnowFlake;
7
+import org.springframework.beans.factory.annotation.Autowired;
8
+import org.springframework.web.bind.annotation.GetMapping;
9
+import org.springframework.web.bind.annotation.PostMapping;
10
+import org.springframework.web.bind.annotation.PutMapping;
11
+import org.springframework.web.bind.annotation.DeleteMapping;
12
+import org.springframework.web.bind.annotation.PathVariable;
13
+import org.springframework.web.bind.annotation.RequestBody;
14
+import org.springframework.web.bind.annotation.RequestMapping;
15
+import org.springframework.web.bind.annotation.RestController;
16
+import com.ruoyi.common.annotation.Log;
17
+import com.ruoyi.common.core.controller.BaseController;
18
+import com.ruoyi.common.core.domain.AjaxResult;
19
+import com.ruoyi.common.enums.BusinessType;
20
+import com.ruoyi.llm.domain.CmcChat;
21
+import com.ruoyi.llm.service.ICmcChatService;
22
+import com.ruoyi.common.utils.poi.ExcelUtil;
23
+import com.ruoyi.common.core.page.TableDataInfo;
24
+
25
+/**
26
+ * cmc聊天记录Controller
27
+ * 
28
+ * @author cmc
29
+ * @date 2025-04-08
30
+ */
31
+@RestController
32
+@RequestMapping("/llm/chat")
33
+public class CmcChatController extends BaseController
34
+{
35
+    @Autowired
36
+    private ICmcChatService cmcChatService;
37
+
38
+    /**
39
+     * 查询cmc聊天记录列表
40
+     */
41
+    @GetMapping("/list")
42
+    public TableDataInfo list(CmcChat cmcChat)
43
+    {
44
+        startPage();
45
+        List<CmcChat> list = cmcChatService.selectCmcChatList(cmcChat);
46
+        return getDataTable(list);
47
+    }
48
+
49
+    /**
50
+     * 导出cmc聊天记录列表
51
+     */
52
+    @Log(title = "cmc聊天记录", businessType = BusinessType.EXPORT)
53
+    @PostMapping("/export")
54
+    public void export(HttpServletResponse response, CmcChat cmcChat)
55
+    {
56
+        List<CmcChat> list = cmcChatService.selectCmcChatList(cmcChat);
57
+        ExcelUtil<CmcChat> util = new ExcelUtil<CmcChat>(CmcChat.class);
58
+        util.exportExcel(response, list, "cmc聊天记录数据");
59
+    }
60
+
61
+    /**
62
+     * 获取cmc聊天记录详细信息
63
+     */
64
+    @GetMapping(value = "/{chatId}")
65
+    public AjaxResult getInfo(@PathVariable("chatId") String chatId)
66
+    {
67
+        return success(cmcChatService.selectCmcChatByChatId(chatId));
68
+    }
69
+
70
+    /**
71
+     * 新增cmc聊天记录
72
+     */
73
+    @Log(title = "cmc聊天记录", businessType = BusinessType.INSERT)
74
+    @PostMapping
75
+    public AjaxResult add(@RequestBody CmcChat cmcChat)
76
+    {
77
+        if (cmcChat.getChatId() == null)
78
+            cmcChat.setChatId(new SnowFlake().generateId());
79
+        return toAjax(cmcChatService.insertCmcChat(cmcChat));
80
+    }
81
+
82
+    /**
83
+     * 修改cmc聊天记录
84
+     */
85
+    @Log(title = "cmc聊天记录", businessType = BusinessType.UPDATE)
86
+    @PutMapping
87
+    public AjaxResult edit(@RequestBody CmcChat cmcChat)
88
+    {
89
+        return toAjax(cmcChatService.updateCmcChat(cmcChat));
90
+    }
91
+
92
+    /**
93
+     * 删除cmc聊天记录
94
+     */
95
+    @Log(title = "cmc聊天记录", businessType = BusinessType.DELETE)
96
+	@DeleteMapping("/{chatIds}")
97
+    public AjaxResult remove(@PathVariable String[] chatIds)
98
+    {
99
+        return toAjax(cmcChatService.deleteCmcChatByChatIds(chatIds));
100
+    }
101
+}

+ 110
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcDocumentController.java View File

@@ -0,0 +1,110 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import java.io.IOException;
4
+import java.util.List;
5
+import javax.servlet.http.HttpServletResponse;
6
+
7
+import org.springframework.beans.factory.annotation.Autowired;
8
+import org.springframework.web.bind.annotation.GetMapping;
9
+import org.springframework.web.bind.annotation.PostMapping;
10
+import org.springframework.web.bind.annotation.PutMapping;
11
+import org.springframework.web.bind.annotation.DeleteMapping;
12
+import org.springframework.web.bind.annotation.PathVariable;
13
+import org.springframework.web.bind.annotation.RequestBody;
14
+import org.springframework.web.bind.annotation.RequestMapping;
15
+import org.springframework.web.bind.annotation.RestController;
16
+import com.ruoyi.common.annotation.Log;
17
+import com.ruoyi.common.core.controller.BaseController;
18
+import com.ruoyi.common.core.domain.AjaxResult;
19
+import com.ruoyi.common.enums.BusinessType;
20
+import com.ruoyi.llm.domain.CmcDocument;
21
+import com.ruoyi.llm.service.ICmcDocumentService;
22
+import com.ruoyi.common.utils.poi.ExcelUtil;
23
+import com.ruoyi.common.core.page.TableDataInfo;
24
+import org.springframework.web.multipart.MultipartFile;
25
+
26
+/**
27
+ * cmc聊天附件Controller
28
+ * 
29
+ * @author cmc
30
+ * @date 2025-04-08
31
+ */
32
+@RestController
33
+@RequestMapping("/llm/document")
34
+public class CmcDocumentController extends BaseController
35
+{
36
+    @Autowired
37
+    private ICmcDocumentService cmcDocumentService;
38
+
39
+    /**
40
+     * 查询cmc聊天附件列表
41
+     */
42
+    @GetMapping("/list")
43
+    public TableDataInfo list(CmcDocument cmcDocument)
44
+    {
45
+        startPage();
46
+        List<CmcDocument> list = cmcDocumentService.selectCmcDocumentList(cmcDocument);
47
+        return getDataTable(list);
48
+    }
49
+
50
+    /**
51
+     * 导出cmc聊天附件列表
52
+     */
53
+    @Log(title = "cmc聊天附件", businessType = BusinessType.EXPORT)
54
+    @PostMapping("/export")
55
+    public void export(HttpServletResponse response, CmcDocument cmcDocument)
56
+    {
57
+        List<CmcDocument> list = cmcDocumentService.selectCmcDocumentList(cmcDocument);
58
+        ExcelUtil<CmcDocument> util = new ExcelUtil<CmcDocument>(CmcDocument.class);
59
+        util.exportExcel(response, list, "cmc聊天附件数据");
60
+    }
61
+
62
+    /**
63
+     * 获取cmc聊天附件详细信息
64
+     */
65
+    @GetMapping(value = "/{documentId}")
66
+    public AjaxResult getInfo(@PathVariable("documentId") String documentId)
67
+    {
68
+        return success(cmcDocumentService.selectCmcDocumentByDocumentId(documentId));
69
+    }
70
+
71
+    /**
72
+     * 上传外部文件
73
+     * @return
74
+     */
75
+    @PostMapping("/upload")
76
+    public AjaxResult upload(MultipartFile[] fileList) throws IOException
77
+    {
78
+        return success(cmcDocumentService.uploadDocument(fileList));
79
+    }
80
+
81
+    /**
82
+     * 新增cmc聊天附件
83
+     */
84
+    @Log(title = "cmc聊天附件", businessType = BusinessType.INSERT)
85
+    @PostMapping
86
+    public AjaxResult add(@RequestBody CmcDocument cmcDocument)
87
+    {
88
+        return toAjax(cmcDocumentService.insertCmcDocument(cmcDocument));
89
+    }
90
+
91
+    /**
92
+     * 修改cmc聊天附件
93
+     */
94
+    @Log(title = "cmc聊天附件", businessType = BusinessType.UPDATE)
95
+    @PutMapping
96
+    public AjaxResult edit(@RequestBody CmcDocument cmcDocument)
97
+    {
98
+        return toAjax(cmcDocumentService.updateCmcDocument(cmcDocument));
99
+    }
100
+
101
+    /**
102
+     * 删除cmc聊天附件
103
+     */
104
+    @Log(title = "cmc聊天附件", businessType = BusinessType.DELETE)
105
+	@DeleteMapping("/{documentIds}")
106
+    public AjaxResult remove(@PathVariable String[] documentIds)
107
+    {
108
+        return toAjax(cmcDocumentService.deleteCmcDocumentByDocumentIds(documentIds));
109
+    }
110
+}

+ 131
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/CmcTopicController.java View File

@@ -0,0 +1,131 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import java.util.List;
4
+import javax.servlet.http.HttpServletResponse;
5
+
6
+import com.ruoyi.common.utils.SnowFlake;
7
+import com.ruoyi.llm.domain.CmcChat;
8
+import com.ruoyi.llm.domain.CmcDocument;
9
+import com.ruoyi.llm.service.ICmcChatService;
10
+import com.ruoyi.llm.service.ICmcDocumentService;
11
+import org.springframework.beans.factory.annotation.Autowired;
12
+import org.springframework.web.bind.annotation.GetMapping;
13
+import org.springframework.web.bind.annotation.PostMapping;
14
+import org.springframework.web.bind.annotation.PutMapping;
15
+import org.springframework.web.bind.annotation.DeleteMapping;
16
+import org.springframework.web.bind.annotation.PathVariable;
17
+import org.springframework.web.bind.annotation.RequestBody;
18
+import org.springframework.web.bind.annotation.RequestMapping;
19
+import org.springframework.web.bind.annotation.RestController;
20
+import com.ruoyi.common.annotation.Log;
21
+import com.ruoyi.common.core.controller.BaseController;
22
+import com.ruoyi.common.core.domain.AjaxResult;
23
+import com.ruoyi.common.enums.BusinessType;
24
+import com.ruoyi.llm.domain.CmcTopic;
25
+import com.ruoyi.llm.service.ICmcTopicService;
26
+import com.ruoyi.common.utils.poi.ExcelUtil;
27
+import com.ruoyi.common.core.page.TableDataInfo;
28
+
29
+/**
30
+ * cmc聊天主题Controller
31
+ * 
32
+ * @author cmc
33
+ * @date 2025-04-08
34
+ */
35
+@RestController
36
+@RequestMapping("/llm/topic")
37
+public class CmcTopicController extends BaseController
38
+{
39
+    @Autowired
40
+    private ICmcTopicService cmcTopicService;
41
+
42
+    @Autowired
43
+    private ICmcChatService cmcChatService;
44
+
45
+    @Autowired
46
+    private ICmcDocumentService cmcDocumentService;
47
+
48
+    /**
49
+     * 查询cmc聊天主题列表
50
+     */
51
+    @GetMapping("/list")
52
+    public TableDataInfo list(CmcTopic cmcTopic)
53
+    {
54
+        startPage();
55
+        List<CmcTopic> list = cmcTopicService.selectCmcTopicList(cmcTopic);
56
+        return getDataTable(list);
57
+    }
58
+
59
+    /**
60
+     * 导出cmc聊天主题列表
61
+     */
62
+    @Log(title = "cmc聊天主题", businessType = BusinessType.EXPORT)
63
+    @PostMapping("/export")
64
+    public void export(HttpServletResponse response, CmcTopic cmcTopic)
65
+    {
66
+        List<CmcTopic> list = cmcTopicService.selectCmcTopicList(cmcTopic);
67
+        ExcelUtil<CmcTopic> util = new ExcelUtil<CmcTopic>(CmcTopic.class);
68
+        util.exportExcel(response, list, "cmc聊天主题数据");
69
+    }
70
+
71
+    /**
72
+     * 获取cmc聊天主题详细信息
73
+     */
74
+    @GetMapping(value = "/{topicId}")
75
+    public AjaxResult getInfo(@PathVariable("topicId") String topicId)
76
+    {
77
+        return success(cmcTopicService.selectCmcTopicByTopicId(topicId));
78
+    }
79
+
80
+    /**
81
+     * 新增cmc聊天主题
82
+     */
83
+    @Log(title = "cmc聊天主题", businessType = BusinessType.INSERT)
84
+    @PostMapping
85
+    public AjaxResult add(@RequestBody CmcTopic cmcTopic)
86
+    {
87
+        cmcTopic.setTopicId(new SnowFlake().generateId());
88
+        cmcTopicService.insertCmcTopic(cmcTopic);
89
+        return success(cmcTopic.getTopicId());
90
+    }
91
+
92
+    /**
93
+     * 修改cmc聊天主题
94
+     */
95
+    @Log(title = "cmc聊天主题", businessType = BusinessType.UPDATE)
96
+    @PutMapping
97
+    public AjaxResult edit(@RequestBody CmcTopic cmcTopic)
98
+    {
99
+        return toAjax(cmcTopicService.updateCmcTopic(cmcTopic));
100
+    }
101
+
102
+    /**
103
+     * 删除cmc聊天主题
104
+     */
105
+    @Log(title = "cmc聊天主题", businessType = BusinessType.DELETE)
106
+	@DeleteMapping("/{topicIds}")
107
+    public AjaxResult remove(@PathVariable String[] topicIds)
108
+    {
109
+        for (String topicId : topicIds) {
110
+            CmcChat cmcChat = new CmcChat();
111
+            cmcChat.setTopicId(topicId);
112
+            List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
113
+            String[] chatIds = new String[cmcChatList.size()];
114
+            for (int i = 0; i < cmcChatList.size(); i++)
115
+                chatIds[i] = cmcChatList.get(i).getChatId();
116
+            if (chatIds.length > 0)
117
+                cmcChatService.deleteCmcChatByChatIds(chatIds);
118
+            for (String chatId : chatIds) {
119
+                CmcDocument cmcDocument = new CmcDocument();
120
+                cmcDocument.setChatId(chatId);
121
+                List<CmcDocument> cmcDocumentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
122
+                String[] documentIds = new String[cmcDocumentList.size()];
123
+                for (int i = 0; i < cmcDocumentList.size(); i++)
124
+                    documentIds[i] = cmcDocumentList.get(i).getDocumentId();
125
+                if (documentIds.length > 0)
126
+                    cmcDocumentService.deleteCmcDocumentByDocumentIds(documentIds);
127
+            }
128
+        }
129
+        return success(cmcTopicService.deleteCmcTopicByTopicIds(topicIds));
130
+    }
131
+}

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

@@ -0,0 +1,125 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import com.alibaba.fastjson2.JSONArray;
4
+import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
+import com.ruoyi.web.llm.service.IMilvusService;
6
+import com.ruoyi.common.core.controller.BaseController;
7
+import com.ruoyi.common.core.domain.AjaxResult;
8
+import org.springframework.beans.factory.annotation.Autowired;
9
+import org.springframework.web.bind.annotation.*;
10
+import org.springframework.web.multipart.MultipartFile;
11
+
12
+import java.io.IOException;
13
+import java.util.List;
14
+
15
+/**
16
+ * cmc知识库Controller
17
+ * 
18
+ * @author cmc
19
+ * @date 2025-04-08
20
+ */
21
+@RestController
22
+@RequestMapping("/llm/knowledge")
23
+public class KnowLedgeController extends BaseController
24
+{
25
+    @Autowired
26
+    private IMilvusService milvusService;
27
+
28
+    @Autowired
29
+    private ILangChainMilvusService langChainMilvusService;
30
+
31
+    /**
32
+     * 新建知识库
33
+     */
34
+    @GetMapping("/list")
35
+    public AjaxResult listKnowLedgeCollection()
36
+    {
37
+        JSONArray collectionNames = milvusService.getCollectionNames();
38
+        return success(collectionNames);
39
+    }
40
+    /**
41
+     * 新建知识库
42
+     */
43
+    @GetMapping("/listByName")
44
+    public AjaxResult listKnowLedgeByCollectionName(String collectionName)
45
+    {
46
+        JSONArray collectionNames = milvusService.listKnowLedgeByCollectionName(collectionName);
47
+        return success(collectionNames);
48
+    }
49
+
50
+    /**
51
+     * 新建知识库
52
+     */
53
+    @PostMapping("/create")
54
+    public AjaxResult createKnowLedgeCollection(String collectionName, String description)
55
+    {
56
+        milvusService.createCollection(collectionName, description, 512);
57
+        return success();
58
+    }
59
+
60
+    /**
61
+     * 修改知识库
62
+     */
63
+    @PostMapping("/modify")
64
+    public AjaxResult modifyKnowLedgeCollection(String collectionName, String newCollectionName)
65
+    {
66
+        milvusService.collectionRename(collectionName, newCollectionName);
67
+        return success();
68
+    }
69
+
70
+    /**
71
+     * 删除知识库集合
72
+     */
73
+    @DeleteMapping("/remove")
74
+    public AjaxResult removeKnowLedgeCollection(String collectionName)
75
+    {
76
+        milvusService.removeAllDocument(collectionName);
77
+        milvusService.deleteCollectionName(collectionName);
78
+        return success();
79
+    }
80
+
81
+    /**
82
+     * 查询知识库文件
83
+     */
84
+    @GetMapping("/listDocument")
85
+    public AjaxResult listKnowledgeDocument(String collectionName, String fileType)
86
+    {
87
+        List<String> documentList = milvusService.listDocument(collectionName, fileType);
88
+        return success(documentList);
89
+    }
90
+
91
+    /**
92
+     * 获取上传进度
93
+     */
94
+    @GetMapping(value = "/getProcess")
95
+    public AjaxResult getProcess() {
96
+        return success(langChainMilvusService.getProcess());
97
+    }
98
+
99
+    /**
100
+     * 导入知识库文件
101
+     */
102
+    @PostMapping("/insertDocument")
103
+    public AjaxResult insertKnowledgeDocument(MultipartFile[] fileList, String collectionName) throws IOException {
104
+        int insertResult = 0;
105
+        insertResult = langChainMilvusService.insertLangchainEmbeddingDocument(fileList, collectionName);
106
+        String message = "文件导入成功";
107
+        if (insertResult == 0) {
108
+            message = "文件导入失败";
109
+            return error(message);
110
+        }
111
+        else
112
+            return success(message);
113
+    }
114
+
115
+    /**
116
+     * 删除知识库文件
117
+     */
118
+    @DeleteMapping("/removeDocument")
119
+    public AjaxResult deleteKnowledgeDocument(String fileName, String collectionName)
120
+    {
121
+        milvusService.removeDocument(collectionName, fileName);
122
+        return success();
123
+    }
124
+
125
+}

+ 113
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/controller/McpController.java View File

@@ -0,0 +1,113 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.common.config.RuoYiConfig;
5
+import com.ruoyi.common.core.controller.BaseController;
6
+import com.ruoyi.llm.domain.CmcChat;
7
+import com.ruoyi.llm.service.ICmcAgentService;
8
+import com.ruoyi.llm.service.ICmcChatService;
9
+import com.ruoyi.llm.service.ICmcTopicService;
10
+import com.ruoyi.web.llm.service.ILangChainMilvusService;
11
+import dev.langchain4j.model.embedding.EmbeddingModel;
12
+import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
13
+import io.milvus.client.MilvusServiceClient;
14
+import io.milvus.param.ConnectParam;
15
+import org.noear.solon.Solon;
16
+import org.noear.solon.ai.chat.ChatModel;
17
+import org.noear.solon.ai.chat.ChatResponse;
18
+import org.noear.solon.ai.chat.ChatSession;
19
+import org.noear.solon.ai.chat.message.AssistantMessage;
20
+import org.noear.solon.ai.chat.message.ChatMessage;
21
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
22
+import org.noear.solon.ai.mcp.McpChannel;
23
+import org.noear.solon.ai.mcp.client.McpClientProvider;
24
+import org.springframework.beans.factory.annotation.Autowired;
25
+import org.springframework.web.bind.annotation.GetMapping;
26
+import org.springframework.web.bind.annotation.RequestMapping;
27
+import org.springframework.web.bind.annotation.RestController;
28
+
29
+import java.io.IOException;
30
+import java.util.*;
31
+
32
+
33
+/**
34
+ * mcp模型上下文协议Controller
35
+ * 
36
+ * @author cmc
37
+ * @date 2025-04-08
38
+ */
39
+@RestController
40
+@RequestMapping("/llm/mcp")
41
+public class McpController extends BaseController
42
+{
43
+    @Autowired
44
+    private ILangChainMilvusService langChainMilvusService;
45
+
46
+    @Autowired
47
+    private ICmcChatService cmcChatService;
48
+
49
+    @Autowired
50
+    private ICmcTopicService cmcTopicService;
51
+
52
+    @Autowired
53
+    private ICmcAgentService cmcAgentService;
54
+
55
+    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
56
+
57
+    private static final String llmServiceUrl = "http://192.168.28.196:8000/v1/chat/completions";
58
+
59
+    private static final MilvusServiceClient milvusClient = new MilvusServiceClient(
60
+            ConnectParam.newBuilder()
61
+                    .withHost("192.168.28.196")
62
+                    .withPort(19530)
63
+                    .build());
64
+
65
+    /**
66
+     * 自动调用mcp工具问答
67
+     * @return
68
+     */
69
+    @GetMapping("/answer")
70
+    public AssistantMessage answer(String topicId, String question) throws IOException {
71
+        McpClientProvider clientProvider = McpClientProvider.builder()
72
+                .channel(McpChannel.SSE)
73
+                .apiUrl("http://localhost:8080/mcp/sse")
74
+                .build();
75
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
76
+                .model("Qwen2.5-7B-Instruct")
77
+                .defaultToolsAdd(clientProvider)
78
+                .build();
79
+
80
+        List<ChatMessage> messages = new ArrayList<>();
81
+        CmcChat cmcChat = new CmcChat();
82
+        cmcChat.setTopicId(topicId);
83
+        List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
84
+        for (CmcChat chat : cmcChatList) {
85
+            messages.add(ChatMessage.ofUser(chat.getInput()));
86
+            messages.add(ChatMessage.ofAssistant(chat.getOutput()));
87
+        }
88
+        messages.add(ChatMessage.ofUser(question));
89
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
90
+        ChatResponse response = chatModel.prompt(chatSession).call();
91
+        String resultContent = response.lastChoice().getMessage().getResultContent();
92
+        AssistantMessage assistantMessage;
93
+        if (resultContent.startsWith("<tool_call>")) {
94
+            String content = resultContent.replace("<tool_call>\n", "").replace("\n</tool_call>", "");
95
+            JSONObject jsonObject = JSONObject.parseObject(content);
96
+            String name = jsonObject.getString("name");
97
+            JSONObject arguments = jsonObject.getJSONObject("arguments");
98
+            if (arguments.getString("templatePath").contains("招标") || arguments.getString("templatePath").contains("询价")) {
99
+                arguments.put("collectionName", "technical");
100
+                String agentName = cmcAgentService.selectCmcAgentByAgentId(cmcTopicService.selectCmcTopicByTopicId(topicId).getAgentId()).getAgentName();
101
+                arguments.put("agentName", agentName);
102
+                arguments.put("title", question);
103
+            }
104
+            resultContent = clientProvider.callToolAsText(name, arguments).getContent();
105
+            assistantMessage = new AssistantMessage(resultContent);
106
+        }
107
+        else
108
+            throw new IOException("模型上下文工具未准备就绪,请重试");
109
+        return assistantMessage;
110
+    }
111
+
112
+
113
+}

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

@@ -0,0 +1,47 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
+import com.ruoyi.common.core.controller.BaseController;
6
+import org.noear.solon.ai.chat.message.AssistantMessage;
7
+import org.springframework.beans.factory.annotation.Autowired;
8
+import org.springframework.web.bind.annotation.GetMapping;
9
+import org.springframework.web.bind.annotation.RequestMapping;
10
+import org.springframework.web.bind.annotation.RestController;
11
+import reactor.core.publisher.Flux;
12
+
13
+import java.util.List;
14
+
15
+/**
16
+ * rag增强检索生成对话Controller
17
+ * 
18
+ * @author cmc
19
+ * @date 2025-04-08
20
+ */
21
+@RestController
22
+@RequestMapping("/llm/rag")
23
+public class RagController extends BaseController
24
+{
25
+    @Autowired
26
+    private ILangChainMilvusService langChainMilvusService;
27
+
28
+    /**
29
+     * 调用LLM+RAG(知识库)生成回答
30
+     */
31
+    @GetMapping("/answer")
32
+    public Flux<AssistantMessage> answerWithCollection(String collectionName, String topicId, String question)
33
+    {
34
+        List<JSONObject> contexts = langChainMilvusService.retrieveFromMilvus(collectionName, question, 10);
35
+        return langChainMilvusService.generateAnswerWithCollection(topicId, question, contexts, "http://192.168.28.196:8000/v1/chat/completions");
36
+    }
37
+
38
+    /**
39
+     * RAG上下文列表
40
+     */
41
+    @GetMapping("/context")
42
+    public List<JSONObject> context(String question, String collectionName)
43
+    {
44
+        return langChainMilvusService.similarityFromMilvus(collectionName, question, 10);
45
+    }
46
+
47
+}

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

@@ -0,0 +1,44 @@
1
+package com.ruoyi.web.llm.controller;
2
+
3
+import com.ruoyi.common.core.controller.BaseController;
4
+import com.ruoyi.web.llm.service.ILangChainMilvusService;
5
+import org.noear.solon.ai.chat.message.AssistantMessage;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.web.bind.annotation.GetMapping;
8
+import org.springframework.web.bind.annotation.RequestMapping;
9
+import org.springframework.web.bind.annotation.RestController;
10
+import reactor.core.publisher.Flux;
11
+
12
+import java.io.IOException;
13
+
14
+/**
15
+ * session对话Controller
16
+ * 
17
+ * @author cmc
18
+ * @date 2025-04-08
19
+ */
20
+@RestController
21
+@RequestMapping("/llm/session")
22
+public class SessionController extends BaseController
23
+{
24
+    @Autowired
25
+    private ILangChainMilvusService langChainMilvusService;
26
+
27
+    /**
28
+     * 生成回答
29
+     */
30
+    @GetMapping("/answer")
31
+    public Flux<AssistantMessage> answer(String topicId, String question) {
32
+        return langChainMilvusService.generateAnswer(topicId, question, "http://192.168.28.196:8000/v1/chat/completions");
33
+    }
34
+
35
+    /**
36
+     * 调用LLM+RAG(外部文件)生成回答
37
+     */
38
+    @GetMapping("/answerWithDocument")
39
+    public Flux<AssistantMessage> answerWithDocument(String topicId, String chatId, String question) throws IOException
40
+    {
41
+        return langChainMilvusService.generateAnswerWithDocument(topicId, chatId, question, "http://192.168.28.196:8000/v1/chat/completions");
42
+    }
43
+
44
+}

+ 67
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/ILangChainMilvusService.java View File

@@ -0,0 +1,67 @@
1
+package com.ruoyi.web.llm.service;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import org.noear.solon.ai.chat.message.AssistantMessage;
5
+import org.springframework.web.multipart.MultipartFile;
6
+import reactor.core.publisher.Flux;
7
+
8
+import java.io.IOException;
9
+import java.util.List;
10
+
11
+public interface ILangChainMilvusService {
12
+
13
+    /**
14
+     * 获取进度
15
+     *
16
+     * @return 结果
17
+     */
18
+    public String getProcess();
19
+
20
+    /**
21
+     * 导入知识库文件
22
+     * @return
23
+     */
24
+    public int insertLangchainEmbeddingDocument(MultipartFile[] fileList, String collectionName);
25
+
26
+    /**
27
+     * 从Milvus检索相关文档
28
+     * @return
29
+     */
30
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK);
31
+
32
+    /**
33
+     * 从Milvus检索相关文档及相关度
34
+     * @return
35
+     */
36
+    public List<JSONObject> similarityFromMilvus(String collectionName, String query, int topK);
37
+
38
+    /**
39
+     * 调用LLM生成回答
40
+     * @return
41
+     */
42
+    public Flux<AssistantMessage> generateAnswer(String topicId, String question, String llmServiceUrl);
43
+
44
+    /**
45
+     * 调用LLM+RAG(知识库)生成回答
46
+     * @return
47
+     */
48
+    public Flux<AssistantMessage> generateAnswerWithCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl);
49
+
50
+    /**
51
+     * 调用LLM+RAG(外部文件)生成回答
52
+     * @return
53
+     */
54
+    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question, String llmServiceUrl) throws IOException;
55
+
56
+    /**
57
+     * 调用LLM+RAG(外部文件+知识库)生成回答
58
+     * @return
59
+     */
60
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question,  List<JSONObject> requests, String llmServiceUrl) throws IOException;
61
+
62
+    /**
63
+     * 获取二级标题下三级标题列表
64
+     * @return
65
+     */
66
+    public List<String> extractSubTitles(String filename, String question) throws IOException;
67
+}

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

@@ -0,0 +1,48 @@
1
+package com.ruoyi.web.llm.service;
2
+
3
+import com.alibaba.fastjson2.JSONArray;
4
+
5
+import java.util.List;
6
+
7
+public interface IMilvusService {
8
+
9
+    /**
10
+     * 新建知识库Collection(含Schema、Field、Index)
11
+     */
12
+    public void createCollection(String collectionName, String description, int dimension);
13
+
14
+    /**
15
+     * 查询知识库Collection
16
+     */
17
+    public JSONArray getCollectionNames();
18
+
19
+    /**
20
+     * 查询知识库Collection
21
+     */
22
+    public JSONArray listKnowLedgeByCollectionName(String collectionName);
23
+
24
+    /**
25
+     * 修改知识库Collection
26
+     */
27
+    public void collectionRename(String collectionName, String newCollectionName);
28
+
29
+    /**
30
+     * 删除知识库Collection
31
+     */
32
+    public void deleteCollectionName(String collectionName);
33
+
34
+    /**
35
+     * 查询知识库文件
36
+     */
37
+    public List<String> listDocument(String collectionName, String fileType);
38
+
39
+    /**
40
+     * 删除知识库文件
41
+     */
42
+    public void removeDocument(String collectionName, String fileName);
43
+
44
+    /**
45
+     * 删除知识库所有文件
46
+     */
47
+    public void removeAllDocument(String collectionName);
48
+}

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

@@ -0,0 +1,435 @@
1
+package com.ruoyi.web.llm.service.impl;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.google.gson.JsonObject;
5
+import com.google.gson.JsonParser;
6
+import com.ruoyi.common.config.RuoYiConfig;
7
+import com.ruoyi.llm.domain.CmcChat;
8
+import com.ruoyi.llm.domain.CmcDocument;
9
+import com.ruoyi.llm.service.ICmcChatService;
10
+import com.ruoyi.llm.service.ICmcDocumentService;
11
+import com.ruoyi.web.llm.service.ILangChainMilvusService;
12
+import dev.langchain4j.data.document.Document;
13
+import dev.langchain4j.data.document.parser.TextDocumentParser;
14
+import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser;
15
+import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter;
16
+import dev.langchain4j.data.embedding.Embedding;
17
+import dev.langchain4j.data.segment.TextSegment;
18
+import dev.langchain4j.model.embedding.EmbeddingModel;
19
+import dev.langchain4j.model.embedding.onnx.bgesmallzhv15.BgeSmallZhV15EmbeddingModel;
20
+import dev.langchain4j.store.embedding.EmbeddingMatch;
21
+import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
22
+import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
23
+import io.milvus.v2.client.ConnectConfig;
24
+import io.milvus.v2.client.MilvusClientV2;
25
+import io.milvus.v2.common.IndexParam;
26
+import io.milvus.v2.service.collection.request.LoadCollectionReq;
27
+import io.milvus.v2.service.collection.request.ReleaseCollectionReq;
28
+import io.milvus.v2.service.vector.request.DeleteReq;
29
+import io.milvus.v2.service.vector.request.InsertReq;
30
+import io.milvus.v2.service.vector.request.SearchReq;
31
+import io.milvus.v2.service.vector.request.data.BaseVector;
32
+import io.milvus.v2.service.vector.request.data.FloatVec;
33
+import io.milvus.v2.service.vector.response.InsertResp;
34
+import io.milvus.v2.service.vector.response.SearchResp;
35
+import org.apache.poi.extractor.POITextExtractor;
36
+import org.apache.poi.extractor.ExtractorFactory;
37
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
38
+import org.apache.poi.xwpf.usermodel.XWPFParagraph;
39
+import org.noear.solon.ai.chat.ChatModel;
40
+import org.noear.solon.ai.chat.ChatResponse;
41
+import org.noear.solon.ai.chat.ChatSession;
42
+import org.noear.solon.ai.chat.message.AssistantMessage;
43
+import org.noear.solon.ai.chat.message.ChatMessage;
44
+import org.noear.solon.ai.chat.session.InMemoryChatSession;
45
+import org.noear.solon.web.sse.SseEvent;
46
+import org.reactivestreams.Publisher;
47
+import org.springframework.beans.factory.annotation.Autowired;
48
+import org.springframework.stereotype.Service;
49
+import org.springframework.web.multipart.MultipartFile;
50
+import reactor.core.publisher.Flux;
51
+
52
+import java.io.*;
53
+import java.util.*;
54
+
55
+@Service
56
+public class LangChainMilvusServiceImpl implements ILangChainMilvusService
57
+{
58
+    @Autowired
59
+    private ICmcChatService cmcChatService;
60
+
61
+    @Autowired
62
+    private ICmcDocumentService cmcDocumentService;
63
+
64
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
65
+            ConnectConfig.builder()
66
+                    .uri("http://192.168.28.196:19530")
67
+                    .build());
68
+
69
+    private static final EmbeddingModel embeddingModel = new BgeSmallZhV15EmbeddingModel();
70
+
71
+    private String processValue = "";
72
+
73
+    /**
74
+     * 上传多文件
75
+     *
76
+     * @return 结果
77
+     */
78
+    public String getProcess() {
79
+        return processValue;
80
+    }
81
+
82
+    /**
83
+     * 导入知识库文件
84
+     * @return
85
+     */
86
+    @Override
87
+    public int insertLangchainEmbeddingDocument(MultipartFile[] fileList, String collectionName)
88
+    {
89
+        processValue = "";
90
+        int successfullyInsertedFiles = 0;
91
+
92
+        for (int i = 0; i < fileList.length; i++) {
93
+            MultipartFile file = fileList[i];
94
+            try {
95
+                File profilePath = new File( RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName);
96
+                if (!profilePath.exists())
97
+                    profilePath.mkdirs();
98
+                File transferFile = new File( profilePath + "/" + file.getOriginalFilename());
99
+                if (!transferFile.exists())
100
+                    file.transferTo(transferFile);
101
+                List<TextSegment> segments = splitDocument(transferFile);
102
+
103
+                // 准备导入数据
104
+                List<JsonObject> data = new ArrayList<>();
105
+                // 提取文本和生成嵌入
106
+                for (TextSegment segment : segments) {
107
+                    String text = segment.text();
108
+                    if (text.trim().isEmpty())
109
+                        continue;
110
+
111
+                    JSONObject fastjsonObj = new JSONObject();
112
+                    fastjsonObj.put("file_name", file.getOriginalFilename());
113
+                    String[] fileName = file.getOriginalFilename().split("\\.");
114
+                    fastjsonObj.put("file_type", fileName[fileName.length - 1]);
115
+                    fastjsonObj.put("content", text);
116
+                    fastjsonObj.put("embedding", embeddingModel.embed(text).content().vectorAsList());
117
+                    String jsonString = fastjsonObj.toJSONString();
118
+                    JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
119
+                    data.add(jsonObject);
120
+                }
121
+
122
+                // 加载集合
123
+                LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
124
+                        .collectionName(collectionName)
125
+                        .build();
126
+                milvusClient.loadCollection(loadCollectionReq);
127
+
128
+                try {
129
+                    // 先删除相同文件名的记录
130
+                    DeleteReq deleteReq = DeleteReq.builder()
131
+                            .collectionName(collectionName)
132
+                            .filter(String.format("file_name == \"%s\"", file.getOriginalFilename()))
133
+                            .build();
134
+                    if (milvusClient.delete(deleteReq).getDeleteCnt() > 0)
135
+                        System.out.println("已删除同名文件记录: " + file.getOriginalFilename());
136
+
137
+                    // 构建导入请求
138
+                    InsertReq insertReq = InsertReq.builder()
139
+                            .collectionName(collectionName)
140
+                            .data(data)
141
+                            .build();
142
+
143
+                    // 执行导入并检查结果
144
+                    InsertResp insertResp = milvusClient.insert(insertReq);
145
+                    if (insertResp != null && insertResp.getInsertCnt() > 0) {
146
+                        successfullyInsertedFiles++;
147
+                        processValue = "上传中: " + String.format("%.2f", (double) (i + 1) / fileList.length * 100) + "%";
148
+                        System.out.println("成功导入" + (i + 1) + "/" + fileList.length + "文件: " + file.getOriginalFilename() + ", 导入记录数: " + insertResp.getInsertCnt());
149
+                    } else {
150
+                        System.err.println("文件导入失败: " + file.getOriginalFilename() + ", 没有记录被导入");
151
+                    }
152
+                } finally {
153
+                    // 释放集合
154
+                    ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
155
+                            .collectionName(collectionName)
156
+                            .build();
157
+                    milvusClient.releaseCollection(releaseCollectionReq);
158
+                }
159
+            } catch (Exception e) {
160
+                System.err.println("处理文件时出错: " + file.getOriginalFilename() + ", 错误: " + e.getMessage());
161
+                e.printStackTrace();
162
+                // 继续处理下一个文件
163
+            }
164
+        }
165
+
166
+        processValue = "上传完成";
167
+        return successfullyInsertedFiles;
168
+    }
169
+
170
+    /**
171
+     * 从Milvus检索相关文档
172
+     * @return
173
+     */
174
+    @Override
175
+    public List<JSONObject> retrieveFromMilvus(String collectionName, String query, int topK) {
176
+        List<JSONObject> resultList = new ArrayList<>();
177
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
178
+        searchResultList.forEach(searchResult -> {
179
+            JSONObject result = new JSONObject();
180
+            result.put("file_name", searchResult.get(0).getEntity().get("file_name"));
181
+            result.put("content", searchResult.get(0).getEntity().get("content"));
182
+            resultList.add(result);
183
+        });
184
+        return resultList;
185
+    }
186
+
187
+    /**
188
+     * 从Milvus检索相关文档及相关度
189
+     * @return
190
+     */
191
+    @Override
192
+    public List<JSONObject> similarityFromMilvus(String collectionName, String query, int topK) {
193
+        List<JSONObject> resultList = new ArrayList<>();
194
+        List<List<SearchResp.SearchResult>> searchResultList = retrieve(collectionName, query, topK);
195
+        searchResultList.forEach(searchResult -> {
196
+            JSONObject result = new JSONObject();
197
+            result.put("score", searchResult.get(0).getScore());
198
+            result.put("file_name", searchResult.get(0).getEntity().get("file_name"));
199
+            result.put("content", searchResult.get(0).getEntity().get("content"));
200
+            resultList.add(result);
201
+        });
202
+        resultList.removeIf(jsonObject -> jsonObject.getDouble("score") < 0.7);
203
+        return resultList;
204
+    }
205
+
206
+    /**
207
+     * 调用LLM生成回答
208
+     * @return
209
+     */
210
+    @Override
211
+    public Flux<AssistantMessage> generateAnswer(String topicId, String prompt, String llmServiceUrl) {
212
+        ChatModel chatModel = ChatModel.of(llmServiceUrl)
213
+
214
+                .model("Qwen2.5-7B-Instruct")
215
+                .build();
216
+
217
+        List<ChatMessage> messages = new ArrayList<>();
218
+        if (topicId != null) {
219
+            CmcChat cmcChat = new CmcChat();
220
+            cmcChat.setTopicId(topicId);
221
+            List<CmcChat> cmcChatList = cmcChatService.selectCmcChatList(cmcChat);
222
+            for (CmcChat chat : cmcChatList) {
223
+                messages.add(ChatMessage.ofUser(chat.getInput()));
224
+                messages.add(ChatMessage.ofAssistant(chat.getOutput()));
225
+            }
226
+        }
227
+        messages.add(ChatMessage.ofUser(prompt));
228
+        ChatSession chatSession =  InMemoryChatSession.builder().messages(messages).build();
229
+        Publisher<ChatResponse> publisher = chatModel.prompt(chatSession).stream();
230
+        return Flux.from(publisher)
231
+                .map(response -> response.lastChoice().getMessage());
232
+    }
233
+
234
+    /**
235
+     * 调用LLM+RAG生成回答
236
+     * @return
237
+     */
238
+    @Override
239
+    public Flux<AssistantMessage> generateAnswerWithCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) {
240
+        StringBuilder sb = new StringBuilder();
241
+        sb.append("问题: ").append(question).append("\n\n");
242
+        sb.append("根据以下上下文回答问题:\n\n");
243
+        for (JSONObject context : contexts) {
244
+            sb.append("文件").append(": ")
245
+                    .append(context.getString("file_name")).append("\n\n")
246
+                    .append("上下文").append(": ")
247
+                    .append(context.getString("content")).append("\n\n");
248
+        }
249
+        return generateAnswer(topicId, sb.toString(), llmServiceUrl);
250
+    }
251
+
252
+    /**
253
+     * 调用LLM生成回答
254
+     */
255
+    @Override
256
+    public Flux<AssistantMessage> generateAnswerWithDocument(String topicId, String chatId, String question, String llmServiceUrl) throws IOException {
257
+        CmcDocument cmcDocument = new CmcDocument();
258
+        cmcDocument.setChatId(chatId);
259
+        List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
260
+        StringBuilder sb = new StringBuilder("问题: " + question + "\n\n").append("根据以下上下文回答问题:\n\n");
261
+        for (CmcDocument document : documentList) {
262
+            File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
263
+            InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
264
+            List<TextSegment> segments = splitDocument(profilePath);
265
+            List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
266
+            embeddingStore.addAll(embeddings, segments);
267
+            Embedding queryEmbedding = embeddingModel.embed(question).content();
268
+            EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
269
+                    .queryEmbedding(queryEmbedding)
270
+                    .minScore(0.7)
271
+                    .build();
272
+            List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
273
+            results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
274
+            for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
275
+                String contexts = embeddingMatch.embedded().toString();
276
+                sb.append("文件").append(": ")
277
+                        .append(document.getPath()).append("\n\n")
278
+                        .append("上下文").append(": ")
279
+                        .append(contexts).append("\n\n");
280
+            }
281
+        }
282
+        return generateAnswer(topicId, sb.toString(), llmServiceUrl);
283
+    }
284
+
285
+    /**
286
+     * 调用LLM生成回答
287
+     */
288
+    @Override
289
+    public Flux<AssistantMessage> generateAnswerWithDocumentAndCollection(String topicId, String question, List<JSONObject> contexts, String llmServiceUrl) throws IOException {
290
+        StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
291
+        CmcChat cmcChat = new CmcChat();
292
+        cmcChat.setTopicId(topicId);
293
+        for (CmcChat chat : cmcChatService.selectCmcChatList(cmcChat)) {
294
+            CmcDocument cmcDocument = new CmcDocument();
295
+            cmcDocument.setChatId(chat.getChatId());
296
+            List<CmcDocument> documentList = cmcDocumentService.selectCmcDocumentList(cmcDocument);
297
+            //技术标书撰写智能体一个话题只会上传一个招标文件
298
+            if (documentList.size() == 1) {
299
+                for (CmcDocument document : documentList) {
300
+                    File profilePath = new File(RuoYiConfig.getProfile() + document.getPath());
301
+                    InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
302
+                    List<TextSegment> segments = splitDocument(profilePath);
303
+                    List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
304
+                    embeddingStore.addAll(embeddings, segments);
305
+                    Embedding queryEmbedding = embeddingModel.embed(question).content();
306
+                    EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
307
+                            .queryEmbedding(queryEmbedding)
308
+                            .minScore(0.7)
309
+                            .build();
310
+                    List<EmbeddingMatch<TextSegment>> results = embeddingStore.search(embeddingSearchRequest).matches();
311
+                    results.sort(Comparator.comparingDouble(EmbeddingMatch<TextSegment>::score).reversed());
312
+                    for (EmbeddingMatch<TextSegment> embeddingMatch : results) {
313
+                        String requests = embeddingMatch.embedded().toString();
314
+                        sb.append(requests).append("\n\n");
315
+                    }
316
+                }
317
+            }
318
+        }
319
+        sb.append("针对本项目招标文件内容,补全以下章节部分:\n\n").append(question);
320
+//        for (JSONObject context : contexts) {
321
+//            sb.append("文件").append(": ")
322
+//                    .append(context.getString("file_name")).append("\n\n")
323
+//                    .append("段落格式").append(": ")
324
+//                    .append(context.getString("content")).append("\n\n");
325
+//        }
326
+        return generateAnswer(topicId, sb.toString(), llmServiceUrl);
327
+    }
328
+
329
+    /**
330
+     * 获取二级标题下三级标题列表
331
+     */
332
+    @Override
333
+    public List<String> extractSubTitles(String filename, String question) throws IOException {
334
+        List<String> subTitles = new ArrayList<>();
335
+        boolean inTargetSection = false;
336
+
337
+        InputStream fileInputStream = new FileInputStream(filename);
338
+        try (XWPFDocument document = new XWPFDocument(fileInputStream)) {
339
+            for (XWPFParagraph paragraph : document.getParagraphs()) {
340
+                String text = paragraph.getText().trim();
341
+                if (paragraph.getStyle() != null) {
342
+                    // 判断主标题
343
+                    if (paragraph.getStyle().equals("3") &&
344
+                            text.contains(question)) {
345
+                        inTargetSection = true;
346
+                        continue;
347
+                    }
348
+
349
+                    // 如果已经在目标节中,收集标题3级别的子标题
350
+                    if (inTargetSection) {
351
+                        if (paragraph.getStyle().equals("4")) {
352
+                            subTitles.add(text);
353
+                        }
354
+                        // 遇到下一个Heading1则退出
355
+                        else if (paragraph.getStyle().equals("3")) {
356
+                            break;
357
+                        }
358
+                    }
359
+                }
360
+            }
361
+        }
362
+        if (subTitles.size() == 0)
363
+            subTitles.add(question);
364
+        return subTitles;
365
+    }
366
+
367
+    /**
368
+     * 检索知识库
369
+     * @return
370
+     */
371
+    private List<List<SearchResp.SearchResult>> retrieve(String collectionName, String query, int topK) {
372
+        List<BaseVector> queryVector = Collections.singletonList(new FloatVec(embeddingModel.embed(query).content().vector()));
373
+
374
+        //  加载集合
375
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
376
+                .collectionName(collectionName)
377
+                .build();
378
+        milvusClient.loadCollection(loadCollectionReq);
379
+
380
+        // 构建SearchParam
381
+        Map<String, Object> searchParams = new HashMap<>();
382
+        searchParams.put("nprobe", 8);
383
+        SearchReq searchReq = SearchReq.builder()
384
+                .collectionName(collectionName)
385
+                .data(queryVector)
386
+                .topK(topK)
387
+                .outputFields(Arrays.asList("file_name", "file_type", "content"))
388
+                .annsField("embedding")
389
+                .metricType(IndexParam.MetricType.COSINE)
390
+                .searchParams(searchParams)
391
+                .build();
392
+
393
+        SearchResp searchResp = milvusClient.search(searchReq);
394
+        List<List<SearchResp.SearchResult>> searchResultList = searchResp.getSearchResults();
395
+
396
+        // 释放集合
397
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
398
+                .collectionName(collectionName)
399
+                .build();
400
+        milvusClient.releaseCollection(releaseCollectionReq);
401
+
402
+        return searchResultList;
403
+    }
404
+
405
+    /**
406
+     * 检索知识库
407
+     */
408
+    private List<TextSegment> splitDocument(File transferFile) throws FileNotFoundException {
409
+        // 加载文档
410
+        Document document;
411
+        InputStream fileInputStream = new FileInputStream(transferFile);
412
+        String filename = transferFile.getName().toLowerCase();
413
+        if (filename.endsWith(".doc") || filename.endsWith(".docx")) {
414
+            try (POITextExtractor extractor = ExtractorFactory.createExtractor(fileInputStream)) {
415
+                String text = extractor.getText();
416
+                document = Document.from(text);
417
+            }
418
+            catch (IOException e) {
419
+                throw new RuntimeException(e);
420
+            }
421
+        }
422
+        else if (filename.endsWith(".pdf")) {
423
+            document = new ApachePdfBoxDocumentParser().parse(fileInputStream);
424
+        }
425
+        else if (filename.endsWith(".txt")) {
426
+            document = new TextDocumentParser().parse(fileInputStream);
427
+        }
428
+        else {
429
+            throw new UnsupportedOperationException("不支持文件类型: " + filename);
430
+        }
431
+        DocumentByParagraphSplitter splitter = new DocumentByParagraphSplitter(300,50);
432
+        return splitter.split(document);
433
+    }
434
+
435
+}

+ 248
- 0
oa-back/ruoyi-llm/src/main/java/com/ruoyi/web/llm/service/impl/MilvusServiceImpl.java View File

@@ -0,0 +1,248 @@
1
+package com.ruoyi.web.llm.service.impl;
2
+
3
+import com.alibaba.fastjson2.JSONArray;
4
+import com.alibaba.fastjson2.JSONObject;
5
+import com.ruoyi.web.llm.service.IMilvusService;
6
+import io.milvus.v2.client.ConnectConfig;
7
+import io.milvus.v2.client.MilvusClientV2;
8
+import io.milvus.v2.common.DataType;
9
+import io.milvus.v2.common.IndexParam;
10
+import io.milvus.v2.service.collection.request.*;
11
+import io.milvus.v2.service.collection.response.DescribeCollectionResp;
12
+import io.milvus.v2.service.collection.response.ListCollectionsResp;
13
+import io.milvus.v2.service.vector.request.DeleteReq;
14
+import io.milvus.v2.service.vector.request.QueryReq;
15
+import io.milvus.v2.service.vector.response.QueryResp;
16
+import org.springframework.stereotype.Service;
17
+
18
+import java.text.SimpleDateFormat;
19
+import java.util.*;
20
+import java.util.stream.Collectors;
21
+
22
+@Service
23
+public class MilvusServiceImpl implements IMilvusService {
24
+
25
+    private static final MilvusClientV2 milvusClient = new MilvusClientV2(
26
+            ConnectConfig.builder()
27
+                    .uri("http://192.168.28.196:19530")
28
+                    .build());
29
+
30
+    /**
31
+     * 新建知识库Collection(含Schema、Field、Index)
32
+     */
33
+    @Override
34
+    public void createCollection(String collectionName, String description, int dimension) {
35
+        CreateCollectionReq.CollectionSchema schema = MilvusClientV2.CreateSchema();
36
+
37
+        schema.addField(AddFieldReq.builder()
38
+                .fieldName("id")
39
+                .dataType(DataType.Int64)
40
+                .isPrimaryKey(true)
41
+                .autoID(true)
42
+                .build());
43
+
44
+        schema.addField(AddFieldReq.builder()
45
+                .fieldName("file_name")
46
+                .dataType(DataType.VarChar)
47
+                .maxLength(256)
48
+                .build());
49
+
50
+        schema.addField(AddFieldReq.builder()
51
+                .fieldName("file_type")
52
+                .dataType(DataType.VarChar)
53
+                .maxLength(10)
54
+                .build());
55
+
56
+        schema.addField(AddFieldReq.builder()
57
+                .fieldName("content")
58
+                .dataType(DataType.VarChar)
59
+                .maxLength(65535)
60
+                .build());
61
+
62
+        schema.addField(AddFieldReq.builder()
63
+                .fieldName("embedding")
64
+                .dataType(DataType.FloatVector)
65
+                .dimension(dimension)
66
+                .build());
67
+
68
+        // 创建索引
69
+        Map<String, Object> extraParams = new HashMap<>();
70
+        extraParams.put("nlist", 64);
71
+        IndexParam indexParam = IndexParam.builder()
72
+                .fieldName("embedding")
73
+                .indexType(IndexParam.IndexType.IVF_FLAT)
74
+                .metricType(IndexParam.MetricType.COSINE)
75
+                .extraParams(extraParams)
76
+                .build();
77
+
78
+        CreateCollectionReq createCollectionParam = CreateCollectionReq.builder()
79
+                .collectionName(collectionName)
80
+                .description(description)
81
+                .collectionSchema(schema)
82
+                .indexParam(indexParam)
83
+                .build();
84
+
85
+        milvusClient.createCollection(createCollectionParam);
86
+    }
87
+
88
+    /**
89
+     * 查询知识库Collection
90
+     */
91
+    @Override
92
+    public JSONArray getCollectionNames() {
93
+        JSONArray jsonArray = new JSONArray();
94
+        ListCollectionsResp listResponse = milvusClient.listCollections();
95
+        if (listResponse != null) {
96
+            List<String> collectionNames = listResponse.getCollectionNames();
97
+            for (String collectionName : collectionNames) {
98
+                JSONObject jsonObject = new JSONObject();
99
+                DescribeCollectionReq request = DescribeCollectionReq.builder()
100
+                        .collectionName(collectionName)
101
+                        .build();
102
+                DescribeCollectionResp describeResponse = milvusClient.describeCollection(request);
103
+                jsonObject.put("collectionId", describeResponse.getCollectionID());
104
+                jsonObject.put("collectionName", collectionName);
105
+                SimpleDateFormat beijingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
106
+                beijingFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
107
+                String beijingTime = beijingFormat.format(describeResponse.getCreateUtcTime());
108
+                jsonObject.put("createdTime", beijingTime);
109
+                jsonObject.put("description", describeResponse.getDescription());
110
+                jsonArray.add(jsonObject);
111
+            }
112
+        }
113
+        return jsonArray;
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
+
148
+    /**
149
+     * 修改知识库Collection
150
+     */
151
+    @Override
152
+    public void collectionRename(String collectionName, String newCollectionName) {
153
+        RenameCollectionReq renameCollectionReq = RenameCollectionReq.builder()
154
+                .collectionName(collectionName)
155
+                .newCollectionName(newCollectionName)
156
+                .build();
157
+
158
+        milvusClient.renameCollection(renameCollectionReq);
159
+    }
160
+
161
+    /**
162
+     * 删除知识库Collection
163
+     */
164
+    @Override
165
+    public void deleteCollectionName(String collectionName) {
166
+        DropCollectionReq dropCollectionReq = DropCollectionReq.builder()
167
+                .collectionName(collectionName)
168
+                .build();
169
+        milvusClient.dropCollection(dropCollectionReq);
170
+    }
171
+
172
+    /**
173
+     * 查询知识库文件
174
+     */
175
+    @Override
176
+    public List<String> listDocument(String collectionName, String fileType) {
177
+        List<String> documentList = new ArrayList<>();
178
+        loadCollectionName(collectionName);
179
+        QueryReq queryParam = QueryReq.builder()
180
+                .collectionName(collectionName)
181
+                .filter("id > 0")
182
+                .outputFields(Arrays.asList("file_type", "file_name"))
183
+                .build();
184
+        if (fileType != null && !fileType.equals(""))
185
+            queryParam = QueryReq.builder()
186
+                .collectionName(collectionName)
187
+                .filter(String.format("file_type == \"%s\"", fileType))
188
+                .outputFields(Arrays.asList("file_type", "file_name"))
189
+                .build();
190
+        QueryResp queryResp = milvusClient.query(queryParam);
191
+        List<QueryResp.QueryResult> rowRecordList;
192
+        if (queryResp != null) {
193
+            rowRecordList = queryResp.getQueryResults();
194
+            for (QueryResp.QueryResult rowRecord : rowRecordList) {
195
+                documentList.add(rowRecord.getEntity().get("file_name").toString());
196
+            }
197
+        }
198
+        releaseCollectionName(collectionName);
199
+        return documentList.stream().distinct().collect(Collectors.toList());
200
+    }
201
+
202
+    /**
203
+     * 删除知识库文件
204
+     */
205
+    @Override
206
+    public void removeDocument(String collectionName, String fileName) {
207
+        loadCollectionName(collectionName);
208
+        DeleteReq deleteReq = DeleteReq.builder()
209
+                .collectionName(collectionName)
210
+                .filter(String.format("file_name == \"%s\"", fileName))
211
+                .build();
212
+        milvusClient.delete(deleteReq);
213
+    }
214
+
215
+    /**
216
+     * 删除知识库所有文件
217
+     */
218
+    @Override
219
+    public void removeAllDocument(String collectionName) {
220
+        loadCollectionName(collectionName);
221
+        DeleteReq deleteReq = DeleteReq.builder()
222
+                .collectionName(collectionName)
223
+                .filter("id > 0")
224
+                .build();
225
+        milvusClient.delete(deleteReq);
226
+    }
227
+
228
+    /**
229
+     * 加载知识库Collection
230
+     */
231
+    public void loadCollectionName(String collectionName) {
232
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
233
+                .collectionName(collectionName)
234
+                .build();
235
+        milvusClient.loadCollection(loadCollectionReq);
236
+    }
237
+
238
+    /**
239
+     * 释放知识库Collection
240
+     */
241
+    public void releaseCollectionName(String collectionName) {
242
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
243
+                .collectionName(collectionName)
244
+                .build();
245
+        milvusClient.releaseCollection(releaseCollectionReq);
246
+    }
247
+
248
+}

+ 36
- 0
oa-back/ruoyi-system/pom.xml View File

@@ -33,6 +33,42 @@
33 33
             <scope>compile</scope>
34 34
         </dependency>
35 35
 
36
+        <dependency>
37
+            <groupId>dev.langchain4j</groupId>
38
+            <artifactId>langchain4j-core</artifactId>
39
+            <version>0.35.0</version>
40
+        </dependency>
41
+
42
+        <dependency>
43
+            <groupId>dev.langchain4j</groupId>
44
+            <artifactId>langchain4j</artifactId>
45
+            <version>0.35.0</version>
46
+        </dependency>
47
+
48
+        <dependency>
49
+            <groupId>dev.langchain4j</groupId>
50
+            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
51
+            <version>0.35.0</version>
52
+        </dependency>
53
+
54
+        <dependency>
55
+            <groupId>dev.langchain4j</groupId>
56
+            <artifactId>langchain4j-embeddings-bge-small-zh-v15</artifactId>
57
+            <version>0.35.0</version>
58
+        </dependency>
59
+
60
+        <dependency>
61
+            <groupId>org.noear</groupId>
62
+            <artifactId>solon-ai</artifactId>
63
+            <version>3.5.1</version>
64
+        </dependency>
65
+
66
+        <dependency>
67
+            <groupId>io.milvus</groupId>
68
+            <artifactId>milvus-sdk-java</artifactId>
69
+            <version>2.6.2</version>
70
+        </dependency>
71
+
36 72
     </dependencies>
37 73
 
38 74
 </project>

+ 67
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcAgent.java View File

@@ -0,0 +1,67 @@
1
+package com.ruoyi.llm.domain;
2
+
3
+import com.ruoyi.common.annotation.Excel;
4
+import com.ruoyi.common.core.domain.BaseEntity;
5
+import org.apache.commons.lang3.builder.ToStringBuilder;
6
+import org.apache.commons.lang3.builder.ToStringStyle;
7
+
8
+/**
9
+ * 智能体对象 cmc_agent
10
+ * 
11
+ * @author ruoyi
12
+ * @date 2025-07-17
13
+ */
14
+public class CmcAgent extends BaseEntity
15
+{
16
+    private static final long serialVersionUID = 1L;
17
+
18
+    /** 智能体id */
19
+    private Integer agentId;
20
+
21
+    /** 名称 */
22
+    @Excel(name = "名称")
23
+    private String agentName;
24
+
25
+    /** 描述 */
26
+    @Excel(name = "描述")
27
+    private String description;
28
+
29
+    public void setAgentId(Integer agentId) 
30
+    {
31
+        this.agentId = agentId;
32
+    }
33
+
34
+    public Integer getAgentId() 
35
+    {
36
+        return agentId;
37
+    }
38
+    public void setAgentName(String agentName) 
39
+    {
40
+        this.agentName = agentName;
41
+    }
42
+
43
+    public String getAgentName() 
44
+    {
45
+        return agentName;
46
+    }
47
+    public void setDescription(String description) 
48
+    {
49
+        this.description = description;
50
+    }
51
+
52
+    public String getDescription() 
53
+    {
54
+        return description;
55
+    }
56
+
57
+    @Override
58
+    public String toString() {
59
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
60
+            .append("agentId", getAgentId())
61
+            .append("agentName", getAgentName())
62
+            .append("description", getDescription())
63
+            .append("createBy", getCreateBy())
64
+            .append("createTime", getCreateTime())
65
+            .toString();
66
+    }
67
+}

+ 126
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcChat.java View File

@@ -0,0 +1,126 @@
1
+package com.ruoyi.llm.domain;
2
+
3
+import com.fasterxml.jackson.annotation.JsonFormat;
4
+import com.ruoyi.common.annotation.Excel;
5
+import com.ruoyi.common.core.domain.BaseEntity;
6
+import org.apache.commons.lang3.builder.ToStringBuilder;
7
+import org.apache.commons.lang3.builder.ToStringStyle;
8
+
9
+import java.util.Date;
10
+
11
+/**
12
+ * cmc聊天记录对象 cmc_chat
13
+ * 
14
+ * @author cmc
15
+ * @date 2025-04-08
16
+ */
17
+public class CmcChat extends BaseEntity
18
+{
19
+    private static final long serialVersionUID = 1L;
20
+
21
+    /** 聊天记录id */
22
+    private String chatId;
23
+
24
+    /** 聊天主题id */
25
+    @Excel(name = "聊天主题id")
26
+    private String topicId;
27
+
28
+    /** 用户id */
29
+    @Excel(name = "用户id")
30
+    private Long userId;
31
+
32
+    /** 输入 */
33
+    @Excel(name = "输入")
34
+    private String input;
35
+
36
+    /** 输入时间 */
37
+    @JsonFormat(pattern = "yyyy-MM-dd")
38
+    @Excel(name = "输入时间", width = 30, dateFormat = "yyyy-MM-dd")
39
+    private Date inputTime;
40
+
41
+    /** 输出 */
42
+    @Excel(name = "输出")
43
+    private String output;
44
+
45
+    /** 输出时间 */
46
+    @JsonFormat(pattern = "yyyy-MM-dd")
47
+    @Excel(name = "输出时间", width = 30, dateFormat = "yyyy-MM-dd")
48
+    private Date outputTime;
49
+
50
+    public void setChatId(String chatId) 
51
+    {
52
+        this.chatId = chatId;
53
+    }
54
+
55
+    public String getChatId() 
56
+    {
57
+        return chatId;
58
+    }
59
+    public void setTopicId(String topicId) 
60
+    {
61
+        this.topicId = topicId;
62
+    }
63
+
64
+    public String getTopicId() 
65
+    {
66
+        return topicId;
67
+    }
68
+    public void setUserId(Long userId) 
69
+    {
70
+        this.userId = userId;
71
+    }
72
+
73
+    public Long getUserId() 
74
+    {
75
+        return userId;
76
+    }
77
+    public void setInput(String input) 
78
+    {
79
+        this.input = input;
80
+    }
81
+
82
+    public String getInput() 
83
+    {
84
+        return input;
85
+    }
86
+    public void setInputTime(Date inputTime) 
87
+    {
88
+        this.inputTime = inputTime;
89
+    }
90
+
91
+    public Date getInputTime() 
92
+    {
93
+        return inputTime;
94
+    }
95
+    public void setOutput(String output) 
96
+    {
97
+        this.output = output;
98
+    }
99
+
100
+    public String getOutput() 
101
+    {
102
+        return output;
103
+    }
104
+    public void setOutputTime(Date outputTime) 
105
+    {
106
+        this.outputTime = outputTime;
107
+    }
108
+
109
+    public Date getOutputTime() 
110
+    {
111
+        return outputTime;
112
+    }
113
+
114
+    @Override
115
+    public String toString() {
116
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
117
+            .append("chatId", getChatId())
118
+            .append("topicId", getTopicId())
119
+            .append("userId", getUserId())
120
+            .append("input", getInput())
121
+            .append("inputTime", getInputTime())
122
+            .append("output", getOutput())
123
+            .append("outputTime", getOutputTime())
124
+            .toString();
125
+    }
126
+}

+ 65
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcDocument.java View File

@@ -0,0 +1,65 @@
1
+package com.ruoyi.llm.domain;
2
+
3
+import com.ruoyi.common.annotation.Excel;
4
+import com.ruoyi.common.core.domain.BaseEntity;
5
+import org.apache.commons.lang3.builder.ToStringBuilder;
6
+import org.apache.commons.lang3.builder.ToStringStyle;
7
+
8
+/**
9
+ * cmc聊天附件对象 cmc_document
10
+ * 
11
+ * @author cmc
12
+ * @date 2025-04-08
13
+ */
14
+public class CmcDocument extends BaseEntity
15
+{
16
+    private static final long serialVersionUID = 1L;
17
+
18
+    /** 附件id */
19
+    private String documentId;
20
+
21
+    /** 聊天记录id */
22
+    @Excel(name = "聊天记录id")
23
+    private String chatId;
24
+
25
+    /** 附件 */
26
+    @Excel(name = "附件")
27
+    private String path;
28
+
29
+    public void setDocumentId(String documentId) 
30
+    {
31
+        this.documentId = documentId;
32
+    }
33
+
34
+    public String getDocumentId() 
35
+    {
36
+        return documentId;
37
+    }
38
+    public void setChatId(String chatId) 
39
+    {
40
+        this.chatId = chatId;
41
+    }
42
+
43
+    public String getChatId() 
44
+    {
45
+        return chatId;
46
+    }
47
+    public void setPath(String path) 
48
+    {
49
+        this.path = path;
50
+    }
51
+
52
+    public String getPath() 
53
+    {
54
+        return path;
55
+    }
56
+
57
+    @Override
58
+    public String toString() {
59
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
60
+            .append("documentId", getDocumentId())
61
+            .append("chatId", getChatId())
62
+            .append("path", getPath())
63
+            .toString();
64
+    }
65
+}

+ 66
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/domain/CmcTopic.java View File

@@ -0,0 +1,66 @@
1
+package com.ruoyi.llm.domain;
2
+
3
+import com.ruoyi.common.annotation.Excel;
4
+import com.ruoyi.common.core.domain.BaseEntity;
5
+import org.apache.commons.lang3.builder.ToStringBuilder;
6
+import org.apache.commons.lang3.builder.ToStringStyle;
7
+
8
+/**
9
+ * cmc聊天主题对象 cmc_topic
10
+ * 
11
+ * @author cmc
12
+ * @date 2025-04-08
13
+ */
14
+public class CmcTopic extends BaseEntity
15
+{
16
+    private static final long serialVersionUID = 1L;
17
+
18
+    /** 聊天主题id */
19
+    private String topicId;
20
+
21
+    /** 智能体id */
22
+    @Excel(name = "智能体id")
23
+    private Integer agentId;
24
+
25
+    /** 主题 */
26
+    @Excel(name = "主题")
27
+    private String topic;
28
+
29
+    public void setTopicId(String topicId) 
30
+    {
31
+        this.topicId = topicId;
32
+    }
33
+
34
+    public String getTopicId() 
35
+    {
36
+        return topicId;
37
+    }
38
+    public void setTopic(String topic) 
39
+    {
40
+        this.topic = topic;
41
+    }
42
+
43
+    public String getTopic() 
44
+    {
45
+        return topic;
46
+    }
47
+    public void setAgentId(Integer agentId)
48
+    {
49
+        this.agentId = agentId;
50
+    }
51
+
52
+    public Integer getAgentId()
53
+    {
54
+        return agentId;
55
+    }
56
+
57
+    @Override
58
+    public String toString() {
59
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
60
+            .append("topicId", getTopicId())
61
+            .append("topic", getTopic())
62
+                .append("agentId", getAgentId())
63
+            .append("createTime", getCreateTime())
64
+            .toString();
65
+    }
66
+}

+ 62
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcAgentMapper.java View File

@@ -0,0 +1,62 @@
1
+package com.ruoyi.llm.mapper;
2
+
3
+import com.ruoyi.llm.domain.CmcAgent;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * 智能体Mapper接口
9
+ * 
10
+ * @author ruoyi
11
+ * @date 2025-07-17
12
+ */
13
+public interface CmcAgentMapper 
14
+{
15
+    /**
16
+     * 查询智能体
17
+     * 
18
+     * @param agentId 智能体主键
19
+     * @return 智能体
20
+     */
21
+    public CmcAgent selectCmcAgentByAgentId(Integer agentId);
22
+
23
+    /**
24
+     * 查询智能体列表
25
+     * 
26
+     * @param cmcAgent 智能体
27
+     * @return 智能体集合
28
+     */
29
+    public List<CmcAgent> selectCmcAgentList(CmcAgent cmcAgent);
30
+
31
+    /**
32
+     * 新增智能体
33
+     * 
34
+     * @param cmcAgent 智能体
35
+     * @return 结果
36
+     */
37
+    public int insertCmcAgent(CmcAgent cmcAgent);
38
+
39
+    /**
40
+     * 修改智能体
41
+     * 
42
+     * @param cmcAgent 智能体
43
+     * @return 结果
44
+     */
45
+    public int updateCmcAgent(CmcAgent cmcAgent);
46
+
47
+    /**
48
+     * 删除智能体
49
+     * 
50
+     * @param agentId 智能体主键
51
+     * @return 结果
52
+     */
53
+    public int deleteCmcAgentByAgentId(Integer agentId);
54
+
55
+    /**
56
+     * 批量删除智能体
57
+     * 
58
+     * @param agentIds 需要删除的数据主键集合
59
+     * @return 结果
60
+     */
61
+    public int deleteCmcAgentByAgentIds(Integer[] agentIds);
62
+}

+ 62
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcChatMapper.java View File

@@ -0,0 +1,62 @@
1
+package com.ruoyi.llm.mapper;
2
+
3
+import com.ruoyi.llm.domain.CmcChat;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * cmc聊天记录Mapper接口
9
+ * 
10
+ * @author cmc
11
+ * @date 2025-04-08
12
+ */
13
+public interface CmcChatMapper 
14
+{
15
+    /**
16
+     * 查询cmc聊天记录
17
+     * 
18
+     * @param chatId cmc聊天记录主键
19
+     * @return cmc聊天记录
20
+     */
21
+    public CmcChat selectCmcChatByChatId(String chatId);
22
+
23
+    /**
24
+     * 查询cmc聊天记录列表
25
+     * 
26
+     * @param cmcChat cmc聊天记录
27
+     * @return cmc聊天记录集合
28
+     */
29
+    public List<CmcChat> selectCmcChatList(CmcChat cmcChat);
30
+
31
+    /**
32
+     * 新增cmc聊天记录
33
+     * 
34
+     * @param cmcChat cmc聊天记录
35
+     * @return 结果
36
+     */
37
+    public int insertCmcChat(CmcChat cmcChat);
38
+
39
+    /**
40
+     * 修改cmc聊天记录
41
+     * 
42
+     * @param cmcChat cmc聊天记录
43
+     * @return 结果
44
+     */
45
+    public int updateCmcChat(CmcChat cmcChat);
46
+
47
+    /**
48
+     * 删除cmc聊天记录
49
+     * 
50
+     * @param chatId cmc聊天记录主键
51
+     * @return 结果
52
+     */
53
+    public int deleteCmcChatByChatId(String chatId);
54
+
55
+    /**
56
+     * 批量删除cmc聊天记录
57
+     * 
58
+     * @param chatIds 需要删除的数据主键集合
59
+     * @return 结果
60
+     */
61
+    public int deleteCmcChatByChatIds(String[] chatIds);
62
+}

+ 62
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcDocumentMapper.java View File

@@ -0,0 +1,62 @@
1
+package com.ruoyi.llm.mapper;
2
+
3
+import com.ruoyi.llm.domain.CmcDocument;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * cmc聊天附件Mapper接口
9
+ * 
10
+ * @author cmc
11
+ * @date 2025-04-08
12
+ */
13
+public interface CmcDocumentMapper 
14
+{
15
+    /**
16
+     * 查询cmc聊天附件
17
+     * 
18
+     * @param documentId cmc聊天附件主键
19
+     * @return cmc聊天附件
20
+     */
21
+    public CmcDocument selectCmcDocumentByDocumentId(String documentId);
22
+
23
+    /**
24
+     * 查询cmc聊天附件列表
25
+     * 
26
+     * @param cmcDocument cmc聊天附件
27
+     * @return cmc聊天附件集合
28
+     */
29
+    public List<CmcDocument> selectCmcDocumentList(CmcDocument cmcDocument);
30
+
31
+    /**
32
+     * 新增cmc聊天附件
33
+     * 
34
+     * @param cmcDocument cmc聊天附件
35
+     * @return 结果
36
+     */
37
+    public int insertCmcDocument(CmcDocument cmcDocument);
38
+
39
+    /**
40
+     * 修改cmc聊天附件
41
+     * 
42
+     * @param cmcDocument cmc聊天附件
43
+     * @return 结果
44
+     */
45
+    public int updateCmcDocument(CmcDocument cmcDocument);
46
+
47
+    /**
48
+     * 删除cmc聊天附件
49
+     * 
50
+     * @param documentId cmc聊天附件主键
51
+     * @return 结果
52
+     */
53
+    public int deleteCmcDocumentByDocumentId(String documentId);
54
+
55
+    /**
56
+     * 批量删除cmc聊天附件
57
+     * 
58
+     * @param documentIds 需要删除的数据主键集合
59
+     * @return 结果
60
+     */
61
+    public int deleteCmcDocumentByDocumentIds(String[] documentIds);
62
+}

+ 62
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/mapper/CmcTopicMapper.java View File

@@ -0,0 +1,62 @@
1
+package com.ruoyi.llm.mapper;
2
+
3
+import com.ruoyi.llm.domain.CmcTopic;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * cmc聊天主题Mapper接口
9
+ * 
10
+ * @author cmc
11
+ * @date 2025-04-08
12
+ */
13
+public interface CmcTopicMapper 
14
+{
15
+    /**
16
+     * 查询cmc聊天主题
17
+     * 
18
+     * @param topicId cmc聊天主题主键
19
+     * @return cmc聊天主题
20
+     */
21
+    public CmcTopic selectCmcTopicByTopicId(String topicId);
22
+
23
+    /**
24
+     * 查询cmc聊天主题列表
25
+     * 
26
+     * @param cmcTopic cmc聊天主题
27
+     * @return cmc聊天主题集合
28
+     */
29
+    public List<CmcTopic> selectCmcTopicList(CmcTopic cmcTopic);
30
+
31
+    /**
32
+     * 新增cmc聊天主题
33
+     * 
34
+     * @param cmcTopic cmc聊天主题
35
+     * @return 结果
36
+     */
37
+    public int insertCmcTopic(CmcTopic cmcTopic);
38
+
39
+    /**
40
+     * 修改cmc聊天主题
41
+     * 
42
+     * @param cmcTopic cmc聊天主题
43
+     * @return 结果
44
+     */
45
+    public int updateCmcTopic(CmcTopic cmcTopic);
46
+
47
+    /**
48
+     * 删除cmc聊天主题
49
+     * 
50
+     * @param topicId cmc聊天主题主键
51
+     * @return 结果
52
+     */
53
+    public int deleteCmcTopicByTopicId(String topicId);
54
+
55
+    /**
56
+     * 批量删除cmc聊天主题
57
+     * 
58
+     * @param topicIds 需要删除的数据主键集合
59
+     * @return 结果
60
+     */
61
+    public int deleteCmcTopicByTopicIds(String[] topicIds);
62
+}

+ 104
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcAgentService.java View File

@@ -0,0 +1,104 @@
1
+package com.ruoyi.llm.service;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.llm.domain.CmcAgent;
5
+import org.springframework.web.multipart.MultipartFile;
6
+
7
+import java.io.IOException;
8
+import java.util.List;
9
+
10
+/**
11
+ * 智能体Service接口
12
+ * 
13
+ * @author ruoyi
14
+ * @date 2025-07-17
15
+ */
16
+public interface ICmcAgentService 
17
+{
18
+    /**
19
+     * 查询智能体
20
+     * 
21
+     * @param agentId 智能体主键
22
+     * @return 智能体
23
+     */
24
+    public CmcAgent selectCmcAgentByAgentId(Integer agentId);
25
+
26
+    /**
27
+     * 查询智能体列表
28
+     * 
29
+     * @param cmcAgent 智能体
30
+     * @return 智能体集合
31
+     */
32
+    public List<CmcAgent> selectCmcAgentList(CmcAgent cmcAgent);
33
+
34
+    /**
35
+     * 获取开场白
36
+     *
37
+     * @param agentName 智能体名称
38
+     * @return 结果
39
+     */
40
+    public String getOpening(String agentName);
41
+
42
+    /**
43
+     * 上传单文件
44
+     *
45
+     * @param file 文件
46
+     * @return 结果
47
+     */
48
+    public JSONObject uploadDocument(MultipartFile file, String agentName) throws IOException;
49
+
50
+    /**
51
+     * 上传修改文件
52
+     *
53
+     * @param file 文件
54
+     * @return 结果
55
+     */
56
+    public JSONObject uploadModifyFile(MultipartFile file, String agentName) throws IOException;
57
+
58
+    /**
59
+     * 上传多文件
60
+     *
61
+     * @param fileList 文件
62
+     * @return 结果
63
+     */
64
+    public String uploadDocumentList(MultipartFile[] fileList, String agentName) throws IOException;
65
+
66
+    /**
67
+     * 获取进度
68
+     *
69
+     * @return 结果
70
+     */
71
+    public String getProcess();
72
+
73
+    /**
74
+     * 新增智能体
75
+     * 
76
+     * @param cmcAgent 智能体
77
+     * @return 结果
78
+     */
79
+    public int insertCmcAgent(CmcAgent cmcAgent);
80
+
81
+    /**
82
+     * 修改智能体
83
+     * 
84
+     * @param cmcAgent 智能体
85
+     * @return 结果
86
+     */
87
+    public int updateCmcAgent(CmcAgent cmcAgent);
88
+
89
+    /**
90
+     * 批量删除智能体
91
+     * 
92
+     * @param agentIds 需要删除的智能体主键集合
93
+     * @return 结果
94
+     */
95
+    public int deleteCmcAgentByAgentIds(Integer[] agentIds);
96
+
97
+    /**
98
+     * 删除智能体信息
99
+     * 
100
+     * @param agentId 智能体主键
101
+     * @return 结果
102
+     */
103
+    public int deleteCmcAgentByAgentId(Integer agentId);
104
+}

+ 62
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcChatService.java View File

@@ -0,0 +1,62 @@
1
+package com.ruoyi.llm.service;
2
+
3
+import com.ruoyi.llm.domain.CmcChat;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * cmc聊天记录Service接口
9
+ * 
10
+ * @author cmc
11
+ * @date 2025-04-08
12
+ */
13
+public interface ICmcChatService 
14
+{
15
+    /**
16
+     * 查询cmc聊天记录
17
+     * 
18
+     * @param chatId cmc聊天记录主键
19
+     * @return cmc聊天记录
20
+     */
21
+    public CmcChat selectCmcChatByChatId(String chatId);
22
+
23
+    /**
24
+     * 查询cmc聊天记录列表
25
+     * 
26
+     * @param cmcChat cmc聊天记录
27
+     * @return cmc聊天记录集合
28
+     */
29
+    public List<CmcChat> selectCmcChatList(CmcChat cmcChat);
30
+
31
+    /**
32
+     * 新增cmc聊天记录
33
+     * 
34
+     * @param cmcChat cmc聊天记录
35
+     * @return 结果
36
+     */
37
+    public int insertCmcChat(CmcChat cmcChat);
38
+
39
+    /**
40
+     * 修改cmc聊天记录
41
+     * 
42
+     * @param cmcChat cmc聊天记录
43
+     * @return 结果
44
+     */
45
+    public int updateCmcChat(CmcChat cmcChat);
46
+
47
+    /**
48
+     * 批量删除cmc聊天记录
49
+     * 
50
+     * @param chatIds 需要删除的cmc聊天记录主键集合
51
+     * @return 结果
52
+     */
53
+    public int deleteCmcChatByChatIds(String[] chatIds);
54
+
55
+    /**
56
+     * 删除cmc聊天记录信息
57
+     * 
58
+     * @param chatId cmc聊天记录主键
59
+     * @return 结果
60
+     */
61
+    public int deleteCmcChatByChatId(String chatId);
62
+}

+ 73
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcDocumentService.java View File

@@ -0,0 +1,73 @@
1
+package com.ruoyi.llm.service;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.llm.domain.CmcDocument;
5
+import org.springframework.web.multipart.MultipartFile;
6
+
7
+import java.io.IOException;
8
+import java.util.List;
9
+
10
+/**
11
+ * cmc聊天附件Service接口
12
+ * 
13
+ * @author cmc
14
+ * @date 2025-04-08
15
+ */
16
+public interface ICmcDocumentService 
17
+{
18
+    /**
19
+     * 查询cmc聊天附件
20
+     * 
21
+     * @param documentId cmc聊天附件主键
22
+     * @return cmc聊天附件
23
+     */
24
+    public CmcDocument selectCmcDocumentByDocumentId(String documentId);
25
+
26
+    /**
27
+     * 查询cmc聊天附件列表
28
+     * 
29
+     * @param cmcDocument cmc聊天附件
30
+     * @return cmc聊天附件集合
31
+     */
32
+    public List<CmcDocument> selectCmcDocumentList(CmcDocument cmcDocument);
33
+
34
+    /**
35
+     * 上传外部文件
36
+     *
37
+     * @param fileList 文件
38
+     * @return 结果
39
+     */
40
+    public JSONObject uploadDocument(MultipartFile[] fileList) throws IOException;
41
+
42
+    /**
43
+     * 新增cmc聊天附件
44
+     * 
45
+     * @param cmcDocument cmc聊天附件
46
+     * @return 结果
47
+     */
48
+    public int insertCmcDocument(CmcDocument cmcDocument);
49
+
50
+    /**
51
+     * 修改cmc聊天附件
52
+     * 
53
+     * @param cmcDocument cmc聊天附件
54
+     * @return 结果
55
+     */
56
+    public int updateCmcDocument(CmcDocument cmcDocument);
57
+
58
+    /**
59
+     * 批量删除cmc聊天附件
60
+     * 
61
+     * @param documentIds 需要删除的cmc聊天附件主键集合
62
+     * @return 结果
63
+     */
64
+    public int deleteCmcDocumentByDocumentIds(String[] documentIds);
65
+
66
+    /**
67
+     * 删除cmc聊天附件信息
68
+     * 
69
+     * @param documentId cmc聊天附件主键
70
+     * @return 结果
71
+     */
72
+    public int deleteCmcDocumentByDocumentId(String documentId);
73
+}

+ 62
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/ICmcTopicService.java View File

@@ -0,0 +1,62 @@
1
+package com.ruoyi.llm.service;
2
+
3
+import com.ruoyi.llm.domain.CmcTopic;
4
+
5
+import java.util.List;
6
+
7
+/**
8
+ * cmc聊天主题Service接口
9
+ * 
10
+ * @author cmc
11
+ * @date 2025-04-08
12
+ */
13
+public interface ICmcTopicService 
14
+{
15
+    /**
16
+     * 查询cmc聊天主题
17
+     * 
18
+     * @param topicId cmc聊天主题主键
19
+     * @return cmc聊天主题
20
+     */
21
+    public CmcTopic selectCmcTopicByTopicId(String topicId);
22
+
23
+    /**
24
+     * 查询cmc聊天主题列表
25
+     * 
26
+     * @param cmcTopic cmc聊天主题
27
+     * @return cmc聊天主题集合
28
+     */
29
+    public List<CmcTopic> selectCmcTopicList(CmcTopic cmcTopic);
30
+
31
+    /**
32
+     * 新增cmc聊天主题
33
+     * 
34
+     * @param cmcTopic cmc聊天主题
35
+     * @return 结果
36
+     */
37
+    public int insertCmcTopic(CmcTopic cmcTopic);
38
+
39
+    /**
40
+     * 修改cmc聊天主题
41
+     * 
42
+     * @param cmcTopic cmc聊天主题
43
+     * @return 结果
44
+     */
45
+    public int updateCmcTopic(CmcTopic cmcTopic);
46
+
47
+    /**
48
+     * 批量删除cmc聊天主题
49
+     * 
50
+     * @param topicIds 需要删除的cmc聊天主题主键集合
51
+     * @return 结果
52
+     */
53
+    public int deleteCmcTopicByTopicIds(String[] topicIds);
54
+
55
+    /**
56
+     * 删除cmc聊天主题信息
57
+     * 
58
+     * @param topicId cmc聊天主题主键
59
+     * @return 结果
60
+     */
61
+    public int deleteCmcTopicByTopicId(String topicId);
62
+}

+ 1222
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcAgentServiceImpl.java
File diff suppressed because it is too large
View File


+ 94
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcChatServiceImpl.java View File

@@ -0,0 +1,94 @@
1
+package com.ruoyi.llm.service.impl;
2
+
3
+import com.ruoyi.llm.domain.CmcChat;
4
+import com.ruoyi.llm.mapper.CmcChatMapper;
5
+import com.ruoyi.llm.service.ICmcChatService;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.stereotype.Service;
8
+
9
+import java.util.List;
10
+
11
+/**
12
+ * cmc聊天记录Service业务层处理
13
+ * 
14
+ * @author cmc
15
+ * @date 2025-04-08
16
+ */
17
+@Service
18
+public class CmcChatServiceImpl implements ICmcChatService 
19
+{
20
+    @Autowired
21
+    private CmcChatMapper cmcChatMapper;
22
+
23
+    /**
24
+     * 查询cmc聊天记录
25
+     * 
26
+     * @param chatId cmc聊天记录主键
27
+     * @return cmc聊天记录
28
+     */
29
+    @Override
30
+    public CmcChat selectCmcChatByChatId(String chatId)
31
+    {
32
+        return cmcChatMapper.selectCmcChatByChatId(chatId);
33
+    }
34
+
35
+    /**
36
+     * 查询cmc聊天记录列表
37
+     * 
38
+     * @param cmcChat cmc聊天记录
39
+     * @return cmc聊天记录
40
+     */
41
+    @Override
42
+    public List<CmcChat> selectCmcChatList(CmcChat cmcChat)
43
+    {
44
+        return cmcChatMapper.selectCmcChatList(cmcChat);
45
+    }
46
+
47
+    /**
48
+     * 新增cmc聊天记录
49
+     * 
50
+     * @param cmcChat cmc聊天记录
51
+     * @return 结果
52
+     */
53
+    @Override
54
+    public int insertCmcChat(CmcChat cmcChat)
55
+    {
56
+        return cmcChatMapper.insertCmcChat(cmcChat);
57
+    }
58
+
59
+    /**
60
+     * 修改cmc聊天记录
61
+     * 
62
+     * @param cmcChat cmc聊天记录
63
+     * @return 结果
64
+     */
65
+    @Override
66
+    public int updateCmcChat(CmcChat cmcChat)
67
+    {
68
+        return cmcChatMapper.updateCmcChat(cmcChat);
69
+    }
70
+
71
+    /**
72
+     * 批量删除cmc聊天记录
73
+     * 
74
+     * @param chatIds 需要删除的cmc聊天记录主键
75
+     * @return 结果
76
+     */
77
+    @Override
78
+    public int deleteCmcChatByChatIds(String[] chatIds)
79
+    {
80
+        return cmcChatMapper.deleteCmcChatByChatIds(chatIds);
81
+    }
82
+
83
+    /**
84
+     * 删除cmc聊天记录信息
85
+     * 
86
+     * @param chatId cmc聊天记录主键
87
+     * @return 结果
88
+     */
89
+    @Override
90
+    public int deleteCmcChatByChatId(String chatId)
91
+    {
92
+        return cmcChatMapper.deleteCmcChatByChatId(chatId);
93
+    }
94
+}

+ 129
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcDocumentServiceImpl.java View File

@@ -0,0 +1,129 @@
1
+package com.ruoyi.llm.service.impl;
2
+
3
+import com.alibaba.fastjson2.JSONObject;
4
+import com.ruoyi.common.config.RuoYiConfig;
5
+import com.ruoyi.common.utils.SnowFlake;
6
+import com.ruoyi.llm.domain.CmcDocument;
7
+import com.ruoyi.llm.mapper.CmcDocumentMapper;
8
+import com.ruoyi.llm.service.ICmcDocumentService;
9
+import org.springframework.beans.factory.annotation.Autowired;
10
+import org.springframework.stereotype.Service;
11
+import org.springframework.web.multipart.MultipartFile;
12
+
13
+import java.io.File;
14
+import java.io.IOException;
15
+import java.util.List;
16
+
17
+/**
18
+ * cmc聊天附件Service业务层处理
19
+ * 
20
+ * @author cmc
21
+ * @date 2025-04-08
22
+ */
23
+@Service
24
+public class CmcDocumentServiceImpl implements ICmcDocumentService 
25
+{
26
+    @Autowired
27
+    private CmcDocumentMapper cmcDocumentMapper;
28
+
29
+    /**
30
+     * 查询cmc聊天附件
31
+     * 
32
+     * @param documentId cmc聊天附件主键
33
+     * @return cmc聊天附件
34
+     */
35
+    @Override
36
+    public CmcDocument selectCmcDocumentByDocumentId(String documentId)
37
+    {
38
+        return cmcDocumentMapper.selectCmcDocumentByDocumentId(documentId);
39
+    }
40
+
41
+    /**
42
+     * 查询cmc聊天附件列表
43
+     * 
44
+     * @param cmcDocument cmc聊天附件
45
+     * @return cmc聊天附件
46
+     */
47
+    @Override
48
+    public List<CmcDocument> selectCmcDocumentList(CmcDocument cmcDocument)
49
+    {
50
+        return cmcDocumentMapper.selectCmcDocumentList(cmcDocument);
51
+    }
52
+
53
+    /**
54
+     * 新增cmc聊天附件
55
+     * 
56
+     * @param cmcDocument cmc聊天附件
57
+     * @return 结果
58
+     */
59
+    @Override
60
+    public int insertCmcDocument(CmcDocument cmcDocument)
61
+    {
62
+        return cmcDocumentMapper.insertCmcDocument(cmcDocument);
63
+    }
64
+
65
+    /**
66
+     * 上传外部文件
67
+     *
68
+     * @param fileList 文件
69
+     * @return 结果
70
+     */
71
+    @Override
72
+    public JSONObject uploadDocument(MultipartFile[] fileList) throws IOException {
73
+        String prefixPath = "";
74
+            prefixPath = "/upload/rag/document";
75
+        File profilePath = new File( RuoYiConfig.getProfile() + prefixPath);
76
+        if (!profilePath.exists())
77
+            profilePath.mkdirs();
78
+        String chatId = new SnowFlake().generateId();
79
+        JSONObject jsonObject = new JSONObject();
80
+        jsonObject.put("chatId", chatId);
81
+        for (MultipartFile file : fileList) {
82
+            File transferFile = new File(profilePath + "/" + file.getOriginalFilename());
83
+            if (!transferFile.exists())
84
+                file.transferTo(transferFile);
85
+            CmcDocument cmcDocument = new CmcDocument();
86
+            cmcDocument.setDocumentId(new SnowFlake().generateId());
87
+            cmcDocument.setChatId(chatId);
88
+            cmcDocument.setPath(prefixPath + "/" + file.getOriginalFilename());
89
+            cmcDocumentMapper.insertCmcDocument(cmcDocument);
90
+        }
91
+        return jsonObject;
92
+    }
93
+
94
+    /**
95
+     * 修改cmc聊天附件
96
+     * 
97
+     * @param cmcDocument cmc聊天附件
98
+     * @return 结果
99
+     */
100
+    @Override
101
+    public int updateCmcDocument(CmcDocument cmcDocument)
102
+    {
103
+        return cmcDocumentMapper.updateCmcDocument(cmcDocument);
104
+    }
105
+
106
+    /**
107
+     * 批量删除cmc聊天附件
108
+     * 
109
+     * @param documentIds 需要删除的cmc聊天附件主键
110
+     * @return 结果
111
+     */
112
+    @Override
113
+    public int deleteCmcDocumentByDocumentIds(String[] documentIds)
114
+    {
115
+        return cmcDocumentMapper.deleteCmcDocumentByDocumentIds(documentIds);
116
+    }
117
+
118
+    /**
119
+     * 删除cmc聊天附件信息
120
+     * 
121
+     * @param documentId cmc聊天附件主键
122
+     * @return 结果
123
+     */
124
+    @Override
125
+    public int deleteCmcDocumentByDocumentId(String documentId)
126
+    {
127
+        return cmcDocumentMapper.deleteCmcDocumentByDocumentId(documentId);
128
+    }
129
+}

+ 96
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/llm/service/impl/CmcTopicServiceImpl.java View File

@@ -0,0 +1,96 @@
1
+package com.ruoyi.llm.service.impl;
2
+
3
+import com.ruoyi.common.utils.DateUtils;
4
+import com.ruoyi.llm.domain.CmcTopic;
5
+import com.ruoyi.llm.mapper.CmcTopicMapper;
6
+import com.ruoyi.llm.service.ICmcTopicService;
7
+import org.springframework.beans.factory.annotation.Autowired;
8
+import org.springframework.stereotype.Service;
9
+
10
+import java.util.List;
11
+
12
+/**
13
+ * cmc聊天主题Service业务层处理
14
+ * 
15
+ * @author cmc
16
+ * @date 2025-04-08
17
+ */
18
+@Service
19
+public class CmcTopicServiceImpl implements ICmcTopicService 
20
+{
21
+    @Autowired
22
+    private CmcTopicMapper cmcTopicMapper;
23
+
24
+    /**
25
+     * 查询cmc聊天主题
26
+     * 
27
+     * @param topicId cmc聊天主题主键
28
+     * @return cmc聊天主题
29
+     */
30
+    @Override
31
+    public CmcTopic selectCmcTopicByTopicId(String topicId)
32
+    {
33
+        return cmcTopicMapper.selectCmcTopicByTopicId(topicId);
34
+    }
35
+
36
+    /**
37
+     * 查询cmc聊天主题列表
38
+     * 
39
+     * @param cmcTopic cmc聊天主题
40
+     * @return cmc聊天主题
41
+     */
42
+    @Override
43
+    public List<CmcTopic> selectCmcTopicList(CmcTopic cmcTopic)
44
+    {
45
+        return cmcTopicMapper.selectCmcTopicList(cmcTopic);
46
+    }
47
+
48
+    /**
49
+     * 新增cmc聊天主题
50
+     * 
51
+     * @param cmcTopic cmc聊天主题
52
+     * @return 结果
53
+     */
54
+    @Override
55
+    public int insertCmcTopic(CmcTopic cmcTopic)
56
+    {
57
+        cmcTopic.setCreateTime(DateUtils.getNowDate());
58
+        return cmcTopicMapper.insertCmcTopic(cmcTopic);
59
+    }
60
+
61
+    /**
62
+     * 修改cmc聊天主题
63
+     * 
64
+     * @param cmcTopic cmc聊天主题
65
+     * @return 结果
66
+     */
67
+    @Override
68
+    public int updateCmcTopic(CmcTopic cmcTopic)
69
+    {
70
+        return cmcTopicMapper.updateCmcTopic(cmcTopic);
71
+    }
72
+
73
+    /**
74
+     * 批量删除cmc聊天主题
75
+     * 
76
+     * @param topicIds 需要删除的cmc聊天主题主键
77
+     * @return 结果
78
+     */
79
+    @Override
80
+    public int deleteCmcTopicByTopicIds(String[] topicIds)
81
+    {
82
+        return cmcTopicMapper.deleteCmcTopicByTopicIds(topicIds);
83
+    }
84
+
85
+    /**
86
+     * 删除cmc聊天主题信息
87
+     * 
88
+     * @param topicId cmc聊天主题主键
89
+     * @return 结果
90
+     */
91
+    @Override
92
+    public int deleteCmcTopicByTopicId(String topicId)
93
+    {
94
+        return cmcTopicMapper.deleteCmcTopicByTopicId(topicId);
95
+    }
96
+}

+ 2
- 0
oa-back/ruoyi-system/src/main/java/com/ruoyi/system/domain/TreeSelectNew.java View File

@@ -58,6 +58,8 @@ public class TreeSelectNew implements Serializable
58 58
         SysUser dispatch = new SysUser();
59 59
         dispatch.setStatus("0");
60 60
         List<SysUser> dispatchList = userPostMapper.selectDispatch(dispatch);
61
+        dispatch.setStatus("3");
62
+        dispatchList.addAll(userPostMapper.selectDispatch(dispatch));
61 63
         if (dept.getDeptId() == 101L) {
62 64
             list.addAll(dsSecretaryList);
63 65
         }

+ 69
- 0
oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcAgentMapper.xml View File

@@ -0,0 +1,69 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper
3
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+<mapper namespace="com.ruoyi.llm.mapper.CmcAgentMapper">
6
+    
7
+    <resultMap type="CmcAgent" id="CmcAgentResult">
8
+        <result property="agentId"    column="agent_id"    />
9
+        <result property="agentName"    column="agent_name"    />
10
+        <result property="description"    column="description"    />
11
+        <result property="createBy"    column="create_by"    />
12
+        <result property="createTime"    column="create_time"    />
13
+    </resultMap>
14
+
15
+    <sql id="selectCmcAgentVo">
16
+        select agent_id, agent_name, description, create_by, create_time from cmc_agent
17
+    </sql>
18
+
19
+    <select id="selectCmcAgentList" parameterType="CmcAgent" resultMap="CmcAgentResult">
20
+        <include refid="selectCmcAgentVo"/>
21
+        <where>  
22
+            <if test="agentName != null  and agentName != ''"> and agent_name like concat('%', #{agentName}, '%')</if>
23
+            <if test="description != null  and description != ''"> and description = #{description}</if>
24
+        </where>
25
+    </select>
26
+    
27
+    <select id="selectCmcAgentByAgentId" parameterType="Integer" resultMap="CmcAgentResult">
28
+        <include refid="selectCmcAgentVo"/>
29
+        where agent_id = #{agentId}
30
+    </select>
31
+
32
+    <insert id="insertCmcAgent" parameterType="CmcAgent" useGeneratedKeys="true" keyProperty="agentId">
33
+        insert into cmc_agent
34
+        <trim prefix="(" suffix=")" suffixOverrides=",">
35
+            <if test="agentName != null">agent_name,</if>
36
+            <if test="description != null">description,</if>
37
+            <if test="createBy != null">create_by,</if>
38
+            <if test="createTime != null">create_time,</if>
39
+         </trim>
40
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
41
+            <if test="agentName != null">#{agentName},</if>
42
+            <if test="description != null">#{description},</if>
43
+            <if test="createBy != null">#{createBy},</if>
44
+            <if test="createTime != null">#{createTime},</if>
45
+         </trim>
46
+    </insert>
47
+
48
+    <update id="updateCmcAgent" parameterType="CmcAgent">
49
+        update cmc_agent
50
+        <trim prefix="SET" suffixOverrides=",">
51
+            <if test="agentName != null">agent_name = #{agentName},</if>
52
+            <if test="description != null">description = #{description},</if>
53
+            <if test="createBy != null">create_by = #{createBy},</if>
54
+            <if test="createTime != null">create_time = #{createTime},</if>
55
+        </trim>
56
+        where agent_id = #{agentId}
57
+    </update>
58
+
59
+    <delete id="deleteCmcAgentByAgentId" parameterType="Integer">
60
+        delete from cmc_agent where agent_id = #{agentId}
61
+    </delete>
62
+
63
+    <delete id="deleteCmcAgentByAgentIds" parameterType="String">
64
+        delete from cmc_agent where agent_id in 
65
+        <foreach item="agentId" collection="array" open="(" separator="," close=")">
66
+            #{agentId}
67
+        </foreach>
68
+    </delete>
69
+</mapper>

+ 83
- 0
oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcChatMapper.xml View File

@@ -0,0 +1,83 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper
3
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+<mapper namespace="com.ruoyi.llm.mapper.CmcChatMapper">
6
+    
7
+    <resultMap type="CmcChat" id="CmcChatResult">
8
+        <result property="chatId"    column="chat_id"    />
9
+        <result property="topicId"    column="topic_id"    />
10
+        <result property="userId"    column="user_id"    />
11
+        <result property="input"    column="input"    />
12
+        <result property="inputTime"    column="input_time"    />
13
+        <result property="output"    column="output"    />
14
+        <result property="outputTime"    column="output_time"    />
15
+    </resultMap>
16
+
17
+    <sql id="selectCmcChatVo">
18
+        select chat_id, topic_id, user_id, input, input_time, output, output_time from cmc_chat
19
+    </sql>
20
+
21
+    <select id="selectCmcChatList" parameterType="CmcChat" resultMap="CmcChatResult">
22
+        <include refid="selectCmcChatVo"/>
23
+        <where>  
24
+            <if test="topicId != null  and topicId != ''"> and topic_id = #{topicId}</if>
25
+            <if test="userId != null "> and user_id = #{userId}</if>
26
+            <if test="input != null  and input != ''"> and input = #{input}</if>
27
+            <if test="inputTime != null "> and input_time = #{inputTime}</if>
28
+            <if test="output != null  and output != ''"> and output = #{output}</if>
29
+            <if test="outputTime != null "> and output_time = #{outputTime}</if>
30
+        </where>
31
+    </select>
32
+    
33
+    <select id="selectCmcChatByChatId" parameterType="String" resultMap="CmcChatResult">
34
+        <include refid="selectCmcChatVo"/>
35
+        where chat_id = #{chatId}
36
+    </select>
37
+
38
+    <insert id="insertCmcChat" parameterType="CmcChat">
39
+        insert into cmc_chat
40
+        <trim prefix="(" suffix=")" suffixOverrides=",">
41
+            <if test="chatId != null">chat_id,</if>
42
+            <if test="topicId != null">topic_id,</if>
43
+            <if test="userId != null">user_id,</if>
44
+            <if test="input != null">input,</if>
45
+            <if test="inputTime != null">input_time,</if>
46
+            <if test="output != null">output,</if>
47
+            <if test="outputTime != null">output_time,</if>
48
+         </trim>
49
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
50
+            <if test="chatId != null">#{chatId},</if>
51
+            <if test="topicId != null">#{topicId},</if>
52
+            <if test="userId != null">#{userId},</if>
53
+            <if test="input != null">#{input},</if>
54
+            <if test="inputTime != null">#{inputTime},</if>
55
+            <if test="output != null">#{output},</if>
56
+            <if test="outputTime != null">#{outputTime},</if>
57
+         </trim>
58
+    </insert>
59
+
60
+    <update id="updateCmcChat" parameterType="CmcChat">
61
+        update cmc_chat
62
+        <trim prefix="SET" suffixOverrides=",">
63
+            <if test="topicId != null">topic_id = #{topicId},</if>
64
+            <if test="userId != null">user_id = #{userId},</if>
65
+            <if test="input != null">input = #{input},</if>
66
+            <if test="inputTime != null">input_time = #{inputTime},</if>
67
+            <if test="output != null">output = #{output},</if>
68
+            <if test="outputTime != null">output_time = #{outputTime},</if>
69
+        </trim>
70
+        where chat_id = #{chatId}
71
+    </update>
72
+
73
+    <delete id="deleteCmcChatByChatId" parameterType="String">
74
+        delete from cmc_chat where chat_id = #{chatId}
75
+    </delete>
76
+
77
+    <delete id="deleteCmcChatByChatIds" parameterType="String">
78
+        delete from cmc_chat where chat_id in 
79
+        <foreach item="chatId" collection="array" open="(" separator="," close=")">
80
+            #{chatId}
81
+        </foreach>
82
+    </delete>
83
+</mapper>

+ 63
- 0
oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcDocumentMapper.xml View File

@@ -0,0 +1,63 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper
3
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+<mapper namespace="com.ruoyi.llm.mapper.CmcDocumentMapper">
6
+    
7
+    <resultMap type="CmcDocument" id="CmcDocumentResult">
8
+        <result property="documentId"    column="document_id"    />
9
+        <result property="chatId"    column="chat_id"    />
10
+        <result property="path"    column="path"    />
11
+    </resultMap>
12
+
13
+    <sql id="selectCmcDocumentVo">
14
+        select document_id, chat_id, path from cmc_document
15
+    </sql>
16
+
17
+    <select id="selectCmcDocumentList" parameterType="CmcDocument" resultMap="CmcDocumentResult">
18
+        <include refid="selectCmcDocumentVo"/>
19
+        <where>  
20
+            <if test="chatId != null  and chatId != ''"> and chat_id = #{chatId}</if>
21
+            <if test="path != null  and path != ''"> and path = #{path}</if>
22
+        </where>
23
+    </select>
24
+    
25
+    <select id="selectCmcDocumentByDocumentId" parameterType="String" resultMap="CmcDocumentResult">
26
+        <include refid="selectCmcDocumentVo"/>
27
+        where document_id = #{documentId}
28
+    </select>
29
+
30
+    <insert id="insertCmcDocument" parameterType="CmcDocument">
31
+        insert into cmc_document
32
+        <trim prefix="(" suffix=")" suffixOverrides=",">
33
+            <if test="documentId != null">document_id,</if>
34
+            <if test="chatId != null">chat_id,</if>
35
+            <if test="path != null">path,</if>
36
+         </trim>
37
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
38
+            <if test="documentId != null">#{documentId},</if>
39
+            <if test="chatId != null">#{chatId},</if>
40
+            <if test="path != null">#{path},</if>
41
+         </trim>
42
+    </insert>
43
+
44
+    <update id="updateCmcDocument" parameterType="CmcDocument">
45
+        update cmc_document
46
+        <trim prefix="SET" suffixOverrides=",">
47
+            <if test="chatId != null">chat_id = #{chatId},</if>
48
+            <if test="path != null">path = #{path},</if>
49
+        </trim>
50
+        where document_id = #{documentId}
51
+    </update>
52
+
53
+    <delete id="deleteCmcDocumentByDocumentId" parameterType="String">
54
+        delete from cmc_document where document_id = #{documentId}
55
+    </delete>
56
+
57
+    <delete id="deleteCmcDocumentByDocumentIds" parameterType="String">
58
+        delete from cmc_document where document_id in 
59
+        <foreach item="documentId" collection="array" open="(" separator="," close=")">
60
+            #{documentId}
61
+        </foreach>
62
+    </delete>
63
+</mapper>

+ 68
- 0
oa-back/ruoyi-system/src/main/resources/mapper/llm/CmcTopicMapper.xml View File

@@ -0,0 +1,68 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<!DOCTYPE mapper
3
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5
+<mapper namespace="com.ruoyi.llm.mapper.CmcTopicMapper">
6
+    
7
+    <resultMap type="CmcTopic" id="CmcTopicResult">
8
+        <result property="topicId"    column="topic_id"    />
9
+        <result property="topic"    column="topic"    />
10
+        <result property="agentId"    column="agent_id"    />
11
+        <result property="createTime"    column="create_time"    />
12
+    </resultMap>
13
+
14
+    <sql id="selectCmcTopicVo">
15
+        select topic_id, topic, agent_id, create_time from cmc_topic
16
+    </sql>
17
+
18
+    <select id="selectCmcTopicList" parameterType="CmcTopic" resultMap="CmcTopicResult">
19
+        <include refid="selectCmcTopicVo"/>
20
+        <where>  
21
+            <if test="topic != null  and topic != ''"> and topic = #{topic}</if>
22
+            <if test="agentId != null "> and agent_id = #{agentId}</if>
23
+        </where>
24
+        order by create_time desc
25
+    </select>
26
+    
27
+    <select id="selectCmcTopicByTopicId" parameterType="String" resultMap="CmcTopicResult">
28
+        <include refid="selectCmcTopicVo"/>
29
+        where topic_id = #{topicId}
30
+    </select>
31
+
32
+    <insert id="insertCmcTopic" parameterType="CmcTopic">
33
+        insert into cmc_topic
34
+        <trim prefix="(" suffix=")" suffixOverrides=",">
35
+            <if test="topicId != null">topic_id,</if>
36
+            <if test="topic != null">topic,</if>
37
+            <if test="agentId != null">agent_id,</if>
38
+            <if test="createTime != null">create_time,</if>
39
+         </trim>
40
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
41
+            <if test="topicId != null">#{topicId},</if>
42
+            <if test="topic != null">#{topic},</if>
43
+            <if test="agentId != null">#{agentId},</if>
44
+            <if test="createTime != null">#{createTime},</if>
45
+         </trim>
46
+    </insert>
47
+
48
+    <update id="updateCmcTopic" parameterType="CmcTopic">
49
+        update cmc_topic
50
+        <trim prefix="SET" suffixOverrides=",">
51
+            <if test="topic != null">topic = #{topic},</if>
52
+            <if test="agentId != null">agent_id = #{agentId},</if>
53
+            <if test="createTime != null">create_time = #{createTime},</if>
54
+        </trim>
55
+        where topic_id = #{topicId}
56
+    </update>
57
+
58
+    <delete id="deleteCmcTopicByTopicId" parameterType="String">
59
+        delete from cmc_topic where topic_id = #{topicId}
60
+    </delete>
61
+
62
+    <delete id="deleteCmcTopicByTopicIds" parameterType="String">
63
+        delete from cmc_topic where topic_id in 
64
+        <foreach item="topicId" collection="array" open="(" separator="," close=")">
65
+            #{topicId}
66
+        </foreach>
67
+    </delete>
68
+</mapper>

+ 114
- 0
oa-ui/src/api/llm/agent.js View File

@@ -0,0 +1,114 @@
1
+/*
2
+ * @Author: wrh
3
+ * @Date: 2025-07-17 18:06:24
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-09-02 16:43:25
6
+ */
7
+import request from '@/utils/request'
8
+
9
+// 查询智能体列表
10
+export function listAgent(query) {
11
+  return request({
12
+    url: '/llm/agent/list',
13
+    method: 'get',
14
+    params: query
15
+  })
16
+}
17
+
18
+// 查询智能体详细
19
+export function getAgent(agentId) {
20
+  return request({
21
+    url: '/llm/agent/' + agentId,
22
+    method: 'get'
23
+  })
24
+}
25
+
26
+// 新增智能体
27
+export function addAgent(data) {
28
+  return request({
29
+    url: '/llm/agent',
30
+    method: 'post',
31
+    data: data
32
+  })
33
+}
34
+
35
+// 修改智能体
36
+export function updateAgent(data) {
37
+  return request({
38
+    url: '/llm/agent',
39
+    method: 'put',
40
+    data: data
41
+  })
42
+}
43
+
44
+// 删除智能体
45
+export function delAgent(agentId) {
46
+  return request({
47
+    url: '/llm/agent/' + agentId,
48
+    method: 'delete'
49
+  })
50
+}
51
+
52
+// 上传单文件
53
+export function uploadFile(file, agentName) {
54
+  const formData = new FormData()
55
+  formData.append('file', file)
56
+  formData.append('agentName', agentName)
57
+  return request({
58
+    url: '/llm/agent/upload',
59
+    method: 'post',
60
+    data: formData,
61
+    headers: {
62
+      'Content-Type': 'multipart/form-data'
63
+    }
64
+  })
65
+}
66
+
67
+// 上传单文件
68
+export function uploadModifyFile(file, agentName) {
69
+  const formData = new FormData()
70
+  formData.append('file', file)
71
+  formData.append('agentName', agentName)
72
+  return request({
73
+    url: '/llm/agent/modifyFile',
74
+    method: 'post',
75
+    data: formData,
76
+    headers: {
77
+      'Content-Type': 'multipart/form-data'
78
+    }
79
+  })
80
+}
81
+
82
+// 上传多文件
83
+export function uploadFileList(fileList, agentName) {
84
+  const formData = new FormData()
85
+  for (let f of fileList) {
86
+    formData.append('fileList', f)
87
+  }
88
+  formData.append('agentName', agentName)
89
+  return request({
90
+    url: '/llm/agent/uploadList',
91
+    method: 'post',
92
+    data: formData,
93
+    headers: {
94
+      'Content-Type': 'multipart/form-data'
95
+    }
96
+  })
97
+}
98
+
99
+// 获取上传进度
100
+export function getProcessValue() {
101
+  return request({
102
+    url: '/llm/agent/getProcess',
103
+    method: 'get'
104
+  })
105
+}
106
+
107
+//获取开场白, agentName:智能体名称
108
+export function opening(agentName) {
109
+  return request({
110
+    url: '/llm/agent/opening',
111
+    method: 'get',
112
+    params: { agentName }
113
+  })
114
+}

+ 50
- 0
oa-ui/src/api/llm/chat.js View File

@@ -0,0 +1,50 @@
1
+/*
2
+ * @Author: wrh
3
+ * @Date: 2025-04-08 14:23:04
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-04-08 14:36:25
6
+ */
7
+import request from '@/utils/request'
8
+
9
+// 查询cmc聊天记录列表
10
+export function listChat(query) {
11
+  return request({
12
+    url: '/llm/chat/list',
13
+    method: 'get',
14
+    params: query
15
+  })
16
+}
17
+
18
+// 查询cmc聊天记录详细
19
+export function getChat(chatId) {
20
+  return request({
21
+    url: '/llm/chat/' + chatId,
22
+    method: 'get'
23
+  })
24
+}
25
+
26
+// 新增cmc聊天记录
27
+export function addChat(data) {
28
+  return request({
29
+    url: '/llm/chat',
30
+    method: 'post',
31
+    data: data
32
+  })
33
+}
34
+
35
+// 修改cmc聊天记录
36
+export function updateChat(data) {
37
+  return request({
38
+    url: '/llm/chat',
39
+    method: 'put',
40
+    data: data
41
+  })
42
+}
43
+
44
+// 删除cmc聊天记录
45
+export function delChat(chatId) {
46
+  return request({
47
+    url: '/llm/chat/' + chatId,
48
+    method: 'delete'
49
+  })
50
+}

+ 67
- 0
oa-ui/src/api/llm/document.js View File

@@ -0,0 +1,67 @@
1
+/*
2
+ * @Author: ysh
3
+ * @Date: 2025-06-13 10:35:45
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-07-29 17:48:55
6
+ */
7
+import request from '@/utils/request'
8
+
9
+// 查询cmc聊天附件列表
10
+export function listDocument(query) {
11
+  return request({
12
+    url: '/llm/document/list',
13
+    method: 'get',
14
+    params: query
15
+  })
16
+}
17
+
18
+// 查询cmc聊天附件详细
19
+export function getDocument(documentId) {
20
+  return request({
21
+    url: '/llm/document/' + documentId,
22
+    method: 'get'
23
+  })
24
+}
25
+
26
+// 新增cmc聊天附件
27
+export function addDocument(data) {
28
+  return request({
29
+    url: '/llm/document',
30
+    method: 'post',
31
+    data: data
32
+  })
33
+}
34
+
35
+// 修改cmc聊天附件
36
+export function updateDocument(data) {
37
+  return request({
38
+    url: '/llm/document',
39
+    method: 'put',
40
+    data: data
41
+  })
42
+}
43
+
44
+// 删除cmc聊天附件
45
+export function delDocument(documentId) {
46
+  return request({
47
+    url: '/llm/document/' + documentId,
48
+    method: 'delete'
49
+  })
50
+}
51
+
52
+
53
+// 上传聊天外部文件
54
+export function uploadDocument(fileList) {
55
+  const formData = new FormData()
56
+  for (let f of fileList) {
57
+    formData.append('fileList', f)
58
+  }
59
+  return request({
60
+    url: '/llm/document/upload',
61
+    method: 'post',
62
+    data: formData,
63
+    headers: {
64
+      'Content-Type': 'multipart/form-data'
65
+    }
66
+  })
67
+}

+ 94
- 0
oa-ui/src/api/llm/knowLedge.js View File

@@ -0,0 +1,94 @@
1
+/*
2
+ * @Author: ysh
3
+ * @Date: 2025-06-30 09:56:10
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-07-18 15:40:00
6
+ */
7
+import request from '@/utils/request'
8
+
9
+// 查询知识库列表
10
+export function listKnowledge() {
11
+  return request({
12
+    url: '/llm/knowledge/list',
13
+    method: 'get',
14
+  })
15
+}
16
+
17
+// 获取上传进度
18
+export function getProcessValue() {
19
+  return request({
20
+    url: '/llm/knowledge/getProcess',
21
+    method: 'get'
22
+  })
23
+}
24
+
25
+// 根据collectionName查询知识库列表
26
+export function listKnowLedgeByCollectionName(collectionName) {
27
+  return request({
28
+    url: '/llm/knowledge/listByName',
29
+    method: 'get',
30
+    params: collectionName
31
+  })
32
+}
33
+
34
+// 新增知识库
35
+export function addKnowledge(collectionName, description) {
36
+  return request({
37
+    url: '/llm/knowledge/create',
38
+    method: 'post',
39
+    params: { collectionName, description }
40
+  })
41
+}
42
+
43
+// 修改知识库
44
+export function updateKnowledge(collectionName, newCollectionName) {
45
+  return request({
46
+    url: '/llm/knowledge/modify',
47
+    method: 'post',
48
+    params: { collectionName, newCollectionName }
49
+  })
50
+}
51
+
52
+// 删除知识库
53
+export function delKnowledge(collectionName) {
54
+  return request({
55
+    url: '/llm/knowledge/remove',
56
+    method: 'delete',
57
+    params: { collectionName }
58
+  })
59
+}
60
+
61
+// 插入知识库文件
62
+export function insertKnowledgeFile(fileList, collectionName) {
63
+  const formData = new FormData()
64
+  for(let f of fileList){
65
+    formData.append('fileList', f)
66
+  }
67
+  formData.append('collectionName', collectionName)
68
+  return request({
69
+    url: '/llm/knowledge/insertDocument',
70
+    method: 'post',
71
+    data: formData,
72
+    headers: {
73
+      'Content-Type': 'multipart/form-data'
74
+    }
75
+  })
76
+}
77
+
78
+// 查看知识库文件
79
+export function listKnowledgeDocument(collectionName, fileType) {
80
+  return request({
81
+    url: '/llm/knowledge/listDocument',
82
+    method: 'get',
83
+    params: { collectionName, fileType }
84
+  })
85
+}
86
+
87
+// 删除知识库文件
88
+export function deleteKnowledgeFile(fileName,collectionName) {
89
+  return request({
90
+    url: '/llm/knowledge/removeDocument',
91
+    method: 'delete',
92
+    params: { fileName, collectionName }
93
+  })
94
+}

+ 16
- 0
oa-ui/src/api/llm/mcp.js View File

@@ -0,0 +1,16 @@
1
+/*
2
+ * @Author: ysh
3
+ * @Date: 2025-07-25 14:52:22
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-07-28 09:48:43
6
+ */
7
+import request from '@/utils/request'
8
+
9
+// 同步回答
10
+export function answer(query) {
11
+  return request({
12
+    url: '/llm/mcp/answer',
13
+    method: 'get',
14
+    params: query
15
+  })
16
+}

+ 189
- 0
oa-ui/src/api/llm/rag.js View File

@@ -0,0 +1,189 @@
1
+/*
2
+ * @Author: ysh
3
+ * @Date: 2025-07-08 16:43:22
4
+ * @LastEditors: Please set LastEditors
5
+ * @LastEditTime: 2025-07-16 15:49:45
6
+ */
7
+import request from '@/utils/request'
8
+import { getToken } from '@/utils/auth'
9
+
10
+// 查询cmc聊天记录列表
11
+export function getAnswer(question, collectionName) {
12
+  return request({
13
+    url: '/llm/rag/answer',
14
+    method: 'get',
15
+    params: { question, collectionName }
16
+  })
17
+}
18
+
19
+// 查询上下文引用文件
20
+export function getContextFile(question, collectionName) {
21
+  return request({
22
+    url: '/llm/rag/context',
23
+    method: 'get',
24
+    params: { question, collectionName }
25
+  })
26
+}
27
+
28
+// 流式回答API - 使用fetch API处理流式响应
29
+export function getAnswerStream(question, collectionName, onMessage, onError, onComplete) {
30
+  const baseURL = process.env.VUE_APP_BASE_API
31
+  const url = `${baseURL}/llm/rag/answer?question=${encodeURIComponent(question)}&collectionName=${encodeURIComponent(collectionName)}`
32
+
33
+  const controller = new AbortController()
34
+
35
+  fetch(url, {
36
+    method: 'GET',
37
+    headers: {
38
+      'Authorization': 'Bearer ' + getToken(),
39
+      'Accept': 'application/json, text/event-stream',
40
+      'Cache-Control': 'no-cache'
41
+    },
42
+    signal: controller.signal
43
+  }).then(response => {
44
+    if (!response.ok) {
45
+      throw new Error(`HTTP error! status: ${response.status}`)
46
+    }
47
+
48
+    const reader = response.body.getReader()
49
+    const decoder = new TextDecoder()
50
+    let buffer = ''
51
+
52
+    function readStream() {
53
+      return reader.read().then(({ done, value }) => {
54
+        if (done) {
55
+          console.log('=== 流式读取完成 ===')
56
+          // 处理缓冲区中剩余的数据
57
+          if (buffer.trim()) {
58
+            console.log('=== 处理剩余缓冲区数据 ===', buffer)
59
+            const lines = buffer.split(/\r?\n/)
60
+            lines.forEach(line => {
61
+              line = line.trim()
62
+              if (!line || line.startsWith(':')) return
63
+
64
+              console.log('处理剩余数据行:', line)
65
+
66
+              // 尝试提取JSON数据
67
+              let jsonData = null
68
+
69
+              if (line.startsWith('data: ')) {
70
+                try {
71
+                  jsonData = JSON.parse(line.slice(6))
72
+                  console.log('解析的剩余SSE数据:', jsonData)
73
+                } catch (error) {
74
+                  console.error('解析剩余SSE数据失败:', error, line)
75
+                }
76
+              } else if (line.startsWith('data:')) {
77
+                try {
78
+                  jsonData = JSON.parse(line.slice(5))
79
+                  console.log('解析的剩余SSE数据(无空格):', jsonData)
80
+                } catch (error) {
81
+                  console.error('解析剩余SSE数据失败(无空格):', error, line)
82
+                }
83
+              } else {
84
+                try {
85
+                  jsonData = JSON.parse(line)
86
+                  console.log('解析的剩余JSON数据:', jsonData)
87
+                } catch (error) {
88
+                  console.error('解析剩余JSON数据失败:', error, line)
89
+                }
90
+              }
91
+
92
+              // 处理解析成功的数据
93
+              if (jsonData) {
94
+                console.log('=== 解析成功的剩余数据 ===', jsonData)
95
+
96
+                if (jsonData.resultContent) {
97
+                  console.log('=== 准备发送剩余resultContent ===', jsonData.resultContent)
98
+                  onMessage(jsonData.resultContent)
99
+                } else if (typeof jsonData === 'string') {
100
+                  console.log('=== 准备发送剩余字符串 ===', jsonData)
101
+                  onMessage(jsonData)
102
+                } else {
103
+                  console.log('=== 剩余数据格式不匹配,跳过content字段 ===', jsonData)
104
+                }
105
+              }
106
+            })
107
+          }
108
+
109
+          onComplete()
110
+          return
111
+        }
112
+
113
+        const chunk = decoder.decode(value, { stream: true })
114
+        console.log('接收到原始数据块:', chunk)
115
+        buffer += chunk
116
+
117
+        // 处理可能包含\r\n的情况
118
+        const lines = buffer.split(/\r?\n/)
119
+
120
+        // 保留最后一行,因为它可能不完整
121
+        buffer = lines.pop() || ''
122
+
123
+        console.log('处理的行数:', lines.length)
124
+        lines.forEach(line => {
125
+          line = line.trim()
126
+          if (!line || line.startsWith(':')) return
127
+
128
+          console.log('处理数据行:', line)
129
+
130
+          // 尝试提取JSON数据
131
+          let jsonData = null
132
+
133
+          if (line.startsWith('data: ')) {
134
+            try {
135
+              jsonData = JSON.parse(line.slice(6))
136
+              console.log('解析的SSE数据:', jsonData)
137
+            } catch (error) {
138
+              console.error('解析SSE数据失败:', error, line)
139
+            }
140
+          } else if (line.startsWith('data:')) {
141
+            try {
142
+              jsonData = JSON.parse(line.slice(5))
143
+              console.log('解析的SSE数据(无空格):', jsonData)
144
+            } catch (error) {
145
+              console.error('解析SSE数据失败(无空格):', error, line)
146
+            }
147
+          } else {
148
+            try {
149
+              jsonData = JSON.parse(line)
150
+              console.log('解析的JSON数据:', jsonData)
151
+            } catch (error) {
152
+              console.error('解析JSON数据失败:', error, line)
153
+            }
154
+          }
155
+
156
+          // 处理解析成功的数据
157
+          if (jsonData) {
158
+            console.log('=== 解析成功的数据 ===', jsonData)
159
+
160
+            if (jsonData.resultContent) {
161
+              console.log('=== 准备发送resultContent ===', jsonData.resultContent)
162
+              onMessage(jsonData.resultContent)
163
+            } else if (typeof jsonData === 'string') {
164
+              console.log('=== 准备发送字符串 ===', jsonData)
165
+              onMessage(jsonData)
166
+            } else {
167
+              console.log('=== 数据格式不匹配,跳过content字段 ===', jsonData)
168
+            }
169
+          }
170
+        })
171
+
172
+        return readStream()
173
+      })
174
+    }
175
+
176
+    return readStream()
177
+  })
178
+    .catch(error => {
179
+      if (error.name === 'AbortError') {
180
+        console.log('请求被取消')
181
+        return
182
+      }
183
+      console.error('流式请求错误:', error)
184
+      onError(new Error('网络连接失败,请检查网络连接后重试'))
185
+    })
186
+
187
+  // 返回controller以便外部可以取消请求
188
+  return controller
189
+}

+ 25
- 0
oa-ui/src/api/llm/session.js View File

@@ -0,0 +1,25 @@
1
+/*
2
+ * @Author: wrh
3
+ * @Date: 2025-04-08 14:23:04
4
+ * @LastEditors: Please set LastEditors
5
+ * @LastEditTime: 2025-07-22 15:42:04
6
+ */
7
+import request from '@/utils/request'
8
+
9
+// 查询cmc聊天记录详细
10
+export function getAnswer(question) {
11
+  return request({
12
+    url: '/llm/session/answer',
13
+    method: 'get',
14
+    params: question
15
+  })
16
+}
17
+
18
+// 查询cmc聊天记录详细
19
+export function getAnswerWithDocument(question) {
20
+  return request({
21
+    url: '/llm/session/answerWithDocument',
22
+    method: 'get',
23
+    params: question
24
+  })
25
+}

+ 44
- 0
oa-ui/src/api/llm/topic.js View File

@@ -0,0 +1,44 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询cmc聊天主题列表
4
+export function listTopic(query) {
5
+  return request({
6
+    url: '/llm/topic/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询cmc聊天主题详细
13
+export function getTopic(topicId) {
14
+  return request({
15
+    url: '/llm/topic/' + topicId,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增cmc聊天主题
21
+export function addTopic(data) {
22
+  return request({
23
+    url: '/llm/topic',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改cmc聊天主题
30
+export function updateTopic(data) {
31
+  return request({
32
+    url: '/llm/topic',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除cmc聊天主题
39
+export function delTopic(topicId) {
40
+  return request({
41
+    url: '/llm/topic/' + topicId,
42
+    method: 'delete'
43
+  })
44
+}

+ 1
- 0
oa-ui/src/assets/icons/svg/robot.svg View File

@@ -0,0 +1 @@
1
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753773517876" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11912" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#FFFFFF" p-id="11913"></path><path d="M512 96.533333m-50.826667 0a50.826667 50.826667 0 1 0 101.653334 0 50.826667 50.826667 0 1 0-101.653334 0Z" fill="#FA759E" p-id="11914"></path><path d="M512 158.053333c-33.92 0-61.493333-27.6-61.493333-61.493333S478.08 35.04 512 35.04s61.493333 27.6 61.493333 61.493333S545.92 158.053333 512 158.053333z m0-101.68c-22.16 0-40.16 18.026667-40.16 40.16S489.866667 136.693333 512 136.693333c22.16 0 40.16-18.026667 40.16-40.16S534.16 56.373333 512 56.373333z" fill="#2B2E63" p-id="11915"></path><path d="M501.333333 147.386667h21.333334v53.546666h-21.333334z" fill="#2B2E63" p-id="11916"></path><path d="M571.946667 246.32c0-25.066667-26.853333-45.386667-59.946667-45.386667s-59.946667 20.32-59.946667 45.386667" fill="#0089EF" p-id="11917"></path><path d="M593.493333 250.533333h-21.333333c0-23.68-26.986667-42.96-60.16-42.96s-60.16 19.28-60.16 42.96h-21.333333c0-35.466667 36.56-64.293333 81.493333-64.293333s81.493333 28.853333 81.493333 64.293333z" fill="#2B2E63" p-id="11918"></path><path d="M137.6 702.48c-50.133333 0-90.773333-53.68-90.773333-119.893333S87.466667 462.666667 137.6 462.666667M886.4 702.48c50.133333 0 90.773333-53.68 90.773333-119.893333S936.533333 462.666667 886.4 462.666667" fill="#0089EF" p-id="11919"></path><path d="M674.746667 916.96H349.253333c-116.4 0-211.653333-95.253333-211.653333-211.653333v-196.453334c0-116.4 95.253333-211.653333 211.653333-211.653333h325.493334c116.4 0 211.653333 95.253333 211.653333 211.653333v196.453334c0 116.426667-95.253333 211.653333-211.653333 211.653333z" fill="#6DC9F7" p-id="11920"></path><path d="M137.6 713.146667c-55.946667 0-101.44-58.56-101.44-130.56s45.52-130.56 101.44-130.56v21.333333c-44.186667 0-80.106667 49.013333-80.106667 109.226667 0 60.24 35.946667 109.226667 80.106667 109.226666v21.333334zM886.4 713.146667v-21.333334c44.186667 0 80.106667-49.013333 80.106667-109.226666 0-60.24-35.946667-109.226667-80.106667-109.226667v-21.333333c55.946667 0 101.44 58.56 101.44 130.56 0 71.973333-45.493333 130.56-101.44 130.56z" fill="#2B2E63" p-id="11921"></path><path d="M644.693333 927.626667h-265.413333c-139.146667 0-252.373333-113.2-252.373333-252.373334v-183.04c0-139.146667 113.2-252.373333 252.373333-252.373333h265.413333c139.146667 0 252.373333 113.2 252.373334 252.373333v183.04c0 139.173333-113.2 252.373333-252.373334 252.373334zM379.306667 261.2c-127.386667 0-231.04 103.653333-231.04 231.04v183.04c0 127.386667 103.653333 231.04 231.04 231.04h265.413333c127.386667 0 231.04-103.653333 231.04-231.04v-183.04c0-127.386667-103.653333-231.04-231.04-231.04h-265.413333z" fill="#2B2E63" p-id="11922"></path><path d="M615.173333 786.8h-206.346666c-87.36 0-158.853333-71.493333-158.853334-158.853333v-89.92c0-87.36 71.493333-158.853333 158.853334-158.853334h206.346666c87.36 0 158.853333 71.493333 158.853334 158.853334v89.92c0 87.386667-71.493333 158.853333-158.853334 158.853333z" fill="#FFD7E5" p-id="11923"></path><path d="M390.16 525.546667m-42.4 0a42.4 42.4 0 1 0 84.8 0 42.4 42.4 0 1 0-84.8 0Z" fill="#2B2E63" p-id="11924"></path><path d="M633.84 525.546667m-42.4 0a42.4 42.4 0 1 0 84.8 0 42.4 42.4 0 1 0-84.8 0Z" fill="#2B2E63" p-id="11925"></path><path d="M512 690.373333c-30.693333 0-58.373333-12.88-74.08-34.453333-3.466667-4.773333-2.4-11.44 2.346667-14.906667 4.773333-3.466667 11.44-2.4 14.906666 2.346667 11.493333 15.84 33.28 25.653333 56.8 25.653333s45.306667-9.84 56.8-25.653333c3.466667-4.773333 10.133333-5.813333 14.906667-2.346667 4.773333 3.466667 5.813333 10.133333 2.346667 14.906667-15.653333 21.573333-43.333333 34.453333-74.026667 34.453333z" fill="#2B2E63" p-id="11926"></path></svg>

+ 2
- 2
oa-ui/src/views/flowable/form/budget/adjust/components/InnerStaffCost.vue View File

@@ -7,8 +7,8 @@
7 7
         <td style="min-width:80px;">姓名</td>
8 8
         <td style="min-width:80px;">人员成本(天)</td>
9 9
         <td style="min-width:80px;">预算天数</td>
10
-        <td style="min-width:80px;">固定成本计</td>
11
-        <td style="min-width:80px;">绩效成本计</td>
10
+        <td style="min-width:80px;">固定成本计</td>
11
+        <td style="min-width:80px;">绩效成本计</td>
12 12
         <td style="width:120px;min-width:80px;">预算金额</td>
13 13
         <td class="adjust" style="width:120px;min-width:80px;">核算固定成本</td>
14 14
         <td class="adjust" style="width:120px;min-width:80px;">核算天数</td>

+ 2
- 2
oa-ui/src/views/flowable/form/budget/adjust/components/OuterStaffCost.vue View File

@@ -8,8 +8,8 @@
8 8
         <td style="min-width:80px;">人员成本(天)</td>
9 9
         <td style="min-width:80px;">预算天数</td>
10 10
         <td style="min-width:80px;">预算系数</td>
11
-        <td style="min-width:100px;">固定成本计</td>
12
-        <td style="min-width:100px;">绩效成本计</td>
11
+        <td style="min-width:100px;">固定成本计</td>
12
+        <td style="min-width:100px;">绩效成本计</td>
13 13
         <td style="width:120px;min-width:80px;">预算金额</td>
14 14
         <td class="adjust" style="width:120px;min-width:80px;">核算天数</td>
15 15
         <td class="adjust" style="width:120px;min-width:80px;">核算系数</td>

+ 4
- 4
oa-ui/src/views/flowable/form/budget/adjust/newBudgetInfo.vue View File

@@ -122,8 +122,8 @@
122 122
             <td>姓名</td>
123 123
             <td>固定成本(天)</td>
124 124
             <td style="width:120px">预算天数</td>
125
-            <td>固定成本计</td>
126
-            <td>绩效成本计</td>
125
+            <td>固定成本计</td>
126
+            <td>绩效成本计</td>
127 127
             <td style="width:120px">金额</td>
128 128
             <td style="width:120px">备注</td>
129 129
           </tr>
@@ -169,8 +169,8 @@
169 169
             <td>单价</td>
170 170
             <td style="width:120px">预算天数</td>
171 171
             <td>预算系数</td>
172
-            <td>固定成本计</td>
173
-            <td>绩效成本计</td>
172
+            <td>固定成本计</td>
173
+            <td>绩效成本计</td>
174 174
             <td style="width:120px">金额</td>
175 175
             <td style="width:120px">备注</td>
176 176
           </tr>

+ 7
- 7
oa-ui/src/views/flowable/form/oa/recruitForm.vue View File

@@ -1,8 +1,8 @@
1 1
 <!--
2 2
  * @Author: ysh
3 3
  * @Date: 2025-08-18 10:59:25
4
- * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-08-19 16:33:56
4
+ * @LastEditors: wrh
5
+ * @LastEditTime: 2025-11-19 14:01:20
6 6
 -->
7 7
 <template>
8 8
   <div>
@@ -45,17 +45,17 @@
45 45
                 <el-button type="primary" size="mini" @click="openProject = true"
46 46
                   v-if="taskName == '招聘申请'">选择项目</el-button>
47 47
                 <el-descriptions border v-if="isSelect" style="margin-top: 10px;" :column="1">
48
-                  <el-descriptions-item label="项目编号" label-class-name="my-label">{{ chooseProject.projectNumber
48
+                  <el-descriptions-item label="项目编号" label-class-name="my-label">{{ chooseProject ? chooseProject.projectNumber : ""
49 49
                     }}</el-descriptions-item>
50
-                  <el-descriptions-item label="项目名称" label-class-name="my-label">{{ chooseProject.projectName
50
+                  <el-descriptions-item label="项目名称" label-class-name="my-label">{{ chooseProject ? chooseProject.projectName : ""
51 51
                     }}</el-descriptions-item>
52
-                  <el-descriptions-item label="项目负责人" label-class-name="my-label">{{ chooseProject.projectLeaderUser ?
53
-                    chooseProject.projectLeaderUser.nickName : ''
52
+                  <el-descriptions-item label="项目负责人" label-class-name="my-label">{{ chooseProject ? (chooseProject.projectLeaderUser ?
53
+                    chooseProject.projectLeaderUser.nickName : '') : ""
54 54
                     }}</el-descriptions-item>
55 55
                   <el-descriptions-item label="承担部门" label-class-name="my-label">
56 56
                     <el-tag size="small">{{ chooseProject.undertakingDeptName }}</el-tag>
57 57
                   </el-descriptions-item>
58
-                  <el-descriptions-item label="项目类型" label-class-name="my-label">{{ chooseProject.projectType
58
+                  <el-descriptions-item label="项目类型" label-class-name="my-label">{{ chooseProject ? chooseProject.projectType : ""
59 59
                     }}</el-descriptions-item>
60 60
                 </el-descriptions>
61 61
               </el-form-item>

+ 1301
- 0
oa-ui/src/views/llm/agent/AgentDetail.vue
File diff suppressed because it is too large
View File


+ 479
- 0
oa-ui/src/views/llm/agent/index.vue View File

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

+ 1327
- 0
oa-ui/src/views/llm/chat/index.vue
File diff suppressed because it is too large
View File


+ 1815
- 0
oa-ui/src/views/llm/knowledge/index.vue
File diff suppressed because it is too large
View File


+ 1
- 1
oa-ui/src/views/oa/brand/brandProject.vue View File

@@ -539,7 +539,7 @@ export default {
539 539
       } else if (row > 20 && row <= 50) {
540 540
         return 'warning'
541 541
       } else if (row > 50 && row <= 80) {
542
-        return ''
542
+        return null
543 543
       } else {
544 544
         return 'success'
545 545
       }

+ 3
- 4
oa-ui/src/views/oa/project/invest.vue View File

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2024-02-27 14:49:15
4 4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-10-14 15:42:58
5
+ * @LastEditTime: 2025-11-20 09:51:32
6 6
 -->
7 7
 <template>
8 8
   <div class="project-wrapper">
@@ -14,8 +14,7 @@
14 14
             @keyup.enter.native="handleQuery" />
15 15
         </el-form-item>
16 16
         <el-form-item label="项目名称" prop="projectName">
17
-          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable
18
-            @keyup.enter.native="handleQuery" />
17
+          <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter.native="handleQuery" />
19 18
         </el-form-item>
20 19
         <el-form-item label="项目负责人" prop="projectLeader">
21 20
           <el-select v-model="queryParams.projectLeader" clearable filterable placeholder="请输入项目负责人" size="large"
@@ -463,7 +462,7 @@ export default {
463 462
       } else if (row > 20 && row <= 50) {
464 463
         return 'warning'
465 464
       } else if (row > 50 && row <= 80) {
466
-        return ''
465
+        return null
467 466
       } else {
468 467
         return 'success'
469 468
       }

+ 10
- 2
oa-ui/src/views/oa/recruit/index.vue View File

@@ -55,8 +55,16 @@
55 55
           {{ getDeptName(scope.row.applyDept) }}
56 56
         </template>
57 57
       </el-table-column>
58
-      <el-table-column label="项目编号" align="center" prop="project.projectNumber" />
59
-      <el-table-column label="项目名称" align="center" prop="project.projectName" />
58
+      <el-table-column label="项目编号" align="center" prop="projectNumber" >
59
+        <template slot-scope="scope">
60
+          <span>{{ scope.row.project ? scope.row.project.projectNumber : "" }}</span>
61
+        </template>
62
+      </el-table-column>
63
+      <el-table-column label="项目名称" align="center" prop="projectName" >
64
+        <template slot-scope="scope">
65
+          <span>{{ scope.row.project ? scope.row.project.projectName : "" }}</span>
66
+        </template>
67
+      </el-table-column>
60 68
       <el-table-column label="申请日期" align="center" prop="applyDate" width="180">
61 69
         <template slot-scope="scope">
62 70
           <span>{{ parseTime(scope.row.applyDate, '{y}-{m}-{d}') }}</span>

+ 4
- 4
oa-ui/src/views/oa/recruit/recruitTab.vue View File

@@ -17,13 +17,13 @@
17 17
         </el-row>
18 18
         <el-row v-show="isProject">
19 19
             <el-col :span="6" :xs="24">
20
-            <el-form-item label="项目编号:" prop="project.projectNumber">
21
-                {{ form.project.projectNumber }}
20
+            <el-form-item label="项目编号:" prop="projectNumber">
21
+                {{ form.project ? form.project.projectNumber : ""}}
22 22
             </el-form-item>
23 23
             </el-col>
24 24
             <el-col :span="6" :xs="24">
25
-            <el-form-item label="项目名称:" prop="project.projectName">
26
-                {{ form.project.projectName }}
25
+            <el-form-item label="项目名称:" prop="projectName">
26
+                {{ form.project ? form.project.projectName : ""}}
27 27
             </el-form-item>
28 28
             </el-col>
29 29
         </el-row>

Loading…
Cancel
Save