lamphua 1 неделю назад
Родитель
Сommit
c9f1b1e2eb

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

45
      * 删除知识库所有文件
45
      * 删除知识库所有文件
46
      */
46
      */
47
     public void removeAllDocument(String collectionName);
47
     public void removeAllDocument(String collectionName);
48
+
49
+    /**
50
+     * 列出所有的title
51
+     */
52
+    public List<String> listTiles(String collectionName);
53
+
54
+    /**
55
+     * 根据title名查询相关content
56
+     */
57
+    public List<String> listByTitle(String collectionName, String title);
48
 }
58
 }

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

75
     private String milvusServiceUrl;
75
     private String milvusServiceUrl;
76
 
76
 
77
     private MilvusClientV2 milvusClient;
77
     private MilvusClientV2 milvusClient;
78
-    
78
+
79
     // 复用 ChatModel 实例
79
     // 复用 ChatModel 实例
80
     private ChatModel chatModel;
80
     private ChatModel chatModel;
81
 
81
 
84
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
84
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
85
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
85
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
86
         }
86
         }
87
-//        milvusClient = new MilvusClientV2(
88
-//                ConnectConfig.builder()
89
-//                        .uri(milvusServiceUrl)
90
-//                        .build());
87
+        milvusClient = new MilvusClientV2(
88
+                ConnectConfig.builder()
89
+                        .uri(milvusServiceUrl)
90
+                        .build());
91
     }
91
     }
92
-    
92
+
93
     @PostConstruct
93
     @PostConstruct
94
     public void initChatModel() {
94
     public void initChatModel() {
95
         // 初始化 ChatModel
95
         // 初始化 ChatModel
97
                 .model("Qwen")
97
                 .model("Qwen")
98
                 .build();
98
                 .build();
99
     }
99
     }
100
-    
100
+
101
     @PreDestroy
101
     @PreDestroy
