Browse Source

Merge branch 'master' of http://173.18.12.196:3000/front/self-constructing_graph

cynthia-qin 3 tuần trước cách đây
mục cha
commit
81a7fb0af0
49 tập tin đã thay đổi với 3807 bổ sung1233 xóa
  1. 2 2
      .env.development
  2. 10 0
      auto-imports.d.ts
  3. 93 0
      components.d.ts
  4. 508 10
      package-lock.json
  5. 2 0
      package.json
  6. 179 1
      src/api/AgentApi.ts
  7. 279 34
      src/api/GraphApi.ts
  8. 1 0
      src/assets/css/common.less
  9. 14 3
      src/components/CreateKBFileDialog.vue
  10. 3 2
      src/components/EditKBDialog.vue
  11. 4 2
      src/components/EditKBFileDialog.vue
  12. 44 52
      src/components/FileViewer/FileViewer.vue
  13. 1 3
      src/components/FileViewer/PdfViewer.vue
  14. 62 56
      src/components/LayoutHeader.vue
  15. 75 131
      src/components/SideMenu.vue
  16. 2 2
      src/dialogs/GraphNodeDialog.vue
  17. 471 0
      src/dialogs/GraphSchemaDialog.vue
  18. 167 53
      src/dialogs/OCRDialog.vue
  19. 4 6
      src/main.ts
  20. 99 19
      src/router/index.ts
  21. 17 0
      src/stores/knowledgeBase.js
  22. 72 33
      src/stores/menu.js
  23. 63 0
      src/types/dataset.ts
  24. 58 55
      src/utils/app.ts
  25. 13 8
      src/utils/http.js
  26. 14 8
      src/utils/misc.ts
  27. 69 63
      src/utils/request.ts
  28. 1 1
      src/utils/session.ts
  29. 15 0
      src/views/404/404.vue
  30. 264 0
      src/views/ConfigurationView.vue
  31. 19 48
      src/views/AppCopy.vue
  32. 81 75
      src/views/GraphManagement.vue
  33. 126 0
      src/views/GraphStdSchemas.vue
  34. 63 162
      src/views/GraphView.vue
  35. 294 0
      src/views/JobView.vue
  36. 50 41
      src/views/KMPlatform/Home/Home.vue
  37. 4 4
      src/views/KMPlatform/KGBuilder/ETM/EntityTypeManagement.vue
  38. 49 37
      src/views/KMPlatform/KnowledgeBase/KBM/KnowledgeBaseManagement.vue
  39. 73 38
      src/views/KMPlatform/KnowledgeBase/KM/KnowledgeManagement.vue
  40. 1 2
      src/views/KMPlatform/Layout.vue
  41. 17 1
      src/views/KMPlatform/OpenPlatform/OpenPlatform.vue
  42. 26 2
      src/views/KMPlatform/OpenPlatform/platformText.vue
  43. 32 72
      src/views/KMPlatform/Permission/AccountManage.vue
  44. 56 55
      src/views/KMPlatform/Permission/Organizational.vue
  45. 29 45
      src/views/KMPlatform/Permission/RoleManage.vue
  46. 8 10
      src/views/KMPlatform/Permission/permission.vue
  47. 0 1
      src/views/Login/Login.vue
  48. 263 95
      src/views/QueueView.vue
  49. 10 1
      vite.config.ts

+ 2 - 2
.env.development

@@ -1,4 +1,4 @@
 NODE_ENV = development
-# # VITE_API_URL = http://173.18.12.205:8005
-VITE_API_URL = http://192.18.1.242:8000
+VITE_API_URL = http://173.18.12.205:8005
+# VITE_API_URL = http://192.18.1.242:8000
 # VITE_API_URL = http://192.18.1.235:8000

+ 10 - 0
auto-imports.d.ts

@@ -0,0 +1,10 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+// biome-ignore lint: disable
+export {}
+declare global {
+
+}

+ 93 - 0
components.d.ts

@@ -0,0 +1,93 @@
+/* eslint-disable */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+// biome-ignore lint: disable
+export {}
+
+/* prettier-ignore */
+declare module 'vue' {
+  export interface GlobalComponents {
+    CreateKBFileDialog: typeof import('./src/components/CreateKBFileDialog.vue')['default']
+    DiseaseTerminology: typeof import('./src/components/KGBuilderTM/DiseaseTerminology.vue')['default']
+    DrugTerminology: typeof import('./src/components/KGBuilderTM/DrugTerminology.vue')['default']
+    EditKBDialog: typeof import('./src/components/EditKBDialog.vue')['default']
+    EditKBFileDialog: typeof import('./src/components/EditKBFileDialog.vue')['default']
+    EditPasswordDialog: typeof import('./src/components/EditPasswordDialog.vue')['default']
+    ElAlert: typeof import('element-plus/es')['ElAlert']
+    ElAside: typeof import('element-plus/es')['ElAside']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElCard: typeof import('element-plus/es')['ElCard']
+    ElCol: typeof import('element-plus/es')['ElCol']
+    ElCollapse: typeof import('element-plus/es')['ElCollapse']
+    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
+    ElContainer: typeof import('element-plus/es')['ElContainer']
+    ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
+    ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElDrawer: typeof import('element-plus/es')['ElDrawer']
+    ElDropdown: typeof import('element-plus/es')['ElDropdown']
+    ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
+    ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
+    ElEmpty: typeof import('element-plus/es')['ElEmpty']
+    ElForm: typeof import('element-plus/es')['ElForm']
+    ElFormItem: typeof import('element-plus/es')['ElFormItem']
+    ElHeader: typeof import('element-plus/es')['ElHeader']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputTag: typeof import('element-plus/es')['ElInputTag']
+    ElLink: typeof import('element-plus/es')['ElLink']
+    ElMain: typeof import('element-plus/es')['ElMain']
+    ElMenu: typeof import('element-plus/es')['ElMenu']
+    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
+    ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElPopover: typeof import('element-plus/es')['ElPopover']
+    ElRadio: typeof import('element-plus/es')['ElRadio']
+    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
+    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
+    ElRow: typeof import('element-plus/es')['ElRow']
+    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElStep: typeof import('element-plus/es')['ElStep']
+    ElSteps: typeof import('element-plus/es')['ElSteps']
+    ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
+    ElSwitch: typeof import('element-plus/es')['ElSwitch']
+    ElTable: typeof import('element-plus/es')['ElTable']
+    ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
+    ElTabPane: typeof import('element-plus/es')['ElTabPane']
+    ElTabs: typeof import('element-plus/es')['ElTabs']
+    ElTag: typeof import('element-plus/es')['ElTag']
+    ElText: typeof import('element-plus/es')['ElText']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
+    ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
+    FileViewer: typeof import('./src/components/FileViewer/FileViewer.vue')['default']
+    GraphCategoryMgr: typeof import('./src/components/GraphCategoryMgr.vue')['default']
+    HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
+    IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
+    IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
+    IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
+    IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
+    IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
+    InspectionTerminology: typeof import('./src/components/KGBuilderTM/InspectionTerminology.vue')['default']
+    LayoutHeader: typeof import('./src/components/LayoutHeader.vue')['default']
+    MarkdownViewer: typeof import('./src/components/FileViewer/MarkdownViewer.vue')['default']
+    OtherTerminology: typeof import('./src/components/KGBuilderTM/OtherTerminology.vue')['default']
+    PdfViewer: typeof import('./src/components/FileViewer/PdfViewer.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    SideMenu: typeof import('./src/components/SideMenu.vue')['default']
+    SurgicalOperationTerminology: typeof import('./src/components/KGBuilderTM/SurgicalOperationTerminology.vue')['default']
+    TestingTerminology: typeof import('./src/components/KGBuilderTM/TestingTerminology.vue')['default']
+    TextViewer: typeof import('./src/components/FileViewer/TextViewer.vue')['default']
+    TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
+    WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
+  }
+  export interface GlobalDirectives {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
+}

+ 508 - 10
package-lock.json

@@ -43,6 +43,8 @@
         "@vue/tsconfig": "^0.7.0",
         "npm-run-all2": "^7.0.2",
         "typescript": "~5.8.0",
+        "unplugin-auto-import": "^19.3.0",
+        "unplugin-vue-components": "^28.7.0",
         "vite": "^6.2.1",
         "vite-plugin-vue-devtools": "^7.7.2",
         "vue-tsc": "^2.2.8"
@@ -2939,6 +2941,19 @@
         "url": "https://github.com/sponsors/antfu"
       }
     },
+    "node_modules/acorn": {
+      "version": "8.15.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/alien-signals": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.4.tgz",
@@ -2965,6 +2980,33 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/anymatch/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://mirrors.huaweicloud.com/repository/npm/argparse/-/argparse-2.0.1.tgz",
@@ -3043,6 +3085,19 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/birpc": {
       "version": "0.2.19",
       "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz",
@@ -3062,6 +3117,19 @@
         "balanced-match": "^1.0.0"
       }
     },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/browserslist": {
       "version": "4.25.0",
       "resolved": "https://mirrors.huaweicloud.com/repository/npm/browserslist/-/browserslist-4.25.0.tgz",
@@ -3143,6 +3211,31 @@
       ],
       "license": "CC-BY-4.0"
     },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3155,6 +3248,13 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/confbox": {
+      "version": "0.2.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/confbox/-/confbox-0.2.2.tgz",
+      "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/convert-source-map": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -3264,9 +3364,9 @@
       "license": "MIT"
     },
     "node_modules/debug": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
-      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+      "version": "4.4.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/debug/-/debug-4.4.1.tgz",
+      "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
       "license": "MIT",
       "dependencies": {
         "ms": "^2.1.3"
@@ -3513,6 +3613,19 @@
       "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
       "license": "MIT"
     },
+    "node_modules/escape-string-regexp": {
+      "version": "5.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -3556,10 +3669,17 @@
         "url": "https://github.com/sindresorhus/execa?sponsor=1"
       }
     },
+    "node_modules/exsolve": {
+      "version": "1.0.7",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/exsolve/-/exsolve-1.0.7.tgz",
+      "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/fdir": {
-      "version": "6.4.3",
-      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
-      "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+      "version": "6.4.6",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fdir/-/fdir-6.4.6.tgz",
+      "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
       "license": "MIT",
       "peerDependencies": {
         "picomatch": "^3 || ^4"
@@ -3592,6 +3712,19 @@
       "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
       "license": "MIT"
     },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/follow-redirects": {
       "version": "1.15.9",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -3740,6 +3873,19 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/globals": {
       "version": "11.12.0",
       "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -3868,6 +4014,19 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/is-core-module": {
       "version": "2.16.1",
       "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-core-module/-/is-core-module-2.16.1.tgz",
@@ -3900,6 +4059,29 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/is-inside-container": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@@ -3919,6 +4101,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
     "node_modules/is-plain-obj": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -4141,6 +4333,24 @@
         "uc.micro": "^2.0.0"
       }
     },
+    "node_modules/local-pkg": {
+      "version": "1.1.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/local-pkg/-/local-pkg-1.1.1.tgz",
+      "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.7.4",
+        "pkg-types": "^2.0.1",
+        "quansync": "^0.2.8"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
     "node_modules/lodash": {
       "version": "4.17.21",
       "resolved": "https://mirrors.huaweicloud.com/repository/npm/lodash/-/lodash-4.17.21.tgz",
@@ -4316,6 +4526,38 @@
       "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
       "license": "MIT"
     },
+    "node_modules/mlly": {
+      "version": "1.7.4",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/mlly/-/mlly-1.7.4.tgz",
+      "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "pathe": "^2.0.1",
+        "pkg-types": "^1.3.0",
+        "ufo": "^1.5.4"
+      }
+    },
+    "node_modules/mlly/node_modules/confbox": {
+      "version": "0.1.8",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/confbox/-/confbox-0.1.8.tgz",
+      "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/mlly/node_modules/pkg-types": {
+      "version": "1.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/pkg-types/-/pkg-types-1.3.1.tgz",
+      "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.1.8",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.1"
+      }
+    },
     "node_modules/mrmime": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -4380,6 +4622,16 @@
       "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
       "license": "MIT"
     },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/normalize-wheel-es": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
@@ -4593,6 +4845,18 @@
         }
       }
     },
+    "node_modules/pkg-types": {
+      "version": "2.1.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/pkg-types/-/pkg-types-2.1.0.tgz",
+      "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "confbox": "^0.2.1",
+        "exsolve": "^1.0.1",
+        "pathe": "^2.0.3"
+      }
+    },
     "node_modules/postcss": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
@@ -4659,6 +4923,23 @@
         "node": ">=6"
       }
     },
+    "node_modules/quansync": {
+      "version": "0.2.10",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/quansync/-/quansync-0.2.10.tgz",
+      "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/antfu"
+        },
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/sxzz"
+        }
+      ],
+      "license": "MIT"
+    },
     "node_modules/read-package-json-fast": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz",
@@ -4673,6 +4954,32 @@
         "node": "^18.17.0 || >=20.5.0"
       }
     },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/readdirp/node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
     "node_modules/regenerate": {
       "version": "1.4.2",
       "resolved": "https://mirrors.huaweicloud.com/repository/npm/regenerate/-/regenerate-1.4.2.tgz",
@@ -4836,6 +5143,13 @@
       "license": "ISC",
       "optional": true
     },
+    "node_modules/scule": {
+      "version": "1.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/scule/-/scule-1.3.0.tgz",
+      "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/semver": {
       "version": "6.3.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -4950,6 +5264,26 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/strip-literal": {
+      "version": "3.0.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/strip-literal/-/strip-literal-3.0.0.tgz",
+      "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^9.0.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/strip-literal/node_modules/js-tokens": {
+      "version": "9.0.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/js-tokens/-/js-tokens-9.0.1.tgz",
+      "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/superjson": {
       "version": "2.2.2",
       "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
@@ -4982,12 +5316,12 @@
       "license": "BSD-2-Clause"
     },
     "node_modules/tinyglobby": {
-      "version": "0.2.12",
-      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
-      "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
+      "version": "0.2.14",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/tinyglobby/-/tinyglobby-0.2.14.tgz",
+      "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
       "license": "MIT",
       "dependencies": {
-        "fdir": "^6.4.3",
+        "fdir": "^6.4.4",
         "picomatch": "^4.0.2"
       },
       "engines": {
@@ -4997,6 +5331,19 @@
         "url": "https://github.com/sponsors/SuperchupuDev"
       }
     },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
     "node_modules/totalist": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -5033,6 +5380,13 @@
       "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
       "license": "MIT"
     },
+    "node_modules/ufo": {
+      "version": "1.6.1",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/ufo/-/ufo-1.6.1.tgz",
+      "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/undici-types": {
       "version": "6.20.0",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
@@ -5097,6 +5451,42 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/unimport": {
+      "version": "4.2.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unimport/-/unimport-4.2.0.tgz",
+      "integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.1",
+        "escape-string-regexp": "^5.0.0",
+        "estree-walker": "^3.0.3",
+        "local-pkg": "^1.1.1",
+        "magic-string": "^0.30.17",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.3",
+        "picomatch": "^4.0.2",
+        "pkg-types": "^2.1.0",
+        "scule": "^1.3.0",
+        "strip-literal": "^3.0.0",
+        "tinyglobby": "^0.2.12",
+        "unplugin": "^2.2.2",
+        "unplugin-utils": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unimport/node_modules/estree-walker": {
+      "version": "3.0.3",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/estree-walker/-/estree-walker-3.0.3.tgz",
+      "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/estree": "^1.0.0"
+      }
+    },
     "node_modules/universalify": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -5107,6 +5497,107 @@
         "node": ">= 10.0.0"
       }
     },
+    "node_modules/unplugin": {
+      "version": "2.3.5",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin/-/unplugin-2.3.5.tgz",
+      "integrity": "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.1",
+        "picomatch": "^4.0.2",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unplugin-auto-import": {
+      "version": "19.3.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin-auto-import/-/unplugin-auto-import-19.3.0.tgz",
+      "integrity": "sha512-iIi0u4Gq2uGkAOGqlPJOAMI8vocvjh1clGTfSK4SOrJKrt+tirrixo/FjgBwXQNNdS7ofcr7OxzmOb/RjWxeEQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "local-pkg": "^1.1.1",
+        "magic-string": "^0.30.17",
+        "picomatch": "^4.0.2",
+        "unimport": "^4.2.0",
+        "unplugin": "^2.3.4",
+        "unplugin-utils": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@nuxt/kit": "^3.2.2",
+        "@vueuse/core": "*"
+      },
+      "peerDependenciesMeta": {
+        "@nuxt/kit": {
+          "optional": true
+        },
+        "@vueuse/core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/unplugin-utils": {
+      "version": "0.2.4",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
+      "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "pathe": "^2.0.2",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sxzz"
+      }
+    },
+    "node_modules/unplugin-vue-components": {
+      "version": "28.7.0",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/unplugin-vue-components/-/unplugin-vue-components-28.7.0.tgz",
+      "integrity": "sha512-3SuWAHlTjOiZckqRBGXRdN/k6IMmKyt2Ch5/+DKwYaT321H0ItdZDvW4r8/YkEKQpN9TN3F/SZ0W342gQROC3Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^3.6.0",
+        "debug": "^4.4.1",
+        "local-pkg": "^1.1.1",
+        "magic-string": "^0.30.17",
+        "mlly": "^1.7.4",
+        "tinyglobby": "^0.2.14",
+        "unplugin": "^2.3.4",
+        "unplugin-utils": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      },
+      "peerDependencies": {
+        "@babel/parser": "^7.15.8",
+        "@nuxt/kit": "^3.2.2",
+        "vue": "2 || 3"
+      },
+      "peerDependenciesMeta": {
+        "@babel/parser": {
+          "optional": true
+        },
+        "@nuxt/kit": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/update-browserslist-db": {
       "version": "1.1.3",
       "resolved": "https://mirrors.huaweicloud.com/repository/npm/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@@ -5391,6 +5882,13 @@
         "typescript": ">=5.0.0"
       }
     },
+    "node_modules/webpack-virtual-modules": {
+      "version": "0.6.2",
+      "resolved": "https://mirrors.huaweicloud.com/repository/npm/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+      "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/which": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",

+ 2 - 0
package.json

@@ -47,6 +47,8 @@
     "@vue/tsconfig": "^0.7.0",
     "npm-run-all2": "^7.0.2",
     "typescript": "~5.8.0",
+    "unplugin-auto-import": "^19.3.0",
+    "unplugin-vue-components": "^28.7.0",
     "vite": "^6.2.1",
     "vite-plugin-vue-devtools": "^7.7.2",
     "vue-tsc": "^2.2.8"

+ 179 - 1
src/api/AgentApi.ts

@@ -3,6 +3,7 @@ import { getRequestId } from '@/utils/misc'
 export interface StandardResponse {
     code: number
     message: string
+    data: any,
     meta: any
     records: any
 }
@@ -12,6 +13,7 @@ const URL_GET_QUEUE = '/api/agent/queue'
 const URL_GET_FILE = '/api/file/browse'
 const URL_GET_USER = '/api/user/session'
 const URL_PUT_USER = '/api/user/signin'
+const URL_GET_CONFIG = '/api/config/do'
 const SUCCESS = 200
 const FAILED = 500
 const PROCESSING = 202
@@ -19,6 +21,36 @@ interface RequestActionParams { // 定义Job接口
     name: string
     value: any
 }
+export interface GraphData {
+    id: string
+    category: string
+    name: string
+    graph_description: string
+    graph_id: number
+    created: string
+    updated: string
+    status: number
+}
+export interface SearchData {
+    job_name: string,
+    start_category: string,
+    job_category: string,
+    status: number,
+}
+export interface JobData {
+    id: number
+    start_category: string
+    job_category: string
+    job_name: string
+    job_details: string
+    job_creator: string
+    job_status: string
+    job_logs: string
+    status: number
+    executor: string
+    created: Date
+    updated: Date
+}
 export interface RequestBody { // 定义Job接口
     id: string
     action: string
@@ -36,8 +68,21 @@ export interface JobData {
     created: Date
     updated: Date
 }
+export const JobCategory = {
+    "SYSTEM_WORD": "WORD文档文字提取",
+    "SYSTEM_OCR": "PDF文档文字提取",
+    "SYSTEM_PDF": "PDF文档文字提取",
+    "SYSTEM_IMAGE": "图片文字提取",
+    "SYSTEM_CHUNKS": "文本分块",
+    "SYSTEM_EMBEDDINGS": "文本向量化",
+    "SYSTEM_SUMMARIZE": "文本摘要",
+    "SYSTEM_QA": "文本问答",
+    "SYSTEM_KB_BUILD": "知识图谱构建",
+    "SYSTEM_KB_EXTRACT": "抽取知识",
+}
 export const JobSatus = [
     //primary success info warning danger
+    { "label": "全部", "value": 9999, "tag": "danger", "could_be_set": false },
     { "label": "无效", "value": -1, "tag": "danger", "could_be_set": false },
     { "label": "准备中", "value": 0, "tag": "primary", "could_be_set": true },
     { "label": "运行中", "value": 1, "tag": "success", "could_be_set": false },
@@ -51,7 +96,40 @@ export const JobSatus = [
     { "label": "跳过", "value": 9, "tag": "info", "could_be_set": true },
     { "label": "重试", "value": 10, "tag": "warning", "could_be_set": true },
 ]
-
+export function getJobCategory(category: string): string {
+    if (category in JobCategory) {
+        return JobCategory[category as keyof typeof JobCategory]
+    }
+    return category
+}
+export function searchJobs(params: SearchData, page: number = 1, page_size: number = 10): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'search_jobs',
+        params: [
+            { name: 'job_status', value: params.status },
+            { name: 'job_category', value: params.job_category },
+            { name: 'start_category', value: params.start_category },
+            { name: 'job_name', value: params.job_name },
+            { name: 'page', value: page },
+            { name: 'page_size', value: page_size },
+        ]
+    }
+    return serverRequest(URL_GET_QUEUE, data)
+}
+export function getStartQueueJobs(categroy: string, name: string, page: number = 1, page_size: number = 10): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_jobs_start_from_queue',
+        params: [
+            { name: 'queue_category', value: categroy },
+            { name: 'queue_name', value: name },
+            { name: 'page', value: page },
+            { name: 'page_size', value: page_size },
+        ]
+    }
+    return serverRequest(URL_GET_QUEUE, data)
+}
 export function getJobStatus(status: number): string {
     var result = JobSatus.find(item => item.value == status)
     if (result) {
@@ -270,4 +348,104 @@ export function getKnowledgeBase(params: any = { name: "", pageNo: 1, pageSize:
 
 export function getKnowledgeBaseFilesList(params: any): Promise<StandardResponse> {
     return serverGetRequest(`/open-platform/knowledge-base/${params.kbId}/files/?file_name=${params.file_name}&pageNo=${params.pageNo}&pageSize=${params.pageSize}`)
+}
+
+//获取可用文件列表
+export function getKnowledgeBaseEnableFiles(kbId: number | string): Promise<StandardResponse> {
+    return serverGetRequest(`/open-platform/knowledge-base/${kbId}/files/enable/?status=1`)
+}
+
+export function getConfig(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_config',
+        params: [
+            { name: 'code', value: params['code'] },
+        ]
+    }
+    return serverRequest(URL_GET_CONFIG, data)
+}
+export function getQueues(): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_queues',
+        params: []
+    }
+    return serverRequest(URL_GET_QUEUE, data)
+}
+export function setConfig(params: any): Promise<StandardResponse> {
+    console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'set_config',
+        params: [
+            { name: 'code', value: params['code'] },
+            { name: 'name', value: params['name'] },
+            { name: 'content', value: params['content'] },
+        ]
+    }
+    return serverRequest(URL_GET_CONFIG, data)
+}
+
+export async function serverStreamRequest(url: string, body: RequestBody) {
+    // var instance = axios.create({baseURL: 'http://127.0.0.1:8000', timeout: 80000})
+    // const handleStream = (response:any) => {
+    //     console.log("Stream started")
+    //     const reader = response.data.getReader()
+    //     const decoder = new TextDecoder()
+    //     let done:boolean = false 
+    //     while (!done) {
+    //       reader.read().then(({ done, value }:any) => {
+    //         done = done
+    //         if (done) {
+    //           console.log("Stream finished")
+    //         } else {
+    //           const chunkValue = decoder.decode(value)
+    //           console.log(chunkValue)
+    //         }
+    //       })
+    //     }
+    // }
+
+    // instance.post(url, body, {responseType: 'stream'})
+    // .then(handleStream)
+    // .catch(error => {
+    // console.error("Error:", error) 
+    // })
+    try {
+        let response = await fetch('http://127.0.0.1:8000' + url, {
+            method: 'POST', body: JSON.stringify(body),
+            headers: {
+                'Content-Type': 'application/json',
+            }
+        });
+        console.log(response);
+        if (!response.ok) {
+            throw new Error('Network response was not ok');
+        }
+        if (response.body === null) {
+            throw new Error('Response body is null');
+        }
+        const reader = response.body.getReader();
+        const textDecoder = new TextDecoder();
+        let result = true;
+        let output = ''
+        while (result) {
+            const { done, value } = await reader.read();
+
+            if (done) {
+                console.log('Stream ended');
+                result = false;
+                break;
+            }
+            const chunkText = textDecoder.decode(value);
+            output += chunkText;
+            console.log('Received chunk:', chunkText);
+        }
+    } catch (e) {
+        console.log(e);
+    }
+
+
 }

+ 279 - 34
src/api/GraphApi.ts

@@ -1,67 +1,312 @@
-import r from '@/utils/request'
+import { Request, AsyncRequest } from '@/utils/request'
 import { getRequestId } from '@/utils/misc'
-import { serverRequest,type RequestBody, type StandardResponse } from '@/api/AgentApi'
+import { serverRequest, type RequestBody, type StandardResponse } from '@/api/AgentApi'
 
 const URL_GET_NODE = '/api/kb/nodes'
+const URL_GET_EDGE = '/api/kb/edges'
 const URL_GET_SUMMARY = '/api/kb/summary'
 const URL_GET_SCHEMA = '/api/kb/schemas'
-export function searchNodes(keyword:string, graph_id:number): Promise<StandardResponse> {
-    var data:RequestBody = {
+const URL_GET_GRAPHS = '/api/kb/graphs'
+const URL_GET_STD_SCHEMA = '/api/kb/std-schemas'
+const URL_GET_REPORT = '/api/kb/report'
+
+export const GraphStatus = {
+    GRAPH_STATUS_NORMAL: 0,
+    GRAPH_STATUS_DELETED: -1,
+    GRAPH_STATUS_NEED_CHECK: 1,
+    GRAPH_STATUS_CHECKING: 2,
+    GRAPH_STATUS_CHECKED: 3,
+}
+export function getGraphCheckReport(graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_latest_report',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
+}
+export function getGraphCheckReportEvents(graph_id: number, page: number, page_size: number, event_name: string): Promise<StandardResponse> {
+    var data: RequestBody = {
         id: getRequestId(),
-        action: 'search_nodes', 
-        params:[
-            {name: 'name', value: keyword} ,
-            {name: 'category', value: "any"} ,
-            {name: 'graph_id', value: graph_id},
+        action: 'get_report_events',
+        params: [
+            { name: 'graph_id', value: graph_id },
+            { name: 'page', value: page },
+            { name: 'page_size', value: page_size },
+            { name: 'event_name', value: event_name },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
+}
+export function getGraphCheckProgressValue(progressData: any): number {
+    for (var i = 0; i < progressData.length; i++) {
+        if (progressData[i].name == "progress") {
+            return Math.ceil(progressData[i].value * 1000) / 10
+        }
+    }
+    return 0
+}
+
+export function getGraphCheckProgressData(progressData: any): any {
+    for (var i = 0; i < progressData.length; i++) {
+        if (progressData[i].name == "progress") {
+            return progressData[i].data
+        }
+    }
+    return 0
+}
+
+export function getGraph(graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_graph',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_GRAPHS, data)
+}
+export function getGraphs(params: any): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_graphs',
+        params: [
+            { name: 'page', value: params['page'] },
+            { name: 'page_size', value: params['page_size'] },
+        ]
+    }
+    return serverRequest(URL_GET_GRAPHS, data)
+}
+
+export function applyGraph(graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'confirm_graph',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_GRAPHS, data)
+}
+
+export function searchNodes(keyword: string, graph_id: number, category: string = "any"): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'search_nodes',
+        params: [
+            { name: 'name', value: keyword },
+            { name: 'category', value: category },
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_NODE, data)
+}
+
+export function getNode(node_id: number, graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_node',
+        params: [
+            { name: 'node_id', value: node_id },
+            { name: 'graph_id', value: graph_id },
         ]
     }
     return serverRequest(URL_GET_NODE, data)
 }
 
