From 8ed0b795f3e6787757da34b11bc99a1bb7ef7c50 Mon Sep 17 00:00:00 2001 From: Chuck1sn Date: Fri, 27 Jun 2025 16:51:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E5=BA=93=E5=92=8C=E6=96=87=E6=A1=A3=E7=9A=84?= =?UTF-8?q?=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=B8=8A=E4=BC=A0=E5=92=8C=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=AE=A1=E7=90=86=EF=BC=8C=E6=9B=B4=E6=96=B0=E7=9B=B8=E5=85=B3?= =?UTF-8?q?API=E6=8E=A5=E5=8F=A3=E5=92=8C=E5=89=8D=E7=AB=AF=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=9F=A5=E8=AF=86=E5=BA=93?= =?UTF-8?q?=E5=92=8C=E6=96=87=E6=A1=A3=E7=9A=84=E8=A7=86=E5=9B=BE=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle.kts | 5 +- .../com/zl/mjga/config/JacksonConfig.java | 35 ++ .../zl/mjga/controller/LibraryController.java | 24 +- .../zl/mjga/dto/knowledge/DocUpdateDto.java | 3 +- frontend/.gitignore | 2 + frontend/src/api/schema/openapi.json | 345 ++++++++++++++++++ frontend/src/api/types/schema.d.ts | 319 ++++++++++++++++ .../src/components/common/PromotionBanner.vue | 40 +- .../src/components/icons/KnowledgeIcon.vue | 7 + frontend/src/components/icons/index.ts | 1 + frontend/src/components/layout/Sidebar.vue | 6 + .../components/modals/LibraryFormDialog.vue | 93 +++++ .../src/components/modals/UserFormDialog.vue | 3 +- .../knowledge/useKnowledgeQuery.ts | 51 +++ .../knowledge/useKnowledgeUpsert.ts | 72 ++++ frontend/src/router/constants.ts | 35 +- frontend/src/router/guards.ts | 2 +- frontend/src/router/index.ts | 4 +- frontend/src/router/modules/ai.ts | 24 ++ frontend/src/router/modules/dashboard.ts | 4 +- frontend/src/types/KnowledgeTypes.ts | 35 ++ .../src/views/KnowledgeDocManagementPage.vue | 238 ++++++++++++ .../src/views/KnowledgeManagementPage.vue | 177 +++++++++ frontend/src/views/KnowledgeSegmentsPage.vue | 97 +++++ frontend/src/views/RoleManagementPage.vue | 2 +- 25 files changed, 1578 insertions(+), 46 deletions(-) create mode 100644 backend/src/main/java/com/zl/mjga/config/JacksonConfig.java create mode 100644 frontend/src/components/icons/KnowledgeIcon.vue create mode 100644 frontend/src/components/modals/LibraryFormDialog.vue create mode 100644 frontend/src/composables/knowledge/useKnowledgeQuery.ts create mode 100644 frontend/src/composables/knowledge/useKnowledgeUpsert.ts create mode 100644 frontend/src/types/KnowledgeTypes.ts create mode 100644 frontend/src/views/KnowledgeDocManagementPage.vue create mode 100644 frontend/src/views/KnowledgeManagementPage.vue create mode 100644 frontend/src/views/KnowledgeSegmentsPage.vue diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 5b872e7..42e4f4b 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -170,9 +170,8 @@ jooq { } forcedTypes { forcedType { - name = "varchar" - includeExpression = ".*" - includeTypes = "INET" + isJsonConverter = true + includeTypes = "(?i:JSON|JSONB)" } } } diff --git a/backend/src/main/java/com/zl/mjga/config/JacksonConfig.java b/backend/src/main/java/com/zl/mjga/config/JacksonConfig.java new file mode 100644 index 0000000..80f4c7e --- /dev/null +++ b/backend/src/main/java/com/zl/mjga/config/JacksonConfig.java @@ -0,0 +1,35 @@ +package com.zl.mjga.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import org.jooq.JSON; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { + return builder -> + builder + .serializationInclusion(JsonInclude.Include.USE_DEFAULTS) + .serializers(new JooqJsonSerializer()); + } + + private static class JooqJsonSerializer extends StdSerializer { + public JooqJsonSerializer() { + super(JSON.class); + } + + @Override + public void serialize(JSON value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeRawValue(value.data()); + } + } +} diff --git a/backend/src/main/java/com/zl/mjga/controller/LibraryController.java b/backend/src/main/java/com/zl/mjga/controller/LibraryController.java index 14a34b7..618b9f6 100644 --- a/backend/src/main/java/com/zl/mjga/controller/LibraryController.java +++ b/backend/src/main/java/com/zl/mjga/controller/LibraryController.java @@ -8,6 +8,8 @@ import com.zl.mjga.repository.LibraryRepository; import com.zl.mjga.service.RagService; import com.zl.mjga.service.UploadService; import jakarta.validation.Valid; + +import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,7 +39,10 @@ public class LibraryController { @GetMapping("/docs") public List queryLibraryDocs(@RequestParam Long libraryId) { - return libraryDocRepository.fetchByLibId(libraryId); + List libraryDocs = libraryDocRepository.fetchByLibId(libraryId); + return libraryDocs.stream().sorted( + Comparator.comparing(LibraryDoc::getId).reversed() + ).toList(); } @GetMapping("/segments") @@ -66,22 +71,19 @@ public class LibraryController { @PutMapping("/doc") public void updateLibraryDoc(@RequestBody @Valid DocUpdateDto docUpdateDto) { - LibraryDoc libraryDoc = new LibraryDoc(); - libraryDoc.setId(docUpdateDto.id()); - libraryDoc.setEnable(docUpdateDto.enable()); - libraryDocRepository.merge(libraryDoc); + LibraryDoc exist = libraryDocRepository.fetchOneById(docUpdateDto.id()); + exist.setEnable(docUpdateDto.enable()); + libraryDocRepository.merge(exist); } - @PostMapping( - value = "/doc/upload", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, - produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(value = "/doc/upload", produces = MediaType.TEXT_PLAIN_VALUE) public String uploadLibraryDoc( - @RequestPart("libraryId") Long libraryId, @RequestPart("file") MultipartFile multipartFile) + @RequestPart("libraryId") String libraryId, @RequestPart("file") MultipartFile multipartFile) throws Exception { String objectName = uploadService.uploadLibraryDoc(multipartFile); Long libraryDocId = - ragService.createLibraryDocBy(libraryId, objectName, multipartFile.getOriginalFilename()); + ragService.createLibraryDocBy( + Long.valueOf(libraryId), objectName, multipartFile.getOriginalFilename()); ragService.embeddingAndCreateDocSegment(libraryDocId, objectName); return objectName; } diff --git a/backend/src/main/java/com/zl/mjga/dto/knowledge/DocUpdateDto.java b/backend/src/main/java/com/zl/mjga/dto/knowledge/DocUpdateDto.java index f89605d..244d371 100644 --- a/backend/src/main/java/com/zl/mjga/dto/knowledge/DocUpdateDto.java +++ b/backend/src/main/java/com/zl/mjga/dto/knowledge/DocUpdateDto.java @@ -1,6 +1,5 @@ package com.zl.mjga.dto.knowledge; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -public record DocUpdateDto(@NotNull Long id, @NotEmpty Boolean enable) {} +public record DocUpdateDto(@NotNull Long id, @NotNull Long libId, @NotNull Boolean enable) {} diff --git a/frontend/.gitignore b/frontend/.gitignore index 59847e1..4a3e8e6 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -186,3 +186,5 @@ compose.yaml Dockerfile Caddyfile start.sh + +.cursor diff --git a/frontend/src/api/schema/openapi.json b/frontend/src/api/schema/openapi.json index 0822105..8d18abd 100644 --- a/frontend/src/api/schema/openapi.json +++ b/frontend/src/api/schema/openapi.json @@ -44,6 +44,51 @@ } } }, + "/knowledge/doc": { + "put": { + "tags": [ + "library-controller" + ], + "operationId": "updateLibraryDoc", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DocUpdateDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": [ + "library-controller" + ], + "operationId": "deleteLibraryDoc", + "parameters": [ + { + "name": "libraryDocId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/ai/llm": { "put": { "tags": [ @@ -192,6 +237,93 @@ } } }, + "/knowledge/library": { + "post": { + "tags": [ + "library-controller" + ], + "operationId": "upsertLibrary", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryUpsertDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": [ + "library-controller" + ], + "operationId": "deleteLibrary", + "parameters": [ + { + "name": "libraryId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/knowledge/doc/upload": { + "post": { + "tags": [ + "library-controller" + ], + "operationId": "uploadLibraryDoc", + "requestBody": { + "content": { + "application/json": { + "schema": { + "required": [ + "file", + "libraryId" + ], + "type": "object", + "properties": { + "libraryId": { + "type": "string" + }, + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/iam/user": { "get": { "tags": [ @@ -963,6 +1095,97 @@ } } }, + "/knowledge/segments": { + "get": { + "tags": [ + "library-controller" + ], + "operationId": "queryLibraryDocSegments", + "parameters": [ + { + "name": "libraryDocId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryDocSegment" + } + } + } + } + } + } + } + }, + "/knowledge/libraries": { + "get": { + "tags": [ + "library-controller" + ], + "operationId": "queryLibraries", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Library" + } + } + } + } + } + } + } + }, + "/knowledge/docs": { + "get": { + "tags": [ + "library-controller" + ], + "operationId": "queryLibraryDocs", + "parameters": [ + { + "name": "libraryId", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryDoc" + } + } + } + } + } + } + } + }, "/iam/users": { "get": { "tags": [ @@ -1354,6 +1577,22 @@ } } }, + "DocUpdateDto": { + "required": [ + "enable", + "id" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "enable": { + "type": "boolean" + } + } + }, "LlmVm": { "required": [ "apiKey", @@ -1422,6 +1661,24 @@ } } }, + "LibraryUpsertDto": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, "UserUpsertDto": { "required": [ "enable", @@ -1789,6 +2046,94 @@ } } }, + "LibraryDocSegment": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "docId": { + "type": "integer", + "format": "int64" + }, + "embeddingId": { + "type": "string" + }, + "content": { + "type": "string" + }, + "tokenUsage": { + "type": "integer", + "format": "int32" + } + } + }, + "Library": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "createTime": { + "type": "string", + "format": "date-time" + } + } + }, + "JSON": { + "type": "object" + }, + "LibraryDoc": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "libId": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "identify": { + "type": "string" + }, + "path": { + "type": "string" + }, + "meta": { + "$ref": "#/components/schemas/JSON" + }, + "enable": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "SUCCESS", + "INDEXING" + ] + }, + "createTime": { + "type": "string", + "format": "date-time" + }, + "updateTime": { + "type": "string", + "format": "date-time" + } + } + }, "UserQueryDto": { "type": "object", "properties": { diff --git a/frontend/src/api/types/schema.d.ts b/frontend/src/api/types/schema.d.ts index 80fea2c..9a825b0 100644 --- a/frontend/src/api/types/schema.d.ts +++ b/frontend/src/api/types/schema.d.ts @@ -20,6 +20,22 @@ export interface paths { patch?: never; trace?: never; }; + "/knowledge/doc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put: operations["updateLibraryDoc"]; + post?: never; + delete: operations["deleteLibraryDoc"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ai/llm": { parameters: { query?: never; @@ -100,6 +116,38 @@ export interface paths { patch?: never; trace?: never; }; + "/knowledge/library": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["upsertLibrary"]; + delete: operations["deleteLibrary"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/knowledge/doc/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["uploadLibraryDoc"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/iam/user": { parameters: { query?: never; @@ -484,6 +532,54 @@ export interface paths { patch?: never; trace?: never; }; + "/knowledge/segments": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["queryLibraryDocSegments"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/knowledge/libraries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["queryLibraries"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/knowledge/docs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["queryLibraryDocs"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/iam/users": { parameters: { query?: never; @@ -684,6 +780,11 @@ export interface components { name: string; group: string; }; + DocUpdateDto: { + /** Format: int64 */ + id: number; + enable: boolean; + }; LlmVm: { /** Format: int64 */ id: number; @@ -705,6 +806,12 @@ export interface components { id?: number; name?: string; }; + LibraryUpsertDto: { + /** Format: int64 */ + id?: number; + name: string; + description?: string; + }; UserUpsertDto: { /** Format: int64 */ id?: number; @@ -829,6 +936,42 @@ export interface components { name: string; isBound?: boolean; }; + LibraryDocSegment: { + /** Format: int64 */ + id?: number; + /** Format: int64 */ + docId?: number; + embeddingId?: string; + content?: string; + /** Format: int32 */ + tokenUsage?: number; + }; + Library: { + /** Format: int64 */ + id?: number; + name?: string; + description?: string; + /** Format: date-time */ + createTime?: string; + }; + JSON: Record; + LibraryDoc: { + /** Format: int64 */ + id?: number; + /** Format: int64 */ + libId?: number; + name?: string; + identify?: string; + path?: string; + meta?: components["schemas"]["JSON"]; + enable?: boolean; + /** @enum {string} */ + status?: "SUCCESS" | "INDEXING"; + /** Format: date-time */ + createTime?: string; + /** Format: date-time */ + updateTime?: string; + }; UserQueryDto: { username?: string; /** Format: date-time */ @@ -973,6 +1116,48 @@ export interface operations { }; }; }; + updateLibraryDoc: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DocUpdateDto"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + deleteLibraryDoc: { + parameters: { + query: { + libraryDocId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; updateLlm: { parameters: { query?: never; @@ -1105,6 +1290,76 @@ export interface operations { }; }; }; + upsertLibrary: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LibraryUpsertDto"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + deleteLibrary: { + parameters: { + query: { + libraryId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + uploadLibraryDoc: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + libraryId: string; + /** Format: binary */ + file: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/plain": string; + }; + }; + }; + }; queryUserWithRolePermission: { parameters: { query: { @@ -1782,6 +2037,70 @@ export interface operations { }; }; }; + queryLibraryDocSegments: { + parameters: { + query: { + libraryDocId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["LibraryDocSegment"][]; + }; + }; + }; + }; + queryLibraries: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["Library"][]; + }; + }; + }; + }; + queryLibraryDocs: { + parameters: { + query: { + libraryId: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": components["schemas"]["LibraryDoc"][]; + }; + }; + }; + }; queryUsers: { parameters: { query: { diff --git a/frontend/src/components/common/PromotionBanner.vue b/frontend/src/components/common/PromotionBanner.vue index 31e3e0d..e94e53c 100644 --- a/frontend/src/components/common/PromotionBanner.vue +++ b/frontend/src/components/common/PromotionBanner.vue @@ -21,25 +21,25 @@ diff --git a/frontend/src/components/icons/KnowledgeIcon.vue b/frontend/src/components/icons/KnowledgeIcon.vue new file mode 100644 index 0000000..18a9607 --- /dev/null +++ b/frontend/src/components/icons/KnowledgeIcon.vue @@ -0,0 +1,7 @@ + diff --git a/frontend/src/components/icons/index.ts b/frontend/src/components/icons/index.ts index fc23c88..e20180d 100644 --- a/frontend/src/components/icons/index.ts +++ b/frontend/src/components/icons/index.ts @@ -11,3 +11,4 @@ export { default as RoleIcon } from "./RoleIcon.vue"; export { default as SettingsIcon } from "./SettingsIcon.vue"; export { default as UsersIcon } from "./UsersIcon.vue"; export { default as PermissionIcon } from "./PermissionIcon.vue"; +export { default as KnowledgeIcon } from "./KnowledgeIcon.vue"; diff --git a/frontend/src/components/layout/Sidebar.vue b/frontend/src/components/layout/Sidebar.vue index 5514a17..dae2a9d 100644 --- a/frontend/src/components/layout/Sidebar.vue +++ b/frontend/src/components/layout/Sidebar.vue @@ -35,6 +35,7 @@ import { RouterLink, useRoute } from "vue-router"; import { DepartmentIcon, + KnowledgeIcon, LlmConfigIcon, PermissionIcon, PositionIcon, @@ -113,6 +114,11 @@ const menuItems = [ path: Routes.LLMCONFIGVIEW.fullPath(), icon: LlmConfigIcon, }, + { + title: "知识库管理", + path: Routes.KNOWLEDGEVIEW.fullPath(), + icon: KnowledgeIcon, + }, ]; const route = useRoute(); diff --git a/frontend/src/components/modals/LibraryFormDialog.vue b/frontend/src/components/modals/LibraryFormDialog.vue new file mode 100644 index 0000000..76ef4e0 --- /dev/null +++ b/frontend/src/components/modals/LibraryFormDialog.vue @@ -0,0 +1,93 @@ + + + diff --git a/frontend/src/components/modals/UserFormDialog.vue b/frontend/src/components/modals/UserFormDialog.vue index 9d2d989..d6aeb16 100644 --- a/frontend/src/components/modals/UserFormDialog.vue +++ b/frontend/src/components/modals/UserFormDialog.vue @@ -116,10 +116,9 @@ const handleFileChange = (event: Event) => { throw err; }, }); - } catch (error) { + } finally { (event.target as HTMLInputElement).value = ""; uploadLoading.value = false; - throw error; } }; diff --git a/frontend/src/composables/knowledge/useKnowledgeQuery.ts b/frontend/src/composables/knowledge/useKnowledgeQuery.ts new file mode 100644 index 0000000..674ba24 --- /dev/null +++ b/frontend/src/composables/knowledge/useKnowledgeQuery.ts @@ -0,0 +1,51 @@ +import client from "@/api/client"; +import type { + DocQueryParams, + Library, + LibraryDoc, + LibraryDocSegment, + SegmentQueryParams, +} from "@/types/KnowledgeTypes"; +import { ref } from "vue"; + +export const useKnowledgeQuery = () => { + const libraries = ref([]); + const docs = ref([]); + const segments = ref([]); + + const fetchLibraries = async () => { + const { data } = await client.GET("/knowledge/libraries", {}); + libraries.value = data || []; + }; + + const fetchLibraryDocs = async (params: DocQueryParams) => { + const { data } = await client.GET("/knowledge/docs", { + params: { + query: { + libraryId: params.libraryId, + }, + }, + }); + docs.value = data || []; + }; + + const fetchDocSegments = async (params: SegmentQueryParams) => { + const { data } = await client.GET("/knowledge/segments", { + params: { + query: { + libraryDocId: params.libraryDocId, + }, + }, + }); + segments.value = data || []; + }; + + return { + libraries, + fetchLibraries, + docs, + fetchLibraryDocs, + segments, + fetchDocSegments, + }; +}; diff --git a/frontend/src/composables/knowledge/useKnowledgeUpsert.ts b/frontend/src/composables/knowledge/useKnowledgeUpsert.ts new file mode 100644 index 0000000..f30c201 --- /dev/null +++ b/frontend/src/composables/knowledge/useKnowledgeUpsert.ts @@ -0,0 +1,72 @@ +import client from "@/api/client"; +import type { + DocUpdateModel, + LibraryUpsertModel, +} from "@/types/KnowledgeTypes"; + +export const useKnowledgeUpsert = () => { + const upsertLibrary = async (library: LibraryUpsertModel) => { + await client.POST("/knowledge/library", { + body: { + id: library.id, + name: library.name, + description: library.description, + }, + }); + }; + + const deleteLibrary = async (libraryId: number) => { + await client.DELETE("/knowledge/library", { + params: { + query: { + libraryId, + }, + }, + }); + }; + + const uploadDoc = async (libraryId: number, file: File) => { + await client.POST("/knowledge/doc/upload", { + body: { + libraryId: libraryId.toString(), + file: file as unknown as string, + }, + bodySerializer: (body) => { + const formData = new FormData(); + for (const [key, value] of Object.entries(body!)) { + formData.set(key, value as unknown as string); + } + return formData; + }, + parseAs: "text", + }); + }; + + const deleteDoc = async (libraryDocId: number) => { + await client.DELETE("/knowledge/doc", { + params: { + query: { + libraryDocId, + }, + }, + }); + }; + + const updateDoc = async (doc: DocUpdateModel) => { + await client.PUT("/knowledge/doc", { + body: { + id: doc.id, + libId: doc.libId, + enable: doc.enable, + }, + }); + }; + + return { + upsertLibrary, + deleteLibrary, + uploadDoc, + deleteDoc, + updateDoc, + }; +}; diff --git a/frontend/src/router/constants.ts b/frontend/src/router/constants.ts index ab23b80..d8ed54f 100644 --- a/frontend/src/router/constants.ts +++ b/frontend/src/router/constants.ts @@ -133,9 +133,9 @@ export const UserRoutes = { export const AiRoutes = { LLMCONFIGVIEW: { path: "llm/config", - name: "llm/config", + name: "llm-config", fullPath: () => `${BaseRoutes.DASHBOARD.path}/llm/config`, - withParams: () => ({ name: "llm/config" }), + withParams: () => ({ name: "llm-config" }), }, SCHEDULERVIEW: { path: "scheduler", @@ -143,6 +143,37 @@ export const AiRoutes = { fullPath: () => `${BaseRoutes.DASHBOARD.path}/scheduler`, withParams: () => ({ name: "scheduler" }), }, + KNOWLEDGEVIEW: { + path: "knowledge", + name: "knowledge", + fullPath: () => `${BaseRoutes.DASHBOARD.path}/knowledge`, + withParams: () => ({ name: "knowledge" }), + }, + KNOWLEDGEDOCVIEW: { + path: "knowledge/:libraryId", + name: "knowledge-docs", + fullPath: () => `${BaseRoutes.DASHBOARD.path}/knowledge/:libraryId`, + withParams: (params: T) => ({ + name: "knowledge-docs", + params: { libraryId: params.libraryId.toString() }, + }), + }, + KNOWLEDGESEGMENTSVIEW: { + path: "knowledge/:libraryId/:docId", + name: "knowledge-segments", + fullPath: () => `${BaseRoutes.DASHBOARD.path}/knowledge/:libraryId/:docId`, + withParams: < + T extends { libraryId: string | number; docId: string | number }, + >( + params: T, + ) => ({ + name: "knowledge-segments", + params: { + libraryId: params.libraryId.toString(), + docId: params.docId.toString(), + }, + }), + }, } as const; export const Routes = { diff --git a/frontend/src/router/guards.ts b/frontend/src/router/guards.ts index df43b55..8b56689 100644 --- a/frontend/src/router/guards.ts +++ b/frontend/src/router/guards.ts @@ -13,7 +13,7 @@ export const authGuard: NavigationGuard = (to) => { }; } if (to.path === Routes.LOGIN.path && userStore.user) { - return { path: `${Routes.DASHBOARD.path}/${Routes.USERVIEW.path}` }; + return { path: Routes.USERVIEW.fullPath() }; } }; diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d96e956..c88f252 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -15,7 +15,7 @@ const routes: RouteRecordRaw[] = [ path: Routes.HOME.path, name: Routes.HOME.name, redirect: { - path: `${Routes.DASHBOARD.path}/${Routes.USERVIEW.path}`, + path: Routes.USERVIEW.fullPath(), }, }, ]; @@ -27,7 +27,7 @@ const router = createRouter({ router.onError((err) => { console.error("router err:", err); - router.push(Routes.USERVIEW.name); + router.push(Routes.USERVIEW.fullPath()); return false; }); diff --git a/frontend/src/router/modules/ai.ts b/frontend/src/router/modules/ai.ts index 0bce41d..e48f45b 100644 --- a/frontend/src/router/modules/ai.ts +++ b/frontend/src/router/modules/ai.ts @@ -11,6 +11,30 @@ const aiRoutes: RouteRecordRaw[] = [ hasPermission: EPermission.READ_LLM_CONFIG_PERMISSION, }, }, + { + path: Routes.KNOWLEDGEVIEW.path, + name: Routes.KNOWLEDGEVIEW.name, + component: () => import("@/views/KnowledgeManagementPage.vue"), + meta: { + requiresAuth: true, + }, + }, + { + path: Routes.KNOWLEDGEDOCVIEW.path, + name: Routes.KNOWLEDGEDOCVIEW.name, + component: () => import("@/views/KnowledgeDocManagementPage.vue"), + meta: { + requiresAuth: true, + }, + }, + { + path: Routes.KNOWLEDGESEGMENTSVIEW.path, + name: Routes.KNOWLEDGESEGMENTSVIEW.name, + component: () => import("@/views/KnowledgeSegmentsPage.vue"), + meta: { + requiresAuth: true, + }, + }, ]; export default aiRoutes; diff --git a/frontend/src/router/modules/dashboard.ts b/frontend/src/router/modules/dashboard.ts index ecb391d..6de228e 100644 --- a/frontend/src/router/modules/dashboard.ts +++ b/frontend/src/router/modules/dashboard.ts @@ -12,6 +12,8 @@ const dashboardRoutes: RouteRecordRaw = { requiresAuth: true, }, children: [ + ...userManagementRoutes, + ...aiRoutes, { path: Routes.OVERVIEW.path, name: Routes.OVERVIEW.name, @@ -28,8 +30,6 @@ const dashboardRoutes: RouteRecordRaw = { requiresAuth: true, }, }, - ...userManagementRoutes, - ...aiRoutes, { path: Routes.NOTFOUND.path, name: Routes.NOTFOUND.name, diff --git a/frontend/src/types/KnowledgeTypes.ts b/frontend/src/types/KnowledgeTypes.ts new file mode 100644 index 0000000..c28a3e9 --- /dev/null +++ b/frontend/src/types/KnowledgeTypes.ts @@ -0,0 +1,35 @@ +import type { components } from "@/api/types/schema"; + +export type Library = components["schemas"]["Library"]; +export type LibraryDoc = components["schemas"]["LibraryDoc"]; +export type LibraryDocSegment = components["schemas"]["LibraryDocSegment"]; + +export interface LibraryUpsertModel { + id?: number; + name: string; + description?: string; +} + +export interface DocUpdateModel { + id: number; + libId: number; + enable: boolean; +} + +export interface LibraryQueryParams { + page: number; + size: number; +} + +export interface DocQueryParams { + libraryId: number; +} + +export interface SegmentQueryParams { + libraryDocId: number; +} + +export enum DocStatus { + SUCCESS = "SUCCESS", + INDEXING = "INDEXING", +} diff --git a/frontend/src/views/KnowledgeDocManagementPage.vue b/frontend/src/views/KnowledgeDocManagementPage.vue new file mode 100644 index 0000000..de84fb3 --- /dev/null +++ b/frontend/src/views/KnowledgeDocManagementPage.vue @@ -0,0 +1,238 @@ + + + diff --git a/frontend/src/views/KnowledgeManagementPage.vue b/frontend/src/views/KnowledgeManagementPage.vue new file mode 100644 index 0000000..e417393 --- /dev/null +++ b/frontend/src/views/KnowledgeManagementPage.vue @@ -0,0 +1,177 @@ + + + diff --git a/frontend/src/views/KnowledgeSegmentsPage.vue b/frontend/src/views/KnowledgeSegmentsPage.vue new file mode 100644 index 0000000..b85bbf8 --- /dev/null +++ b/frontend/src/views/KnowledgeSegmentsPage.vue @@ -0,0 +1,97 @@ + + + diff --git a/frontend/src/views/RoleManagementPage.vue b/frontend/src/views/RoleManagementPage.vue index 1fa0805..d4ffb6a 100644 --- a/frontend/src/views/RoleManagementPage.vue +++ b/frontend/src/views/RoleManagementPage.vue @@ -1,6 +1,6 @@