From 3ca2c89288db1d6cba514ced02c91624ef5fe497 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 09 六月 2026 16:15:56 +0800
Subject: [PATCH] feat(collaborativeApproval): 添加知识库RAG向量检索问答功能

---
 .gitignore                                              |    2 
 doc/知识库RAG功能实现文档.md                                     | 1034 ++++++++++++++++++++++++++++++++++++++
 src/api/collaborativeApproval/knowledgeBase.js          |   52 +
 src/views/collaborativeApproval/knowledgeBase/index.vue |  525 +++++++++++++++++++
 4 files changed, 1,609 insertions(+), 4 deletions(-)

diff --git a/.gitignore b/.gitignore
index 78a752d..f76fc8f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@
 yarn-error.log*
 **/*.log
 
+.claude/
+
 tests/**/coverage/
 tests/e2e/reports
 selenium-debug.log
diff --git "a/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md" "b/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..4a94cc5
--- /dev/null
+++ "b/doc/\347\237\245\350\257\206\345\272\223RAG\345\212\237\350\203\275\345\256\236\347\216\260\346\226\207\346\241\243.md"
@@ -0,0 +1,1034 @@
+# 鐭ヨ瘑搴揜AG鍚戦噺妫�绱㈠姛鑳藉疄鐜版枃妗�
+
+## 涓�銆佸姛鑳芥杩�
+
+鍩轰簬 RAG锛圧etrieval-Augmented Generation锛夋妧鏈疄鐜扮煡璇嗗簱闂瓟鍔熻兘锛屾敮鎸侊細
+- 鐭ヨ瘑搴撶鐞嗭紙CRUD锛�
+- 鏂囦欢涓婁紶涓庡悜閲忓寲澶勭悊
+- 鍩轰簬鍚戦噺妫�绱㈢殑鏅鸿兘闂瓟
+- 澶氱鏂囦欢鏍煎紡鏀寔锛坱xt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df锛�
+
+## 浜屻�佹妧鏈灦鏋�
+
+### 2.1 鎶�鏈爤
+| 缁勪欢 | 鎶�鏈� |
+|------|------|
+| 鍚戦噺鏁版嵁搴� | Pinecone |
+| Embedding妯″瀷 | 闃块噷浜� DashScope text-embedding-v3 |
+| LLM | 闃块噷浜戦�氫箟鍗冮棶 qwen-max |
+| 妗嗘灦 | langchain4j |
+| ORM | MyBatis-Plus |
+
+### 2.2 鏋舵瀯鍥�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                        鍓嶇搴旂敤                              鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                     Controller Layer                         鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹� KnowledgeBaseCtrl   鈹�  鈹� KnowledgeChatController     鈹�   鈹�
+鈹�  鈹� (鐭ヨ瘑搴撶鐞�)         鈹�  鈹� (鐭ヨ瘑搴撻棶绛�)                 鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      Service Layer                           鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹侹nowledgeBaseService 鈹�  鈹� KnowledgeRagService         鈹�   鈹�
+鈹�  鈹� (鐭ヨ瘑搴揅RUD)         鈹�  鈹� (鍚戦噺鍖�/妫�绱�)                鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                              鈹�
+                              鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      AI Layer                                鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹� KnowledgeChatAgent  鈹�  鈹� EmbeddingStore (Pinecone)   鈹�   鈹�
+鈹�  鈹� (闂瓟Agent)          鈹�  鈹� (鍚戦噺瀛樺偍)                   鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 涓夈�佸悗绔疄鐜�
+
+### 3.1 鏁版嵁搴撹璁�
+
+#### 3.1.1 鐭ヨ瘑搴撹〃锛坘nowledge_base锛�
+
+```sql
+CREATE TABLE knowledge_base (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    title VARCHAR(255) COMMENT '鐭ヨ瘑鏍囬',
+    type VARCHAR(50) COMMENT '鐭ヨ瘑绫诲瀷',
+    scenario VARCHAR(255) COMMENT '閫傜敤鍦烘櫙',
+    efficiency VARCHAR(20) COMMENT '瑙e喅鏁堢巼',
+    problem TEXT COMMENT '闂鎻忚堪',
+    solution TEXT COMMENT '瑙e喅鏂规',
+    key_points TEXT COMMENT '鍏抽敭瑕佺偣',
+    creator VARCHAR(100) COMMENT '鍒涘缓浜�',
+    usage_count INT DEFAULT 0 COMMENT '浣跨敤娆℃暟',
+    file_count INT DEFAULT 0 COMMENT '鏂囦欢鏁伴噺',
+    total_chunk_count INT DEFAULT 0 COMMENT '鎬诲垏鐗囨暟閲�',
+    description VARCHAR(500) COMMENT '鐭ヨ瘑搴撴弿杩�',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    create_user INT,
+    update_user INT,
+    tenant_id BIGINT,
+    dept_id BIGINT
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撹〃';
+```
+
+#### 3.1.2 鐭ヨ瘑搴撳悜閲忚褰曡〃锛坘nowledge_base_vector锛�
+
+```sql
+CREATE TABLE knowledge_base_vector (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
+    knowledge_base_id BIGINT NOT NULL COMMENT '鍏宠仈鐭ヨ瘑搴揑D',
+    storage_blob_id BIGINT NOT NULL COMMENT '鍏宠仈鏂囦欢blob ID',
+    file_name VARCHAR(255) NOT NULL COMMENT '鏂囦欢鍚嶇О',
+    file_type VARCHAR(50) NOT NULL COMMENT '鏂囦欢绫诲瀷',
+    vector_status TINYINT DEFAULT 0 COMMENT '鍚戦噺鍖栫姸鎬�: 0-寰呭鐞�, 1-澶勭悊涓�, 2-宸插畬鎴�, 3-澶辫触',
+    vector_error VARCHAR(500) COMMENT '鍚戦噺鍖栧け璐ュ師鍥�',
+    chunk_count INT DEFAULT 0 COMMENT '鍒囩墖鏁伴噺',
+    namespace VARCHAR(100) COMMENT '鍚戦噺鍛藉悕绌洪棿',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    create_user INT,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    update_user INT,
+    tenant_id BIGINT,
+    dept_id BIGINT,
+    INDEX idx_knowledge_base_id (knowledge_base_id),
+    INDEX idx_storage_blob_id (storage_blob_id),
+    INDEX idx_vector_status (vector_status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐭ヨ瘑搴撴枃浠跺悜閲忚褰曡〃';
+```
+
+### 3.2 Maven渚濊禆
+
+```xml
+<!-- langchain4j BOM -->
+<dependencyManagement>
+    <dependencies>
+        <dependency>
+            <groupId>dev.langchain4j</groupId>
+            <artifactId>langchain4j-bom</artifactId>
+            <version>1.0.0-beta3</version>
+            <type>pom</type>
+            <scope>import</scope>
+        </dependency>
+    </dependencies>
+</dependencyManagement>
+
+<dependencies>
+    <!-- langchain4j 鏍稿績 -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-spring-boot-starter</artifactId>
+    </dependency>
+
+    <!-- Pinecone 鍚戦噺鏁版嵁搴� -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-pinecone</artifactId>
+    </dependency>
+
+    <!-- 闃块噷浜� DashScope -->
+    <dependency>
+        <groupId>dev.langchain4j</groupId>
+        <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
+    </dependency>
+</dependencies>
+```
+
+### 3.3 閰嶇疆鏂囦欢锛坅pplication.yml锛�
+
+```yaml
+# Pinecone 鍚戦噺鏁版嵁搴撻厤缃�
+pinecone:
+  api-key: your-pinecone-api-key
+  index: your-index-name
+  namespace: knowledge-base
+
+# langchain4j 閰嶇疆
+langchain4j:
+  community:
+    dashscope:
+      streaming-chat-model:
+        api-key: your-dashscope-api-key
+        model-name: "qwen-max"
+      embedding-model:
+        api-key: your-dashscope-api-key
+        model-name: "text-embedding-v3"
+```
+
+### 3.4 鏍稿績浠g爜瀹炵幇
+
+#### 3.4.1 瀹炰綋绫�
+
+**KnowledgeBase.java**
+```java
+@Data
+@TableName("knowledge_base")
+public class KnowledgeBase implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String title;
+    private String type;
+    private String scenario;
+    private String efficiency;
+    private String problem;
+    private String solution;
+    private String keyPoints;
+    private String creator;
+    private Integer usageCount;
+    private Integer fileCount;
+    private Integer totalChunkCount;
+    private String description;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
+```
+
+**KnowledgeBaseVector.java**
+```java
+@Data
+@TableName("knowledge_base_vector")
+public class KnowledgeBaseVector implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long knowledgeBaseId;
+    private Long storageBlobId;
+    private String fileName;
+    private String fileType;
+    private Integer vectorStatus;
+    private String vectorError;
+    private Integer chunkCount;
+    private String namespace;
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    // 鍚戦噺鍖栫姸鎬佸父閲�
+    public static final int STATUS_PENDING = 0;
+    public static final int STATUS_PROCESSING = 1;
+    public static final int STATUS_COMPLETED = 2;
+    public static final int STATUS_FAILED = 3;
+}
+```
+
+#### 3.4.2 EmbeddingStore閰嶇疆
+
+**EmbeddingStoreConfig.java**
+```java
+@Configuration
+public class EmbeddingStoreConfig {
+
+    @Value("${pinecone.api-key}")
+    private String pineconeApiKey;
+
+    @Value("${pinecone.index}")
+    private String indexName;
+
+    @Value("${pinecone.namespace}")
+    private String namespace;
+
+    @Bean
+    public Pinecone pinecone() {
+        return new Pinecone.Builder(pineconeApiKey).build();
+    }
+
+    @Bean
+    public Index pineconeIndex(Pinecone pinecone) {
+        return pinecone.getIndexConnection(indexName);
+    }
+
+    @Bean
+    public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) {
+        return PineconeEmbeddingStore.builder()
+                .apiKey(pineconeApiKey)
+                .index(indexName)
+                .nameSpace(namespace)
+                .createIndex(PineconeServerlessIndexConfig.builder()
+                        .cloud("AWS")
+                        .region("us-east-1")
+                        .dimension(embeddingModel.dimension())
+                        .build())
+                .build();
+    }
+}
+```
+
+#### 3.4.3 RAG鏈嶅姟瀹炵幇
+
+**KnowledgeRagService.java**
+```java
+public interface KnowledgeRagService {
+    void processVectorAsync(Long vectorId);
+    void processVector(Long vectorId);
+    List<String> searchRelevantContent(String namespace, String query, int maxResults);
+    void deleteEmbeddings(String namespace, Long storageBlobId);
+}
+```
+
+**KnowledgeRagServiceImpl.java**锛堟牳蹇冨疄鐜帮級
+```java
+@Slf4j
+@Service
+public class KnowledgeRagServiceImpl implements KnowledgeRagService {
+
+    private final KnowledgeBaseVectorService knowledgeBaseVectorService;
+    private final StorageBlobService storageBlobService;
+    private final EmbeddingModel embeddingModel;
+    private final EmbeddingStore<TextSegment> embeddingStore;
+    private final FileProperties fileProperties;
+    private final Index pineconeIndex;
+
+    @Value("${pinecone.namespace}")
+    private String namespace;
+
+    private static final int CHUNK_SIZE = 500;
+    private static final int CHUNK_OVERLAP = 100;
+    private static final long CHUNK_THRESHOLD_BYTES = 80L * 1024 * 1024;
+    private static final int EMBEDDING_MAX_LENGTH = 8000;
+
+    @Override
+    @Async("threadPoolTaskExecutor")
+    public void processVectorAsync(Long vectorId) {
+        processVector(vectorId);
+    }
+
+    @Override
+    public void processVector(Long vectorId) {
+        KnowledgeBaseVector vector = knowledgeBaseVectorService.getById(vectorId);
+        if (vector == null) return;
+
+        try {
+            // 鏇存柊鐘舵�佷负澶勭悊涓�
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_PROCESSING, null, null);
+
+            // 鑾峰彇鏂囦欢鍐呭
+            StorageBlob blob = storageBlobService.getById(vector.getStorageBlobId());
+            File file = getFile(blob);
+            String content = extractFileContent(file, vector.getFileName());
+
+            if (content == null || content.trim().isEmpty()) {
+                throw new RuntimeException("鏂囦欢鍐呭涓虹┖");
+            }
+
+            // 鏂囨湰鍒囩墖
+            List<TextSegment> chunks;
+            boolean needChunk = file.length() > CHUNK_THRESHOLD_BYTES || content.length() > EMBEDDING_MAX_LENGTH;
+            if (needChunk) {
+                chunks = splitText(content, vector);
+            } else {
+                Map<String, Object> metadata = buildMetadata(vector);
+                chunks = List.of(TextSegment.from(content, new Metadata(metadata)));
+            }
+
+            // 鐢熸垚宓屽叆鍚戦噺骞跺瓨鍌�
+            int chunkCount = 0;
+            for (TextSegment chunk : chunks) {
+                Embedding embedding = embeddingModel.embed(chunk).content();
+                embeddingStore.add(embedding, chunk);
+                chunkCount++;
+            }
+
+            // 鏇存柊鐘舵�佷负瀹屾垚
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_COMPLETED, chunkCount, null);
+
+        } catch (Exception e) {
+            log.error("鍚戦噺鍖栧鐞嗗け璐�", e);
+            knowledgeBaseVectorService.updateVectorStatus(vectorId, STATUS_FAILED, null, e.getMessage());
+        }
+    }
+
+    @Override
+    public List<String> searchRelevantContent(String namespace, String query, int maxResults) {
+        Embedding queryEmbedding = embeddingModel.embed(query).content();
+        EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
+                .queryEmbedding(queryEmbedding)
+                .maxResults(maxResults)
+                .minScore(0.7)
+                .build();
+        EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(searchRequest);
+        return searchResult.matches().stream()
+                .map(match -> match.embedded().text())
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public void deleteEmbeddings(String namespace, Long storageBlobId) {
+        Struct filter = Struct.newBuilder()
+                .putFields("storageBlobId", Value.newBuilder()
+                        .setStructValue(Struct.newBuilder()
+                                .putFields("$eq", Value.newBuilder()
+                                        .setNumberValue(storageBlobId.doubleValue())
+                                        .build()))
+                        .build())
+                .build();
+        pineconeIndex.delete(new ArrayList<>(), false, this.namespace, filter);
+    }
+
+    private String extractFileContent(File file, String fileName) throws Exception {
+        String ext = getFileExtension(fileName);
+        if (isPlainText(ext)) {
+            return readFileWithEncoding(file);
+        }
+        if ("docx".equals(ext)) {
+            return extractDocx(file);
+        }
+        if ("xlsx".equals(ext) || "xls".equals(ext)) {
+            return extractExcel(file);
+        }
+        return readFileWithEncoding(file);
+    }
+
+    // ... 鍏朵粬杈呭姪鏂规硶
+}
+```
+
+#### 3.4.4 鐭ヨ瘑搴撻棶绛擜gent
+
+**KnowledgeChatAgent.java**
+```java
+@AiService(
+        wiringMode = EXPLICIT,
+        streamingChatModel = "qwenStreamingChatModel",
+        chatMemoryProvider = "chatMemoryProvider"
+)
+public interface KnowledgeChatAgent {
+
+    @SystemMessage("""
+            浣犳槸浼佷笟鐭ヨ瘑搴撻棶绛斿姪鎵嬨��
+            浣犻渶瑕佸熀浜庢彁渚涚殑鐭ヨ瘑搴撳唴瀹瑰洖绛旂敤鎴烽棶棰樸��
+            閬靛惊浠ヤ笅瑙勫垯锛�
+            1. 涓ユ牸鍩轰簬鐭ヨ瘑搴撳唴瀹瑰洖绛旓紝涓嶈缂栭�犱俊鎭�
+            2. 濡傛灉鐭ヨ瘑搴撲腑娌℃湁鐩稿叧淇℃伅锛屾槑纭憡鐭ョ敤鎴�
+            3. 鍥炵瓟瑕佸噯纭�佺畝娲併�佹湁鏉$悊
+            4. 寮曠敤鏉ユ簮鏃舵敞鏄�"鏍规嵁鐭ヨ瘑搴撳唴瀹�"
+            """)
+    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
+}
+```
+
+#### 3.4.5 Controller灞�
+
+**KnowledgeBaseController.java**
+```java
+@RestController
+@RequestMapping("/knowledgeBase")
+@Tag(name = "鐭ヨ瘑搴撶鐞�")
+public class KnowledgeBaseController {
+
+    @GetMapping("/getList")
+    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
+                              @RequestParam(defaultValue = "10") long size,
+                              KnowledgeBase knowledgeBase) {
+        Page page = new Page(current, size);
+        return AjaxResult.success(knowledgeBaseService.listpage(page, knowledgeBase));
+    }
+
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase) {
+        return AjaxResult.success(knowledgeBaseService.save(knowledgeBase));
+    }
+
+    @PostMapping("/update")
+    public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase) {
+        return AjaxResult.success(knowledgeBaseService.updateById(knowledgeBase));
+    }
+
+    @DeleteMapping("/delete")
+    public AjaxResult delete(@RequestBody List<Long> ids) {
+        return AjaxResult.success(knowledgeBaseService.removeByIds(ids));
+    }
+
+    @GetMapping("/vector/status/{knowledgeBaseId}")
+    @Operation(summary = "鏌ヨ鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��")
+    public AjaxResult getVectorStatus(@PathVariable Long knowledgeBaseId) {
+        return AjaxResult.success(knowledgeBaseVectorService.getVectorStatusByKnowledgeBaseId(knowledgeBaseId));
+    }
+
+    @PostMapping("/vector/reprocess/{vectorId}")
+    @Operation(summary = "閲嶆柊鍚戦噺鍖栨枃浠�")
+    public AjaxResult reprocessVector(@PathVariable Long vectorId) {
+        knowledgeBaseVectorService.reprocessVector(vectorId);
+        return AjaxResult.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    }
+
+    @PostMapping("/file/save")
+    @Operation(summary = "淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�")
+    public AjaxResult saveKnowledgeBaseFiles(@RequestBody KnowledgeBaseFileDTO dto) {
+        // 淇濆瓨闄勪欢鍏宠仈骞惰Е鍙戝悜閲忓寲
+        // ...
+    }
+
+    @DeleteMapping("/file/delete")
+    @Operation(summary = "鍒犻櫎鐭ヨ瘑搴撴枃浠�")
+    public AjaxResult deleteKnowledgeBaseFiles(@RequestBody List<Long> vectorIds) {
+        knowledgeBaseVectorService.deleteVectors(vectorIds);
+        return AjaxResult.success();
+    }
+}
+```
+
+**KnowledgeChatController.java**
+```java
+@RestController
+@RequestMapping("/ai/knowledge")
+@Tag(name = "鐭ヨ瘑搴撻棶绛�")
+public class KnowledgeChatController {
+
+    private final KnowledgeChatAgent knowledgeChatAgent;
+    private final KnowledgeRagService knowledgeRagService;
+    private final KnowledgeBaseService knowledgeBaseService;
+
+    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
+    @Operation(summary = "鐭ヨ瘑搴撻棶绛�")
+    public Flux<String> chat(@RequestBody KnowledgeChatRequest request) {
+        // 妫�绱㈢浉鍏冲唴瀹�
+        String namespace = "kb-" + request.getKnowledgeBaseId();
+        List<String> relevantContents = knowledgeRagService.searchRelevantContent(
+                namespace, request.getQuestion(), 5);
+
+        if (relevantContents.isEmpty()) {
+            return Flux.just("鐭ヨ瘑搴撲腑鏈壘鍒扮浉鍏冲唴瀹�");
+        }
+
+        // 鏋勫缓涓婁笅鏂�
+        StringBuilder context = new StringBuilder();
+        context.append("浠ヤ笅鏄粠鐭ヨ瘑搴撲腑妫�绱㈠埌鐨勭浉鍏冲唴瀹癸細\n\n");
+        for (int i = 0; i < relevantContents.size(); i++) {
+            context.append("銆愬唴瀹�").append(i + 1).append("銆慭n");
+            context.append(relevantContents.get(i)).append("\n\n");
+        }
+        context.append("---\n璇峰熀浜庝互涓婄煡璇嗗簱鍐呭鍥炵瓟锛歕n").append(request.getQuestion());
+
+        return knowledgeChatAgent.chat(request.getMemoryId(), context.toString());
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鐭ヨ瘑搴撳垪琛�")
+    public AjaxResult listKnowledgeBases() {
+        return AjaxResult.success(knowledgeBaseService.list());
+    }
+}
+```
+
+---
+
+## 鍥涖�丄PI鎺ュ彛鏂囨。
+
+### 4.1 鐭ヨ瘑搴撶鐞嗘帴鍙�
+
+| 鎺ュ彛 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 鑾峰彇鍒楄〃 | GET | /knowledgeBase/getList | 鍒嗛〉鏌ヨ鐭ヨ瘑搴撳垪琛� |
+| 鏂板鐭ヨ瘑搴� | POST | /knowledgeBase/add | 鍒涘缓鐭ヨ瘑搴� |
+| 鏇存柊鐭ヨ瘑搴� | POST | /knowledgeBase/update | 鏇存柊鐭ヨ瘑搴撲俊鎭� |
+| 鍒犻櫎鐭ヨ瘑搴� | DELETE | /knowledgeBase/delete | 鎵归噺鍒犻櫎鐭ヨ瘑搴� |
+| 鏌ヨ鍚戦噺鍖栫姸鎬� | GET | /knowledgeBase/vector/status/{id} | 鏌ヨ鏂囦欢鍚戦噺鍖栫姸鎬� |
+| 閲嶆柊鍚戦噺鍖� | POST | /knowledgeBase/vector/reprocess/{id} | 閲嶆柊澶勭悊澶辫触鐨勬枃浠� |
+| 淇濆瓨鏂囦欢鍏宠仈 | POST | /knowledgeBase/file/save | 涓婁紶鏂囦欢鍚庡叧鑱斿埌鐭ヨ瘑搴� |
+| 鍒犻櫎鏂囦欢 | DELETE | /knowledgeBase/file/delete | 鍒犻櫎鐭ヨ瘑搴撴枃浠� |
+
+### 4.2 鐭ヨ瘑搴撻棶绛旀帴鍙�
+
+| 鎺ュ彛 | 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| 鐭ヨ瘑搴撻棶绛� | POST | /ai/knowledge/chat | 娴佸紡杩斿洖闂瓟缁撴灉 |
+| 鐭ヨ瘑搴撳垪琛� | GET | /ai/knowledge/list | 鑾峰彇鍙�夌煡璇嗗簱鍒楄〃 |
+
+### 4.3 鎺ュ彛璇︾粏璇存槑
+
+#### 4.3.1 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�
+
+**璇锋眰**
+```json
+POST /knowledgeBase/file/save
+{
+    "knowledgeBaseId": 1,
+    "storageBlobIds": [100, 101, 102]
+}
+```
+
+**鍝嶅簲**
+```json
+{
+    "code": 200,
+    "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+#### 4.3.2 鐭ヨ瘑搴撻棶绛�
+
+**璇锋眰**
+```json
+POST /ai/knowledge/chat
+Content-Type: application/json
+
+{
+    "knowledgeBaseId": 1,
+    "memoryId": "session-uuid",
+    "question": "濡備綍澶勭悊搴撳瓨鐩樼偣宸紓锛�"
+}
+```
+
+**鍝嶅簲**锛圫SE娴佸紡锛�
+```
+鏍规嵁鐭ヨ瘑搴撳唴瀹癸紝搴撳瓨鐩樼偣宸紓鐨勫鐞嗘祦绋嬪涓嬶細
+
+1. 鍙戠幇宸紓鍚庯紝棣栧厛鏍稿鐩樼偣璁板綍...
+2. 妫�鏌ユ槸鍚︽湁婕忕洏鎴栭敊鐩�...
+3. ...
+```
+
+---
+
+## 浜斻�佸墠绔疄鐜�
+
+### 5.1 鐭ヨ瘑搴撶鐞嗛〉闈�
+
+```vue
+<template>
+  <div class="knowledge-base">
+    <!-- 鍒楄〃 -->
+    <el-table :data="tableData" border>
+      <el-table-column prop="title" label="鐭ヨ瘑鏍囬" />
+      <el-table-column prop="type" label="鐭ヨ瘑绫诲瀷" />
+      <el-table-column prop="fileCount" label="鏂囦欢鏁伴噺" />
+      <el-table-column prop="totalChunkCount" label="鍒囩墖鏁伴噺" />
+      <el-table-column label="鎿嶄綔">
+        <template #default="{ row }">
+          <el-button @click="handleEdit(row)">缂栬緫</el-button>
+          <el-button @click="handleFiles(row)">鏂囦欢绠$悊</el-button>
+          <el-button @click="handleChat(row)">闂瓟</el-button>
+          <el-button type="danger" @click="handleDelete(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getKnowledgeBaseList, deleteKnowledgeBase } from '@/api/knowledge'
+
+const tableData = ref([])
+
+const loadData = async () => {
+  const res = await getKnowledgeBaseList({ current: 1, size: 10 })
+  tableData.value = res.data.records
+}
+
+onMounted(loadData)
+</script>
+```
+
+### 5.2 鏂囦欢涓婁紶涓庡悜閲忓寲鐘舵��
+
+```vue
+<template>
+  <div class="file-manager">
+    <!-- 鏂囦欢涓婁紶 -->
+    <el-upload
+      :action="uploadUrl"
+      :on-success="handleUploadSuccess"
+      multiple
+    >
+      <el-button type="primary">涓婁紶鏂囦欢</el-button>
+    </el-upload>
+
+    <!-- 鏂囦欢鍒楄〃涓庡悜閲忓寲鐘舵�� -->
+    <el-table :data="fileList">
+      <el-table-column prop="fileName" label="鏂囦欢鍚�" />
+      <el-table-column label="鍚戦噺鍖栫姸鎬�">
+        <template #default="{ row }">
+          <el-tag :type="getStatusType(row.vectorStatus)">
+            {{ getStatusText(row.vectorStatus) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="chunkCount" label="鍒囩墖鏁�" />
+      <el-table-column label="鎿嶄綔">
+        <template #default="{ row }">
+          <el-button v-if="row.vectorStatus === 3" @click="reprocess(row)">
+            閲嶆柊澶勭悊
+          </el-button>
+          <el-button type="danger" @click="deleteFile(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script setup>
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/upload'
+
+// 涓婁紶鎴愬姛鍚庝繚瀛樺叧鑱�
+const uploadedBlobIds = ref([])
+
+const handleUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobIds.value.push(response.data.id)
+  }
+}
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+  await saveKnowledgeBaseFiles({
+    knowledgeBaseId: props.knowledgeBaseId,
+    storageBlobIds: uploadedBlobIds.value
+  })
+  // 鍒锋柊鏂囦欢鍒楄〃
+  loadFileList()
+}
+
+// 鐘舵�佹枃鏈槧灏�
+const getStatusText = (status) => {
+  const map = {
+    0: '寰呭鐞�',
+    1: '澶勭悊涓�',
+    2: '宸插畬鎴�',
+    3: '澶辫触'
+  }
+  return map[status] || '鏈煡'
+}
+
+const getStatusType = (status) => {
+  const map = {
+    0: 'info',
+    1: 'warning',
+    2: 'success',
+    3: 'danger'
+  }
+  return map[status] || 'info'
+}
+</script>
+```
+
+### 5.3 鐭ヨ瘑搴撻棶绛旂晫闈�
+
+```vue
+<template>
+  <div class="knowledge-chat">
+    <!-- 鐭ヨ瘑搴撻�夋嫨 -->
+    <el-select v-model="selectedKbId" placeholder="閫夋嫨鐭ヨ瘑搴�">
+      <el-option
+        v-for="kb in knowledgeBases"
+        :key="kb.id"
+        :label="kb.title"
+        :value="kb.id"
+      />
+    </el-select>
+
+    <!-- 瀵硅瘽鍖哄煙 -->
+    <div class="chat-messages">
+      <div
+        v-for="(msg, index) in messages"
+        :key="index"
+        :class="['message', msg.role]"
+      >
+        <div class="content">{{ msg.content }}</div>
+      </div>
+    </div>
+
+    <!-- 杈撳叆妗� -->
+    <el-input
+      v-model="inputQuestion"
+      placeholder="璇疯緭鍏ラ棶棰�"
+      @keyup.enter="sendMessage"
+    >
+      <template #append>
+        <el-button @click="sendMessage" :loading="loading">鍙戦��</el-button>
+      </template>
+    </el-input>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getKnowledgeBaseList, knowledgeChat } from '@/api/knowledge'
+
+const knowledgeBases = ref([])
+const selectedKbId = ref(null)
+const messages = ref([])
+const inputQuestion = ref('')
+const loading = ref(false)
+const memoryId = ref(crypto.randomUUID())
+
+const sendMessage = async () => {
+  if (!inputQuestion.value.trim()) return
+  if (!selectedKbId.value) {
+    ElMessage.warning('璇烽�夋嫨鐭ヨ瘑搴�')
+    return
+  }
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  messages.value.push({
+    role: 'user',
+    content: inputQuestion.value
+  })
+
+  loading.value = true
+
+  try {
+    // 娴佸紡璇锋眰
+    const response = await fetch('/api/ai/knowledge/chat', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({
+        knowledgeBaseId: selectedKbId.value,
+        memoryId: memoryId.value,
+        question: inputQuestion.value
+      })
+    })
+
+    // 澶勭悊SSE娴佸紡鍝嶅簲
+    const reader = response.body.getReader()
+    const decoder = new TextDecoder()
+    let aiContent = ''
+
+    messages.value.push({ role: 'assistant', content: '' })
+
+    while (true) {
+      const { done, value } = await reader.read()
+      if (done) break
+
+      const text = decoder.decode(value)
+      aiContent += text
+      messages.value[messages.value.length - 1].content = aiContent
+    }
+  } finally {
+    loading.value = false
+    inputQuestion.value = ''
+  }
+}
+
+onMounted(async () => {
+  const res = await getKnowledgeBaseList()
+  knowledgeBases.value = res.data
+})
+</script>
+```
+
+### 5.4 API灏佽
+
+```javascript
+// api/knowledge.js
+import request from '@/utils/request'
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛�
+export function getKnowledgeBaseList(params) {
+  return request({
+    url: '/knowledgeBase/getList',
+    method: 'get',
+    params
+  })
+}
+
+// 鏂板鐭ヨ瘑搴�
+export function addKnowledgeBase(data) {
+  return request({
+    url: '/knowledgeBase/add',
+    method: 'post',
+    data
+  })
+}
+
+// 鏇存柊鐭ヨ瘑搴�
+export function updateKnowledgeBase(data) {
+  return request({
+    url: '/knowledgeBase/update',
+    method: 'post',
+    data
+  })
+}
+
+// 鍒犻櫎鐭ヨ瘑搴�
+export function deleteKnowledgeBase(ids) {
+  return request({
+    url: '/knowledgeBase/delete',
+    method: 'delete',
+    data: ids
+  })
+}
+
+// 鑾峰彇鏂囦欢鍚戦噺鍖栫姸鎬�
+export function getVectorStatus(knowledgeBaseId) {
+  return request({
+    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+    method: 'get'
+  })
+}
+
+// 閲嶆柊鍚戦噺鍖�
+export function reprocessVector(vectorId) {
+  return request({
+    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+    method: 'post'
+  })
+}
+
+// 淇濆瓨鏂囦欢鍏宠仈
+export function saveKnowledgeBaseFiles(data) {
+  return request({
+    url: '/knowledgeBase/file/save',
+    method: 'post',
+    data
+  })
+}
+
+// 鍒犻櫎鏂囦欢
+export function deleteKnowledgeBaseFiles(vectorIds) {
+  return request({
+    url: '/knowledgeBase/file/delete',
+    method: 'delete',
+    data: vectorIds
+  })
+}
+
+// 鐭ヨ瘑搴撻棶绛旓紙娴佸紡锛�
+export async function knowledgeChat(data) {
+  const response = await fetch('/api/ai/knowledge/chat', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(data)
+  })
+  return response.body
+}
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛紙闂瓟鐢級
+export function getKnowledgeBaseListForChat() {
+  return request({
+    url: '/ai/knowledge/list',
+    method: 'get'
+  })
+}
+```
+
+---
+
+## 鍏�佹牳蹇冩祦绋�
+
+### 6.1 鏂囦欢涓婁紶涓庡悜閲忓寲娴佺▼
+
+```
+1. 鍓嶇璋冪敤 /common/upload 涓婁紶鏂囦欢 鈫� 杩斿洖 storageBlobId
+2. 鍓嶇璋冪敤 /knowledgeBase/file/save 鍏宠仈鏂囦欢鍒扮煡璇嗗簱
+3. 鍚庣鍒涘缓 KnowledgeBaseVector 璁板綍锛堢姸鎬侊細寰呭鐞嗭級
+4. 鍚庣寮傛璋冪敤 KnowledgeRagService.processVectorAsync()
+   鈹溾攢鈹� 鏇存柊鐘舵�佷负"澶勭悊涓�"
+   鈹溾攢鈹� 鎻愬彇鏂囦欢鍐呭锛堟敮鎸佸绉嶆牸寮忥級
+   鈹溾攢鈹� 鑷姩妫�娴嬫枃浠剁紪鐮侊紙UTF-8/GBK锛�
+   鈹溾攢鈹� 鏂囨湰鍒囩墖锛堝ぇ鏂囦欢鎴栭暱鍐呭鎵嶅垏鐗囷級
+   鈹溾攢鈹� 鐢熸垚 Embedding 鍚戦噺
+   鈹溾攢鈹� 瀛樺偍鍒� Pinecone
+   鈹斺攢鈹� 鏇存柊鐘舵�佷负"瀹屾垚"鎴�"澶辫触"
+```
+
+### 6.2 鐭ヨ瘑搴撻棶绛旀祦绋�
+
+```
+1. 鐢ㄦ埛閫夋嫨鐭ヨ瘑搴擄紝杈撳叆闂
+2. 鍓嶇璋冪敤 /ai/knowledge/chat锛堟祦寮忔帴鍙o級
+3. 鍚庣澶勭悊锛�
+   鈹溾攢鈹� 鏋勫缓鍛藉悕绌洪棿锛歬b-{knowledgeBaseId}
+   鈹溾攢鈹� 璋冪敤 Embedding 妯″瀷鐢熸垚闂鍚戦噺
+   鈹溾攢鈹� 浠� Pinecone 妫�绱㈢浉鍏冲唴瀹癸紙minScore=0.7, maxResults=5锛�
+   鈹溾攢鈹� 鏋勫缓涓婁笅鏂� Prompt
+   鈹溾攢鈹� 璋冪敤 LLM 鐢熸垚鍥炵瓟
+   鈹斺攢鈹� 娴佸紡杩斿洖缁撴灉
+```
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+1. **Pinecone 鍛藉悕绌洪棿**锛氫笉鑳戒娇鐢� `__default__`锛屽繀椤讳娇鐢ㄨ嚜瀹氫箟鍛藉悕绌洪棿
+2. **鏂囦欢缂栫爜**锛氳嚜鍔ㄦ娴� UTF-8/GBK锛岄伩鍏嶄贡鐮�
+3. **鍒囩墖绛栫暐**锛�
+   - 鏂囦欢 > 80MB 鎴栧唴瀹� > 8000 瀛楃鏃舵墠鍒囩墖
+   - 鍒囩墖澶у皬 500 瀛楃锛岄噸鍙� 100 瀛楃
+   - 浼樺厛鍦ㄥ彞瀛愯竟鐣屽垏鍒�
+4. **Embedding 闄愬埗**锛氶樋閲屼簯 DashScope 闄愬埗鍗曟杈撳叆鏈�澶� 8192 瀛楃
+5. **鍚戦噺鍒犻櫎**锛氫娇鐢� Pinecone 鍘熺敓瀹㈡埛绔紝閫氳繃 metadata filter 鍒犻櫎
+6. **寮傛澶勭悊**锛氬悜閲忓寲浣跨敤 `@Async` 寮傛鎵ц锛岄伩鍏嶉樆濉炴帴鍙�
+
+---
+
+## 鍏�佹枃浠舵竻鍗�
+
+### 鍚庣鏂囦欢
+```
+src/main/java/com/ruoyi/
+鈹溾攢鈹� approve/
+鈹�   鈹溾攢鈹� controller/
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseController.java
+鈹�   鈹溾攢鈹� pojo/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBase.java
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseVector.java
+鈹�   鈹溾攢鈹� service/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseService.java
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseVectorService.java
+鈹�   鈹�   鈹斺攢鈹� impl/
+鈹�   鈹�       鈹溾攢鈹� KnowledgeBaseServiceImpl.java
+鈹�   鈹�       鈹斺攢鈹� KnowledgeBaseVectorServiceImpl.java
+鈹�   鈹溾攢鈹� mapper/
+鈹�   鈹�   鈹溾攢鈹� KnowledgeBaseMapper.java
+鈹�   鈹�   鈹斺攢鈹� KnowledgeBaseVectorMapper.java
+鈹�   鈹斺攢鈹� dto/
+鈹�       鈹斺攢鈹� KnowledgeBaseVectorVO.java
+鈹斺攢鈹� ai/
+    鈹溾攢鈹� config/
+    鈹�   鈹溾攢鈹� EmbeddingStoreConfig.java
+    鈹�   鈹斺攢鈹� XiaozhiAgentConfig.java
+    鈹溾攢鈹� controller/
+    鈹�   鈹斺攢鈹� KnowledgeChatController.java
+    鈹溾攢鈹� assistant/
+    鈹�   鈹斺攢鈹� KnowledgeChatAgent.java
+    鈹溾攢鈹� service/
+    鈹�   鈹溾攢鈹� KnowledgeRagService.java
+    鈹�   鈹斺攢鈹� impl/
+    鈹�       鈹斺攢鈹� KnowledgeRagServiceImpl.java
+    鈹斺攢鈹� dto/
+        鈹斺攢鈹� KnowledgeChatRequest.java
+```
+
+### 鍓嶇鏂囦欢
+```
+src/views/knowledge/
+鈹溾攢鈹� index.vue              # 鐭ヨ瘑搴撳垪琛�
+鈹溾攢鈹� form.vue               # 鏂板/缂栬緫琛ㄥ崟
+鈹溾攢鈹� files.vue              # 鏂囦欢绠$悊
+鈹斺攢鈹� chat.vue               # 鐭ヨ瘑搴撻棶绛�
+
+src/api/knowledge.js       # API灏佽
+```
\ No newline at end of file
diff --git a/src/api/collaborativeApproval/knowledgeBase.js b/src/api/collaborativeApproval/knowledgeBase.js
index b195525..8ad24dd 100644
--- a/src/api/collaborativeApproval/knowledgeBase.js
+++ b/src/api/collaborativeApproval/knowledgeBase.js
@@ -53,3 +53,55 @@
   });
 }
 
+// 鑾峰彇鐭ヨ瘑搴撴枃浠跺悜閲忓寲鐘舵��
+export function getVectorStatus(knowledgeBaseId) {
+  return request({
+    url: `/knowledgeBase/vector/status/${knowledgeBaseId}`,
+    method: "get",
+  });
+}
+
+// 閲嶆柊鍚戦噺鍖栨枃浠�
+export function reprocessVector(vectorId) {
+  return request({
+    url: `/knowledgeBase/vector/reprocess/${vectorId}`,
+    method: "post",
+  });
+}
+
+// 淇濆瓨鐭ヨ瘑搴撴枃浠跺叧鑱�
+export function saveKnowledgeBaseFiles(data) {
+  return request({
+    url: "/knowledgeBase/file/save",
+    method: "post",
+    data: data,
+  });
+}
+
+// 鍒犻櫎鐭ヨ瘑搴撴枃浠�
+export function deleteKnowledgeBaseFiles(vectorIds) {
+  return request({
+    url: "/knowledgeBase/file/delete",
+    method: "delete",
+    data: vectorIds,
+  });
+}
+
+// 鑾峰彇鐭ヨ瘑搴撳垪琛�(闂瓟鐢�)
+export function getKnowledgeBaseListForChat() {
+  return request({
+    url: "/ai/knowledge/list",
+    method: "get",
+  });
+}
+
+// 鐭ヨ瘑搴撻棶绛�(娴佸紡)
+export async function knowledgeChat(data) {
+  const response = await fetch("/api/ai/knowledge/chat", {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify(data),
+  });
+  return response.body;
+}
+
diff --git a/src/views/collaborativeApproval/knowledgeBase/index.vue b/src/views/collaborativeApproval/knowledgeBase/index.vue
index 43fee33..8d625cf 100644
--- a/src/views/collaborativeApproval/knowledgeBase/index.vue
+++ b/src/views/collaborativeApproval/knowledgeBase/index.vue
@@ -225,18 +225,141 @@
         </div>
       </div>
     </FormDialog>
+
+    <!-- 鏂囦欢绠$悊寮圭獥 -->
+    <FormDialog
+      v-model="filesDialogVisible"
+      title="鏂囦欢绠$悊"
+      :width="'900px'"
+      @close="closeFilesDialog"
+      @confirm="closeFilesDialog"
+      @cancel="closeFilesDialog"
+    >
+      <div class="file-manager">
+        <!-- 鏂囦欢涓婁紶 -->
+        <div class="upload-section">
+          <el-upload
+            :action="uploadUrl"
+            :headers="uploadHeaders"
+            :on-success="handleUploadSuccess"
+            :on-error="handleUploadError"
+            :before-upload="beforeUpload"
+            multiple
+            :show-file-list="false"
+            accept=".txt,.md,.docx,.xlsx,.xls,.pdf"
+          >
+            <el-button type="primary">涓婁紶鏂囦欢</el-button>
+          </el-upload>
+          <el-button
+            type="success"
+            @click="saveFiles"
+            :disabled="uploadedBlobIds.length === 0"
+            :loading="savingFiles"
+            style="margin-left: 10px"
+          >
+            淇濆瓨鏂囦欢鍏宠仈
+          </el-button>
+        </div>
+
+        <!-- 鏂囦欢鍒楄〃涓庡悜閲忓寲鐘舵�� -->
+        <el-table :data="fileList" style="margin-top: 20px" border>
+          <el-table-column prop="fileName" label="鏂囦欢鍚�" show-overflow-tooltip />
+          <el-table-column prop="fileType" label="鏂囦欢绫诲瀷" width="100" />
+          <el-table-column label="鍚戦噺鍖栫姸鎬�" width="120">
+            <template #default="{ row }">
+              <el-tag :type="getStatusType(row.vectorStatus)">
+                {{ getStatusText(row.vectorStatus) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="chunkCount" label="鍒囩墖鏁�" width="100" align="center" />
+          <el-table-column prop="createTime" label="涓婁紶鏃堕棿" width="180" />
+          <el-table-column label="鎿嶄綔" width="150" align="center">
+            <template #default="{ row }">
+              <el-button
+                v-if="row.vectorStatus === 3"
+                type="text"
+                @click="reprocessFile(row)"
+              >
+                閲嶆柊澶勭悊
+              </el-button>
+              <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c">
+                鍒犻櫎
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </FormDialog>
+
+    <!-- 鐭ヨ瘑搴撻棶绛斿脊绐� -->
+    <FormDialog
+      v-model="chatDialogVisible"
+      title="鐭ヨ瘑搴撻棶绛�"
+      :width="'800px'"
+      @close="closeChatDialog"
+      @confirm="closeChatDialog"
+      @cancel="closeChatDialog"
+    >
+      <div class="knowledge-chat">
+        <div class="chat-header">
+          <el-tag type="success">褰撳墠鐭ヨ瘑搴�: {{ currentKnowledgeBase?.title }}</el-tag>
+        </div>
+
+        <!-- 瀵硅瘽鍖哄煙 -->
+        <div class="chat-messages" ref="chatMessagesRef">
+          <div
+            v-for="(msg, index) in messages"
+            :key="index"
+            :class="['message', msg.role]"
+          >
+            <div class="message-role">{{ msg.role === 'user' ? '鎴�' : 'AI鍔╂墜' }}</div>
+            <div class="message-content">{{ msg.content }}</div>
+          </div>
+          <div v-if="chatLoading" class="message assistant">
+            <div class="message-role">AI鍔╂墜</div>
+            <div class="message-content typing">姝e湪鎬濊�冧腑...</div>
+          </div>
+        </div>
+
+        <!-- 杈撳叆妗� -->
+        <div class="chat-input">
+          <el-input
+            v-model="inputQuestion"
+            placeholder="璇疯緭鍏ラ棶棰橈紝鎸夊洖杞﹀彂閫�"
+            @keyup.enter="sendMessage"
+            :disabled="chatLoading"
+          >
+            <template #append>
+              <el-button @click="sendMessage" :loading="chatLoading">鍙戦��</el-button>
+            </template>
+          </el-input>
+        </div>
+      </div>
+    </FormDialog>
   </div>
 </template>
 
 <script setup>
 import { Search } from "@element-plus/icons-vue";
-import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch } from "vue";
+import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch, nextTick } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import PIMTable from "@/components/PIMTable/PIMTable.vue";
 import FormDialog from '@/components/Dialog/FormDialog.vue';
-import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js";
+import {
+  listKnowledgeBase,
+  delKnowledgeBase,
+  addKnowledgeBase,
+  updateKnowledgeBase,
+  getVectorStatus,
+  reprocessVector,
+  saveKnowledgeBaseFiles,
+  deleteKnowledgeBaseFiles,
+  knowledgeChat
+} from "@/api/collaborativeApproval/knowledgeBase.js";
 import useUserStore from '@/store/modules/user';
 import { userListNoPageByTenantId } from '@/api/system/user.js';
+import { getToken } from "@/utils/auth";
 
 // 琛ㄥ崟楠岃瘉瑙勫垯
 const rules = {
@@ -283,7 +406,17 @@
   dialogTitle: "",
   dialogType: "add",
   viewDialogVisible: false,
-  currentKnowledge: {}
+  currentKnowledge: {},
+  filesDialogVisible: false,
+  currentKnowledgeBase: null,
+  fileList: [],
+  uploadedBlobIds: [],
+  savingFiles: false,
+  chatDialogVisible: false,
+  messages: [],
+  inputQuestion: "",
+  chatLoading: false,
+  memoryId: ""
 });
 
 const {
@@ -297,7 +430,17 @@
   dialogTitle,
   dialogType,
   viewDialogVisible,
-  currentKnowledge
+  currentKnowledge,
+  filesDialogVisible,
+  currentKnowledgeBase,
+  fileList,
+  uploadedBlobIds,
+  savingFiles,
+  chatDialogVisible,
+  messages,
+  inputQuestion,
+  chatLoading,
+  memoryId
 } = toRefs(data);
 
 // 琛ㄥ崟寮曠敤
@@ -305,6 +448,12 @@
 // 鐢ㄦ埛鐩稿叧
 const userStore = useUserStore();
 const userList = ref([]);
+// 鑱婂ぉ娑堟伅瀹瑰櫒寮曠敤
+const chatMessagesRef = ref();
+
+// 鏂囦欢涓婁紶鐩稿叧
+const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload";
+const uploadHeaders = { Authorization: "Bearer " + getToken() };
 
 // 琛ㄦ牸鍒楅厤缃�
 const tableColumn = ref([
@@ -352,6 +501,18 @@
     }
   },
   {
+    label: "鏂囦欢鏁伴噺",
+    prop: "fileCount",
+    width: 100,
+    align: "center"
+  },
+  {
+    label: "鍒囩墖鏁伴噺",
+    prop: "totalChunkCount",
+    width: 100,
+    align: "center"
+  },
+  {
     label: "浣跨敤娆℃暟",
     prop: "usageCount",
     width: 100,
@@ -379,6 +540,20 @@
         type: "text",
         clickFun: (row) => {
           openForm("edit", row);
+        }
+      },
+      {
+        name: "鏂囦欢",
+        type: "text",
+        clickFun: (row) => {
+          openFilesDialog(row);
+        }
+      },
+      {
+        name: "闂瓟",
+        type: "text",
+        clickFun: (row) => {
+          openChatDialog(row);
         }
       },
       {
@@ -680,6 +855,260 @@
 const handleExport = () => {
   proxy.download('/knowledgeBase/export', { ...searchForm.value }, '鐭ヨ瘑搴�.xlsx')
 }
+
+// ============ 鏂囦欢绠$悊鐩稿叧 ============
+
+// 鎵撳紑鏂囦欢绠$悊寮圭獥
+const openFilesDialog = (row) => {
+  currentKnowledgeBase.value = row;
+  filesDialogVisible.value = true;
+  loadFileList();
+};
+
+// 鍔犺浇鏂囦欢鍒楄〃
+const loadFileList = async () => {
+  if (!currentKnowledgeBase.value?.id) return;
+
+  try {
+    const res = await getVectorStatus(currentKnowledgeBase.value.id);
+    fileList.value = res.data || [];
+  } catch (error) {
+    console.error("鍔犺浇鏂囦欢鍒楄〃澶辫触:", error);
+    ElMessage.error("鍔犺浇鏂囦欢鍒楄〃澶辫触");
+  }
+};
+
+// 涓婁紶鍓嶆牎楠�
+const beforeUpload = (file) => {
+  const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf'];
+  const fileName = file.name.toLowerCase();
+  const isAllowed = allowedTypes.some(type => fileName.endsWith(type));
+
+  if (!isAllowed) {
+    ElMessage.error('鍙敮鎸� txt銆乵d銆乨ocx銆亁lsx銆亁ls銆乸df 鏍煎紡鐨勬枃浠�');
+    return false;
+  }
+
+  const isLt50M = file.size / 1024 / 1024 < 50;
+  if (!isLt50M) {
+    ElMessage.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 50MB');
+    return false;
+  }
+
+  return true;
+};
+
+// 涓婁紶鎴愬姛
+const handleUploadSuccess = (response, file) => {
+  if (response.code === 200) {
+    uploadedBlobIds.value.push(response.data.id);
+    ElMessage.success(`鏂囦欢 ${file.name} 涓婁紶鎴愬姛`);
+  } else {
+    ElMessage.error(response.msg || "涓婁紶澶辫触");
+  }
+};
+
+// 涓婁紶澶辫触
+const handleUploadError = (error, file) => {
+  ElMessage.error(`鏂囦欢 ${file.name} 涓婁紶澶辫触`);
+};
+
+// 淇濆瓨鏂囦欢鍏宠仈
+const saveFiles = async () => {
+  if (uploadedBlobIds.value.length === 0) {
+    ElMessage.warning("璇峰厛涓婁紶鏂囦欢");
+    return;
+  }
+
+  savingFiles.value = true;
+  try {
+    await saveKnowledgeBaseFiles({
+      knowledgeBaseId: currentKnowledgeBase.value.id,
+      storageBlobIds: uploadedBlobIds.value
+    });
+
+    ElMessage.success("鏂囦欢鍏宠仈淇濆瓨鎴愬姛锛屾鍦ㄥ悗鍙板鐞嗗悜閲忓寲");
+    uploadedBlobIds.value = [];
+
+    // 寤惰繜鍒锋柊鏂囦欢鍒楄〃锛岀粰鍚庡彴澶勭悊鏃堕棿
+    setTimeout(() => {
+      loadFileList();
+    }, 1000);
+  } catch (error) {
+    console.error("淇濆瓨鏂囦欢鍏宠仈澶辫触:", error);
+    ElMessage.error("淇濆瓨鏂囦欢鍏宠仈澶辫触");
+  } finally {
+    savingFiles.value = false;
+  }
+};
+
+// 閲嶆柊澶勭悊鍚戦噺鍖栫殑鏂囦欢
+const reprocessFile = async (row) => {
+  try {
+    await reprocessVector(row.id);
+    ElMessage.success("宸查噸鏂版彁浜ゅ悜閲忓寲浠诲姟");
+    // 寤惰繜鍒锋柊
+    setTimeout(() => {
+      loadFileList();
+    }, 1000);
+  } catch (error) {
+    console.error("閲嶆柊澶勭悊澶辫触:", error);
+    ElMessage.error("閲嶆柊澶勭悊澶辫触");
+  }
+};
+
+// 鍒犻櫎鏂囦欢
+const deleteFile = async (row) => {
+  try {
+    await ElMessageBox.confirm(
+      "纭畾瑕佸垹闄よ鏂囦欢鍚楋紵鍒犻櫎鍚庡皢鏃犳硶鎭㈠鍚戦噺鏁版嵁",
+      "鍒犻櫎纭",
+      {
+        confirmButtonText: "纭畾",
+        cancelButtonText: "鍙栨秷",
+        type: "warning"
+      }
+    );
+
+    await deleteKnowledgeBaseFiles([row.id]);
+    ElMessage.success("鍒犻櫎鎴愬姛");
+    loadFileList();
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error("鍒犻櫎鏂囦欢澶辫触:", error);
+      ElMessage.error("鍒犻櫎鏂囦欢澶辫触");
+    }
+  }
+};
+
+// 鐘舵�佹枃鏈槧灏�
+const getStatusText = (status) => {
+  const map = {
+    0: '寰呭鐞�',
+    1: '澶勭悊涓�',
+    2: '宸插畬鎴�',
+    3: '澶辫触'
+  };
+  return map[status] || '鏈煡';
+};
+
+// 鐘舵�佹爣绛剧被鍨嬫槧灏�
+const getStatusType = (status) => {
+  const map = {
+    0: 'info',
+    1: 'warning',
+    2: 'success',
+    3: 'danger'
+  };
+  return map[status] || 'info';
+};
+
+// 鍏抽棴鏂囦欢绠$悊寮圭獥
+const closeFilesDialog = () => {
+  filesDialogVisible.value = false;
+  currentKnowledgeBase.value = null;
+  fileList.value = [];
+  uploadedBlobIds.value = [];
+  getList(); // 鍒锋柊涓诲垪琛紝鏇存柊鏂囦欢鏁伴噺
+};
+
+// ============ 鐭ヨ瘑搴撻棶绛旂浉鍏� ============
+
+// 鎵撳紑闂瓟寮圭獥
+const openChatDialog = (row) => {
+  currentKnowledgeBase.value = row;
+  chatDialogVisible.value = true;
+  memoryId.value = crypto.randomUUID();
+  messages.value = [];
+  inputQuestion.value = "";
+};
+
+// 鍙戦�佹秷鎭�
+const sendMessage = async () => {
+  if (!inputQuestion.value.trim()) {
+    ElMessage.warning("璇疯緭鍏ラ棶棰�");
+    return;
+  }
+
+  const question = inputQuestion.value.trim();
+
+  // 娣诲姞鐢ㄦ埛娑堟伅
+  messages.value.push({
+    role: 'user',
+    content: question
+  });
+
+  inputQuestion.value = "";
+  chatLoading.value = true;
+
+  // 婊氬姩鍒板簳閮�
+  await nextTick();
+  scrollToBottom();
+
+  try {
+    // 娴佸紡璇锋眰
+    const response = await fetch('/api/ai/knowledge/chat', {
+      method: 'POST',
+      headers: {
+        'Content-Type': 'application/json',
+        'Authorization': 'Bearer ' + getToken()
+      },
+      body: JSON.stringify({
+        knowledgeBaseId: currentKnowledgeBase.value.id,
+        memoryId: memoryId.value,
+        question: question
+      })
+    });
+
+    if (!response.ok) {
+      throw new Error('璇锋眰澶辫触');
+    }
+
+    // 澶勭悊SSE娴佸紡鍝嶅簲
+    const reader = response.body.getReader();
+    const decoder = new TextDecoder();
+    let aiContent = '';
+
+    messages.value.push({ role: 'assistant', content: '' });
+
+    while (true) {
+      const { done, value } = await reader.read();
+      if (done) break;
+
+      const text = decoder.decode(value);
+      aiContent += text;
+      messages.value[messages.value.length - 1].content = aiContent;
+
+      // 婊氬姩鍒板簳閮�
+      await nextTick();
+      scrollToBottom();
+    }
+  } catch (error) {
+    console.error("闂瓟璇锋眰澶辫触:", error);
+    ElMessage.error("闂瓟璇锋眰澶辫触锛岃绋嶅悗閲嶈瘯");
+    messages.value.push({
+      role: 'assistant',
+      content: '鎶辨瓑锛屽彂鐢熶簡閿欒锛岃绋嶅悗閲嶈瘯'
+    });
+  } finally {
+    chatLoading.value = false;
+  }
+};
+
+// 婊氬姩鍒板簳閮�
+const scrollToBottom = () => {
+  if (chatMessagesRef.value) {
+    chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight;
+  }
+};
+
+// 鍏抽棴闂瓟寮圭獥
+const closeChatDialog = () => {
+  chatDialogVisible.value = false;
+  currentKnowledgeBase.value = null;
+  messages.value = [];
+  inputQuestion.value = "";
+};
 </script>
 
 <style scoped>
@@ -755,4 +1184,92 @@
   font-size: 14px;
   color: #909399;
 }
+
+/* 鏂囦欢绠$悊鏍峰紡 */
+.file-manager {
+  padding: 20px 0;
+}
+
+.upload-section {
+  display: flex;
+  align-items: center;
+}
+
+/* 鐭ヨ瘑搴撻棶绛旀牱寮� */
+.knowledge-chat {
+  display: flex;
+  flex-direction: column;
+  height: 500px;
+}
+
+.chat-header {
+  margin-bottom: 16px;
+}
+
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding: 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 16px;
+}
+
+.message {
+  margin-bottom: 16px;
+  max-width: 80%;
+}
+
+.message.user {
+  margin-left: auto;
+  text-align: right;
+}
+
+.message.assistant {
+  margin-right: auto;
+}
+
+.message-role {
+  font-size: 12px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+
+.message-content {
+  display: inline-block;
+  padding: 10px 14px;
+  border-radius: 8px;
+  line-height: 1.6;
+  word-wrap: break-word;
+  white-space: pre-wrap;
+}
+
+.message.user .message-content {
+  background: #409eff;
+  color: white;
+}
+
+.message.assistant .message-content {
+  background: white;
+  color: #303133;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.typing {
+  animation: typing 1.5s infinite;
+}
+
+@keyframes typing {
+  0%, 50%, 100% {
+    opacity: 1;
+  }
+  25%, 75% {
+    opacity: 0.5;
+  }
+}
+
+.chat-input {
+  margin-top: auto;
+}
+
 </style>

--
Gitblit v1.9.3