-export function getNodeNeighbors(node_id:number, graph_id:number): Promise<StandardResponse> {
-    var data:RequestBody = {
+// export function getIndividualNodes(graph_id:number): Promise<StandardResponse> {
+//     var data:RequestBody = {
+//         id: getRequestId(),
+//         action:'get_individual_nodes',
+//         params:[
+//             {name: 'graph_id', value: graph_id},
+//         ]
+//     } 
+//     return serverRequest(URL_GET_NODE, data)
+// }
+
+// export function getContradictions(graph_id:number): Promise<StandardResponse> {
+//     var data:RequestBody = {
+//         id: getRequestId(),
+//         action:'get_contradictions',
+//         params:[
+//             {name: 'graph_id', value: graph_id},
+//         ]
+//     } 
+//     return serverRequest(URL_GET_NODE, data)
+// }
+export function updateNode(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
         id: getRequestId(),
-        action:'neighbors',
-        params:[
-            {name: 'node_id', value: node_id},
-            {name: 'graph_id', value: graph_id},
+        action: 'update_node',
+        params: [
+            { name: 'node_id', value: params['node_id'] },
+            { name: 'graph_id', value: params['graph_id'] },
+            { name: 'name', value: params['name'] },
+            { name: 'category', value: params['category'] },
         ]
-    } 
+    }
     return serverRequest(URL_GET_NODE, data)
 }
-export function getGraphSummary(params:any): Promise<StandardResponse> {
-    console.log(params)
-    var data:RequestBody = {
+export function searchEdges(keyword: string, graph_id: number, category: string = "any"): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'search_edges',
+        params: [
+            { name: 'name', value: keyword },
+            { name: 'category', value: category },
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_EDGE, data)
+}
+export function getNodeNeighbors(node_id: number, graph_id: number): Promise<StandardResponse> {
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'neighbors',
+        params: [
+            { name: 'node_id', value: node_id },
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_NODE, data)
+}
+export function getGraphSummary(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
         id: getRequestId(),
         action: 'get_summary',
-        params:[
-            {name: 'graph_id', value: params['graph_id']},      
-        ] 
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+        ]
     }
     return serverRequest(URL_GET_SUMMARY, data)
 }
 
-export function getGraphNodeSchema(params:any): Promise<StandardResponse> {
-    console.log(params)
-    var data:RequestBody = {
+export function getGraphNodeSchema(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
         id: getRequestId(),
         action: 'get_nodes_schemas',
-        params:[
-            {name: 'graph_id', value: params['graph_id']},
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
         ]
-    } 
+    }
     return serverRequest(URL_GET_SCHEMA, data)
 }
 
 