102
     public void destroyMilvusClient() {
102
     public void destroyMilvusClient() {
103
         if (milvusClient != null) {
103
         if (milvusClient != null) {
137
                 // 构建上传目录路径
137
                 // 构建上传目录路径
138
                 String uploadDir = RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName;
138
                 String uploadDir = RuoYiConfig.getProfile() + "/upload/rag/knowledge/" + collectionName;
139
                 File profilePath = new File(uploadDir);
139
                 File profilePath = new File(uploadDir);
140
-                
140
+
141
                 // 确保目录存在,创建目录并设置权限
141
                 // 确保目录存在,创建目录并设置权限
142
                 if (!profilePath.exists())
142
                 if (!profilePath.exists())
143
                     profilePath.mkdirs();
143
                     profilePath.mkdirs();
491
     private String extractAllTitles(File transferFile) throws Exception {
491
     private String extractAllTitles(File transferFile) throws Exception {
492
         StringBuilder titlesBuilder = new StringBuilder();
492
         StringBuilder titlesBuilder = new StringBuilder();
493
         String filename = transferFile.getName().toLowerCase();
493
         String filename = transferFile.getName().toLowerCase();
494
-        
494
+
495
         if (filename.endsWith(".docx")) {
495
         if (filename.endsWith(".docx")) {
496
             try (XWPFDocument document = new XWPFDocument(new FileInputStream(transferFile))) {
496
             try (XWPFDocument document = new XWPFDocument(new FileInputStream(transferFile))) {
497
                 for (XWPFParagraph paragraph : document.getParagraphs()) {
497
                 for (XWPFParagraph paragraph : document.getParagraphs()) {
558
                                     String[] paragraphs = currentLevel2Content.toString().split("\n");
558
                                     String[] paragraphs = currentLevel2Content.toString().split("\n");
559
                                     for (String para : paragraphs) {
559
                                     for (String para : paragraphs) {
560
                                         if (para.trim().isEmpty()) continue;
560
                                         if (para.trim().isEmpty()) continue;
561
-                                        
561
+
562
                                         // 检查是否是三级标题(简单判断:如果是三级标题,样式应该是3,但这里需要重新解析)
562
                                         // 检查是否是三级标题(简单判断:如果是三级标题,样式应该是3,但这里需要重新解析)
563
                                         // 由于已经将内容转为字符串,这里采用另一种方式:假设三级标题以数字+点+空格开头(如"1.1.")
563
                                         // 由于已经将内容转为字符串,这里采用另一种方式:假设三级标题以数字+点+空格开头(如"1.1.")
564
                                         // 或者可以重新遍历段落,但为了效率,这里采用简单的判断方式
564
                                         // 或者可以重新遍历段落,但为了效率,这里采用简单的判断方式
569
                                                 break;
569
                                                 break;
570
                                             }
570
                                             }
571
                                         }
571
                                         }
572
-                                        
572
+
573
                                         if (isLevel3Title) {
573
                                         if (isLevel3Title) {
574
                                             // 保存当前三级标题内容
574
                                             // 保存当前三级标题内容
575
                                             if (currentLevel3Content.length() != 0) {
575
                                             if (currentLevel3Content.length() != 0) {
627
                         String[] paragraphs = currentLevel2Content.toString().split("\n");
627
                         String[] paragraphs = currentLevel2Content.toString().split("\n");
628
                         for (String para : paragraphs) {
628
                         for (String para : paragraphs) {
629
                             if (para.trim().isEmpty()) continue;
629
                             if (para.trim().isEmpty()) continue;
630
-                            
630
+
631
                             boolean isLevel3Title = false;
631
                             boolean isLevel3Title = false;
632
                             for (XWPFParagraph p : xwpfDocument.getParagraphs()) {
632
                             for (XWPFParagraph p : xwpfDocument.getParagraphs()) {
633
                                 if (p.getText().trim().equals(para.trim()) && p.getStyle() != null && p.getStyle().equals("3")) {
633
                                 if (p.getText().trim().equals(para.trim()) && p.getStyle() != null && p.getStyle().equals("3")) {
635
                                     break;
635
                                     break;
636
                                 }
636
                                 }
637
                             }
637
                             }
638
-                            
638
+
639
                             if (isLevel3Title) {
639
                             if (isLevel3Title) {
640
                                 // 保存当前三级标题内容
640
                                 // 保存当前三级标题内容
641
                                 if (currentLevel3Content.length() != 0) {
641
                                 if (currentLevel3Content.length() != 0) {
694
                                 StringBuilder currentLevel3Content = new StringBuilder();
694
                                 StringBuilder currentLevel3Content = new StringBuilder();
695
                                 Range level2Range = hwpfDocument.getRange();
695
                                 Range level2Range = hwpfDocument.getRange();
696
                                 boolean foundCurrentLevel2 = false;
696
                                 boolean foundCurrentLevel2 = false;
697
-                                
697
+
698
                                 for (int j = 0; j < level2Range.numParagraphs(); j++) {
698
                                 for (int j = 0; j < level2Range.numParagraphs(); j++) {
699
                                     Paragraph p = level2Range.getParagraph(j);
699
                                     Paragraph p = level2Range.getParagraph(j);
700
                                     String paraText = p.text().trim();
700
                                     String paraText = p.text().trim();
701
                                     if (paraText.isEmpty()) continue;
701
                                     if (paraText.isEmpty()) continue;
702
-                                    
702
+
703
                                     // 找到当前二级标题的开始
703
                                     // 找到当前二级标题的开始
704
                                     if (p.getStyleIndex() == 2 && paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
704
                                     if (p.getStyleIndex() == 2 && paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
705
                                         foundCurrentLevel2 = true;
705
                                         foundCurrentLevel2 = true;
706
                                     }
706
                                     }
707
-                                    
707
+
708
                                     if (foundCurrentLevel2) {
708
                                     if (foundCurrentLevel2) {
709
                                         short paraStyleIndex = p.getStyleIndex();
709
                                         short paraStyleIndex = p.getStyleIndex();
710
-                                        
710
+
711
                                         if (paraStyleIndex == 3) {
711
                                         if (paraStyleIndex == 3) {
712
                                             // 三级标题
712
                                             // 三级标题
713
                                             // 保存当前三级标题内容
713
                                             // 保存当前三级标题内容
729
                                         }
729
                                         }
730
                                     }
730
                                     }
731
                                 }
731
                                 }
732
-                                
732
+
733
                                 // 保存最后一个三级标题内容
733
                                 // 保存最后一个三级标题内容
734
                                 if (currentLevel3Content.length() != 0) {
734
                                 if (currentLevel3Content.length() != 0) {
735
                                     TextSegment segment = TextSegment.from(currentLevel3Content.toString());
735
                                     TextSegment segment = TextSegment.from(currentLevel3Content.toString());
762
                         StringBuilder currentLevel3Content = new StringBuilder();
762
                         StringBuilder currentLevel3Content = new StringBuilder();
763
                         Range level2Range = hwpfDocument.getRange();
763
                         Range level2Range = hwpfDocument.getRange();
764
                         boolean foundCurrentLevel2 = false;
764
                         boolean foundCurrentLevel2 = false;
765
-                        
765
+
766
                         for (int j = 0; j < level2Range.numParagraphs(); j++) {
766
                         for (int j = 0; j < level2Range.numParagraphs(); j++) {
767
                             Paragraph p = level2Range.getParagraph(j);
767
                             Paragraph p = level2Range.getParagraph(j);
768
                             String paraText = p.text().trim();
768
                             String paraText = p.text().trim();
769
                             if (paraText.isEmpty()) continue;
769
                             if (paraText.isEmpty()) continue;
770
-                            
770
+
771
                             // 找到当前二级标题的开始
771
                             // 找到当前二级标题的开始
772
                             if (p.getStyleIndex() == 2 && paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
772
                             if (p.getStyleIndex() == 2 && paraText.equals(currentLevel2Content.toString().split("\n")[0].trim())) {
773
                                 foundCurrentLevel2 = true;
773
                                 foundCurrentLevel2 = true;
774
                             }
774
                             }
775
-                            
775
+
776
                             if (foundCurrentLevel2) {
776
                             if (foundCurrentLevel2) {
777
                                 short paraStyleIndex = p.getStyleIndex();
777
                                 short paraStyleIndex = p.getStyleIndex();
778
-                                
778
+
779
                                 if (paraStyleIndex == 3) {
779
                                 if (paraStyleIndex == 3) {
780
                                     // 三级标题
780
                                     // 三级标题
781
                                     // 保存当前三级标题内容
781
                                     // 保存当前三级标题内容
797
                                 }
797
                                 }
798
                             }
798
                             }
799
                         }
799
                         }
800
-                        
800
+
801
                         // 保存最后一个三级标题内容
801
                         // 保存最后一个三级标题内容
802
                         if (currentLevel3Content.length() != 0) {
802
                         if (currentLevel3Content.length() != 0) {
803
                             TextSegment segment = TextSegment.from(currentLevel3Content.toString());
803
                             TextSegment segment = TextSegment.from(currentLevel3Content.toString());

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

35
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
35
         if (milvusServiceUrl == null || milvusServiceUrl.isEmpty()) {
36
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
36
             throw new IllegalArgumentException("milvusServiceUrl 配置不能为空");
37
         }
37
         }
38
-//        milvusClient = new MilvusClientV2(
39
-//                ConnectConfig.builder()
40
-//                        .uri(milvusServiceUrl)
41
-//                        .build());
38
+        milvusClient = new MilvusClientV2(
39
+                ConnectConfig.builder()
40
+                        .uri(milvusServiceUrl)
41
+                        .build());
42
     }
42
     }
43
-    
43
+
44
     @PreDestroy
44
     @PreDestroy
45
     public void destroyMilvusClient() {
45
     public void destroyMilvusClient() {
46
         if (milvusClient != null) {
46
         if (milvusClient != null) {
76
                 .build());
76
                 .build());
77
 
77
 
78
         schema.addField(AddFieldReq.builder()
78
         schema.addField(AddFieldReq.builder()
79
-                .fieldName("file_type")
79
+                .fieldName("title")
80
                 .dataType(DataType.VarChar)
80
                 .dataType(DataType.VarChar)
81
-                .maxLength(10)
81
+                .maxLength(256)
82
                 .build());
82
                 .build());
83
 
83
 
84
         schema.addField(AddFieldReq.builder()
84
         schema.addField(AddFieldReq.builder()
85
-                .fieldName("title")
85
+                .fieldName("file_type")
86
                 .dataType(DataType.VarChar)
86
                 .dataType(DataType.VarChar)
87
-                .maxLength(256)
87
+                .maxLength(10)
88
                 .build());
88
                 .build());
89
 
89
 
90
         schema.addField(AddFieldReq.builder()
90
         schema.addField(AddFieldReq.builder()
220
                 .build();
220
                 .build();
221
         if (fileType != null && !fileType.equals(""))
221
         if (fileType != null && !fileType.equals(""))
222
             queryParam = QueryReq.builder()
222
             queryParam = QueryReq.builder()
223
-                .collectionName(collectionName)
224
-                .filter(String.format("file_type == \"%s\"", fileType))
225
-                .outputFields(Arrays.asList("file_type", "file_name"))
226
-                .build();
223
+                    .collectionName(collectionName)
224
+                    .filter(String.format("file_type == \"%s\"", fileType))
225
+                    .outputFields(Arrays.asList("file_type", "file_name"))
226
+                    .build();
227
         QueryResp queryResp = milvusClient.query(queryParam);
227
         QueryResp queryResp = milvusClient.query(queryParam);
228
         List<QueryResp.QueryResult> rowRecordList;
228
         List<QueryResp.QueryResult> rowRecordList;
229
         if (queryResp != null) {
229
         if (queryResp != null) {
271
         milvusClient.delete(deleteReq);
271
         milvusClient.delete(deleteReq);
272
     }
272
     }
273
 
273
 
274
+    /**
275
+     * 列出所有的title
276
+     */
277
+    @Override
278
+    public List<String> listTiles(String collectionName) {
279
+        List<String> titleList = new ArrayList<>();
280
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
281
+                .collectionName(collectionName)
282
+                .build();
283
+        milvusClient.loadCollection(loadCollectionReq);
284
+        QueryReq queryParam = QueryReq.builder()
285
+                .collectionName(collectionName)
286
+                .filter("id > 0")
287
+                .outputFields(Arrays.asList("title"))
288
+                .build();
289
+        QueryResp queryResp = milvusClient.query(queryParam);
290
+        List<QueryResp.QueryResult> rowRecordList;
291
+        if (queryResp != null) {
292
+            rowRecordList = queryResp.getQueryResults();
293
+            for (QueryResp.QueryResult rowRecord : rowRecordList) {
294
+                Object title = rowRecord.getEntity().get("title");
295
+                if (title != null) {
296
+                    titleList.add(title.toString());
297
+                }
298
+            }
299
+        }
300
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
301
+                .collectionName(collectionName)
302
+                .build();
303
+        milvusClient.releaseCollection(releaseCollectionReq);
304
+        return titleList.stream().distinct().collect(Collectors.toList());
305
+    }
306
+
307
+    /**
308
+     * 根据title名查询相关content
309
+     */
310
+    @Override
311
+    public List<String> listByTitle(String collectionName, String title) {
312
+        List<String> contentList = new ArrayList<>();
313
+        LoadCollectionReq loadCollectionReq = LoadCollectionReq.builder()
314
+                .collectionName(collectionName)
315
+                .build();
316
+        milvusClient.loadCollection(loadCollectionReq);
317
+        QueryReq queryParam = QueryReq.builder()
318
+                .collectionName(collectionName)
319
+                .filter(String.format("title == \"%s\"", title))
320
+                .outputFields(Arrays.asList("content"))
321
+                .build();
322
+        QueryResp queryResp = milvusClient.query(queryParam);
323
+        List<QueryResp.QueryResult> rowRecordList;
324
+        if (queryResp != null) {
325
+            rowRecordList = queryResp.getQueryResults();
326
+            for (QueryResp.QueryResult rowRecord : rowRecordList) {
327
+                Object content = rowRecord.getEntity().get("content");
328
+                if (content != null) {
329
+                    contentList.add(content.toString());
330
+                }
331
+            }
332
+        }
333
+        ReleaseCollectionReq releaseCollectionReq = ReleaseCollectionReq.builder()
334
+                .collectionName(collectionName)
335
+                .build();
336
+        milvusClient.releaseCollection(releaseCollectionReq);
337
+        return contentList;
338
+    }
339
+
274
 }
340
 }

+ 19
- 1
oa-ui/src/api/llm/knowLedge.js Просмотреть файл

2
  * @Author: ysh
2
  * @Author: ysh
3
  * @Date: 2025-06-30 09:56:10
3
  * @Date: 2025-06-30 09:56:10
4
  * @LastEditors: wrh
4
  * @LastEditors: wrh
5
- * @LastEditTime: 2025-07-18 15:40:00
5
+ * @LastEditTime: 2026-04-17 09:54:52
6
  */
6
  */
7
 import request from '@/utils/request'
7
 import request from '@/utils/request'
8
 
8
 
92
     params: { fileName, collectionName }
92
     params: { fileName, collectionName }
93
   })
93
   })
94
 }
94
 }
95
+
96
+// 列出所有的title
97
+export function listTiles(collectionName) {
98
+  return request({
99
+    url: '/llm/knowledge/listTiles',
100
+    method: 'get',
101
+    params: { collectionName }
102
+  })
103
+}
104
+
105
+// 根据title名查询相关content
106
+export function listByTitle(collectionName, title) {
107
+  return request({
108
+    url: '/llm/knowledge/listByTitle',
109
+    method: 'get',
110
+    params: { collectionName, title }
111
+  })
112
+}

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

71
       <!-- 右侧面板 -->
71
       <!-- 右侧面板 -->
72
       <div class="right-panel">
72
       <div class="right-panel">
73
         <div class="panel-header">
73
         <div class="panel-header">
74
-          <h3>{{ isChatMode ? '知识库对话' : '文件列表' }}</h3>
74
+          <h3>{{ isChatMode ? '知识库对话' : (isTitleMode ? '标题列表' : '文件列表') }}</h3>
75
           <div class="header-actions">
75
           <div class="header-actions">
76
-            <el-button v-if="!isChatMode" type="primary" icon="el-icon-chat-round" @click="handleChat(selectedKnowledge)"
76
+            <el-button v-if="!isChatMode && !isTitleMode" type="primary" icon="el-icon-chat-round" @click="handleChat(selectedKnowledge)"
77
               :disabled="!selectedKnowledge" v-hasPermi="['llm:knowledge:chat']">
77
               :disabled="!selectedKnowledge" v-hasPermi="['llm:knowledge:chat']">
78
               开始对话
78
               开始对话
79
             </el-button>
79
             </el-button>
80
-            <el-button v-if="!isChatMode" type="primary" icon="el-icon-upload" @click="handleUpload(selectedKnowledge)"
80
+            <el-button v-if="!isChatMode && !isTitleMode" type="primary" icon="el-icon-upload" @click="handleUpload(selectedKnowledge)"
81
               :disabled="!selectedKnowledge" v-hasPermi="['llm:knowledge:upload']">
81
               :disabled="!selectedKnowledge" v-hasPermi="['llm:knowledge:upload']">
82
               上传文件
82
               上传文件
83
             </el-button>
83
             </el-button>
84
+            <el-button v-if="!isChatMode && !isTitleMode" type="default" icon="el-icon-notebook-2" @click="switchToTitleMode"
85
+              :disabled="!selectedKnowledge">
86
+              查看内容
87
+            </el-button>
84
             <el-button v-if="isChatMode" type="default" icon="el-icon-document" @click="switchToFileMode">
88
             <el-button v-if="isChatMode" type="default" icon="el-icon-document" @click="switchToFileMode">
85
               返回文件列表
89
               返回文件列表
86
             </el-button>
90
             </el-button>
91
+            <el-button v-if="isTitleMode" type="default" icon="el-icon-document" @click="switchToFileMode">
92
+              返回文件列表
93
+            </el-button>
87
           </div>
94
           </div>
88
         </div>
95
         </div>
89
 
96
 
90
         <!-- 文件列表模式 -->
97
         <!-- 文件列表模式 -->
91
-        <div v-if="!isChatMode" class="file-content" v-loading="fileLoading">
98
+        <div v-if="!isChatMode && !isTitleMode" class="file-content" v-loading="fileLoading">
92
           <div v-if="selectedKnowledge" class="selected-knowledge">
99
           <div v-if="selectedKnowledge" class="selected-knowledge">
93
             <i class="el-icon-folder folder-icon">
100
             <i class="el-icon-folder folder-icon">
94
             </i>
101
             </i>
133
             @pagination="handleFilePagination" :autoScroll="false" />
140
             @pagination="handleFilePagination" :autoScroll="false" />
134
         </div>
141
         </div>
135
 
142
 
143
+        <!-- 标题列表模式 -->
144
+        <div v-if="!isChatMode && isTitleMode" class="title-content" v-loading="titleLoading">
145
+          <div v-if="selectedKnowledge" class="selected-knowledge">
146
+            <i class="el-icon-folder folder-icon">
147
+            </i>
148
+            <span class="knowledge-name">{{ selectedKnowledge.collectionName }}</span>
149
+            <span class="title-mode-badge">标题模式</span>
150
+          </div>
151
+
152
+          <div v-if="!selectedKnowledge" class="empty-state">
153
+            <i class="el-icon-notebook-2 empty-icon">
154
+            </i>
155
+            <p>请选择一个知识库查看内容</p>
156
+          </div>
157
+
158
+          <div v-else-if="titleList.length === 0" class="empty-state">
159
+            <i class="el-icon-notebook-2 empty-icon">
160
+            </i>
161
+            <p>该知识库暂无标题</p>
162
+          </div>
163
+
164
+          <div v-else class="title-content-container">
165
+            <!-- 左侧标题列表 -->
166
+            <div class="title-list">
167
+              <div v-for="(title, index) in titleList" :key="index" class="title-item"
168
+                :class="{ 'active': selectedTitle === title }"
169
+                @click="selectTitle(title)">
170
+                <i class="el-icon-notebook-2 title-icon"></i>
171
+                <span class="title-text">{{ title }}</span>
172
+              </div>
173
+            </div>
174
+
175
+            <!-- 右侧内容列表 -->
176
+            <div class="content-list" v-loading="contentLoading">
177
+              <div v-if="!selectedTitle" class="empty-state">
178
+                <i class="el-icon-document empty-icon"></i>
179
+                <p>请选择一个标题查看内容</p>
180
+              </div>
181
+              <div v-else-if="contentList.length === 0" class="empty-state">
182
+                <i class="el-icon-document empty-icon"></i>
183
+                <p>该标题暂无内容</p>
184
+              </div>
185
+              <div v-else class="content-items">
186
+                <div v-for="(content, index) in contentList" :key="index" class="content-item">
187
+                  <div class="content-header">
188
+                    <span class="content-index">{{ index + 1 }}</span>
189
+                  </div>
190
+                  <div class="content-body">
191
+                    {{ content }}
192
+                  </div>
193
+                </div>
194
+              </div>
195
+            </div>
196
+          </div>
197
+        </div>
198
+
136
         <!-- 聊天模式 -->
199
         <!-- 聊天模式 -->
137
         <div v-else class="chat-content">
200
         <div v-else class="chat-content">
138
           <div v-if="selectedKnowledge" class="selected-knowledge">
201
           <div v-if="selectedKnowledge" class="selected-knowledge">
302
 import { parseTime } from "@/utils/ruoyi";
365
 import { parseTime } from "@/utils/ruoyi";
303
 import { Message } from 'element-ui'
366
 import { Message } from 'element-ui'
304
 import { getToken } from "@/utils/auth";
367
 import { getToken } from "@/utils/auth";
305
-import { listKnowledge, listKnowLedgeByCollectionName, addKnowledge, updateKnowledge, delKnowledge, insertKnowledgeFile, listKnowledgeDocument, deleteKnowledgeFile, getProcessValue } from "@/api/llm/knowLedge";
368
+import { listKnowledge, listKnowLedgeByCollectionName, addKnowledge, updateKnowledge, delKnowledge, insertKnowledgeFile, listKnowledgeDocument, deleteKnowledgeFile, getProcessValue, listTiles, listByTitle } from "@/api/llm/knowLedge";
306
 import { getAnswer, getAnswerStream, getContextFile } from '@/api/llm/rag';
369
 import { getAnswer, getAnswerStream, getContextFile } from '@/api/llm/rag';
307
 import { marked } from 'marked';
370
 import { marked } from 'marked';
308
 
371
 
451
       },
514
       },
452
 
515
 
453
       // 定时器
516
       // 定时器
454
-      timer: null
517
+      timer: null,
518
+
519
+      // 标题和内容相关状态
520
+      isTitleMode: false, // 是否显示标题模式
521
+      titleList: [], // 标题列表
522
+      contentList: [], // 内容列表
523
+      selectedTitle: null, // 选中的标题
524
+      titleLoading: false, // 标题加载状态
525
+      contentLoading: false // 内容加载状态
455
     }
526
     }
456
   },
527
   },
457
   mounted() {
528
   mounted() {
529
+    // 重置状态,确保页面刷新后不会停留在对话模式或标题模式
530
+    this.isChatMode = false;
531
+    this.isTitleMode = false;
532
+    this.chatMessages = [];
533
+    this.chatInput = '';
534
+    this.streamingStarted = false;
535
+    this.titleList = [];
536
+    this.contentList = [];
537
+    this.selectedTitle = null;
538
+    
458
     this.getList();
539
     this.getList();
459
   },
540
   },
460
 
541
 
581
       }
662
       }
582
 
663
 
583
       this.isChatMode = false;
664
       this.isChatMode = false;
665
+      this.isTitleMode = false;
666
+    },
667
+
668
+    /** 切换到标题模式 */
669
+    switchToTitleMode() {
670
+      this.isTitleMode = true;
671
+      this.isChatMode = false;
672
+      this.titleList = [];
673
+      this.contentList = [];
674
+      this.selectedTitle = null;
675
+      
676
+      // 获取标题列表
677
+      this.titleLoading = true;
678
+      listTiles(this.selectedKnowledge.collectionName).then(response => {
679
+        if (Array.isArray(response.data)) {
680
+          this.titleList = response.data;
681
+        } else {
682
+          this.titleList = [];
683
+        }
684
+        this.titleLoading = false;
685
+      }).catch(error => {
686
+        this.titleLoading = false;
687
+        this.$modal.msgError("获取标题列表失败:" + error.message);
688
+      });
689
+    },
690
+
691
+    /** 选择标题 */
692
+    selectTitle(title) {
693
+      this.selectedTitle = title;
694
+      this.contentList = [];
695
+      
696
+      // 获取内容列表
697
+      this.contentLoading = true;
698
+      listByTitle(this.selectedKnowledge.collectionName, title).then(response => {
699
+        if (Array.isArray(response.data)) {
700
+          this.contentList = response.data;
701
+        } else {
702
+          this.contentList = [];
703
+        }
704
+        this.contentLoading = false;
705
+      }).catch(error => {
706
+        this.contentLoading = false;
707
+        this.$modal.msgError("获取内容列表失败:" + error.message);
708
+      });
584
     },
709
     },
585
 
710
 
586
     /** 发送消息 */
711
     /** 发送消息 */
1282
   overflow-y: auto;
1407
   overflow-y: auto;
1283
 }
1408
 }
1284
 
1409
 
1410
+.title-content {
1411
+  flex: 1;
1412
+  padding: 20px;
1413
+  overflow: hidden;
1414
+}
1415
+
1416
+.title-content-container {
1417
+  display: flex;
1418
+  height: calc(100vh - 240px);
1419
+  gap: 20px;
1420
+  overflow: hidden;
1421
+}
1422
+
1423
+.title-list {
1424
+  width: 300px;
1425
+  background: #fafbfc;
1426
+  border-radius: 8px;
1427
+  border: 1px solid #e4e7ed;
1428
+  padding: 12px;
1429
+  overflow-y: auto;
1430
+}
1431
+
1432
+.title-item {
1433
+  display: flex;
1434
+  align-items: center;
1435
+  gap: 8px;
1436
+  padding: 10px 12px;
1437
+  margin-bottom: 8px;
1438
+  border-radius: 6px;
1439
+  cursor: pointer;
1440
+  transition: all 0.3s ease;
1441
+  background: white;
1442
+  border: 1px solid #e4e7ed;
1443
+
1444
+  &:hover {
1445
+    border-color: #409eff;
1446
+    background: #f0f9ff;
1447
+  }
1448
+
1449
+  &.active {
1450
+    border-color: #409eff;
1451
+    background: #e6f7ff;
1452
+    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
1453
+  }
1454
+
1455
+  .title-icon {
1456
+    color: #409eff;
1457
+    font-size: 16px;
1458
+  }
1459
+
1460
+  .title-text {
1461
+    flex: 1;
1462
+    font-size: 14px;
1463
+    color: #303133;
1464
+    line-height: 1.4;
1465
+    overflow: hidden;
1466
+    text-overflow: ellipsis;
1467
+    white-space: nowrap;
1468
+  }
1469
+}
1470
+
1471
+.content-list {
1472
+  flex: 1;
1473
+  background: #fafbfc;
1474
+  border-radius: 8px;
1475
+  border: 1px solid #e4e7ed;
1476
+  padding: 20px;
1477
+  overflow-y: auto;
1478
+}
1479
+
1480
+.content-items {
1481
+  display: flex;
1482
+  flex-direction: column;
1483
+  gap: 16px;
1484
+}
1485
+
1486
+.content-item {
1487
+  background: white;
1488
+  border: 1px solid #e4e7ed;
1489
+  border-radius: 8px;
1490
+  padding: 16px;
1491
+  transition: all 0.3s ease;
1492
+
1493
+  &:hover {
1494
+    border-color: #409eff;
1495
+    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
1496
+  }
1497
+
1498
+  .content-header {
1499
+    display: flex;
1500
+    align-items: center;
1501
+    margin-bottom: 12px;
1502
+
1503
+    .content-index {
1504
+      background: #409eff;
1505
+      color: white;
1506
+      width: 24px;
1507
+      height: 24px;
1508
+      border-radius: 50%;
1509
+      display: flex;
1510
+      align-items: center;
1511
+      justify-content: center;
1512
+      font-size: 12px;
1513
+      font-weight: 600;
1514
+    }
1515
+  }
1516
+
1517
+  .content-body {
1518
+    font-size: 14px;
1519
+    color: #303133;
1520
+    line-height: 1.6;
1521
+    white-space: pre-wrap;
1522
+    word-break: break-word;
1523
+  }
1524
+}
1525
+
1526
+.title-mode-badge {
1527
+  background: #67c23a;
1528
+  color: white;
1529
+  padding: 2px 8px;
1530
+  border-radius: 12px;
1531
+  font-size: 12px;
1532
+  margin-left: auto;
1533
+}
1534
+
1285
 // 聊天模式样式
1535
 // 聊天模式样式
1286
 .chat-content {
1536
 .chat-content {
1287
   flex: 1;
1537
   flex: 1;

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