diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/DataManagementServiceConfiguration.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/DataManagementServiceConfiguration.java index 8e2b586c..f502beb6 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/DataManagementServiceConfiguration.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/DataManagementServiceConfiguration.java @@ -1,6 +1,5 @@ package com.datamate.datamanagement; -import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; @@ -10,7 +9,6 @@ * 数据管理服务配置类 - 多源接入、元数据、血缘治理 */ @Configuration -@EnableFeignClients(basePackages = "com.datamate.datamanagement.infrastructure.client") @EnableAsync @ComponentScan(basePackages = { "com.datamate.datamanagement", diff --git a/backend/services/main-application/src/main/java/com/datamate/main/DataMateApplication.java b/backend/services/main-application/src/main/java/com/datamate/main/DataMateApplication.java index 01a46310..0a0d5a65 100644 --- a/backend/services/main-application/src/main/java/com/datamate/main/DataMateApplication.java +++ b/backend/services/main-application/src/main/java/com/datamate/main/DataMateApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @@ -23,6 +24,7 @@ @EnableAsync @EnableScheduling @EnableCaching +@EnableFeignClients(basePackages = "com.datamate.*") public class DataMateApplication { public static void main(String[] args) { SpringApplication.run(DataMateApplication.class, args); diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/model/KnowledgeBase.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/model/KnowledgeBase.java index 4a571b3c..b41922c1 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/model/KnowledgeBase.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/model/KnowledgeBase.java @@ -2,6 +2,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import com.datamate.common.domain.model.base.BaseEntity; +import com.datamate.rag.indexer.interfaces.dto.RagType; import lombok.Getter; import lombok.Setter; @@ -25,6 +26,11 @@ public class KnowledgeBase extends BaseEntity { */ private String description; + /** + * RAG 类型 + */ + private RagType type; + /** * 嵌入模型 */ diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/client/GraphRagClient.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/client/GraphRagClient.java new file mode 100644 index 00000000..924f4e7d --- /dev/null +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/client/GraphRagClient.java @@ -0,0 +1,23 @@ +package com.datamate.rag.indexer.infrastructure.client; + +import com.datamate.common.infrastructure.common.Response; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; + +/** + * 知识图谱RAG客户端 + * + * @author dallas + * @since 2026-01-15 + */ +@FeignClient(name = "rag-service", url = "${collection.service.url:http://datamate-backend-python:18000}") +public interface GraphRagClient { + /** + * 启动知识图谱RAG任务 + * @param knowledgeBaseId 知识库ID + * @return 任务详情 + */ + @PostMapping("/api/rag/process/{id}") + Response startGraphRagTask(@PathVariable("id") String knowledgeBaseId); +} diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/event/RagEtlService.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/event/RagEtlService.java index 355e4b2c..4dbfac59 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/event/RagEtlService.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/event/RagEtlService.java @@ -8,8 +8,10 @@ import com.datamate.rag.indexer.domain.model.FileStatus; import com.datamate.rag.indexer.domain.model.RagFile; import com.datamate.rag.indexer.domain.repository.RagFileRepository; +import com.datamate.rag.indexer.infrastructure.client.GraphRagClient; import com.datamate.rag.indexer.infrastructure.milvus.MilvusService; import com.datamate.rag.indexer.interfaces.dto.AddFilesReq; +import com.datamate.rag.indexer.interfaces.dto.RagType; import com.google.common.collect.Lists; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.DocumentParser; @@ -58,6 +60,8 @@ public class RagEtlService { private final ModelConfigRepository modelConfigRepository; + private final GraphRagClient graphRagClient; + private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); @Async @@ -65,6 +69,11 @@ public class RagEtlService { public void processAfterCommit(DataInsertedEvent event) { // 执行 RAG 处理流水线 List ragFiles = ragFileRepository.findNotSuccessByKnowledgeBaseId(event.knowledgeBase().getId()); + if (RagType.GRAPH.equals(event.knowledgeBase().getType())){ + log.info("Knowledge base {} is of type GRAPH. Skipping RAG ETL processing.", event.knowledgeBase().getName()); + graphRagClient.startGraphRagTask(event.knowledgeBase().getId()); + return; + } ragFiles.forEach(ragFile -> { try { diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/KnowledgeBaseCreateReq.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/KnowledgeBaseCreateReq.java index 1f07553a..fd62dc62 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/KnowledgeBaseCreateReq.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/KnowledgeBaseCreateReq.java @@ -28,6 +28,8 @@ public class KnowledgeBaseCreateReq { @Size(max = 512, message = "知识库描述长度必须在 0 到 512 之间") private String description; + private RagType type = RagType.DOCUMENT; + /** * 嵌入模型 */ diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/RagType.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/RagType.java new file mode 100644 index 00000000..a0073735 --- /dev/null +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/interfaces/dto/RagType.java @@ -0,0 +1,19 @@ +package com.datamate.rag.indexer.interfaces.dto; + +/** + * RAG 类型 + * + * @author dallas + * @since 2026-01-15 + */ +public enum RagType { + /** + * 文档 + */ + DOCUMENT, + + /** + * 知识图谱 + */ + GRAPH, +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3649d2a5..7f4d20fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,9 +15,12 @@ "lucide-react": "^0.539.0", "react": "^18.1.1", "react-dom": "^18.1.1", + "react-force-graph-3d": "^1.29.0", "react-redux": "^9.2.0", "react-router": "^7.8.0", - "recharts": "2.15.0" + "recharts": "2.15.0", + "tailwind-merge": "^2.5.2", + "three-spritetext": "^1.8.4" }, "devDependencies": { "@eslint/js": "^9.33.0", @@ -188,7 +191,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1975,6 +1977,12 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2137,7 +2145,6 @@ "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -2155,7 +2162,6 @@ "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2223,7 +2229,6 @@ "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/types": "8.39.1", @@ -2502,6 +2507,22 @@ "d3-zoom": "^3.0.0" } }, + "node_modules/3d-force-graph": { + "version": "1.79.0", + "resolved": "https://registry.npmjs.org/3d-force-graph/-/3d-force-graph-1.79.0.tgz", + "integrity": "sha512-0RUNcfiH12f93loY/iS4wShzhXzdLLN4futvFnintF7eP30DjX+nAdLDAGOZwSflhijQyVwnGtpczNjFrDLUzQ==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1", + "kapsule": "^1.16", + "three": ">=0.118 <1", + "three-forcegraph": "1", + "three-render-objects": "^1.35" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -2516,13 +2537,21 @@ "node": ">= 0.6" } }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2771,7 +2800,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", @@ -3077,6 +3105,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -3117,6 +3151,22 @@ "node": ">=12" } }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -3138,6 +3188,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -3147,6 +3203,15 @@ "node": ">=12" } }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -3163,12 +3228,24 @@ "node": ">=12" } }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-selection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3253,12 +3330,23 @@ "node": ">=12" } }, + "node_modules/data-bind-mapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/data-bind-mapper/-/data-bind-mapper-1.0.3.tgz", + "integrity": "sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.1", @@ -3485,7 +3573,6 @@ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3933,6 +4020,20 @@ "dev": true, "license": "ISC" }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4318,6 +4419,15 @@ "dev": true, "license": "ISC" }, + "node_modules/jerrypick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/jerrypick/-/jerrypick-1.1.2.tgz", + "integrity": "sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/jiti": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", @@ -4425,6 +4535,18 @@ "node": "*" } }, + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4723,6 +4845,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4975,6 +5103,44 @@ "node": ">= 0.6" } }, + "node_modules/ngraph.events": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.4.0.tgz", + "integrity": "sha512-NeDGI4DSyjBNBRtA86222JoYietsmCXbs8CEB0dZ51Xeh4lhVl1y3wpWLumczvnha8sFQIW4E0vvVWwgmX2mGw==", + "license": "BSD-3-Clause" + }, + "node_modules/ngraph.forcelayout": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ngraph.forcelayout/-/ngraph.forcelayout-3.3.1.tgz", + "integrity": "sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==", + "license": "BSD-3-Clause", + "dependencies": { + "ngraph.events": "^1.0.0", + "ngraph.merge": "^1.0.0", + "ngraph.random": "^1.0.0" + } + }, + "node_modules/ngraph.graph": { + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/ngraph.graph/-/ngraph.graph-20.1.1.tgz", + "integrity": "sha512-KNtZWYzYe7SMOuG3vvROznU+fkPmL5cGYFsWjqt+Ob1uF5xZz5EjomtsNOZEIwVuD37/zokeEqNK1ghY4/fhDg==", + "license": "BSD-3-Clause", + "dependencies": { + "ngraph.events": "^1.4.0" + } + }, + "node_modules/ngraph.merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ngraph.merge/-/ngraph.merge-1.0.0.tgz", + "integrity": "sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg==", + "license": "MIT" + }, + "node_modules/ngraph.random": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ngraph.random/-/ngraph.random-1.2.0.tgz", + "integrity": "sha512-4EUeAGbB2HWX9njd6bP6tciN6ByJfoaAvmVL9QTaZSeXrW46eNGA9GajiXiPBbvFqxUWFkEbyo6x5qsACUuVfA==", + "license": "BSD-3-Clause" + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -5236,6 +5402,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -5265,6 +5443,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.28.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6024,7 +6212,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6037,7 +6224,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6046,18 +6232,49 @@ "react": "^18.3.1" } }, + "node_modules/react-force-graph-3d": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/react-force-graph-3d/-/react-force-graph-3d-1.29.0.tgz", + "integrity": "sha512-YCD4W+SA9oeK7mMXZ9pXAGSbDZ3+6IYxv8nPZcqqYeiP1nqZIB/cbMveD3S2bH9EqsGrUMW5qFXjAm5topSblw==", + "license": "MIT", + "dependencies": { + "3d-force-graph": "^1.79", + "prop-types": "15", + "react-kapsule": "^2.5" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-kapsule": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-kapsule/-/react-kapsule-2.5.7.tgz", + "integrity": "sha512-kifAF4ZPD77qZKc4CKLmozq6GY1sBzPEJTIJb0wWFK6HsePJatK3jXplZn2eeAt3x67CDozgi7/rO8fNQ/AL7A==", + "license": "MIT", + "dependencies": { + "jerrypick": "^1.1.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -6188,8 +6405,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6675,6 +6891,16 @@ "node": ">=8" } }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", @@ -6720,6 +6946,67 @@ "node": ">=18" } }, + "node_modules/three": { + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", + "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", + "license": "MIT" + }, + "node_modules/three-forcegraph": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/three-forcegraph/-/three-forcegraph-1.43.0.tgz", + "integrity": "sha512-1AqLmTCjjjwcuccObG96fCxiRnNJjCLdA5Mozl7XK+ROwTJ6QEJPo2XJ6uxWeuAmPE7ukMhgv4lj28oZSfE4wg==", + "license": "MIT", + "dependencies": { + "accessor-fn": "1", + "d3-array": "1 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "data-bind-mapper": "1", + "kapsule": "^1.16", + "ngraph.forcelayout": "3", + "ngraph.graph": "20", + "tinycolor2": "1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "three": ">=0.118.3" + } + }, + "node_modules/three-render-objects": { + "version": "1.40.4", + "resolved": "https://registry.npmjs.org/three-render-objects/-/three-render-objects-1.40.4.tgz", + "integrity": "sha512-Ukpu1pei3L5r809izvjsZxwuRcYLiyn6Uvy3lZ9bpMTdvj3i6PeX6w++/hs2ZS3KnEzGjb6YvTvh4UQuwHTDJg==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "float-tooltip": "^1.7", + "kapsule": "^1.16", + "polished": "4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "three": ">=0.168" + } + }, + "node_modules/three-spritetext": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/three-spritetext/-/three-spritetext-1.10.0.tgz", + "integrity": "sha512-t08iP1FCU1lQh8T5MmCpdijKgas8GDHJE0LqMGBuVu3xqMMpFnEZhTlih7FlxLPQizHIGoumUSpfOlY1GO/Tgg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "three": ">=0.86.0" + } + }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -6735,6 +7022,12 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -6773,7 +7066,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6877,7 +7169,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7045,7 +7336,6 @@ "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", @@ -7136,7 +7426,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/frontend/package.json b/frontend/package.json index d781ac0b..b8b60050 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,9 +18,12 @@ "lucide-react": "^0.539.0", "react": "^18.1.1", "react-dom": "^18.1.1", + "react-force-graph-3d": "^1.29.0", "react-redux": "^9.2.0", "react-router": "^7.8.0", - "recharts": "2.15.0" + "recharts": "2.15.0", + "tailwind-merge": "^2.5.2", + "three-spritetext": "^1.8.4" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/frontend/src/components/CardView.tsx b/frontend/src/components/CardView.tsx index ace44d24..316df21a 100644 --- a/frontend/src/components/CardView.tsx +++ b/frontend/src/components/CardView.tsx @@ -6,6 +6,12 @@ import { formatDateTime } from "@/utils/unit"; import ActionDropdown from "./ActionDropdown"; import { Database } from "lucide-react"; +interface BadgeItem { + label: string; + color?: string; + background?: string; +} + interface BaseCardDataType { id: string | number; name: string; @@ -18,7 +24,7 @@ interface BaseCardDataType { color?: string; } | null; description: string; - tags?: string[]; + tags?: Array; statistics?: { label: string; value: string | number }[]; updatedAt?: string; } @@ -47,7 +53,7 @@ interface CardViewProps { } // 标签渲染组件 -const TagsRenderer = ({ tags }: { tags?: any[] }) => { +const TagsRenderer = ({ tags }: { tags?: Array }) => { const [visibleTags, setVisibleTags] = useState([]); const [hiddenTags, setHiddenTags] = useState([]); const containerRef = useRef(null); @@ -75,7 +81,11 @@ const TagsRenderer = ({ tags }: { tags?: any[] }) => { const tagElement = document.createElement("span"); tagElement.className = "ant-tag ant-tag-default"; tagElement.style.margin = "2px"; - tagElement.textContent = typeof tag === "string" ? tag : tag.name; + if (typeof tag === "string") { + tagElement.textContent = tag; + } else { + tagElement.textContent = tag.label; + } tempDiv.appendChild(tagElement); tagElements.push(tagElement); @@ -126,7 +136,17 @@ const TagsRenderer = ({ tags }: { tags?: any[] }) => {
{hiddenTags.map((tag, index) => ( - {typeof tag === "string" ? tag : tag.name} + + {typeof tag === "string" ? tag : tag.label} + ))}
@@ -135,7 +155,17 @@ const TagsRenderer = ({ tags }: { tags?: any[] }) => { return (
{visibleTags.map((tag, index) => ( - {typeof tag === "string" ? tag : tag.name} + + {typeof tag === "string" ? tag : tag.label} + ))} {hiddenTags.length > 0 && ( (props: CardViewProps) { > {item?.name} + {(item?.tags?.length ?? 0) > 0 && + item.tags[0] && + typeof item.tags[0] !== "string" && ( + + {item.tags[0].label} + + )} {item?.status && (
@@ -242,7 +285,15 @@ function CardView(props: CardViewProps) {
{/* Tags */} - + + typeof tag === "string" || index !== 0 + ) + : [] + } + /> {/* Description */}

diff --git a/frontend/src/index.css b/frontend/src/index.css index 172fcbb1..0496c057 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -72,4 +72,26 @@ .border-left { @apply border-l border-[#f0f0f0]; } + .cosmic-modal-bg { + @apply bg-gradient-to-br from-slate-950 via-slate-900 to-black; + } + .cosmic-modal-panel { + background: radial-gradient(circle at 20% 20%, rgba(59, 130, 246, 0.15), transparent 60%), + radial-gradient(circle at 80% 0%, rgba(236, 72, 153, 0.15), transparent 55%), + #020617; + color: #f8fafc; + } + .cosmic-btn { + @apply border-white/30 text-white/80 hover:text-white active:text-white; + background: linear-gradient(135deg, rgba(59, 130, 246, 0.3), rgba(147, 51, 234, 0.3)); + border-color: rgba(255, 255, 255, 0.25) !important; + } + .cosmic-btn.danger { + background: linear-gradient(135deg, rgba(244, 63, 94, 0.35), rgba(59, 130, 246, 0.2)); + } + .cosmic-graph-panel { + background: radial-gradient(circle at 50% 20%, rgba(37, 99, 235, 0.25), transparent 55%), + radial-gradient(circle at 80% 60%, rgba(236, 72, 153, 0.2), transparent 55%), + #01030f; + } } diff --git a/frontend/src/mock/knowledgeBase.tsx b/frontend/src/mock/knowledgeBase.tsx index b2b9fb59..d249ca62 100644 --- a/frontend/src/mock/knowledgeBase.tsx +++ b/frontend/src/mock/knowledgeBase.tsx @@ -129,7 +129,7 @@ export const mockKnowledgeBases: KnowledgeBase[] = [ name: "产品技术文档库", description: "包含所有产品相关的技术文档和API说明,支持多种格式文档的智能解析和向量化处理", - type: "unstructured", + type: "DOCUMENT", status: "ready", fileCount: 45, chunkCount: 1250, @@ -216,7 +216,7 @@ export const mockKnowledgeBases: KnowledgeBase[] = [ id: 2, name: "FAQ结构化知识库", description: "客服常见问题的结构化问答对,支持快速检索和智能匹配", - type: "structured", + type: "DOCUMENT", status: "vectorizing", fileCount: 12, chunkCount: 890, @@ -251,4 +251,31 @@ export const mockKnowledgeBases: KnowledgeBase[] = [ ], vectorizationHistory: [], }, + { + id: 3, + name: "企业知识图谱", + description: "包含企业人员、项目、合作伙伴等关系的知识图谱", + type: "GRAPH", + status: "ready", + fileCount: 8, + chunkCount: 420, + vectorCount: 0, + size: "32 MB", + progress: 100, + createdAt: "2024-01-10", + lastUpdated: "2024-01-25", + vectorDatabase: "neo4j", + customEntities: ["人员", "组织", "项目"], + config: { + embeddingModel: "text-embedding-3-large", + chunkSize: 256, + overlap: 20, + sliceMethod: "paragraph", + enableQA: true, + vectorDimension: 1536, + sliceOperators: ["semantic-split"], + }, + files: [], + vectorizationHistory: [], + }, ]; diff --git a/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx b/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx index ecc197df..8366b342 100644 --- a/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx +++ b/frontend/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.tsx @@ -5,11 +5,12 @@ import { DeleteOutlined, EditOutlined, ReloadOutlined, + CloseOutlined, } from "@ant-design/icons"; import { useNavigate, useParams } from "react-router"; import DetailHeader from "@/components/DetailHeader"; import { SearchControls } from "@/components/SearchControls"; -import { KBFile, KnowledgeBaseItem } from "../knowledge-base.model"; +import { KBFile, KnowledgeBaseItem, KnowledgeGraphNode, KnowledgeGraphEdge, KBType } from "../knowledge-base.model"; import { mapFileData, mapKnowledgeBase } from "../knowledge-base.const"; import { deleteKnowledgeBaseByIdUsingDelete, @@ -17,10 +18,13 @@ import { queryKnowledgeBaseByIdUsingGet, queryKnowledgeBaseFilesUsingGet, retrieveKnowledgeBaseContent, + fetchKnowledgeGraph, } from "../knowledge-base.api"; import useFetchData from "@/hooks/useFetchData"; import AddDataDialog from "../components/AddDataDialog"; import CreateKnowledgeBase from "../components/CreateKnowledgeBase"; +import KnowledgeGraphView, { GraphEntitySelection } from "../components/KnowledgeGraphView"; +import { Network } from "lucide-react"; interface StatisticItem { icon?: React.ReactNode; @@ -49,6 +53,10 @@ const KnowledgeBaseDetailPage: React.FC = () => { const [recallLoading, setRecallLoading] = useState(false); const [recallResults, setRecallResults] = useState([]); const [recallQuery, setRecallQuery] = useState(""); + const [graphVisible, setGraphVisible] = useState(false); + const [graphLoading, setGraphLoading] = useState(false); + const [graphData, setGraphData] = useState<{ nodes: KnowledgeGraphNode[]; edges: KnowledgeGraphEdge[] }>({ nodes: [], edges: [] }); + const [graphSelection, setGraphSelection] = useState(null); const fetchKnowledgeBaseDetails = async (id: string) => { const { data } = await queryKnowledgeBaseByIdUsingGet(id); @@ -61,6 +69,17 @@ const KnowledgeBaseDetailPage: React.FC = () => { } }, [id]); + useEffect(() => { + if (!graphVisible) { + return; + } + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + return () => { + document.body.style.overflow = previousOverflow; + }; + }, [graphVisible]); + const { loading, tableData: files, @@ -119,7 +138,47 @@ const KnowledgeBaseDetailPage: React.FC = () => { setRecallLoading(false); }; - const operations = [ + const handleGraphFetch = async () => { + if (!knowledgeBase?.id) return; + setGraphLoading(true); + setGraphSelection(null); + try { + const { data } = await fetchKnowledgeGraph({ knowledge_base_id: knowledgeBase.id, query: "*" }); + setGraphData({ nodes: data?.nodes ?? [], edges: data?.edges ?? [] }); + } catch { + setGraphData({ nodes: [], edges: [] }); + } + setGraphLoading(false); + }; + + const handleOpenGraph = () => { + setGraphSelection(null); + setGraphVisible(true); + if (!graphData.nodes.length) { + handleGraphFetch(); + } + }; + + const handleCloseGraph = () => { + setGraphVisible(false); + setGraphSelection(null); + }; + + const handleGraphRefresh = () => { + handleGraphFetch(); + }; + + type DetailOperation = NonNullable["operations"][number]>; + const graphOperation: DetailOperation | null = knowledgeBase?.type === KBType.GRAPH + ? { + key: "graph", + label: "知识图谱", + icon: , + onClick: handleOpenGraph, + } + : null; + + const baseOperations: DetailOperation[] = [ { key: "edit", label: "编辑知识库", @@ -152,6 +211,8 @@ const KnowledgeBaseDetailPage: React.FC = () => { }, ]; + const operations: DetailOperation[] = [graphOperation, ...baseOperations].filter(Boolean) as DetailOperation[]; + const fileOps = [ { key: "delete", @@ -256,6 +317,101 @@ const KnowledgeBaseDetailPage: React.FC = () => { onUpdate={handleRefreshPage} onClose={() => setShowEdit(false)} /> + {graphVisible && ( +

+
+
+
+
知识图谱
+
{knowledgeBase?.name}
+
+
+ + +
+
+
+ {graphLoading ? ( +
+ +
+ ) : ( + + )} + {graphSelection && ( +
+
+ {graphSelection.type === "node" ? "节点详情" : "边详情"} +
+
ID: {graphSelection.data.id}
+ {graphSelection.type === "edge" && ( +
+
+ 类型 + {graphSelection.data.type} +
+
+ 源节点 + {graphSelection.data.source} +
+
+ 目标节点 + {graphSelection.data.target} +
+
+
+ )} + {graphSelection.type === "node" && ( +
+
+ 标签 + + {graphSelection.data.labels?.join(", ") || "-"} + +
+
+
+ )} +
+ {Object.entries(graphSelection.data.properties ?? {}).map(([key, value]) => ( +
+ {key} + + {typeof value === "object" ? JSON.stringify(value) : String(value ?? "-")} + +
+ ))} + {!Object.keys(graphSelection.data.properties ?? {}).length && ( +
暂无属性
+ )} +
+
+ )} +
+
+
+ )}
diff --git a/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx b/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx index 9fa96477..86826a80 100644 --- a/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx +++ b/frontend/src/pages/KnowledgeBase/components/AddDataDialog.tsx @@ -15,6 +15,7 @@ import { addKnowledgeBaseFilesUsingPost } from "../knowledge-base.api"; import DatasetFileTransfer from "@/components/business/DatasetFileTransfer"; import { DescriptionsItemType } from "antd/es/descriptions"; import { DatasetFileCols } from "../knowledge-base.const"; +import { DatasetType } from "@/pages/DataManagement/dataset.model"; const sliceOptions = [ { label: "默认分块", value: "DEFAULT_CHUNK" }, diff --git a/frontend/src/pages/KnowledgeBase/components/CreateKnowledgeBase.tsx b/frontend/src/pages/KnowledgeBase/components/CreateKnowledgeBase.tsx index c0a16f6a..b1fe1abd 100644 --- a/frontend/src/pages/KnowledgeBase/components/CreateKnowledgeBase.tsx +++ b/frontend/src/pages/KnowledgeBase/components/CreateKnowledgeBase.tsx @@ -1,5 +1,7 @@ -import { Button, Form, Input, message, Modal, Select } from "antd"; -import { PlusOutlined } from "@ant-design/icons"; +import { Button, Form, Input, message, Modal, Select, Tooltip } from "antd"; +import RadioCard from "@/components/RadioCard"; +import { InfoCircleOutlined, PlusOutlined } from "@ant-design/icons"; +import { Share2, BookOpen } from "lucide-react"; import { useEffect, useState } from "react"; import { useDispatch } from "react-redux"; import { queryModelListUsingGet } from "@/pages/SettingsPage/settings.apis"; @@ -8,7 +10,7 @@ import { createKnowledgeBaseUsingPost, updateKnowledgeBaseByIdUsingPut, } from "../knowledge-base.api"; -import { KnowledgeBaseItem } from "../knowledge-base.model"; +import { KnowledgeBaseItem, KBType } from "../knowledge-base.model"; import { showSettings } from "@/store/slices/settingsSlice"; export default function CreateKnowledgeBase({ @@ -60,18 +62,29 @@ export default function CreateKnowledgeBase({ description: data.description, embeddingModel: data.embeddingModel, chatModel: data.chatModel, + type: data.type ?? KBType.DOCUMENT, + customEntities: data.customEntities ?? [], }); + } else { + form.setFieldsValue({ type: KBType.DOCUMENT, customEntities: [] }); } }, [isEdit, data, form]); + const typeValue = Form.useWatch("type", form); + const isGraphKB = typeValue === KBType.GRAPH; + const handleCreateKnowledgeBase = async () => { try { const values = await form.validateFields(); + const payload = { + ...values, + customEntities: values.type === KBType.GRAPH ? values.customEntities : undefined, + }; if (isEdit && data) { - await updateKnowledgeBaseByIdUsingPut(data.id!, values); + await updateKnowledgeBaseByIdUsingPut(data.id!, payload); message.success("知识库更新成功"); } else { - await createKnowledgeBaseUsingPost(values); + await createKnowledgeBaseUsingPost(payload); message.success("知识库创建成功"); } setOpen(false); @@ -110,6 +123,50 @@ export default function CreateKnowledgeBase({ onOk={handleCreateKnowledgeBase} >
+ + form.setFieldValue("type", val)} + options={[ + { + value: KBType.DOCUMENT, + label: "向量知识库", + description: "适用于文档类知识,支持召回+生成", + icon: BookOpen, + }, + { + value: KBType.GRAPH, + label: "知识图谱", + description: "自定义实体/关系,强化结构化推理", + icon: Share2, + }, + ]} + /> + + {isGraphKB && ( + + 识别实体(可多选) + + + +
+ } + name="customEntities" + rules={[{ type: "array" }]} + > +