-export function getGraphEdgeSchema(params:any): Promise<StandardResponse> {
-    console.log(params)
-    var data:RequestBody = {
+export function getGraphEdgeSchema(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
         id: getRequestId(),
         action: 'get_edges_schemas',
-        params:[
-            {name: 'graph_id', value: params['graph_id']},
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_SCHEMA, data)
+}
+
+export function getGraphStdSchemas(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'get_std_schemas',
+        params: [
+            { name: 'page', value: params['page'] },
+            { name: 'page_size', value: params['page_size'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+
+export function reindexSchema(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'index_std_schema',
+        params: [
+            { name: 'schema_id', value: params['schema_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+
+export function searchStdSchema(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'std_schema_search',
+        params: [
+            { name: 'query_word', value: params['query_word'] },
+            { name: 'type', value: params['type'] },
+            { name: 'schema_id', value: params['schema_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+export function updateStdSchemaContent(params: any): Promise<StandardResponse> {
+    // console.log("updateStdSchemaContent", params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'std_schema_update_content',
+        params: [
+            { name: 'schema_id', value: params['schema_id'] },
+            { name: 'content', value: params['content'] },
+        ]
+    }
+    return serverRequest(URL_GET_STD_SCHEMA, data)
+}
+export function updateGraphNodeSchema(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'update_node_schema',
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+            { name: 'category', value: params['category'] },
+            { name: 'category_new', value: params['category_new'] },
+        ]
+    }
+    return serverRequest(URL_GET_SCHEMA, data)
+}
+export function updateGraphEdgeSchema(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'update_edge_schema',
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+            { name: 'category', value: params['category'] },
+            { name: 'category_new', value: params['category_new'] },
         ]
-    } 
+    }
     return serverRequest(URL_GET_SCHEMA, data)
+}
+
+export function createGraphCheckReport(params: any): Promise<StandardResponse> {
+    // console.log(params)
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'create_graph_check_report',
+        params: [
+            { name: 'graph_id', value: params['graph_id'] },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
+}
+export function getGraphCheckProgress(graph_id: number): Promise<StandardResponse> {
+
+    var data: RequestBody = {
+        id: getRequestId(),
+        action: 'graph_report_progress',
+        params: [
+            { name: 'graph_id', value: graph_id },
+        ]
+    }
+    return serverRequest(URL_GET_REPORT, data)
 }

+ 1 - 0
src/assets/css/common.less

@@ -2,6 +2,7 @@
 @import './reset.css';
 
 .hide-scrollbar {
+  overflow: hidden;
 
   &::--webkit--scrollbar {
     display: none

+ 14 - 3
src/components/CreateKBFileDialog.vue

@@ -1,10 +1,10 @@
 <template>
   <div class="add-kb-file-dialog">
     <el-dialog v-model="dialogVisible" title="知识库" width="1000px" align-center :show-close="false"
-      @closed="() => { emit('update:modelValue', false) }">
+      @closed="handleClosed">
       <el-form ref="formRef" style="max-width: 100%" :model="formData" label-width="auto" class="demo-dynamic">
         <el-form-item prop="name" label="知识库名称:">
-          {{ formData.name }}
+          <span style="font-weight: bold;">{{ formData.name }}</span>
         </el-form-item>
         <el-form-item prop="description" label="知识库备注:">
           {{ formData.description }}
@@ -80,7 +80,7 @@
                 <template #default="{ row }">
                   <!-- <p>{{ row.knowledge_type }}</p> -->
                   <el-select v-model="row.knowledge_type" placeholder="Select" style="width: 180px">
-                    <el-option v-for="item in formData.knowledgeTypeOptions" :key="item.value" :label="item.label"
+                    <el-option v-for="item in knowledgeBaseStore.knowledgeType" :key="item.value" :label="item.label"
                       :value="item.value" />
                   </el-select>
                 </template>
@@ -136,6 +136,8 @@ import { ref, watch, getCurrentInstance, toRaw } from "vue"
 import { api } from "@/utils/config"
 import { ElMessage } from 'element-plus'
 import { getSessionVar, deleteSessionVar } from "@/utils/session";
+import { useKnowledgeBaseStore } from "@/stores/knowledgeBase"
+const knowledgeBaseStore = useKnowledgeBaseStore()
 const { proxy } = getCurrentInstance()
 
 const props = defineProps({ modelValue: Boolean, knowledgeBase: Object })
@@ -265,6 +267,15 @@ const beforeRemove = (uploadFile, uploadFiles) => {
   //   () => false
   // )
 }
+
+const handleClosed = () => {
+  emit('update:modelValue', false)
+  if (formData.value.fileTableData.length > 0) {
+    formData.value.fileTableData.slice(0, formData.value.fileTableData.length)
+    proxy.$refs['formRef'].resetFields()
+    proxy.$refs['fileUploadRef'].clearFiles()
+  }
+}
 </script>
 
 <style lang="less" scoped>

+ 3 - 2
src/components/EditKBDialog.vue

@@ -9,8 +9,8 @@
         </el-form-item>
         <label for="">知识库描述</label>
         <el-form-item prop="description" label="">
-          <el-input type="textarea" v-model="formData.description" resize="none"
-            :autosize="{ minRows: 3, maxRows: 3 }" />
+          <el-input type="textarea" v-model="formData.description" resize="none" :autosize="{ minRows: 3 }"
+            maxlength="400" />
         </el-form-item>
       </el-form>
 
@@ -29,6 +29,7 @@
 <script setup>
 import { ref, watch, getCurrentInstance, toRaw } from "vue"
 import { api } from "@/utils/config"
+
 const { proxy } = getCurrentInstance()
 
 const props = defineProps({ modelValue: Boolean, knowledgeBase: Object })

+ 4 - 2
src/components/EditKBFileDialog.vue

@@ -8,7 +8,7 @@
             <el-table :data="formData.fileTableData" border table-layout="fixed" height="400"
               style="max-width: 100%;box-sizing: border-box;min-width: 0px;">
               <el-table-column type="index" label="#" width="50" />
-              <el-table-column label="导入文件标题" prop="file_name">
+              <el-table-column label="文件标题" prop="file_name">
                 <template #default="{ row, $index }">
                   <p :contenteditable="true" class="input-box" @blur="updateInputBox($event, $index, 'file_name')">{{
                     row.file_name }}</p>
@@ -18,7 +18,7 @@
                 <template #default="{ row }">
                   <!-- <p>{{ row.knowledge_type }}</p> -->
                   <el-select v-model="row.knowledge_type" placeholder="Select" style="width: 180px">
-                    <el-option v-for="item in formData.knowledgeTypeOptions" :key="item.value" :label="item.label"
+                    <el-option v-for="item in knowledgeBaseStore.knowledgeType" :key="item.value" :label="item.label"
                       :value="item.value" />
                   </el-select>
                 </template>
@@ -66,6 +66,7 @@ import { cloneDeep } from "lodash"
 import { ref, watch, getCurrentInstance, toRaw } from "vue"
 import { api } from "@/utils/config"
 import { ElMessage } from 'element-plus'
+import { useKnowledgeBaseStore } from "@/stores/knowledgeBase"
 const { proxy } = getCurrentInstance()
 
 const props = defineProps({ modelValue: Boolean, fileTable: Array })
@@ -79,6 +80,7 @@ const formData = ref({
     { value: "临床路径", label: "临床路径" },
   ]
 })
+const knowledgeBaseStore = useKnowledgeBaseStore()
 let dialogVisible = ref(false)
 const handleCancel = () => {
   emit('update:modelValue', false)

+ 44 - 52
src/components/FileViewer/FileViewer.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="document-viewer">
+  <div class="document-viewer" :style="props.fileType === 'pdf' ? { overflow: 'hidden' } : {}">
     <div class="mask"></div>
-    <div ref="containerRef" class="container" :style="{ width: props.width + 'px' }">
-      <div class="top" ref="topRef">
-        <span v-show="props.fileName">{{ props.fileName }}</span>
+    <div ref="containerRef" class="container"
+      :style="{ width: props.width + 'px', paddingTop: containerSize.offset + 'px' }">
+      <div class="top" ref="topRef" :style="{ width: props.width + 'px' }">
+        <span v-show="props.fileName" class="file-name">{{ props.fileName }}</span>
         <span class="close">
           <el-icon @click="emit('closeViewer')">
             <Close />
@@ -46,6 +47,7 @@ const emit = defineEmits(['closeViewer'])
 const topRef = ref()
 const containerRef = ref() //.containerd的ref
 let containerSize = ref({
+  offset: 0,
   width: 0,
   height: 0,
 })
@@ -73,7 +75,9 @@ function errorHandler() {
   emit('closeViewer')
 }
 const handleResize = () => {
+  // console.log(containerRef.value.offsetHeight, topRef.value.offsetHeight)
   containerSize.value = {
+    offset: topRef.value.offsetHeight,
     width: containerRef.value.offsetWidth,
     height: containerRef.value.offsetHeight - topRef.value.offsetHeight
   }
@@ -98,15 +102,12 @@ const handleKeydown = (event) => {
 }
 
 onMounted(() => {
-  containerSize.value = {
-    width: containerRef.value.offsetWidth,
-    height: containerRef.value.offsetHeight - topRef.value.offsetHeight
-  }
-  // window.addEventListener('resize', handleResize)
+  handleResize()
+  window.addEventListener('resize', handleResize)
   window.addEventListener('keydown', handleKeydown)
 })
 onBeforeUnmount(() => {
-  // window.removeEventListener('resize', handleResize)
+  window.removeEventListener('resize', handleResize)
   window.removeEventListener('keydown', handleKeydown)
 })
 </script>
@@ -120,66 +121,31 @@ onBeforeUnmount(() => {
   top: 0;
   left: 0;
   z-index: 100;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
 
   .mask {
-    // height: 100vh;
-    // width: 100vw;
     position: fixed;
-    // overflow: auto;
     top: 0;
     left: 0;
     bottom: 0;
     right: 0;
     z-index: 50;
-    // display: flex;
-    // flex-direction: column;
     background-color: rgb(128, 128, 128, 0.8);
     opacity: 0.5;
   }
 
-  // display: flex;
-  // flex-direction: column;
-  // background-color: rgb(128, 128, 128, 0.8);
-  // opacity: 0.5;
-
-  // opacity: 0.5;
-  .top {
-    // flex: 0 0 auto;
-    background-color: white;
-    clear: both;
-    padding: 5px 10px;
-    position: sticky;
-    z-index: 60;
-    top: 0;
-    display: flex;
-    justify-content: end;
-
-    .close {
-      // width: 100px;
-      margin-left: auto;
-      display: inline-block;
-      cursor: pointer;
-
-      &:hover {}
-
-      // float: right;
-    }
-
-  }
-
   .container {
-    // opacity: 1;
-    min-height: 100%;
-    // height: 100%;
-    // height: 10000px;
-    // flex: 1 1 auto;
+    min-height: 0;
+    flex: 1 1 auto;
     margin: auto;
     box-sizing: border-box;
     background-color: white;
     position: relative;
+    overflow: visible;
     z-index: 60;
 
-    // overflow: auto;
     &:deep(.vue-office-pptx) {
       height: auto;
 
@@ -188,10 +154,36 @@ onBeforeUnmount(() => {
 
         .pptx-preview-wrapper {
           height: auto !important;
-          // overflow: hidden;
         }
       }
     }
+
+    .top {
+      background-color: white;
+      clear: both;
+      padding: 5px 10px;
+      position: fixed;
+      z-index: 65;
+      top: 0px;
+      display: flex;
+      justify-content: end;
+      position: -webkit-sticky;
+
+      .file-name {
+        font-weight: bold;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+      }
+
+      .close {
+
+        margin-left: auto;
+        display: inline-block;
+        cursor: pointer;
+      }
+
+    }
   }
 
   .no-view {

+ 1 - 3
src/components/FileViewer/PdfViewer.vue

@@ -1,6 +1,6 @@
 <template>
   <iframe :src="pdfjsAddr + `?file=${encodeURIComponent(props.src)}`" frameborder="0"
-    :style="{ height: props.parentHeight - 2 + 'px' }" />
+    :style="{ height: props.parentHeight + 'px' }"></iframe>
 </template>
 
 <script setup>
@@ -11,7 +11,5 @@ import { pdfjsAddr } from '@/utils/config';
 <style lang="less" scoped>
 iframe {
   width: 100%;
-  height: 1000px;
-  // box-sizing: margin-box;
 }
 </style>

+ 62 - 56
src/components/LayoutHeader.vue

@@ -3,7 +3,7 @@
     <div class="logo"></div>
     <div class="menu">
       <el-menu :default-active="currentPath" ref="menuRef" class="el-menu-demo" mode="horizontal">
-        <el-menu-item :index="item.path" v-for="item in routeList" :key="item.name"
+        <el-menu-item :index="item.path" v-for="item in menuStore.routeList" :key="item.name"
           :class="{ 'external-link-item': isExternalLink(item.path) }" @click="handleMenuClick(item.path)">{{ item.title
           }}</el-menu-item>
       </el-menu>
@@ -32,20 +32,23 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted, onBeforeUnmount,getCurrentInstance } from 'vue'
+import { ref, computed, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
 import { useMenuStore } from "@/stores/menu.js"
 import { useRoute, useRouter } from "vue-router";
 import { getSessionVar, clearSessionVar, saveSessionVar } from '@/utils/session'
 import EditPasswordDialog from "@/components/EditPasswordDialog.vue"
 import { knowledgeGraphAddr } from "@/utils/config"
-const { updateRouteList } = useMenuStore();
+import { isNotLogin } from "@/utils/app"
+
 const { proxy } = getCurrentInstance()
 const route = useRoute()
 const router = useRouter()
 let timer
-
+let editPassShow = ref(false)
 const menuRef = ref()
-const { routeList } = useMenuStore()
+// const { routeList, updateRouteList } = useMenuStore()
+const menuStore = useMenuStore()
+// console.log(operationPermissions)
 const user = ref({
   id: "0",
   full_name: 'John Doe',
@@ -59,7 +62,7 @@ user.value = {
 
 // 机构相关
 const orgList = ref([])
-const currentOrg = ref(getSessionVar('org_id') || '') // 当前机构id
+let currentOrg = ref(getSessionVar('org_id') || '') // 当前机构id
 
 // 获取机构列表
 const fetchOrgList = async () => {
@@ -67,70 +70,70 @@ const fetchOrgList = async () => {
   const { records } = await proxy.$http.get('/open-platform/sys/loadSURO')
   orgList.value = records
   // 默认选中第一个
-  const res =  await proxy.$http.get('/open-platform/sys/currSURO')
-  console.log('当前机构11:', res)
-    currentOrg.value = res
-    saveSessionVar('org_id', res)
+  const res = await proxy.$http.get('/open-platform/sys/currSURO')
+  // console.log('当前机构11:', res)
+  currentOrg.value = +res
+  saveSessionVar('org_id', +res)
 
-  console.log('机构列表:', orgList.value)
-  console.log('当前机构:', currentOrg.value)
+  // console.log('机构列表:', orgList.value)
+  // console.log('当前机构:', currentOrg.value)
 }
 
 // 切换机构
 const changeOrg = async (orgId) => {
   // 可调用后端切换机构接口
- const res =  await proxy.$http.post(`/open-platform/sys/changeSURO/${orgId}`)
- console.log('切换机构结果:', res)
+  const res = await proxy.$http.post(`/open-platform/sys/changeSURO/${orgId}`)
+  // console.log('切换机构结果:', res)
   saveSessionVar('org_id', orgId)
-   saveSessionVar("knowledageSystem", '');
-    saveSessionVar('routeList', '')
+  saveSessionVar("knowledageSystem", '');
+  saveSessionVar('routeList', '')
   // 可选:刷新页面或重新拉取权限/菜单等
-  updateRouteList([]);
+  menuStore.updateRouteList([]);
   let knowledageSystem = '';
-    let routeList = [{
-      path: '/kmplatform/home',
-      name: 'kmplatform-home',
-      title: "主页",
-      children: []
-    }]
-    res.records[0].menu_permissions.sort((a, b) => {
-      return a.id - b.id;
-    });
-    res.records[0].menu_permissions.forEach((item) => {
-      if (item.menu_name == "知识更新管理") {
-        knowledageSystem = 'true';
-        routeList.push({
-          path: knowledgeGraphAddr,
-          name: '',
-          title: item.name,
-          children: item.children,
-        });
-      } else if (item.menu_route) {
-        routeList.push({
-          path: item.menu_route,
-          name: item.menu_route.split("/")[2],
-          title: item.name,
-          children: item.children,
-        });
-      }
-    });
-    console.log("knowledageSystem", knowledageSystem);
-    saveSessionVar("knowledageSystem", knowledageSystem);
-    saveSessionVar('routeList', JSON.stringify(routeList))
+  let routeList = [{
+    path: '/kmplatform/home',
+    name: 'kmplatform-home',
+    title: "主页",
+    children: []
+  }]
+  res.records[0].menu_permissions.sort((a, b) => {
+    return a.id - b.id;
+  });
+  res.records[0].menu_permissions.forEach((item) => {
+    if (item.menu_name == "知识更新管理") {
+      knowledageSystem = 'true';
+      routeList.push({
+        path: knowledgeGraphAddr,
+        name: '',
+        title: item.name,
+        children: item.children,
+      });
+    } else if (item.menu_route) {
+      routeList.push({
+        path: item.menu_route,
+        name: item.menu_route.split("/")[2],
+        title: item.name,
+        children: item.children,
+      });
+    }
+  });
+  // console.log("knowledageSystem", knowledageSystem);
+  saveSessionVar("knowledageSystem", knowledageSystem);
+  saveSessionVar('routeList', JSON.stringify(routeList))
+  menuStore.updateRouteList(routeList);
 
-    updateRouteList(routeList);
-    
   // 刷新页面
-  window.location.href = '/kmplatform/home';
+  // window.location.href = '/kmplatform/home';
+  router.push({ path: menuStore.routeList[0].path })
 }
 
-let editPassShow = ref(false)
+
 const currentPath = computed(() => {
-  console.log('当前路由:', route)
+  // console.log('当前路由:', route)
   let temp = ""
-  for (let i = 0; i < routeList.length; i++) {
+  for (let i = 0; i < menuStore.routeList.length; i++) {
     for (let j = 0; j < route.matched.length; j++) {
-      if (routeList[i].path === route.matched[j].path) {
+      if (menuStore.routeList[i].path === route.matched[j].path) {
         temp = route.matched[j].path
         break
       }
@@ -152,7 +155,7 @@ function handleMenuClick(path) {
   if (/^https?/g.test(path)) {
     const newWindow = window.open(path, '_blank');
     timer = setInterval(() => {
-      newWindow?.postMessage({ type: 'login', username: getSessionVar("full_name"), userID: getSessionVar("user_id") }, "*")
+      newWindow?.postMessage({ type: 'login', username: getSessionVar("full_name"), userId: getSessionVar("user_id") }, "*")
     }, 1000)
     menuRef.value.updateActiveIndex(currentPath.value)
   } else {
@@ -168,7 +171,10 @@ function handleLogin(event) {
 }
 onMounted(() => {
   window.addEventListener('message', handleLogin);
-  fetchOrgList()
+  (!isNotLogin()) && fetchOrgList()
+  if (route.name === 'kmplatform') {
+    router.push({ path: menuStore.routeList[0].path })
+  }
 })
 
 onBeforeUnmount(() => {

+ 75 - 131
src/components/SideMenu.vue

@@ -1,114 +1,53 @@
 <template>
   <div class="left-nav">
-    <el-menu default-active="1-1" class="el-menu-vertical-demo">
-      <el-sub-menu index="1">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>工作台</span>
-        </template>
-        <el-menu-item-group>
-          <el-menu-item @click="router.push({ name: 'home' })" index="1-1">工作台概览</el-menu-item>
-        </el-menu-item-group>
-      </el-sub-menu>
-      <el-sub-menu index="2">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>工作队列({{ queues.length }})</span>
-        </template>
-        <el-menu-item-group>
-          <template v-for="(queue, index) in queues" :key="queue.id">
-            <el-menu-item :index="'2' + '-' + index" @click="queueClicked(queue)">
-              <!-- <template #title> -->
-              <span>{{ queue.name }}</span>
-              <el-tag v-if="queue.type == 'input'" effect="light" round>START</el-tag>
-              <!-- </template> -->
-            </el-menu-item>
+    <div class="nav-section">
+      <el-menu :default-active="$route.path" router>
+        <h3>工作台</h3>
+        <el-menu-item index="/kmplatform/kgbuilder/home" :route="{ name: 'home' }">
+          <template #title>
+            <el-icon>
+              <Monitor />
+            </el-icon>
+            <span>工作台概览</span>
           </template>
-        </el-menu-item-group>
-      </el-sub-menu>
-      <el-sub-menu index="3">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>知识图谱</span>
-        </template>
-        <el-menu-item-group>
-          <el-menu-item index="3-1" @click="handleMenuClick('graph')">全部图谱</el-menu-item>
-        </el-menu-item-group>
-      </el-sub-menu>
-      <el-sub-menu index="4">
-        <template #title>
-          <i class="el-icon-location"></i>
-          <span>工作人员({{ workers.length }})</span>
-        </template>
-        <el-menu-item-group>
-          <el-menu-item v-for="worker in workers" :key="worker.id" @click="selectWorker(worker)"
-            :index="'4-' + worker.id">
+        </el-menu-item>
+        <el-menu-item index="/kmplatform/kgbuilder/config" :route="{ name: 'config' }">
+          <template #title>
             <el-icon>
-              <User />
+              <Setting />
             </el-icon>
-            <span>{{ worker.name }}</span>
-            <el-tag :type="worker.status === '空闲' ? 'success' : 'warning'" size="small">
-              {{ worker.status }}
-            </el-tag>
-          </el-menu-item>
-        </el-menu-item-group>
-      </el-sub-menu>
-    </el-menu>
-    <!-- <div class="nav-section">
-      <h3>工作台</h3>
-      <el-menu :default-active="$route.path" router>
-        <el-menu-item index="/home" :route="{ path: '/home' }">
+            <span>配置</span>
+          </template>
+        </el-menu-item>
+
+        <h3>文本抽取</h3>
+        <el-menu-item v-for="queue in queues" :key="queue.id"
+          :index="'/kmplatform/kgbuilder/workspace/queue/' + queue.id" @click="queueClicked(queue)">
           <template #title>
-            <el-icon></el-icon>
-            <span>工作台概览</span>
+            <el-icon>
+              <Briefcase />
+            </el-icon>
+            <span>{{ queue.name }}</span>
+            <el-tag v-if="queue.type == 'input'" effect="light" round>START</el-tag>
           </template>
         </el-menu-item>
-      </el-menu>
-    </div>
 
-    <div class="nav-section">
-      <h3>工作队列({{ queues.length }})</h3>
-      <el-menu>
-        <template v-for="queue in queues" :key="queue.id">
-          <el-menu-item :index="'queue' + '_' + queue.id" @click="queueClicked(queue)">
-            <template #title>
-              <span>{{ queue.name }}</span>
-              <el-tag v-if="queue.type == 'input'" effect="light" round>START</el-tag>
-            </template>
-          </el-menu-item>
-        </template>
-      </el-menu>
-    </div>
-    <div class="nav-section">
-      <h3>知识图谱</h3>
-      <el-menu>
-        <el-menu-item @click="handleMenuClick('graph')">
+        <h3>知识图谱</h3>
+        <el-menu-item index="/kmplatform/kgbuilder/workspace/graph" @click="handleMenuClick('graph')">
+          <el-icon>
+            <Collection />
+          </el-icon>
           <span>全部图谱</span>
         </el-menu-item>
-      </el-menu>
-    </div>
-    <div class="nav-section">
-      <h3>工作人员({{ workers.length }})</h3>
-      <el-menu>
-        <el-menu-item v-for="worker in workers" :key="worker.id" @click="selectWorker(worker)">
+        <el-menu-item index="/kmplatform/kgbuilder/workspace/graph-std-schemas"
+          @click="handleMenuClick('graph-std-schemas')">
           <el-icon>
-            <User />
+            <Memo />
           </el-icon>
-          <span>{{ worker.name }}</span>
-          <el-tag :type="worker.status === '空闲' ? 'success' : 'warning'" size="small">
-            {{ worker.status }}
-          </el-tag>
-        </el-menu-item>
-      </el-menu>
-    </div> -->
-    <!-- <div class="nav-section">
-      <h3>知识库管理平台</h3>
-      <el-menu>
-        <el-menu-item @click="router.push({ name: 'kmplatform' })">
-          <span>知识库管理</span>
+          <span>图谱标准</span>
         </el-menu-item>
       </el-menu>
-    </div> -->
+    </div>
   </div>
 </template>
 
@@ -128,46 +67,47 @@ const queues = [
     id: 0,
     name: "全部工作",
     taskCount: 1,
-    tasks: []
-  },
-  {
-    id: 6,
-    name: "WORD文档抽取",
-    taskCount: 1,
     tasks: [],
-    type: "input"
-  },
-  {
-    id: 1,
-    name: "PDF文档抽取",
-    taskCount: 1,
-    tasks: [],
-    type: "input"
+    type: ""
   },
   // {
+  //   id: 6,
+  //   name: "WORD文档抽取",
+  //   taskCount: 1,
+  //   tasks: [],
+  //   type: "input"
+  // },
+  // {
+  //   id: 1,
+  //   name: "PDF文档抽取",
+  //   taskCount: 1,
+  //   tasks: [],
+  //   type: "input"
+  // },
+  // {
   //   id: 2,
   //   name: "文本校对",
   //   taskCount: 1,
   //   tasks: []
   // },
-  {
-    id: 3,
-    name: "文本切片队列",
-    taskCount: 1,
-    tasks: []
-  },
-  {
-    id: 4,
-    name: "知识抽取",
-    taskCount: 1,
-    tasks: []
-  },
-  {
-    id: 5,
-    name: "知识图谱构建",
-    taskCount: 1,
-    tasks: []
-  },
+  // {
+  //   id: 3,
+  //   name: "文本切片队列",
+  //   taskCount: 1,
+  //   tasks: []
+  // },
+  // {
+  //   id: 4,
+  //   name: "知识抽取",
+  //   taskCount: 1,
+  //   tasks: []
+  // },
+  // {
+  //   id: 5,
+  //   name: "知识图谱构建",
+  //   taskCount: 1,
+  //   tasks: []
+  // },
 ]
 
 
@@ -188,7 +128,7 @@ const queueClicked = (queue: any) => {
 }
 
 const handleMenuClick = (menu: string) => {
-  console.log("handleMenuClick called with menu:", menu); // 调试输出
+  // console.log("handleMenuClick called with menu:", menu); // 调试输出
   emit('selectMenu', { name: menu });
 }
 const selectWorker = (worker: Worker) => {
@@ -199,9 +139,13 @@ const selectWorker = (worker: Worker) => {
 </script>
 
 <style scoped lang="less">
+h3 {
+  font-weight: bold;
+}
+
 .left-nav {
   // width: 280px;
-  height: 100vh;
+  // height: 100vh;
 
   // border-right: 0px solid #e4e7ed;
   // padding: 20px;
@@ -222,4 +166,4 @@ const selectWorker = (worker: Worker) => {
 .el-menu-vertical-demo {
   height: 100%;
 }
-</style>
+</style>

+ 2 - 2
src/dialogs/GraphNodeDialog.vue

@@ -21,7 +21,7 @@
 
 <script setup lang="ts">
 
-import { ref, onMounted, defineEmits, nextTick } from 'vue'
+import { ref, onMounted, nextTick } from 'vue'
 import { getNodeNeighbors } from '@/api/GraphApi'
 import svgPanZoom from 'svg-pan-zoom';
 import { ElMessageBox } from 'element-plus';
@@ -31,7 +31,7 @@ const edgesData = ref([{ from: 1, to: 2, fromPos: { x: 100, y: 100 }, toPos: { x
 const svgContainer = ref()
 const props = defineProps({
   title: { type: String, required: true, default: '图谱节点详情' },
-  nodeId: { type: Number, required: true, default: 0 },
+  nodeId: { type: Number, default: 0 },
   graphId: { type: Number, required: true, default: 0 }
 
 })

+ 471 - 0
src/dialogs/GraphSchemaDialog.vue

@@ -0,0 +1,471 @@
+<template>
+
+    <el-dialog title="标准详情" v-model="dialogFormVisible" style="width: 800px; height: 700px;">
+        <el-tabs v-model="dialogCurrentTab" style="width: 100%;">
+            <el-tab-pane label="实体类型" name="tab1">
+                <el-row>
+                    <!-- <el-col :span="12" style=" border-right:1px solid #CDCDCD;"> -->
+
+                    <el-button @click="handleAddSchema('entity')">增加</el-button>
+
+                    <el-table :data="currentData?.entities" style="width: 100%; height: 500px;"
+                        :tree-props="{ children: 'props' }" row-key="id"
+                        :default-sort="{ props: 'id', order: 'descending' }">
+                        <el-table-column prop="name" label="名称" width="200">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.name" placeholder="请输入名称" style="width: 100px">{{ scope.name
+                                    }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.name
+                                    }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{ scope.row.name
+                                }}</span>
+                                <span v-else>{{ scope.row.name }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="category" label="类型" width="250">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.category" placeholder="请输入类型" style="width: 100px">{{
+                                        scope.category }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.category
+                                    }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{
+                                    scope.row.category }}</span>
+                                <span v-else>{{ scope.row.category }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column label="操作" width="200">
+                            <template v-slot="scope">
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleEditSchema('entity', scope.row.id, true)">编辑</el-link> &nbsp;
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleDeleteSchema('entity', scope.row.id)">删除</el-link>
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="success"
+                                    @click="handleSaveSchema('entity', scope.row.id)">保存</el-link>&nbsp;
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="warning"
+                                    @click="handleCancelEditSchema('entity', scope.row.id)">取消</el-link>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+
+                </el-row>
+            </el-tab-pane>
+            <el-tab-pane label="关系类型" name="tab2">
+                <el-row>
+
+                    <el-button @click="handleAddSchema('relation')">增加</el-button>
+
+                    <el-table :data="currentData?.relations" style="width: 100%; height: 500px;"
+                        :tree-props="{ children: 'props' }" row-key="id"
+                        :default-sort="{ props: 'id', order: 'descending' }">
+                        <el-table-column prop="name" label="名称" width="200">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.name" placeholder="请输入名称" style="width: 100px">{{ scope.name
+                                    }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.name
+                                    }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{ scope.row.name
+                                }}</span>
+                                <span v-else>{{ scope.row.name }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column prop="category" label="类型" width="250">
+                            <template v-slot="scope">
+                                <el-input v-if="scope.row.editable && scope.row.action != 'delete'"
+                                    v-model="scope.row.category" placeholder="请输入类型" style="width: 100px">{{
+                                        scope.category }}</el-input>
+                                <span v-else-if="scope.row.action == 'delete'"
+                                    style="color: red; text-decoration: line-through;"><i>{{ scope.row.category
+                                    }}</i></span>
+                                <span v-else-if="scope.row.action == 'create'" style="color: green; ">{{
+                                    scope.row.category }}</span>
+                                <span v-else>{{ scope.row.category }}</span>
+                            </template>
+                        </el-table-column>
+                        <el-table-column label="操作" width="200">
+                            <template v-slot="scope">
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleEditSchema('relation', scope.row.id, true)">编辑</el-link> &nbsp;
+                                <el-link v-if="!scope.row.editable && scope.row.action != 'delete'" type="primary"
+                                    @click="handleDeleteSchema('relation', scope.row.id)">删除</el-link>
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="success"
+                                    @click="handleSaveSchema('relation', scope.row.id)">保存</el-link>&nbsp;
+                                <el-link v-if="scope.row.editable && scope.row.action != 'delete'" type="warning"
+                                    @click="handleCancelEditSchema('relation', scope.row.id)">取消</el-link>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+
+                </el-row>
+            </el-tab-pane>
+            <el-tab-pane label="调试" name="debug">
+                <el-row>
+                    <div style="width: 100%; height: 500px; overflow: auto;">{{ JSON.stringify(currentData) }}</div>
+                </el-row>
+
+            </el-tab-pane>
+        </el-tabs>
+        <template #footer>
+            <el-button type="primary" @click="handleUpdateStdSchema">更新图谱标准</el-button>
+            <el-button type="success" @click="handleApply">应用</el-button>
+            <el-button @click="dialogFormVisible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+<script setup lang="ts">
+import { ElMessage } from 'element-plus'
+import { ref, defineEmits } from 'vue'
+import { ElMessageBox, ElNotification } from 'element-plus'
+import { updateStdSchemaContent, reindexSchema } from '@/api/GraphApi'
+import { type SchemaData } from '@/types/dataset'
+
+
+const currentSchemaId = ref(0)
+const currentData = ref<SchemaData>()
+const currentDialogRelation = ref({})
+// const entitiesEditable = ref({})
+// const propsEditable = ref({})
+
+const dialogFormVisible = ref(false)
+const dialogCurrentTab = ref('tab1')
+const currentId = ref(0)
+
+const emits = defineEmits(['stdUpdated']);
+
+const handleAddSchema = (schemaType: string) => {
+    currentId.value++
+    if (currentData.value == null) {
+        return
+    }
+
+    if (schemaType == "entity") {
+        currentData.value.entities.unshift({ id: `${currentId.value}`, name: "", category: "", old_name: "", old_category: "", action: "create", editable: false, is_new: true, props: [] })
+        handleEditSchema(schemaType, `${currentId.value}`, true)
+    }
+    if (schemaType == "relation") {
+        currentData.value.relations.unshift({ id: `${currentId.value}`, name: "", category: "", old_name: "", old_category: "", action: "create", editable: false, is_new: true, props: [] })
+        handleEditSchema(schemaType, `${currentId.value}`, true)
+    }
+
+}
+const handleApplyDataChange = () => {
+    if (currentData.value == null) {
+        return
+    }
+
+    var targetData = currentData.value.entities
+    for (var i = 0; i < targetData.length;) {
+
+        if (targetData[i].action == "delete") {
+            targetData.splice(i, 1)
+            continue
+        }
+        i++
+
+    }
+    targetData = currentData.value.relations
+    for (var i = 0; i < targetData.length;) {
+
+        if (targetData[i].action == "delete") {
+            targetData.splice(i, 1)
+            continue
+        }
+        i++
+    }
+}
+const handleApply = (need_confirm: boolean = false) => {
+    if (currentData.value == null) {
+        return
+    }
+
+    if (need_confirm) {
+        ElMessageBox.confirm('接受数据变更会更新当前的对话框数据,但是不会对原始数据产生影响。若需保存变更,请点击‘更新图谱标准’按钮', '提示', {}).then(() => {
+            handleApplyDataChange()
+        }).catch(() => { })
+    } else {
+        handleApplyDataChange()
+    }
+
+}
+const handleSaveSchema = (schemaType: string, id: any) => {
+    // console.log("handleSaveEntity")
+    if (currentData.value == null) {
+        return
+    }
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].id == id) {
+
+            if (targetData[i].name == "") {
+                ElMessage({ type: 'warning', message: '名称不能为空' })
+                return
+            }
+            if (targetData[i].category == "") {
+                ElMessage({ type: 'warning', message: '类型不能为空' })
+                return
+            }
+            if (targetData[i].action == "create") {
+                targetData[i].action = "create"
+                targetData[i].props = []
+            } else {
+                targetData[i].action = "update"
+            }
+            targetData[i].is_new = false
+
+        } else {
+            for (var j = 0; j < targetData[i].props.length; j++) {
+                if (targetData[i].props[j].id == id) {
+                    if (targetData[i].props[j].name == "") {
+                        ElMessage({ type: 'warning', message: '名称不能为空' })
+                        return
+                    }
+                    if (targetData[i].props[j].category == "") {
+                        ElMessage({ type: 'warning', message: '类型不能为空' })
+                        return
+                    }
+                    if (targetData[i].props[j].action == "create") {
+                        targetData[i].props[j].action = "create"
+                    } else {
+                        targetData[i].props[j].action = "update"
+                    }
+                    targetData[i].is_new = false
+                }
+            }
+        }
+    }
+    handleEditSchema(schemaType, id, false)
+}
+const handleCancelEditSchema = (schemaType: string, id: any) => {
+    // console.log("handleCancelEditEntity")
+    if (currentData.value == null) {
+        return
+    }
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].id == id && targetData[i].is_new == true) {
+            targetData.splice(i, 1)
+            break
+        }
+        if (targetData[i].id == id) {
+            targetData[i].name = targetData[i].old_name
+            targetData[i].category = targetData[i].old_category
+            handleEditSchema(schemaType, id, false)
+            break
+        } else {
+            for (var j = 0; j < targetData[i].props.length; j++) {
+                if (targetData[i].props[j].id == id) {
+                    targetData[i].props[j].name = targetData[i].props[j].old_name
+                    targetData[i].props[j].category = targetData[i].props[j].old_category
+                    handleEditSchema(schemaType, id, false)
+                    break
+                }
+            }
+        }
+    }
+}
+const handleCancelEditSchemaAll = (schemaType: string) => {
+    if (currentData.value == null) {
+        return
+    }
+
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].editable == true) {
+            targetData[i].editable = false
+            if (targetData[i].is_new) {
+                targetData.splice(i, 1)
+            }
+            break
+        }
+
+        for (var j = 0; j < targetData[i].props.length; j++) {
+            if (targetData[i].props[j].editable == true) {
+                targetData[i].props[j].editable = false
+                if (targetData[i].props[j].is_new) {
+                    targetData[i].props.splice(j, 1)
+                }
+                break
+            }
+        }
+    }
+}
+const handleEditSchema = (schemaType: string, id: any, newValue: boolean) => {
+    // console.log("handleEditEntity")
+    if (currentData.value == null) {
+        return
+    }
+    handleCancelEditSchemaAll(schemaType)
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    for (var i = 0; i < targetData.length; i++) {
+        if (targetData[i].id == id) {
+            targetData[i].editable = newValue
+            if (newValue == true) {
+                targetData[i].old_name = targetData[i].name
+                targetData[i].old_category = targetData[i].category
+            }
+        } else {
+            for (var j = 0; j < targetData[i].props.length; j++) {
+                if (targetData[i].props[j].id == id) {
+                    targetData[i].props[j].editable = newValue
+                    if (newValue == true) {
+                        targetData[i].props[j].old_name = targetData[i].props[j].name
+                        targetData[i].props[j].old_category = targetData[i].props[j].category
+                    }
+                } else {
+                    targetData[i].props[j].editable = false
+                }
+            }
+
+            targetData[i].editable = false
+        }
+    }
+}
+const handleDeleteSchema = (schemaType: string, id: any, confirm: boolean = true) => {
+    if (currentData.value == null) return
+    var targetData = currentData.value.entities
+    if (schemaType == "relation") {
+        targetData = currentData.value.relations
+    }
+    if (confirm == false) {
+        for (var i = 0; i < targetData.length; i++) {
+            if (targetData[i].id == id) {
+                targetData[i].action = "delete"
+                break
+            }
+        }
+        return
+    }
+    ElMessageBox.confirm('是否删除该实体类型?', '提示', {}).then(() => {
+        for (var i = 0; i < targetData.length; i++) {
+            if (targetData[i].id == id) {
+                targetData[i].action = "delete"
+                for (var j = 0; j < targetData[i].props.length; j++) {
+                    targetData[i].props[j].action = "delete"
+                }
+                break
+            } else {
+                for (var j = 0; j < targetData[i].props.length; j++) {
+                    if (targetData[i].props[j].id == id) {
+                        targetData[i].props[j].action = "delete"
+                        break
+                    }
+                }
+            }
+        }
+        ElMessage({ type: 'success', message: '删除成功' })
+
+    })
+}
+
+const handleUpdateStdSchema = () => {
+    if (currentData.value == null) return
+
+
+    ElMessageBox.confirm('请注意,此操作将更新图谱标准数据,引用该标准的图谱数据不会被自动更新', '提示', {}).then(() => {
+        var content = JSON.stringify(currentData.value);
+        updateStdSchemaContent({ schema_id: currentSchemaId.value, content: content }).then(response => {
+            ElNotification({
+                title: 'Success',
+                message: '更新图谱标准成功',
+                type: 'success',
+            })
+            emits('stdUpdated')
+            // reindexSchema({schema_id: currentData.value.data.id}).then(response => {
+            //     ElNotification({
+            //     title: 'Success',
+            //     message: '更新图谱索引成功',
+            //     type:'success', 
+            //     }) 
+            // })
+        })
+
+        dialogFormVisible.value = false
+    })
+}
+// function handleCurrentEntityChange(data:any) {
+//     if (data == null) {
+//         return
+//     }
+//     currentDialogEntity.value = data
+// }
+
+// function generateIdForData(){
+//     var id = currentId.value
+//     for (var i=0;i<currentData.value.data.entities.length;i++) {
+//         currentData.value.data.entities[i].id=`${id}`
+//         currentData.value.data.entities[i].new_name= ""
+//         currentData.value.data.entities[i].new_category= ""
+//         currentData.value.data.entities[i].editable = false
+//         var subId =0
+//         for (var j=0;j<currentData.value.data.entities[i].props.length;j++) {
+//             currentData.value.data.entities[i].props[j].id=`${id}-${subId}`
+//             currentData.value.data.entities[i].props[j].new_name=""
+//             currentData.value.data.entities[i].props[j].new_category=""
+//             currentData.value.data.entities[i].props[j].editable=false
+//             subId++ 
+//         }
+//         id ++ 
+//     }
+//     for (var i=0;i<currentData.value.data.relations.length;i++) {
+//         currentData.value.data.relations[i].id=id 
+//         id++
+//     }
+//     currentId.value = id
+// }
+function handleCurrentRelationChange(data: any) {
+    if (data == null) {
+        return
+    }
+    currentDialogRelation.value = data
+}
+function showDialog(visible: boolean = true, schemaData: any, id: number) {
+    var data = schemaData
+    // console.log("schemaData", schemaData)
+    currentSchemaId.value = id
+    // console.log("showDialog", data)
+    dialogFormVisible.value = visible
+    currentData.value = { entities: [], relations: [] }
+
+
+    for (var i = 0; i < data.entities.length; i++) {
+        var entity = { ...data.entities[i], editable: false, is_new: false, action: "", props: [] }
+        if ("props" in data.entities[i] == false) {
+            data.entities[i].props = []
+        }
+        for (var j = 0; j < data.entities[i].props.length; j++) {
+            entity.props.push({ ...data.entities[i].props[j], editable: false, is_new: false, action: "", })
+        }
+        currentData.value.entities.push(entity)
+    }
+    for (var i = 0; i < data.relations.length; i++) {
+        var entity = { ...data.relations[i], editable: false, is_new: false, action: "", props: [] }
+        if ("props" in data.relations[i] == false) {
+            data.relations[i].props = []
+        }
+        for (var j = 0; j < data.relations[i].props.length; j++) {
+            entity.props.push({ ...data.relations[i].props[j], editable: false, is_new: false, action: "", })
+        }
+        currentData.value.relations.push(entity)
+    }
+    //generateIdForData()
+
+}
+defineExpose({ showDialog })
+</script>

+ 167 - 53
src/dialogs/OCRDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog :title="title" v-model="dialogFormVisible" @closed="handleClosed">
+  <el-dialog :title="title" v-model="dialogFormVisible" width="800" @closed="handleClosed">
     <el-form :model="form" ref="formRef">
       <el-form-item label="任务名称" prop="name" :label-width="formLabelWidth" required>
         <el-input v-model="form.name" autocomplete="off"></el-input>
@@ -11,9 +11,9 @@
         </el-select>
       </el-form-item>
       <el-form-item label="文件" :label-width="formLabelWidth">
-        <el-upload ref="upload" :action="'/api/file/upload/pdf/' + form.job_id" :mutiple="false" :limit="100"
+        <el-upload ref="upload" :action="'/api/file/upload/pdf/' + form.job_id" :multiple="true" :limit="10000"
           :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" v-model:file-list="fileList"
-          :on-exceed="handleExceed" :auto-upload="false">
+          :on-exceed="handleExceed" :auto-upload="false" style="width: 100%;">
           <el-button @click.stop="knowledgeBase.visible = true" size="small" type="primary">知识库导入</el-button>
           <el-button slot="trigger" size="small" type="primary">上传文件</el-button>
 
@@ -21,14 +21,16 @@
         </el-upload>
       </el-form-item>
     </el-form>
-    <div slot="footer">
+    <footer slot="footer">
       <el-button type="primary" @click="handleConfirm">确 定</el-button>
       <el-button @click="dialogFormVisible = false">取 消</el-button>
-    </div>
-    <div class="knowledge-base" v-show="knowledgeBase.visible">
-      <div class="topbar"><span @click="knowledgeBase.visible = false" class="close-knowledge-base"><el-icon>
+    </footer>
+    <div class="knowledge-base" v-show="knowledgeBase.visible" v-loading="loading" element-loading-text="导入中...">
+      <div class="topbar">
+        <span @click="knowledgeBase.visible = false" class="close-knowledge-base"><el-icon>
             <Close />
-          </el-icon></span>
+          </el-icon>
+        </span>
       </div>
       <div class="knowledge-base-content">
         <el-scrollbar class="knowledge-base-list">
@@ -37,7 +39,8 @@
             <span class="number">文件数量</span>
           </div>
           <div :class="knowledgeBase.activeId === item.id ? 'knowledge-base-item active' : 'knowledge-base-item'"
-            v-for="item in knowledgeBase.data" :key="item.id" @click="handleKnowledgeBaseClick(toRaw(item))">
+            v-for="(item, index) in knowledgeBase.data" :key="item.id"
+            @click="handleKnowledgeBaseClick(toRaw(item), index)">
             <span class="name">
               <span class="icon">
                 <span class="document-icon"></span>
@@ -53,16 +56,25 @@
         <div class="knowledge-base-detail">
           <div class="management-content">
             <div class="management-content-top">
-              <span class="search">
-                <el-input v-model="knowledgeBase.querySearch" size="large" placeholder="搜索"
-                  @keydown.enter="debounceGetKBfileList" :prefix-icon="Search" />
-              </span>
-              <span class="add-file" @click="handleSelectedImport()">
-                <span class="text">批量导入</span>
-              </span>
+              <div class="left">
+                <span class="search">
+                  <el-input v-model="knowledgeBase.querySearch" size="large" placeholder="搜索"
+                    @keydown.enter="debounceGetKBfileList" :prefix-icon="Search" />
+                </span>
+              </div>
+              <div class="right">
+                <span class="add-file" @click="handleAllImport">
+                  <span class="text">全部导入</span>
+                </span>
+                <span class="add-file" :disabled="knowledgeBase.selectedNum > 0 ? 'false' : 'true'"
+                  @click="handleSelectedImport">
+                  <span class="text">批量导入</span>
+                </span>
+              </div>
+
             </div>
             <el-scrollbar class="management-content-middle">
-              <el-table :data="knowledgeBase.filesList" ref="KBTableRef">
+              <el-table :data="knowledgeBase.filesList" ref="KBTableRef" @selection-change="handleSelectionChange">
                 <el-table-column :selectable="handleSelectable" type="selection" width="30" />
                 <el-table-column label="#" prop="index" width="50" />
                 <el-table-column prop="file_name" min-width="150" label="标题">
@@ -72,12 +84,20 @@
                         <i :class="`${row.file_type}-icon`"></i>
                       </span>
                       <span class="text-area">
-                        {{ row.file_name }}
+                        <el-text style="vertical-align: bottom;width: 100%;" line-clamp="1">
+                          {{ row.file_name }}
+                        </el-text>
                       </span>
                     </div>
                   </template>
                 </el-table-column>
-                <el-table-column prop="knowledge_type" label="知识类型" />
+                <el-table-column prop="knowledge_type" label="知识类型">
+                  <template #default="{ row }">
+                    <el-text line-clamp="1" style="vertical-align: bottom;">
+                      {{ row.knowledge_type }}
+                    </el-text>
+                  </template>
+                </el-table-column>
                 <el-table-column prop="version" label="版本" />
                 <el-table-column prop="author" label="作者(主编)" />
                 <el-table-column prop="year" label="年份" />
@@ -99,7 +119,7 @@
                   <template #default="{ row }">
                     <div class="operation">
                       <el-button link type="primary" :disabled="!row.status"
-                        @click="handleImportFiles([toRaw(row)])">导入</el-button>
+                        @click="handleImportFiles([{ ...row, file_url: `/open-platform/files/${row.id}/download` }])">导入</el-button>
                     </div>
                   </template>
                 </el-table-column>
@@ -112,12 +132,9 @@
                   @size-change="handleSizeChange" @current-change="handleCurrentChange" />
               </div>
             </el-scrollbar>
-
           </div>
         </div>
-
       </div>
-
     </div>
   </el-dialog>
 
@@ -138,9 +155,10 @@ const formRef = ref()
 const dialogFormVisible = ref(false)
 const formLabelWidth = ref('120px')
 
+let loading = ref<boolean>(false)
 const paginationData = ref<any>({
   currentPage: 1,
-  pageSizes: [10, 50, 100, 200],
+  pageSizes: [10, 50, 100, 1000, 10000],
   disabled: false,
   total: 0,
   defaultPageSize: 10,
@@ -170,19 +188,25 @@ type kbData = {
 type knowledgeBaseType = {
   visible: boolean,
   activeId: number | string,
+  activeIndex: number,
   querySearch: string,
   data: kbData[],
   filesList: any[]
+  selectedNum: number
 }
 let knowledgeBase = ref<knowledgeBaseType>({
   visible: false,
   activeId: 0,
+  activeIndex: -1,
   querySearch: "",
   data: [],
-  filesList: []
+  filesList: [],
+  selectedNum: 0,
+
 })
-function handleKnowledgeBaseClick(data: kbData) {
+function handleKnowledgeBaseClick(data: kbData, index: number) {
   knowledgeBase.value.activeId = data.id
+  knowledgeBase.value.activeIndex = index
   paginationData.value.currentPage = 1
 }
 function handleGetKBfileList() {
@@ -197,8 +221,8 @@ function handleGetKBfileList() {
       knowledgeBase.value.filesList = data.list
       for (let i = 0; i < knowledgeBase.value.filesList.length; i++) {
         knowledgeBase.value.filesList[i].index = (paginationData.value.currentPage - 1) * paginationData.value.currentPageSize + i + 1
-        knowledgeBase.value.filesList[i].isValid = true
-        checkLinkValidity(i, knowledgeBase.value.filesList[i].minio_url)
+        // knowledgeBase.value.filesList[i].isValid = true
+        // checkLinkValidity(i, knowledgeBase.value.filesList[i].minio_url)
       }
       paginationData.value.total = data.total
     }
@@ -243,6 +267,10 @@ const showDialog = (visible: boolean = true) => {
   dialogFormVisible.value = visible
 }
 
+function handleSelectionChange(newSelection: any[]) {
+  knowledgeBase.value.selectedNum = newSelection.length
+}
+
 // 清除上传文件列表
 const clearFileList = () => {
   fileList.value = []
@@ -262,6 +290,30 @@ function handleGetKnowledgeBase() {
     console.log(e)
   })
 }
+
+async function handleAllImport() {
+  try {
+    const res = await getKnowledgeBaseFilesList({
+      file_name: "",
+      pageSize: knowledgeBase.value.data[knowledgeBase.value.activeIndex].file_count,
+      pageNo: 1,
+      kbId: knowledgeBase.value.activeId
+    })
+    const { data, code } = res
+    if (code === 200) {
+      let filterData = data.list.filter((it: any) => it.status)
+      filterData = filterData.map((it: any) => {
+        it.file_url = `/open-platform/files/${it.id}/download`
+        return it
+      })
+      handleImportFiles(filterData)
+    }
+
+  } catch (e) {
+    console.log(e)
+  }
+}
+
 function fetchFile(fileName: string, fileUrl: string) {
   axios.get(fileUrl, { responseType: 'blob' })
     .then(async response => {
@@ -310,9 +362,14 @@ function handleSelectable(row: any, index: number) {
   return row.status
 }
 function handleSelectedImport() {
-  const SelectionRows = KBTableRef.value.getSelectionRows()
-  handleImportFiles(toRaw(SelectionRows))
-  knowledgeBase.value.visible = false
+  const selectionRows = KBTableRef.value.getSelectionRows().map((it: any) => {
+    let copyIt: any = JSON.parse(JSON.stringify(it))
+    copyIt.file_url = `/open-platform/files/${copyIt.id}/download`
+    return copyIt
+  })
+  if (selectionRows.length == 0) return;
+  handleImportFiles(selectionRows)
+
   // console.log(SelectionRows)
 }
 // 使用原生方法计算文件的hash值,该方法兼容性较差
@@ -326,7 +383,7 @@ async function calculateFileHashForNative(file: File) {
 }
 
 // 使用crypto-js计算文件的hash值 ,该方法易发生卡顿
-async function calculateFileHashForCryptoJS(file: File) {
+async function calculateFileHashForCryptoJS(file: File): Promise<string> {
   return new Promise((resolve, reject) => {
     try {
       const reader = new FileReader();
@@ -336,13 +393,12 @@ async function calculateFileHashForCryptoJS(file: File) {
         // 将 ArrayBuffer 转换为 CryptoJS 可以处理的 WordArray
         const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
         // 计算文件的 SHA-256 哈希值并转为 Base64 编码字符串
-        const hashBase64 = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Base64);
+        // const hashBase64 = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Base64);
         // 计算文件的 SHA-256 哈希值并转为 16 进制字符串
         const hashHex = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.hex);
         // console.log('calculateFileHashForCryptoJS:', hashHex);
         resolve(hashHex)
       };
-
       reader.onerror = () => {
         reject('')
       }
@@ -418,8 +474,11 @@ const handleExceed: UploadProps['onExceed'] = (files) => {
   upload.value!.handleStart(file)
 }
 
-const handleRemove = (file: any, fileList: any) => {
-  console.log(file, fileList)
+const handleRemove = async (file: any, fileList: any) => {
+  const hashHex: string = await calculateFileHashForCryptoJS(file.raw)
+  const hashHexIndex = importedFileHashes.value.indexOf(hashHex)
+  importedFileHashes.value.splice(hashHexIndex, 1)
+  // console.log(file, fileList)
 }
 
 async function calculateFileHash(file: File) {
@@ -444,11 +503,12 @@ async function calculateFileHash(file: File) {
   });
 }
 
-function handleImportFiles(filesList: any[]) {
-  filesList.forEach(async (fileInfo) => {
-    const { file_name, minio_url } = fileInfo;
+async function handleImportFiles(filesList: any[]) {
+  loading.value = true
+  await Promise.allSettled(filesList.map(async (fileInfo) => {
+    const { file_name, minio_url, file_url } = fileInfo;
     try {
-      const response = await axios.get(minio_url, { responseType: 'blob' });
+      const response = await axios.get(file_url, { responseType: 'blob' });
       const rawFile = new File([response.data], file_name, { type: response.data.type });
       const fileHash = await calculateFileHash(rawFile);
 
@@ -477,7 +537,9 @@ function handleImportFiles(filesList: any[]) {
       });
       console.log(error);
     }
-  });
+  }))
+  loading.value = false
+  knowledgeBase.value.visible = false
 }
 
 const handleSuccess = (response: any, file: any, fileList: any) => {
@@ -550,6 +612,47 @@ onMounted(() => {
 </script>
 
 <style lang="less" scoped>
+footer {
+  text-align: right;
+}
+
+:deep(.el-upload-list) {
+  max-height: 300px;
+  // width: auto;
+  overflow: auto;
+
+  /*定义滚动条高宽及背景
+ 高宽分别对应横竖滚动条的尺寸*/
+  &::-webkit-scrollbar {
+    width: 10px;
+    height: 10px;
+    background-color: #F5F5F5;
+    display: none;
+  }
+
+  /*定义滚动条轨道
+ 内阴影+圆角*/
+  &::-webkit-scrollbar-track {
+    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+    border-radius: 10px;
+    background-color: #F5F5F5;
+  }
+
+  /*定义滑块
+ 内阴影+圆角*/
+  &::-webkit-scrollbar-thumb {
+    border-radius: 10px;
+    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+    background-color: #DDDEE0;
+  }
+
+  &:hover {
+    &::-webkit-scrollbar {
+      display: block;
+    }
+  }
+}
+
 .knowledge-base {
   position: fixed;
   top: 50%;
@@ -681,29 +784,32 @@ onMounted(() => {
     .management-content-top {
       clear: both;
       flex: 0 0 auto;
-      position: relative;
+      display: flex;
+
+      .right {
+        margin-left: auto;
+      }
+
+      .left {}
 
       .add-file {
-        position: absolute;
-        right: 10px;
-        bottom: 0px;
-        transform: translateY(-25%);
         color: white;
-        // width: 100px;
         background-color: #169BD5;
         margin-left: auto;
         padding: 10px 25px;
         border-radius: 3px;
         display: inline-block;
         cursor: pointer;
-        // float: right;
-        // display: flex;
+        margin-right: 10px;
 
-        // .el-icon,
         .text {
           // vertical-align: middle;
           // margin-left: 10px;
         }
+
+        &[disabled='true'] {
+          opacity: 0.5;
+        }
       }
     }
 
@@ -737,9 +843,17 @@ onMounted(() => {
     }
 
     .list-name {
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      display: inline;
+      display: flex;
+      flex-wrap: nowrap;
+      align-items: center;
+
+      .icon-area {
+        flex: 0 0 auto;
+      }
+
+      .text-area {
+        flex: 1 1 auto;
+      }
     }
 
     .circle {

+ 4 - 6
src/main.ts

@@ -1,23 +1,21 @@
 import '@/assets/css/main.css'
 import "@/assets/css/common.less"
 import 'animate.css';
-
-import { createApp } from 'vue'
-import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
+import { createApp } from 'vue'
+// import ElementPlus from 'element-plus'
 import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import { createPinia } from 'pinia'
 import App from './App.vue'
 import router from '@/router/index.ts'
 import http from "@/utils/http.js"
 
-
 const app = createApp(App)
 for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
   app.component(key, component)
 }
+app.config.globalProperties.$http = http
 app.use(createPinia())
 app.use(router)
-app.use(ElementPlus)
-app.config.globalProperties.$http = http
+// app.use(ElementPlus)
 app.mount('#app')

+ 99 - 19
src/router/index.ts

@@ -1,6 +1,6 @@
 import { createRouter, createWebHistory, isNavigationFailure } from "vue-router";
 import Login from "@/views/Login/Login.vue";
-import AppCopy from "@/views/AppCopy.vue";
+import Framework from "@/views/Framework.vue";
 import HomeView from "@/views/HomeView.vue";
 import Layout from "@/views/KMPlatform/Layout.vue";
 import Home from "@/views/KMPlatform/Home/Home.vue";
@@ -16,33 +16,50 @@ import PermissionManage from "@/views/KMPlatform/Permission/permission.vue";
 import RoleManage from "@/views/KMPlatform/Permission/RoleManage.vue";
 import EntityRelationshipTypeManagement from '@/views/KMPlatform/KGBuilder/KRTM/EntityRelationshipTypeManagement.vue'
 import { getSessionVar, deleteSessionVar } from "@/utils/session";
+import page404 from "@/views/404/404.vue"
+import { isNotLogin } from "@/utils/app"
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
   routes: [
     {
       path: "/",
-      redirect: "/kmplatform/home",
+      redirect: "/kmplatform",
+      name: "top",
+      meta: {
+        auth: false,
+      }
+    },
+    {
+      path: "/404",
+      name: 'page404',
+      component: page404,
+      meta: {
+        auth: false,
+      }
     },
     {
       path: "/login",
       name: "login",
       component: Login,
       meta: {
-
+        auth: false,
       }
     },
     {
       path: "/kmplatform",
       name: "kmplatform",
       component: Layout,
-      redirect: "/kmplatform/home",
+      meta: {
+        auth: false,
+      },
       children: [
         {
           path: "home",
           name: "kmplatform-home",
           meta: {
             title: "主页",
+            auth: true,
           },
           component: Home,
           children: [],
@@ -53,6 +70,7 @@ const router = createRouter({
           meta: {
             title: "知识库",
             keepAlive: true,
+            auth: true,
           },
           component: KnowledgeBase,
           redirect: "/kmplatform/knowledgebase/kbm",
@@ -63,6 +81,7 @@ const router = createRouter({
               meta: {
                 title: "知识库",
                 keepAlive: true,
+                auth: true,
               },
               component: KnowledgeBaseManagement,
             },
@@ -72,6 +91,7 @@ const router = createRouter({
               meta: {
                 title: "知识库",
                 keepAlive: false,
+                auth: true,
               },
               component: KnowledgeManagement,
             },
@@ -82,14 +102,18 @@ const router = createRouter({
           name: "kgbuilder",
           meta: {
             title: "知识图谱构建",
+            auth: true,
           },
           redirect: "/kmplatform/kgbuilder/home",
-          component: AppCopy,
+          component: Framework,
           children: [
             {
               path: "home",
               name: "home",
               component: HomeView,
+              meta: {
+                auth: true,
+              },
             },
             {
               path: "workspace",
@@ -100,21 +124,49 @@ const router = createRouter({
                   path: "queue/:id",
                   name: "queue",
                   component: () => import("@/views/QueueView.vue"),
+                  meta: {
+                    auth: true,
+                  },
+                },
+                {
+                  path: 'job/:id',
+                  name: 'job-view',
+                  component: () => import('@/views/JobView.vue'),
+                  meta: {
+                    auth: true,
+                  },
                 },
                 {
                   path: "worker",
                   name: "worker",
                   component: () => import("@/views/WorkerView.vue"),
+                  meta: {
+                    auth: true,
+                  },
                 },
                 {
                   path: "graph",
                   name: "graph",
                   component: () => import("@/views/GraphView.vue"),
+                  meta: {
+                    auth: true,
+                  },
+                },
+                {
+                  path: 'graph-std-schemas',
+                  name: 'graph-std-schemas',
+                  component: () => import('@/views/GraphStdSchemas.vue'),
+                  meta: {
+                    auth: true,
+                  },
                 },
                 {
                   path: "graph-mgr/:id",
                   name: "graph-mgr",
                   component: () => import("@/views/GraphManagement.vue"),
+                  meta: {
+                    auth: true,
+                  },
                 },
               ],
             },
@@ -122,6 +174,17 @@ const router = createRouter({
               path: "about",
               name: "about",
               component: () => import("@/views/AboutView.vue"),
+              meta: {
+                auth: true,
+              },
+            },
+            {
+              path: 'config',
+              name: 'config',
+              component: () => import('@/views/ConfigurationView.vue'),
+              meta: {
+                auth: true,
+              },
             },
           ]
         },
@@ -131,14 +194,19 @@ const router = createRouter({
           redirect: "/kmplatform/openplatform/queue/0",
           meta: {
             title: "开放平台",
+            auth: false,
+
           },
           component: OpenPlatform,
           children: [
-             {
-                  path: "queue/:id",
-                  name: "platformText",
-                  component: () => import("@/views/KMPlatform/OpenPlatform/platformText.vue"),
-                },
+            {
+              path: "queue/:id",
+              name: "platformText",
+              component: () => import("@/views/KMPlatform/OpenPlatform/platformText.vue"),
+              meta: {
+                auth: false,
+              },
+            },
           ],
         },
         {
@@ -146,6 +214,7 @@ const router = createRouter({
           name: "kgpermission",
           meta: {
             title: "系统权限",
+            auth: true,
           },
           redirect: "/kmplatform/kgpermission/AccountManage",
           component: PermissionManage,
@@ -155,6 +224,7 @@ const router = createRouter({
               name: "kgb-RoleManage",
               meta: {
                 title: "角色管理",
+                auth: true,
               },
               component: RoleManage,
             },
@@ -163,6 +233,7 @@ const router = createRouter({
               name: "kgb-AccountManage",
               meta: {
                 title: "账号管理",
+                auth: true,
               },
               component: AccountManage,
             },
@@ -171,6 +242,7 @@ const router = createRouter({
               name: "kgb-Organizational",
               meta: {
                 title: "账号管理",
+                auth: true,
               },
               component: () => import("@/views/KMPlatform/Permission/Organizational.vue"),
             },
@@ -181,24 +253,32 @@ const router = createRouter({
   ],
 });
 
+
+function hasRoute(name: string) {
+  const routes = router.getRoutes()
+  return routes.some((it) => it.name === name)
+}
+
 router.beforeEach((to, from, next) => {
-  if (
-    getSessionVar("session_id") == null &&
-    getSessionVar("username") == null
-  ) {
-    if (to.name !== "login") {
-      next({ name: "login" }); // 重定向到登录页面
+  if (hasRoute(to.name as string)) {
+    if (isNotLogin()) {
+      if (to.meta.auth && to.name !== 'login') {
+        next({ name: 'login' });
+      } else {
+        next()
+      }
     } else {
-      next(); // 确认转移
+      next()
     }
   } else {
-    next(); // 确认转移
+    next('/404')
   }
 });
 
 router.afterEach((to, from, failure) => {
   if (isNavigationFailure(failure)) {
-    console.log('failed navigation', failure)
+    // if (to.fullPath === from.fullPath) return;
+    // console.log('failed navigation', failure)
   } else {
     to.meta.title ? (document.title = to.meta.title as string) : document.title = '知识图谱自动化构建平台'
   }

+ 17 - 0
src/stores/knowledgeBase.js

@@ -0,0 +1,17 @@
+// 该文件存储知识库相关的公共数据
+
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+import http from '@/utils/http'
+
+export const useKnowledgeBaseStore = defineStore('knowledgeBase', () => {
+  let knowledgeType = ref([
+    { id: 1, value: "中华医学会诊疗指南", label: "中华医学会诊疗指南" },
+    { id: 2, value: "规培十四五教材", label: "规培十四五教材" },
+    { id: 3, value: "临床路径", label: "临床路径" },
+  ])
+  function getKnowledgeType() {
+
+  }
+  return { knowledgeType }
+})

+ 72 - 33
src/stores/menu.js

@@ -1,46 +1,85 @@
-import { ref } from 'vue'
+import { ref, computed } from 'vue'
 import { defineStore } from 'pinia'
 import {
   deleteSessionVar,
   getSessionVar,
   saveSessionVar,
 } from "@/utils/session";
+
 export const useMenuStore = defineStore('menu', () => {
-  let routeList = ref(getSessionVar("routeList") ? JSON.parse(getSessionVar("routeList")) : [])
-  //   const routeList = ref([{
-  //     path: '/kmplatform/home',
-  //     name: 'kmplatform-home',
-  //     title: "主页",
-  //     children: []
-  //   },
-  //   {
-  //     path: '/kmplatform/knowledgebase',
-  //     name: 'knowledgebase',
-  //     title: "知识库",
-  //     children: []
-  //   },
-  //   {
-  //     path: '/kmplatform/kgbuilder',
-  //     name: 'kgbuilder',
-  //     title: "知识图谱构建",
-  //     children: []
-  //   },
-  //   {
-  //     path: '/kmplatform/openplatform',
-  //     name: 'openplatform',
-  //     title: "开放平台",
-  //     children: []
-  //   },
-  //   {
-  //     path: '/kmplatform/kgpermission',
-  //     name: 'kgpermission',
-  //     title: "系统权限",
-  //     children: []
-  //   }
+  function initRouteList() {
+    return getSessionVar("routeList") ?
+      JSON.parse(getSessionVar("routeList")) :
+      [{
+        path: '/kmplatform/openplatform',
+        name: 'openplatform',
+        title: "开放平台",
+        children: []
+      }]
+  }
+  let routeList = ref(initRouteList())
+
+  // const routeList = ref([{
+  //   path: '/kmplatform/home',
+  //   name: 'kmplatform-home',
+  //   title: "主页",
+  //   children: []
+  // },
+  // {
+  //   path: '/kmplatform/knowledgebase',
+  //   name: 'knowledgebase',
+  //   title: "知识库",
+  //   children: []
+  // },
+  // {
+  //   path: '/kmplatform/kgbuilder',
+  //   name: 'kgbuilder',
+  //   title: "知识图谱构建",
+  //   children: []
+  // },
+  // {
+  //   path: '/kmplatform/openplatform',
+  //   name: 'openplatform',
+  //   title: "开放平台",
+  //   children: []
+  // },
+  // {
+  //   path: '/kmplatform/kgpermission',
+  //   name: 'kgpermission',
+  //   title: "系统权限",
+  //   children: []
+  // }
   // ])
+  const operationPermissions = computed(() => {
+    let permissions = {}
+    function traverseTree(node) {
+      // 如果当前节点有子节点,递归遍历每个子节点
+      if (node.children && node.children.length > 0) {
+        node.children.forEach(child => {
+          permissions[child.menu_route] = true
+          // console.log(child.name); // 处理子节点
+          traverseTree(child); // 递归遍历子节点的子节点
+        });
+      }
+    }
+
+    routeList.value.forEach(node => {
+      traverseTree(node);
+    });
+    return permissions
+  })
 
   const updateRouteList = (newRoutes) => {
     routeList.value = newRoutes; // 更新路由列表
   };
-  return { routeList, updateRouteList }
+  function isOP(code) {
+    return operationPermissions.value[code] ? true : false;
+  }
+  return { routeList, updateRouteList, operationPermissions, isOP }
+}, {
+  persist: {
+    enable: true,
+  }
 })
+
+export default useMenuStore

+ 63 - 0
src/types/dataset.ts

@@ -0,0 +1,63 @@
+import type { ListCache } from "element-plus";
+
+type SchemaDataItem = {
+  id: string;
+  name: string;
+  category: string; 
+  old_name: string;
+  old_category: string;
+  action: string;
+  editable: boolean;
+  is_new: boolean;
+  props: SchemaDataItem[];
+}
+type SchemaData = {
+  entities: SchemaDataItem[];
+  relations: SchemaDataItem[];
+
+}
+
+type ViewSchemaData = {
+  id: number
+  name: string
+  category: string
+  content: string
+  data: SchemaData
+}
+
+type ViewCategoryData = {
+ name: string
+ std_name:string
+ type:string 
+}
+
+type NodesData = {
+  id: number
+  name: string
+  category: string
+}
+
+type EdgeData = {
+  id: number
+  name: string
+  category: string
+  src_id: number
+  dest_id:number
+}
+
+type ContradictionData = {
+  count: number
+  edges: EdgeData[]
+  src_id: number
+  src_name: string
+  src_category: string
+  dest_id: number
+  dest_name: string
+  dest_category: string
+}
+export {type SchemaData, 
+  type ViewSchemaData, 
+  type ViewCategoryData,
+  type NodesData,
+  type EdgeData,
+  type ContradictionData,}

+ 58 - 55
src/utils/app.ts

@@ -1,66 +1,69 @@
-
+import { getSessionVar } from '@/utils/session'
 /**
  * 重置一个参数对象
  * @param args
  * @param def
  */
-export function resetArgs<T>(args:T, def:Partial<T> = {}):T {
+export function resetArgs<T>(args: T, def: Partial<T> = {}): T {
+
+  let val: { [k: string]: any } = {}
 
-    let val: { [k:string]:any }  = {}
-  
-    for (let key in args) {
-      if (def.hasOwnProperty(key)) {
-        val[key]  = def[key]
-      } else {
-        if (Array.isArray(args[key])) val[key] = [];
-        if ('string' == typeof args[key]) val[key] = '';
-        if ('number' == typeof args[key]) val[key] = null;
-        if ('boolean' == typeof args[key]) val[key] = false;
-      }
+  for (let key in args) {
+    if (def.hasOwnProperty(key)) {
+      val[key] = def[key]
+    } else {
+      if (Array.isArray(args[key])) val[key] = [];
+      if ('string' == typeof args[key]) val[key] = '';
+      if ('number' == typeof args[key]) val[key] = null;
+      if ('boolean' == typeof args[key]) val[key] = false;
     }
-    return val as T
   }
-  
-  /**
-   * 下载或者保存一个Blob
-   * @param blob
-   * @param fileName
-   * @param isOpen
-   * 接口返回数据流时,如果是pdf可以设置isOpen直接新窗口打开
-   * export function exportReport(params: { fileCode:string }) {
-   *   return request({
-   *     responseType:"blob",
-   *     closeResponseInterceptors:true,
-   *     url: '/customer-service/open/api/report/getReport',
-   *     method: 'get',
-   *     params
-   *   })
-   * }
-   */
-  export function saveBlob(blob:Blob,fileName:string,isOpen = false):void{
-  
-    let url = window.URL.createObjectURL(blob);
-    if(isOpen){
-      window.open(url)
-    }else {
-      let a = document.createElement("a");
-      document.body.appendChild(a);
-      a.setAttribute("display","none")
-      a.href = url;
-      a.download = fileName;
-      a.click();
-      a.remove();
-      window.URL.revokeObjectURL(url);
-    }
+  return val as T
+}
+
+/**
+ * 下载或者保存一个Blob
+ * @param blob
+ * @param fileName
+ * @param isOpen
+ * 接口返回数据流时,如果是pdf可以设置isOpen直接新窗口打开
+ * export function exportReport(params: { fileCode:string }) {
+ *   return request({
+ *     responseType:"blob",
+ *     closeResponseInterceptors:true,
+ *     url: '/customer-service/open/api/report/getReport',
+ *     method: 'get',
+ *     params
+ *   })
+ * }
+ */
+export function saveBlob(blob: Blob, fileName: string, isOpen = false): void {
+
+  let url = window.URL.createObjectURL(blob);
+  if (isOpen) {
+    window.open(url)
+  } else {
+    let a = document.createElement("a");
+    document.body.appendChild(a);
+    a.setAttribute("display", "none")
+    a.href = url;
+    a.download = fileName;
+    a.click();
+    a.remove();
+    window.URL.revokeObjectURL(url);
   }
-  
-  
-  export function toFormData (data:any): FormData{
-    const formData = new FormData()
-    for (const key in data) {
-      formData.append(key, data[key])
-    }
-    return formData
+}
+
+export function isNotLogin(): boolean {
+  return getSessionVar("session_id") == null &&
+    getSessionVar("username") == null
+}
+export function toFormData(data: any): FormData {
+  const formData = new FormData()
+  for (const key in data) {
+    formData.append(key, data[key])
   }
+  return formData
+}
 
-  export default {resetArgs,saveBlob,toFormData}
+export default { resetArgs, saveBlob, toFormData, isNotLogin }

+ 13 - 8
src/utils/http.js

@@ -1,7 +1,8 @@
 import axios from 'axios'
 import { ElMessage } from 'element-plus'
-import { getSessionVar, deleteSessionVar } from "@/utils/session";
-
+import { getSessionVar, clearSessionVar } from "@/utils/session";
+import { useRouter } from "vue-router"
+const router = useRouter();
 const NETWORK_ERROR = '网络错误,请稍后重试!'
 // 创建 axios 实例
 const http = axios.create({
@@ -35,8 +36,7 @@ http.interceptors.request.use(
 // 响应拦截器
 http.interceptors.response.use(
   (response) => {
-    // 对响应数据做些什么
-    // 可以在此处理响应数据
+    // 对响应数据做些什么,可以在此处理响应数据
     const { data, code, message } = response.data
     if (code === 200) {
       return data || response.data
@@ -50,11 +50,17 @@ http.interceptors.response.use(
     // return response.data;  // 这里直接返回 response.data,方便后续使用
   },
   (error) => {
-    // 响应错误时做些什么
-    // 根据错误类型做相应处理
+    // 处理未登录
+    if (error.response.status == 401) {
+      clearSessionVar()
+      router.push("/login")
+      return null
+    }
+
+    // 响应错误时做些什么,根据错误类型做相应处理
+
     if (error.response) {
       // 请求已发出,服务器响应了状态码,但状态码超出了 2xx 的范围
-
       ElMessage({
         message: NETWORK_ERROR,
         type: "warning",
@@ -63,7 +69,6 @@ http.interceptors.response.use(
 
     } else if (error.request) {
       // 请求已经发出,但没有收到响应
-
       ElMessage({
         message: "没有收到响应",
         type: "warning"

+ 14 - 8
src/utils/misc.ts

@@ -1,13 +1,19 @@
 
 import { format } from 'date-fns';
-export function formatDate (dateString:string) :string {
-    if (!dateString) {
-      return ''; // 或者你可以返回一个默认值,如 'N/A' 
-    }
-    return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); // 可以根据需要调整格式字符串
-  };
+export function formatDate(dateString: string): string {
+  if (!dateString) {
+    return ''; // 或者你可以返回一个默认值,如 'N/A' 
+  }
+  return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); // 可以根据需要调整格式字符串
+};
 
+export function formatOverflowText(text: string, len: number): string {
+  if (text.length <= len) {
+    return text;
+  }
+  return text.slice(0, len - 1) + "...";
+}
 
-export function getRequestId():string {
-    return Math.floor(Date.now()).toString(); // 除以 1000 以获取秒级时间戳 
+export function getRequestId(): string {
+  return Math.floor(Date.now()).toString(); // 除以 1000 以获取秒级时间戳 
 }

+ 69 - 63
src/utils/request.ts

@@ -1,13 +1,13 @@
 import axios from "axios";
 import { toFormData } from '@/utils/app'
-import type { AxiosInstance, AxiosRequestConfig, AxiosResponse,InternalAxiosRequestConfig } from "axios";
-import { getSessionVar,deleteSessionVar } from "@/utils/session";
+import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
+import { getSessionVar, deleteSessionVar } from "@/utils/session";
 import { useRouter } from 'vue-router'
-export const baseURL:string = import.meta.env.VITE_BASE_URL
 
+export const baseURL: string = import.meta.env.VITE_BASE_URL
 export const timeout = 80000
 
-export const statusDesc:{[value: number]:string} = {
+export const statusDesc: { [value: number]: string } = {
   0: "SUCCESS",
   400: "请求错误(400)",
   401: "未授权,请重新登录(401)",
@@ -31,85 +31,91 @@ export type ResponseResult<T> = {
 };
 
 declare module 'axios' {
-  export interface AxiosRequestConfig{
-    closeLoading?:boolean,//默认所有请求Loading,可关闭
-    token?:string,//默认获取本地token,可针对某个请求写死或置空
-    isFormRequest?:boolean,//将请求自动转换为表单请求
-    closeInstance?:boolean
+  export interface AxiosRequestConfig {
+    closeLoading?: boolean,//默认所有请求Loading,可关闭
+    token?: string,//默认获取本地token,可针对某个请求写死或置空
+    isFormRequest?: boolean,//将请求自动转换为表单请求
+    closeInstance?: boolean
   }
 }
 
 export type RequestConfig = Omit<AxiosRequestConfig, 'closeInstance' | 'transformRequest'>
 
+export class AsyncRequest {
+  instance: AxiosInstance;
+  constructor() {
+    this.instance = axios.create({ baseURL, timeout })
+  }
+}
 export class Request {
   instance: AxiosInstance;
   constructor(config: AxiosRequestConfig) {
     this.instance = axios.create(config);
     this.instance.interceptors.request.use(
-        (config:InternalAxiosRequestConfig) => {          
-          if ("token" in config){
-            config.headers.Authorization = config.token
-          }else {
-            if (getSessionVar('session_id') != null && getSessionVar('username') != null){
-              config.headers.Authorization = 'Beaver ' + getSessionVar('username') + ' ' + getSessionVar('session_id');
-            }
-            
-            //config.headers.Authorization = getToken()
+      (config: InternalAxiosRequestConfig) => {
+        if ("token" in config) {
+          config.headers.Authorization = config.token
+        } else {
+          if (getSessionVar('session_id') != null && getSessionVar('username') != null) {
+            config.headers.Authorization = 'Beaver ' + getSessionVar('username') + ' ' + getSessionVar('session_id');
           }
-          if (config.isFormRequest){config.transformRequest = toFormData}
-          if(!config.closeLoading){
-            //Loading
-          }
-          return config;
-        },
-        (err: any) => {
-          // 请求错误,这里可以用全局提示框进行提示
-          return Promise.reject(err);
+
+          //config.headers.Authorization = getToken()
+        }
+        if (config.isFormRequest) { config.transformRequest = toFormData }
+        if (!config.closeLoading) {
+          //Loading
         }
+        return config;
+      },
+      (err: any) => {
+        // 请求错误,这里可以用全局提示框进行提示
+        return Promise.reject(err);
+      }
     );
     this.instance.interceptors.response.use(
-        (res: AxiosResponse) => {
-          // 直接返回res,当然你也可以只返回res.data
-          // 系统如果有自定义code也可以在这里处理
-          return res.data;
-        },
-        (err: any) => {
-          
-          let message = "";
-          if (statusDesc[err.response.status]){
-            message = statusDesc[err.response.status]
-          }else {
-            message = `连接出错(${err.response.status})!`
-          }
-          // 这里错误消息可以使用全局弹框展示出来
-          // 比如element plus 可以使用 ElMessage
-          // ElMessage({
-          //   showClose: true,
-          //   message: `${message},请检查网络或联系管理员!`,
-          //   type: "error",
-          // });
-          // 这里是AxiosError类型,所以一般我们只reject我们需要的响应即可
-          if (err.response.status == 401){
-            const router = useRouter();
-           
-            deleteSessionVar("session_id")
-            deleteSessionVar("user_id")
-            deleteSessionVar("username")
-            deleteSessionVar("full_name")
-            router.replace({path:"/"});
-           
-            return [];
-          }
-          console.log(message)
-          return Promise.reject(err.response);
+      (res: AxiosResponse) => {
+        // 直接返回res,当然你也可以只返回res.data
+        // 系统如果有自定义code也可以在这里处理
+        return res.data;
+      },
+      (err: any) => {
+
+        let message = "";
+        if (statusDesc[err.response.status]) {
+          message = statusDesc[err.response.status]
+        } else {
+          message = `连接出错(${err.response.status})!`
+        }
+        // 这里错误消息可以使用全局弹框展示出来
+        // 比如element plus 可以使用 ElMessage
+        // ElMessage({
+        //   showClose: true,
+        //   message: `${message},请检查网络或联系管理员!`,
+        //   type: "error",
+        // });
+        // 这里是AxiosError类型,所以一般我们只reject我们需要的响应即可
+        if (err.response.status == 401) {
+          const router = useRouter();
+
+          deleteSessionVar("session_id")
+          deleteSessionVar("user_id")
+          deleteSessionVar("username")
+          deleteSessionVar("full_name")
+          document.location.href = "/"
+
+          return [];
         }
+        console.log(message)
+        return Promise.reject(err.response);
+      }
     );
   }
 
   //未拦截请求,响应原封不动返回
 
   unhandledRequest<T>(config: RequestConfig): Promise<AxiosResponse<ResponseResult<T>>> {
-    return this.instance.request({...config,closeInstance:true});
+    return this.instance.request({ ...config, closeInstance: true });
   }
   //做了拦截处理,自动报错,只返回关心的数据
   request<T>(config: RequestConfig): Promise<T> {
@@ -117,7 +123,7 @@ export class Request {
   }
 }
 
-export  default new Request({baseURL,timeout})
+export default new Request({ baseURL, timeout })
 
 
 

+ 1 - 1
src/utils/session.ts

@@ -12,6 +12,6 @@ export function deleteSessionVar(name: string) {
     return sessionStorage.removeItem(name);
 }
 
-export function clearSessionVar(name: string) {
+export function clearSessionVar() {
     return sessionStorage.clear();
 }

+ 15 - 0
src/views/404/404.vue

@@ -0,0 +1,15 @@
+<template>
+  <el-empty :image-size="200">
+    <el-button type="primary"><router-link to="/kmplatform">返回主页</router-link></el-button>
+  </el-empty>
+</template>
+
+<script setup>
+
+</script>
+
+<style lang="less" scoped>
+a {
+  color: white;
+}
+</style>

+ 264 - 0
src/views/ConfigurationView.vue

@@ -0,0 +1,264 @@
+<template>
+    <el-row>
+        <h2>配置</h2>
+    </el-row>
+    <el-row style="margin:15px; ">
+        <el-tabs v-model="activeName" style="width: 100%; ">
+            <el-tab-pane label="基本配置" name="basic"
+                style="width: 100%;height: 800px; overflow-y:auto; display: flex; flex-direction: row;">
+                <el-form :model="basicConfig" label-width="120px" style="margin: 5px;">
+                    <el-form-item label="名称" prop="name" style="height: 25px;">
+                        <el-input v-model="basicConfig.name"></el-input>
+                    </el-form-item>
+                    <el-form-item label="联系人" prop="contactor" style="height: 25px;">
+                        <el-input v-model="basicConfig.contactor"></el-input>
+                    </el-form-item>
+                    <el-form-item label="联系电话" prop="contact_phone" style="height: 25px;">
+                        <el-input v-model="basicConfig.contact_phone"></el-input>
+                    </el-form-item>
+                    <el-form-item label="启用日期" prop="system_uptime" style="height: 25px;">
+                        <el-date-picker v-model="basicConfig.system_uptime" type="date" placeholder="选择日期">
+                        </el-date-picker>
+                    </el-form-item>
+                    <el-form-item label="结束日期" prop="system_downtime" style="height: 25px;">
+                        <el-date-picker v-model="basicConfig.system_downtime" type="date" placeholder="选择日期">
+                        </el-date-picker>
+                    </el-form-item>
+                </el-form>
+            </el-tab-pane>
+            <el-tab-pane label="队列配置" name="first" style="width: 100%;height: 800px; overflow-y:auto;">
+                <div v-for="(queue) in queueData" :key="queue.id"
+                    style="margin:0px 8px 10px 0px;width:350px; display: inline-block; ">
+                    <span style="font-weight: bold;"> {{ queue.queue_label }}</span> <span style="font-size: 8pt;">({{
+                        queue.queue_category }}_{{ queue.queue_name }})</span><br />
+                    <br />
+                    <div style="border: solid 1px #CDCDCD;padding:8px;">
+                        当成功时,转到 {{ queue.queue_config?.success.queue_category }}_{{
+                            queue.queue_config?.success.queue_name }}<br />
+                        当失败时,转到 {{ queue.queue_config?.failed.queue_category }}_{{ queue.queue_config?.failed.queue_name
+                        }}<br />
+                        当错误时,转到 {{ queue.queue_config?.error.queue_category }}_{{ queue.queue_config?.error.queue_name
+                        }}<br />
+                        最大实例数:<el-select v-model="queue.queue_config.max_instance" style="width:55px;margin-right:5px;">
+                            <el-option v-for="item in [1, 2, 3, 4, 5]" :key="item" :label="item"
+                                :value="item"></el-option>
+                        </el-select>
+                        <div style="border-top: solid 1px #CDCDCD; margin-top:5px; display:grid;justify-items: right;">
+                            <el-button @click="handleSaveConfig(queue)"
+                                style=" width:80px; margin-top:3px;">保存</el-button>
+                        </div>
+                    </div>
+
+
+                </div>
+            </el-tab-pane>
+            <el-tab-pane label="大模型配置" name="second">
+
+                <el-button link type="primary" @click="handleSaveLLMConfig" style="">保存</el-button>
+                <div style="width: 100%;display: flex; flex-direction: row; flex-wrap:wrap;">
+                    <div
+                        style="width: 300px;height:210px;border: 1px solid #CDCDCD; display: flex; flex-direction: column;justify-content:center;margin-right: 8px; margin-bottom: 8px;">
+                        <el-button link type="primary" @click="handleAddLLMConfig">添加</el-button>
+                    </div>
+                    <template v-for="(item) in llmConfig.items" :key="item.id">
+                        <div
+                            style="width: 300px;height:210px;border: 1px solid #CDCDCD; display: flex; flex-direction: column;justify-content:center;margin-right: 8px; margin-bottom: 8px;">
+                            <div v-if="item.is_default"
+                                style="position: relative; width: 15px; height:15px;left: 0px; top:15px; background-color: darkgreen;">
+                                <span style="height:15px;">&nbsp;</span>
+                            </div>
+                            <el-form :model="item" label-width="80px" style="margin: 5px;">
+                                <el-form-item label="类型" prop="api_name" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false">{{ item.api_name }}</span>
+                                    <el-select v-else v-model="item.api_name" style="width: 150px;">
+                                        <el-option v-for="option in ['openai', 'deepseek v3', 'deepseek r1']"
+                                            :key="option" :label="option" :value="option"></el-option>
+                                    </el-select>
+                                </el-form-item>
+                                <el-form-item lable="模型" prop="model_name" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false">{{ item.model_name }}</span>
+                                    <el-input v-else v-model="item.model_name"></el-input>
+                                </el-form-item>
+                                <el-form-item label="API 地址" prop="api_host" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false" style="overflow: hidden;">{{
+                                        formatOverflowText(item.api_host, 32) }}</span>
+                                    <el-input v-else v-model="item.api_host"></el-input>
+                                </el-form-item>
+                                <el-form-item label="API Key" prop="api_key" style="height: 25px;">
+                                    <span v-if="getLLMConfigEditFlag(item) == false">********</span>
+                                    <el-input v-else v-model="item.api_key"></el-input>
+                                </el-form-item>
+                                <el-form-item style="display:grid;justify-items: right; height:25px;">
+                                    <el-button v-if="!item.is_default" link type="primary"
+                                        @click="handleSetLLMConfigDefault(item)" style=" ">设为默认</el-button>
+                                    <el-button link type="danger" @click="handleDeleteLLMConfig(item)"
+                                        v-if="getLLMConfigEditFlag(item) == false" style=" ">删除</el-button>
+                                    <el-button link type="primary" @click="handleEditLLMConfig(item)"
+                                        v-if="getLLMConfigEditFlag(item) == false" style=" ">编辑</el-button>
+                                    <el-button link type="primary" @click="handleCancelEditLLMConfig(item)"
+                                        v-if="getLLMConfigEditFlag(item) == true" style=" ">取消</el-button>
+
+                                </el-form-item>
+                            </el-form>
+                        </div>
+                    </template>
+                </div>
+            </el-tab-pane>
+            <!-- <el-tab-pane label="许可证" name="third">
+        
+        第一步:通过hash数值查询许可证信息<br/>
+        第二步:导入许可证文件
+        <el-image style="width: 300px; height: 300px" :src="'/api/agent/qrcode/somm'" wid></el-image>
+    </el-tab-pane> -->
+        </el-tabs>
+    </el-row>
+</template>
+<script setup lang="ts">
+import { ref, onMounted, watch, nextTick } from 'vue'
+import { getConfig, setConfig, getQueues } from '@/api/AgentApi'
+import { ElMessage } from 'element-plus'
+import { formatOverflowText } from '@/utils/misc'
+import { ITEM_RENDER_EVT } from 'element-plus/es/components/virtual-list/src/defaults.mjs'
+interface ViewBasicInfo {
+    name: string,
+    contactor: string,
+    contact_phone: string,
+    system_uptime: Date,
+    system_downtime: Date
+}
+interface ActionQueue {
+    queue_category: string
+    queue_name: string
+}
+interface QueueConfig {
+    command: string
+    script: string
+    args: string[]
+    success: ActionQueue
+    failed: ActionQueue
+    error: ActionQueue
+    max_instance: number
+}
+interface QueueData {
+    id: number
+    queue_category: string
+    queue_name: string
+    queue_label: string
+    queue_config: QueueConfig
+}
+interface ConfigData {
+    id: number
+    code: string
+    name: string
+    content: string
+    status: number
+}
+interface LLMConfigData {
+    current_api_name: string
+    items: LLMConfigItemData[]
+}
+interface LLMConfigItemData {
+    api_key: string
+    api_host: string
+    api_name: string
+    model_name: string
+    is_default: boolean
+}
+const basicConfig = ref<ViewBasicInfo>({} as ViewBasicInfo)
+const activeName = ref('first')
+const queueData = ref<QueueData[]>([])
+const llmConfig = ref<LLMConfigData>({ current_api_name: "", items: [] })
+const llmConfigEditFlag = ref<Map<LLMConfigItemData, boolean>>(new Map())
+function loadQueueConfigData() {
+    queueData.value.forEach((queue) => {
+        var queue_code = queue.queue_category + "_" + queue.queue_name
+        getConfig({ code: queue_code }).then((res) => {
+            var config: ConfigData = res.records[0] as ConfigData
+            var config_data: QueueConfig = JSON.parse(config.content) as QueueConfig
+            queue.queue_config = config_data
+            if (queue.queue_config != null) {
+                if (queue.queue_config.max_instance === undefined) {
+                    queue.queue_config.max_instance = 1
+                }
+            }
+        })
+    })
+}
+function loadLLMConfigData() {
+    getConfig({ code: "LLM_CONFIG" }).then((res) => {
+        var config: ConfigData = res.records[0] as ConfigData
+        var config_data: LLMConfigData = JSON.parse(config.content) as LLMConfigData
+        llmConfig.value = config_data
+    })
+}
+function handleSaveLLMConfig() {
+    llmConfig.value.items.forEach((item) => {
+        if (item.is_default) {
+            llmConfig.value.current_api_name = item.api_name
+        }
+    })
+    var config: ConfigData = { id: 0, code: "LLM_CONFIG", name: "大模型配置", content: JSON.stringify(llmConfig.value), status: 1 }
+    setConfig(config).then((res) => {
+        if (res.code == 200) {
+            ElMessage.success("保存成功")
+        }
+    })
+
+}
+function handleSaveConfig(queue: QueueData) {
+
+}
+function handleSetLLMConfigDefault(item: LLMConfigItemData) {
+    llmConfig.value.items.forEach((item) => {
+        item.is_default = false
+    })
+    item.is_default = true
+}
+function handleDeleteLLMConfig(item: LLMConfigItemData) {
+    var index = llmConfig.value.items.indexOf(item)
+    llmConfig.value.items.splice(index, 1)
+}
+function getLLMConfigEditFlag(item: LLMConfigItemData) {
+    return llmConfigEditFlag.value.get(item) == true ? true : false
+}
+function handleEditLLMConfig(item: LLMConfigItemData) {
+    llmConfigEditFlag.value.set(item, true)
+}
+function handleCancelEditLLMConfig(item: LLMConfigItemData) {
+    llmConfigEditFlag.value.set(item, false)
+}
+function handleAddLLMConfig() {
+    var item: LLMConfigItemData = { api_key: "", api_host: "", api_name: "", is_default: false, model_name: "" }
+    item.api_name = "openai"
+
+    llmConfig.value.items.push(item)
+    llmConfigEditFlag.value.set(item, true)
+}
+function loadQueues() {
+    getQueues().then(async (res) => {
+        queueData.value = []
+        let data = res.records as QueueData[]
+        // loadQueueConfigData()
+        await Promise.allSettled(data.map(async (queue, index) => {
+            var queue_code = data[index].queue_category + "_" + data[index].queue_name
+            await getConfig({ code: queue_code }).then((res) => {
+                var config: ConfigData = res.records[0] as ConfigData
+                var config_data: QueueConfig = JSON.parse(config.content) as QueueConfig
+                data[index].queue_config = config_data
+                if (data[index].queue_config != null) {
+                    if (data[index].queue_config.max_instance === undefined) {
+                        data[index].queue_config.max_instance = 1
+                    }
+                }
+            })
+            queueData.value.push(data[index])
+        }))
+        // queueData.value = data
+    })
+}
+onMounted(() => {
+    loadQueues()
+    loadLLMConfigData()
+})
+</script>
+<style lang="css" scoped></style>

+ 19 - 48
src/views/AppCopy.vue

@@ -1,6 +1,5 @@
 <template>
-
-  <el-container class="main-container">
+  <div class="main-container">
     <el-container>
       <el-aside class="main-aside">
         <SideMenu @selectQueue="onSelectQueue" @selectMenu="onSelectMenu"></SideMenu>
@@ -12,80 +11,52 @@
         </router-view>
       </el-main>
     </el-container>
-  </el-container>
+  </div>
 </template>
 <script setup lang="ts">
 import { onMounted, ref, watch, watchEffect } from "vue";
 import { useRouter, useRoute } from "vue-router";
 import { RouterView } from "vue-router";
 import SideMenu from "@/components/SideMenu.vue";
-import { useMenuStore } from "@/stores/menu.js";
 
-const router = useRouter();
 
+const router = useRouter();
+const route = useRoute()
 //校验注册信息
 const onSelectQueue = (idData: any) => {
   // console.log(router.getRoutes());
   // 跳转到队列详情页并且传递队列ID作为查询参数
   // 假设队列详情页的路由路径是 '/workspace/queue'
   // 可以使用 router.push 方法来实现路由跳转
-
   router.push({ name: 'queue', params: { id: idData.id } });
 };
 const onSelectMenu = (menuData: any) => {
-  if (menuData.name == "graph") {
-    router.push({ name: 'graph' });
-  }
+  router.push({ name: menuData.name });
+  // if (menuData.name == "graph") {
+  //   router.push({ name: 'graph' });
+  // }
 };
 
 </script>
-<style scoped>
-#app {
-  width: 100vw;
-  height: 100vh;
-  overflow: auto;
-}
-
+<style scoped lang="less">
 .main-container {
   width: 100%;
-  height: 100%;
-  overflow: hidden;
-  box-sizing: margin-box;
-  padding: 0px;
-  margin: 0px;
-  min-width: 0px;
-}
+  // height: calc(100vh - 150px);
 
-.login-box {
-  width: 100%;
-  height: 100%;
-  background-image: url("@/assets/images/loginBanar.png");
-  background-repeat: no-repeat;
-  background-size: cover;
-  box-sizing: margin-box;
+  &>.el-container {
+    // width: 100%;
+    // height: 100%;
+    padding: 0px;
+    margin: 0px;
+    min-width: 0px;
+  }
 }
 
-.login-container {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-  margin: auto;
-  width: 500px;
-  /* width: 100vw; */
-  /* height: 100vh; */
-  padding: 50px;
-  background-color: #eef6ff;
-  border-radius: 12px 12px 12px 12px;
-}
 
-.login-header {
-  font-size: 26px;
-  font-weight: bold;
-  margin-bottom: 20px;
-}
+
 
 .content-area {
+  // height: 100px;
   padding: 20px;
   width: calc(100% - 300px);
   overflow: auto;

+ 81 - 75
src/views/GraphManagement.vue

@@ -1,107 +1,112 @@
 <template>
-    <GraphNodeDialog ref="graphNodeDialog" :title="'节点详情'" :graph-id="graphId" ></GraphNodeDialog>
-  <div>
-    <el-row>
-        <span class="view-title">图谱数据管理</span>
-      </el-row>
-    <el-row>
-        <el-tabs v-model="activeName" @tab-click="handleClick" style="width: 100%;">
-            <el-tab-pane label="基本信息" name="tab1">
-                <el-collapse accordion>
-                    <el-collapse-item  name="1">
-                        <template #title><span style="color:blue;">图谱数据概览</span></template>
-                        当前图谱共有 {{ graphSummary.nodes_count }} 个节点,{{ graphSummary.edges_count }} 条边<br>
-                        
-                    </el-collapse-item>
+    <GraphNodeDialog ref="graphNodeDialog" :title="'节点详情'" :graph-id="graphId"></GraphNodeDialog>
+    <div>
+        <el-row>
+            <span class="view-title">图谱数据管理</span>
+        </el-row>
+        <el-row>
+            <el-tabs v-model="activeName" @tab-click="handleClick" style="width: 100%;">
+                <el-tab-pane label="基本信息" name="tab1">
+                    <el-collapse accordion>
+                        <el-collapse-item name="1">
+                            <template #title><span style="color:blue;">图谱数据概览</span></template>
+                            当前图谱共有 {{ graphSummary.nodes_count }} 个节点,{{ graphSummary.edges_count }} 条边<br>
 
-                    <el-collapse-item  name="2">
-                        <template #title><span style="color:blue;">节点类型</span></template>
-                        节点类型包括:{{ graphSummary.nodes_categories.join(', ') }}<br></el-collapse-item>
-                        
-                    <el-collapse-item  name="3">
-                        <template #title><span style="color:blue;">边类型</span></template>
-                        边类型包括:{{ graphSummary.edges_categories.join(', ') }}<br></el-collapse-item>
-                </el-collapse>
-            </el-tab-pane>
-            <el-tab-pane label="节点搜索" name="tab2">                
-                <div style="display: flex; flex-direction: row;">
-                    <el-input style="width: 240px" v-model="formData.searchText" placeholder="请输入你要搜索的节点" />
-                    <el-button type="primary" @click="loadGraphNodes(formData.searchText)">节点搜索</el-button>
-                </div>
-                
-                <el-table :data="graphNodes"  height="650" style="width: 100%"  @current-change="handleCurrentChange">
-                <el-table-column prop="id" label="ID" width="180"></el-table-column>
-                <el-table-column prop="name" label="名称"></el-table-column>
-                <el-table-column prop="category" label="类型"></el-table-column>
+                        </el-collapse-item>
 
-                </el-table>
-            </el-tab-pane>
-            <el-tab-pane label="类型整合" name="tab3">
-                <GraphCategoryMgr :graph-id="graphId"></GraphCategoryMgr>
-            </el-tab-pane>
-            <el-tab-pane label="术语标注" name="tab4">术语标注</el-tab-pane>
-            <el-tab-pane label="语义检查" name="tab5"><el-menu>
-                <el-menu-item index="4-1">孤立节点</el-menu-item>
-                <el-menu-item index="4-2">矛盾关系</el-menu-item>
-                <el-menu-item index="4-2">循环关系</el-menu-item>
-                <el-menu-item index="4-2">冗余关系</el-menu-item>
-                <el-menu-item index="4-2">自动检查</el-menu-item>
-                </el-menu></el-tab-pane>
-        </el-tabs>
+                        <el-collapse-item name="2">
+                            <template #title><span style="color:blue;">节点类型</span></template>
+                            节点类型包括:{{ graphSummary.nodes_categories.join(', ') }}<br></el-collapse-item>
 
-    <el-col :span="18">
+                        <el-collapse-item name="3">
+                            <template #title><span style="color:blue;">边类型</span></template>
+                            边类型包括:{{ graphSummary.edges_categories.join(', ') }}<br></el-collapse-item>
+                    </el-collapse>
+                </el-tab-pane>
+                <el-tab-pane label="节点搜索" name="tab2">
+                    <div style="display: flex; flex-direction: row;">
+                        <el-input style="width: 240px" v-model="formData.searchText" placeholder="请输入你要搜索的节点" />
+                        <el-button type="primary" @click="loadGraphNodes(formData.searchText)">节点搜索</el-button>
+                    </div>
 
-    </el-col>
-    
-    <el-col :span="6">
-        <span style="margin-left: 20px;"> </span>
-    </el-col>
+                    <el-table :data="graphNodes" height="650" style="width: 100%" @current-change="handleCurrentChange">
+                        <el-table-column prop="id" label="ID" width="180"></el-table-column>
+                        <el-table-column prop="name" label="名称"></el-table-column>
+                        <el-table-column prop="category" label="类型"></el-table-column>
 
-    </el-row>
+                    </el-table>
+                </el-tab-pane>
+                <el-tab-pane label="类型整合" name="tab3">
+                    <GraphCategoryMgr :graph-id="graphId"></GraphCategoryMgr>
+                </el-tab-pane>
+                <el-tab-pane label="术语标注" name="tab4">术语标注</el-tab-pane>
+                <el-tab-pane label="语义检查" name="tab5"><el-menu>
+                        <el-menu-item index="4-1">孤立节点</el-menu-item>
+                        <el-menu-item index="4-2">矛盾关系</el-menu-item>
+                        <el-menu-item index="4-2">循环关系</el-menu-item>
+                        <el-menu-item index="4-2">冗余关系</el-menu-item>
+                        <el-menu-item index="4-2">自动检查</el-menu-item>
+                    </el-menu></el-tab-pane>
+            </el-tabs>
 
-  </div>
+            <el-col :span="18">
+
+            </el-col>
+
+            <el-col :span="6">
+                <span style="margin-left: 20px;"> </span>
+            </el-col>
+
+        </el-row>
+
+    </div>
 </template>
 
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
-import {useRoute,useRouter} from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import { getSessionVar } from '@/utils/session'
 import { searchNodes, getGraphSummary } from '@/api/GraphApi'
 import { ElNotification } from 'element-plus'
-import  GraphNodeDialog  from '@/dialogs/GraphNodeDialog.vue'
+import GraphNodeDialog from '@/dialogs/GraphNodeDialog.vue'
 import GraphCategoryMgr from '@/components/GraphCategoryMgr.vue'
-const graphNodeDialog=ref()
+let activeName = ref("")
+const graphNodeDialog = ref()
 const formData = ref({
     searchText: '%',
 })
 const graphId = ref(0)
-const graphSummary = ref({nodes_count:0, edges_count:0, nodes_categories:[], edges_categories:[]})
-const currentNode = ref({id:0})
+const graphSummary = ref({ nodes_count: 0, edges_count: 0, nodes_categories: [], edges_categories: [] })
+const currentNode = ref({ id: 0 })
 const graphNodes = ref([])
 onMounted(() => {
     var route = useRoute()
-    graphId.value = Number(route.params.id as string  | "0" )
-    console.log("graphId", graphId.value)
+    graphId.value = Number(route.params.id as string | "0")
+    // console.log("graphId", graphId.value)
     loadGraphSummary()
     loadGraphNodes(formData.value.searchText)
 })
-const handleCurrentChange = (val:any) => {
+const handleCurrentChange = (val: any) => {
     if (val == null) return
-    console.log("handleCurrentChange", val)
+    // console.log("handleCurrentChange", val)
     graphNodeDialog.value.showDialog(true, val.id)
 }
 const loadGraphSummary = () => {
-    console.log("loadGraphSummary", graphId.value) 
-    getGraphSummary({graph_id: graphId.value}).then((res:any) => {
-        graphSummary.value = res.records[0]})
+    // console.log("loadGraphSummary", graphId.value) 
+    getGraphSummary({ graph_id: graphId.value }).then((res: any) => {
+        graphSummary.value = res.records[0]
+    })
 }
-const loadGraphNodes = (searchKey:string) => {
-    console.log("loadGraphNodes", searchKey, graphId.value)
+function handleClick() {
+
+}
+const loadGraphNodes = (searchKey: string) => {
+    // console.log("loadGraphNodes", searchKey, graphId.value)
     if (graphId.value == 0 || graphId.value == undefined) {
         ElNotification({
             title: 'Error',
             message: 'Graph ID is required',
-            type: 'error', 
+            type: 'error',
         })
     }
     if (searchKey == '') {
@@ -109,10 +114,10 @@ const loadGraphNodes = (searchKey:string) => {
             title: 'Error',
             message: 'Search key is required',
             type: 'error',
-        }) 
+        })
         return
     }
-    searchNodes(searchKey, graphId.value).then((res:any) => {
+    searchNodes(searchKey, graphId.value).then((res: any) => {
         graphNodes.value = res.records
     })
 }
@@ -122,10 +127,11 @@ const loadGraphNodes = (searchKey:string) => {
 .view-title {
     font-size: 24px;
     font-weight: bold;
-    margin-bottom: 30px; 
-    color:darkblue;
+    margin-bottom: 30px;
+    color: darkblue;
 }
+
 .graphMenu {
-    width:100%
+    width: 100%
 }
 </style>

+ 126 - 0
src/views/GraphStdSchemas.vue

@@ -0,0 +1,126 @@
+<template>
+
+    <GraphSchemaDialog ref="graphSchemaDialog" @std-updated="handleStdSchemaUpdated" />
+    <el-row>
+        <span style="text-align: right;">
+            <h2>图谱标准</h2>
+        </span>
+    </el-row>
+    <el-row style="margin:15px;">
+        <el-button @click="handleRefreshTable">刷新</el-button>
+    </el-row>
+
+    <el-row>
+        <el-table :fit="false" :data="pageData.records" highlight-current-row>
+            <el-table-column prop="schema_system" label="标准机构" width="100"></el-table-column>
+            <el-table-column prop="schema_type" label="标准类型" width="100"></el-table-column>
+            <el-table-column prop="category" label="分类" width="100"></el-table-column>
+            <el-table-column prop="name" label="名称" width="200"></el-table-column>
+
+            <el-table-column label="操作" min-width="200">
+                <template v-slot="scope">
+                    <el-button type="primary" @click="handleShowStdSchema(scope.row)">查看</el-button>
+                    <el-button type="primary" @click="handleIndexSchema(scope.row)">索引</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+
+    </el-row>
+    <el-row style="margin-top:30px;">
+        <el-pagination background style="margin-left: auto;" layout="prev, pager, next" :total="pageData.total"
+            :page-size="pageData.page_size" @current-change="handleCurrentPageChange"
+            :current-page.sync="pageData.page">
+        </el-pagination>
+    </el-row>
+
+</template>
+
+
+<script setup lang="ts">
+
+import { ref, onMounted, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+import GraphSchemaDialog from '@/dialogs/GraphSchemaDialog.vue'
+import { ElNotification, ElMessageBox } from 'element-plus'
+import { formatTime } from 'element-plus/es/components/countdown/src/utils.mjs'
+import { formatDate } from '@/utils/misc'
+import { getSessionVar, saveSessionVar } from '@/utils/session'
+import { getGraphStdSchemas, reindexSchema } from '@/api/GraphApi'
+//从路由参数中获取队列ID
+
+const sessionId = ref("")
+//dialog related
+
+const graphSchemaDialog = ref()
+const currentData = ref({})
+const currentDialogEntity = ref({})
+const entitiesEditable = ref({})
+
+type pageMetaType = {
+
+}
+
+const pageData = ref({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
+const pageMeta = ref({})
+const route = useRoute()
+const router = useRouter()
+
+const loadTableData = () => {
+    getGraphStdSchemas({ page: pageData.value.page, page_size: pageData.value.page_size }).then((res: any) => {
+        pageData.value.records = res.records
+        pageMeta.value = res.meta || {}
+    })
+}
+
+function handleStdSchemaUpdated() {
+    // console.log("handleStdSchemaUpdated")
+
+}
+function handleShowStdSchema(data: any) {
+    // console.log("handleShowStdSchema", data)
+    data['data'] = JSON.parse(data["content"])
+    graphSchemaDialog.value.showDialog(true, data['data'], data.id)
+}
+
+
+const handleIndexSchema = (data: any) => {
+    // console.log(data)
+    ElMessageBox.confirm('是否确定索引该标准?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+    }).then(() => {
+        // 用户点击了确定按钮,执行相应的操作
+        // console.log('用户点击了确定按钮');
+        reindexSchema({ schema_id: data.id }).then((res: any) => {
+            var result = `总计${res.records[0]["entities_count"]}个实体,${res.records[0]["relations_count"]}个关系`
+            ElNotification({
+                title: 'Success',
+                message: '索引成功,' + result,
+                type: 'success',
+                duration: 3000, // 显示时间(单位:毫秒),0 表示不自动关闭  }) 
+            })
+        })
+    })
+}
+const handleCurrentPageChange = (val: number) => {
+    pageData.value.page = val
+    loadTableData()
+}
+const handleRefreshTable = () => {
+    loadTableData()
+}
+onMounted(() => {
+
+    loadTableData()
+})
+</script>
+
+<style lang="css" scoped>
+.prop_header {
+    font-weight: bold;
+    margin-top: 10px;
+    margin-bottom: 5px;
+}
+</style>

+ 63 - 162
src/views/GraphView.vue

@@ -1,6 +1,4 @@
 <template>
-
-
     <el-row>
         <span style="text-align: right;">
             <h2>知识图谱</h2>
@@ -8,78 +6,35 @@
     </el-row>
     <el-row style="margin:15px;">
         <el-button @click="handleRefreshTable">刷新</el-button>
+        <el-button @click="handleStreamTest">Stream</el-button>
     </el-row>
 
     <el-row>
-        <el-table :data="pageData.records" highlight-current-row @current-change="handleCurrentChange">
-            <el-table-column prop="id" label="ID" width="50"></el-table-column>
-            <el-table-column prop="job_category" label="工作类型" width="200"></el-table-column>
-            <el-table-column prop="job_name" label="工作名称" width="200"></el-table-column>
-            <el-table-column prop="job_creator" label="创建人" width="100"></el-table-column>
-            <el-table-column prop="status" label="状态" width="100">
-                <template v-slot="scope">
-                    <el-tag :type="getJobStatusTag(scope.row.status)">{{ formatStatus(scope.row.status) }}</el-tag>
-                </template>
-            </el-table-column>
-            <el-table-column label="操作" width="500">
-                <template v-slot="scope">
-                    <el-menu mode="horizontal">
-
-                        <el-sub-menu index="1">
-                            <template #title>操作</template>
-                            <el-menu-item index="1-1" @click="handleViewJob(scope.row)">查看</el-menu-item>
-                        </el-sub-menu>
+        <template v-for="(index) in pageData.records" :key="index">
+            <el-card shadow="hover" style="width: 300px; margin: 10px;">
+                <div slot="header" style="border-bottom: 1px solid #EFEFEF; height: 50px; clear: both;">
+                    <span>#{{ index.graph_id }}</span>
+                    <el-button @click="handleViewGraph(index)" style="float: right; width:75px; padding: 3px 0"
+                        type="">查看</el-button>
+                </div>
+                <div class="card-content">
+                    <div style="margin-top:15px;font-size:16px; clear: both;">
+                        {{ index.name }}
+                        <el-button @click="handleApply(index)" type="primary"
+                            style="float: right; width:75px; padding: 3px 0;">应用</el-button>
+                    </div>
+                </div>
+            </el-card>
+        </template>
 
-                    </el-menu>
-                </template>
-            </el-table-column>
-        </el-table>
 
     </el-row>
     <el-row style="margin-top:30px;">
-        <el-pagination background layout="prev, pager, next" :total="pageData.total" :page-size="pageData.page_size"
-            @current-change="handleCurrentPageChange" :current-page.sync="pageData.page">
+        <el-pagination style="margin-left: auto;" background layout="prev, pager, next" :total="pageData.total"
+            :page-size="pageData.page_size" @current-change="handleCurrentPageChange"
+            :current-page.sync="pageData.page">
         </el-pagination>
     </el-row>
-    <el-drawer title="工作详情" v-model="showDrawer" :direction="showDrawerDirection">
-        <el-tabs type="border-card" v-model="jobDetailsActivateTab">
-            <el-tab-pane label="基本信息" name="basic_info">
-                <div class="prop_header">任务类型</div>
-                <div>{{ currentJob.job_category }}</div>
-                <div class="prop_header">任务名称</div>
-                <div>{{ currentJob.job_name }}</div>
-                <div class="prop_header">创建人</div>
-                <div>{{ currentJob.job_creator }}</div>
-                <div class="prop_header">创建时间</div>
-                <div>{{ formatDate(currentJob.created) }}</div>
-                <div class="prop_header">更新时间</div>
-                <div>{{ formatDate(currentJob.updated) }}</div>
-                <div class="prop_header">日志</div>
-                <div><el-input type="textarea" v-model="currentJob.job_logs" :rows="20"></el-input></div>
-            </el-tab-pane>
-            <el-tab-pane label="文件夹" name="file_browse">
-                <el-row>
-                    <el-col :span="4">
-                        <el-tree :data="jobFileData" :props="jobFileTree" @node-click="handleTreeNodeClick"></el-tree>
-                    </el-col>
-                    <el-col :span="20">
-                        <el-table v-if="jobFileInDir.length > 0" :data="jobFileInDir" highlight-current-row
-                            style="width: 100%;">
-                            <el-table-column prop="type" label="类型" width="75"></el-table-column>
-                            <el-table-column prop="name" label="名称" width="250"></el-table-column>
-                            <el-table-column prop="size" label="操作" width="100">
-                                <template v-slot="scope">
-                                    <a :href="'/api/file/download/' + currentJob?.id + '/' + scope.row.path">下载</a>
-                                </template>
-                            </el-table-column>
-                        </el-table>
-                    </el-col>
-                </el-row>
-            </el-tab-pane>
-        </el-tabs>
-
-
-    </el-drawer>
 
 </template>
 
@@ -98,131 +53,77 @@ import {
     JobSatus,
     deleteJob,
     updateJob,
-    updateJobStatus
+    updateJobStatus,
+    serverStreamRequest,
 } from '@/api/AgentApi'
-import type { JobData, } from '@/api/AgentApi'
-import OCRDialog from '@/dialogs/OCRDialog.vue'
-import QueueSelectDialog from '@/dialogs/QueueSelectDialog.vue'
-import { ElNotification, ElMessageBox } from 'element-plus'
-import { formatTime } from 'element-plus/es/components/countdown/src/utils.mjs'
-import { formatDate } from '@/utils/misc'
-import { getSessionVar, saveSessionVar } from '@/utils/session'
-//从路由参数中获取队列ID
-
-const sessionId = ref<string | undefined>("")
-const ocrDialog = ref()
-const queueSelectDialog = ref()
-const queueId = ref("")
-const queueData = ref({ id: "0", queue_category: "", queue_name: "", title: "" })
-const showDrawer = ref(false)
-const showDrawerDirection = ref('rtl')
-const jobDetailsActivateTab = ref('basic_info')
-const queueIdNameDict = ref([
-    { id: "0", name: "DEFAULT", category: 'SYSTEM', title: "缺省队列", dialog: ocrDialog },
-    { id: "1", name: "OCR", category: 'SYSTEM', title: "文档OCR", dialog: ocrDialog },
-    { id: "2", name: "OCR_RESULTS", title: "OCR结果校对", category: 'SYSTEM', dialog: null },
-    { id: "3", name: "CHUNKS", title: "文本切片队列", category: 'SYSTEM', dialog: null },
-    { id: "4", name: "KB_EXTRACT", title: "知识抽取", category: 'SYSTEM', dialog: null },
-    { id: "5", name: "KB_BUILD", title: "图谱数据构建", category: 'SYSTEM', dialog: null },
-    { id: "6", name: "WORD", title: "WORD文档抽取", category: 'SYSTEM', dialog: ocrDialog },
-])
+import type { JobData, RequestBody, GraphData } from '@/api/AgentApi'
+import { getGraphs, applyGraph } from '@/api/GraphApi'
 
-const jobStatusList = ref(JobSatus)
-const jobFileTree = ref({
-    children: 'children',
-    label: 'label'
-})
-const pageData = ref({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
-const jobFileData = ref([])
-const jobFileInDir = ref([])
-const currentJob = ref<any>({ id: 0, job_category: "USER", job_name: "", job_creator: "", job_logs: "", updated: "", created: null })
-const currentActionJob = ref({ id: 0, job_category: "USER", job_name: "" })
+import { getSessionVar } from '@/utils/session'
+//从路由参数中获取队列ID
 
+interface PageData {
+    page: number;
+    pages: number;
+    page_size: number;
+    total: number;
+    records: GraphData[];
+}
+const pageData = ref<PageData>({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
 const route = useRoute()
 const router = useRouter()
-watch(route, (newValue, oldValue) => {
-    let para = newValue.params.id as string | undefined
-    if (para == undefined) {
-        return
-    }
-    console.log('route changed')
-    queueId.value = para
-    queueData.value.id = "0"
-    loadQueueData()
-})
 
 const handleRefreshTable = () => {
-    loadQueueData()
+    pageData.value.page = 1
+    loadGraphs()
+}
+function handleStreamTest() {
+    var data: RequestBody = {
+        id: "1",
+        action: 'search_nodes',
+        params: []
+    }
+    serverStreamRequest("/kb/stream", data)
 }
 function formatStatus(status: number) {
     return getJobStatus(status)
 }
-function loadQueueData() {
 
-    jobFileData.value = []
-    jobFileInDir.value = []
-    currentJob.value = { id: 0, job_category: "USER", job_name: "" }
-    currentActionJob.value = { id: 0, job_category: "USER", job_name: "" }
-    // 根据队列ID获取队列数据
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+function handleViewGraph(graph: GraphData) {
 
-    if (queue_data == undefined) {
-        queueData.value = { id: "999", queue_category: 'SYSTEM', queue_name: 'UNKNOWN', title: "未知队列" }
+    if (graph == null) {
         return
     }
-    getQueue(queue_data.category, queue_data.name).then((res) => {
-        if (res.records.length == 0) {
-            return
-        }
-        queueData.value.id = res.records[0].id
-        queueData.value.queue_category = res.records[0].queue_category
-        queueData.value.queue_name = res.records[0].queue_name
-        queueData.value.title = queue_data.title
-    })
-    getJobs(queue_data.category, queue_data.name, pageData.value.page, pageData.value.page_size).then((res) => {
-        pageData.value.page = res.meta.page
-        pageData.value.pages = res.meta.pages
-        pageData.value.total = res.meta.total
-        pageData.value.records = res.records
-
-    })
-}
-function handleTreeNodeClick(data: any) {
-    if (data.type == 'dir') {
-        browseJobFile({ job_id: currentJob.value.id, path: data.name }).then((res) => {
-            jobFileInDir.value = res.records
-        })
-    }
+    router.push({ name: `graph-mgr`, params: { id: graph.graph_id } })
 }
-function handleCurrentPageChange(page: number) {
-    getJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
+function loadGraphs() {
+    getGraphs({ page: pageData.value.page, page_size: pageData.value.page_size }).then((res: any) => {
         pageData.value.page = res.meta.page
         pageData.value.pages = res.meta.pages
         pageData.value.total = res.meta.total
         pageData.value.records = res.records
     })
 }
-function handleViewJob(job: JobData) {
-    if (job == null) {
-        return
-    }
-    router.push({ name: 'graph-mgr', params: { id: job.id } })
+function handleCurrentPageChange(page: number) {
+    pageData.value.page = page
+    loadGraphs()
 }
-function handleCurrentChange(data: any) {
-    if (data == null) {
-        return
-    }
-
-    //currentJob.value = data
+function handleApply(record: GraphData) {
+    applyGraph(record.graph_id).then((res: any) => {
+        const { code } = res
+        if (code === 200) {
+            loadGraphs()
+        }
+    }).catch((err: any) => {
+        console.log(err)
+    })
 }
 onMounted(() => {
-    sessionId.value = getSessionVar("session_id") as string | undefined
-    queueId.value = "5"
-    loadQueueData()
+    loadGraphs()
 })
 </script>
 
-<style lang="css" scoped>
+<style lang="less" scoped>
 .prop_header {
     font-weight: bold;
     margin-top: 10px;

+ 294 - 0
src/views/JobView.vue

@@ -0,0 +1,294 @@
+<template>
+
+    <TextViewDialog ref="textViewDialog" :title="'查看文件'" :content="textViewData"></TextViewDialog>
+
+    <el-row>
+        <el-page-header @back="handleGoBack" :content="jobData?.job_name" title="返回">
+        </el-page-header>
+        <el-divider></el-divider>
+    </el-row>
+    <el-row>
+        <el-col :span="20">
+            <el-tabs type="border-card" v-model="activeTab">
+                <el-tab-pane label="基本信息" name="basic_info">
+                    <el-row>
+                        <el-col :span="6">
+                            <div class="prop_header">任务ID</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ jobData?.id }}</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="prop_header">任务类型</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ getJobCategory(jobData?.start_category) }}</div>
+                        </el-col>
+                    </el-row>
+                    <el-row>
+                        <el-col :span="6">
+                            <div class="prop_header">当前工序</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ getJobCategory(jobData?.job_category) }}</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="prop_header">任务名称</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <el-tooltip class="item" effect="dark" :content="jobData?.job_name" placement="top-end">
+                                <div>{{ formatFilename(jobData?.job_name, 20) }}</div>
+                            </el-tooltip>
+                        </el-col>
+                    </el-row>
+                    <el-row>
+                        <el-col :span="6">
+                            <div class="prop_header">任务状态</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ getJobStatus(jobData?.status) }}</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="prop_header">创建人</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ jobData?.job_creator }}</div>
+                        </el-col>
+                    </el-row>
+                    <el-row>
+                        <el-col :span="6">
+                            <div class="prop_header">创建时间</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ jobData?.created }}</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="prop_header">更新时间</div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div>{{ jobData?.updated }}</div>
+                        </el-col>
+                    </el-row>
+                    <el-row>
+                        <div class="prop_header" style="background-color: #EFEFEF; width: 100%;">日志</div><br />
+                    </el-row>
+                    <el-row>
+                        <div style="white-space: pre-wrap; height:500px; width:100%; overflow-y: auto;">{{
+                            jobData?.job_logs }}</div>
+
+                    </el-row>
+
+
+                </el-tab-pane>
+                <el-tab-pane label="文件夹" name="file_browse">
+                    <el-row>
+                        <el-col :span="4" style="border-right: 1px solid #CDCDCD;">
+                            <el-tree :data="jobFileData" :props="jobFileTree"
+                                @node-click="handleTreeNodeClick"></el-tree>
+                        </el-col>
+                        <el-col :span="20"
+                            style="display:flex;flex-direction: row; flex-wrap: wrap; height:500px; overflow: auto;">
+                            <template v-for="item in jobFileInDir">
+                                <el-card shadow="hover" style="width: 200px; height: 120px; margin: 10px;">
+                                    <div class="card-header">
+                                        <span style="font-size:10px;">
+                                            <span v-if="item.type == 'file'">文件</span>
+                                            <span v-if="item.type == 'dir'">文件夹</span>
+                                        </span>
+                                    </div>
+                                    <div class="card-content">
+                                        <el-tooltip class="item" effect="dark" :content="item.name" placement="top-end">
+                                            <span
+                                                style="font-size:12px;display: flex; flex-wrap: nowrap; height:25px;">{{
+                                                    formatFilename(item.name, 20) }}</span>
+                                        </el-tooltip>
+                                    </div>
+
+                                    <div class="bottom clearfix">
+
+                                        <a
+                                            :href="'/api/file/download/' + jobData?.id + '?path=' + encodeURIComponent(item.path)">下载</a>
+                                        <a @click="handleViewFile(jobData, item.path)">查看</a>
+                                    </div>
+                                </el-card>
+
+                            </template>
+                            <!-- <el-table v-if="jobFileInDir.length > 0" :data="jobFileInDir" highlight-current-row style="width: 100%;">
+                                <el-table-column prop="type" label="类型" width="50">
+     
+                                </el-table-column>
+                                <el-table-column prop="name" label="名称" width="250">
+     
+                                </el-table-column>
+                                <el-table-column prop="size" label="操作" width="100">
+                                    <template v-slot="scope">
+                                        <a :href="'/api/file/download/'+jobData?.id+'?path='+encodeURIComponent(scope.row.path)">下载</a>
+                                        <a  @click="handleViewFile(jobData, scope.row.path)">查看</a>
+                                    </template>
+                                </el-table-column>
+                            </el-table> -->
+                        </el-col>
+                    </el-row>
+                </el-tab-pane>
+            </el-tabs>
+        </el-col>
+        <el-col :span="4">
+            <el-menu @select="handleMenuSelect" :default-active="activeTab" mode="vertical"
+                class="el-menu-vertical-demo" menu-trigger="click">
+                <el-menu-item-group index="CHANGE_CATEGORY">
+                    <template #title>工序变更</template>
+                    <el-menu-item :index="jobData.start_category">开始文字提取</el-menu-item>
+                    <el-menu-item index="SYSTEM_CHUNKS">开始文本切片</el-menu-item>
+                    <el-menu-item index="SYSTEM_KB_EXTRACT">开始抽取知识</el-menu-item>
+                    <el-menu-item index="SYSTEM_KB_BUILD">开始构建图谱</el-menu-item>
+                </el-menu-item-group>
+            </el-menu>
+        </el-col>
+    </el-row>
+</template>
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { getJob, browseJobFile, getJobStatus, getFileContent, putQueueJob, getJobCategory } from '@/api/AgentApi'
+import { formatDate } from '@/utils/misc'
+import TextViewDialog from '@/dialogs/TextViewDialog.vue'
+import type { JobData } from '@/api/AgentApi'
+import { ElMessageBox, ElNotification } from 'element-plus'
+
+interface JobFile {
+    type: string
+    name: string
+    path: string
+}
+const router = useRouter()
+
+const jobId = ref<number>(0)
+const jobData = ref<JobData>({} as JobData)
+
+var textViewContent = "hello"
+const textViewData = ref(textViewContent)
+const textViewDialog = ref()
+const jobFileTree = ref({
+    children: 'children',
+    label: 'display_name'
+})
+const jobFileData = ref([])
+const jobFileInDir = ref<JobFile[]>([])
+
+const activeTab = ref('basic_info')
+function formatFolderName(name: string) {
+    let translate = new Map([
+        ["logs", "日志"],
+        ["results", "结果"],
+        ["chunks", "切片"],
+        ["files", "文件"],
+        ["ocr_output", "文本转换结果"],
+        ["kb_extract", "知识抽取"],
+        ["kb_build", "知识构建"],
+        ["upload", "文件上传"],
+        ["output", "输出"]]
+    )
+    if (translate.get(name) != undefined) {
+        return translate.get(name)
+    }
+    return name
+}
+function handleTreeNodeClick(data: any, node: any, event: any) {
+    if (data.type == 'dir') {
+        browseJobFile({ job_id: jobData.value.id, path: data.name }).then((res) => {
+            jobFileInDir.value = res.records
+        })
+    }
+}
+function handleViewFile(job: JobData, path: string) {
+    const url = '/api/file/view/' + job?.id + '?path=' + encodeURIComponent(path)
+    // console.log(url)
+    getFileContent(url).then((res) => {
+        // console.log(res)
+        textViewData.value = res.records[0].content
+        textViewDialog.value.showDialog(true)
+    })
+}
+function handleGoBack() {
+    router.push({ name: 'queue', params: { id: 0 } })
+}
+function loadJobData() {
+    // console.log(jobId.value)
+    getJob(jobId.value).then((res) => {
+        jobData.value = res.records[0] as JobData
+
+        browseJobFile({ job_id: jobId.value, path: "" }).then((res) => {
+            for (let i = 0; i < res.records.length; i++) {
+                res.records[i]['label'] = res.records[i].name
+                res.records[i]['children'] = []
+                res.records[i]['display_name'] = formatFolderName(res.records[i].name)
+            }
+            jobFileData.value = res.records
+
+        })
+    })
+}
+function formatFilename(name: string, length: number) {
+    if (name == undefined) {
+        return ""
+    }
+    if (name.length > length) {
+        return name.substring(0, length / 2) + "..." + name.substring(name.length - length / 2)
+    }
+    return name
+}
+function handleMenuSelect(key: string, keyPath: string) {
+    var category = getJobCategory(keyPath)
+    ElMessageBox.confirm(
+        `确定要转移任务到队列 "${category}" 吗?`,
+        '提示',
+    ).then(() => {
+        let category = key.split("_")[0]
+        let name = key.split("_")[2] ? key.split("_")[1] + '_' + key.split("_")[2] : key.split("_")[1]
+        handleJobChangeCategory(category, name)
+    })
+}
+function handleJobChangeCategory(category: string, name: string) {
+
+    var params = { job_id: jobId.value, queue_category: category, queue_name: name }
+
+    putQueueJob(params).then((res: any) => {
+        ElNotification({
+            title: '提示',
+            message: '任务已转移到队列',
+            type: 'success',
+            duration: 3000
+        })
+        loadJobData()
+    })
+
+}
+onMounted(() => {
+    var route = useRoute()
+    // console.log(route.params)
+    var id = Number.parseInt(route.params.id as string)
+    jobId.value = id
+    loadJobData()
+})
+</script>
+<style lang="css" scoped>
+.prop_header {
+    font-weight: bold;
+    margin-bottom: 5px;
+}
+
+.bottom {
+    margin-top: 15px;
+    line-height: 12px;
+}
+
+.clearfix:before,
+.clearfix:after {
+    display: table;
+    content: "";
+}
+
+.clearfix:after {
+    clear: both
+}
+</style>

+ 50 - 41
src/views/KMPlatform/Home/Home.vue

@@ -21,19 +21,24 @@
         </div>
       </div>
       <div class="modules">
-        <div v-for="item in modules" @click="handleModuleClick(item.path)">{{ item.name }}</div>
+        <div v-for="item in modules" @click="handleModuleClick(item.path)">{{ item.title }}</div>
       </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, watchEffect, onBeforeUnmount, onMounted } from 'vue'
+import { ref, watchEffect, onBeforeUnmount, onMounted, watch } from 'vue'
 import { useRouter } from 'vue-router'
+import { intersectionBy, assign } from 'lodash'
 import { knowledgeGraphAddr } from "@/utils/config"
 import { getSessionVar } from '@/utils/session'
-import { handleError } from 'vue'
-const router = useRouter()
+import { useMenuStore } from "@/stores/menu"
+
+import { storeToRefs } from 'pinia'
+const menuStore = useMenuStore();
+const { routeList } = storeToRefs(menuStore)
+const router: any = useRouter()
 let timer: any
 // console.log("getSessionVar('knowledageSystem') ", getSessionVar('knowledageSystem'))
 let process = ref<any>([
@@ -53,49 +58,54 @@ let process = ref<any>([
     description: "知识更新、维护",
   }
 ])
-let modules = ref<any>([
-  {
-    name: "知识库构建",
-    path: "/kmplatform/knowledgebase/kbm",
-
-
-  },
-  {
-    name: '知识图谱构建',
-    path: "/kmplatform/kgbuilder",
-
-  },
-  {
-    name: "知识图谱维护",
-    path: knowledgeGraphAddr,
-
-  }
-])
-watchEffect(() => {
-  if (getSessionVar('knowledageSystem') == '') {
-    modules.value = [
-      {
-        name: "知识库构建",
-        path: "/kmplatform/knowledgebase/kbm",
-
-
-      },
-      {
-        name: '知识图谱构建',
-        path: "/kmplatform/kgbuilder",
 
-      }
-    ]
-  }
+const initModules = () => {
+  return [
+    {
+      title: "知识库构建",
+      path: "/kmplatform/knowledgebase",
+    },
+    {
+      title: '知识图谱构建',
+      path: "/kmplatform/kgbuilder",
+
+    },
+    {
+      title: "知识图谱维护",
+      path: knowledgeGraphAddr,
+    }
+  ]
 }
-)
+let modules = ref<any>(initModules())
+watch(routeList, (newValue) => {
+  console.log("routeList", newValue)
+  modules.value = intersectionBy(initModules(), newValue, 'path')
+  if (modules.value.length === 0) {
+    modules.value = new Array(3).fill({ title: "暂无内容" })
+  }
+}, { immediate: true })
+// watchEffect(() => {
+//   if (getSessionVar('knowledageSystem') == '') {
+//     modules.value = [
+//       {
+//         title: "知识库构建",
+//         path: "/kmplatform/knowledgebase",
+//       },
+//       {
+//         title: '知识图谱构建',
+//         path: "/kmplatform/kgbuilder",
+//       }
+//     ]
+//   }
+// }
+// )
 
 function handleModuleClick(path: string) {
   if (/^https?/g.test(path)) {
-    path = 'http://localhost:8081/home.html'
+    // path = 'http://localhost:8081/home.html'
     const newWindow = window.open(path, '_blank');
     timer = setInterval(() => {
-      newWindow?.postMessage({ type: 'login', username: getSessionVar("full_name"), userId: getSessionVar("user_id") }, "*")
+      newWindow?.postMessage({ type: 'login', usertitle: getSessionVar("full_title"), userId: getSessionVar("user_id") }, "*")
     }, 1000)
   } else {
     router.push({ path: path })
@@ -120,7 +130,6 @@ onBeforeUnmount(() => {
 
 <style lang="less" scoped>
 .rocket-icon {
-
   width: 95px;
   height: 95px;
 }

+ 4 - 4
src/views/KMPlatform/KGBuilder/ETM/EntityTypeManagement.vue

@@ -105,13 +105,13 @@ function handleNewlyAdd() {
   }
 
   .elTable :deep(.el-table__body),
-  .elTable :v-deep(.el-table__footer),
-  .elTable :v-deep(.el-table__header) {
+  .elTable :deep(.el-table__footer),
+  .elTable :deep(.el-table__header) {
     table-layout: auto;
   }
 
-  .elTable :v-deep(.el-table__empty-block),
-  .elTable :v-deep(.el-table__body) {
+  .elTable :deep(.el-table__empty-block),
+  .elTable :deep(.el-table__body) {
     width: 100% !important;
   }
 }

+ 49 - 37
src/views/KMPlatform/KnowledgeBase/KBM/KnowledgeBaseManagement.vue

@@ -22,7 +22,7 @@
     </div>
     <main>
       <div class="grid-arrange" v-if="arrange === 'grid'">
-        <div class="box">
+        <div class="box" v-if="operationPermissions['a-1']">
           <div>
             <span class="icon" @click="handleCreateKB"><el-icon>
                 <Plus />
@@ -36,16 +36,17 @@
               <i class="folder-icon" style="height: 32px;width:32px;"></i>
             </span>
             <span class="text">
-              <div class="title" @click="toKMById(item.id)">{{ item.name }}</div>
+              <div class="title" @click="operationPermissions['a-2'] ? toKMById(item.id) : null">{{ item.name }}</div>
               <div class="remark">
-                <template v-if=" typeof item.tags === 'string'">
-                 <el-tag type="info" effect="plain" hit>{{ item.tags }}</el-tag>
+                <template v-if="typeof item.tags === 'string'">
+                  <el-tag type="info" effect="plain" hit>{{ item.tags }}</el-tag>
 
                 </template>
                 <template v-else-if="Array.isArray(item.tags)">
-                  <el-tag style="margin-right: 5px;padding: 4px;" v-for="(tag, tagIndex) in item.tags" :key="tagIndex">{{ tag }}</el-tag>
+                  <el-tag style="margin-right: 5px;padding: 4px;" v-for="(tag, tagIndex) in item.tags"
+                    :key="tagIndex">{{ tag }}</el-tag>
                 </template>
-               
+
               </div>
             </span>
           </div>
@@ -58,7 +59,7 @@
             <span class="add-label">
               <i class="label-icon"></i>
               <i class="label-text">添加标签</i>
-              <i class="circle-plus" @click="handleAddKBTags(index)"> <el-icon>
+              <i class="circle-plus" v-if="operationPermissions['a-5']" @click="handleAddKBTags(index)"> <el-icon>
                   <CirclePlus />
                 </el-icon></i>
             </span>
@@ -73,8 +74,9 @@
                 </span>
               </template>
               <template #default>
-                <div><el-button link @click="handleEditKB(item)">编辑</el-button></div>
-                <div><el-button link type="danger" @click="handleDeleteKB(item.id)">删除</el-button></div>
+                <div v-if="operationPermissions['a-3']"><el-button link @click="handleEditKB(item)">编辑</el-button></div>
+                <div v-if="operationPermissions['a-4']"><el-button link type="danger"
+                    @click="handleDeleteKB(item.id)">删除</el-button></div>
               </template>
             </el-popover>
 
@@ -82,38 +84,44 @@
         </div>
       </div>
       <div class="list-arrange" v-if="arrange === 'list'">
-        <div class="list-arrange-top"><button class="create-kb" @click="handleCreateKB">创建知识库</button></div>
+        <div class="list-arrange-top"><button v-if="operationPermissions['a-1']" class="create-kb"
+            @click="handleCreateKB">创建知识库</button></div>
         <el-table class="elTable" :data="KBData" style="width: 100%; min-width: 0px;">
-          <el-table-column prop="name" label="知识库名称">
+          <el-table-column prop="name" label="知识库名称" min-width="100">
             <template #default="{ row }">
               <div class="list-name">
                 <div class="icon-area">
                   <i class="document-icon"></i>
                 </div>
                 <div class="text-area">
-                  <div class="name" @click="toKMById(row.id)">{{ row.name }}</div>
+                  <div class="name" @click="operationPermissions['a-2'] ? toKMById(row.id) : null">{{
+                    row.name }}
+                  </div>
                   <div class="remark">{{ row.description }}</div>
                 </div>
               </div>
             </template>
           </el-table-column>
-          <el-table-column prop="file_count" label="文件上数量" min-width="120" />
+          <el-table-column prop="file_count" label="文件上数量" width="120" />
           <el-table-column prop="tags" label="知识库标签">
             <template #default="{ row }">
-              <i style="white-space: nowrap;text-overflow: ellipsis;">{{ row.tags }}</i>
+              <i style="white-space: nowrap;text-overflow: ellipsis;">
+                {{ Array.isArray(row.tags) ? row.tags.join("、") : '' }}
+              </i>
             </template>
           </el-table-column>
-          <el-table-column prop="created_at" label="知识库创建时间" min-width="150" />
-          <el-table-column prop="creator" label="创建人" />
-          <el-table-column fixed="right" label="操作" width="200">
+          <el-table-column prop="created_at" label="知识库创建时间" width="150" />
+          <el-table-column prop="creator" label="创建人" width="150" />
+          <el-table-column fixed="right" label="操作" width="150">
             <template #default="{ row }">
-              <el-button link type="primary" @click="toKMById(row.id)">
+              <el-button link type="primary" v-if="operationPermissions['a-2']" @click="toKMById(row.id)">
                 查看
               </el-button>
-              <el-button link type="primary" @click="handleEditKB(row)">
+              <el-button link type="primary" v-if="operationPermissions['a-3']" @click="handleEditKB(row)">
                 编辑
               </el-button>
-              <el-button link type="danger" @click="handleDeleteKB(row.id)">删除</el-button>
+              <el-button link type="danger" v-if="operationPermissions['a-4']"
+                @click="handleDeleteKB(row.id)">删除</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -177,6 +185,8 @@ import { confirmDelete } from "@/utils/confirmation"
 import EditKBDialog from "@/components/EditKBDialog.vue"
 import { api } from "@/utils/config.js"
 import { ElMessage, ElMessageBox } from "element-plus"
+import { useMenuStore } from "@/stores/menu.js"
+const { operationPermissions } = useMenuStore();
 const router = useRouter()
 const { proxy } = getCurrentInstance()
 let editKBDialogData = ref({ show: false, kb: {} })
@@ -190,7 +200,7 @@ defineOptions({
 const formData = ref({
   name: "",
   description: "",
-  tags: ""
+  tags: []
 })
 const rules = {
   name: [
@@ -261,8 +271,8 @@ function handleAddKBTags(index) {
     inputErrorMessage: '无效输入',
   })
     .then(async ({ value }) => {
-       let tags = []
-      if(item.tags) {
+      let tags = []
+      if (item.tags) {
         if (typeof item.tags === 'string') {
           tags = item.tags.split()
           tags.push(value)
@@ -270,10 +280,10 @@ function handleAddKBTags(index) {
           tags = [...item.tags]
           tags.push(value)
         }
-      }else {
+      } else {
         tags = value.split()
       }
-       
+
       const data = await proxy.$http.put(api.knowledgeBase + `/${item.id}`, {
         "name": item.name,
         "description": item.description,
@@ -283,8 +293,8 @@ function handleAddKBTags(index) {
         type: 'success',
         message: `添加知识库标签成功!`,
       })
-      // KBData.value.splice(index, 1, data)
-      getKnowledgeBase()
+      KBData.value.splice(index, 1, data)
+      // getKnowledgeBase()
     })
     .catch((e) => {
       console.log(e)
@@ -458,7 +468,7 @@ onMounted(() => {
         height: 200px;
         border: 1px solid #E5E8EC;
         border-radius: 10px;
-        box-shadow: 0px 0px 1px gray;
+        box-shadow: 0px 0px 1px rgb(199, 193, 193);
         box-sizing: border-box;
         padding: 20px 10px 20px 15px;
         display: flex;
@@ -469,7 +479,6 @@ onMounted(() => {
           display: flex;
           flex: 0 0 auto;
 
-
           .icon {
             flex: 0 0 auto;
           }
@@ -555,19 +564,20 @@ onMounted(() => {
           .operation {
             padding: 10px;
             background: #EFEFF0;
-
-
           }
         }
       }
 
       .box1 {
         background-color: #FCFCFD;
+        // transition: all 0.5s linear;
 
-        .icon {
+        // &:hover {
+        //   transform: translate(0px, -10px) scale(1.05);
+        // }
 
+        .icon {
           background-color: #F5F8FF;
-
         }
       }
     }
@@ -577,15 +587,17 @@ onMounted(() => {
 
       .list-arrange-top {
         clear: both;
-        min-height: 50px;
+        // min-height: 50px;
         background-color: #fff;
-        padding: 10px 20px;
+        // padding: 10px 20px;
         box-sizing: border-box;
+        text-align: right;
 
         .create-kb {
-          margin-left: auto;
+
+          margin: 5px 0px;
           display: inline-block;
-          float: right;
+          // float: right;
           border: none;
           padding: 8px 15px;
           border-radius: 5px;

+ 73 - 38
src/views/KMPlatform/KnowledgeBase/KM/KnowledgeManagement.vue

@@ -18,37 +18,47 @@
               :prefix-icon="Search" />
           </div>
 
-          <span @click="addFileVisible = true" class="add-file"><el-icon color="#fff">
+          <span v-if="operationPermissions['a-6']" @click="addFileVisible = true" class="add-file"><el-icon
+              color="#fff">
               <Plus />
             </el-icon> <i class="text">添加文件</i></span>
-          <span class="add-file" style="margin-right: 10px;" @click="handleBatchEditFiles"><i class="text"
-              style="margin: 0px;">批量修改文件</i></span>
+          <span class="add-file" style="margin-right: 10px;" v-if="operationPermissions['a-10']"
+            @click="handleBatchEditFiles"><i class="text" style="margin: 0px;">批量修改文件</i></span>
         </div>
         <div class="management-content-middle">
           <el-table :data="filesList" ref="fileTableRef" size="large" style="width: 99% ;">
             <el-table-column type="selection" width="35" />
-            <el-table-column label="#" prop="index" width="60" />
+            <el-table-column label="#" prop="index" width="50" />
             <el-table-column prop="file_name" min-width="150" label="标题">
               <template #default="{ row }">
                 <div class="list-name">
                   <span class="icon-area">
                     <i :class="`${row.file_type}-icon file-icon`"></i>
                   </span>
-                  <span @click="handleViewFile(row.minio_url, row.file_type)" class="text-area">
+                  <el-text line-clamp="1" class="text-area"
+                    @click="operationPermissions['a-7'] ? handleViewFile(row) : null">
                     {{ row.file_name }}
-                  </span>
+                  </el-text>
                 </div>
               </template>
             </el-table-column>
-            <el-table-column prop="knowledge_type" label="知识类型" />
+            <el-table-column prop="knowledge_type" label="知识类型">
+              <template #default="{ row }">
+                <el-text line-clamp="1" style="vertical-align: bottom;">{{ row.knowledge_type }}</el-text>
+              </template>
+            </el-table-column>
             <el-table-column prop="version" label="版本" />
-            <el-table-column prop="author" label="作者(主编)" />
-            <el-table-column prop="year" label="年份" min-width="70" />
-            <el-table-column prop="creator" label="上传人" />
-            <el-table-column prop="created_at" label="上传时间" width="170" />
-            <el-table-column prop="" label="状态">
+            <el-table-column prop="author" label="作者(主编)" min-width="90" />
+            <el-table-column prop="year" label="年份" min-width="60" />
+            <el-table-column prop="creator" label="上传人">
+              <template #default="{ row }">
+                <el-text line-clamp="1" style="vertical-align: bottom;">{{ row.creator }}</el-text>
+              </template>
+            </el-table-column>
+            <el-table-column prop="created_at" label="上传时间" width="155" />
+            <el-table-column prop="status" label="状态" width="107">
               <template #default="{ row, $index }">
-                <div style="display:flex; align-items: center;gap: 10px;">
+                <div class="status">
                   <span v-if="row.status">
                     <i class="circle" type="success"></i>
                     <el-text type="success">可用</el-text>
@@ -61,28 +71,26 @@
                     <i class="circle" type="danger"></i>
                     <el-text type="danger">禁用</el-text>
                   </span>
-                  <el-switch v-model="row.status" @change="handleStatusChange(row.id, row.status)"></el-switch>
+                  <el-switch v-if="operationPermissions['a-8']" v-model="row.status"
+                    @change="handleStatusChange(row.id, row.status)"></el-switch>
                 </div>
               </template>
             </el-table-column>
-            <el-table-column prop="created_at" label="下载" width="70">
+            <el-table-column v-if="operationPermissions['a-9']" prop="created_at" label="下载" width="50">
               <template #default="{ row }">
-                <i class="document-download-icon" :disabled="row.status ? true : false"
-                  @click="handleDownloadFile(row.minio_url, row.file_name, row.status)"></i>
+                <div style="text-align: center;">
+                  <i class="document-download-icon" :disabled="row.status ? true : false"
+                    @click="handleDownloadFile(row)"></i>
+                </div>
               </template>
             </el-table-column>
-            <el-table-column fixed="right" label="操作">
+            <el-table-column fixed="right" label="操作" min-width="85">
               <template #default="{ row }">
                 <div class="operation">
-
-                  <!-- <el-icon size="24">
-                    <Operation />
-                  </el-icon>
-                  <el-icon size="24">
-                    <MoreFilled />
-                  </el-icon> -->
-                  <el-button link type="primary" @click="handleEditFile(toRaw([row]))">修改</el-button>
-                  <el-button link type="danger" @click="handleDeleteFile(row.id)">删除</el-button>
+                  <el-button link type="primary" v-if="operationPermissions['a-10']"
+                    @click="handleEditFile(toRaw([row]))">修改</el-button>
+                  <el-button link type="danger" v-if="operationPermissions['a-11']"
+                    @click="handleDeleteFile(row.id)">删除</el-button>
                 </div>
               </template>
             </el-table-column>
@@ -111,7 +119,7 @@
 import { saveAs } from 'file-saver'
 import { ElMessage } from 'element-plus'
 import { ref, getCurrentInstance, computed, watch, toRaw, onMounted } from 'vue'
-import { Download, Search } from '@element-plus/icons-vue'
+import { Search } from '@element-plus/icons-vue'
 import { useRoute, useRouter } from 'vue-router';
 import axios from 'axios';
 import { api } from '@/utils/config';
@@ -119,6 +127,8 @@ import { confirmDelete } from "@/utils/confirmation"
 import CreateKBFileDialog from '@/components/CreateKBFileDialog.vue';
 import FileViewer from "@/components/FileViewer/FileViewer.vue"
 import EditKBFileDialog from "@/components/EditKBFileDialog.vue"
+import { useMenuStore } from "@/stores/menu.js"
+const { operationPermissions } = useMenuStore();
 const { proxy } = getCurrentInstance()
 const route = useRoute()
 const router = useRouter()
@@ -172,7 +182,7 @@ const handleCloseViewer = () => {
 };
 // 修改状态
 const handleStatusChange = async (id, status) => {
-  console.log('handleStatusChange', status)
+  // console.log('handleStatusChange', status)
   try {
     await proxy.$http.get(api.files + `${id}/changeStatus`, {
       params: {
@@ -210,14 +220,19 @@ function handleEditFile(data) {
   editKBFileData.value.data = data;
 }
 
-const handleViewFile = (url, type) => {
+const handleViewFile = (fileData) => {
+  const { minio_url, file_type, file_name, id } = fileData
+  const url = `/open-platform/files/${id}/download`
   viewFileData.value.show = true;
   viewFileData.value.url = url;
-  viewFileData.value.type = type
+  viewFileData.value.type = file_type
+  viewFileData.value.name = file_name
   // console.log(viewFileData.value)
 }
-function handleDownloadFile(file_url, file_name, status = true) {
+function handleDownloadFile(fileData) {
+  const { minio_url, file_name, status, id } = fileData
   if (status) {
+    const file_url = `/open-platform/files/${id}/download`
     saveAs(file_url, file_name)
   }
 }
@@ -394,9 +409,13 @@ onMounted(() => {
       }
     }
 
-    &:v-deep(.el-table) {
+    :deep(.el-table) {
       min-width: 0px;
-      max-width: 100%;
+      max-width: auto;
+
+      .cell {
+        padding: 0px 8px;
+      }
 
       .document-download-icon {
         width: 28px;
@@ -410,14 +429,30 @@ onMounted(() => {
       }
 
       .list-name {
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        display: inline;
+        // white-space: nowrap;
+        // text-overflow: ellipsis;
+        display: flex;
+        align-items: center;
 
         .text-area,
         .icon-area {
           vertical-align: bottom;
         }
+
+        .icon-area {
+          flex: 0 0 auto;
+        }
+
+        .text-area {
+          flex: 1 1 auto;
+        }
+      }
+
+      .status {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        white-space: nowrap;
       }
 
       .circle {
@@ -426,7 +461,7 @@ onMounted(() => {
         height: 10px;
         background-color: #8AD068;
         display: inline-block;
-        margin-right: 10px;
+        margin-right: 7px;
 
         &:is([type='warning']) {
           background-color: #E6A23C;

+ 1 - 2
src/views/KMPlatform/Layout.vue

@@ -6,7 +6,6 @@
       </el-header>
       <el-main>
         <router-view v-slot="{ Component }">
-          <!-- {{ console.log('Layout', Component) }} -->
           <transition @before-enter="beforeEnter" @after-leave="afterLeave">
             <component :is='Component'></component>
           </transition>
@@ -63,7 +62,7 @@ function afterLeave() {
 .el-container {
   width: 100%;
   height: 100%;
-  min-width: 800px;
+  min-width: 1200px;
   margin: 0px;
   padding: 0px;
   // min-width: var(--layoutMinWidth);

+ 17 - 1
src/views/KMPlatform/OpenPlatform/OpenPlatform.vue

@@ -36,13 +36,29 @@ import { ref } from 'vue';
 import { useRouter } from 'vue-router';
 const router = useRouter();
 const handlePath = (id) => {
-  router.push({ name: 'platformText', params: { id: id } });
+
+  router.push({ name: 'platformText', params: { id: id } }).catch(err => console.log(err));
 };
 
 </script>
 
 <style lang="less" scoped>
+.main-container {
+  height: 100%;
+}
+
 .el-menu-open-platform {
   height: 100%;
 }
+
+.main-aside {
+  position: fixed;
+  z-index: 10;
+  height: calc(100vh - 90px);
+}
+
+.content-area {
+  box-sizing: border-box;
+  padding-left: 305px;
+}
 </style>

+ 26 - 2
src/views/KMPlatform/OpenPlatform/platformText.vue

@@ -111,7 +111,7 @@
         </div>
       </template>
       <p>接口描述:根据指定的源节点ID,获取与其关联的所有关系及目标节点信息。</p>
-      <p class="mx-10">请求方式:GET</p>
+      <p class="mx-10">请求方式:POST</p>
       <p>请求地址:/v1/knowledge/nodes/{srcId}/relationships</p>
 
       <h3>路径参数</h3>
@@ -121,7 +121,19 @@
         <el-table-column prop="required" label="必填" />
         <el-table-column prop="description" label="说明" />
       </el-table>
+      <h3>请求参数 (Request Body - JSON)</h3>
+      <el-table :data="requestParams2" border style="width: 100%">
+        <el-table-column prop="fieldName" label="字段名" />
+        <el-table-column prop="name" label="名称" />
+        <el-table-column prop="type" label="类型" />
+        <el-table-column prop="required" label="必填" />
+        <el-table-column prop="description" label="描述" />
+        <el-table-column prop="defaultValue" label="默认值" />
+      </el-table>
 
+      <h3>请求示例</h3>
+      <!-- <pre><code>{{ requestExample }}</code></pre> -->
+      <pre><code v-html="highlightHtml(requestExample2, 'json')"></code></pre>
       <h3>响应结构 (Response - JSON)</h3>
       <!-- <h4>顶层字段</h4> -->
       <el-table :data="topLevelResponseParams" border style="width: 100%">
@@ -235,6 +247,10 @@ const requestParams = ref([
   { fieldName: 'limit', name: '每页记录数', type: 'int', required: '否', description: '指定每页返回的最大实体数量。', defaultValue: '系统默认' }
 ]);
 
+const requestParams2 = ref([
+  { fieldName: 'relation_name', name: '关系名称', type: 'string', required: '否', description: '(可选)按关系名称过滤结果。', defaultValue: '无' },
+]);
+
 // 请求示例
 const requestExample = ref(`{
     "name": "感冒",
@@ -242,6 +258,10 @@ const requestExample = ref(`{
     "limit": 10
 }`);
 
+const requestExample2 = ref(`
+{
+  "relation_name": "所属科室"
+}`);
 // 响应参数
 const responseParams = ref([
   { fieldName: 'success', name: '请求状态', type: 'Boolean', description: 'true 表示请求成功处理;false 表示发生错误。' },
@@ -296,7 +316,11 @@ const responseExample = ref(`{
 }`);
 // 路径参数
 const pathParams = ref([
-  { paramName: 'srcId', type: 'string', required: '是', description: '源节点的唯一标识符' }
+  { paramName: 'srcId', type: 'string', required: '是', description: '源节点的唯一标识符' },
+  // { paramName: 'category', name: '节点类型', type: 'string', required: '否', description: '(可选)按节点类型过滤结果。', defaultValue: '无' },
+  // { paramName: 'distance', name: '相似度阈值', type: 'Float', required: '否', description: '向量计算的距离阈值。仅返回相似度小于等于此值的实体 (0=完全相等, 2=完全不相关)。值越小,匹配度要求越高。', defaultValue: '1.45' },
+  // { paramName: 'pageNo', name: '页码', type: 'int', required: '否', description: '指定要获取的结果页码(从 1 开始计数)。', defaultValue: '1' },
+  // { paramName: 'limit', name: '每页记录数', type: 'int', required: '否', description: '指定每页返回的最大实体数量。', defaultValue: '系统默认' }
 ]);
 
 // 顶层响应参数

+ 32 - 72
src/views/KMPlatform/Permission/AccountManage.vue

@@ -5,48 +5,27 @@
         创建账号
       </el-button>
     </div> -->
-    <el-table
-      border
-      :data="tableData"
-      style="
+    <el-table border :data="tableData" style="
         width: 80%;
         font-size: 20px;
         overflow: hidden;
         border-radius: 4px;
         margin: 20px 0 0 50px;
-      "
-    >
+      ">
       <el-table-column prop="id" label="ID" width="180" align="center" />
-      <el-table-column
-        prop="username"
-        label="用户名"
-        width="180"
-        align="center"
-      />
-      <el-table-column
-        prop="full_name"
-        label="全名"
-        width="300"
-        align="center"
-      />
+      <el-table-column prop="username" label="用户名" width="180" align="center" />
+      <el-table-column prop="full_name" label="全名" width="300" align="center" />
       <el-table-column prop="email" label="邮箱" width="400" align="center" />
       <el-table-column fixed="right" label="操作" align="center">
         <template #default="scope">
-          <el-button link type="primary" @click="handleEdit(scope.row)"
-            >编辑</el-button
-          >
+          <el-button link type="primary" v-if="operationPermissions['c-2']"
+            @click="handleEdit(scope.row)">编辑</el-button>
         </template>
       </el-table-column>
     </el-table>
     <div class="page">
-      <el-pagination
-        v-if="total > 0"
-        :page-sizes="[10, 20, 30, 40, 100]"
-        :page-size="page.pageSize"
-        :total="total"
-        @current-change="handleCurrentChange"
-        layout="total, prev, pager, next, jumper"
-      ></el-pagination>
+      <el-pagination v-if="total > 0" :page-sizes="[10, 20, 30, 40, 100]" :page-size="page.pageSize" :total="total"
+        @current-change="handleCurrentChange" layout="total, prev, pager, next, jumper"></el-pagination>
     </div>
     <el-dialog v-model="dialogFormVisible" title="编辑账号" width="500">
       <el-form :model="form">
@@ -55,12 +34,7 @@
         </el-form-item>
         <el-form-item label="角色">
           <el-select v-model="form.role_ids" placeholder="Please select a zone">
-            <el-option
-              v-for="item in rolesData"
-              :key="item.id"
-              :label="item.name"
-              :value="item.id"
-            />
+            <el-option v-for="item in rolesData" :key="item.id" :label="item.name" :value="item.id" />
           </el-select>
         </el-form-item>
       </el-form>
@@ -71,35 +45,26 @@
         </div>
       </template>
     </el-dialog>
-    <el-dialog
-      v-model="orgRoleDialogVisible"
-      title="用户机构与角色管理"
-      width="70vw"
-    >
-      <el-button type="primary" @click="handleAddOrgRole" style="margin: 10px"
-        >新增机构角色</el-button
-      >
+    <el-dialog v-model="orgRoleDialogVisible" title="用户机构与角色管理" width="70vw">
+      <el-button type="primary" v-if="operationPermissions['c-2-1']" @click="handleAddOrgRole"
+        style="margin: 10px">新增机构角色</el-button>
       <el-table :data="orgRoleTable" style="width: 100%">
         <el-table-column prop="organ_name" label="机构" />
         <el-table-column prop="role_name" label="角色" />
         <el-table-column prop="data_type" label="类型">
           <template #default="scope">
             <span>
-              {{ scope.row.data_type === 1 ? '个人' : scope.row.data_type === 2 ? '本机构' : scope.row.data_type === 3 ? '机构及下辖机构' : '' }}
+              {{ scope.row.data_type === 1 ? '个人' : scope.row.data_type === 2 ? '本机构' : scope.row.data_type === 3 ?
+                '机构及下辖机构' : '' }}
             </span>
           </template>
         </el-table-column>
         <el-table-column label="操作" width="180">
           <template #default="scope">
             <el-button size="small" @click="handleEditOrgRole(scope.row)"
-              >编辑</el-button
-            >
-            <el-button
-              size="small"
-              type="danger"
-              @click="handleDeleteOrgRole(scope.row)"
-              >删除</el-button
-            >
+              v-if="operationPermissions['c-2-2']">编辑</el-button>
+            <el-button size="small" type="danger" @click="handleDeleteOrgRole(scope.row)"
+              v-if="operationPermissions['c-2-3']">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -107,24 +72,13 @@
       <el-dialog v-model="isEditOrgRole" :title="EditOrgRoleTittle" width="700">
         <el-form :model="orgRoleForm" label-width="80px">
           <el-form-item label="机构">
-            <el-tree-select
-              v-model="orgRoleForm.organ_id"
-              :data="orgList"
-              :props="{ label: 'name', value: 'id', children: 'children' }"
-              placeholder="请选择机构"
-              clearable
-              check-strictly
-              style="width: 100%"
-            />
+            <el-tree-select v-model="orgRoleForm.organ_id" :data="orgList"
+              :props="{ label: 'name', value: 'id', children: 'children' }" placeholder="请选择机构" clearable check-strictly
+              style="width: 100%" />
           </el-form-item>
           <el-form-item label="角色">
             <el-select v-model="orgRoleForm.role_id" placeholder="请选择角色">
-              <el-option
-                v-for="role in roleList"
-                :key="role.id"
-                :label="role.name"
-                :value="role.id"
-              />
+              <el-option v-for="role in roleList" :key="role.id" :label="role.name" :value="role.id" />
             </el-select>
           </el-form-item>
           <el-form-item label="类型">
@@ -146,6 +100,8 @@
 
 <script setup>
 import { ref, reactive, getCurrentInstance } from "vue";
+import { useMenuStore } from "@/stores/menu.js"
+const { operationPermissions } = useMenuStore();
 const { proxy } = getCurrentInstance();
 let tableData = ref([]);
 let total = ref(0);
@@ -223,7 +179,7 @@ const handleEditOrgRole = (row) => {
 };
 // 删除机构角色
 const handleDeleteOrgRole = async (row) => {
-  await proxy.$http.post("/open-platform/userRoleOrgan/delete/"+ row.id);
+  await proxy.$http.post("/open-platform/userRoleOrgan/delete/" + row.id);
   fetchUserOrgRoles(currentUserId.value);
 };
 // 保存机构角色
@@ -247,7 +203,7 @@ const init = () => {
       },
     })
     .then((res) => {
-      console.log(res, "11");
+      // console.log(res, "11");
       if (res.records && res.records.length > 0) {
         res.records.forEach((item) => {
           tableData.value.push(item);
@@ -303,10 +259,14 @@ const submitChange = () => {
 
 <style lang="less" scoped>
 :deep(.el-table th) {
-  background-color: #eef6ff !important; /* 设置你想要的背景颜色 */
-  color: #333; /* 设置表头文字颜色 */
-  font-weight: bold; /* 设置表头文字加粗 */
+  background-color: #eef6ff !important;
+  /* 设置你想要的背景颜色 */
+  color: #333;
+  /* 设置表头文字颜色 */
+  font-weight: bold;
+  /* 设置表头文字加粗 */
 }
+
 .page {
   display: flex;
   justify-content: end;

+ 56 - 55
src/views/KMPlatform/Permission/Organizational.vue

@@ -2,17 +2,20 @@
     <el-card>
         <el-row justify="space-between" align="middle" class="mb-2">
             <el-col>
-                <el-button type="primary" @click="openAddDialog">新增机构</el-button>
+                <el-button type="primary" @click="openAddDialog" v-if="operationPermissions['c-3']">新增机构</el-button>
             </el-col>
         </el-row>
-        <el-table :data="orgList" style="width: 100%"  row-key="id" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
+        <el-table :data="orgList" style="width: 100%" row-key="id"
+            :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
             <el-table-column prop="name" label="机构名称" />
             <el-table-column prop="manager" label="管理员" />
             <el-table-column prop="phone" label="管理员电话" />
             <el-table-column label="操作" width="180">
                 <template #default="scope">
-                    <el-button size="small" @click="openEditDialog(scope.row)">编辑</el-button>
-                    <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
+                    <el-button size="small" @click="openEditDialog(scope.row)"
+                        v-if="operationPermissions['c-4']">编辑</el-button>
+                    <el-button size="small" type="danger" @click="handleDelete(scope.row)"
+                        v-if="operationPermissions['c-5']">删除</el-button>
                 </template>
             </el-table-column>
         </el-table>
@@ -24,15 +27,9 @@
                     <el-input v-model="form.name" />
                 </el-form-item>
                 <el-form-item label="父级机构" prop="parent_id">
-                    <el-tree-select
-                        v-model="form.parent_id"
-                        :data="orgList"
-                        :props="{ label: 'name', value: 'id', children: 'children' }"
-                        placeholder="不选则为一级机构"
-                        clearable
-                        check-strictly
-                        style="width: 100%;"
-                    />
+                    <el-tree-select v-model="form.parent_id" :data="orgList"
+                        :props="{ label: 'name', value: 'id', children: 'children' }" placeholder="不选则为一级机构" clearable
+                        check-strictly style="width: 100%;" />
                 </el-form-item>
                 <el-form-item label="负责人" prop="manager">
                     <el-input v-model="form.manager" />
@@ -60,23 +57,25 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted,getCurrentInstance } from 'vue'
+import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
+import { useMenuStore } from "@/stores/menu.js"
+const { operationPermissions } = useMenuStore();
 const { proxy } = getCurrentInstance()
 
 // 模拟接口
 const fetchOrgList = async () => {
-  const {records} = await proxy.$http.get('/open-platform/organ/loadData')
-  orgList.value =  records
+    const { records } = await proxy.$http.get('/open-platform/organ/loadData')
+    orgList.value = records
 }
 const fetchAdminList = async () => {
-     const {records} = await proxy.$http.get('/open-platform/user/users',{
+    const { records } = await proxy.$http.get('/open-platform/user/users', {
         params: {
-        userName:'',
-        pageNo: 1,
-        pageSize: 100,
+            userName: '',
+            pageNo: 1,
+            pageSize: 100,
         }
-      })
+    })
     return records
 }
 
@@ -136,32 +135,32 @@ const handleSubmit = () => {
         // const admin = adminList.value.find(u => u.account === form.adminAccount)
         if (form.id) {
             // 编辑
-           proxy.$http.post('/open-platform/organ/update', form)
-            .then((res) => {
-                if (res.code === 200) {
-                   fetchOrgList()
-                    ElMessage.success('编辑成功')
-                } else {
+            proxy.$http.post('/open-platform/organ/update', form)
+                .then((res) => {
+                    if (res.code === 200) {
+                        fetchOrgList()
+                        ElMessage.success('编辑成功')
+                    } else {
+                        ElMessage.error('编辑失败')
+                    }
+                })
+                .catch(() => {
                     ElMessage.error('编辑失败')
-                }
-            })
-            .catch(() => {
-                ElMessage.error('编辑失败')
-            })
+                })
         } else {
             // 新增
-           proxy.$http.post('/open-platform/organ/insert', form)
-            .then((res) => {
-                if (res.code === 200) {
-                   fetchOrgList()
-                    ElMessage.success('新增成功')
-                } else {
+            proxy.$http.post('/open-platform/organ/insert', form)
+                .then((res) => {
+                    if (res.code === 200) {
+                        fetchOrgList()
+                        ElMessage.success('新增成功')
+                    } else {
+                        ElMessage.error('新增失败')
+                    }
+                })
+                .catch(() => {
                     ElMessage.error('新增失败')
-                }
-            })
-            .catch(() => {
-                ElMessage.error('新增失败')
-            })
+                })
         }
         dialogVisible.value = false
     })
@@ -169,18 +168,18 @@ const handleSubmit = () => {
 const handleDelete = (row) => {
     ElMessageBox.confirm('确定删除该机构吗?', '提示', { type: 'warning' })
         .then(() => {
-           proxy.$http.post('/open-platform/organ/delete/' + row.id, )
-            .then((res) => {
-                if (res.code === 200) {
-                    fetchOrgList()
-                    ElMessage.success('删除成功')
-                } else {
+            proxy.$http.post('/open-platform/organ/delete/' + row.id,)
+                .then((res) => {
+                    if (res.code === 200) {
+                        fetchOrgList()
+                        ElMessage.success('删除成功')
+                    } else {
+                        ElMessage.error('删除失败')
+                    }
+                })
+                .catch(() => {
                     ElMessage.error('删除失败')
-                }
-            })
-            .catch(() => {
-                ElMessage.error('删除失败')
-            })
+                })
         })
 }
 
@@ -191,5 +190,7 @@ onMounted(async () => {
 </script>
 
 <style scoped>
-.mb-2 { margin-bottom: 16px; }
+.mb-2 {
+    margin-bottom: 16px;
+}
 </style>

+ 29 - 45
src/views/KMPlatform/Permission/RoleManage.vue

@@ -5,49 +5,27 @@
         创建账号
       </el-button>
     </div> -->
-    <el-table
-      border
-      :data="tableData"
-      style="
+    <el-table border :data="tableData" style="
         width: 80%;
         font-size: 20px;
         overflow: hidden;
         border-radius: 4px;
         margin: 20px 0 0 50px;
-      "
-    >
+      ">
       <el-table-column prop="id" label="ID" width="180" align="center" />
       <el-table-column prop="name" label="角色名" width="280" align="center" />
-      <el-table-column
-        prop="description"
-        label="描述"
-        width="400"
-        align="center"
-      />
+      <el-table-column prop="description" label="描述" width="400" align="center" />
       <el-table-column fixed="right" label="操作" align="center">
         <template #default="scope">
-          <el-button link type="primary" @click="handleChange(scope.row)"
-            >编辑</el-button
-          >
+          <el-button link type="primary" v-if="operationPermissions['c-1']"
+            @click="handleChange(scope.row)">编辑</el-button>
         </template>
       </el-table-column>
     </el-table>
-    <el-dialog
-      v-model="dialogFormVisible"
-      title="编辑角色"
-      width="500"
-      @close="handleClose"
-    >
+    <el-dialog v-model="dialogFormVisible" title="编辑角色" width="500" @close="handleClose">
       <div></div>
-      <el-tree
-        style="max-width: 600px"
-        :data="permissionData"
-        show-checkbox
-        node-key="id"
-        :default-checked-keys="currentCheckedKeys"
-        :props="defaultProps"
-        ref="tree"
-      />
+      <el-tree style="max-width: 600px" :data="permissionData" show-checkbox node-key="id"
+        :default-checked-keys="currentCheckedKeys" :props="defaultProps" ref="tree" />
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="handleClose">取消</el-button>
@@ -60,6 +38,8 @@
 
 <script setup>
 import { ref, reactive, getCurrentInstance, nextTick } from "vue";
+import { useMenuStore } from "@/stores/menu.js"
+const { operationPermissions } = useMenuStore();
 const { proxy } = getCurrentInstance();
 const defaultProps = {
   children: "children",
@@ -81,7 +61,7 @@ const tree = ref(null);
 
 const init = () => {
   proxy.$http.get("/open-platform/user/roles").then((res) => {
-    console.log(res, "11");
+    // console.log(res, "11");
     if (res.records && res.records.length > 0) {
       tableData.value = res.records
     } else {
@@ -115,26 +95,30 @@ const handleClose = () => {
   });
 };
 const submitChange = () => {
-    form.permission_ids = tree.value.getCheckedKeys();
-    proxy.$http
-      .post("/open-platform/user/roles", form)
-      .then((res) => {
-        proxy.$message.success("修改成功");
-        dialogFormVisible.value = false;
-        init();
-      })
-      .catch((err) => {
-        proxy.$message.error("修改失败");
-      });
+  form.permission_ids = tree.value.getCheckedKeys();
+  proxy.$http
+    .post("/open-platform/user/roles", form)
+    .then((res) => {
+      proxy.$message.success("修改成功");
+      dialogFormVisible.value = false;
+      init();
+    })
+    .catch((err) => {
+      proxy.$message.error("修改失败");
+    });
 };
 </script>
 
 <style lang="less" scoped>
 :deep(.el-table th) {
-  background-color: #eef6ff !important; /* 设置你想要的背景颜色 */
-  color: #333; /* 设置表头文字颜色 */
-  font-weight: bold; /* 设置表头文字加粗 */
+  background-color: #eef6ff !important;
+  /* 设置你想要的背景颜色 */
+  color: #333;
+  /* 设置表头文字颜色 */
+  font-weight: bold;
+  /* 设置表头文字加粗 */
 }
+
 :deep(.el-table .el-table__cell) {
   padding: 10px 0 !important;
 }

+ 8 - 10
src/views/KMPlatform/Permission/permission.vue

@@ -2,12 +2,8 @@
   <div class="kg-builder">
     <div class="aside" style="width: 200px">
       <ul class="menu-list">
-        <li
-          v-for="i in menuList"
-          :class="route.path === i.path ? 'menu-item active' : 'menu-item'"
-          :key="i.name"
-          @click="handleMenuClick(toRaw(i))"
-        >
+        <li v-for="i in menuList" :class="route.path === i.path ? 'menu-item active' : 'menu-item'" :key="i.name"
+          @click="handleMenuClick(toRaw(i))">
           {{ i.title }}
         </li>
       </ul>
@@ -21,6 +17,8 @@
 <script setup>
 import { ref, toRaw } from "vue";
 import { useRoute, useRouter } from "vue-router";
+import { useMenuStore } from "@/stores/menu.js"
+const { operationPermissions } = useMenuStore();
 const route = useRoute();
 const router = useRouter();
 let menuList = ref([
@@ -62,17 +60,17 @@ function handleMenuClick(data) {
     }
 
     .menu-list {
-       margin: 15px 0px 0px;
+      margin: 15px 0px 0px;
 
-         .menu-item {
+      .menu-item {
         cursor: pointer;
         padding: 10px;
         margin-bottom: 5px;
 
         &:is(.active) {
           // color: #0A61F7;
-          color:#40a1ff;
-           background: #eef6ff;
+          color: #40a1ff;
+          background: #eef6ff;
         }
 
         &:hover {

+ 0 - 1
src/views/Login/Login.vue

@@ -287,7 +287,6 @@ onMounted(() => {
   });
 });
 watchEffect(() => {
-  // 监听路由变化
   console.log("route changed", route);
   if (route.query.logout) {
     handleLogout()

+ 263 - 95
src/views/QueueView.vue

@@ -1,47 +1,90 @@
 <template>
-    <OCRDialog ref="ocrDialog" :title="'OCR任务'" :queue_category="queueData.queue_category"
+    <OCRDialog ref="ocrDialog" :title="queueData.title" :queue_category="queueData.queue_category"
         :queue_name="queueData.queue_name" @success="jobCreated"></OCRDialog>
     <QueueSelectDialog ref="queueSelectDialog" :title="'选择队列'" @success="jobMoved" :queue_data="queueIdNameDict">
     </QueueSelectDialog>
     <TextViewDialog ref="textViewDialog" :title="'查看文件'" :content="textViewData"></TextViewDialog>
     <el-row>
         <span style="text-align: right;">
-            <h2>工作队列- {{ queueData.title }} </h2>
+            <h2>工作队列- {{ pageTitle }} </h2>
         </span>
     </el-row>
+
     <el-row style="margin:15px;">
-        <el-button v-if="allowCreateJob" type="primary" @click="handleCreateJob">新建工作</el-button>
-        <el-button @click="handleRefreshTable">刷新</el-button>
-    </el-row>
+        <el-form :model="searchForm" :inline="true" label-position="right">
+            <el-form-item label="任务名称" prop="job_name" :label-width="70" style="margin-left:0px;margin-right:0px;">
+                <el-input v-model="searchForm.job_name" placeholder="请输入任务名称" :maxlength="32"></el-input>
+            </el-form-item>
+            <el-form-item label="任务类型" prop="start_category" :label-width="70"
+                style="margin-left:5px;margin-right:0px;">
+                <el-select v-model="searchForm.start_category" placeholder="请选择任务类型" style="width:100px;">
+                    <el-option v-for="item in jobStartCategoryList" :key="item.id" :label="item.label"
+                        :value="item.id"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="工序" prop="job_category" :label-width="50" style="margin-left:5px;margin-right:0px;">
+                <el-select v-model="searchForm.job_category" placeholder="请选择任务类型" style="width:120px;margin:0px;">
+                    <el-option v-for="item in jobCategoryList" :key="item.id" :label="item.label"
+                        :value="item.id"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="状态" prop="status" :label-width="50" style="margin-left:5px;margin-right:0px;">
+                <el-select v-model="searchForm.status" placeholder="请选择状态" style="width:120px;">
+                    <el-option v-for="item in jobStatusList" :key="item.value" :label="item.label"
+                        :value="item.value"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item style="justify-items: right;margin-left:10px;">
+                <el-button type="primary" @click="handleSearch">搜索</el-button>
+                <el-button type="primary" @click="handleSearchReset" style="margin-left:5px;">重置</el-button>
+                <el-dropdown style="margin-left:5px;">
+                    <el-button type="primary">
+                        新建<i class="el-icon-arrow-down el-icon--right"></i>
+                    </el-button>
+                    <template #dropdown>
+                        <el-dropdown-menu>
+                            <el-dropdown-item @click="handleCreateJob('SYSTEM', 'OCR', 'PDF 任务')">PDF
+                                任务</el-dropdown-item>
+                            <el-dropdown-item @click="handleCreateJob('SYSTEM', 'WORD', 'Word 任务')">Word
+                                任务</el-dropdown-item>
+                        </el-dropdown-menu>
+                    </template>
 
+                </el-dropdown>
+                <el-button @click="handleRefreshTable" style="margin-left:5px;" type="success">刷新</el-button>
+            </el-form-item>
+        </el-form>
+    </el-row>
     <el-row>
-        <el-table :data="pageData.records" height="650" highlight-current-row @current-change="handleCurrentChange">
+        <el-table :data="pageData.records" height="650" style="width: 100%;" highlight-current-row
+            @current-change="handleCurrentChange">
             <el-table-column prop="id" label="ID" width="50"></el-table-column>
-            <el-table-column prop="job_category" label="工作类型" width="200"></el-table-column>
-            <el-table-column prop="job_name" label="工作名称" width="200"></el-table-column>
+            <el-table-column prop="job_name" label="文件名称"></el-table-column>
+            <el-table-column prop="start_category" label="任务类型" width="200">
+                <template v-slot="scope">
+                    {{ formatJobCategory(scope.row.start_category) }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="job_category" label="当前工序" width="200">
+                <template v-slot="scope">
+                    {{ formatJobCategory(scope.row.job_category) }}
+                </template>
+            </el-table-column>
             <el-table-column prop="job_creator" label="创建人" width="100"></el-table-column>
             <el-table-column prop="status" label="状态" width="100">
                 <template v-slot="scope">
                     <el-tag :type="getJobStatusTag(scope.row.status)">{{ formatStatus(scope.row.status) }}</el-tag>
                 </template>
             </el-table-column>
-            <el-table-column label="操作" width="500">
+            <el-table-column label="操作">
                 <template v-slot="scope">
                     <el-menu mode="horizontal">
-
                         <el-sub-menu index="1">
                             <template #title>操作</template>
                             <el-menu-item index="1-1" @click="handleViewJob(scope.row)">查看</el-menu-item>
-                            <el-menu-item index="1-2" @click="handlePutJobIntoQueue(scope.row)">移入队列</el-menu-item>
+                            <!-- <el-menu-item index="1-2" @click="handlePutJobIntoQueue(scope.row)">移入队列</el-menu-item> -->
                             <el-menu-item index="1-3" @click="handleDeleteJob(scope.row)">删除</el-menu-item>
                         </el-sub-menu>
-                        <el-sub-menu index="4">
-                            <template #title>变更状态</template>
-                            <template v-for="item in jobStatusList">
-                                <el-menu-item v-if="item.could_be_set" index="4-{{item.id}}"
-                                    @click="handleJobSetStatus(scope.row, item.value)">{{ item.label }}</el-menu-item>
-                            </template>
-                        </el-sub-menu>
                     </el-menu>
                 </template>
             </el-table-column>
@@ -49,25 +92,28 @@
 
     </el-row>
     <el-row style="margin-top:30px;">
-        <el-pagination background layout="prev, pager, next" :total="pageData.total" :page-size="pageData.page_size"
-            @current-change="handleCurrentPageChange" :current-page.sync="pageData.page">
-        </el-pagination>
+        <div class="pagination">
+            <el-pagination background layout="prev, pager, next" :total="pageData.total" :page-size="pageData.page_size"
+                @current-change="handleCurrentPageChange" :current-page.sync="pageData.page">
+            </el-pagination>
+            <span style="margin-right: 20px;">{{ pageData.total }}条记录</span>
+        </div>
     </el-row>
     <el-drawer title="工作详情" v-model="showDrawer" :direction="showDrawerDirection" size="45%">
         <el-tabs type="border-card" v-model="jobDetailsActivateTab">
             <el-tab-pane label="基本信息" name="basic_info">
                 <div class="prop_header">任务类型</div>
-                <div>{{ currentJob.job_category }}</div>
+                <div>{{ currentJob?.start_category }}</div>
                 <div class="prop_header">任务名称</div>
-                <div>{{ currentJob.job_name }}</div>
+                <div>{{ currentJob?.job_name }}</div>
                 <div class="prop_header">创建人</div>
-                <div>{{ currentJob.job_creator }}</div>
+                <div>{{ currentJob?.job_creator }}</div>
                 <div class="prop_header">创建时间</div>
-                <div>{{ formatDate(currentJob.created) }}</div>
+                <div>{{ formatDate(currentJob?.created) }}</div>
                 <div class="prop_header">更新时间</div>
-                <div>{{ formatDate(currentJob.updated) }}</div>
+                <div>{{ formatDate(currentJob?.updated) }}</div>
                 <div class="prop_header">日志</div>
-                <div><el-input type="textarea" v-model="currentJob.job_logs" :rows="20"></el-input></div>
+                <div><el-input type="textarea" :rows="20">{{ currentJob?.job_logs }}</el-input></div>
             </el-tab-pane>
             <el-tab-pane label="文件夹" name="file_browse">
                 <el-row>
@@ -77,6 +123,9 @@
                     <el-col :span="20">
                         <el-table v-if="jobFileInDir.length > 0" :data="jobFileInDir" highlight-current-row
                             style="width: 100%;">
+                            <el-table-column prop="type" label="类型" width="50">
+
+                            </el-table-column>
                             <el-table-column prop="name" label="名称" width="250">
 
                             </el-table-column>
@@ -101,11 +150,12 @@
 
 <script setup lang="ts">
 
-import { ref, onMounted, watch } from 'vue'
-import { useRoute } from 'vue-router'
+import { ref, onMounted, watch, watchEffect } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
 import {
     getJob,
     getJobs,
+    getStartQueueJobs,
     getQueue,
     browseJobFile,
     getJobStatus,
@@ -114,9 +164,11 @@ import {
     deleteJob,
     updateJob,
     updateJobStatus,
+    searchJobs,
     getFileContent
 } from '@/api/AgentApi'
-import type { JobData, } from '@/api/AgentApi'
+import type { JobData, SearchData } from '@/api/AgentApi'
+
 import OCRDialog from '@/dialogs/OCRDialog.vue'
 import QueueSelectDialog from '@/dialogs/QueueSelectDialog.vue'
 import TextViewDialog from '@/dialogs/TextViewDialog.vue'
@@ -124,27 +176,42 @@ import { ElNotification, ElMessageBox } from 'element-plus'
 import { formatTime } from 'element-plus/es/components/countdown/src/utils.mjs'
 import { formatDate } from '@/utils/misc'
 import { getSessionVar, saveSessionVar } from '@/utils/session'
-//从路由参数中获取队列ID
 
-const sessionId = ref<any>("")
+const router = useRouter()
+//从路由参数中获取队列ID
+interface ViewJobData {
+    id: number,
+    job_category: string,
+    job_name: string,
+    job_creator: string,
+    created: string,
+    updated: string,
+    job_logs: string,
+    start_category: string,
+}
+const sessionId = ref("")
 const ocrDialog = ref()
+const pageTitle = ref("全部工作")
 const allowCreateJob = ref(false)
 const queueSelectDialog = ref()
 const textViewDialog = ref()
-const queueId = ref<any>("")
+//const queueId = ref("")
 const queueData = ref({ id: "0", queue_category: "", queue_name: "", title: "" })
 const showDrawer = ref(false)
 const showDrawerDirection = ref('rtl')
 const jobDetailsActivateTab = ref('basic_info')
 const queueIdNameDict = ref([
-    { id: "0", name: "DEFAULT", category: 'SYSTEM', title: "全部工作", dialog: null },
-    { id: "1", name: "OCR", category: 'SYSTEM', title: "文档OCR", dialog: ocrDialog },
-    { id: "2", name: "OCR_RESULTS", title: "OCR结果校对", category: 'SYSTEM', dialog: null },
-    { id: "3", name: "CHUNKS", title: "文本切片队列", category: 'SYSTEM', dialog: null },
-    { id: "4", name: "KB_EXTRACT", title: "知识抽取", category: 'SYSTEM', dialog: null },
-    { id: "5", name: "KB_BUILD", title: "图谱数据构建", category: 'SYSTEM', dialog: null },
-    { id: "6", name: "WORD", title: "WORD文档抽取", category: 'SYSTEM', dialog: ocrDialog },
+    { id: "0", name: "DEFAULT", category: 'SYSTEM', title: "全部工作", dialog: null, is_start: false },
+    { id: "1", name: "OCR", category: 'SYSTEM', title: "文档OCR", dialog: ocrDialog, is_start: true },
+    { id: "2", name: "OCR_RESULTS", title: "OCR结果校对", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "3", name: "CHUNKS", title: "文本切片队列", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "4", name: "KB_EXTRACT", title: "知识抽取", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "5", name: "KB_BUILD", title: "图谱数据构建", category: 'SYSTEM', dialog: null, is_start: false },
+    { id: "6", name: "WORD", title: "WORD文档抽取", category: 'SYSTEM', dialog: ocrDialog, is_start: true },
 ])
+const searchForm = ref<SearchData>({ job_name: "", start_category: "SYSTEM_ALL", status: 9999, job_category: "SYSTEM_ALL" })
+const jobStartCategoryList = ref([{ id: "SYSTEM_ALL", label: "所有类型" }, { id: "SYSTEM_WORD", label: "WORD文档抽取" }, { id: "SYSTEM_OCR", label: "PDF文档抽取" }])
+const jobCategoryList = ref([{ id: "SYSTEM_ALL", label: "所有工序" }, { id: "SYSTEM_CHUNKS", label: "文档切片" }, { id: "SYSTEM_KB_EXTRACT", label: "知识抽取" }, { id: "SYSTEM_KB_BUILD", label: "知识构建" }])
 var textViewContent = "hello"
 const textViewData = ref(textViewContent)
 const jobStatusList = ref(JobSatus)
@@ -155,24 +222,36 @@ const jobFileTree = ref({
 const pageData = ref({ page: 1, pages: 1, page_size: 10, total: 0, records: [] })
 const jobFileData = ref([])
 const jobFileInDir = ref([])
-const currentJob = ref<any>({ id: 0, job_category: "USER", job_name: "" })
+const currentJob = ref<ViewJobData>({ id: 0, job_category: "USER", job_name: "", job_creator: "", created: "", updated: "", job_logs: "no job logs", start_category: "" })
 const currentActionJob = ref({ id: 0, job_category: "USER", job_name: "" })
 
 const route = useRoute()
 
-watch(route, (newValue, oldValue) => {
-    let para = newValue.params.id as string | undefined
+// watchEffect(route, (newValue, oldValue) => {
+//     let para = newValue.params.id as string | undefined
+//     if (para == undefined) {
+//         return
+//     }
+//     console.log('route changed')
+//     //queueId.value = para
+//     queueData.value.id = "0"
+//     loadQueueData() 
+// })
+watchEffect(() => {
+    let para = route.params.id as string | undefined
     if (para == undefined) {
         return
     }
-    console.log('route changed')
-    queueId.value = para
+    // console.log('route changed')
+    //queueId.value = para
     queueData.value.id = "0"
+    // queueData.value.queue_category = "SYSTEM"
+    // queueData.value.queue_name = "ALL"
+    // queueData.value.title = "全部工作"
     loadQueueData()
 })
 
-
-const handleCreateJob = () => {
+const handleCreateJob = (category: string, name: string, dialog_title: string) => {
     if (queueData.value.id == "0") {
         ElNotification.info({
             title: '消息',
@@ -180,13 +259,15 @@ const handleCreateJob = () => {
         })
         return
     }
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    queueData.value.queue_category = category
+    queueData.value.queue_name = name
+    queueData.value.title = dialog_title
+    let queue_data = queueIdNameDict.value.find((item) => item.category == category && item.name == name)
     if (queue_data == undefined) {
         return
     }
-    // console.log(queue_data)
+    // console.log("HHHH")
     if (queue_data.dialog != undefined) {
-        // console.log(queue_data.dialog)
         queue_data.dialog.showDialog()
     }
 }
@@ -201,36 +282,71 @@ const handleViewFile = (job: any, path: string) => {
     })
 }
 const handleRefreshTable = () => {
-    loadQueueData()
+    handleCurrentPageChange(pageData.value.page)
 }
 function formatStatus(status: number) {
     return getJobStatus(status)
 }
-function formatFolderName(name: string) {
+function formatJobCategory(category: string) {
+    var title = ""
+    queueIdNameDict.value.forEach((item) => {
+        // console.log(category, 'category')
+        if ((item.category + "_" + item.name) == category) {
+            // console.log(item.title)
+            title = item.title
 
-    var translate: any = {
-        logs: "日志",
-        results: "结果", chunks: "切片", files: "文件",
-        ocr_output: "文本转换结果", kb_extract: "知识抽取", kb_build: "知识构建",
-        upload: '文件上传', output: '输出'
-    }
-    // console.log(translate[name])
-    if (name in translate) {
-        return translate[name] as string
+        }
+    })
+    return title
+}
+function formatFolderName(name: string) {
+    let translate = new Map([
+        ["logs", "日志"],
+        ["results", "结果"],
+        ["chunks", "切片"],
+        ["files", "文件"],
+        ["ocr_output", "文本转换结果"],
+        ["kb_extract", "知识抽取"],
+        ["kb_build", "知识构建"],
+        ["upload", "文件上传"],
+        ["output", "输出"]]
+    )
+    if (translate.get(name) != undefined) {
+        return translate.get(name)
     }
     return name
 }
+function resetViewData() {
+    jobFileData.value = []
+    jobFileInDir.value = []
+    currentJob.value = { id: 0, job_category: "USER", job_name: "", job_creator: "", created: "", updated: "", job_logs: "no job logs", start_category: "" }
+    searchForm.value = { job_name: "", start_category: "SYSTEM_ALL", status: 9999, job_category: "SYSTEM_ALL" }
+    pageData.value.page = 1
+    pageData.value.page_size = 10
+}
+function loadJobs(reset: boolean) {
+    // console.log("searchJobs")
+    if (reset) {
+        resetViewData()
+    }
+    searchJobs(searchForm.value, pageData.value.page, pageData.value.page_size).then((res) => {
+        pageData.value.page = res.meta.page
+        pageData.value.pages = res.meta.pages
+        pageData.value.total = res.meta.total
+        pageData.value.records = res.records
+    })
+}
 function loadQueueData() {
 
     jobFileData.value = []
     jobFileInDir.value = []
-    currentJob.value = { id: 0, job_category: "USER", job_name: "" }
+    //currentJob.value = {id:0,job_category:"USER",job_name:""}
     currentActionJob.value = { id: 0, job_category: "USER", job_name: "" }
     // 根据队列ID获取队列数据
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    let queue_data = queueIdNameDict.value.find((item) => item.category == queueData.value.queue_category && item.name == queueData.value.queue_name)
 
     if (queue_data == undefined) {
-        queueData.value = { id: "999", queue_category: 'SYSTEM', queue_name: 'UNKNOWN', title: "未知队列" }
+        queueData.value = { id: "999", queue_category: 'SYSTEM', queue_name: 'ALL', title: "未知队列" }
         return
     }
 
@@ -248,14 +364,10 @@ function loadQueueData() {
         queueData.value.queue_category = res.records[0].queue_category
         queueData.value.queue_name = res.records[0].queue_name
         queueData.value.title = queue_data.title
+        // console.log(queueData.value, ' queueData')
+        handleCurrentPageChange(1)
     })
-    getJobs(queue_data.category, queue_data.name, pageData.value.page, pageData.value.page_size).then((res) => {
-        pageData.value.page = res.meta.page
-        pageData.value.pages = res.meta.pages
-        pageData.value.total = res.meta.total
-        pageData.value.records = res.records
 
-    })
 }
 function jobMoved() {
     queueSelectDialog.value.showDialog(null, false)
@@ -269,7 +381,7 @@ function jobMoved() {
     })
 }
 function jobCreated() {
-    let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    let queue_data = queueIdNameDict.value.find((item) => item.category == queueData.value.queue_category && item.name == queueData.value.queue_name)
     if (queue_data == undefined) {
 
         ElNotification.info({
@@ -336,12 +448,28 @@ function handleTreeNodeClick(data: any) {
     }
 }
 function handleCurrentPageChange(page: number) {
-    getJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
-        pageData.value.page = res.meta.page
-        pageData.value.pages = res.meta.pages
-        pageData.value.total = res.meta.total
-        pageData.value.records = res.records
-    })
+    pageData.value.page = page
+    loadJobs(false)
+    // let queue_data = queueIdNameDict.value.find((item) => item.id == queueId.value)
+    // console.log(queue_data)
+    // if (queue_data == undefined) {
+    //     return
+    // }
+    // if (queue_data?.is_start == false) {
+    //     getJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
+    //         pageData.value.page = res.meta.page
+    //         pageData.value.pages = res.meta.pages
+    //         pageData.value.total = res.meta.total
+    //         pageData.value.records = res.records
+    //     }) 
+    // } else {
+    //     getStartQueueJobs(queueData.value.queue_category, queueData.value.queue_name, page).then((res) => {
+    //         pageData.value.page = res.meta.page
+    //         pageData.value.pages = res.meta.pages
+    //         pageData.value.total = res.meta.total 
+    //         pageData.value.records = res.records
+    //     })
+    // }
 }
 function handleJobMenuSelect(key: string, keyPath: string[]) {
     // console.log(key, keyPath)
@@ -350,21 +478,22 @@ function handleViewJob(job: JobData) {
     if (job == null) {
         return
     }
-    jobFileData.value = []
-    jobFileInDir.value = []
-    getJob(job.id).then((res) => {
-        currentJob.value = res.records[0]
-        showDrawer.value = true
-        browseJobFile({ job_id: job.id, path: "" }).then((res) => {
-            for (let i = 0; i < res.records.length; i++) {
-                res.records[i]['label'] = res.records[i].name
-                res.records[i]['children'] = []
-                res.records[i]['display_name'] = formatFolderName(res.records[i].name)
-            }
-            jobFileData.value = res.records
-            // console.log("show drawer")
-        })
-    })
+    router.push({ name: 'job-view', params: { id: job.id } })
+    // jobFileData.value = []
+    // jobFileInDir.value = []
+    // getJob(job.id).then((res) => {
+    //     currentJob.value = res.records[0]
+    //     showDrawer.value = true
+    //     browseJobFile({ job_id:job.id, path:""}).then((res) => {        
+    //         for (let i = 0; i < res.records.length; i++) {
+    //             res.records[i]['label'] = res.records[i].name 
+    //             res.records[i]['children'] = []
+    //             res.records[i]['display_name'] = formatFolderName(res.records[i].name)
+    //         }
+    //         jobFileData.value = res.records
+    //         console.log("show drawer")
+    //     })
+    // })
 }
 function handleCurrentChange(data: any) {
     if (data == null) {
@@ -373,10 +502,28 @@ function handleCurrentChange(data: any) {
 
     //currentJob.value = data
 }
+function handleSearch() {
+    pageData.value.page = 1
+    pageData.value.pages = 0
+    pageData.value.total = 0
+    pageData.value.records = []
+    pageTitle.value = "搜索结果"
+    loadJobs(false)
+}
+function handleSearchReset() {
+    pageData.value.page = 1
+    pageData.value.pages = 0
+    pageData.value.total = 0
+    pageData.value.records = []
+    resetViewData()
+    pageTitle.value = "全部工作"
+    loadJobs(false)
+}
 onMounted(() => {
-    sessionId.value = getSessionVar("session_id") as string | undefined
-    queueId.value = route.params.id as string | undefined
-    loadQueueData()
+    sessionId.value = getSessionVar("session_id") as string | ""
+
+    //loadQueueData()
+    loadJobs(true)
 })
 </script>
 
@@ -386,4 +533,25 @@ onMounted(() => {
     margin-top: 10px;
     margin-bottom: 5px;
 }
+
+.el-dropdown {
+    vertical-align: top;
+}
+
+.el-dropdown+.el-dropdown {
+    margin-left: 15px;
+}
+
+.el-icon-arrow-down {
+    font-size: 12px;
+}
+
+.pagination {
+    margin-left: auto;
+    display: flex;
+    /* text-align: right; */
+    justify-content: end;
+    align-items: center;
+    gap: 10px;
+}
 </style>

+ 10 - 1
vite.config.ts

@@ -5,6 +5,9 @@ import vue from '@vitejs/plugin-vue'
 import vueDevTools from 'vite-plugin-vue-devtools'
 import { loadEnv } from 'vite'
 import vueJsx from "@vitejs/plugin-vue-jsx";
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 
 export default defineConfig(({ command, mode }) => {
 
@@ -35,7 +38,13 @@ export default defineConfig(({ command, mode }) => {
     plugins: [
       vue(),
       vueJsx(),
-      //vueDevTools(),
+      vueDevTools(),
+      AutoImport({
+        resolvers: [ElementPlusResolver()],
+      }),
+      Components({
+        resolvers: [ElementPlusResolver()],
+      }),
     ],
     resolve: {
       alias: {