From 453237aef8b6c03294c55f03fcc6265987a10866 Mon Sep 17 00:00:00 2001 From: HYana Date: Wed, 26 Mar 2025 17:01:22 +0800 Subject: [PATCH 001/121] =?UTF-8?q?feat:=20webUI2.0=20=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E4=BB=8B=E9=9D=A2=E6=9B=B4=E6=96=B0=201.=20=E5=89=A9=E4=BD=99?= =?UTF-8?q?=E7=99=BB=E9=99=86=E6=B3=A8=E5=86=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= =?UTF-8?q?=202.=20=E5=89=A9=E4=BD=99=E6=8F=92=E4=BB=B6=E5=88=97=E8=A1=A8&?= =?UTF-8?q?=E5=B8=82=E5=9C=BA=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/.browserslistrc | 4 - web/.editorconfig | 5 - web/.eslintrc.js | 10 - web/.gitignore | 22 - web/README.md | 1 - web/index.html | 16 - web/jsconfig.json | 20 - web/package-lock.json | 6363 ----------------- web/package.json | 46 - web/public/favicon.ico | Bin 9662 -> 0 bytes web/src/App.vue | 329 - web/src/assets/langbot-logo-block.png | Bin 14953 -> 0 bytes web/src/assets/langbot-logo.png | Bin 24667 -> 0 bytes web/src/components/AboutDialog.vue | 90 - web/src/components/InitDialog.vue | 88 - web/src/components/LoginDialog.vue | 76 - web/src/components/MarketPluginCard.vue | 181 - web/src/components/Marketplace.vue | 228 - web/src/components/NumberFieldData.vue | 75 - web/src/components/PageTitle.vue | 51 - web/src/components/PluginCard.vue | 257 - web/src/components/SettingWindow.vue | 287 - web/src/components/TaskCard.vue | 144 - web/src/components/TaskListDialog.vue | 91 - web/src/main.js | 20 - web/src/pages/DashBoard.vue | 151 - web/src/pages/Logs.vue | 140 - web/src/pages/Plugins.vue | 325 - web/src/pages/Settings.vue | 89 - web/src/plugins/index.js | 31 - web/src/plugins/vuetify.js | 19 - web/src/router/index.js | 46 - web/src/store/index.js | 53 - web/src/styles/settings.scss | 10 - web/vite.config.mjs | 64 - web_ui/.gitignore | 41 + web_ui/README.md | 36 + web_ui/eslint.config.mjs | 16 + web_ui/next.config.ts | 7 + web_ui/package-lock.json | 5833 +++++++++++++++ web_ui/package.json | 28 + web_ui/public/file.svg | 1 + web_ui/public/globe.svg | 1 + web_ui/public/next.svg | 1 + web_ui/public/vercel.svg | 1 + web_ui/public/window.svg | 1 + web_ui/src/app/favicon.ico | Bin 0 -> 25931 bytes web_ui/src/app/global.css | 37 + .../app/home/bot-config/ICreateBotField.ts | 3 + .../app/home/bot-config/botConfig.module.css | 23 + .../components/bot-card/BotCard.tsx | 39 + .../components/bot-card/BotCardVO.ts | 28 + .../bot-card/CreateBotCardComponent.tsx | 0 .../components/bot-card/botCard.module.css | 67 + .../components/bot-form/BotForm.tsx | 222 + .../components/bot-form/BotFormEntity.ts | 20 + .../bot-form/ChooseAdapterEntity.ts | 4 + web_ui/src/app/home/bot-config/page.tsx | 150 + .../dynamic-form/DynamicFormComponent.tsx | 30 + .../dynamic-form/DynamicFormItemComponent.tsx | 38 + .../dynamic-form/DynamicFormItemConfig.ts | 51 + .../dynamic-form/testDynamicConfigList.ts | 41 + .../EmptyAndCreateComponent.tsx | 35 + .../emptyAndCreate.module.css | 54 + .../home-sidebar/HomeSidebar.module.css | 67 + .../components/home-sidebar/HomeSidebar.tsx | 107 + .../home-sidebar/HomeSidebarChild.tsx | 41 + .../home-sidebar/sidbarConfigList.ts | 28 + .../components/home-titlebar/HomeTitleBar.tsx | 16 + .../home-titlebar/HomeTittleBar.module.css | 15 + web_ui/src/app/home/layout.module.css | 18 + web_ui/src/app/home/layout.tsx | 36 + .../app/home/llm-config/ICreateLLMField.ts | 8 + .../app/home/llm-config/LLMConfig.module.css | 90 + .../llm-config/component/llm-card/LLMCard.tsx | 39 + .../component/llm-card/LLMCardVO.ts | 27 + .../llm-config/component/llm-form/LLMForm.tsx | 177 + web_ui/src/app/home/llm-config/page.tsx | 126 + web_ui/src/app/home/mock-api/index.ts | 1374 ++++ web_ui/src/app/home/page.tsx | 6 + .../pipeline-form/PipelineChildFormEntity.ts | 20 + .../pipeline-form/PipelineFormComponent.tsx | 512 ++ .../pipelineFormStyle.module.css | 12 + web_ui/src/app/home/pipeline-config/page.tsx | 30 + .../pipeline-config/pipelineConfig.module.css | 4 + web_ui/src/app/home/plugin-config/page.tsx | 7 + web_ui/src/app/infra/api/api-types/index.ts | 11 + .../pipelines/GetMetaDataResponse.ts | 47 + .../CreateCardComponent.tsx | 27 + .../createCartComponent.module.css | 16 + web_ui/src/app/infra/basic-types/I18N.ts | 4 + web_ui/src/app/layout.tsx | 22 + web_ui/src/app/login/layout.tsx | 13 + web_ui/src/app/login/page.tsx | 7 + web_ui/src/app/not-found.tsx | 8 + web_ui/src/app/page.tsx | 7 + web_ui/tsconfig.json | 27 + 97 files changed, 9757 insertions(+), 9332 deletions(-) delete mode 100644 web/.browserslistrc delete mode 100644 web/.editorconfig delete mode 100644 web/.eslintrc.js delete mode 100644 web/.gitignore delete mode 100644 web/README.md delete mode 100644 web/index.html delete mode 100644 web/jsconfig.json delete mode 100644 web/package-lock.json delete mode 100644 web/package.json delete mode 100644 web/public/favicon.ico delete mode 100644 web/src/App.vue delete mode 100644 web/src/assets/langbot-logo-block.png delete mode 100644 web/src/assets/langbot-logo.png delete mode 100644 web/src/components/AboutDialog.vue delete mode 100644 web/src/components/InitDialog.vue delete mode 100644 web/src/components/LoginDialog.vue delete mode 100644 web/src/components/MarketPluginCard.vue delete mode 100644 web/src/components/Marketplace.vue delete mode 100644 web/src/components/NumberFieldData.vue delete mode 100644 web/src/components/PageTitle.vue delete mode 100644 web/src/components/PluginCard.vue delete mode 100644 web/src/components/SettingWindow.vue delete mode 100644 web/src/components/TaskCard.vue delete mode 100644 web/src/components/TaskListDialog.vue delete mode 100644 web/src/main.js delete mode 100644 web/src/pages/DashBoard.vue delete mode 100644 web/src/pages/Logs.vue delete mode 100644 web/src/pages/Plugins.vue delete mode 100644 web/src/pages/Settings.vue delete mode 100644 web/src/plugins/index.js delete mode 100644 web/src/plugins/vuetify.js delete mode 100644 web/src/router/index.js delete mode 100644 web/src/store/index.js delete mode 100644 web/src/styles/settings.scss delete mode 100644 web/vite.config.mjs create mode 100644 web_ui/.gitignore create mode 100644 web_ui/README.md create mode 100644 web_ui/eslint.config.mjs create mode 100644 web_ui/next.config.ts create mode 100644 web_ui/package-lock.json create mode 100644 web_ui/package.json create mode 100644 web_ui/public/file.svg create mode 100644 web_ui/public/globe.svg create mode 100644 web_ui/public/next.svg create mode 100644 web_ui/public/vercel.svg create mode 100644 web_ui/public/window.svg create mode 100644 web_ui/src/app/favicon.ico create mode 100644 web_ui/src/app/global.css create mode 100644 web_ui/src/app/home/bot-config/ICreateBotField.ts create mode 100644 web_ui/src/app/home/bot-config/botConfig.module.css create mode 100644 web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx create mode 100644 web_ui/src/app/home/bot-config/components/bot-card/BotCardVO.ts create mode 100644 web_ui/src/app/home/bot-config/components/bot-card/CreateBotCardComponent.tsx create mode 100644 web_ui/src/app/home/bot-config/components/bot-card/botCard.module.css create mode 100644 web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx create mode 100644 web_ui/src/app/home/bot-config/components/bot-form/BotFormEntity.ts create mode 100644 web_ui/src/app/home/bot-config/components/bot-form/ChooseAdapterEntity.ts create mode 100644 web_ui/src/app/home/bot-config/page.tsx create mode 100644 web_ui/src/app/home/components/dynamic-form/DynamicFormComponent.tsx create mode 100644 web_ui/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx create mode 100644 web_ui/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts create mode 100644 web_ui/src/app/home/components/dynamic-form/testDynamicConfigList.ts create mode 100644 web_ui/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx create mode 100644 web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css create mode 100644 web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css create mode 100644 web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx create mode 100644 web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx create mode 100644 web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts create mode 100644 web_ui/src/app/home/components/home-titlebar/HomeTitleBar.tsx create mode 100644 web_ui/src/app/home/components/home-titlebar/HomeTittleBar.module.css create mode 100644 web_ui/src/app/home/layout.module.css create mode 100644 web_ui/src/app/home/layout.tsx create mode 100644 web_ui/src/app/home/llm-config/ICreateLLMField.ts create mode 100644 web_ui/src/app/home/llm-config/LLMConfig.module.css create mode 100644 web_ui/src/app/home/llm-config/component/llm-card/LLMCard.tsx create mode 100644 web_ui/src/app/home/llm-config/component/llm-card/LLMCardVO.ts create mode 100644 web_ui/src/app/home/llm-config/component/llm-form/LLMForm.tsx create mode 100644 web_ui/src/app/home/llm-config/page.tsx create mode 100644 web_ui/src/app/home/mock-api/index.ts create mode 100644 web_ui/src/app/home/page.tsx create mode 100644 web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineChildFormEntity.ts create mode 100644 web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx create mode 100644 web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css create mode 100644 web_ui/src/app/home/pipeline-config/page.tsx create mode 100644 web_ui/src/app/home/pipeline-config/pipelineConfig.module.css create mode 100644 web_ui/src/app/home/plugin-config/page.tsx create mode 100644 web_ui/src/app/infra/api/api-types/index.ts create mode 100644 web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts create mode 100644 web_ui/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx create mode 100644 web_ui/src/app/infra/basic-component/create-card-component/createCartComponent.module.css create mode 100644 web_ui/src/app/infra/basic-types/I18N.ts create mode 100644 web_ui/src/app/layout.tsx create mode 100644 web_ui/src/app/login/layout.tsx create mode 100644 web_ui/src/app/login/page.tsx create mode 100644 web_ui/src/app/not-found.tsx create mode 100644 web_ui/src/app/page.tsx create mode 100644 web_ui/tsconfig.json diff --git a/web/.browserslistrc b/web/.browserslistrc deleted file mode 100644 index dc3bc09a..00000000 --- a/web/.browserslistrc +++ /dev/null @@ -1,4 +0,0 @@ -> 1% -last 2 versions -not dead -not ie 11 diff --git a/web/.editorconfig b/web/.editorconfig deleted file mode 100644 index 7053c49a..00000000 --- a/web/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -[*.{js,jsx,ts,tsx,vue}] -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true -insert_final_newline = true diff --git a/web/.eslintrc.js b/web/.eslintrc.js deleted file mode 100644 index 6e7e1b14..00000000 --- a/web/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - root: true, - env: { - node: true, - }, - extends: [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - ], -} diff --git a/web/.gitignore b/web/.gitignore deleted file mode 100644 index 11f5d714..00000000 --- a/web/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -.DS_Store -node_modules -/dist - -# local env files -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/web/README.md b/web/README.md deleted file mode 100644 index 280a55a2..00000000 --- a/web/README.md +++ /dev/null @@ -1 +0,0 @@ -# WebUI diff --git a/web/index.html b/web/index.html deleted file mode 100644 index 150c3979..00000000 --- a/web/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - LangBot 面板 - - - -
- - - - diff --git a/web/jsconfig.json b/web/jsconfig.json deleted file mode 100644 index dad0634c..00000000 --- a/web/jsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "target": "es5", - "module": "esnext", - "baseUrl": "./", - "moduleResolution": "bundler", - "paths": { - "@/*": [ - "src/*" - ] - }, - "lib": [ - "esnext", - "dom", - "dom.iterable", - "scripthost" - ] - } -} diff --git a/web/package-lock.json b/web/package-lock.json deleted file mode 100644 index 331b2815..00000000 --- a/web/package-lock.json +++ /dev/null @@ -1,6363 +0,0 @@ -{ - "name": "web", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "web", - "version": "0.0.0", - "dependencies": { - "@koumoul/vjsf": "^3.0.0-beta.46", - "@mdi/font": "7.4.47", - "ajv": "^8.17.1", - "ajv-dist": "^8.17.1", - "ajv-errors": "^3.0.0", - "ajv-formats": "^3.0.1", - "ajv-i18n": "^4.2.0", - "ansi_up": "^6.0.2", - "axios": "^1.7.7", - "codemirror": "^5.65.18", - "core-js": "^3.37.1", - "json-editor-vue": "^0.17.3", - "roboto-fontface": "*", - "vue": "^3.4.31", - "vuedraggable": "^4.1.0", - "vuetify": "^3.6.11", - "vuex": "^4.0.2" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.0.5", - "eslint": "^8.57.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.4.0", - "eslint-plugin-vue": "^9.27.0", - "sass": "1.77.6", - "unplugin-fonts": "^1.1.1", - "unplugin-vue-components": "^0.27.2", - "unplugin-vue-router": "^0.10.0", - "vite": "^5.3.3", - "vite-plugin-vuetify": "^2.0.3", - "vue-router": "^4.4.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@antfu/utils": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", - "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.6" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@codemirror/autocomplete": { - "version": "6.18.2", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.2.tgz", - "integrity": "sha512-wJGylKtMFR/Ds6Gh01+OovXE/pncPiKZNNBKuC39pKnH+XK5d9+WsNqcrdxPjFPFTigRBqse0rfxw9UxrfyhPg==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0" - }, - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/commands": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", - "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" - } - }, - "node_modules/@codemirror/lang-json": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", - "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/json": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz", - "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/lint": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz", - "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/search": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.7.tgz", - "integrity": "sha512-6+iLsXvITWKHYlkgHPCs/qiX4dNzn8N78YfhOFvPtPYCkuXqZq10rAfsUMhOq7O/1VjJqdXRflyExlfVcu/9VQ==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/state": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", - "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==", - "license": "MIT" - }, - "node_modules/@codemirror/view": { - "version": "6.34.2", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.2.tgz", - "integrity": "sha512-d6n0WFvL970A9Z+l9N2dO+Hk9ev4hDYQzIx+B9tCyBP0W5wPEszi1rhuyFesNSkLZzXbQE5FPH7F/z/TMJfoPA==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.4.0", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", - "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", - "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", - "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsep-plugin/assignment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", - "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@jsep-plugin/regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", - "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@json-layout/core": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/@json-layout/core/-/core-0.32.1.tgz", - "integrity": "sha512-/x+D8epj48MKPlDTKE7lgwjd6ThFF99+fZwxwuActV3XAFwE55bu0sK45i9yDoTkgEHHWCDlxlOdDkJP0hRQ/g==", - "license": "MIT", - "dependencies": { - "@json-layout/vocabulary": "^0.23.2", - "@types/markdown-it": "^13.0.1", - "ajv": "^8.12.0", - "ajv-errors": "^3.0.0", - "ajv-formats": "^2.1.1", - "ajv-i18n": "^4.2.0", - "debug": "^4.3.4", - "immer": "^10.0.3", - "magicast": "^0.3.3", - "markdown-it": "^13.0.2" - } - }, - "node_modules/@json-layout/core/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@json-layout/vocabulary": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/@json-layout/vocabulary/-/vocabulary-0.23.2.tgz", - "integrity": "sha512-CDQ/nFZmcMdhn0Ud/f5Q3IoRemQQdw2CPm5pufRqo27T71JhIw12KluVW1jsZWlwK3v7q7yqOoVS8Ax9bUOQ4w==", - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "ajv-errors": "^3.0.0", - "ajv-formats": "^2.1.1", - "debug": "^4.3.4" - } - }, - "node_modules/@json-layout/vocabulary/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@jsonquerylang/jsonquery": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jsonquerylang/jsonquery/-/jsonquery-3.1.1.tgz", - "integrity": "sha512-P6Qo5egd3W8TBpqQsqaZtZ9lPO7oXBM21QdkYamCAYZHv9VCPXiI8NeIuSoXdoe5zKVZPUWmqaI14uacJLmcNw==", - "license": "ISC", - "bin": { - "jsonquery": "bin/cli.js" - } - }, - "node_modules/@koumoul/vjsf": { - "version": "3.0.0-beta.46", - "resolved": "https://registry.npmjs.org/@koumoul/vjsf/-/vjsf-3.0.0-beta.46.tgz", - "integrity": "sha512-dp9EuyZrZNRHb5+8eLMHWNEI9HPhpLoeOWzDWj43tE+162mGmhi42SqVMS5fbx73ZHF2FHe4jZszgdj0MiYB2A==", - "license": "MIT", - "dependencies": { - "@json-layout/core": "0.32.1", - "@vueuse/core": "^10.5.0", - "debug": "^4.3.4", - "ejs": "^3.1.9" - }, - "peerDependencies": { - "vue": "^3.4.3", - "vuetify": "^3.6.13" - } - }, - "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", - "license": "MIT" - }, - "node_modules/@lezer/highlight": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", - "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", - "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", - "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@mdi/font": { - "version": "7.4.47", - "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", - "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==", - "license": "Apache-2.0" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@parcel/watcher": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", - "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.0", - "@parcel/watcher-darwin-arm64": "2.5.0", - "@parcel/watcher-darwin-x64": "2.5.0", - "@parcel/watcher-freebsd-x64": "2.5.0", - "@parcel/watcher-linux-arm-glibc": "2.5.0", - "@parcel/watcher-linux-arm-musl": "2.5.0", - "@parcel/watcher-linux-arm64-glibc": "2.5.0", - "@parcel/watcher-linux-arm64-musl": "2.5.0", - "@parcel/watcher-linux-x64-glibc": "2.5.0", - "@parcel/watcher-linux-x64-musl": "2.5.0", - "@parcel/watcher-win32-arm64": "2.5.0", - "@parcel/watcher-win32-ia32": "2.5.0", - "@parcel/watcher-win32-x64": "2.5.0" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", - "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", - "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", - "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", - "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", - "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", - "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", - "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", - "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", - "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", - "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", - "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", - "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", - "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@replit/codemirror-indentation-markers": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/@replit/codemirror-indentation-markers/-/codemirror-indentation-markers-6.5.3.tgz", - "integrity": "sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==", - "license": "MIT", - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", - "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", - "integrity": "sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.5.tgz", - "integrity": "sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.5.tgz", - "integrity": "sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.5.tgz", - "integrity": "sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.5.tgz", - "integrity": "sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.5.tgz", - "integrity": "sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.5.tgz", - "integrity": "sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.5.tgz", - "integrity": "sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.5.tgz", - "integrity": "sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.5.tgz", - "integrity": "sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.5.tgz", - "integrity": "sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.5.tgz", - "integrity": "sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.5.tgz", - "integrity": "sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.5.tgz", - "integrity": "sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.5.tgz", - "integrity": "sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.5.tgz", - "integrity": "sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sphinxxxx/color-conversion": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz", - "integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw==", - "license": "ISC" - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "license": "MIT" - }, - "node_modules/@types/markdown-it": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.9.tgz", - "integrity": "sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==", - "license": "MIT", - "dependencies": { - "@types/linkify-it": "^3", - "@types/mdurl": "^1" - } - }, - "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "license": "MIT" - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", - "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue-macros/common": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.14.0.tgz", - "integrity": "sha512-xwQhDoEXRNXobNQmdqOD20yUGdVLVLZe4zhDlT9q/E+z+mvT3wukaAoJG80XRnv/BcgOOCVpxqpkQZ3sNTgjWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.6", - "@rollup/pluginutils": "^5.1.0", - "@vue/compiler-sfc": "^3.5.4", - "ast-kit": "^1.1.0", - "local-pkg": "^0.5.0", - "magic-string-ast": "^0.6.2" - }, - "engines": { - "node": ">=16.14.0" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.2.25" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.10.tgz", - "integrity": "sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.10", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.10.tgz", - "integrity": "sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.10", - "@vue/shared": "3.5.10" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.10.tgz", - "integrity": "sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.10", - "@vue/compiler-dom": "3.5.10", - "@vue/compiler-ssr": "3.5.10", - "@vue/shared": "3.5.10", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.47", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.10.tgz", - "integrity": "sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.10", - "@vue/shared": "3.5.10" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/@vue/reactivity": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.10.tgz", - "integrity": "sha512-kW08v06F6xPSHhid9DJ9YjOGmwNDOsJJQk0ax21wKaUYzzuJGEuoKNU2Ujux8FLMrP7CFJJKsHhXN9l2WOVi2g==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.10" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.10.tgz", - "integrity": "sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.10", - "@vue/shared": "3.5.10" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.10.tgz", - "integrity": "sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.10", - "@vue/runtime-core": "3.5.10", - "@vue/shared": "3.5.10", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.10.tgz", - "integrity": "sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.10", - "@vue/shared": "3.5.10" - }, - "peerDependencies": { - "vue": "3.5.10" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.10.tgz", - "integrity": "sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==", - "license": "MIT" - }, - "node_modules/@vuetify/loader-shared": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.0.3.tgz", - "integrity": "sha512-Ss3GC7eJYkp2SF6xVzsT7FAruEmdihmn4OCk2+UocREerlXKWgOKKzTN5PN3ZVN5q05jHHrsNhTuWbhN61Bpdg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "upath": "^2.0.1" - }, - "peerDependencies": { - "vue": "^3.0.0", - "vuetify": "^3.0.0" - } - }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-typescript": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", - "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", - "license": "MIT", - "peerDependencies": { - "acorn": ">=8.9.0" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-dist": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv-dist/-/ajv-dist-8.17.1.tgz", - "integrity": "sha512-KzJwANMzTTR/RERGnkx+bHzmxIfMTPMMv7+cH1d6Lx9UQ7BZyhiieq4hnO5lRuBWOtYTUL8hyWs7RJYI/45Rtg==", - "license": "MIT" - }, - "node_modules/ajv-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", - "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.0.1" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-i18n": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-4.2.0.tgz", - "integrity": "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.0.0-beta.0" - } - }, - "node_modules/ansi_up": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.2.tgz", - "integrity": "sha512-3G3vKvl1ilEp7J1u6BmULpMA0xVoW/f4Ekqhl8RTrJrhEBkonKn5k3bUc5Xt+qDayA6iDX0jyUh3AbZjB/l0tw==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ast-kit": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-1.2.1.tgz", - "integrity": "sha512-h31wotR7rkFLrlmGPn0kGqOZ/n5EQFvp7dBs400chpHDhHc8BK3gpvyHDluRujuGgeoTAv3dSIMz9BI3JxAWyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.6", - "pathe": "^1.1.2" - }, - "engines": { - "node": ">=16.14.0" - } - }, - "node_modules/ast-walker-scope": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.6.2.tgz", - "integrity": "sha512-1UWOyC50xI3QZkRuDj6PqDtpm1oHWtYs+NQGwqL/2R11eN3Q81PHAHPM0SWW3BNQm53UDwS//Jv8L4CCVLM1bQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.3", - "ast-kit": "^1.0.1" - }, - "engines": { - "node": ">=16.14.0" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/builtins": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", - "integrity": "sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/builtins/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "devOptional": 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/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/codemirror": { - "version": "5.65.18", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.18.tgz", - "integrity": "sha512-Gaz4gHnkbHMGgahNt3CA5HBk5lLQBqmD/pBgeB4kQU6OedZmqMBjlRF0LSrp2tJ4wlLNPm2FfaUd1pDy0mdlpA==", - "license": "MIT" - }, - "node_modules/codemirror-wrapped-line-indent": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/codemirror-wrapped-line-indent/-/codemirror-wrapped-line-indent-1.0.8.tgz", - "integrity": "sha512-5UwuHCz4oAZuvot1DbfFxSxJacTESdNGa/KpJD7HfpVpDAJdgB1vV9OG4b4pkJqPWuOfIpFLTQEKS85kTpV+XA==", - "license": "MIT", - "peerDependencies": { - "@codemirror/language": "^6.9.0", - "@codemirror/state": "^6.2.1", - "@codemirror/view": "^6.17.1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", - "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-js": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", - "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/eslint-compat-utils/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-config-standard": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", - "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "eslint": "^8.0.1", - "eslint-plugin-import": "^2.25.2", - "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", - "eslint-plugin-promise": "^6.0.0" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-es-x": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/ota-meshi", - "https://opencollective.com/eslint" - ], - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.11.0", - "eslint-compat-utils": "^0.5.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": ">=8" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", - "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.9.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-n": { - "version": "16.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz", - "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "builtins": "^5.0.1", - "eslint-plugin-es-x": "^7.5.0", - "get-tsconfig": "^4.7.0", - "globals": "^13.24.0", - "ignore": "^5.2.4", - "is-builtin-module": "^3.2.1", - "is-core-module": "^2.12.1", - "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.3" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", - "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-vue": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz", - "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "globals": "^13.24.0", - "natural-compare": "^1.4.0", - "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.15", - "semver": "^7.6.3", - "vue-eslint-parser": "^9.4.3", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/esm-env": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.1.4.tgz", - "integrity": "sha512-oO82nKPHKkzIj/hbtuDYy/JHqBHFlMIW36SDiPCVsj87ntDLcWN+sJ1erdVryd4NxODacFTsdrIE3b7IamqbOg==", - "license": "MIT" - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrap": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", - "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/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/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", - "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==", - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "license": "MIT" - }, - "node_modules/immutable-json-patch": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/immutable-json-patch/-/immutable-json-patch-6.0.1.tgz", - "integrity": "sha512-BHL/cXMjwFZlTOffiWNdY8ZTvNyYLrutCnWxrcKPHr5FqpAb6vsO6WWSPnVSys3+DruFN6lhHJJPHi8uELQL5g==", - "license": "ISC" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "license": "Apache-2.0", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsep": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", - "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-editor-vue": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/json-editor-vue/-/json-editor-vue-0.17.3.tgz", - "integrity": "sha512-MVpD3TInIlruq9ye/J3XmYHTH+pqfyW0E1GVUV2ug5M0X/19zGslJ+FgeikDflvTVUxhVuCEnc9spMYmPj5Lyw==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "vanilla-jsoneditor": "^2.0.0", - "vue-demi": "^0.14.10" - }, - "peerDependencies": { - "@vue/composition-api": ">=1", - "vue": "2||3" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/json-editor-vue/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz", - "integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsonpath-plus": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.2.0.tgz", - "integrity": "sha512-T9V+8iNYKFL2n2rF+w02LBOT2JjDnTjioaNFrxRy0Bv1y/hNsqR/EBK7Ojy2ythRHwmz2cRIls+9JitQGZC/sw==", - "license": "MIT", - "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/jsonrepair": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.10.0.tgz", - "integrity": "sha512-0Ex64Exiw0rPUEcSbhPN0ae4/5D0DZLIob9yagAF1OG5iU0mP+/t7q4gcxtQdn6i7FuQy2J/w1XbOdu/uhGV0w==", - "license": "ISC", - "bin": { - "jsonrepair": "bin/cli.js" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", - "license": "MIT", - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/local-pkg": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", - "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.4.2", - "pkg-types": "^1.0.3" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/magic-string-ast": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-0.6.2.tgz", - "integrity": "sha512-oN3Bcd7ZVt+0VGEs7402qR/tjgjbM7kPlH/z7ufJnzTLVBzXJITRHOJiwMmmYMgZfdoWQsfQcY+iKlxiBppnMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.10" - }, - "engines": { - "node": ">=16.14.0" - } - }, - "node_modules/magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "node_modules/markdown-it": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz", - "integrity": "sha512-FtwnEuuK+2yVU7goGn/MJ0WBZMM9ZPgU9spqlFs7/A/pDIUNSOQZhUgOqYCficIuR2QaFnrt8LHqBWsbTAoI5w==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "~3.0.1", - "linkify-it": "^4.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "license": "MIT" - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mlly": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", - "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.3", - "pathe": "^1.1.2", - "pkg-types": "^1.1.1", - "ufo": "^1.5.3" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "license": "MIT" - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT", - "optional": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", - "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.7", - "mlly": "^1.7.1", - "pathe": "^1.1.2" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/roboto-fontface": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz", - "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==", - "license": "Apache-2.0" - }, - "node_modules/rollup": { - "version": "4.22.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.5.tgz", - "integrity": "sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.5", - "@rollup/rollup-android-arm64": "4.22.5", - "@rollup/rollup-darwin-arm64": "4.22.5", - "@rollup/rollup-darwin-x64": "4.22.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.5", - "@rollup/rollup-linux-arm-musleabihf": "4.22.5", - "@rollup/rollup-linux-arm64-gnu": "4.22.5", - "@rollup/rollup-linux-arm64-musl": "4.22.5", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.5", - "@rollup/rollup-linux-riscv64-gnu": "4.22.5", - "@rollup/rollup-linux-s390x-gnu": "4.22.5", - "@rollup/rollup-linux-x64-gnu": "4.22.5", - "@rollup/rollup-linux-x64-musl": "4.22.5", - "@rollup/rollup-win32-arm64-msvc": "4.22.5", - "@rollup/rollup-win32-ia32-msvc": "4.22.5", - "@rollup/rollup-win32-x64-msvc": "4.22.5", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/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", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sortablejs": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", - "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==", - "license": "MIT" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-mod": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", - "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svelte": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.1.13.tgz", - "integrity": "sha512-xVNk8yLsZNfkyqWzVg8+nfU9ewiSjVW0S4qyTxfKa6Y7P5ZBhA+LDsh2cHWIXJQMltikQAk6W3sqGdQZSH58PA==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "acorn-typescript": "^1.4.13", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "esm-env": "^1.0.0", - "esrap": "^1.2.2", - "is-reference": "^3.0.2", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "license": "MIT" - }, - "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unplugin": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz", - "integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.12.1", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "webpack-sources": "^3" - }, - "peerDependenciesMeta": { - "webpack-sources": { - "optional": true - } - } - }, - "node_modules/unplugin-fonts": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.1.1.tgz", - "integrity": "sha512-/Aw/rL9D2aslGGM0vi+2R2aG508RSwawLnnBuo+JDSqYc4cHJO1R1phllhN6GysEhBp/6a4B6+vSFPVapWyAAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.12", - "unplugin": "^1.3.1" - }, - "peerDependencies": { - "@nuxt/kit": "^3.0.0", - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/unplugin-vue-components": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz", - "integrity": "sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.0", - "chokidar": "^3.6.0", - "debug": "^4.3.6", - "fast-glob": "^3.3.2", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.11", - "minimatch": "^9.0.5", - "mlly": "^1.7.1", - "unplugin": "^1.12.1" - }, - "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/unplugin-vue-components/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/unplugin-vue-components/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/unplugin-vue-router": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.10.8.tgz", - "integrity": "sha512-xi+eLweYAqolIoTRSmumbi6Yx0z5M0PLvl+NFNVWHJgmE2ByJG1SZbrn+TqyuDtIyln20KKgq8tqmL7aLoiFjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.4", - "@rollup/pluginutils": "^5.1.0", - "@vue-macros/common": "^1.12.2", - "ast-walker-scope": "^0.6.2", - "chokidar": "^3.6.0", - "fast-glob": "^3.3.2", - "json5": "^2.2.3", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.11", - "mlly": "^1.7.1", - "pathe": "^1.1.2", - "scule": "^1.3.0", - "unplugin": "^1.12.2", - "yaml": "^2.5.0" - }, - "peerDependencies": { - "vue-router": "^4.4.0" - }, - "peerDependenciesMeta": { - "vue-router": { - "optional": true - } - } - }, - "node_modules/unplugin-vue-router/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/upath": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", - "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vanilla-jsoneditor": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-2.0.2.tgz", - "integrity": "sha512-/qsSp2B/sQsyW7SO8wq7vvEeq4Wqs5JT1j5SSfqlID7CPB9S95+vhzUjrKMpUk0YtxsySlxDecAcXK5Lzg22Sw==", - "license": "ISC", - "dependencies": { - "@codemirror/autocomplete": "^6.18.1", - "@codemirror/commands": "^6.7.1", - "@codemirror/lang-json": "^6.0.1", - "@codemirror/language": "^6.10.3", - "@codemirror/lint": "^6.8.2", - "@codemirror/search": "^6.5.6", - "@codemirror/state": "^6.4.1", - "@codemirror/view": "^6.34.1", - "@fortawesome/free-regular-svg-icons": "^6.6.0", - "@fortawesome/free-solid-svg-icons": "^6.6.0", - "@jsonquerylang/jsonquery": "^3.1.1", - "@lezer/highlight": "^1.2.1", - "@replit/codemirror-indentation-markers": "^6.5.3", - "ajv": "^8.17.1", - "codemirror-wrapped-line-indent": "^1.0.8", - "diff-sequences": "^29.6.3", - "immutable-json-patch": "^6.0.1", - "jmespath": "^0.16.0", - "json-source-map": "^0.6.1", - "jsonpath-plus": "^9.0.0 || ^10.1.0", - "jsonrepair": "^3.0.0", - "lodash-es": "^4.17.21", - "memoize-one": "^6.0.0", - "natural-compare-lite": "^1.4.0", - "sass": "^1.80.4", - "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0", - "vanilla-picker": "^2.12.3" - } - }, - "node_modules/vanilla-jsoneditor/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/vanilla-jsoneditor/node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", - "license": "MIT", - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/vanilla-jsoneditor/node_modules/sass": { - "version": "1.80.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz", - "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==", - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/vanilla-picker": { - "version": "2.12.3", - "resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.12.3.tgz", - "integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==", - "license": "ISC", - "dependencies": { - "@sphinxxxx/color-conversion": "^2.2.2" - } - }, - "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-vuetify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.0.4.tgz", - "integrity": "sha512-A4cliYUoP/u4AWSRVRvAPKgpgR987Pss7LpFa7s1GvOe8WjgDq92Rt3eVXrvgxGCWvZsPKziVqfHHdCMqeDhfw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vuetify/loader-shared": "^2.0.3", - "debug": "^4.3.3", - "upath": "^2.0.1" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": ">=5", - "vue": "^3.0.0", - "vuetify": "^3.0.0" - } - }, - "node_modules/vue": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.10.tgz", - "integrity": "sha512-Vy2kmJwHPlouC/tSnIgXVg03SG+9wSqT1xu1Vehc+ChsXsRd7jLkKgMltVEFOzUdBr3uFwBCG+41LJtfAcBRng==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.10", - "@vue/compiler-sfc": "3.5.10", - "@vue/runtime-dom": "3.5.10", - "@vue/server-renderer": "3.5.10", - "@vue/shared": "3.5.10" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/vue-router": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", - "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/vuedraggable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", - "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", - "license": "MIT", - "dependencies": { - "sortablejs": "1.14.0" - }, - "peerDependencies": { - "vue": "^3.0.1" - } - }, - "node_modules/vuetify": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.2.tgz", - "integrity": "sha512-q0WTcRG977+a9Dqhb8TOaPm+Xmvj0oVhnBJhAdHWFSov3HhHTTxlH2nXP/GBTXZuuMHDbBeIWFuUR2/1Fx0PPw==", - "license": "MIT", - "engines": { - "node": "^12.20 || >=14.13" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/johnleider" - }, - "peerDependencies": { - "typescript": ">=4.7", - "vite-plugin-vuetify": ">=1.0.0", - "vue": "^3.3.0", - "webpack-plugin-vuetify": ">=2.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vite-plugin-vuetify": { - "optional": true - }, - "webpack-plugin-vuetify": { - "optional": true - } - } - }, - "node_modules/vuex": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz", - "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.0.0-beta.11" - }, - "peerDependencies": { - "vue": "^3.0.2" - } - }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/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": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, - "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", - "license": "MIT" - } - } -} diff --git a/web/package.json b/web/package.json deleted file mode 100644 index fa14b869..00000000 --- a/web/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "web", - "version": "0.0.0", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "lint": "eslint . --fix --ignore-path .gitignore" - }, - "dependencies": { - "@koumoul/vjsf": "^3.0.0-beta.46", - "@mdi/font": "7.4.47", - "ajv": "^8.17.1", - "ajv-dist": "^8.17.1", - "ajv-errors": "^3.0.0", - "ajv-formats": "^3.0.1", - "ajv-i18n": "^4.2.0", - "ansi_up": "^6.0.2", - "axios": "^1.7.7", - "codemirror": "^5.65.18", - "core-js": "^3.37.1", - "json-editor-vue": "^0.17.3", - "roboto-fontface": "*", - "vue": "^3.4.31", - "vuedraggable": "^4.1.0", - "vuetify": "^3.6.11", - "vuex": "^4.0.2" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.0.5", - "eslint": "^8.57.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^6.4.0", - "eslint-plugin-vue": "^9.27.0", - "sass": "1.77.6", - "unplugin-fonts": "^1.1.1", - "unplugin-vue-components": "^0.27.2", - "unplugin-vue-router": "^0.10.0", - "vite": "^5.3.3", - "vite-plugin-vuetify": "^2.0.3", - "vue-router": "^4.4.0" - } -} diff --git a/web/public/favicon.ico b/web/public/favicon.ico deleted file mode 100644 index 00a756f5cedd93bf5228625c6ddeb185eb09e441..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9662 zcmeHNdvH|M8NUexq$trkrKnZ03e-*oERISmZ6_fF2tE*HM5!o4he5CdtzczJhk;r| z1VY{z0t66HK&HGBAPR|j$fJ!s5=e4)H`&du*}Suxglu*A#}Pwe`|O^c3x@mbgM-VSJ`3ohh7TJoUE4I7 zZ{7HNZT~2elU}5n;v8~zbdb~Gpti;;YA8A-bkU{FQSht2@wXPf?7a>A#t+`e+g_(8 za|Ll>9XiAr&K-7Yt7RG?G*2ki31KAMz&2I}igtdzZ>HH46E)U=|1H-;X5A! z!k_p8)yNk_-yC-|@zK6Ibi>)6=A?!>8W&CFr;~&qm`gk#gdJ|_*MI!rdcq8`r%TLkU)|a!ipr8+BJ=fpF%HjZ?bca7eff=_$3y(IROv*& zwU>^e&s*%zZNRY##6rWt_eh)aa;LuM=X5*Xq>BCTf*-H?wt?=tqw5gYY~(;(G937; zj}QFEKbdrgJ|NqTR&qKWA_t3ggxjycj`&n_Ig{FNG?T-2gPO|nsC@g}Tku7bA!7rz z)EdZ+cFl$>h&4;R@o_xZxqz(oW?`%Ezlu|Gyn8HVKj_!`2R6~4P9TSqsJ+!f6?-|p zVScK8;#{A(iR{SBo~1f}*G>G~9`VLnQ|Z}+8cT9WlQ_epFU*5aU!%tFHKJYB*<_MA zpQ~e`4#fWYtR&=J66*%`zbAef+%e?tA*kn7>aU!<$uq{kZz;95SiRb`n6;$!_=PTqR#CIH$&Jn4-cH7o8*jD)E;;pes;y9R2sh+4NKN4p z#4?Xq06lg4mr+}bMd9l45Bq>Wld0lVJowW=cIJ=Bj~@JiPvwLd(q+a1UlBD_mXI+! zP0VNSI0PL&e-H8HTku0m=E4(VP3sxsu?Hbj{%1nBwno@0ef~`wxHuFDe>4Z)ryH%! z)PePiDdQs_n=n6lY&qo>vDV;N?v35`K7;*$IK>>76Q%y^1qZ}Db~@Ur`kPeP2zy8`E_mSVj5{1hu_mawn&lO59Y`(~#C$_0VwT7U zU2`x{{EV|~+gz&F6be5(Y^_wCw}Z6FFLugPZ{9Fp6uZ}6IfdBh#6G8kEM^^-U}WNyh%o zAC7m1TfNrj_*FePkBWK7^H&wuJnk_k_Yim8V|e6W*Zafko1-L1IP^QO>KM#5+yD6A zCgz3>2zm79!-qm`?{YZWSBj|M&7|kKq0J@L@v+N+rN=Or9#?{zkP;FxO>x zcZP40Yk*&TPsO^Iv9tUE(iqIx`5WGq4UzhlGsjFMDuN1s zU#)s>6<`m-cXffE55TO-eKNX*;_~N5-BXNjMxJHHQzKOTT>k{J4q^-v zJ)C7*qDsJvwR2sSj{!cYH|Gkp|32U+Vd=T R2s@W^jbA>raUCv6{{yx2ON;;j diff --git a/web/src/App.vue b/web/src/App.vue deleted file mode 100644 index 926ad3a7..00000000 --- a/web/src/App.vue +++ /dev/null @@ -1,329 +0,0 @@ - - - - - diff --git a/web/src/assets/langbot-logo-block.png b/web/src/assets/langbot-logo-block.png deleted file mode 100644 index b2caca7b6b950b6fb2b79d71d1bbe20576c2c906..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14953 zcmeHO^;?r~)MpIF=&sSA2$IqbBL*mqf;0wW(#YuA=;mu74P%O^lrlncf{Nl021v)S zm?AJFCEv&QPk4WRf7rF_dhR&qIdPwJpU-)+9qcUGSzs)5bad?2R_0E0bfEr!KPUuv zk|6ZK0{CMNvvP}|qf?go_XEW|HjbjB(-W~ahhL5_(fBgUB|FcYHDbhZ_zim}2{rO(s4FTQ2ki{w;iB`V!&BChOLO${D zK36)5ogCtbVZK+_e*QA^5ov~8Wv_pB+)00Ra@ox(z7*q%NpJOe`1xe+J8_KT^!VB0 z31jj1^^BG_mEWx_?4~X3Y^ETsqFuT<`eH~BIOs}zEuBXVm#7>&A@j$y_(`ZM-4^B{ zR=0KKtgn`;APNC`#mD&b*NOZL=^!~FOLqmv3oC|Q3mAD{M}O#hW^E_5 zj-T<1B1PC&mUf2aBa$y~7^M+_1c|0;Ib@KHhRPUzbNmMXX8p}*!|}ZSYCa+JTe#TC zu@heoCp{;K6YqF4(LFL2ccs*i1V#3ckb3+vi?@ifstx);bhIj0&_C<{3OW4RSwO zl)7o_K!eTjj{7~ZhCN#Z({#^=v$i`j@^du~N1TMB7;qRn&Gf1t^6auCp|c08UA;_v{19U-n#GSi7Z5xeofO}3AOyo71X`_xgNJ&(X_up*vS(+ zGk6E*;$}(NCPCthF)Xs>6>|qn?5*m@yQ$3wT4u}9^s|3oT?*1I%Blz)aU=$g5`JpN z%uc9%ldNwH9(|Gdm0Y%PfBLqf<2|z8W;m60qC|zhOT1%I!v6t&)o>1U?sFwC_3s1I zq9@!`v+>QkTm7vl_RW*`HKZJhWPL&KEg!H!`fYFjo40gwc9l(C!PS z)YpMk%v6p2F7kN!O6v-e(vA|_BzN2g~ZV3+G0{kNxig1uuTW`BeT1oO}?XsCySX!;ZHhU?J@*#WZ|_^#7l z4$m~R(XkdVO>_JBudMI&AYG*4iloidO$wN(f^Em;x(z!;LQBsdOK1dd%*ZORGhK^A z)ZAIJ$F`9FnzX11F&>F@?*0^uL8Abz+gO4Fa(9I(f>eiY<0~FB8Z}96OQ58zC>>7b zhWGWX9Q_(>@XTcuMx#8f!QuxqQ1s`i@6`BNyxFCqRMkZR;zP^^IP*)Z$@2n;FRb+Y zzEpEEz)?j6*E>BPF`E-EBcagGny`Eh75c^eIXd_2qG=ZpU_SfaIa(F9(WI$td(j>J{J>!1$8+YZ^cvAIMYw`suBv)^ce3s< zVmm_h*>19k`{D6bU(dCvgN<`^Wq9%-6?D{cW#$L5%Rw$lL~~1v^}D2^9k^8dKu0ga z&npmA$%q80(5c`lWtl0Vv1@MWDUB!fPmep>9yaMi8NW2oI-eegX~aq7_lNV$P7}^H z^LMh|*5@m33pXR@%gu4ZR5D=7T>AHws3SoAhF_HjpN895zUvUzuXk>-v&0yJ_+e%HxO{o_xlLfp+%V) z3Xi;Le;d8cK+{p>O<%f1^yJH8mAB_zIgmq39YjoM(wE^J>I;S1Eo7<}0t!{^Gd5A+ zVGTl5%ng)g(OITtqMkqlQzf656}>luI7A#S(>Fu4>sldSMt&yLI8==sJerw{j- zcoGrqtKwT&!)jwC(|N`3JWYvkuQO+fw@41yL?{EsmCP`$(orNAh~n%mkvl@x-0=dkWpAtft8#@@`ZC!anJzTXy!?zaY_?|BoY@qoveCQfE z8sDBL!KaOP!M)IKz0UtYZ9)xO3!L+8^JyFnbHwMFU+gh5qmcRm2NRcAmJ<^+;kshL z#ih9x98%=F~skNc_{0GVBsQUqxW0ZC0JxYbp}verdiW;1l9EW|DQV zI_a^(^^Y%}g>&tC%fx~8$^t?XL*auW>!3!uMx25F;q4dD1+TxX6Ae@e$H5}8r;8<@ zKM=JYo_B3IMliM{Eld8ckRq;oTg%^o$31^%QgIu!c8*wvO*ZxC z9kdYmT$lkmO^@rXOr52b>}01l`)AOfrYMg!49zrZ*7eM6jFzo@joI_(Co%Q1dZy5C zilJ`U@`nryF(mM%e{m)6HMuqELc2nBXm*4LT`XS4leoo7*J`@*?2(fzwj#}R4Ntua zI>mZ;BVP)OUv*7JF&JTru)QL_PaGKib=exdKi!0VLPH1zPNkKeF(}Zw zlvw;{vB=41AQV3Sh!s$-S~yo!BXcEjBD_ z18t~_MpSDKT%kg<{SE6qMAIssN9W0}YJ7Ai#uBek!@AvEXGhVuj@vR@FubM;zbYJw zhmJx?>`hn?AdsIfUXWvG&sBaT=Znw@-Dq*Tiu;*yA`fPZ56`lVGd~(AI1E z=Q;!wKupUAyO#cUho<$nV>MWgG#3#eiF-PIZOxKD!&b`g+E&&lxb*?kq%i3wfa+!a?p6lQ#ly^}N_KHO!yT)@F~ETF?O7u6pu#Wz9eu z&7JeB80{<@m_OEmyaD8nSJHJK0TjFTwvnL)|&v?NqvzVaiHNl-GP zVeteI{LDK8j8?glNha+h`b~}gFZ}!Qz|(G_wdzE=1&SK)+QmfSrJrsWUokE_5M=j% zQB6Cq@gES5&vq+JjO|I&F7QD%p3LX}>QV#gJln;bklMk?rmuAbZ69~`cvycENNci~ zYRzM9#4CKnRN-Ah*LiOEk@Ju$KR~C}Mtk58=5E|>J;OTOR{58HkRa&!z_S+R?|frh6X`1zLVPI!y@ zJHYdw85f5s>)&%0f@A4#iBGUqxS#wjYJq0ssS0E^OE;CHHBPfp*&c1Wb9k9^eNPQr z!Xz_3;@-|OQ_q*&kAA6SD#CuknOS|sQUu1BNN3CaqY)&;{yYDZ_hMG&aHI&&@k@BxZuxdrqTzmWIF##%QGptcv8WVxtt^+9Tw&M(o3 zt7xe*8p|wkOQHHK@cLM^VChbWdgp&{*ywAk#nzU*%U)F?*ke8?0aiIi0 zOw--AbxHn6Mv^Hedx?)1e z*ms_o96qrAoDwEN`C2FF8{mRdPgz3XO=7QHx2a(nZ&rB`4 z{o*vY5kmD1(9%Gee_S9C)Vd*^E7HILLP%ICL8X8&}_r&o1A3Za5htg7)4@M6q88^jp=8gOx7Xy&b2fEW9G8 z7hjpzBOKA<^^%tYO!h=LELMF6M)Nhk29D@Vd_D^H_oLnH4{FM{W$HXK zB1kuUh%Zf_?%cgksAl#=(;fwYeH%q4Hs=-4w_dPr zMS2@U;iYb@=$km-yx<6|e*&_!6ePdi7yn=#-Jyg*(338Jnpuyhj`M?ftdc$`%4Ut0WH=)E5aGl+2eCtn-J^`?UwjA zHv;=yKA7`mC{a_BwESy6a;3?P+L14JzMs#H%4q}a0-5f1Iewk7kx`i`%1nDnw2=NK zi!AQe*k>TmudThWfM)|D*Uvo2RJHxZ5kT?CYehKQ+tHw@>SOxM8bdZq=Mw>B8Cb%s z%PQFvqVD4d?N@jFI*ke@+M;sa_(O)-d_gcN@7zi5LP&v`^OL8)Z&CB*iBHJx%YLt0 z**Ydt!;-hH71H$tYh3JYU-%Wf)vPxey5bYr2R~Q7^u`jzXu29_j|M63kjpHf7Wq)P zPabM@bbHX}y>BUzbnvM6!;g0ArwLcQ*pnO5xxXG8ItVF0>UX{VxSKsj?cj&7uopDo z*|8x3fkIA-ML24g?}2^HB_4g;7r)KNG7yYs6_%4{T7FLcTY(BBqnckRz4H05N1YI@-D3?3ufY=P#k zd%clPwkUS{UQd^lx6aVbB{m?J`jeOp>Viy>o1EDCH&LtM+rs5ns6!7cqq?-!R~t*} z?tL7@GCAI(O^87g#Phinq~1yzU^Sx788Ul{dZm?IS-ePDA@A|)Jt&9RADH^k4n`(ZJS*8a59Eu`ZTHk;)**mfH!|QV6SuADO8A1w``$;beJRN*7 zDq)wxl)Q9B(|7kNdIx77ONwT2kS#;+#tL+>EE$9%!zXe2_DkTS9CYDTHpWG0x%XkB z{9`_VpjlSaPb0K`Yl$1dA5%zYEWMC>yB!f&CXc3HeLx+3Dw20FV-bC;s<2ktX{pr@ z`8P&*=e(idRZ-86??iJDO=Jm^-ql9ZAU~&ODkTba7WGB&eV5iocL9DatHrdvx~o9(^>!lrM7ffgfs%q-`Bq zistKdIL1&-b>I%6?N&?y^AR|0b<6Qap$|qZ zc+JGjmwm4~Uk!=$+mV~Kj`K%Li3O!c{kJaUW#uZJ+6;RX)xav zs;QQ&8noR!FwJoiR9NkWMrBQX{QPOWS)Bcuj`(2pmh_*?x*hlJ@K<&gjLYV}BmY(l z#Ta!!)LXFI^zJGNI-Oi-+woh(`v84D{zYbu?P7#p7A%h+NzYmx=e z1W!J99^en(FJ}*qp!ZoK)ypYhE@1<+PHBCHVQMcnPLt0`Lo-484Ba7?h4c}rfRQN- zDH2?C^g9Z;iOa-oiryqIH$Y-TjV45M`X^$GTi_7-M%knsbUa_iK<-L#q;q(Ymy6sLSmN(P!m~)r&iT3tjTa4hSu5aZP8IaPqW4-xLA6Wsea}YTt;^p zz9c1Isj|u3r-e}tTE$ceSNbFVw&9PH2H!MOh2SD+jddE5P7$E4F3cQ`=<%hoVq&SQ zhOCMCRi=BH$`YeP&qKw+#KO*p@r74-*W%UFN4OK}@qqPbF78VY_wR8E+)|*J`1W!1 z?QRc@X3eTIdi`|TGl;tZRJJ8-_edKauO=-#bYqKVX3 zB8fc`=Iw)5H%KPYg6m9q%+Ib#cM|sOO(GoFO>DRj1yEPYt)iOWm^P~_hj_>r@F-z+ z3&xkDjYAAXSKF7klph0y9J#NkgXE!LzOb-0H}?4qgl2n9Se}ubw^jo&pSl{!Z~@GL%G0pil&Vs?7s#+gv|13>?O-~mUPzbP;L>su*vj|=5udOQ!KNvW2=eTmy&p9r+yzo0UCJn?*_Xtr zrkV<@3V2Dfg6lUb8c1VX&cw;1vf1FF?kn_FXDYbrMVPxK_CfB47i-g9`-|wJx#OAP z$3j=84bv@{uAv7vb>Bo?uDc7_uH8?h7@`6+uE=0oFdH=$d6N+XSZ$ohh|CkyuCFLc z0;S^2ZD0NH=xTOLM+Cm=V>73+F5@+k-td|}Hbhm&2x9?f6ua=g^!8n;=JNkAQTIx7R5S=Ic2i9@lkj=n^ zv}2Qx`XK#iz?KXbdi5ZdIizhtB%kO()^Ow3e#nNXKrM=TS($z^wLJ4|f5%85xH?e>3s zAajYhg2$!sC0F~&N~jrGGdO+8wQPKXdfhA>f|UZvCxEm47MmxX^B!(Ae46vQg+5g52@QU@V`x9Gw;V%nl*fN;E1~=Sr5ddjz$R&WgzPig9ysIpA=@{H zb6k`v>kX}JtQOnxE141PCsbX^)9R@Z^Y%Acl{JDP7nj^x*?nGI1P2Db*70ojZ zyN#BDzr$6wTrpH06IsOwX1{vmt9sz;M9nA>O&VRD_b0@^1E0G$Xh+T$&S?x9T0W?} zL$v<(3SPG!gD#$%9X2aN=1gXNQWlKsmwY0*T|izhNkc$&nXbtk=)b?Vh7t{UT=bQI zT7XlBWCOLUiX|Dm_O4K2iFR<*g8GAB8t&`4UUf<8>kxE|(GwZ9ijuV2uSZGKA>uUC z(ZEpn>ouL@^!5S>UcmCC7Ztj9`TLzQ2eeJ?Q1ZL=*?ZI8@1MG*6*!s;nZB0UE?=bG z_6GlKd~vz2$0b^``cC-G^3HK3(Z;?bxY|dmFJYKiS}bvVQ9OrZUny zn@6)gtBWW^mu2px$!m6{9FG3?>&kbueeS>YJ;LJfj+f(QK1g=MABAXmD=avuH%n3? zMdbE9{Q;kwS$Jzq^NQeV3_Sx-MjY)liln55nO!2UlcDLA%fq+w!!$l8Ij?f2(%!!U zb~+q>0Xnrof2$Q)^?;L*!g4WRoyhQxZpzduip|KB%-_dd-}E5hw1V)(!|j*La?%?S z_iRKuH;duW7fC|F4L%fUP{TX84P1822+u zKbap_@Wm;)km1vwwAILOlD}s!Qod8AdH$$;^5gPXWoF{zZ*no2b-S@28VypW-Z?u{ zXKw;}`rP;=>jY2L)za|$t37NMOmwC)jl&ITXpKd(5v%gn+S(BkB1h-NmGYORu z{PE;VCEm-8ZJy7|yg8D{emOp|IYg5s<=;{D99#C`Yv6*2qtZUhaCR1W=gv5OoTfD4 z<;HmQpm^2QX_F^R;j_2IZ^}&0oje`IM^Uzo;3W9(%&iHFHG&HItCKm`C#8K&{aJ3NYtb5@KB=~|-uGoG zYJXVLBZ;lwwAJDu9T@@Iw$xn67v$*q9ycU&X;h9H|Ay#Vfs2M=Ts&F|baJt>EWL=% z_-s|{O96`M^`$~5?edf|pJ)eaCM}Em+P3zh#+NA52iCka-ajy5CFwrn2rq>&U3c}5 zuX%}0o2W2n1edn7;x^a~uvESVAVn9qFO0HOa922V4imHJe6{;6Kq^dmQ^JL5)`>D# z*H;%j5B;*Jz<9BFooS(2V9od=dP>BTA`ZVm8pzi3MU#|r`XTDDrk4L8rQ4EO`rH-A=4 z3%SI4b@u86{uL^??uF$w>6GT|Y4L&q%uj5S4?+c01)m#!{)3ycwQu@`-78g#~Ty5;X2#`*ECNNRsGDwuD6R9YJPkXHhz$a^qNIUxEVy>^-~z7dPiygt@O z89!U1E-Jr+3UNTnF=X^KcBWz1CMTfiGKcY*c|;s&y-czCO-wiH^SJX zMMTzZ)a}eW5ng-?VmU#sor}Pz;N|;X_Pnr+9W2H_-cvG!MdymDkfw7n`_)W%E!? zZ34sddkDH#wfA zqOY5E-;yG9z4rYIE~(=o$aQ(x{`i`7c#Gfa=$?xJ9O~|03KA8+I?ziEkkwvV>w1M! zf7?Ku!`{Fyqz05=OZ`tpV=WePK$hLNnUi#1h$!yDjURcN z!GzjBS6UPLF0ALq2^Cm|=HRJuZ>CdvT}#R9Z~vlZw_G&zP`_3>8$!KJL|l~-d$VgU z@og3o8L|^6$EaCjp$1c*B?}aIG6?PQ=EnWLVGp;UjM_Gtm$*vi^s0?~Hq%klJuvYO zR#P;qHavu_UWzFfqYyCSOMtv!Y6j2Zw1kz$3K1g3Caf!$$QlO*8h_$p$IS-mh{~eg z2Pok;`h@XzeVC~ERdxd#B03t)r3^XAnPkR)(pYB^#iTY{1!4yS3ril~5X_nRJyD{G z!h0-7S0U2v7o0RUBo;=ajWwh%sDJ!y#h3Cm)jRn@DUXULnk&ViuMef;U%^UR6w^u1{_B^qsN^t_ zmtl>;UJ43dNv|mjE0hUwCz2G$uy2O58q5Z%r77OVC6b;w1uFF%jrcyj%Hom-Z?&k3 zI~~6*4yXQ3VrWi8ZRtWk9^@|7-It4ZEVHm^GZZ3aabx!Q1cQiT>k$-3wc zS&rebMK{jA2h)j2uMh>+!SSy}nDs1yL0l>ppY0*wj@7!P%p8{}*&ykeS!-!_YDyxh zkue7b8?dWpyfn*{vS&~D(t=TkK>+rGL<&!{#_$I;2>5Zh^LPkeHFJVPXEj&1a*qVl*oida)W)wOw0g_SIWflNVj|3?x(l@enWSkyWt1W|S5JB6 zhrqSfpFdkBu4ebJiJ^BEC7_E+?hoC$IHW_&btHDW)Pd1s%y?EsY@h}EWpU2>Ylcr5 zm#x11sP~{D#MGZRz95QZEy%OXo{0cAt2SrsJQ7G7$YSq*ug^V9f?Nc5BG+5rC>eh? zWSY-em$Werfa{cZlumw;)MyEqg~YHnCx(eV$LeGKQ&+Fr1A{por3RV-$SWG6w%L{N zqfLSvapz1}Rxf@cB}^z>vq6dm<5F({-Ik-d_?m}_1rzaaeELtKoml&q-!yW!G4!;R z%$Q8G?4KjRWoKE5t;AfQiYV7ADQYo4x=`jvs=t^43T0SC9Nm{A+EBFUH^k(%iy4CnvU8UsQrx^01uXnFeqnfsIR71VC?CeSQ_#8zr@f{TVCr+N>G{j{5TDO9}Qt0)>em0 z77AgkVKQf_(+`}uOn<|!-JOdYCTU%xr){7fqP~fxio}6B=wswN|68|`hl*rZz-*%| z@0GZnLx4_Mx&_5btlq-VOOz`44~GF4;X*x#;v?DusUE3AmJGrss|%HSTflD5AvaAMgO_*$og) zLl!k|Qf0IGrWoLZXB!cjE0?spik!Go3hd3V<`&u)yOqk)Rhb@}4Khx>ys$@SOeX*+ zQbeBdnoPJeK9H6*N*Yt=-8?*Ip=~l$|3fo@FN*5g%fm zt-fab{Y0SaV`_Yq=3Emg&$ukuUuCh;9zX>qDj3 zOPEzaMw|eP-@MY4?#Cp}kuC;9S^7@tIAk04zJHnsE!x*=HfXzla%apZW_xWL=r328 zwIecNwEv}8V_uw~Dzh#N1*yltRR!h+G$xaB)cJIHV>ki7(f<)2z(M2Z%Xo0di3{N~ zw5ka#?q4SAWA9;Zy>8up!vg>Xmus8=FoycnaRa8=g4Gc7sy>9E6>DrJt8=4Ns#7)| zJO!YP&YNR_X-F4MXUr6R6Uf`5Q|!9dcUel1?=`d(_#bTpOKR;La&k$_%49X>nUnpr==@SQ`+TNddJcNKIzvapdxztu6K30j z+j`1>6_~n!9I8`ih?}LrodHB^OMPB|iw}cnN z^;7R~pH#+C631c)y$#&4A2V%X);W9k*20}3!56&=F;bZ7eyWT>Xzr(o#at!*+tL;|sq+{;>3QekZmA!QwX}B@$2)cysXF zZ7sf@Tfb8}ealmf#L6Dnnq~Uv8|^fUm{($_N|2O5)J1f7m=X@aun=Das;BPyb4?m+ zZqSt%v|%>V5kKyDC6R(C!{FYW0xL!K#^duQvd-~4J366sW+|Szg8fKdRiW+vA0QQw=^Cn+ z9rnVHV+Y6~BRvjKyBx*$q`OfH?wm)40X4x3_b+lG8Uq1<(gU~Zy=&%NommYbB9S63 zbRBGKY(4rNMNSvbLNj^eiB9C7Nenh2hH)XHH+P1hb_9`$$1>_JtTm9D*sIZG`AEcmK_zd&HyC5N$Wnu z)l#>}dWI0DBy3!IBhU;512%i#sHWQ7g~+U)6Kng|xDEpyRCkr^Ovn%)p!oJZgXBNo zHRaOc(bw#}dt&Hd4D~vA#U2nI1#{oqTH`=Yim1>fLzGSQ%^Iq7p^uy3)0mL1%9NG1~6zD{f{{J1Nc(oiO4k_h)`kw#{F4!e#Xucw@@VrlHS;I zg8YK$t&mz)?tulx`bG^y}bPv`& z@jrIEd>AS9OHH$BH~u3A_zvU-_bl@sSDeg+OIKK%1+H2idf^pD*@2^BFZ9?gZ>~i6 zCj)L|{jopLV4mp+U1T(Xx!)<7p|g^LzSeGga$Qpu9K#k2?h{L|E^+gF%#&7Gn$;q( zmJ!dBzF39PCr{ihar>?d-I{eV?$>g(7>vENWCj9AE0UTY z?-VBH*Dh)sVMISq=Lt4Qo$>*)BtT-Gsk6dsTwYep4W=u{l?;syhJ|>uHvmf35&|&q z3bF@T;s$owZYiP8|8<)k*pNa)n$I>XtG75^l!874bJl2wYcH*PD!_D&IXw$&4JAx^ zcwHfren^)%d4^>eWqr1ZqkpHttjzO-&NF@8D6s$7Xqa>WunX;63WS6KJzv?ynR?xx z=^WiECKa2*r1~mAK9?{{pu>9`XM2X_jv5));r^%I2rpj}DG2k3Cryd?ofwa)NC}i5 z3z%5=j+j{5VkD$aCITC?+`Y&}Z@_fK0u(02`P=BDt4}JUs~4(T9OX(&++G6-fD2_1 zh{|xFu_o;h>oNL%!x}xE7>lw%B8x6_rPM-3K)swcY>khN4GL!z8DRhH3*uSTAf zxFKM(vk9-TB_-b#-fqPrk;wFLEo4(qzJwl=uz90dcrR{Qu+svrN`b!4`&D WM;=@(*}%7>bk-Jj=8dMlDgOfo2LuHG diff --git a/web/src/assets/langbot-logo.png b/web/src/assets/langbot-logo.png deleted file mode 100644 index 567a2e4fcfd12d8ed6ca25ded209e56f1c51040c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24667 zcmcG$g;&(y6E}_`UD8OiEFq0FBDtg>-5p9JDM(81(k!Til8Q9a9ZG{pcejM3bUk-J z-{(2!_YeHefy3E7@AurfbLY-|&1>d@(0HLpfJ=>whK5FtSB;&~d)=R7@8XD=`=|6bA8}bed zFoa-9LQJu>*c$~MNbg5PJd3SXVp1V2Bh6-`iHhwQ=gBe7r(8-8_k}63!Y8|5{j#d5vhc6(y%bu);@$1~3c$#D`#x6o{8ePHqC=FP_VYg!}x+r>X- z>mz~pZ}zq*c`{@LENB%4{6DE2ZapN{bbpciNaJ_TBO@xEns>oAq#1$BD}jRO-K%yH z5B0?1aHTh|ZcfGDjg|$j4mmZbe+%ME@GO`7=Z@Q^Khe6U{-2DyRQXEe(UY`$;pE8= z@u5*C96bVcG_r6wJjb1S!1aivi0vU#l{ej3Rgviivt9;$Jt+2e&izj@+HqC}7=}R$>zypN5IgeCd zX$R6L{<)H_Xo={_%rPVfYETQ`5wvBVT^X?k(1BryFIYtbKEmMo??@Q!JtnGmUeDLW0KUbaGWN}wejAt%w32PP9< z7re|j8lePA=&#M9b&aBo0t>Jq9?(EuEFe{}kSC7>gCaJ#s38zYgGf-^2HEnj-yOY9 zV7#1*d+$geOOZn$IWn%8ZIy;}KYMMocP<|e7+X*QrK9?A1Zm6!>fhOI-0wxHQD zFvSZrBr9=4lQL+Xc%_mT;e=X{+Ite2r^k$7qW5`QE@S1))vuJXQ?a2t14VZa2#0~S zS+?-su~iK1rd=is_#Sh#0|ROvy+AwvcH-z(s8y06ar3j2)GmVfG#Who)s}cbr8l)I zL)sxY3^j3fZ7l+pf(IlKU=?yyRk)L(^h#6;VE#|I-)kW47%fryZsQ>$ksbGO{*!I4 zESL->QJVNh+KlnvTY(kWOEx4|iybY5y_Y$*Cb$`Jc>W#y>xOk}1Y0>DHRUsAAi_Ln z9imr`i9Bhx|4;tqEaJq`*U1VGy7LrAl3}t{9hev4EBV_r#M<5=;4(aVFl$-_m;@HO zKK-8?=HCtXpySw%foTv_8N6D6iQBY45&{pP^L|Jb(L77=Cv~!70haP)L%TLizO%5A z6W*mk`G|PpNfL}iX$mDyFd4GLhH&WFnEh`lsD)SUUcKX~I6`Ftu6FaGU1DlcQz{DwKe5 z^mJ#}=8w3_U!Hf85EBo#O?&E1>4dMLBE|g!_4I&;KnZGb^(Np0-~4X+(@PS*i=(X5 zJf-vMk61#nzTyk#qh26?*j2nbgU+`i6&I(789Y$+=$UO~#{K_&@FemrMgB8lvh)IL z58snle`M8eDE!~Ng)lmu^2ldxEh+bOgudNIiN3Nugo&?NXWfpPW6%>im!R#&C zs=X8r${Y@>v=t}?s@!@|?mN;gjq;oV>;JSG<4-_&<@WzH_riyw)E3A5Pa56-28s(t znMmvZ@7F$OoRTu;EW8(Fc>4Us(%HBEH-GL#^hGRuKW9pnjzuL(NC%-U7pijHU+`YawS=x6R0MqSu%g@XZqMLGwHQ<{C4eFe$Bmp zA}HGU>qwg!TC4(kH#LDz2G*q4kez{@AT{_g;5#<>epFZW#d^TrqSR$6&k(z~% zCOVELeqluP7BY06oCvFk=QPHbeP_TTad0!+o?37~S~BxB2Jzuy$Jx=vhP)&~W5iBy zX78#czRQ=L*s|j+K!<~1_b`BVeyiQfRFL`wXTFK?z^aj2`$<{^XEj6)m)IE>4DsL< z2L)k_*p&96tLvP+A=c4_gs16iJbH+0kBC@8&kxufB@!R%&Q5H;zTaP~kUNaeL3-<} z+ZncBRxR82^(9?pKgO?A1*gGG_$326&hWE`_tu8$qC<7naTzeyjyv0yvLi@@d>+id zTL@LEZDqrXbLm*rMu%T-e*X@+AGP^*XPk_|kJbOvKZ!aQ0>|GRr6n7Ozx{*ng~P@9 z5+0spIn;?haamqUiMF1dui1{wvoEovIuy#MCegV`hM%ex##gkWJ#@7==hanRUDonE2D-k3yzl@Cfmp^pxUA|&r%zKZD^tnQ z1;kkz?9!vSLsiHzteoomk^bf-VNN;(_r%eaYTJm{57ab7HPI3N<~L>GNAUTbGYiFW?am&~XTwQ=`I@W_O&KV3W~9f+d@bB!XnjSg~diRuvCrZH|sL z)6K@OWZoxd-IDNouob&Q?yJ=ATI!E?w$=xcGnB0z9H(Rc=5{xTv`aXgSCcDnsPl2! zOnX9WEj@V>_b`WW;GY(OW;;BgdT6-FMM|#rUB$4*3k~VS*QGlv8oiw@IFvQbaPx(U z!-*0I57)<}iysJfetf8)4t+-cvc9*0sKm5ESy-|#K9d907E4%w0?$FT%eM6mjM6WUDSa)cMfKfzvQAebFn`s8Lq1-W$8|i&d~u4GhyIO3rkbL^LD94b`Nv< z97zyn&>i!vme&YLyUcQd7oTV2wY zr);gLjJT+#zCFx+@|)NFy+KZ32avM)qg-6FvM8=Si2&#=n?0gOfJ2>OiLD$b-odP| zzSf7>zZw$c7W`CKI$M|q9?|OU+>aGAC@ouNW1u|T7$C+Z&5`^GB&{wZr~_A9gm&}S zCPS67z3^}$%{p>u5ean|y6S7LBoo(#JHE!E#I>?NezelL9gekNT<=E2jZSjx3NUS> z`+wA0OfN9-IbAyPefYTgr;EjHs>vi6dGd=2y8a@@h=+eOO*nI zl0wb!p!)39e#R6)c7k>+uhfN0-f@4FJhN?`fA+pn~ktA8o4v@37 zo*x}v^AnCTO_FO-y_^iO<=tWIa#TwBuk&TTXyjKB4ED&tK8ul7`Es0OW)&i^Ss+<< zX_$k@GifQI9IQn9CLQXM!UtWn>E?)vcfny#%D!CqIQbG}&N^Bbo7fGc;W!xlrGQb% zVn)-dbhuFve3$w0rP69;DVl{QbH4IjYpX;^<*~`ldv-N^XtI6gf?<1-0Bctt231#2 zgn%s-EFzv-bqzJH40%$ct`Lp+Q-@W=I(18s8x5ravkDG6&dU*C7=5A=i%A2YhdA4$ zmJc79Ib1M(hW`mJgFcl z(iS&7QIAnG6n-uamw_!Te*L)Lojto>o*C5b&PN=;3i$+g-Dz-{&Fpk8b6JgNFzbw$ zsyMhj(fr-T6h(}V^ED3`7z`VDBl&ITAf}-%RXn_QF+aT@BS3fG`$|OEI(ISkaxpYd zIdXom;k%(2FvW~@jy-k8)a%thYjoGHH6eKYpJL_6urNtfIzhzqKN-U9;*_USaMgv{ zy&n%W9^&Hi&p&IwH6Cgujm%0Qu8JzTY|gsN@NxhX+m32+=bJ7vJP%D20!|*QU+Q9A z9mZ}2Rxkgwut!O;6>dbnRl5wZd02S7nafe@M;*p33JKrl-n%$osplaXjr&*5?PA;d zYNH3KS5CB4jqM90IdJ|24XZV?M62=D@wc7i*L^Nu%kmTEgWN(tpKUzf{pzPGAXl;Lo4MPtO4kI}{-_w<-9+WLN z06)9-m}+{Fwqu3i`&T7WSm8v6&n}8CZ?ZHXLFR!?`33hNT(0?)P8WmQi$sl?r{lBifalh0J# z**e=TJ*mEjeNo?;lCyBT6@uj+;GeWMmd;*!tMNuU7Ex3DqNOO7*w_i5h{|?5<@}|? zh1LcTkJ7yquKE;qq3^b1a>|WQIa&Vr6p)9xEaKrkHpFFHIUMIupW=%<7t4HVWqH@_ z(?X!*R*dT z0dl20G(%4LxV&=BqQS|!Vy`%j1kot>C;tse(ndv! zgL$Gt6W(|i{p^Sp->ex|+JPYkb9#+*p~bi)6w)1&--nhPTy|PbjYbX* z3nTiU%l}FZZ)q?{HKW&e=#2aR#I&Nka@Rt=vpw4l%TM7(Pdd@tRL|Rz5Ra2a_r*vK zPyPgV%9^_vZOW6wR`UI&G9Dcx09L2eG)i_ROL^DHzODzA%?p3s2x>^LkC58OO3D&olb z{RL;6ve)V%4lX0a2EN1;Ni=x8Bucn3`ObTf%=K+S7-#j0#_7WpGkVGo{dM2*mmCDL zXz7bqEyg>`ACuv;yqfHmR% ztl)naSNg6FU5DSLlDR5N%DrO&u_fg%kqzWAjcC6<)Wf z%KyO^qsNXjtHE#gqp8YVTc(UzEEz6smBw1iJAJ+v&L5$l1L!SJbs&3_!Kf{`K-QQ_ zGhM%u4_2x9Fwsw%w%fijOEfN2x4(VxHN|0CM(0-hQSH zbSr|_QcfjPeXFJ9@bSwI0db&wOPqebI-=a&k5TirAug+B(Qw54?JkjYt{ze1>WLpdNK`X)_pzJ>`L&yx7Hj5UHRrNtu~4&0{u&G_M;(($;iE!k|e z!DFKzurLy_6tvzFO9%(kN}$Gj*!4v!mfCJ1*Xq~)IsO!QaF{T3g6i@6G{+bxokz;s z9lZNBG*wcnLprK&?KapLXnd?o`=XQ>QUnN^BpjFi2~mDK9Xt3_YGm9AqyLSA%c@*< zU5H6^akcIKFaB>y?y$kn{>t&GOiIlU;DFI*%iBJmXV&Wuv4zAuC}~OyGvk1`R~9im;%VON!U#a^zrMRY<{@LJ;5x=r8HkREUE!QoVRH^^n}=Fo`l*#LO?rfRf|gV?5n1&2$W7?EcS1M+I+P$ z4dq7g$5)SFn8Tm!m!49x@@tnLd<`g?lG;LT6(#a<5=|E3WoWMhLLB`Yjh>2K$|1fQ z9SYQY6Q(;io=`-0>b-=Sp+kVKdgRmjkfY_0NQOXB@$c!g!=@m!y6$Y0r_C%}?Z?`o z;%qEYQO?%*lb;MV<(^h$xBMCwL5_}7oys$PUV`pTW6&O9k2Kn}%moL07(8OUwe?r{ zw}|2C3xK>S4$m{FZwCT|MB_%O`!plnI=DW4DP*x64Gz7YxncdVi}Ldve?bWVFXf8` zp70wH&a?Aw@sZTzry3ELbK%uEuXckazM&T3r+Jn4v71xBcKH`~d5-G=&+l;AY;vEZ zxfSUWBz)Scke9aDdbrTzP>`A$ANn9GV7St~er}rv$H*GGhcOuPYAC;5AcqSNXa);aY z=?l@67GjBT8%q^+ALV?w|L}#uSJikR>nLbcV2QxSKvnUuhh5&W@!kYg1e=57nV$u+U3Q-fN z3R9mqW9c6RHMQTeQ=1eg;Fp)}n*Yr8bI*QHtVyQ~iRsyi%6CNc=qC0b6Sf>L2!AqP zNfDo?0eYId#I||S%}HqyIdhOV=|=&6LfT1`H|JdGba=zl6-7i;4^#HwTI3;X)8le>?T|u z!R?+`%4uatLE%VoyAg`}o3}zgEXV7sUWdrwi{*N(4`At$G=N{MP#6$_0+Ur8CC2)A8B+i2MXHTnsdCk-p~~$@!MB zQ9Jdbg!KEaIjNG0Xt!)mM# zMx%bC`7sZ;J@fXSF;nX=o{L86msTL+k%zfOv#tRpal8yB?LM83jZ6I^XeE}PMCA}O zjgb3@(w&kU-!+xE>jjcQKbwy33>*fw0EW^=%tiCguPg+VlwJV1@?~k{fvF`9F1CiC zYlp|NEGL!5!1+i7C9Km}ZHF}+e*gaHmYcV{7-WS*n!BtW>ec*qJ4PTG{t%+C^gm9@ zSdPUmMfv8nyM~zFsjE%oBrf~@+=QFGVRwBegflUjqvbLp%+89N;(4$o?9=XwQMge4 zTOm8=#IHg$L03Q6dAK|%2)pMX%m1p`Ed%?pS5DblDtftsc<~Slsi6)=Gr&sdq0;;_6`vH7}qsCpfdwAw$Fu{iVhv6hy&r;AnKOg8nYYb10 z4M7;B9Q=>FiK`4d+h03~HkNN{HQDe>wrZDtvb5Hc2sZbMlhwQlmE|$V%hBf_yV1B- z1#|47tWpzZkPv5hau~^gLWj4o@@fep>7GsYSA{5Jx_) zEdSo=P~D>`5eP3{rXCgXGiKY(cOQ8KVl--+J>TM2a(emywew+5TvT+6M1Jr7EA-{T zNy>w8lEGsODom5wdTsYLi+jn;`(Ba=!Fh-=iWqP7F6p)8Hsgi zhrR^Nug&_)f-a$bEdsUuZGL~KRbcAMbLmIUUyPNA%>x!+?S~!cYX_`3{3!c%(o>fwrJAY&5!AL)?7O|Er`!N7B5Vyvf@P6O1Z5OPpdOl zh{&^dmJ_ooEEi9BfQZQFTSgpc}G!$M7*5=HM1SZM|v05Od?Y&LKZl~`4LTUv_FC6sKmy(e{12N!w$ zBxPs=D3$_3KtX_>W7O*@4K)+s&UGQw29Ff%H2!HlCa$1W3CnWL!6elbO1ydw6LmAK zt^N{_y|ncFC01sd*-uCP=d^v<8T>#DYJSBgN-Qy91fy`&^aPbcWDyN>jhCB&SrMd% zx?FvE_D0=0mZp=-4LpGO#Yk$#z%r+)KJ0KOSZu9}?H?PBpi;twQx$FrcZzJkdir+G za6Vn(^UT%A`e6_KM|Qm5b3E2}iZ7OuEiM9BCpfk~YC!TGGvOMLNYcXv%52yJQny)< z=C4NT1K>+-mPWVgX^GpZ-}R2&UJ`H~v#-W`EMbCEotkusehO=`x{YqCwDCMB~(V86k->?1F?Do;3^^FUc8PrN)&`EZL+>q9(gVA5@ zvH#Z#z?X;~g$;#d`5z|4COzF-imW6rwUl)R251A^Q2SE}GTg5=5O;A_GpOP zg{*R$_RaF2#0+WpY#mW`#bVITWjLny=Cz!SnB_t_0F?&dYA~S2^iE*-tJgnzs25sZ zF0W7XXE0-bfK=%E>wPoSCZ}J6l5YwUE_9 zDm4(Ul%A(>EM2w#$0Jc3h%o5529_tE#>+moX58|K-#6L-;LPDnGG}~p~ zD)p03R<8Ts5lZg$aC?z6rHxpOjn{HjiB>3J3IP{`j&PnG0vR5IfZIkyuS0JFkY%mg z-nn^d#21rTDkr3w@o!8X3D$;R$Kx`ps~XGBZXgx&c2DaONFin<4^*R?!XJw*6KXYt>XoTc?2Vk-`8$ z(W4m|NXT)Ny!qWVM^CNAb?9esIfnl}x^O2EDJD|#q+`LT+o9M^Uvcb2VGLuah7@-ODj4SyBoqYCB3QOGrwnRSnHrXb(8y)`}*4oo&VWSs;gmu^^_TW zU5f!-9E&oY)>(n(Bn<$iLEZ%&NtlVUznU&aSDaN#gVYI`8%k?meSt^wKWy3c9~$|0 z6r{gzt5)lPIOoXhKHWGzA7s-znB0&5&g+cLm+&u}_itIY0hbqDA!7Rr8JPmi3l^4m zTub9~{E7wH%}gx_pl;$m*>&?H%3i!}~4FH`QyKqaI9ab_nXaqa;OXE=I*nVh14}HAdL)2<-+~D3W zkvgKyL`sL|VPS>GxV#THYmd4Xa-Po!(g^A$y;4;rqdXjW`GSC62PLxiN{tM+GcNXc z#lc3mh}nU8XEBhkF9}_f=r`~C11TQgb8{lY66uu&7dxs%^e__{oI>coqh=Q-7H+^F z^=V8UOV!VI)Rn&jJNTeU;2l`)9zb%uaT%RSGhC%DOe|uSd3teso_qf+hjAuM6?Q;Jfsb!XF88?im3A`T}}e076}@LqPw5VmC1>&ys^MV9sJizBf_U81XN(fM~dT; zXCmy!l8ll=3Yj4>a|Uk!N!lBGM+|W|mXf4Z825N{0|hOZn`N*=hUcb6v>C z!_O{v`f1Kg2Xu-jZni37qY~R;!<{fx@sPf6$95l7Hii{h@5FX@6yB3DUKA=1J&e1Q zSlbV{VVCP=v}D{OMJ4kMW)67FZf9{12*#&sVnMoDUnQXVYYl9-E9X2b%l|QD*@cK_ zxdV?oj$BLq@r)`=P5E`r_Dq$Yq&SW3Ko&C}P^n;I14s4o$%!Z+_AH6%7=N0hr}$xq z(5zmZxYwF^c*3NBd_UrS(Eu-slqw=5)Z?Zt_ASXay^xEMSmO*DJZ@@Z$`Fk3SkoXA z_C)7P#wP*hJfJx%`N7v0>=*~tq$Ac2pzHpzXimSZL7*9dNN~TT))?^zdJEoI7VO!7_!DeC zHQB0>j3<77=E&9m^X@vK2ugBN36i+}N=&m1XBQba_sH-_Z-d|~tAh8WZ*`|d)3zEl z)1BM-lK+WdBJqo;U6wM0+uKc2D|~e22oMP!jT41pbq(d2cXlSb znEkjQeQ|&**mBGj4JfH^YJ0t)-+ii5dLpA#nvwqlwkLFNg`sP!TI;?brTS`A=}mYr zG#9W|k={P+WKsXwCpZI}Me1T|DLu^&`_pIM)m{HbL~-MXj*V)x8C4pfs=!47j(7Za zJ6(=~Ki_DjaPu#W`}S&ks(!%!ui##T zi?Qg|ggFV8Sh8K(#AS=o5N-QtP>edYRC2i?^A-4;!KjNdwrccW2QFZ zuy5W1@|)MeJQADDEevVGGkJNo?(ymRq`cYdSLs+ifdVGjMoQzZ<=)1*)=X7(Ae!G{ zgTA1uN3R!(E#B0>E2B5+41V02s|7hrMuV4>!OU`DzfLt@XKXG5HYml^Qc8K^dn8ou zg=#8YY_alRL*}wRUzc*KX%J~#0SV`SH79zzSVSAPFINA zmvX*+5w}_bg%bJOsVbqE$VW##FKE*Aa6fO;$g^v9>yB?@>%U~l@q6j}wh3ezla50E z7ZxQa*@`7vJu8LDJ@OY~R8}H(0$c`t64&iGhvreaw zoRYW4uP`PZ#RKoMU#iAjf=sf7!InKS(q+tJF-bt^lkrklw&9QXpO4(E{0>~z=3c6-&R2SnsNaB>OSk>z`gTF4+3dCPH)w%I&S_GQV=|~I2ar+&<*Bz; zN1DMlK;t(_QlKKPy{O(`qlVIa6V(k((E{pk`e)KGof~WHnRVU0k%)^G^Oe-KJ+oCz2s@kCEZS3 z2eHD>OU2ulrELW*FtSFV(TqBTH=p9Gi<}4~<$5AmXwNGNVivCT#;17UloerJhxw z{x3?%`D4(Si9*CbzB(Hi2+bBk9oP}MnNY4|cp%dDlvnZ82lXnM+D9jyn5FErx(LUy z+e8r;RIA(;%2{{j9hQF4RQC$0Sjvg4oVJCuUjIBh706(N z<$I_YSaLDZjV|GF{XfDs$2?bxaQ0A?{~s!b*I-UUBV5KfK(QTY5ec-w>Yi`oBe4Z| zm1Oa(zLJ;tDX!nV?onaKgjmkSKvktZ4$Q z4vrhlQZ*#(J*it}7H1lt}w|!CHcxg@{I1E!lEzT>&#VTU*ukxXlfJ{(*B7n@i^Wz_hU2 z%KOI{IkF^*4~f@s5!V~dJ>!Om{K4x#+jds%!ZO*gxjcom^c*cfbd#`(SBG9I^s@Yczf8`+)xqDh>k*yvzJtBx9$v&XDC z$&@NEJ(QM=+_1h&37u!A2Q6RhTLilWKBjk>jzR=^>Fw+5_e;y{Q%reW;oNdQs-wqU zbJ&u#-o)XufQ8G-vZwZ6jCYzPaC12gkaDFyDE~`gcFPySfa=nMIyE?XtMW%X$~9-v zOfn!Q>z0>$!on5v=zi&}W7fZ7E*I_EE~m)cBh*hbkNY-P2Hd6wl8>-rk&vz zn1Q~{VaL&5!vZUoJFU~6c^5(76j?D{t^$e?obEQ8BU$6do!?d5-2RTwU5eWR(+ zm4All*^)2D%MMXPt>}=BMgGUIm-^E`KAuh<44q2ghLhli%g%;BcY!BKsi7}-i|DrG%P*ZmeYUw*-KEbN!5$Bfm11K`j~77HmmXqD$M^`JCR7aF!x#GWsJmyZl=t ze~vLPy6!h(q7sJfJNkmaNo$}%1nA-2RY7o$UeKcdCj&p!>lJJ?WUa%!k)m?^#XSvjS_ z;-aNB?>LjnU&53!Jd5eBl4|y94EuJNhehrxcZ=%XmoGT*0P4A0}4 z9nXI^zWh~6<#xk?i5YKyXH?;J9W*KA?_zGtt2{=I{2o5Sp+FCCw3}t>Gbv3=Mr936 z#|IzYt{PCOHQ%!cU!(t=Qn*^5iz>=>hjZpnmruF}QXQUkx{*3Udd%0mwRqF+wtVOo z{RZ0~2`fgd@c#{ZJNjE3F6{6g`db4M;S4q+K9tysQ8}SW`-{<0Y*dp`Hmy?W zc-_R0X3_HIxXrjJ_V+3!(fS5KKVId9oXQduuRP#1@A%1+Hb^HF?9rAwD`8mZql(r3 z-iWx?Q7F0p@6`;nJh`dv4$&)acEOH}gn%9D(BPzaWbeoQKdr?Lzrv5}| zWR5Tl?}b8WYv+UYV`{+?;@J?pCqfBaDfrNIH(-1Z#izU`?#kxPt}HUo#3JV3Z8$E~ z=dw}^I_yfJ#cwf+*BXSaa(VAMlb$)zh$VxBuxUV=1V@-0YWL-x-XjA7X3evB_*G6) z(2teuy>Z|+ivc}Ord4AVEtksT1$ZA-Z@;h({$eM?Ey=(>oVz<9_1filijeB&TIQ!y z1ueR@uh)Sy&W3p8sE}Y(i$jR!|1^L?z6i3L2@?tXTUtlAK9!$txPG-gCc8bL&ya8F zw6-VDSlrF{8&{uqjHJT%9w}v6dE)(+OKI-v%~TADywIJ`7qX667S-|4LNgLZpA$W` zQP=!F4@Vsko5>fg>}1v|)+3AN*m5b&>w;besNphuszufL;R!S6UzwY1(2 zX#44n1~wEd@zXi&^L~8F!My!_Krj3x(Jn2b*BxCqR2Rt(R!iyGbf?#Soy9QCKou&R z#y&#&%tLWWnP@x%lGwO2m1oLP(JAL*`0Z_*M0Ild8=L%e{s7T`W#M-1Vco_>%qT)! zM!zmgVxpXO2sZru`b#(8NF$(+ytp@Qv%fH~{z)>{Ao}_7wCwAR4r!;gIPF3zZy4b; z%#;adn|a!F`MVk!n3tfLld$1exBDjUb%e?y-QTu%(hD*6t&!vE!xb(!SSzPPr?heE zXzp$vClHj{_uW1Q~ugX zec^T9jUed{YjKGWPISkPe`cOn=*k2@A?$mPUWP+)@u80%t}7?X38tS7)qz-BRr);g ztUdfWtCaVN_Q`K&>bMx9(=H2%y!Oq|PXF%v_HLhu#-)+{>lzb3x+VZyHZ*j#>%!?7 zIa&PiUuaL}%9@*tTBBd^?fB==fWsAjI?0b8{>60;=g`;S&{Tk9JfFgVWtsy3#*j)_58C4%vt59GXv0vP8TWq=8m8+Sv7g8wN@8&*;au} zGu5H=Z-@{(B@fe|q)+EM&$I`!zYt7(6Q>{j@iZ^p&0hJnP<0pJUqYyj#YSK4fMM+Z zGI-3q_;XVs-1#cpv`tX0{q`Qs*#e zn6|{f-`X2Bzq=PoNfFBPtKP(A=@sTd3gH6(e2mzr-Rzb`=`!`9Zp-ilKUgS^tpQTq zA-2ylB&YT~AN#XSl&+@bqq{v1;AIJNg3A~+fp zFYfkao^&*_AX26cfUfM!FALn>)`kSzygOX0Dwp}oi!FdJ#RgV6iBd5s(m98WR^do) zzLgZi zN)XmMrAi;wL@l*w3XF6=dNAo=WVQOsUOPzg@Z@sF;-vccKkFA-=BE(gVj>@KL#r&} zX;uEKtSU;Dbsibq{PIk1{RX938X@~9z>^ZH`waMCll2U*0b)TMyfM#zd>~O~9q3QX zrsAc%+B7dbzzU81_{o#Qo1l1SQV<)P|6wV(4gamp-$p4^u0*o(N;>A^=C<$W7*hlyYNfv|LT{hqcOBCWxha=H@-T2uTuRn4nr{>CPPDNn1|I z@=t&CxYa7trpx9tY3`kVO1o}1*Ec+o@_u~j3~yB|GBop5z%j&K$w2oy*eTF8p6lF`03?1-hI)jHKQ(| z2ZAfNU5(|Z%dP6)QwYBmF4qYC>t88G{%d!93_=mfo94V9Uh{3?={XMt+Z6~|KG~D* z0={zd^_3~Fg(2?=RzS!HDf!u&SiEBbz-4{|2NXCZKIQch{ApMio?`f#^$0nz|MIMQ zj0i}pv2k=>pR7wWZkO%-{e;H29ff~uoV}AT#tU){$@E6_dCjCgm3;Z%G3VbWKI6N& zmYvR?PfmO>7myk~;*0};_l9aNa||p%3qS3MQ+Rq-pidIX=W%l1tls3^OGCQM!cdBt zKHTar44%ssTzF-_sp~WuN=aGcw+^O3>+MgaPtTx7Voh)Re>>(g@F#p>bc+5bN^QKZ zjtk0Q53iIO!~;q~rZI>-Z~rhvODb;`VD}dv|>rS5HaHwIt6g#!~T-3OEyl)EvV} zyp|aqymQUkycf9JD~cz+IUM#Hna)CD)h8?|xi7Sr?#i#Ol zz~gh~m(FnA7T3?qo6`3@YZ^{#fD}<$E4hS zPvPpQq}?Bdk;n~?^dtID^`@8$7#T@C78#qMDQ-d@HtB`xYjhOmRP=)A@P4q=mJXFv zJN`)?*X`*_N9~+t*ZIxJbkf*dsAT&4oHK3XY;XB2+aDK7p8kr1<(&9d@z&UJo(3AkjO96dHdEyuM^URvYNNt7$WiN8btJ;fJMeFz$+lXux#V|ah zeN?X&e>m9s#5ndSnA*>t1#pQKTRD@XD@XBg+l%V$&5=AS#YKo_M{TvTy)p1vw>I=O zM?+&cLH%C<4)osUI!)ds<9%IJoq}y&q}(HY_EL~LlPvT_jAgej_unE)Ua!WB$-#9`mK5`)8^ z|68e1?Bc$jaUmsoFg4yo(3}CH?5FwvdTxEF)hG ztQiwQt>C}dU9GF9n9~MW!HxU&z5f#P<%Anbt^HWKEz{wjjJa0ktcX`5BAH1k=u>O> zw5y-;m`VGz1{*S*P5ahn#ie0t_yHi6*wV@2^?x2a$%2z?$ zYwo;1DkX5CZxI!nUQKPQ+suhl>st+Rq_b*GN5tsLIuyRtW zpxXD6l2R}RP;(pzpK?Y1+ec%HBf?Gsy4{=%^8ea3XaEg4Cri9ZF%rm1MovGv``BKR zP_KFFC;Xuv-C+QO2P;iNZd*!5#q)mO*?;9(mVhImsUfa(uA8POJ}GLqK8d@{$WJ$F zr+^(x5P5Hl30EnEfk8pW;!4X_6%XaG%tpnpH9~=kZYGGz2pj9?np@)fNT>=h$FYQo z$=XXnj%&3%r``$BpjGP3VKJ8d#n{@>V=AKYjzsegT#lk&SguwZh|3Lv(W~fFRyS7t zT29O71I&9TOSALrv8bH#P+Y~QIDc2BVqA9%k-?S3EAv-xumphRT4QFyZ~ zqy>{ZSy&7Hrv4=)CtpIi8Fb@s{z?;_V8AhYuj{|=ss^aoCmXx|^+HahwnU&$qcw3_ zQ5I&%QplW>DwH1t-Hw>Z($K05Xlm9#thwa_#<>gJvtUs6o9!9>y<+F|z6d&SmH6xd zv8K)1LL&47g2*^%?enZjst^w_+`ql11>sQzLT1eflIeMMN>wNI>{H+t8 z{G|^q7<=>UE)~Il&gccT+_K$&vlOz?MdJ6f92qaRy!Li!*nnSX|DW2v{2$6MeE+d# zXH>E;Gh+#roha)V24!E8B^hFf>>(kHA?qOCmXakC*=66dOUN=QTO}!5ifmc>oays< zy}p0J_XlI7-T99L{YQg2(NYkG`Eff%dluiIN2}j@c)c3y{a+WzkXKKL|1->m7GPTloJOV7jS70Qk&%jag%y1O;Mm~p4ubx3 z%3g=8w@lP4wWic=b)zur7?`&{;Gc&bAD`yWlBC6_HU@69G9v-p`-yh~dedf7Knlr| zKKe~nsf}iVugg7L$M-$RsK^7L`|7Gx4gGc!stt81B1vW;*r7~y6Ywx9CRJJ}DF+@4 zt5SrixfEhEKDvEF_xLW5L;(0SN)|BPp!n^I{;Xlokd7jyC3V*geKW8M+@=UQ-wRwe zC9Brp;ste=b~`!6(1zu&2~OORLp^%WuWx`1>EHrcJZjPy@?j?r?0#FZi%B66cPXS9 z|B`p5NhIc{x|c0ZzEg(pCRM2trNM)5h8FO4Ms9S`l2m7SqIf!4(Nb8pP0Z9K)CUC%f483u*2KU zp9?$VYc|+qy*7Xeqw$G_HHAl)!8lE4!GlU+&AeKtfBn_>dH}M5Y!jJijRhwVjbl>h z)(r!0eSG?*7Cb@XEZ{)J;wGK4Rk$=~NU9XL`}V3`hT=JjMnF1)Vgl`zUx^-Vsfk>g zwbIOgWv+j=H9EWCt?vBHFmT#9k|5!m!-&8rSKB^{VczQeuUWlqG$N!YsM zVZx2xfHVOzL{1}%l4N&hDIfZnHj%=4lX%v0)Tt7{IjIz~?^&$(?f1;ie~DgT0HAu) zSR6pq0V04yhq~bZWn!yTl%~ve7`ho@y-t8_yenke*W)IiKzNBBX9DQ+7aEN{ih4ER z=;geVm<=s3A|CK-Px8UWtinQWu{x@|6V$WoAJ6SDz@n*&3__kb8aw?pA!SSwA}XW^HQ`pbw7%xS>sj+TM>7ia(0*V6afda>u#~%T`^C(ca2gAEvf2w&CT6R zy~a!T=zkHgMi>v_CsVfFiav>z)DeIksa&<%{4;EXTKu0F!|3IzXoT9P1ef0Q1H$`E z^lXob|D;qElhaVwLk>6|H{bf`8?_q`I9JHFNC`rhH(wJtlbh#n`|5Lcb2WYt@J@LW?TcyV$(FCPc z&`;yxtoKbl4Ez&)Ghn} z7hA*@^8luzaPkz2NQ#H}Vqq|p(L~Rh0Q<+LPMLAzyL51{XR}rV4DvsC6NP~tQIR>* z70atVsh-*xsvA8Vo!SeIoObU=cof4Z>t?|giuK zgk2EVRfE^<yJAj-7en6r@jJ=I!Gl!XEGUl z8z^l636Gtm5Ktgx^xM=KNvV2jJg;iArS^kT5zKDQpi@tP{oTj?GIF%Q`WwuRim};M zzqBXcF+Jyh-bin`rghfD!P8aJYW3Lv0~n4|!DJ_DtRFkrk9xj8-^QmBf165&m2Zw3 z@D_<{Fw>az=^+`pJx~|+Bk8}_aW}YiFyuRclqGWw_X$ujf5bnmnZLgG<&C%jQAHdi zFzz|%s5`nbv*J1#T7*uiFCJ_<7i-N$+OZ*4D%w~OQNR;)UOah)U;G42ee&|{iYdHv z#=BT?++I&~qVtTs`(bOuz4pfErKfZlMRnQOadbMYXSBkt%7j)rQe7EflQM9?>OWSv zIaW)->$C6zvpM)p(ajcd$JXA0Z*|bIH|*oCQ%F>3WZwA?D%Ep6h*fieoLUxxr>m0C z=Z=j!<5>l}#VKc-lCP_#7{=ypBr@3An{g~`DizlF!`hA{uxtr7Mlpvw~g zg%S{&K#VN&77v;p9Odu?L446`lJD00d|J-xC4dH=-R3Z3$z9^Ee>;JGB3SD$SMvGo zxvYV3w&n}r!$hwmxK_6U*vYrcR2`a{&#jkSl?AR;iap}IZ_~{+Csak!>^fp_9#duw zJg>O3CzIFeWECH5Cw9iKnhU8(!JW+cBeAZ^ux;(Iqvw__Rgw>av1|xS7wr zfh->SpUYcs@p}a?%*xMl~Rgjz+h+qko2%NplB0J8$Y2SPDD=jezMg5lI@ZffQ!OCHCcK8 z&{>}Efs4y9U4d4~$U+(rby*@eojwD|0<6}!-nwvF11fM+63!eYZHNZ^s*|f>i<`}F zZX0D9sAMW}~ z4k0=OiIA0_3DZ)XlZ`7XYyh7M&=(RT7j_(-FY7*xj5ez^jl!Ae7G;X?M36ZOiUzcb zrb-EoZDEGameID{`VyuETHCcP=Zq5bztNNPMgH?DE8{e61*$P`9TZlKvUAD=@2gyY z72{HIPCE$PFBJLC#C@h}3Vu5c(0p6FneF6J4!Qw_^Bio09K|~G?1pS`oFyu6M9LcN z&|(JYZ)GpyO-=xJmc*4U?>W9#*KwoQ?6x1NycIc<7won`RZAQ%b- zF*1wr6GaSS?N}Mh2qeRfGN&FN{;OY$0m%bv!&&xnes0Lsqrg@x!voJzEag@y6|nRM zoe0*!r=|GV@Iw+{?S66pt3N4DF*k*CYK8S=Vc$yDQYW?b8eZ-6r2P4mDq*-J+{p>s z>;G%QLpQhG>dJme!F}Lp!6sbzH04@`y|zDQ@LBX`i`Z#pkirM3fZ7fd9H)qMvxmX- zy~_XW?GIDWXU+8CEt|!`YBL=_Op2#UF)IG_XS(k)(l=^l?zF{F4ilxggn(9e-5lO! zlB0kg)&!U9z;8e0x%#=d9C$v2dk#?*h#el$l@79-f=SSWzhN)60?MRFOF&J-?*@ZN zd@Qeek9Cuyw5JUks0f(fCBGgx2=d+g#^+KYs-60;3iXEj552h&qzj^?=avej`@R$< z9&bgGmuvHRYZK9U$A+IGAKAVR`R4ftgqjG%s2AQn-YWgagYG z6?2I~@ZQZFT4R6u9vHozNZAvbvO6CX;WFSze6yMXATCxS>D$e!-bMAft#<6n>A1eN zGO;w?TsiXkh=`v+>&R+-Mv&leYn|qDak&n95a?++GttPlNB`7$>o4bUZJ?EYA zgRxN&T1~xP1nHiD!jLAL*uqDofj00(x?@R!gpSaX$K4My`d^ZPqL=E@3cXi0-3Z}n zI}lR|<59KHa#_z`PSpCWleZ+n^`N<~=;_!EZp;j7to!cR$Gr<|;Gn&EwmEWWh;Hvb zTaXcrVLjW>Q1yN~Jv{)?0}(G881KT{+xpLD5~u^5WvYKp9P$F0CA!vt&~(=OC=}xY zk87qD5<&nhqrXkodd;(#TwJ#L;!D#95P+)TbY z@S{*ik++svIF6W|9tyHu_25i3&-)xKimQnTG=cXfQdrs}js)FU78s1ZJMPX$!tg8zM! z__kw6jvw+3P!x)| z+~V9mwYvPcx(8s!{*z=2rbx0)Nq-=5aw?cVn_((Fb7l(|FF2n?903nl5qLmmj5l7l zjsB;+(_GOVFjuch%vgGtkJG;pCgLPT8mXk(m_198B6ZXM_2!8Pz?`si`Ks97F_Ab0 zlr}c^@9AaGYD(&;Xc^8tl!3F$#+36c1PcO5iLhiT5+^JbL~Ug?nrm2N`!e%aRTZvy z%uQ+%vBlV0)>5r*+08opvHXz0`$Qc%6pA(?66psbd>U<5JzwMlr6?5Nshrp=f_-te z7g$UASo;UM<;ot#0`qm(+3S?wGuZx?D7@epY~EUxjzYbualvckbt|B4aqX)KwJ_0S z{IqZvFZ@L6|D%Gs0vbL+8*C>jfL^W&S|kLCoLz0e%5NUplXz9PXonG=b9Hdn<$^4mNwW!an$ zp*@Z?J*FP-=&VsTmJT|ZxoVd+uaGLk8kd!o!FVFF@6qj|Q*P%3UE5nGme-O$9F5#t z>R-)Jm@Cfv5bLm%_bpic$;Pm^tqI*HlY6#rNbDQxsTxOH;rr|C9sI$Q4NR<2h&)q7 zu4yITVMvFt@B5Nf^^mAiI!mZeJmpOlUxV?twG-@HL*Lqsi2d5#gK6SI%%Ws_QHs`@988txxRlZKXABu7z(M`L)bFTtQDo{gB28YFw6|z0Jcv z#FB)aZwi*oyUt}nmBs%=#&0q0!qu+$Qv>!aL*kc_32$Le)Qp~I;x7mbTS${MP>miG_W=cD#YZ`3#n2mkz} zC8&NcYi8GKb9bg}`S2TtxfD88s8a*YAN)3={A^Y(hTdv_vp>P^`(rTnV)&94egkuG zcz<(jF0JXP_vHT1A18OTp)5xAM6NfnL7`eS&{CW(6_l?MNef9_6{dpXPwGPv3$&d7 zA1?hs5w9-8p@`yl7gMn<{->c%vl*ot(4;(gF<8yj_bvH%vBeLLY54bUl?e%Hy4$N! zUh^`L*RFs3)9fz_b^78I`J~9z9|H%qL3QM-ft5)cTv9ZUjq4m%Ib~MD`UF(f#3oMcQ_?0;v5^Mz>pd;n!38f0S0S^rYEk_F;PK}zy)p=3Wk|YyohkzJ zokeiBJYgjTIhd1HP6yeTRVM#Q4y7X|4w5PTNG%gV&|t#1$mba$lak@j6N(vas>rx% zR>-9Ca&7PH`<1p}T7(BWwspIIZAz?i`tbqTE)GNK;>7hqfEo4tS^&dI>L0EIy zW~zvrxP#9{tlYqF3`P{p>!frsgJ;pLX> zBJ-_KYN)n2Et~!2WX+_i+wHJbZDLSbZuvnQRm4Jl+9k&+(lfK5*dBF8NV&7=f*J6 z{qJ>i=>#Ml4F;YLbtXvgqKRkoYCahL4L{s(ETM%AmWzVw8ouB18Dqn^fN^_<kZw&iT&}+_Bk!^fo1h?_gw=S$mkvU8WUnLAwXXe7&>cW zD{QC&Eb4V+C1sY!V_0$qHHmAN{w|GHTT?>;-;u#Sw$`tbJ)WVJqUaxo`<3503(0I$ zxWtbZUyT26J%vTCiz))~-v`)mRTBepaD?GoA~N%}6*c7dG;f~pY%g`bJ1a-ijAA#! zh*JgEsi8v8lAw6k3M;{-^I!#vLRGQD$jXt;iUEfuxfF{(Eu!K2zE43bCT7ZQ!g5uQWb$dYP-Dkc;``zky|r zGmHJ}#>ug(7YKFCQ>JAx%*fNPXd~z*MyTmiUw6?H<8M%R=6c+UjM8`cO!IFVZxTQD zCNV-=PKd$Pa@Ruh<`j3EY)!M}3mxsbi^VGESvozJ#e(Gr`wFDX1mBI*5w(s&jT{Vo zi~Y|*(l3XR-XmL=;r%ZsLC-pd7Qg=P8tknhLsdxC59|0h+XCXX z6=}+qxQbEAyp8nZh{OsVjSLpA;nSGuhNx~F`%kEeZdnoQuMW)95@mn4h-VU8Q|8_J z@1BUz$~&%HU8YO%`rx}1tLbZW6ngc%*uHy+K)NBNXvp+(fTkC6S^CdqkuiP8_q$6^x$URZZ?oFJG-W3WYuP=rmcV}tfH1mO KQ5D)Z9{dk=rMwUT diff --git a/web/src/components/AboutDialog.vue b/web/src/components/AboutDialog.vue deleted file mode 100644 index c7f1037f..00000000 --- a/web/src/components/AboutDialog.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/components/InitDialog.vue b/web/src/components/InitDialog.vue deleted file mode 100644 index e25ff56d..00000000 --- a/web/src/components/InitDialog.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/web/src/components/LoginDialog.vue b/web/src/components/LoginDialog.vue deleted file mode 100644 index 087bb3e7..00000000 --- a/web/src/components/LoginDialog.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/components/MarketPluginCard.vue b/web/src/components/MarketPluginCard.vue deleted file mode 100644 index ad1379f8..00000000 --- a/web/src/components/MarketPluginCard.vue +++ /dev/null @@ -1,181 +0,0 @@ - - - - - diff --git a/web/src/components/Marketplace.vue b/web/src/components/Marketplace.vue deleted file mode 100644 index 89c0ad29..00000000 --- a/web/src/components/Marketplace.vue +++ /dev/null @@ -1,228 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/components/NumberFieldData.vue b/web/src/components/NumberFieldData.vue deleted file mode 100644 index fbdc86e7..00000000 --- a/web/src/components/NumberFieldData.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/components/PageTitle.vue b/web/src/components/PageTitle.vue deleted file mode 100644 index a4cfb4f0..00000000 --- a/web/src/components/PageTitle.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/components/PluginCard.vue b/web/src/components/PluginCard.vue deleted file mode 100644 index 899e0329..00000000 --- a/web/src/components/PluginCard.vue +++ /dev/null @@ -1,257 +0,0 @@ - - - - - diff --git a/web/src/components/SettingWindow.vue b/web/src/components/SettingWindow.vue deleted file mode 100644 index ff4f2735..00000000 --- a/web/src/components/SettingWindow.vue +++ /dev/null @@ -1,287 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/components/TaskCard.vue b/web/src/components/TaskCard.vue deleted file mode 100644 index 167f6f41..00000000 --- a/web/src/components/TaskCard.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - - - diff --git a/web/src/pages/Plugins.vue b/web/src/pages/Plugins.vue deleted file mode 100644 index fcdade39..00000000 --- a/web/src/pages/Plugins.vue +++ /dev/null @@ -1,325 +0,0 @@ - - - - - diff --git a/web/src/pages/Settings.vue b/web/src/pages/Settings.vue deleted file mode 100644 index 58a39699..00000000 --- a/web/src/pages/Settings.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/src/plugins/index.js b/web/src/plugins/index.js deleted file mode 100644 index 3ca203de..00000000 --- a/web/src/plugins/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * plugins/index.js - * - * Automatically included in `./src/main.js` - */ - -// Plugins -import vuetify from './vuetify' -import router from '@/router' -import store from '@/store' -import axios from 'axios' - -export function registerPlugins (app) { - app - .use(vuetify) - .use(router) - .use(store) - - // 读取用户令牌 - const token = localStorage.getItem('user-token') - - if (token) { - store.state.user.jwtToken = token - } - - // 所有axios请求均携带用户令牌 - axios.defaults.headers.common['Authorization'] = `Bearer ${store.state.user.jwtToken}` - - app.config.globalProperties.$axios = axios - store.commit('initializeFetch') -} diff --git a/web/src/plugins/vuetify.js b/web/src/plugins/vuetify.js deleted file mode 100644 index 1db02919..00000000 --- a/web/src/plugins/vuetify.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * plugins/vuetify.js - * - * Framework documentation: https://vuetifyjs.com` - */ - -// Styles -import '@mdi/font/css/materialdesignicons.css' -import 'vuetify/styles' - -// Composables -import { createVuetify } from 'vuetify' - -// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides -export default createVuetify({ - theme: { - defaultTheme: 'light', - }, -}) diff --git a/web/src/router/index.js b/web/src/router/index.js deleted file mode 100644 index b8572783..00000000 --- a/web/src/router/index.js +++ /dev/null @@ -1,46 +0,0 @@ - -/** - * router/index.ts - * - * Automatic routes for `./src/pages/*.vue` - */ - -// Composables -import { createRouter, createWebHashHistory } from 'vue-router/auto' -import DashBoard from '../pages/DashBoard.vue' -import Settings from '../pages/Settings.vue' -import Logs from '../pages/Logs.vue' -import Plugins from '../pages/Plugins.vue' - -const routes = [ - { path: '/', component: DashBoard }, - { path: '/settings', component: Settings }, - { path: '/logs', component: Logs }, - { path: '/plugins', component: Plugins }, -] - -const router = createRouter({ - history: createWebHashHistory(), - routes, -}) - -// Workaround for https://github.com/vitejs/vite/issues/11804 -router.onError((err, to) => { - if (err?.message?.includes?.('Failed to fetch dynamically imported module')) { - if (!localStorage.getItem('vuetify:dynamic-reload')) { - console.log('Reloading page to fix dynamic import error') - localStorage.setItem('vuetify:dynamic-reload', 'true') - location.assign(to.fullPath) - } else { - console.error('Dynamic import error, reloading page did not fix it', err) - } - } else { - console.error(err) - } -}) - -router.isReady().then(() => { - localStorage.removeItem('vuetify:dynamic-reload') -}) - -export default router diff --git a/web/src/store/index.js b/web/src/store/index.js deleted file mode 100644 index 5374ff1e..00000000 --- a/web/src/store/index.js +++ /dev/null @@ -1,53 +0,0 @@ -import { createStore } from 'vuex' -import router from '@/router' -import axios from 'axios' - -export default createStore({ - state: { - // 开发时使用 - // apiBaseUrl: 'http://localhost:5300/api/v1', - apiBaseUrl: '/api/v1', - autoRefreshLog: false, - autoScrollLog: true, - settingsPageTab: '', - version: 'v0.0.0', - debug: false, - enabledPlatformCount: 0, - user: { - tokenChecked: false, - tokenValid: false, - systemInitialized: true, - jwtToken: '', - }, - pluginsView: 'installed', - marketplaceParams: { - query: '', - page: 1, - per_page: 10, - sort_by: 'pushed_at', - sort_order: 'DESC', - }, - marketplacePlugins: [], - marketplaceTotalPages: 0, - marketplaceTotalPluginsCount: 0, - }, - mutations: { - initializeFetch() { - axios.defaults.baseURL = this.state.apiBaseUrl - - axios.get('/system/info').then(response => { - this.state.version = response.data.data.version - this.state.debug = response.data.data.debug - this.state.enabledPlatformCount = response.data.data.enabled_platform_count - }) - }, - fetchSystemInfo() { - axios.get('/system/info').then(response => { - this.state.version = response.data.data.version - this.state.debug = response.data.data.debug - this.state.enabledPlatformCount = response.data.data.enabled_platform_count - }) - } - }, - actions: {}, -}) diff --git a/web/src/styles/settings.scss b/web/src/styles/settings.scss deleted file mode 100644 index 3e36a279..00000000 --- a/web/src/styles/settings.scss +++ /dev/null @@ -1,10 +0,0 @@ -/** - * src/styles/settings.scss - * - * Configures SASS variables and Vuetify overwrites - */ - -// https://vuetifyjs.com/features/sass-variables/` -// @use 'vuetify/settings' with ( -// $color-pack: false -// ); diff --git a/web/vite.config.mjs b/web/vite.config.mjs deleted file mode 100644 index 31052bbe..00000000 --- a/web/vite.config.mjs +++ /dev/null @@ -1,64 +0,0 @@ -// Plugins -import Components from 'unplugin-vue-components/vite' -import Vue from '@vitejs/plugin-vue' -import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' -import ViteFonts from 'unplugin-fonts/vite' -import VueRouter from 'unplugin-vue-router/vite' - -import { commonjsDeps } from '@koumoul/vjsf/utils/build.js' - -// Utilities -import { defineConfig } from 'vite' -import { fileURLToPath, URL } from 'node:url' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - VueRouter(), - Vue({ - template: { transformAssetUrls } - }), - // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme - Vuetify({ - autoImport: true, - styles: { - configFile: 'src/styles/settings.scss', - }, - }), - Components(), - ViteFonts({ - google: { - families: [{ - name: 'Roboto', - styles: 'wght@100;300;400;500;700;900', - }], - }, - }), - ], - define: { 'process.env': {} }, - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - }, - extensions: [ - '.js', - '.json', - '.jsx', - '.mjs', - '.ts', - '.tsx', - '.vue', - ], - }, - server: { - port: 3002, - }, - optimizeDeps: { - include: commonjsDeps, - }, - build: { - commonjsOptions: { - transformMixedEsModules: true, - }, - } -}) diff --git a/web_ui/.gitignore b/web_ui/.gitignore new file mode 100644 index 00000000..5ef6a520 --- /dev/null +++ b/web_ui/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/web_ui/README.md b/web_ui/README.md new file mode 100644 index 00000000..e215bc4c --- /dev/null +++ b/web_ui/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/web_ui/eslint.config.mjs b/web_ui/eslint.config.mjs new file mode 100644 index 00000000..c85fb67c --- /dev/null +++ b/web_ui/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/web_ui/next.config.ts b/web_ui/next.config.ts new file mode 100644 index 00000000..e9ffa308 --- /dev/null +++ b/web_ui/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/web_ui/package-lock.json b/web_ui/package-lock.json new file mode 100644 index 00000000..19c92988 --- /dev/null +++ b/web_ui/package-lock.json @@ -0,0 +1,5833 @@ +{ + "name": "web_ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web_ui", + "version": "0.1.0", + "dependencies": { + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "antd": "^5.24.6", + "next": "15.2.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "uuidjs": "^5.1.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.2.4", + "typescript": "^5" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.0.tgz", + "integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==", + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.23.0", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz", + "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@ant-design/v5-patch-for-react-19": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@ant-design/v5-patch-for-react-19/-/v5-patch-for-react-19-1.0.3.tgz", + "integrity": "sha512-iWfZuSUl5kuhqLUw7jJXUQFMMkM7XpW7apmKzQBQHU0cpifYW4A79xIBt9YVO5IBajKpPG5UKP87Ft7Yrw1p/w==", + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "antd": ">=5.22.6", + "react": ">=19.0.0", + "react-dom": ">=19.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", + "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.0.tgz", + "integrity": "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.23.0.tgz", + "integrity": "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", + "integrity": "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.3.1", + "@emnapi/runtime": "^1.3.1", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@next/env": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz", + "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.2.4.tgz", + "integrity": "sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==", + "dev": true, + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz", + "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz", + "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz", + "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz", + "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz", + "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz", + "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz", + "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz", + "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmmirror.com/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.6.tgz", + "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz", + "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", + "dev": true + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.17.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", + "integrity": "sha512-U58sbKhDrthHlxHRJw7ZLiLDZGmAUOZUbpw0S6nL27sYUdhvgBLCRu/keSd6qcTsfArd1sRFCCBxzWATGr/0UA==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/react": { + "version": "19.0.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.12.tgz", + "integrity": "sha512-V6Ar115dBDrjbtXSrS+/Oruobc+qVbbUxDFC1RSbRqLt5SYvxxyIDrSC85RWml54g+jfNeEMZhEj7wW07ONQhA==", + "dev": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", + "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/type-utils": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", + "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", + "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", + "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.28.0", + "@typescript-eslint/utils": "8.28.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", + "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", + "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/visitor-keys": "8.28.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", + "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.28.0", + "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/typescript-estree": "8.28.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", + "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.28.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@unrs/rspack-resolver-binding-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-EcjI0Hh2HiNOM0B9UuYH1PfLWgE6/SBQ4dKoHXWNloERfveha/n6aUZSBThtPGnJenmdfaJYXXZtqyNbWtJAFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.3.0.tgz", + "integrity": "sha512-3CgG+mhfudDfnaDqwEl0W1mcGTto5f5mqPyJSXcWDxrnNc7pr/p01khIgWOoOD1eCwVejmgpYvRKGBwJPwgHOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-freebsd-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.3.0.tgz", + "integrity": "sha512-ww8BwryDrpXlSajwSIEUXEv8oKDkw04L2ke3hxjaxWohuBV8pAQie9XBS4yQTyREuL2ypcqbARfoCXJJzVp7ig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm-gnueabihf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.3.0.tgz", + "integrity": "sha512-WyhonI1mkuAlnG2iaMjk7uy4aWX+FWi2Au8qCCwj57wVHbAEfrN6xN2YhzbrsCC+ciumKhj5c01MqwsnYDNzWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm-musleabihf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm-musleabihf/-/rspack-resolver-binding-linux-arm-musleabihf-1.3.0.tgz", + "integrity": "sha512-+uCP6hIAMVWHKQnLZHESJ1U1TFVGLR3FTeaS2A4zB0k8w+IbZlWwl9FiBUOwOiqhcCCyKiUEifgnYFNGpxi3pw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.3.0.tgz", + "integrity": "sha512-p+s/Wp8rf75Qqs2EPw4HC0xVLLW+/60MlVAsB7TYLoeg1e1CU/QCis36FxpziLS0ZY2+wXdTnPUxr+5kkThzwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.3.0.tgz", + "integrity": "sha512-cZEL9jmZ2kAN53MEk+fFCRJM8pRwOEboDn7sTLjZW+hL6a0/8JNfHP20n8+MBDrhyD34BSF4A6wPCj/LNhtOIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-ppc64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-ppc64-gnu/-/rspack-resolver-binding-linux-ppc64-gnu-1.3.0.tgz", + "integrity": "sha512-IOeRhcMXTNlk2oApsOozYVcOHu4t1EKYKnTz4huzdPyKNPX0Y9C7X8/6rk4aR3Inb5s4oVMT9IVKdgNXLcpGAQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-s390x-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-s390x-gnu/-/rspack-resolver-binding-linux-s390x-gnu-1.3.0.tgz", + "integrity": "sha512-op54XrlEbhgVRCxzF1pHFcLamdOmHDapwrqJ9xYRB7ZjwP/zQCKzz/uAsSaAlyQmbSi/PXV7lwfca4xkv860/Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-x64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.3.0.tgz", + "integrity": "sha512-orbQF7sN02N/b9QF8Xp1RBO5YkfI+AYo9VZw0H2Gh4JYWSuiDHjOPEeFPDIRyWmXbQJuiVNSB+e1pZOjPPKIyg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-x64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.3.0.tgz", + "integrity": "sha512-kpjqjIAC9MfsjmlgmgeC8U9gZi6g/HTuCqpI7SBMjsa7/9MvBaQ6TJ7dtnsV/+DXvfJ2+L5teBBXG+XxfpvIFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-wasm32-wasi": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.3.0.tgz", + "integrity": "sha512-JAg0hY3kGsCPk7Jgh16yMTBZ6VEnoNR1DFZxiozjKwH+zSCfuDuM5S15gr50ofbwVw9drobIP2TTHdKZ15MJZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/rspack-resolver-binding-win32-arm64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.3.0.tgz", + "integrity": "sha512-h5N83i407ntS3ndDkhT/3vC3Dj8oP0BIwMtekETNJcxk7IuWccSXifzCEhdxxu/FOX4OICGIHdHrxf5fJuAjfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-win32-ia32-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-ia32-msvc/-/rspack-resolver-binding-win32-ia32-msvc-1.3.0.tgz", + "integrity": "sha512-9QH7Gq3dRL8Q/D6PGS3Dwtjx9yw6kbCEu6iBkAUhFTDAuVUk2L0H/5NekRVA13AQaSc3OsEUKt60EOn/kq5Dug==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-win32-x64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.3.0.tgz", + "integrity": "sha512-IYuXJCuwBOVV0H73l6auaZwtAPHjCPBJkxd4Co0yO6dSjDM5Na5OceaxhUmJLZ3z8kuEGhTYWIHH7PchGztnlg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.24.6", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.24.6.tgz", + "integrity": "sha512-xIlTa/1CTbgkZsdU/dOXkYvJXb9VoiMwsaCzpKFH2zAEY3xqOfwQ57/DdG7lAdrWP7QORtSld4UA6suxzuTHXw==", + "dependencies": { + "@ant-design/colors": "^7.2.0", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.6", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.33.1", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.2.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.11.1", + "rc-input": "~1.7.3", + "rc-input-number": "~9.4.0", + "rc-mentions": "~2.19.1", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.3", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.6", + "rc-slider": "~11.1.8", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.50.4", + "rc-tabs": "~15.5.1", + "rc-textarea": "~1.9.0", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.8.1", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, + "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==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.23.0.tgz", + "integrity": "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.23.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.2.4.tgz", + "integrity": "sha512-v4gYjd4eYIme8qzaJItpR5MMBXJ0/YV07u7eb50kEnlEmX7yhOjdUdzz70v4fiINYRjLf8X8TbogF0k7wlz6sA==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "15.2.4", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.9.1.tgz", + "integrity": "sha512-euxa5rTGqHeqVxmOHT25hpk58PxkQ4mNoX6Yun4ooGaCHAxOCojJYNvjmyeOQxj/LyW+3fulH0+xtk+p2kPPTw==", + "dev": true, + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^1.3.0", + "rspack-resolver": "^1.1.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", + "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "optional": true + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "dev": true, + "dependencies": { + "semver": "^7.6.3" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/next": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz", + "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==", + "dependencies": { + "@next/env": "15.2.4", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.15", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.2.4", + "@next/swc-darwin-x64": "15.2.4", + "@next/swc-linux-arm64-gnu": "15.2.4", + "@next/swc-linux-arm64-musl": "15.2.4", + "@next/swc-linux-x64-gnu": "15.2.4", + "@next/swc-linux-x64-musl": "15.2.4", + "@next/swc-win32-arm64-msvc": "15.2.4", + "@next/swc-win32-x64-msvc": "15.2.4", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rc-cascader": { + "version": "3.33.1", + "resolved": "https://registry.npmmirror.com/rc-cascader/-/rc-cascader-3.33.1.tgz", + "integrity": "sha512-Kyl4EJ7ZfCBuidmZVieegcbFw0RcU5bHHSbtEdmuLYd0fYHCAiYKZ6zon7fWAVyC6rWWOOib0XKdTSf7ElC9rg==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmmirror.com/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/rc-drawer/-/rc-drawer-7.2.0.tgz", + "integrity": "sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-image": { + "version": "7.11.1", + "resolved": "https://registry.npmmirror.com/rc-image/-/rc-image-7.11.1.tgz", + "integrity": "sha512-XuoWx4KUXg7hNy5mRTy1i8c8p3K8boWg6UajbHpDXS5AlRVucNfTi5YxTtPBTBzegxAZpvuLfh3emXFt6ybUdA==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.7.3", + "resolved": "https://registry.npmmirror.com/rc-input/-/rc-input-1.7.3.tgz", + "integrity": "sha512-A5w4egJq8+4JzlQ55FfQjDnPvOaAbzwC3VLOAdOytyek3TboSOP9qxN+Gifup+shVXfvecBLBbWBpWxmk02SWQ==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.4.0", + "resolved": "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-9.4.0.tgz", + "integrity": "sha512-Tiy4DcXcFXAf9wDhN8aUAyMeCLHJUHA/VA/t7Hj8ZEx5ETvxG7MArDOSE6psbiSCo+vJPm4E3fGN710ITVn6GA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.7.1", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.19.1", + "resolved": "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.19.1.tgz", + "integrity": "sha512-KK3bAc/bPFI993J3necmaMXD2reZTzytZdlTvkeBbp50IGH1BDPDvxLdHDUrpQx2b2TGaVJsn+86BvYa03kGqA==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.7.1", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.9.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.3", + "resolved": "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.3.tgz", + "integrity": "sha512-42szwnn8VYQoT6GnjO00i1iwqV9D1TTMvxObWsuLwgl0TsOokzhkYiufdtQBsJMFjJravS1hfDKVMHLKLcPE4g==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.4.1.tgz", + "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmmirror.com/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.7.0.tgz", + "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.6", + "resolved": "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.6.tgz", + "integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.8", + "resolved": "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.8.tgz", + "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.50.4", + "resolved": "https://registry.npmmirror.com/rc-table/-/rc-table-7.50.4.tgz", + "integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.5.2", + "resolved": "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-15.5.2.tgz", + "integrity": "sha512-Hbqf2IV6k/jPgfMjPtIDmPV0D0C9c/fN4B/fYcoh9qqaUzUZQoK0PYzsV3UaV+3UsmyoYt48p74m/HkLhGTw+w==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.9.0.tgz", + "integrity": "sha512-dQW/Bc/MriPBTugj2Kx9PMS5eXCCGn2cxoIaichjbNvOiARlaHdI99j4DTxLl/V8+PIfW06uFy7kjfUIDDKyxQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.7.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmmirror.com/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.8.1", + "resolved": "https://registry.npmmirror.com/rc-upload/-/rc-upload-4.8.1.tgz", + "integrity": "sha512-toEAhwl4hjLAI1u8/CgKWt30BR06ulPa4iGQSMvSXoHzO88gPCslxqV/mnn4gJU7PDoltGIC9Eh+wkeudqgHyw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/rc-virtual-list": { + "version": "3.18.5", + "resolved": "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.18.5.tgz", + "integrity": "sha512-1FuxVSxhzTj3y8k5xMPbhXCB0t2TOiI3Tq+qE2Bu+GGV7f+ECVuQl4OUg6lZ2qT5fordTW7CBpr9czdzXCI7Pg==", + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rspack-resolver": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rspack-resolver/-/rspack-resolver-1.3.0.tgz", + "integrity": "sha512-az/PLDwa1xijNv4bAFBS8mtqqJC1Y3lVyFag4cuyIUOHq/ft5kSZlHbqYaLZLpsQtPWv4ZGDo5ycySKJzUvU/A==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/JounQin" + }, + "optionalDependencies": { + "@unrs/rspack-resolver-binding-darwin-arm64": "1.3.0", + "@unrs/rspack-resolver-binding-darwin-x64": "1.3.0", + "@unrs/rspack-resolver-binding-freebsd-x64": "1.3.0", + "@unrs/rspack-resolver-binding-linux-arm-gnueabihf": "1.3.0", + "@unrs/rspack-resolver-binding-linux-arm-musleabihf": "1.3.0", + "@unrs/rspack-resolver-binding-linux-arm64-gnu": "1.3.0", + "@unrs/rspack-resolver-binding-linux-arm64-musl": "1.3.0", + "@unrs/rspack-resolver-binding-linux-ppc64-gnu": "1.3.0", + "@unrs/rspack-resolver-binding-linux-s390x-gnu": "1.3.0", + "@unrs/rspack-resolver-binding-linux-x64-gnu": "1.3.0", + "@unrs/rspack-resolver-binding-linux-x64-musl": "1.3.0", + "@unrs/rspack-resolver-binding-wasm32-wasi": "1.3.0", + "@unrs/rspack-resolver-binding-win32-arm64-msvc": "1.3.0", + "@unrs/rspack-resolver-binding-win32-ia32-msvc": "1.3.0", + "@unrs/rspack-resolver-binding-win32-x64-msvc": "1.3.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==" + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "devOptional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "engines": { + "node": ">=12.22" + } + }, + "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==", + "dev": true, + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuidjs": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/uuidjs/-/uuidjs-5.1.0.tgz", + "integrity": "sha512-HAQPtUkr7t5Ud3uCwRcqtBRNagu/2aerrrBQE6PzgSluGijvFF75UaOq22Xw545GGviRjSLhc4c8CaSMI5h4Ng==", + "bin": { + "uuidjs": "dist/cli.js" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/web_ui/package.json b/web_ui/package.json new file mode 100644 index 00000000..be4677cc --- /dev/null +++ b/web_ui/package.json @@ -0,0 +1,28 @@ +{ + "name": "web_ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "antd": "^5.24.6", + "next": "15.2.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "uuidjs": "^5.1.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.2.4", + "typescript": "^5" + } +} diff --git a/web_ui/public/file.svg b/web_ui/public/file.svg new file mode 100644 index 00000000..004145cd --- /dev/null +++ b/web_ui/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/public/globe.svg b/web_ui/public/globe.svg new file mode 100644 index 00000000..567f17b0 --- /dev/null +++ b/web_ui/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/public/next.svg b/web_ui/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/web_ui/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/public/vercel.svg b/web_ui/public/vercel.svg new file mode 100644 index 00000000..77053960 --- /dev/null +++ b/web_ui/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/public/window.svg b/web_ui/public/window.svg new file mode 100644 index 00000000..b2b2a44f --- /dev/null +++ b/web_ui/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web_ui/src/app/favicon.ico b/web_ui/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/web_ui/src/app/global.css b/web_ui/src/app/global.css new file mode 100644 index 00000000..334cc8f7 --- /dev/null +++ b/web_ui/src/app/global.css @@ -0,0 +1,37 @@ +* { + margin: 0; + padding: 0; +} + +:root { + /* 适用于 Firefox 的滚动条 */ + scrollbar-color: rgba(0, 0, 0, 0.2) transparent; /* 滑块颜色 + 轨道颜色 */ + scrollbar-width: thin; /* auto | thin | none */ +} + +/* WebKit 内核浏览器定制 */ +::-webkit-scrollbar { + width: 6px; /* 垂直滚动条宽度 */ + height: 6px; /* 水平滚动条高度 */ +} + +::-webkit-scrollbar-track { + background: transparent; /* 隐藏轨道背景 */ +} + +::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); /* 半透明黑色 */ + border-radius: 3px; + transition: background 0.3s; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.35); /* 悬停加深 */ +} + +/* 兼容 Edge */ +@supports (-ms-ime-align:auto) { + body { + -ms-overflow-style: -ms-autohiding-scrollbar; /* 自动隐藏滚动条 */ + } +} diff --git a/web_ui/src/app/home/bot-config/ICreateBotField.ts b/web_ui/src/app/home/bot-config/ICreateBotField.ts new file mode 100644 index 00000000..fea53c17 --- /dev/null +++ b/web_ui/src/app/home/bot-config/ICreateBotField.ts @@ -0,0 +1,3 @@ +export interface ICreateBotField { + +} \ No newline at end of file diff --git a/web_ui/src/app/home/bot-config/botConfig.module.css b/web_ui/src/app/home/bot-config/botConfig.module.css new file mode 100644 index 00000000..ae201324 --- /dev/null +++ b/web_ui/src/app/home/bot-config/botConfig.module.css @@ -0,0 +1,23 @@ +.configPageContainer { + width: 100%; + height: 100%; +} + +.cardContainer { + width: 420px; + height: 220px; + border: 1px solid black; +} + +.botListContainer { + align-self: flex-start; + justify-self: flex-start; + width: calc(100% - 60px); + margin: auto; + display: grid; + grid-template-rows: repeat(auto-fill, minmax(220px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); + gap: 15px; + justify-items: center; + align-items: center; +} \ No newline at end of file diff --git a/web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx b/web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx new file mode 100644 index 00000000..af078a24 --- /dev/null +++ b/web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx @@ -0,0 +1,39 @@ +import {BotCardVO} from "@/app/home/bot-config/components/bot-card/BotCardVO"; +import styles from "./botCard.module.css"; + +export default function BotCard({ + botCardVO +}: { + botCardVO: BotCardVO; +}) { + return ( +
+ {/* icon和基本信息 */} +
+ {/* icon */} +
+ ICO +
+ {/* bot基本信息 */} +
+
+ {botCardVO.name} +
+
+ 平台:{botCardVO.adapter} +
+
+ 绑定流水线:{botCardVO.pipelineName} +
+
+
+ {/* 描述和创建时间 */} +
+ 描述:{botCardVO.description} +
+
+ 更新时间:{botCardVO.updateTime} +
+
+ ) +} \ No newline at end of file diff --git a/web_ui/src/app/home/bot-config/components/bot-card/BotCardVO.ts b/web_ui/src/app/home/bot-config/components/bot-card/BotCardVO.ts new file mode 100644 index 00000000..6f32b183 --- /dev/null +++ b/web_ui/src/app/home/bot-config/components/bot-card/BotCardVO.ts @@ -0,0 +1,28 @@ +export interface IBotCardVO { + id: string; + name: string; + adapter: string; + description: string; + updateTime: string; + pipelineName: string; +} + +export class BotCardVO implements IBotCardVO { + id: string; + adapter: string; + description: string; + name: string; + updateTime: string; + pipelineName: string; + + + constructor(props: IBotCardVO) { + this.id = props.id; + this.name = props.name; + this.adapter = props.adapter; + this.description = props.description; + this.updateTime = props.updateTime; + this.pipelineName = props.pipelineName; + } + +} \ No newline at end of file diff --git a/web_ui/src/app/home/bot-config/components/bot-card/CreateBotCardComponent.tsx b/web_ui/src/app/home/bot-config/components/bot-card/CreateBotCardComponent.tsx new file mode 100644 index 00000000..e69de29b diff --git a/web_ui/src/app/home/bot-config/components/bot-card/botCard.module.css b/web_ui/src/app/home/bot-config/components/bot-card/botCard.module.css new file mode 100644 index 00000000..ad7cc5d1 --- /dev/null +++ b/web_ui/src/app/home/bot-config/components/bot-card/botCard.module.css @@ -0,0 +1,67 @@ +.iconBasicInfoContainer { + width: 300px; + height: 100px; + margin-left: 20px; + display: flex; + flex-direction: row; +} + +.cardContainer { + width: 360px; + height: 200px; + background-color: #FFF; + border-radius: 9px; + box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; +} + +.iconBasicInfoContainer { + width: 300px; + height: 100px; + margin-left: 20px; + display: flex; + flex-direction: row; +} + +.icon { + width: 90px; + height: 90px; + border-radius: 5px; + font-size: 40px; + line-height: 90px; + text-align: center; + color: #ffffff; + background: rgba(96, 149, 209, 0.31); + border: 1px solid rgba(96, 149, 209, 0.31); +} + +.basicInfoContainer { + width: 200px; + height: 90px; + padding-left: 20px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; +} + +.basicInfoText { + +} + +.bigText { + font-size: 20px; +} + +.urlAndUpdateText { + margin-left: 20px; +} + +.createCardContainer { + font-size: 90px; + background: #6062E7; + color: white; +} \ No newline at end of file diff --git a/web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx new file mode 100644 index 00000000..f1d65056 --- /dev/null +++ b/web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx @@ -0,0 +1,222 @@ +import {BotFormEntity, IBotFormEntity} from "@/app/home/bot-config/components/bot-form/BotFormEntity"; +import {fetchAdapterList} from "@/app/home/mock-api/index" +import {Button, Form, Input, Select, Space} from "antd"; +import {useEffect, useState} from "react"; +import {IChooseAdapterEntity} from "@/app/home/bot-config/components/bot-form/ChooseAdapterEntity"; +import { + DynamicFormItemConfig, + IDynamicFormItemConfig, + parseDynamicFormItemType +} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; +import {UUID} from 'uuidjs' +import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; +import {ICreateLLMField} from "@/app/home/llm-config/ICreateLLMField"; + +export default function BotForm({ + initBotId, + onFormSubmit, + onFormCancel, +}: { + initBotId?: string; + onFormSubmit: (value: IBotFormEntity) => void; + onFormCancel: (value: IBotFormEntity) => void; +}) { + const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = useState(new Map()) + const [form] = Form.useForm(); + const [showDynamicForm, setShowDynamicForm] = useState(false) + const [dynamicForm] = Form.useForm(); + const [adapterNameList, setAdapterNameList] = useState([]) + const [dynamicFormConfigList, setDynamicFormConfigList] = useState([]) + + useEffect(() => { + initBotFormComponent() + if (initBotId) { + onEditMode() + } else { + onCreateMode() + } + }, []) + + async function initBotFormComponent() { + // 拉取adapter + const rawAdapterList = await fetchAdapterList() + // 初始化适配器选择列表 + setAdapterNameList( + rawAdapterList.map(item => { + return { + label: item.label.zh_CN, + value: item.name + } + }) + ) + // 初始化适配器表单map + rawAdapterList.forEach(rawAdapter => { + adapterNameToDynamicConfigMap.set( + rawAdapter.name, + rawAdapter.spec.config.map(item => + new DynamicFormItemConfig({ + default: item.default, + id: UUID.generate(), + label: item.label, + name: item.name, + required: item.required, + type: parseDynamicFormItemType(item.type) + }) + ) + ) + }) + // 拉取初始化表单信息 + if (initBotId) { + getBotFieldById(initBotId).then(val => { + form.setFieldsValue(val) + handleAdapterSelect(val.adapter) + }) + } else { + form.resetFields() + } + setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap) + } + + async function onCreateMode() { + + } + + function onEditMode() { + + } + + async function getBotFieldById(botId: string): Promise { + return new BotFormEntity({ + adapter: "telegram", + description: "模拟拉取bot", + name: "模拟电报bot", + adapter_config: { + token: "aaabbbccc" + }, + }) + } + + function handleAdapterSelect(adapterName: string) { + if (adapterName) { + console.log(adapterNameToDynamicConfigMap) + const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName) + if (dynamicFormConfigList) { + console.log(dynamicFormConfigList) + setDynamicFormConfigList(dynamicFormConfigList) + } + setShowDynamicForm(true) + } else { + setShowDynamicForm(false) + } + } + + function handleSubmitButton() { + form.submit() + } + + function handleFormFinish(value: IBotFormEntity) { + dynamicForm.submit() + } + + // 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 + function onDynamicFormSubmit(value: object) { + if (initBotId) { + // 编辑提交 + console.log('submit edit', form.getFieldsValue() ,value) + } else { + // 创建提交 + console.log('submit create', form.getFieldsValue() ,value) + } + onFormSubmit(form.getFieldsValue()) + setShowDynamicForm(false) + form.resetFields() + dynamicForm.resetFields() + + } + + function handleSaveButton() { + + } + + return ( +
+
+ + label={"机器人名称"} + name={"name"} + rules={[{required: true, message: "该项为必填项哦~"}]} + > + + + + + label={"描述"} + name={"description"} + rules={[{required: true, message: "该项为必填项哦~"}]} + > + + + + + label={"平台/适配器选择"} + name={"adapter"} + rules={[{required: true, message: "该项为必填项哦~"}]} + > + + } + + { + config.type === DynamicFormItemType.BOOLEAN && + + } + + { + config.type === DynamicFormItemType.STRING_ARRAY && + + + + + label={"模型供应商"} + name={"model_provider"} + rules={[{required: true, message: "该项为必填项哦~"}]} + > + + + + + label={"开启能力"} + name={"abilities"} + > + + + + + + { + !editMode && + + } + { + editMode && + + } + + + + +
+ + ) +} \ No newline at end of file diff --git a/web_ui/src/app/home/llm-config/page.tsx b/web_ui/src/app/home/llm-config/page.tsx new file mode 100644 index 00000000..56d8b25a --- /dev/null +++ b/web_ui/src/app/home/llm-config/page.tsx @@ -0,0 +1,126 @@ +"use client" + +import {useState} from "react"; +import {LLMCardVO} from "@/app/home/llm-config/component/llm-card/LLMCardVO"; +import styles from "./LLMConfig.module.css" +import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent"; +import {Modal} from "antd"; +import LLMCard from "@/app/home/llm-config/component/llm-card/LLMCard"; +import LLMForm from "@/app/home/llm-config/component/llm-form/LLMForm"; +import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; + +export default function LLMConfigPage() { + const [cardList, setCardList] = useState([ + new LLMCardVO({ + id: "1", + name: "测试模型", + model: "GPT-4o", + URL: "www.openai.com", + company: "OpenAI", + updateTime: "2025.1.2" + }), + new LLMCardVO({ + id: "2", + name: "测试模型", + model: "GPT-4o", + URL: "www.openai.com", + company: "OpenAI", + updateTime: "2025.1.2" + }), + new LLMCardVO({ + id: "3", + name: "测试模型", + model: "GPT-4o", + URL: "www.openai.com", + company: "OpenAI", + updateTime: "2025.1.2" + }), + new LLMCardVO({ + id: "4", + name: "测试模型", + model: "GPT-4o", + URL: "www.openai.com", + company: "OpenAI", + updateTime: "2025.1.2" + }), + new LLMCardVO({ + id: "5", + name: "测试模型", + model: "GPT-4o", + URL: "www.openai.com", + company: "OpenAI", + updateTime: "2025.1.2" + }), + ]) + const [modalOpen, setModalOpen] = useState(false); + const [isEditForm, setIsEditForm] = useState(false) + const [nowSelectedLLM, setNowSelectedLLM] = useState(null) + + function selectLLM(cardVO: LLMCardVO) { + setIsEditForm(true) + setNowSelectedLLM(cardVO) + console.log("set now vo", cardVO) + setModalOpen(true) + } + function handleCreateModelClick() { + setIsEditForm(false) + setNowSelectedLLM(null) + setModalOpen(true); + } + + return ( +
+ setModalOpen(false)} + onCancel={() => setModalOpen(false)} + width={700} + footer={null} + > + { + setModalOpen(false); + }} + onFormCancel={() => { + setModalOpen(false); + }} + /> + + { + cardList.length > 0 && +
+ {cardList.map(cardVO => { + return
{selectLLM(cardVO)}}> + +
+ })} + +
+ } + + { + cardList.length === 0 && +
+ { + handleCreateModelClick() + }} + /> +
+ } +
+ ) +} diff --git a/web_ui/src/app/home/mock-api/index.ts b/web_ui/src/app/home/mock-api/index.ts new file mode 100644 index 00000000..3c4e40c9 --- /dev/null +++ b/web_ui/src/app/home/mock-api/index.ts @@ -0,0 +1,1374 @@ +import {GetMetaDataResponse} from "@/app/infra/api/api-types/pipelines/GetMetaDataResponse"; +import {ApiResponse} from "@/app/infra/api/api-types"; + +export async function fetchAdapterList() { + return [ + { + "description": { + "en_US": "Discord Adapter", + "zh_CN": "Discord 适配器" + }, + "icon": "", + "label": { + "en_US": "Discord", + "zh_CN": "Discord" + }, + "name": "discord", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "Client ID", + "zh_CN": "客户端ID" + }, + "name": "client_id", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "QQ Official API (Webhook)", + "zh_CN": "QQ 官方 API (Webhook)" + }, + "icon": "", + "label": { + "en_US": "QQ Official API", + "zh_CN": "QQ 官方 API" + }, + "name": "qqofficial", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "App ID", + "zh_CN": "应用ID" + }, + "name": "appid", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Secret", + "zh_CN": "密钥" + }, + "name": "secret", + "required": true, + "type": "string" + }, + { + "default": 2284, + "label": { + "en_US": "Port", + "zh_CN": "监听端口" + }, + "name": "port", + "required": true, + "type": "integer" + }, + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "Telegram Adapter", + "zh_CN": "电报适配器" + }, + "icon": "", + "label": { + "en_US": "Telegram", + "zh_CN": "电报" + }, + "name": "telegram", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "WeCom Adapter", + "zh_CN": "企业微信适配器" + }, + "icon": "", + "label": { + "en_US": "WeCom", + "zh_CN": "企业微信" + }, + "name": "wecom", + "spec": { + "config": [ + { + "default": "0.0.0.0", + "label": { + "en_US": "Host", + "zh_CN": "监听主机" + }, + "name": "host", + "required": true, + "type": "string" + }, + { + "default": 2290, + "label": { + "en_US": "Port", + "zh_CN": "监听端口" + }, + "name": "port", + "required": true, + "type": "integer" + }, + { + "default": "", + "label": { + "en_US": "Corpid", + "zh_CN": "企业ID" + }, + "name": "corpid", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Secret", + "zh_CN": "密钥" + }, + "name": "secret", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "EncodingAESKey", + "zh_CN": "消息加解密密钥" + }, + "name": "EncodingAESKey", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Contacts Secret", + "zh_CN": "通讯录密钥" + }, + "name": "contacts_secret", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "GeWeChat Adapter", + "zh_CN": "GeWeChat 适配器" + }, + "icon": "", + "label": { + "en_US": "GeWeChat", + "zh_CN": "GeWeChat(个人微信)" + }, + "name": "gewechat", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "GeWeChat URL", + "zh_CN": "GeWeChat URL" + }, + "name": "gewechat_url", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "GeWeChat file download URL", + "zh_CN": "GeWeChat 文件下载URL" + }, + "name": "gewechat_file_url", + "required": true, + "type": "string" + }, + { + "default": 2286, + "label": { + "en_US": "Port", + "zh_CN": "端口" + }, + "name": "port", + "required": true, + "type": "integer" + }, + { + "default": "", + "label": { + "en_US": "Callback URL", + "zh_CN": "回调URL" + }, + "name": "callback_url", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "App ID", + "zh_CN": "应用ID" + }, + "name": "app_id", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "OneBot v11 Adapter", + "zh_CN": "OneBot v11 适配器" + }, + "icon": "", + "label": { + "en_US": "OneBot v11 Adapter", + "zh_CN": "OneBot v11 适配器" + }, + "name": "aiocqhttp", + "spec": { + "config": [ + { + "default": "0.0.0.0", + "label": { + "en_US": "Host", + "zh_CN": "主机" + }, + "name": "host", + "required": true, + "type": "string" + }, + { + "default": 2280, + "label": { + "en_US": "Port", + "zh_CN": "端口" + }, + "name": "port", + "required": true, + "type": "integer" + }, + { + "default": "", + "label": { + "en_US": "Access Token", + "zh_CN": "访问令牌" + }, + "name": "access-token", + "required": false, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "Official Account Adapter", + "zh_CN": "微信公众号适配器" + }, + "icon": "", + "label": { + "en_US": "Official Account", + "zh_CN": "微信公众号" + }, + "name": "officialaccount", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "EncodingAESKey", + "zh_CN": "消息加解密密钥" + }, + "name": "EncodingAESKey", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "App ID", + "zh_CN": "应用ID" + }, + "name": "AppID", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "App Secret", + "zh_CN": "应用密钥" + }, + "name": "AppSecret", + "required": true, + "type": "string" + }, + { + "default": "0.0.0.0", + "label": { + "en_US": "Host", + "zh_CN": "监听主机" + }, + "name": "host", + "required": true, + "type": "string" + }, + { + "default": 2287, + "label": { + "en_US": "Port", + "zh_CN": "监听端口" + }, + "name": "port", + "required": true, + "type": "integer" + } + ] + } + }, + { + "description": { + "en_US": "Nakuru Adapter", + "zh_CN": "Nakuru 适配器(go-cqhttp)" + }, + "icon": "", + "label": { + "en_US": "Nakuru", + "zh_CN": "Nakuru" + }, + "name": "nakuru", + "spec": { + "config": [ + { + "default": "127.0.0.1", + "label": { + "en_US": "Host", + "zh_CN": "主机" + }, + "name": "host", + "required": true, + "type": "string" + }, + { + "default": 5700, + "label": { + "en_US": "HTTP Port", + "zh_CN": "HTTP端口" + }, + "name": "http_port", + "required": true, + "type": "integer" + }, + { + "default": 8080, + "label": { + "en_US": "WebSocket Port", + "zh_CN": "WebSocket端口" + }, + "name": "ws_port", + "required": true, + "type": "integer" + }, + { + "default": "", + "label": { + "en_US": "Token", + "zh_CN": "令牌" + }, + "name": "token", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "QQ Official API (WebSocket)", + "zh_CN": "QQ 官方 API (WebSocket)" + }, + "icon": "", + "label": { + "en_US": "QQBotPy", + "zh_CN": "QQBotPy" + }, + "name": "qq-botpy", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "App ID", + "zh_CN": "应用ID" + }, + "name": "appid", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Secret", + "zh_CN": "密钥" + }, + "name": "secret", + "required": true, + "type": "string" + }, + { + "default": [], + "label": { + "en_US": "Intents", + "zh_CN": "权限" + }, + "name": "intents", + "required": true, + "type": "array[string]" + } + ] + } + }, + { + "description": { + "en_US": "DingTalk Adapter", + "zh_CN": "钉钉适配器" + }, + "icon": "", + "label": { + "en_US": "DingTalk", + "zh_CN": "钉钉" + }, + "name": "dingtalk", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "Client ID", + "zh_CN": "客户端ID" + }, + "name": "client_id", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Client Secret", + "zh_CN": "客户端密钥" + }, + "name": "client_secret", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Robot Code", + "zh_CN": "机器人代码" + }, + "name": "robot_code", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Robot Name", + "zh_CN": "机器人名称" + }, + "name": "robot_name", + "required": true, + "type": "string" + } + ] + } + }, + { + "description": { + "en_US": "Lark Adapter", + "zh_CN": "飞书适配器" + }, + "icon": "", + "label": { + "en_US": "Lark", + "zh_CN": "飞书" + }, + "name": "lark", + "spec": { + "config": [ + { + "default": "", + "label": { + "en_US": "App ID", + "zh_CN": "应用ID" + }, + "name": "app_id", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "App Secret", + "zh_CN": "应用密钥" + }, + "name": "app_secret", + "required": true, + "type": "string" + }, + { + "default": "", + "label": { + "en_US": "Bot Name", + "zh_CN": "机器人名称" + }, + "name": "bot_name", + "required": true, + "type": "string" + }, + { + "default": false, + "label": { + "en_US": "Enable Webhook Mode", + "zh_CN": "启用Webhook模式" + }, + "name": "enable-webhook", + "required": true, + "type": "boolean" + }, + { + "default": 2285, + "label": { + "en_US": "Webhook Port", + "zh_CN": "Webhook端口" + }, + "name": "port", + "required": true, + "type": "integer" + }, + { + "default": "", + "label": { + "en_US": "Encrypt Key", + "zh_CN": "加密密钥" + }, + "name": "encrypt-key", + "required": true, + "type": "string" + } + ] + } + } + ] +} + +export async function fetchPipelineMetaData(): Promise> { + return { + "code": 0, + "data": { + "configs": [ + { + "label": { + "en_US": "Trigger", + "zh_CN": "触发条件" + }, + "name": "trigger", + "stages": [ + { + "config": [ + { + "default": false, + "description": { + "en_US": "Whether to trigger when the message mentions the bot", + "zh_CN": "是否在消息@机器人时触发" + }, + "label": { + "en_US": "At", + "zh_CN": "@" + }, + "name": "at", + "required": true, + "type": "boolean" + }, + { + "default": [], + "description": { + "en_US": "The prefix of the message", + "zh_CN": "消息前缀" + }, + "items": { + "type": "string" + }, + "label": { + "en_US": "Prefix", + "zh_CN": "前缀" + }, + "name": "prefix", + "required": true, + "type": "array" + }, + { + "default": [], + "description": { + "en_US": "The regexp of the message", + "zh_CN": "消息正则表达式" + }, + "items": { + "type": "string" + }, + "label": { + "en_US": "Regexp", + "zh_CN": "正则表达式" + }, + "name": "regexp", + "required": true, + "type": "array" + }, + { + "default": 0, + "description": { + "en_US": "The probability of the random response, range from 0.0 to 1.0", + "zh_CN": "随机响应概率,范围为 0.0-1.0" + }, + "label": { + "en_US": "Random", + "zh_CN": "随机" + }, + "name": "random", + "required": false, + "type": "float" + } + ], + "description": { + "en_US": "The group respond rule of the pipeline", + "zh_CN": "群响应规则" + }, + "label": { + "en_US": "Group Respond Rule", + "zh_CN": "群响应规则" + }, + "name": "group-respond-rules" + }, + { + "config": [ + { + "default": "blacklist", + "description": { + "en_US": "The mode of the access control", + "zh_CN": "访问控制模式" + }, + "label": { + "en_US": "Mode", + "zh_CN": "模式" + }, + "name": "mode", + "options": [ + { + "label": { + "en_US": "Blacklist", + "zh_CN": "黑名单" + }, + "name": "blacklist" + }, + { + "label": { + "en_US": "Whitelist", + "zh_CN": "白名单" + }, + "name": "whitelist" + } + ], + "required": true, + "type": "select" + }, + { + "default": [], + "items": { + "type": "string" + }, + "label": { + "en_US": "Blacklist", + "zh_CN": "黑名单" + }, + "name": "blacklist", + "required": true, + "type": "array" + }, + { + "default": [], + "items": { + "type": "string" + }, + "label": { + "en_US": "Whitelist", + "zh_CN": "白名单" + }, + "name": "whitelist", + "required": true, + "type": "array" + } + ], + "label": { + "en_US": "Access Control", + "zh_CN": "访问控制" + }, + "name": "access-control" + }, + { + "config": [ + { + "default": [], + "description": { + "en_US": "The prefix of the message", + "zh_CN": "消息前缀" + }, + "items": { + "type": "string" + }, + "label": { + "en_US": "Prefix", + "zh_CN": "前缀" + }, + "name": "prefix", + "required": true, + "type": "array" + }, + { + "default": [], + "description": { + "en_US": "The regexp of the message", + "zh_CN": "消息正则表达式" + }, + "items": { + "type": "string" + }, + "label": { + "en_US": "Regexp", + "zh_CN": "正则表达式" + }, + "name": "regexp", + "required": true, + "type": "array" + } + ], + "label": { + "en_US": "Ignore Rules", + "zh_CN": "消息忽略规则" + }, + "name": "ignore-rules" + } + ] + }, + { + "label": { + "en_US": "Safety Control", + "zh_CN": "安全控制" + }, + "name": "safety", + "stages": [ + { + "config": [ + { + "default": "all", + "label": { + "en_US": "Scope", + "zh_CN": "检查范围" + }, + "name": "scope", + "options": [ + { + "label": { + "en_US": "All", + "zh_CN": "全部" + }, + "name": "all" + }, + { + "label": { + "en_US": "Income Message", + "zh_CN": "传入消息(用户消息)" + }, + "name": "income-msg" + }, + { + "label": { + "en_US": "Output Message", + "zh_CN": "传出消息(机器人消息)" + }, + "name": "output-msg" + } + ], + "required": true, + "type": "select" + }, + { + "default": false, + "label": { + "en_US": "Check Sensitive Words", + "zh_CN": "检查敏感词" + }, + "name": "check-sensitive-words", + "required": true, + "type": "boolean" + } + ], + "label": { + "en_US": "Content Filter", + "zh_CN": "内容过滤" + }, + "name": "content-filter" + }, + { + "config": [ + { + "default": 60, + "label": { + "en_US": "Window Length", + "zh_CN": "窗口长度(秒)" + }, + "name": "window-length", + "required": true, + "type": "integer" + }, + { + "default": 60, + "label": { + "en_US": "Limitation", + "zh_CN": "限制次数" + }, + "name": "limitation", + "required": true, + "type": "integer" + }, + { + "default": "drop", + "label": { + "en_US": "Strategy", + "zh_CN": "策略" + }, + "name": "strategy", + "options": [ + { + "label": { + "en_US": "Drop", + "zh_CN": "丢弃" + }, + "name": "drop" + }, + { + "label": { + "en_US": "Wait", + "zh_CN": "等待" + }, + "name": "wait" + } + ], + "required": true, + "type": "select" + } + ], + "label": { + "en_US": "Rate Limit", + "zh_CN": "速率限制" + }, + "name": "rate-limit" + } + ] + }, + { + "label": { + "en_US": "AI Feature", + "zh_CN": "AI 能力" + }, + "name": "ai", + "stages": [ + { + "config": [ + { + "default": "local-agent", + "label": { + "en_US": "Runner", + "zh_CN": "运行器" + }, + "name": "runner", + "options": [ + { + "label": { + "en_US": "Embedded Agent", + "zh_CN": "内置 Agent" + }, + "name": "local-agent" + }, + { + "label": { + "en_US": "Dify Service API", + "zh_CN": "Dify 服务 API" + }, + "name": "dify-service-api" + }, + { + "label": { + "en_US": "Aliyun Dashscope App API", + "zh_CN": "阿里云百炼平台 API" + }, + "name": "dashscope-app-api" + } + ], + "required": true, + "type": "select" + } + ], + "label": { + "en_US": "Runner", + "zh_CN": "运行方式" + }, + "name": "runner" + }, + { + "config": [ + { + "label": { + "en_US": "Model", + "zh_CN": "模型" + }, + "name": "model", + "required": true, + "scope": "/provider/models/llm", + "type": "select" + }, + { + "default": 10, + "label": { + "en_US": "Max Round", + "zh_CN": "最大回合数" + }, + "name": "max-round", + "required": true, + "type": "integer" + }, + { + "items": { + "properties": { + "content": { + "type": "string" + }, + "role": { + "default": "user", + "type": "string" + } + }, + "type": "object" + }, + "label": { + "en_US": "Prompt", + "zh_CN": "提示词" + }, + "name": "prompt", + "required": true, + "type": "array" + } + ], + "description": { + "en_US": "Configure the embedded agent of the pipeline", + "zh_CN": "配置内置 Agent" + }, + "label": { + "en_US": "Embedded Agent", + "zh_CN": "内置 Agent" + }, + "name": "local-agent" + }, + { + "config": [ + { + "label": { + "en_US": "Base URL", + "zh_CN": "基础 URL" + }, + "name": "base-url", + "required": true, + "type": "string" + }, + { + "default": "chat", + "label": { + "en_US": "App Type", + "zh_CN": "应用类型" + }, + "name": "app-type", + "options": [ + { + "label": { + "en_US": "Chat", + "zh_CN": "聊天(包括Chatflow)" + }, + "name": "chat" + }, + { + "label": { + "en_US": "Agent", + "zh_CN": "Agent" + }, + "name": "agent" + }, + { + "label": { + "en_US": "Workflow", + "zh_CN": "工作流" + }, + "name": "workflow" + } + ], + "required": true, + "type": "select" + }, + { + "label": { + "en_US": "API Key", + "zh_CN": "API 密钥" + }, + "name": "api-key", + "required": true, + "type": "string" + }, + { + "default": "plain", + "label": { + "en_US": "CoT Convert", + "zh_CN": "思维链转换策略" + }, + "name": "thinking-convert", + "options": [ + { + "label": { + "en_US": "Convert to ...", + "zh_CN": "转换成 ..." + }, + "name": "plain" + }, + { + "label": { + "en_US": "Original", + "zh_CN": "原始" + }, + "name": "original" + }, + { + "label": { + "en_US": "Remove", + "zh_CN": "移除" + }, + "name": "remove" + } + ], + "required": true, + "type": "select" + } + ], + "description": { + "en_US": "Configure the Dify service API of the pipeline", + "zh_CN": "配置 Dify 服务 API" + }, + "label": { + "en_US": "Dify Service API", + "zh_CN": "Dify 服务 API" + }, + "name": "dify-service-api" + }, + { + "config": [ + { + "default": "agent", + "label": { + "en_US": "App Type", + "zh_CN": "应用类型" + }, + "name": "app-type", + "options": [ + { + "label": { + "en_US": "Agent", + "zh_CN": "Agent" + }, + "name": "agent" + }, + { + "label": { + "en_US": "Workflow", + "zh_CN": "工作流" + }, + "name": "workflow" + } + ], + "required": true, + "type": "select" + }, + { + "label": { + "en_US": "API Key", + "zh_CN": "API 密钥" + }, + "name": "api-key", + "required": true, + "type": "string" + }, + { + "label": { + "en_US": "App ID", + "zh_CN": "应用 ID" + }, + "name": "app-id", + "required": true, + "type": "string" + }, + { + "default": "参考资料来自:", + "label": { + "en_US": "References Quote", + "zh_CN": "引用文本" + }, + "name": "references_quote", + "required": false, + "type": "string" + } + ], + "description": { + "en_US": "Configure the Aliyun Dashscope App API of the pipeline", + "zh_CN": "配置阿里云百炼平台 API" + }, + "label": { + "en_US": "Aliyun Dashscope App API", + "zh_CN": "阿里云百炼平台 API" + }, + "name": "dashscope-app-api" + } + ] + }, + { + "label": { + "en_US": "Output Processing", + "zh_CN": "输出处理" + }, + "name": "output", + "stages": [ + { + "config": [ + { + "default": 1000, + "label": { + "en_US": "Threshold", + "zh_CN": "阈值" + }, + "name": "threshold", + "required": true, + "type": "integer" + }, + { + "default": "forward", + "label": { + "en_US": "Strategy", + "zh_CN": "策略" + }, + "name": "strategy", + "options": [ + { + "label": { + "en_US": "Forward Message Component", + "zh_CN": "转发消息组件" + }, + "name": "forward" + }, + { + "label": { + "en_US": "Convert to Image", + "zh_CN": "转换为图片" + }, + "name": "image" + } + ], + "required": true, + "type": "select" + }, + { + "default": "", + "label": { + "en_US": "Font Path", + "zh_CN": "字体路径" + }, + "name": "font-path", + "required": true, + "type": "string" + } + ], + "label": { + "en_US": "Long Text Processing", + "zh_CN": "长文本处理" + }, + "name": "long-text-processing" + }, + { + "config": [ + { + "default": 0, + "label": { + "en_US": "Min", + "zh_CN": "最小秒数" + }, + "name": "min", + "required": true, + "type": "integer" + }, + { + "default": 0, + "label": { + "en_US": "Max", + "zh_CN": "最大秒数" + }, + "name": "max", + "required": true, + "type": "integer" + } + ], + "label": { + "en_US": "Force Delay", + "zh_CN": "强制延迟" + }, + "name": "force-delay" + }, + { + "config": [ + { + "default": true, + "label": { + "en_US": "Hide Exception", + "zh_CN": "不输出异常信息给用户" + }, + "name": "hide-exception", + "required": true, + "type": "boolean" + }, + { + "default": true, + "label": { + "en_US": "At Sender", + "zh_CN": "在回复中@发送者" + }, + "name": "at-sender", + "required": true, + "type": "boolean" + }, + { + "default": false, + "label": { + "en_US": "Quote Origin", + "zh_CN": "引用原文" + }, + "name": "quote-origin", + "required": true, + "type": "boolean" + }, + { + "default": true, + "label": { + "en_US": "Track Function Calls", + "zh_CN": "跟踪函数调用" + }, + "name": "track-function-calls", + "required": true, + "type": "boolean" + } + ], + "label": { + "en_US": "Misc", + "zh_CN": "杂项" + }, + "name": "misc" + } + ] + } + ] + }, + "msg": "ok" + } +} + diff --git a/web_ui/src/app/home/page.tsx b/web_ui/src/app/home/page.tsx new file mode 100644 index 00000000..f9fbf66f --- /dev/null +++ b/web_ui/src/app/home/page.tsx @@ -0,0 +1,6 @@ +export default function Home() { + return ( +
+
+ ); +} diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineChildFormEntity.ts b/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineChildFormEntity.ts new file mode 100644 index 00000000..a2038c35 --- /dev/null +++ b/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineChildFormEntity.ts @@ -0,0 +1,20 @@ +import {DynamicFormItemConfig} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; + +export interface IPipelineChildFormEntity { + name: string; + label: string; + formItems: DynamicFormItemConfig[] +} + +export class PipelineChildFormEntity implements IPipelineChildFormEntity { + formItems: DynamicFormItemConfig[]; + label: string; + name: string; + + constructor(props: IPipelineChildFormEntity) { + this.form = props.form; + this.label = props.label; + this.name = props.name; + this.formItems = props.formItems; + } +} \ No newline at end of file diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx b/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx new file mode 100644 index 00000000..9a7ae574 --- /dev/null +++ b/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx @@ -0,0 +1,512 @@ +import {Form, Button, Switch, Select, Input, InputNumber} from "antd"; +import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'; +import {useState} from "react"; +import styles from "./pipelineFormStyle.module.css" + +export default function PipelineFormComponent({ + onFinish, + onCancel, +}: { + onFinish: () => void; + onCancel: () => void; +}) { + const [nowFormIndex, setNowFormIndex] = useState(0) + // 这里不好,可以改成enum等 + const formLabelList: FormLabel[] = [ + {label: "AI能力", name: "ai"}, + {label: "触发条件", name: "trigger"}, + {label: "安全能力", name: "safety"}, + {label: "输出处理", name: "output"}, + ] + + function getNowFormLabel() { + return formLabelList[nowFormIndex] + } + + + function getPreFormLabel(): undefined | FormLabel { + if (nowFormIndex !== undefined && nowFormIndex > 0) { + return formLabelList[nowFormIndex - 1] + } else { + return undefined + } + } + + function getNextFormLabel(): undefined | FormLabel { + if (nowFormIndex !== undefined && nowFormIndex < formLabelList.length - 1) { + return formLabelList[nowFormIndex + 1] + } else { + return undefined + } + } + + function addFormLabelIndex() { + if (nowFormIndex < formLabelList.length - 1) { + setNowFormIndex(nowFormIndex + 1) + } + } + + function reduceFormLabelIndex() { + if (nowFormIndex > 0) { + setNowFormIndex(nowFormIndex - 1) + } + } + + return ( +
+

+ {getNowFormLabel().label} +

+ {/* AI能力表单 ai */} +
+ {/* Runner 配置区块 */} +
运行器
+ + + + + + + {/* TODO 这里要做转换处理 */} + + + + + {/* Dify 服务 API 区块 */} +
配置Dify服务API
+ + + + + ...\<\/think\>", value: "plain" }, + { label: "原始", value: "original" }, + { label: "移除", value: "remove" } + ]} + /> + + + {/* 阿里云百炼区块 */} +
配置阿里云百炼平台 API
+ + + + + + +
+ + {/* 触发条件表单 trigger */} +
+ {/* 群响应规则块 */} + + 群响应规则 + + + + + + + + + + + + + 访问控制 + + + + + + + + + + + + + + + + + + {/* 速率限制块 rate-limit */} + + + + + + + + + + + + + + + {/* 强制延迟区块 */} + + + + + + + + + {/* 杂项区块 */} + + + + + + + + + + + + + + + +
+ + +
+ +
+ ) +} + +enum PipelineFormRoute { + +} + +interface FormPageLabel { + formIndex: number, + formName: string, + formLabel: string, +} + +interface FormLabel { + label: string, + name: string, +} diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css b/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css new file mode 100644 index 00000000..b011fa39 --- /dev/null +++ b/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css @@ -0,0 +1,12 @@ +.formItemSubtitle { + font-size: 18px; + font-weight: bold; + margin-bottom: 10px; +} + +.changeFormButtonGroupContainer { + width: 250px; + display: flex; + flex-direction: row; + justify-content: space-between; +} diff --git a/web_ui/src/app/home/pipeline-config/page.tsx b/web_ui/src/app/home/pipeline-config/page.tsx new file mode 100644 index 00000000..f9363e2e --- /dev/null +++ b/web_ui/src/app/home/pipeline-config/page.tsx @@ -0,0 +1,30 @@ +"use client" +import {Modal} from "antd"; +import {useState} from "react"; +import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; +import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent"; + + +export default function PluginConfigPage() { + const [modalOpen, setModalOpen] = useState(false); + const [isEditForm, setIsEditForm] = useState(false) + + + return ( +
+ setModalOpen(false)} + onCancel={() => setModalOpen(false)} + width={700} + footer={null} + > + {}} onCancel={() => {}}/> + + + {setModalOpen(true)}}/> +
+ ); +} diff --git a/web_ui/src/app/home/pipeline-config/pipelineConfig.module.css b/web_ui/src/app/home/pipeline-config/pipelineConfig.module.css new file mode 100644 index 00000000..66240516 --- /dev/null +++ b/web_ui/src/app/home/pipeline-config/pipelineConfig.module.css @@ -0,0 +1,4 @@ +.formItemSubTitle { + font-size: 30px; + font-weight: bold; +} diff --git a/web_ui/src/app/home/plugin-config/page.tsx b/web_ui/src/app/home/plugin-config/page.tsx new file mode 100644 index 00000000..8bc22362 --- /dev/null +++ b/web_ui/src/app/home/plugin-config/page.tsx @@ -0,0 +1,7 @@ +export default function pluginConfigPage() { + return ( +
+

PluginConfigPage

+
+ ); +} diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts new file mode 100644 index 00000000..ce30d80b --- /dev/null +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -0,0 +1,11 @@ +export interface ApiResponse { + code: number; + data: T; + msg: string; +} + +export interface I18nText { + en_US: string; + zh_CN: string; +} + diff --git a/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts b/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts new file mode 100644 index 00000000..41a1b8dc --- /dev/null +++ b/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts @@ -0,0 +1,47 @@ +export interface GetMetaDataResponse { + configs: Config[] +} + + +interface Label { + en_US: string; + zh_CN: string; +} + +interface Option { + label: Label; + name: string; +} + +interface ConfigItem { + default?: boolean | Array | number | string; + description?: Label; + items?: { + type?: string; + properties?: { + [key: string]: { + type: string; + default?: any; + }; + }; + }; + label: Label; + name: string; + options?: Option[]; + required: boolean; + scope?: string; + type: string; +} + +interface Stage { + config: ConfigItem[]; + description?: Label; + label: Label; + name: string; +} + +interface Config { + label: Label; + name: string; + stages: Stage[]; +} diff --git a/web_ui/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx b/web_ui/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx new file mode 100644 index 00000000..3e33d712 --- /dev/null +++ b/web_ui/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx @@ -0,0 +1,27 @@ +import styles from "./createCartComponent.module.css"; + +export default function CreateCardComponent({ + width, + height, + plusSize, + onClick, +}: { + width: number; + height: number; + plusSize: number; + onClick: () => void +}) { + return ( +
+ + +
+ ) +} \ No newline at end of file diff --git a/web_ui/src/app/infra/basic-component/create-card-component/createCartComponent.module.css b/web_ui/src/app/infra/basic-component/create-card-component/createCartComponent.module.css new file mode 100644 index 00000000..8d751a01 --- /dev/null +++ b/web_ui/src/app/infra/basic-component/create-card-component/createCartComponent.module.css @@ -0,0 +1,16 @@ +.cardContainer { + width: 360px; + height: 200px; + background-color: #FFF; + border-radius: 9px; + box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; +} + +.createCardContainer { + font-size: 90px; + color: #acacac; +} \ No newline at end of file diff --git a/web_ui/src/app/infra/basic-types/I18N.ts b/web_ui/src/app/infra/basic-types/I18N.ts new file mode 100644 index 00000000..54111e6c --- /dev/null +++ b/web_ui/src/app/infra/basic-types/I18N.ts @@ -0,0 +1,4 @@ +export interface I18NText { + en_US: string; + zh_CN: string; +} \ No newline at end of file diff --git a/web_ui/src/app/layout.tsx b/web_ui/src/app/layout.tsx new file mode 100644 index 00000000..4d25b364 --- /dev/null +++ b/web_ui/src/app/layout.tsx @@ -0,0 +1,22 @@ +import "./global.css" +import type { Metadata } from "next"; + + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/web_ui/src/app/login/layout.tsx b/web_ui/src/app/login/layout.tsx new file mode 100644 index 00000000..e868ff78 --- /dev/null +++ b/web_ui/src/app/login/layout.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export default function LoginLayout({ + children +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
{children}
+
+ ) +} diff --git a/web_ui/src/app/login/page.tsx b/web_ui/src/app/login/page.tsx new file mode 100644 index 00000000..ec5499d9 --- /dev/null +++ b/web_ui/src/app/login/page.tsx @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+

loginpage

+
+ ); +} diff --git a/web_ui/src/app/not-found.tsx b/web_ui/src/app/not-found.tsx new file mode 100644 index 00000000..9a023999 --- /dev/null +++ b/web_ui/src/app/not-found.tsx @@ -0,0 +1,8 @@ +export default function NotFound() { + return ( +
+ {/* TODO: @qidongrui 这里404页面有时间要更新*/} +

Langbot没有找到该页面喔~

+
+ ); +} diff --git a/web_ui/src/app/page.tsx b/web_ui/src/app/page.tsx new file mode 100644 index 00000000..e839b271 --- /dev/null +++ b/web_ui/src/app/page.tsx @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+ +
+ ); +} diff --git a/web_ui/tsconfig.json b/web_ui/tsconfig.json new file mode 100644 index 00000000..c1334095 --- /dev/null +++ b/web_ui/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From b1c7bf5b5803d1a360a9f6bb1bad78cb77c97b0f Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 21 Apr 2025 12:46:07 +0800 Subject: [PATCH 002/121] =?UTF-8?q?feat:=20webUI=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=B5=81=E6=B0=B4=E7=BA=BF=E8=A1=A8=E5=8D=95=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=201.=20=E6=96=B0=E5=A2=9E=E6=8F=90=E4=BA=A4=E6=8C=89=E9=92=AE?= =?UTF-8?q?=202.=20=E4=BC=98=E5=8C=96=E6=8C=89=E9=92=AE=E5=92=8C=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E9=A1=B9=E7=9A=84=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pipeline-form/PipelineFormComponent.tsx | 30 +++++++++---------- .../pipelineFormStyle.module.css | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx b/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx index 9a7ae574..5eec0ebf 100644 --- a/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx +++ b/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx @@ -211,9 +211,7 @@ export default function PipelineFormComponent({ style={{ display: getNowFormLabel().name === "trigger" ? 'block' : 'none' }} > {/* 群响应规则块 */} - - 群响应规则 - +
群响应规则
- - - 访问控制 - +
访问控制
- - +
消息忽略规则
{/* 内容过滤块 content-filter */} +
内容过滤
{/* 速率限制块 rate-limit */} - +
速率限制
{/* 长文本处理区块 */} - +
长文本处理
{/* 强制延迟区块 */} - +
强制延迟
{/* 杂项区块 */} - +
杂项
{getNextFormLabel()?.label || "暂无更多"} + + diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css b/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css index b011fa39..f1b052f4 100644 --- a/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css +++ b/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css @@ -5,7 +5,7 @@ } .changeFormButtonGroupContainer { - width: 250px; + width: 320px; display: flex; flex-direction: row; justify-content: space-between; From b81eb9be0cc8df3640e3a1c4ef13e0be7d6968a9 Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 21 Apr 2025 13:12:54 +0800 Subject: [PATCH 003/121] =?UTF-8?q?feat:=20webUI=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=AF=B7=E6=B1=82=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web_ui/package-lock.json | 106 ++++++++++++-- web_ui/package.json | 1 + web_ui/src/app/infra/http/HttpClient.ts | 177 ++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 web_ui/src/app/infra/http/HttpClient.ts diff --git a/web_ui/package-lock.json b/web_ui/package-lock.json index 19c92988..269c3839 100644 --- a/web_ui/package-lock.json +++ b/web_ui/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@ant-design/v5-patch-for-react-19": "^1.0.3", "antd": "^5.24.6", + "axios": "^1.8.4", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -1838,6 +1839,11 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1862,6 +1868,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -1932,7 +1948,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2052,6 +2067,17 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -2209,6 +2235,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2234,7 +2268,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2319,7 +2352,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -2328,7 +2360,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -2364,7 +2395,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -2376,7 +2406,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -2946,6 +2975,25 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -2961,11 +3009,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3003,7 +3064,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3027,7 +3087,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3109,7 +3168,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3175,7 +3233,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3187,7 +3244,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -3202,7 +3258,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -3804,7 +3859,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -3831,6 +3885,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4222,6 +4295,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/web_ui/package.json b/web_ui/package.json index be4677cc..9e7c20f7 100644 --- a/web_ui/package.json +++ b/web_ui/package.json @@ -11,6 +11,7 @@ "dependencies": { "@ant-design/v5-patch-for-react-19": "^1.0.3", "antd": "^5.24.6", + "axios": "^1.8.4", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts new file mode 100644 index 00000000..e765e539 --- /dev/null +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -0,0 +1,177 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' + +type JSONValue = string | number | boolean | JSONObject | JSONArray | null +interface JSONObject { [key: string]: JSONValue } +interface JSONArray extends Array {} + +export interface ResponseData { + code: number + message: string + data: T + timestamp: number +} + +export interface RequestConfig extends AxiosRequestConfig { + isSSR?: boolean // 服务端渲染标识 + retry?: number // 重试次数 +} + +class HttpClient { + private instance: AxiosInstance + // 暂不需要SSR + // private ssrInstance: AxiosInstance | null = null + + constructor(baseURL?: string) { + this.instance = axios.create({ + baseURL: baseURL || this.getBaseUrl(), + timeout: 15000, + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + } + }) + + this.initInterceptors() + } + + // 兜底URL,如果使用未配置会走到这里 + private getBaseUrl(): string { + // NOT IMPLEMENT + if (typeof window === 'undefined') { + // 服务端环境 + return "" + } + // 客户端环境 + return "" + } + + // 获取Session + private async getSession() { + // NOT IMPLEMENT + return "" + } + + // 同步获取Session + private getSessionSync() { + // NOT IMPLEMENT + return "" + } + + // 拦截器配置 + private initInterceptors() { + // 请求拦截 + this.instance.interceptors.request.use( + async (config) => { + // 服务端请求自动携带 cookie, Langbot暂时用不到SSR相关 + // if (typeof window === 'undefined' && config.isSSR) { } + const { cookies } = await import('next/headers') + config.headers.Cookie = cookies().toString() + + // 客户端添加认证头 + if (typeof window !== 'undefined') { + // NOT IMPLEMENT 从本地取Session,为空跳转到登陆页 + // const session = await this.getSession() + const session = this.getSessionSync() + config.headers.Authorization = `Bearer ${session}` + } + + return config + }, + (error) => Promise.reject(error) + ) + + // 响应拦截 + this.instance.interceptors.response.use( + (response: AxiosResponse) => { + // 响应拦截处理写在这里,暂无业务需要 + + return response + }, + (error: AxiosError) => { + // 统一错误处理 + if (error.response) { + const { status, data } = error.response + const errMessage = data?.message || error.message + + switch (status) { + case 401: + // 401 处理 + break + case 403: + console.error('Permission denied:', errMessage) + break + case 500: + // TODO 弹Toast窗 + console.error('Server error:', errMessage) + break + } + + return Promise.reject({ + code: data?.code || status, + message: errMessage, + data: data?.data || null + }) + } + + return Promise.reject({ + code: -1, + message: error.message || 'Network Error', + data: null + }) + } + ) + } + + + // 转换下划线为驼峰 + private convertKeysToCamel(obj: JSONValue): JSONValue { + if (Array.isArray(obj)) { + return obj.map(v => this.convertKeysToCamel(v)) + } else if (obj !== null && typeof obj === 'object') { + return Object.keys(obj).reduce((acc, key) => { + const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + acc[camelKey] = this.convertKeysToCamel((obj as JSONObject)[key]) + return acc + }, {} as JSONObject) + } + return obj + } + + // 核心请求方法 + public async request(config: RequestConfig): Promise { + try { + // 这里未来如果需要SSR可以将前面替换为SSR的instance + const instance = config.isSSR ? this.instance : this.instance + const response = await instance.request>(config) + return response.data.data + } catch (error) { + return this.handleError(error) + } + } + + private handleError(error: any): never { + if (axios.isCancel(error)) { + throw { code: -2, message: 'Request canceled', data: null } + } + throw error + } + + // 快捷方法 + public get(url: string, params?: object, config?: RequestConfig) { + return this.request({ method: 'get', url, params, ...config }) + } + + public post(url: string, data?: object, config?: RequestConfig) { + return this.request({ method: 'post', url, data, ...config }) + } + + public put(url: string, data?: object, config?: RequestConfig) { + return this.request({ method: 'put', url, data, ...config }) + } + + public delete(url: string, config?: RequestConfig) { + return this.request({ method: 'delete', url, ...config }) + } +} + +export const httpClient = new HttpClient() From db8cc65e08aabf7efc287ce1ee122d06a32823ff Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 21 Apr 2025 16:24:32 +0800 Subject: [PATCH 004/121] chore: ignore web/ for git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 17271201..bfd0e3f2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ botpy.log* /libs/wecom_api/test.py /venv /jp-tyo-churros-05.rockchin.top +web/ \ No newline at end of file From 43c5411265a8d48edaca5c03ad0e98b428fc99a8 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 24 Apr 2025 22:19:27 +0800 Subject: [PATCH 005/121] feat(webui): implement provider, platform, pipeline api request methods --- web_ui/src/app/infra/api/api-types/index.ts | 92 ++++++++++++++++++ web_ui/src/app/infra/http/HttpClient.ts | 101 +++++++++++++++++++- 2 files changed, 191 insertions(+), 2 deletions(-) diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index ce30d80b..d5615c9f 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -9,3 +9,95 @@ export interface I18nText { zh_CN: string; } +export interface ApiRespProviderRequesters { + requesters: Requester[]; +} + +export interface ApiRespProviderRequester { + requester: Requester; +} + +export interface Requester { + name: string; + label: I18nText; + description: I18nText; + icon?: string; + spec: object; +} + +export interface ApiRespProviderLLMModels { + models: LLMModel[]; +} + +export interface ApiRespProviderLLMModel { + model: LLMModel; +} + +export interface LLMModel { + name: string; + description: string; + uuid: string; + requester: string; + requester_config: object; + extra_args: object; + api_keys: string[]; + abilities: string[]; + created_at: string; + updated_at: string; +} + +export interface ApiRespPipelines { + pipelines: Pipeline[]; +} + +export interface ApiRespPipeline { + pipeline: Pipeline; +} + +export interface Pipeline { + uuid: string; + name: string; + description: string; + for_version: string; + config: object; + stages: string[]; + created_at: string; + updated_at: string; +} + +export interface ApiRespPlatformAdapters { + adapters: Adapter[]; +} + +export interface ApiRespPlatformAdapter { + adapter: Adapter; +} + +export interface Adapter { + name: string; + label: I18nText; + description: I18nText; + icon?: string; + spec: object; +} + +export interface ApiRespPlatformBots { + bots: Bot[]; +} + +export interface ApiRespPlatformBot { + bot: Bot; +} + +export interface Bot { + uuid: string; + name: string; + description: string; + enable: boolean; + adapter: string; + adapter_config: object; + use_pipeline_name: string; + use_pipeline_uuid: string; + created_at: string; + updated_at: string; +} \ No newline at end of file diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index e765e539..8618f973 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -1,8 +1,13 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' +import { + ApiResponse, ApiRespProviderRequesters, ApiRespProviderRequester, ApiRespProviderLLMModels, + ApiRespProviderLLMModel, LLMModel, ApiRespPipelines, ApiRespPipeline, Pipeline, ApiRespPlatformAdapters, + ApiRespPlatformAdapter, ApiRespPlatformBots, ApiRespPlatformBot, Bot +} from '../api/api-types' type JSONValue = string | number | boolean | JSONObject | JSONArray | null interface JSONObject { [key: string]: JSONValue } -interface JSONArray extends Array {} +interface JSONArray extends Array { } export interface ResponseData { code: number @@ -42,7 +47,7 @@ class HttpClient { return "" } // 客户端环境 - return "" + return "" } // 获取Session @@ -172,6 +177,98 @@ class HttpClient { public delete(url: string, config?: RequestConfig) { return this.request({ method: 'delete', url, ...config }) } + + // real api request implementation + // ============ Provider API ============ + public getProviderRequesters(): Promise> { + return this.get('/api/v1/provider/requesters') + } + + public getProviderRequester(name: string): Promise> { + return this.get(`/api/v1/provider/requesters/${name}`) + } + + public getProviderRequesterIconURL(name: string): string { + return `/api/v1/provider/requesters/${name}/icon` + } + + // ============ Provider Model LLM ============ + public getProviderLLMModels(): Promise> { + return this.get('/api/v1/provider/models/llm') + } + + public getProviderLLMModel(uuid: string): Promise> { + return this.get(`/api/v1/provider/models/llm/${uuid}`) + } + + public createProviderLLMModel(model: LLMModel): Promise> { + return this.post('/api/v1/provider/models/llm', model) + } + + public deleteProviderLLMModel(uuid: string): Promise> { + return this.delete(`/api/v1/provider/models/llm/${uuid}`) + } + + // ============ Pipeline API ============ + public getGeneralPipelineMetadata(): Promise> { // as designed, this method will be deprecated, and only for developer to check the prefered config schema + return this.get('/api/v1/pipelines/_/metadata') + } + + public getPipelines(): Promise> { + return this.get('/api/v1/pipelines') + } + + public getPipeline(uuid: string): Promise> { + return this.get(`/api/v1/pipelines/${uuid}`) + } + + public createPipeline(pipeline: Pipeline): Promise> { + return this.post('/api/v1/pipelines', pipeline) + } + + public updatePipeline(uuid: string, pipeline: Pipeline): Promise> { + return this.put(`/api/v1/pipelines/${uuid}`, pipeline) + } + + public deletePipeline(uuid: string): Promise> { + return this.delete(`/api/v1/pipelines/${uuid}`) + } + + // ============ Platform API ============ + public getAdapters(): Promise> { + return this.get('/api/v1/platform/adapters') + } + + public getAdapter(name: string): Promise> { + return this.get(`/api/v1/platform/adapters/${name}`) + } + + public getAdapterIconURL(name: string): string { + return `/api/v1/platform/adapters/${name}/icon` + } + + // ============ Platform Bots ============ + public getBots(): Promise> { + return this.get('/api/v1/platform/bots') + } + + public getBot(uuid: string): Promise> { + return this.get(`/api/v1/platform/bots/${uuid}`) + } + + public createBot(bot: Bot): Promise> { + return this.post('/api/v1/platform/bots', bot) + } + + public updateBot(uuid: string, bot: Bot): Promise> { + return this.put(`/api/v1/platform/bots/${uuid}`, bot) + } + + public deleteBot(uuid: string): Promise> { + return this.delete(`/api/v1/platform/bots/${uuid}`) + } + + // ============ Plugins API ============ } export const httpClient = new HttpClient() From 2d64447c08f0354565412a9084ae2f0013ac8525 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 24 Apr 2025 22:42:31 +0800 Subject: [PATCH 006/121] feat(webui): user, system, plugins api client --- web_ui/src/app/infra/api/api-types/index.ts | 79 +++++++++++++++++++++ web_ui/src/app/infra/http/HttpClient.ts | 69 +++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index d5615c9f..35d91057 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -9,6 +9,10 @@ export interface I18nText { zh_CN: string; } +export interface AsyncTaskCreatedResp { + task_id: number; +} + export interface ApiRespProviderRequesters { requesters: Requester[]; } @@ -100,4 +104,79 @@ export interface Bot { use_pipeline_uuid: string; created_at: string; updated_at: string; +} + +// plugins +export interface ApiRespPlugins { + plugins: Plugin[]; +} + +export interface ApiRespPlugin { + plugin: Plugin; +} + +export interface Plugin { + author: string; + name: string; + description: I18nText; + label: I18nText; + version: string; + enabled: boolean; + priority: number; + status: string; + tools: object[]; + event_handlers: object; + main_file: string; + pkg_path: string; + repository: string; + config_schema: object; +} + +export interface ApiRespPluginConfig { + config: object; +} + +export interface PluginReorderElement { + author: string; + name: string; + priority: number; +} + +// system +export interface ApiRespSystemInfo { + debug: boolean; + version: string; +} + +export interface ApiRespAsyncTasks { + tasks: AsyncTask[]; +} + +export interface ApiRespAsyncTask { + task: AsyncTask; +} + +export interface AsyncTaskRuntimeInfo { + done: boolean; + exception?: string; + result?: object; + state: string; +} + +export interface AsyncTaskTaskContext { + current_action: string; + log: string; +} + +export interface AsyncTask { + id: number; + kind: string; + name: string; + task_type: string; // system or user + runtime: AsyncTaskRuntimeInfo; + task_context: AsyncTaskTaskContext; +} + +export interface ApiRespUserToken { + token: string; } \ No newline at end of file diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index 8618f973..af0cdb35 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -2,7 +2,9 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } f import { ApiResponse, ApiRespProviderRequesters, ApiRespProviderRequester, ApiRespProviderLLMModels, ApiRespProviderLLMModel, LLMModel, ApiRespPipelines, ApiRespPipeline, Pipeline, ApiRespPlatformAdapters, - ApiRespPlatformAdapter, ApiRespPlatformBots, ApiRespPlatformBot, Bot + ApiRespPlatformAdapter, ApiRespPlatformBots, ApiRespPlatformBot, Bot, ApiRespPlugins, ApiRespPlugin, Plugin, + ApiRespPluginConfig, PluginReorderElement, AsyncTaskCreatedResp, ApiRespSystemInfo, ApiRespAsyncTasks, AsyncTask, + ApiRespAsyncTask, ApiRespUserToken } from '../api/api-types' type JSONValue = string | number | boolean | JSONObject | JSONArray | null @@ -269,6 +271,71 @@ class HttpClient { } // ============ Plugins API ============ + public getPlugins(): Promise> { + return this.get('/api/v1/plugins') + } + + public getPlugin(author: string, name: string): Promise> { + return this.get(`/api/v1/plugins/${author}/${name}`) + } + + public getPluginConfig(author: string, name: string): Promise> { + return this.get(`/api/v1/plugins/${author}/${name}/config`) + } + + public updatePluginConfig(author: string, name: string, config: object): Promise> { + return this.put(`/api/v1/plugins/${author}/${name}/config`, config) + } + + public togglePlugin(author: string, name: string, target_enabled: boolean): Promise> { + return this.post(`/api/v1/plugins/${author}/${name}/toggle`, { target_enabled }) + } + + public reorderPlugins(plugins: PluginReorderElement[]): Promise> { + return this.post('/api/v1/plugins/reorder', plugins) + } + + public updatePlugin(author: string, name: string): Promise> { + return this.post(`/api/v1/plugins/${author}/${name}/update`) + } + + public installPluginFromGithub(source: string): Promise> { + return this.post('/api/v1/plugins/install/github', { source }) + } + + public removePlugin(author: string, name: string): Promise> { + return this.delete(`/api/v1/plugins/${author}/${name}`) + } + + // ============ System API ============ + public getSystemInfo(): Promise> { + return this.get('/api/v1/system/info') + } + + public getAsyncTasks(): Promise> { + return this.get('/api/v1/system/tasks') + } + + public getAsyncTask(id: number): Promise> { + return this.get(`/api/v1/system/tasks/${id}`) + } + + // ============ User API ============ + public checkIfInited(): Promise> { + return this.get('/api/v1/user/init') + } + + public initUser(user: string, password: string): Promise> { + return this.post('/api/v1/user/init', { user, password }) + } + + public authUser(user: string, password: string): Promise> { + return this.post('/api/v1/user/auth', { user, password }) + } + + public checkUserToken(): Promise> { + return this.get('/api/v1/user/check-token') + } } export const httpClient = new HttpClient() From e03e12539a2a15185133ffb36bce265dd9c189db Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 26 Apr 2025 11:19:05 +0800 Subject: [PATCH 007/121] refactor: rename page routers --- .../{bot-config => bots}/ICreateBotField.ts | 0 .../{bot-config => bots}/botConfig.module.css | 0 .../components/bot-card/BotCard.tsx | 2 +- .../components/bot-card/BotCardVO.ts | 0 .../bot-card/CreateBotCardComponent.tsx | 0 .../components/bot-card/botCard.module.css | 0 .../components/bot-form/BotForm.tsx | 6 ++--- .../components/bot-form/BotFormEntity.ts | 0 .../bot-form/ChooseAdapterEntity.ts | 0 .../app/home/{bot-config => bots}/page.tsx | 8 +++---- .../home-sidebar/sidbarConfigList.ts | 24 +++++++++---------- .../{llm-config => models}/ICreateLLMField.ts | 0 .../LLMConfig.module.css | 0 .../component/llm-card/LLMCard.tsx | 2 +- .../component/llm-card/LLMCardVO.ts | 0 .../component/llm-form/LLMForm.tsx | 4 ++-- .../app/home/{llm-config => models}/page.tsx | 6 ++--- .../pipeline-form/PipelineChildFormEntity.ts | 0 .../pipeline-form/PipelineFormComponent.tsx | 0 .../pipelineFormStyle.module.css | 0 .../{pipeline-config => pipelines}/page.tsx | 0 .../pipelineConfig.module.css | 0 .../home/{plugin-config => plugins}/page.tsx | 0 23 files changed, 26 insertions(+), 26 deletions(-) rename web_ui/src/app/home/{bot-config => bots}/ICreateBotField.ts (100%) rename web_ui/src/app/home/{bot-config => bots}/botConfig.module.css (100%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-card/BotCard.tsx (94%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-card/BotCardVO.ts (100%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-card/CreateBotCardComponent.tsx (100%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-card/botCard.module.css (100%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-form/BotForm.tsx (96%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-form/BotFormEntity.ts (100%) rename web_ui/src/app/home/{bot-config => bots}/components/bot-form/ChooseAdapterEntity.ts (100%) rename web_ui/src/app/home/{bot-config => bots}/page.tsx (94%) rename web_ui/src/app/home/{llm-config => models}/ICreateLLMField.ts (100%) rename web_ui/src/app/home/{llm-config => models}/LLMConfig.module.css (100%) rename web_ui/src/app/home/{llm-config => models}/component/llm-card/LLMCard.tsx (94%) rename web_ui/src/app/home/{llm-config => models}/component/llm-card/LLMCardVO.ts (100%) rename web_ui/src/app/home/{llm-config => models}/component/llm-form/LLMForm.tsx (97%) rename web_ui/src/app/home/{llm-config => models}/page.tsx (94%) rename web_ui/src/app/home/{pipeline-config => pipelines}/components/pipeline-form/PipelineChildFormEntity.ts (100%) rename web_ui/src/app/home/{pipeline-config => pipelines}/components/pipeline-form/PipelineFormComponent.tsx (100%) rename web_ui/src/app/home/{pipeline-config => pipelines}/components/pipeline-form/pipelineFormStyle.module.css (100%) rename web_ui/src/app/home/{pipeline-config => pipelines}/page.tsx (100%) rename web_ui/src/app/home/{pipeline-config => pipelines}/pipelineConfig.module.css (100%) rename web_ui/src/app/home/{plugin-config => plugins}/page.tsx (100%) diff --git a/web_ui/src/app/home/bot-config/ICreateBotField.ts b/web_ui/src/app/home/bots/ICreateBotField.ts similarity index 100% rename from web_ui/src/app/home/bot-config/ICreateBotField.ts rename to web_ui/src/app/home/bots/ICreateBotField.ts diff --git a/web_ui/src/app/home/bot-config/botConfig.module.css b/web_ui/src/app/home/bots/botConfig.module.css similarity index 100% rename from web_ui/src/app/home/bot-config/botConfig.module.css rename to web_ui/src/app/home/bots/botConfig.module.css diff --git a/web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx b/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx similarity index 94% rename from web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx rename to web_ui/src/app/home/bots/components/bot-card/BotCard.tsx index af078a24..29dd54f0 100644 --- a/web_ui/src/app/home/bot-config/components/bot-card/BotCard.tsx +++ b/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx @@ -1,4 +1,4 @@ -import {BotCardVO} from "@/app/home/bot-config/components/bot-card/BotCardVO"; +import {BotCardVO} from "@/app/home/bots/components/bot-card/BotCardVO"; import styles from "./botCard.module.css"; export default function BotCard({ diff --git a/web_ui/src/app/home/bot-config/components/bot-card/BotCardVO.ts b/web_ui/src/app/home/bots/components/bot-card/BotCardVO.ts similarity index 100% rename from web_ui/src/app/home/bot-config/components/bot-card/BotCardVO.ts rename to web_ui/src/app/home/bots/components/bot-card/BotCardVO.ts diff --git a/web_ui/src/app/home/bot-config/components/bot-card/CreateBotCardComponent.tsx b/web_ui/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx similarity index 100% rename from web_ui/src/app/home/bot-config/components/bot-card/CreateBotCardComponent.tsx rename to web_ui/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx diff --git a/web_ui/src/app/home/bot-config/components/bot-card/botCard.module.css b/web_ui/src/app/home/bots/components/bot-card/botCard.module.css similarity index 100% rename from web_ui/src/app/home/bot-config/components/bot-card/botCard.module.css rename to web_ui/src/app/home/bots/components/bot-card/botCard.module.css diff --git a/web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx similarity index 96% rename from web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx rename to web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index f1d65056..72b5f69f 100644 --- a/web_ui/src/app/home/bot-config/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -1,8 +1,8 @@ -import {BotFormEntity, IBotFormEntity} from "@/app/home/bot-config/components/bot-form/BotFormEntity"; +import {BotFormEntity, IBotFormEntity} from "@/app/home/bots/components/bot-form/BotFormEntity"; import {fetchAdapterList} from "@/app/home/mock-api/index" import {Button, Form, Input, Select, Space} from "antd"; import {useEffect, useState} from "react"; -import {IChooseAdapterEntity} from "@/app/home/bot-config/components/bot-form/ChooseAdapterEntity"; +import {IChooseAdapterEntity} from "@/app/home/bots/components/bot-form/ChooseAdapterEntity"; import { DynamicFormItemConfig, IDynamicFormItemConfig, @@ -10,7 +10,7 @@ import { } from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; import {UUID} from 'uuidjs' import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; -import {ICreateLLMField} from "@/app/home/llm-config/ICreateLLMField"; +import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; export default function BotForm({ initBotId, diff --git a/web_ui/src/app/home/bot-config/components/bot-form/BotFormEntity.ts b/web_ui/src/app/home/bots/components/bot-form/BotFormEntity.ts similarity index 100% rename from web_ui/src/app/home/bot-config/components/bot-form/BotFormEntity.ts rename to web_ui/src/app/home/bots/components/bot-form/BotFormEntity.ts diff --git a/web_ui/src/app/home/bot-config/components/bot-form/ChooseAdapterEntity.ts b/web_ui/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts similarity index 100% rename from web_ui/src/app/home/bot-config/components/bot-form/ChooseAdapterEntity.ts rename to web_ui/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts diff --git a/web_ui/src/app/home/bot-config/page.tsx b/web_ui/src/app/home/bots/page.tsx similarity index 94% rename from web_ui/src/app/home/bot-config/page.tsx rename to web_ui/src/app/home/bots/page.tsx index d2a36f08..4ac6ca5b 100644 --- a/web_ui/src/app/home/bot-config/page.tsx +++ b/web_ui/src/app/home/bots/page.tsx @@ -4,10 +4,10 @@ import {useEffect, useState} from "react"; import styles from "./botConfig.module.css"; import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent"; import {useRouter} from "next/navigation"; -import {BotCardVO} from "@/app/home/bot-config/components/bot-card/BotCardVO"; +import {BotCardVO} from "@/app/home/bots/components/bot-card/BotCardVO"; import {Modal} from "antd"; -import BotForm from "@/app/home/bot-config/components/bot-form/BotForm"; -import BotCard from "@/app/home/bot-config/components/bot-card/BotCard"; +import BotForm from "@/app/home/bots/components/bot-form/BotForm"; +import BotCard from "@/app/home/bots/components/bot-card/BotCard"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent" @@ -105,7 +105,7 @@ export default function BotConfigPage() { subTitle={"快去创建一个吧!"} buttonText={"创建大模型 GO!"} onButtonClick={() => { - router.push("/home/llm-config"); + router.push("/home/models"); }} /> } diff --git a/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts b/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts index 3c96e4f6..ff78a281 100644 --- a/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts +++ b/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts @@ -2,27 +2,27 @@ import {SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChil export const sidebarConfigList = [ new SidebarChildVO({ - id: "llm-config", - name: "大模型配置", + id: "models", + name: "模型配置", icon: "", - route: "/home/llm-config", + route: "/home/models", }), new SidebarChildVO({ - id: "platform-config", - name: "机器人配置", + id: "bots", + name: "机器人", icon: "", - route: "/home/bot-config", + route: "/home/bots", }), new SidebarChildVO({ - id: "plugin-config", - name: "插件配置", + id: "plugins", + name: "插件管理", icon: "", - route: "/home/plugin-config", + route: "/home/plugins", }), new SidebarChildVO({ - id: "pipeline-config", - name: "流水线配置", + id: "pipelines", + name: "流水线", icon: "", - route: "/home/pipeline-config", + route: "/home/pipelines", }) ] diff --git a/web_ui/src/app/home/llm-config/ICreateLLMField.ts b/web_ui/src/app/home/models/ICreateLLMField.ts similarity index 100% rename from web_ui/src/app/home/llm-config/ICreateLLMField.ts rename to web_ui/src/app/home/models/ICreateLLMField.ts diff --git a/web_ui/src/app/home/llm-config/LLMConfig.module.css b/web_ui/src/app/home/models/LLMConfig.module.css similarity index 100% rename from web_ui/src/app/home/llm-config/LLMConfig.module.css rename to web_ui/src/app/home/models/LLMConfig.module.css diff --git a/web_ui/src/app/home/llm-config/component/llm-card/LLMCard.tsx b/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx similarity index 94% rename from web_ui/src/app/home/llm-config/component/llm-card/LLMCard.tsx rename to web_ui/src/app/home/models/component/llm-card/LLMCard.tsx index a8ab5f4b..9a11e2b3 100644 --- a/web_ui/src/app/home/llm-config/component/llm-card/LLMCard.tsx +++ b/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx @@ -1,5 +1,5 @@ import styles from "../../LLMConfig.module.css" -import {LLMCardVO} from "@/app/home/llm-config/component/llm-card/LLMCardVO"; +import {LLMCardVO} from "@/app/home/models/component/llm-card/LLMCardVO"; export default function LLMCard({ cardVO diff --git a/web_ui/src/app/home/llm-config/component/llm-card/LLMCardVO.ts b/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts similarity index 100% rename from web_ui/src/app/home/llm-config/component/llm-card/LLMCardVO.ts rename to web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts diff --git a/web_ui/src/app/home/llm-config/component/llm-form/LLMForm.tsx b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx similarity index 97% rename from web_ui/src/app/home/llm-config/component/llm-form/LLMForm.tsx rename to web_ui/src/app/home/models/component/llm-form/LLMForm.tsx index 433bf44a..e0ca4b84 100644 --- a/web_ui/src/app/home/llm-config/component/llm-form/LLMForm.tsx +++ b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx @@ -1,6 +1,6 @@ -import styles from "@/app/home/llm-config/LLMConfig.module.css"; +import styles from "@/app/home/models/LLMConfig.module.css"; import {Button, Form, Input, Select, SelectProps, Space} from "antd"; -import {ICreateLLMField} from "@/app/home/llm-config/ICreateLLMField"; +import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import {useEffect, useState} from "react"; export default function LLMForm({ diff --git a/web_ui/src/app/home/llm-config/page.tsx b/web_ui/src/app/home/models/page.tsx similarity index 94% rename from web_ui/src/app/home/llm-config/page.tsx rename to web_ui/src/app/home/models/page.tsx index 56d8b25a..a0ed8fd0 100644 --- a/web_ui/src/app/home/llm-config/page.tsx +++ b/web_ui/src/app/home/models/page.tsx @@ -1,12 +1,12 @@ "use client" import {useState} from "react"; -import {LLMCardVO} from "@/app/home/llm-config/component/llm-card/LLMCardVO"; +import {LLMCardVO} from "@/app/home/models/component/llm-card/LLMCardVO"; import styles from "./LLMConfig.module.css" import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent"; import {Modal} from "antd"; -import LLMCard from "@/app/home/llm-config/component/llm-card/LLMCard"; -import LLMForm from "@/app/home/llm-config/component/llm-form/LLMForm"; +import LLMCard from "@/app/home/models/component/llm-card/LLMCard"; +import LLMForm from "@/app/home/models/component/llm-form/LLMForm"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; export default function LLMConfigPage() { diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineChildFormEntity.ts b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts similarity index 100% rename from web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineChildFormEntity.ts rename to web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx similarity index 100% rename from web_ui/src/app/home/pipeline-config/components/pipeline-form/PipelineFormComponent.tsx rename to web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx diff --git a/web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css b/web_ui/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css similarity index 100% rename from web_ui/src/app/home/pipeline-config/components/pipeline-form/pipelineFormStyle.module.css rename to web_ui/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css diff --git a/web_ui/src/app/home/pipeline-config/page.tsx b/web_ui/src/app/home/pipelines/page.tsx similarity index 100% rename from web_ui/src/app/home/pipeline-config/page.tsx rename to web_ui/src/app/home/pipelines/page.tsx diff --git a/web_ui/src/app/home/pipeline-config/pipelineConfig.module.css b/web_ui/src/app/home/pipelines/pipelineConfig.module.css similarity index 100% rename from web_ui/src/app/home/pipeline-config/pipelineConfig.module.css rename to web_ui/src/app/home/pipelines/pipelineConfig.module.css diff --git a/web_ui/src/app/home/plugin-config/page.tsx b/web_ui/src/app/home/plugins/page.tsx similarity index 100% rename from web_ui/src/app/home/plugin-config/page.tsx rename to web_ui/src/app/home/plugins/page.tsx From 4db15fcac7cc591b71849ecf7b8730344b5c4649 Mon Sep 17 00:00:00 2001 From: HYana Date: Sat, 26 Apr 2025 18:23:52 +0800 Subject: [PATCH 008/121] feta:plugin page temporary commit --- web_ui/src/app/home/plugins/page.tsx | 42 ++++++++++- .../plugins/plugin-installed/PluginCardVO.ts | 24 +++++++ .../PluginInstalledComponent.tsx | 71 +++++++++++++++++++ .../plugin-card/PluginCardComponent.tsx | 33 +++++++++ .../plugin-card/pluginCard.module.css | 49 +++++++++++++ .../plugin-market/PluginMarketComponent.tsx | 9 +++ .../PluginMarketCardComponent.tsx | 7 ++ 7 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts create mode 100644 web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx create mode 100644 web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx create mode 100644 web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css create mode 100644 web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx create mode 100644 web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx diff --git a/web_ui/src/app/home/plugins/page.tsx b/web_ui/src/app/home/plugins/page.tsx index 8bc22362..877f89e2 100644 --- a/web_ui/src/app/home/plugins/page.tsx +++ b/web_ui/src/app/home/plugins/page.tsx @@ -1,7 +1,45 @@ -export default function pluginConfigPage() { +"use client" +import { Radio } from 'antd'; +import {useState} from "react"; +import PluginInstalledComponent from "@/app/home/plugins/plugin-installed/PluginInstalledComponent"; +import PluginMarketComponent from "@/app/home/plugins/plugin-market/PluginMarketComponent"; + +export default function PluginConfigPage() { + enum PageType { + INSTALLED = "installed", + MARKET = 'market' + } + + const [nowPageType, setNowPageType] = useState(PageType.INSTALLED) + return (
-

PluginConfigPage

+
+ { + // 这里静态类型检测有问题 + setNowPageType(e.target.value) + }} + /> +
+
+ { + nowPageType === PageType.INSTALLED && + } + { + nowPageType === PageType.MARKET && + } +
+
); } diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts b/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts new file mode 100644 index 00000000..e83bd75c --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts @@ -0,0 +1,24 @@ +export interface IPluginCardVO { + author: string, + version: string, + name: string, + description: string, + handlerCount: number, +} + +export class PluginCardVO implements IPluginCardVO{ + description: string; + handlerCount: number; + name: string; + author: string; + version: string; + + constructor(prop: IPluginCardVO) { + this.description = prop.description + this.handlerCount = prop.handlerCount + this.name = prop.name + this.author = prop.author + this.version = prop.version + } + +} diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx new file mode 100644 index 00000000..39e03b86 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -0,0 +1,71 @@ +"use client" + +import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; +import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; +import {useEffect, useState} from "react"; +import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent"; + +export default function PluginInstalledComponent () { + const [pluginList, setPluginList] = useState([]) + + useEffect(() => { + initData() + }, []) + + function initData() { + getPluginList().then((value) => { + setPluginList(value) + }) + } + + async function getPluginList() { + return [ + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1" + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1" + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1" + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1" + }) + ] + } + + return ( +
+ { + pluginList.map((vo, index) => { + return
+ +
+ }) + } + {}} + /> +
+ ) +} diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx new file mode 100644 index 00000000..162dc271 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -0,0 +1,33 @@ +import styles from "./pluginCard.module.css" +import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; + +export default function PluginCardComponent({ + cardVO +}: { + cardVO: PluginCardVO +}) { + return ( +
+ {/* header */} +
+ {/* left author */} +
{cardVO.author}
+ {/* right icon & version */} +
+ +
+
+ {/* content */} +
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */} +
+
+ +
+
+
+ ); +} diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css new file mode 100644 index 00000000..6f966543 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css @@ -0,0 +1,49 @@ +.cardContainer { + width: 360px; + height: 129px; + background-color: #FFF; + border-radius: 9px; + box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; +} + +.cardHeader { + width: 100%; + height: 30px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + background-color: #79c1f4; + + .iconVersionContainer { + width: 100px; + height: 100%; + background-color: #063a5a; + } +} + +.cardContent { + width: 100%; + height: 70px; + background-color: #e38787; +} + +.cardFooter { + width: 100%; + height: 30px; + background-color: #f8ff6d; +} + + +.fontGray { + color: #6C6C6C; +} + +.boldFont { + font-size: 22px; + font-weight: bold; +} diff --git a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx new file mode 100644 index 00000000..c6b7b540 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -0,0 +1,9 @@ +"use client" + +export default function PluginMarketComponent () { + return ( +
+ plugin-market +
+ ) +} diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx new file mode 100644 index 00000000..b2a94b74 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx @@ -0,0 +1,7 @@ +export function PluginMarketCardComponent() { + return ( +
+ plugin market card +
+ ) +} From 59e4c85be5de651068650476eef346a25424bcd6 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 26 Apr 2025 19:39:45 +0800 Subject: [PATCH 009/121] fix: bad ret type of api client request methods --- web_ui/src/app/home/bots/page.tsx | 60 ++++++++++--------- web_ui/src/app/infra/http/HttpClient.ts | 78 +++++++++++++------------ 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/web_ui/src/app/home/bots/page.tsx b/web_ui/src/app/home/bots/page.tsx index 4ac6ca5b..4e7a8c5c 100644 --- a/web_ui/src/app/home/bots/page.tsx +++ b/web_ui/src/app/home/bots/page.tsx @@ -9,6 +9,7 @@ import {Modal} from "antd"; import BotForm from "@/app/home/bots/components/bot-form/BotForm"; import BotCard from "@/app/home/bots/components/bot-card/BotCard"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent" +import {httpClient} from "@/app/infra/http/HttpClient"; export default function BotConfigPage() { @@ -23,13 +24,17 @@ export default function BotConfigPage() { // TODO:补齐加载转圈逻辑 checkHasLLM().then((hasLLM) => { if (hasLLM) { - const botList = getBotList() - if (botList.length === 0) { - setPageShowRule(BotConfigPageShowRule.NO_BOT) - } else { + getBotList().then((botList) => { + if (botList.length === 0) { + setPageShowRule(BotConfigPageShowRule.NO_BOT) + } else { setPageShowRule(BotConfigPageShowRule.HAVE_BOT) - } - setBotList(botList) + } + setBotList(botList) + }).catch((err) => { + // TODO error toast + console.error("get bot list error (useEffect)", err) + }) } else { setPageShowRule(BotConfigPageShowRule.NO_LLM) } @@ -41,27 +46,28 @@ export default function BotConfigPage() { return true } - function getBotList(): BotCardVO[] { - let botList: BotCardVO[] = [ - new BotCardVO({ - adapter: "QQ bot", - description: "1111", - id: "1111", - name: "第一个bot", - updateTime: "202300001111", - pipelineName: "默认流水线", - }), - new BotCardVO({ - adapter: "WX bot", - description: "22211", - id: "2222", - name: "第2个bot", - updateTime: "2025011011", - pipelineName: "默认流水线", - }), - ] - // botList = [] - return botList + function getBotList(): Promise { + + return new Promise((resolve) => { + httpClient.getBots().then((resp) => { + console.log("get bot list (getBotList)", resp) + const botList: BotCardVO[] = resp.bots.map((bot: any) => { + return new BotCardVO({ + adapter: bot.adapter, + description: bot.description, + id: bot.id, + name: bot.name, + updateTime: bot.update_time, + pipelineName: bot.pipeline_name, + }) + }) + resolve(botList) + }).catch((err) => { + // TODO error toast + console.error("get bot list error", err) + resolve([]) + }) + }) } function handleCreateBotClick() { diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index af0cdb35..f02bbff2 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -43,6 +43,7 @@ class HttpClient { // 兜底URL,如果使用未配置会走到这里 private getBaseUrl(): string { + return "http://localhost:5300" // NOT IMPLEMENT if (typeof window === 'undefined') { // 服务端环境 @@ -61,7 +62,7 @@ class HttpClient { // 同步获取Session private getSessionSync() { // NOT IMPLEMENT - return "" + return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicm9ja2NoaW5xQGdtYWlsLmNvbSIsImlzcyI6IkxhbmdCb3QtY29tbXVuaXR5IiwiZXhwIjoyMzUwNDk1NTQ3fQ.d6r0lNGud1OecOLMM-ADDDwiABmek3hkMIFH7ZBkaX4" } // 拦截器配置 @@ -71,8 +72,9 @@ class HttpClient { async (config) => { // 服务端请求自动携带 cookie, Langbot暂时用不到SSR相关 // if (typeof window === 'undefined' && config.isSSR) { } - const { cookies } = await import('next/headers') - config.headers.Cookie = cookies().toString() + // cookie not required + // const { cookies } = await import('next/headers') + // config.headers.Cookie = cookies().toString() // 客户端添加认证头 if (typeof window !== 'undefined') { @@ -182,11 +184,11 @@ class HttpClient { // real api request implementation // ============ Provider API ============ - public getProviderRequesters(): Promise> { + public getProviderRequesters(): Promise { return this.get('/api/v1/provider/requesters') } - public getProviderRequester(name: string): Promise> { + public getProviderRequester(name: string): Promise { return this.get(`/api/v1/provider/requesters/${name}`) } @@ -195,53 +197,53 @@ class HttpClient { } // ============ Provider Model LLM ============ - public getProviderLLMModels(): Promise> { + public getProviderLLMModels(): Promise { return this.get('/api/v1/provider/models/llm') } - public getProviderLLMModel(uuid: string): Promise> { + public getProviderLLMModel(uuid: string): Promise { return this.get(`/api/v1/provider/models/llm/${uuid}`) } - public createProviderLLMModel(model: LLMModel): Promise> { + public createProviderLLMModel(model: LLMModel): Promise { return this.post('/api/v1/provider/models/llm', model) } - public deleteProviderLLMModel(uuid: string): Promise> { + public deleteProviderLLMModel(uuid: string): Promise { return this.delete(`/api/v1/provider/models/llm/${uuid}`) } // ============ Pipeline API ============ - public getGeneralPipelineMetadata(): Promise> { // as designed, this method will be deprecated, and only for developer to check the prefered config schema + public getGeneralPipelineMetadata(): Promise { // as designed, this method will be deprecated, and only for developer to check the prefered config schema return this.get('/api/v1/pipelines/_/metadata') } - public getPipelines(): Promise> { + public getPipelines(): Promise { return this.get('/api/v1/pipelines') } - public getPipeline(uuid: string): Promise> { + public getPipeline(uuid: string): Promise { return this.get(`/api/v1/pipelines/${uuid}`) } - public createPipeline(pipeline: Pipeline): Promise> { + public createPipeline(pipeline: Pipeline): Promise { return this.post('/api/v1/pipelines', pipeline) } - public updatePipeline(uuid: string, pipeline: Pipeline): Promise> { + public updatePipeline(uuid: string, pipeline: Pipeline): Promise { return this.put(`/api/v1/pipelines/${uuid}`, pipeline) } - public deletePipeline(uuid: string): Promise> { + public deletePipeline(uuid: string): Promise { return this.delete(`/api/v1/pipelines/${uuid}`) } // ============ Platform API ============ - public getAdapters(): Promise> { + public getAdapters(): Promise { return this.get('/api/v1/platform/adapters') } - public getAdapter(name: string): Promise> { + public getAdapter(name: string): Promise { return this.get(`/api/v1/platform/adapters/${name}`) } @@ -250,90 +252,90 @@ class HttpClient { } // ============ Platform Bots ============ - public getBots(): Promise> { + public getBots(): Promise { return this.get('/api/v1/platform/bots') } - public getBot(uuid: string): Promise> { + public getBot(uuid: string): Promise { return this.get(`/api/v1/platform/bots/${uuid}`) } - public createBot(bot: Bot): Promise> { + public createBot(bot: Bot): Promise { return this.post('/api/v1/platform/bots', bot) } - public updateBot(uuid: string, bot: Bot): Promise> { + public updateBot(uuid: string, bot: Bot): Promise { return this.put(`/api/v1/platform/bots/${uuid}`, bot) } - public deleteBot(uuid: string): Promise> { + public deleteBot(uuid: string): Promise { return this.delete(`/api/v1/platform/bots/${uuid}`) } // ============ Plugins API ============ - public getPlugins(): Promise> { + public getPlugins(): Promise { return this.get('/api/v1/plugins') } - public getPlugin(author: string, name: string): Promise> { + public getPlugin(author: string, name: string): Promise { return this.get(`/api/v1/plugins/${author}/${name}`) } - public getPluginConfig(author: string, name: string): Promise> { + public getPluginConfig(author: string, name: string): Promise { return this.get(`/api/v1/plugins/${author}/${name}/config`) } - public updatePluginConfig(author: string, name: string, config: object): Promise> { + public updatePluginConfig(author: string, name: string, config: object): Promise { return this.put(`/api/v1/plugins/${author}/${name}/config`, config) } - public togglePlugin(author: string, name: string, target_enabled: boolean): Promise> { + public togglePlugin(author: string, name: string, target_enabled: boolean): Promise { return this.post(`/api/v1/plugins/${author}/${name}/toggle`, { target_enabled }) } - public reorderPlugins(plugins: PluginReorderElement[]): Promise> { + public reorderPlugins(plugins: PluginReorderElement[]): Promise { return this.post('/api/v1/plugins/reorder', plugins) } - public updatePlugin(author: string, name: string): Promise> { + public updatePlugin(author: string, name: string): Promise { return this.post(`/api/v1/plugins/${author}/${name}/update`) } - public installPluginFromGithub(source: string): Promise> { + public installPluginFromGithub(source: string): Promise { return this.post('/api/v1/plugins/install/github', { source }) } - public removePlugin(author: string, name: string): Promise> { + public removePlugin(author: string, name: string): Promise { return this.delete(`/api/v1/plugins/${author}/${name}`) } // ============ System API ============ - public getSystemInfo(): Promise> { + public getSystemInfo(): Promise { return this.get('/api/v1/system/info') } - public getAsyncTasks(): Promise> { + public getAsyncTasks(): Promise { return this.get('/api/v1/system/tasks') } - public getAsyncTask(id: number): Promise> { + public getAsyncTask(id: number): Promise { return this.get(`/api/v1/system/tasks/${id}`) } // ============ User API ============ - public checkIfInited(): Promise> { + public checkIfInited(): Promise { return this.get('/api/v1/user/init') } - public initUser(user: string, password: string): Promise> { + public initUser(user: string, password: string): Promise { return this.post('/api/v1/user/init', { user, password }) } - public authUser(user: string, password: string): Promise> { + public authUser(user: string, password: string): Promise { return this.post('/api/v1/user/auth', { user, password }) } - public checkUserToken(): Promise> { + public checkUserToken(): Promise { return this.get('/api/v1/user/check-token') } } From cf2e1a473e1cbf28c763625cef295d9bccd203fd Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 26 Apr 2025 19:54:54 +0800 Subject: [PATCH 010/121] feat: fetch adapters from api --- .../home/bots/components/bot-form/BotForm.tsx | 8 +- web_ui/src/app/home/mock-api/index.ts | 641 ------------------ 2 files changed, 4 insertions(+), 645 deletions(-) diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index 72b5f69f..4eb6918e 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -1,5 +1,4 @@ import {BotFormEntity, IBotFormEntity} from "@/app/home/bots/components/bot-form/BotFormEntity"; -import {fetchAdapterList} from "@/app/home/mock-api/index" import {Button, Form, Input, Select, Space} from "antd"; import {useEffect, useState} from "react"; import {IChooseAdapterEntity} from "@/app/home/bots/components/bot-form/ChooseAdapterEntity"; @@ -11,6 +10,7 @@ import { import {UUID} from 'uuidjs' import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; +import {httpClient} from "@/app/infra/http/HttpClient"; export default function BotForm({ initBotId, @@ -39,10 +39,10 @@ export default function BotForm({ async function initBotFormComponent() { // 拉取adapter - const rawAdapterList = await fetchAdapterList() + const rawAdapterList = await httpClient.getAdapters() // 初始化适配器选择列表 setAdapterNameList( - rawAdapterList.map(item => { + rawAdapterList.adapters.map(item => { return { label: item.label.zh_CN, value: item.name @@ -50,7 +50,7 @@ export default function BotForm({ }) ) // 初始化适配器表单map - rawAdapterList.forEach(rawAdapter => { + rawAdapterList.adapters.forEach(rawAdapter => { adapterNameToDynamicConfigMap.set( rawAdapter.name, rawAdapter.spec.config.map(item => diff --git a/web_ui/src/app/home/mock-api/index.ts b/web_ui/src/app/home/mock-api/index.ts index 3c4e40c9..ec6db0ef 100644 --- a/web_ui/src/app/home/mock-api/index.ts +++ b/web_ui/src/app/home/mock-api/index.ts @@ -1,647 +1,6 @@ import {GetMetaDataResponse} from "@/app/infra/api/api-types/pipelines/GetMetaDataResponse"; import {ApiResponse} from "@/app/infra/api/api-types"; -export async function fetchAdapterList() { - return [ - { - "description": { - "en_US": "Discord Adapter", - "zh_CN": "Discord 适配器" - }, - "icon": "", - "label": { - "en_US": "Discord", - "zh_CN": "Discord" - }, - "name": "discord", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "Client ID", - "zh_CN": "客户端ID" - }, - "name": "client_id", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "QQ Official API (Webhook)", - "zh_CN": "QQ 官方 API (Webhook)" - }, - "icon": "", - "label": { - "en_US": "QQ Official API", - "zh_CN": "QQ 官方 API" - }, - "name": "qqofficial", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "App ID", - "zh_CN": "应用ID" - }, - "name": "appid", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Secret", - "zh_CN": "密钥" - }, - "name": "secret", - "required": true, - "type": "string" - }, - { - "default": 2284, - "label": { - "en_US": "Port", - "zh_CN": "监听端口" - }, - "name": "port", - "required": true, - "type": "integer" - }, - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "Telegram Adapter", - "zh_CN": "电报适配器" - }, - "icon": "", - "label": { - "en_US": "Telegram", - "zh_CN": "电报" - }, - "name": "telegram", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "WeCom Adapter", - "zh_CN": "企业微信适配器" - }, - "icon": "", - "label": { - "en_US": "WeCom", - "zh_CN": "企业微信" - }, - "name": "wecom", - "spec": { - "config": [ - { - "default": "0.0.0.0", - "label": { - "en_US": "Host", - "zh_CN": "监听主机" - }, - "name": "host", - "required": true, - "type": "string" - }, - { - "default": 2290, - "label": { - "en_US": "Port", - "zh_CN": "监听端口" - }, - "name": "port", - "required": true, - "type": "integer" - }, - { - "default": "", - "label": { - "en_US": "Corpid", - "zh_CN": "企业ID" - }, - "name": "corpid", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Secret", - "zh_CN": "密钥" - }, - "name": "secret", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "EncodingAESKey", - "zh_CN": "消息加解密密钥" - }, - "name": "EncodingAESKey", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Contacts Secret", - "zh_CN": "通讯录密钥" - }, - "name": "contacts_secret", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "GeWeChat Adapter", - "zh_CN": "GeWeChat 适配器" - }, - "icon": "", - "label": { - "en_US": "GeWeChat", - "zh_CN": "GeWeChat(个人微信)" - }, - "name": "gewechat", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "GeWeChat URL", - "zh_CN": "GeWeChat URL" - }, - "name": "gewechat_url", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "GeWeChat file download URL", - "zh_CN": "GeWeChat 文件下载URL" - }, - "name": "gewechat_file_url", - "required": true, - "type": "string" - }, - { - "default": 2286, - "label": { - "en_US": "Port", - "zh_CN": "端口" - }, - "name": "port", - "required": true, - "type": "integer" - }, - { - "default": "", - "label": { - "en_US": "Callback URL", - "zh_CN": "回调URL" - }, - "name": "callback_url", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "App ID", - "zh_CN": "应用ID" - }, - "name": "app_id", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "OneBot v11 Adapter", - "zh_CN": "OneBot v11 适配器" - }, - "icon": "", - "label": { - "en_US": "OneBot v11 Adapter", - "zh_CN": "OneBot v11 适配器" - }, - "name": "aiocqhttp", - "spec": { - "config": [ - { - "default": "0.0.0.0", - "label": { - "en_US": "Host", - "zh_CN": "主机" - }, - "name": "host", - "required": true, - "type": "string" - }, - { - "default": 2280, - "label": { - "en_US": "Port", - "zh_CN": "端口" - }, - "name": "port", - "required": true, - "type": "integer" - }, - { - "default": "", - "label": { - "en_US": "Access Token", - "zh_CN": "访问令牌" - }, - "name": "access-token", - "required": false, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "Official Account Adapter", - "zh_CN": "微信公众号适配器" - }, - "icon": "", - "label": { - "en_US": "Official Account", - "zh_CN": "微信公众号" - }, - "name": "officialaccount", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "EncodingAESKey", - "zh_CN": "消息加解密密钥" - }, - "name": "EncodingAESKey", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "App ID", - "zh_CN": "应用ID" - }, - "name": "AppID", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "App Secret", - "zh_CN": "应用密钥" - }, - "name": "AppSecret", - "required": true, - "type": "string" - }, - { - "default": "0.0.0.0", - "label": { - "en_US": "Host", - "zh_CN": "监听主机" - }, - "name": "host", - "required": true, - "type": "string" - }, - { - "default": 2287, - "label": { - "en_US": "Port", - "zh_CN": "监听端口" - }, - "name": "port", - "required": true, - "type": "integer" - } - ] - } - }, - { - "description": { - "en_US": "Nakuru Adapter", - "zh_CN": "Nakuru 适配器(go-cqhttp)" - }, - "icon": "", - "label": { - "en_US": "Nakuru", - "zh_CN": "Nakuru" - }, - "name": "nakuru", - "spec": { - "config": [ - { - "default": "127.0.0.1", - "label": { - "en_US": "Host", - "zh_CN": "主机" - }, - "name": "host", - "required": true, - "type": "string" - }, - { - "default": 5700, - "label": { - "en_US": "HTTP Port", - "zh_CN": "HTTP端口" - }, - "name": "http_port", - "required": true, - "type": "integer" - }, - { - "default": 8080, - "label": { - "en_US": "WebSocket Port", - "zh_CN": "WebSocket端口" - }, - "name": "ws_port", - "required": true, - "type": "integer" - }, - { - "default": "", - "label": { - "en_US": "Token", - "zh_CN": "令牌" - }, - "name": "token", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "QQ Official API (WebSocket)", - "zh_CN": "QQ 官方 API (WebSocket)" - }, - "icon": "", - "label": { - "en_US": "QQBotPy", - "zh_CN": "QQBotPy" - }, - "name": "qq-botpy", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "App ID", - "zh_CN": "应用ID" - }, - "name": "appid", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Secret", - "zh_CN": "密钥" - }, - "name": "secret", - "required": true, - "type": "string" - }, - { - "default": [], - "label": { - "en_US": "Intents", - "zh_CN": "权限" - }, - "name": "intents", - "required": true, - "type": "array[string]" - } - ] - } - }, - { - "description": { - "en_US": "DingTalk Adapter", - "zh_CN": "钉钉适配器" - }, - "icon": "", - "label": { - "en_US": "DingTalk", - "zh_CN": "钉钉" - }, - "name": "dingtalk", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "Client ID", - "zh_CN": "客户端ID" - }, - "name": "client_id", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Client Secret", - "zh_CN": "客户端密钥" - }, - "name": "client_secret", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Robot Code", - "zh_CN": "机器人代码" - }, - "name": "robot_code", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Robot Name", - "zh_CN": "机器人名称" - }, - "name": "robot_name", - "required": true, - "type": "string" - } - ] - } - }, - { - "description": { - "en_US": "Lark Adapter", - "zh_CN": "飞书适配器" - }, - "icon": "", - "label": { - "en_US": "Lark", - "zh_CN": "飞书" - }, - "name": "lark", - "spec": { - "config": [ - { - "default": "", - "label": { - "en_US": "App ID", - "zh_CN": "应用ID" - }, - "name": "app_id", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "App Secret", - "zh_CN": "应用密钥" - }, - "name": "app_secret", - "required": true, - "type": "string" - }, - { - "default": "", - "label": { - "en_US": "Bot Name", - "zh_CN": "机器人名称" - }, - "name": "bot_name", - "required": true, - "type": "string" - }, - { - "default": false, - "label": { - "en_US": "Enable Webhook Mode", - "zh_CN": "启用Webhook模式" - }, - "name": "enable-webhook", - "required": true, - "type": "boolean" - }, - { - "default": 2285, - "label": { - "en_US": "Webhook Port", - "zh_CN": "Webhook端口" - }, - "name": "port", - "required": true, - "type": "integer" - }, - { - "default": "", - "label": { - "en_US": "Encrypt Key", - "zh_CN": "加密密钥" - }, - "name": "encrypt-key", - "required": true, - "type": "string" - } - ] - } - } - ] -} - export async function fetchPipelineMetaData(): Promise> { return { "code": 0, From ca183d2eb785a5db31820c0f05c99089999b4990 Mon Sep 17 00:00:00 2001 From: HYana Date: Sat, 26 Apr 2025 23:03:13 +0800 Subject: [PATCH 011/121] feat: finish installed plugin page & install from github --- web_ui/src/app/home/plugins/page.tsx | 3 +- .../PluginInstalledComponent.tsx | 54 +++++++++++++++++-- .../plugin-card/PluginCardComponent.tsx | 20 +++++-- .../plugin-card/pluginCard.module.css | 43 +++++++++++---- .../src/app/home/plugins/plugins.module.css | 25 +++++++++ 5 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 web_ui/src/app/home/plugins/plugins.module.css diff --git a/web_ui/src/app/home/plugins/page.tsx b/web_ui/src/app/home/plugins/page.tsx index 877f89e2..c7448c75 100644 --- a/web_ui/src/app/home/plugins/page.tsx +++ b/web_ui/src/app/home/plugins/page.tsx @@ -3,6 +3,7 @@ import { Radio } from 'antd'; import {useState} from "react"; import PluginInstalledComponent from "@/app/home/plugins/plugin-installed/PluginInstalledComponent"; import PluginMarketComponent from "@/app/home/plugins/plugin-market/PluginMarketComponent"; +import styles from './plugins.module.css' export default function PluginConfigPage() { enum PageType { @@ -31,7 +32,7 @@ export default function PluginConfigPage() { }} /> -
+
{ nowPageType === PageType.INSTALLED && } diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 39e03b86..aaa5d0e9 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -4,9 +4,15 @@ import CreateCardComponent from "@/app/infra/basic-component/create-card-compone import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; import {useEffect, useState} from "react"; import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent"; +import styles from "@/app/home/plugins/plugins.module.css"; +import {Modal, Input} from "antd"; +import {GithubOutlined} from "@ant-design/icons"; export default function PluginInstalledComponent () { const [pluginList, setPluginList] = useState([]) + const [modalOpen, setModalOpen] = useState(false) + const [githubURL, setGithubURL] = useState("") + useEffect(() => { initData() @@ -51,8 +57,48 @@ export default function PluginInstalledComponent () { ] } + function handleModalConfirm() { + installPlugin(githubURL) + setModalOpen(false) + } + + function installPlugin(url: string) { + // TODO 接安装Plugin的接口 + console.log("installPlugin: ", url) + } return ( -
+
+ + + 从 GitHub 安装插件 +
+ } + centered + open={modalOpen} + onOk={() => handleModalConfirm()} + onCancel={() => setModalOpen(false)} + width={500} + destroyOnClose={true} + > +
+
+ 目前仅支持从 GitHub 安装 +
+ setGithubURL(e.target.value)} + /> +
+ { pluginList.map((vo, index) => { return
@@ -62,9 +108,11 @@ export default function PluginInstalledComponent () { } {}} + onClick={() => { + setModalOpen(true) + }} />
) diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index 162dc271..be137ded 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -1,5 +1,7 @@ import styles from "./pluginCard.module.css" import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; +import {GithubOutlined, LinkOutlined, ToolOutlined} from '@ant-design/icons'; +import {Tag} from 'antd' export default function PluginCardComponent({ cardVO @@ -14,7 +16,11 @@ export default function PluginCardComponent({
{cardVO.author}
{/* right icon & version */}
- + + v{cardVO.version}
{/* content */} @@ -24,8 +30,16 @@ export default function PluginCardComponent({
{/* footer */}
-
- +
+
+ + 1 +
+
diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css index 6f966543..44ed2665 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css @@ -1,41 +1,45 @@ .cardContainer { width: 360px; - height: 129px; + height: 140px; + box-sizing: border-box; background-color: #FFF; border-radius: 9px; + padding-top: 10px; + padding-bottom: 10px; box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px; display: flex; flex-direction: column; - align-items: flex-start; + align-items: center; justify-content: space-evenly; } .cardHeader { - width: 100%; + width: 90%; height: 30px; display: flex; flex-direction: row; align-items: center; justify-content: space-between; - background-color: #79c1f4; .iconVersionContainer { - width: 100px; + width: 90px; height: 100%; - background-color: #063a5a; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; } } .cardContent { - width: 100%; + width: 90%; + height: 70px; - background-color: #e38787; } .cardFooter { - width: 100%; + width: 90%; height: 30px; - background-color: #f8ff6d; } @@ -47,3 +51,22 @@ font-size: 22px; font-weight: bold; } + +.linkSettingContainer { + width: 80px; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .link { + width: 32px; + cursor: pointer; + text-align: center; + display: flex; + flex-direction: row; + align-self: center; + justify-content: space-between; + } +} diff --git a/web_ui/src/app/home/plugins/plugins.module.css b/web_ui/src/app/home/plugins/plugins.module.css new file mode 100644 index 00000000..438ce546 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugins.module.css @@ -0,0 +1,25 @@ +.pluginListContainer { + align-self: flex-start; + justify-self: flex-start; + width: calc(100% - 60px); + margin: auto; + display: grid; + grid-template-rows: repeat(auto-fill, minmax(160px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); + gap: 15px; + justify-items: center; + align-items: center; +} + +.modalTitle { + display: flex; + flex-direction: row; + align-items: center; +} + +.modalBody { + height: 80px; + display: flex; + flex-direction: column; + justify-content: space-around; +} \ No newline at end of file From 9c6f2ce0888982a04b54271eb27b6d3fd161d8a6 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 10:55:03 +0800 Subject: [PATCH 012/121] feat(bots): crud api request --- .../home/bots/components/bot-card/BotCard.tsx | 4 +- .../home/bots/components/bot-form/BotForm.tsx | 50 +++++++++++++++---- web_ui/src/app/home/bots/page.tsx | 11 ++-- web_ui/src/app/infra/api/api-types/index.ts | 12 ++--- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx b/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx index 29dd54f0..92d2449a 100644 --- a/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx +++ b/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx @@ -31,9 +31,9 @@ export default function BotCard({
描述:{botCardVO.description}
-
+ {/*
更新时间:{botCardVO.updateTime} -
+
*/}
) } \ No newline at end of file diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index 4eb6918e..b3fa4999 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -11,6 +11,7 @@ import {UUID} from 'uuidjs' import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import {httpClient} from "@/app/infra/http/HttpClient"; +import { Bot } from "@/app/infra/api/api-types"; export default function BotForm({ initBotId, @@ -69,6 +70,7 @@ export default function BotForm({ if (initBotId) { getBotFieldById(initBotId).then(val => { form.setFieldsValue(val) + // TODO 这里有个bug,adapter config 并没有被设置到表单中,表单一直都只显示默认值 handleAdapterSelect(val.adapter) }) } else { @@ -86,22 +88,20 @@ export default function BotForm({ } async function getBotFieldById(botId: string): Promise { - return new BotFormEntity({ - adapter: "telegram", - description: "模拟拉取bot", - name: "模拟电报bot", - adapter_config: { - token: "aaabbbccc" - }, + const bot = (await httpClient.getBot(botId)).bot + let botFormEntity = new BotFormEntity({ + adapter: bot.adapter, + description: bot.description, + name: bot.name, + adapter_config: bot.adapter_config }) + return botFormEntity } function handleAdapterSelect(adapterName: string) { if (adapterName) { - console.log(adapterNameToDynamicConfigMap) const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName) if (dynamicFormConfigList) { - console.log(dynamicFormConfigList) setDynamicFormConfigList(dynamicFormConfigList) } setShowDynamicForm(true) @@ -123,19 +123,47 @@ export default function BotForm({ if (initBotId) { // 编辑提交 console.log('submit edit', form.getFieldsValue() ,value) + let updateBot: Bot = { + uuid: initBotId, + name: form.getFieldsValue().name, + description: form.getFieldsValue().description, + adapter: form.getFieldsValue().adapter, + adapter_config: value + } + httpClient.updateBot(initBotId, updateBot).then(res => { + // TODO success toast + console.log("update bot success", res) + }).catch(err => { + // TODO error toast + console.log("update bot error", err) + }) } else { // 创建提交 console.log('submit create', form.getFieldsValue() ,value) + let newBot: Bot = { + name: form.getFieldsValue().name, + description: form.getFieldsValue().description, + adapter: form.getFieldsValue().adapter, + adapter_config: value + } + httpClient.createBot(newBot).then(res => { + // TODO success toast + console.log(res) + }).catch(err => { + // TODO error toast + console.log(err) + }) } onFormSubmit(form.getFieldsValue()) setShowDynamicForm(false) form.resetFields() dynamicForm.resetFields() - + // TODO 刷新bot列表 + // TODO 关闭当前弹窗 } function handleSaveButton() { - + form.submit() } return ( diff --git a/web_ui/src/app/home/bots/page.tsx b/web_ui/src/app/home/bots/page.tsx index 4e7a8c5c..1cb64f0f 100644 --- a/web_ui/src/app/home/bots/page.tsx +++ b/web_ui/src/app/home/bots/page.tsx @@ -10,7 +10,7 @@ import BotForm from "@/app/home/bots/components/bot-form/BotForm"; import BotCard from "@/app/home/bots/components/bot-card/BotCard"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent" import {httpClient} from "@/app/infra/http/HttpClient"; - +import { Bot } from "@/app/infra/api/api-types"; export default function BotConfigPage() { const router = useRouter(); @@ -50,15 +50,14 @@ export default function BotConfigPage() { return new Promise((resolve) => { httpClient.getBots().then((resp) => { - console.log("get bot list (getBotList)", resp) - const botList: BotCardVO[] = resp.bots.map((bot: any) => { + const botList: BotCardVO[] = resp.bots.map((bot: Bot) => { return new BotCardVO({ adapter: bot.adapter, description: bot.description, - id: bot.id, + id: bot.uuid || "", name: bot.name, - updateTime: bot.update_time, - pipelineName: bot.pipeline_name, + updateTime: bot.updated_at || "", + pipelineName: bot.use_pipeline_name || "", }) }) resolve(botList) diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index 35d91057..a68c022f 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -94,16 +94,16 @@ export interface ApiRespPlatformBot { } export interface Bot { - uuid: string; + uuid?: string; name: string; description: string; - enable: boolean; + enable?: boolean; adapter: string; adapter_config: object; - use_pipeline_name: string; - use_pipeline_uuid: string; - created_at: string; - updated_at: string; + use_pipeline_name?: string; + use_pipeline_uuid?: string; + created_at?: string; + updated_at?: string; } // plugins From 5672bdb4065a9f6abf3a93a61c19993b58952f11 Mon Sep 17 00:00:00 2001 From: BaiCai <101706274+baicai99@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:21:45 +0800 Subject: [PATCH 013/121] fix: bugs in bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复bug:UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 1487: illegal multibyte sequence 方法:指定编码。pipeline_config = json.load(open('templates/default-pipeline-config.json', encoding='utf-8')) * Create 1 * Delete plugins /1 * 修复:FileNotFoundError: [WinError 3] 系统找不到指定的路径。: 'plugins' * 优化插件依赖检查逻辑,移除创建plugins目录的代码 --- pkg/core/bootutils/deps.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/core/bootutils/deps.py b/pkg/core/bootutils/deps.py index 4443079c..8d8e7e00 100644 --- a/pkg/core/bootutils/deps.py +++ b/pkg/core/bootutils/deps.py @@ -61,9 +61,11 @@ async def install_deps(deps: list[str]): async def precheck_plugin_deps(): print('[Startup] Prechecking plugin dependencies...') - for dir in os.listdir("plugins"): - subdir = os.path.join("plugins", dir) - if not os.path.isdir(subdir): - continue - if 'requirements.txt' in os.listdir(subdir): - pkgmgr.install_requirements(os.path.join(subdir, 'requirements.txt'), extra_params=['-q', '-q', '-q']) + # 只有在plugins目录存在时才执行插件依赖安装 + if os.path.exists("plugins"): + for dir in os.listdir("plugins"): + subdir = os.path.join("plugins", dir) + if not os.path.isdir(subdir): + continue + if 'requirements.txt' in os.listdir(subdir): + pkgmgr.install_requirements(os.path.join(subdir, 'requirements.txt'), extra_params=['-q', '-q', '-q']) From 43d73bc49344cd4e754580f7e1c876b0331cdd53 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 13:58:06 +0800 Subject: [PATCH 014/121] feat: load requesters & llm models from api --- .../models/component/llm-card/LLMCard.tsx | 3 - .../models/component/llm-card/LLMCardVO.ts | 3 - .../component/llm-form/ChooseAdapterEntity.ts | 4 + .../models/component/llm-form/LLMForm.tsx | 24 ++++-- web_ui/src/app/home/models/page.tsx | 75 ++++++++----------- 5 files changed, 54 insertions(+), 55 deletions(-) create mode 100644 web_ui/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts diff --git a/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx b/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx index 9a11e2b3..3cad394d 100644 --- a/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx +++ b/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx @@ -31,9 +31,6 @@ export default function LLMCard({
URL:{cardVO.URL}
-
- 更新时间:{cardVO.updateTime} -
); } \ No newline at end of file diff --git a/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts b/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts index 0a9ba785..6b0b07d3 100644 --- a/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts +++ b/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts @@ -4,7 +4,6 @@ export interface ILLMCardVO { model: string; company: string; URL: string; - updateTime: string; } export class LLMCardVO implements ILLMCardVO { @@ -13,7 +12,6 @@ export class LLMCardVO implements ILLMCardVO { model: string; company: string; URL: string; - updateTime: string; constructor(props: ILLMCardVO) { this.id = props.id; @@ -21,7 +19,6 @@ export class LLMCardVO implements ILLMCardVO { this.model = props.model; this.company = props.company; this.URL = props.URL; - this.updateTime = props.updateTime; } } \ No newline at end of file diff --git a/web_ui/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts b/web_ui/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts new file mode 100644 index 00000000..dd97d284 --- /dev/null +++ b/web_ui/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts @@ -0,0 +1,4 @@ +export interface IChooseRequesterEntity { + label: string + value: string +} \ No newline at end of file diff --git a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx index e0ca4b84..c580031e 100644 --- a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx +++ b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx @@ -2,6 +2,8 @@ import styles from "@/app/home/models/LLMConfig.module.css"; import {Button, Form, Input, Select, SelectProps, Space} from "antd"; import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import {useEffect, useState} from "react"; +import {IChooseRequesterEntity} from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; +import { httpClient } from "@/app/infra/http/HttpClient"; export default function LLMForm({ editMode, @@ -27,7 +29,10 @@ export default function LLMForm({ value: 'vision', }, ]; + const [requesterNameList, setRequesterNameList] = useState([]) + useEffect(() => { + initLLMModelFormComponent() if (editMode && initLLMId) { getLLMConfig(initLLMId).then(val => { form.setFieldsValue(val) @@ -35,7 +40,18 @@ export default function LLMForm({ } else { form.resetFields() } - }) + }, []) + + async function initLLMModelFormComponent() { + const requesterNameList = await httpClient.getProviderRequesters() + setRequesterNameList(requesterNameList.requesters.map(item => { + return { + label: item.label.zh_CN, + value: item.name + } + })) + // TODO 拉取初始化表单信息 + } async function getLLMConfig(id: string): Promise { return { @@ -102,11 +118,7 @@ export default function LLMForm({ style={{width: 120}} onChange={() => { }} - options={[ - {value: 'OpenAI', label: 'OpenAI'}, - {value: 'OLAMA', label: 'OLAMA'}, - {value: 'DeepSeek', label: 'DeepSeek'}, - ]} + options={requesterNameList} /> diff --git a/web_ui/src/app/home/models/page.tsx b/web_ui/src/app/home/models/page.tsx index a0ed8fd0..24536fd3 100644 --- a/web_ui/src/app/home/models/page.tsx +++ b/web_ui/src/app/home/models/page.tsx @@ -1,6 +1,6 @@ "use client" -import {useState} from "react"; +import {useState, useEffect} from "react"; import {LLMCardVO} from "@/app/home/models/component/llm-card/LLMCardVO"; import styles from "./LLMConfig.module.css" import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent"; @@ -8,54 +8,43 @@ import {Modal} from "antd"; import LLMCard from "@/app/home/models/component/llm-card/LLMCard"; import LLMForm from "@/app/home/models/component/llm-form/LLMForm"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; +import { httpClient } from "@/app/infra/http/HttpClient"; +import { LLMModel } from "@/app/infra/api/api-types"; export default function LLMConfigPage() { - const [cardList, setCardList] = useState([ - new LLMCardVO({ - id: "1", - name: "测试模型", - model: "GPT-4o", - URL: "www.openai.com", - company: "OpenAI", - updateTime: "2025.1.2" - }), - new LLMCardVO({ - id: "2", - name: "测试模型", - model: "GPT-4o", - URL: "www.openai.com", - company: "OpenAI", - updateTime: "2025.1.2" - }), - new LLMCardVO({ - id: "3", - name: "测试模型", - model: "GPT-4o", - URL: "www.openai.com", - company: "OpenAI", - updateTime: "2025.1.2" - }), - new LLMCardVO({ - id: "4", - name: "测试模型", - model: "GPT-4o", - URL: "www.openai.com", - company: "OpenAI", - updateTime: "2025.1.2" - }), - new LLMCardVO({ - id: "5", - name: "测试模型", - model: "GPT-4o", - URL: "www.openai.com", - company: "OpenAI", - updateTime: "2025.1.2" - }), - ]) + const [cardList, setCardList] = useState([]) const [modalOpen, setModalOpen] = useState(false); const [isEditForm, setIsEditForm] = useState(false) const [nowSelectedLLM, setNowSelectedLLM] = useState(null) + useEffect(() => { + getLLMModelList().then((llmModelList) => { + setCardList(llmModelList) + }) + }, []) + + function getLLMModelList(): Promise { + return new Promise((resolve) => { + httpClient.getProviderLLMModels().then((resp) => { + const llmModelList: LLMCardVO[] = resp.models.map((model: LLMModel) => { + console.log("model", model) + return new LLMCardVO({ + id: model.uuid, + name: model.name, + model: model.name, + company: model.requester, + URL: model.requester_config.base_url, + }) + }) + resolve(llmModelList) + }).catch((err) => { + // TODO error toast + console.error("get LLM model list error", err) + resolve([]) + }) + }) + } + function selectLLM(cardVO: LLMCardVO) { setIsEditForm(true) setNowSelectedLLM(cardVO) From b57186e894178e522dccbd424ff9ef8f08a4f5a2 Mon Sep 17 00:00:00 2001 From: HYana Date: Sun, 27 Apr 2025 15:35:26 +0800 Subject: [PATCH 015/121] feat: finish plugin market --- web_ui/package-lock.json | 13 +++ web_ui/package.json | 2 + .../plugins/plugin-installed/PluginCardVO.ts | 2 +- .../plugin-card/PluginCardComponent.tsx | 2 +- .../plugin-market/PluginMarketComponent.tsx | 81 ++++++++++++++++++- .../PluginMarketCardComponent.tsx | 57 ++++++++++++- .../plugin-market-card/PluginMarketCardVO.ts | 31 +++++++ .../pluginMarketCard.module.css | 77 ++++++++++++++++++ 8 files changed, 256 insertions(+), 9 deletions(-) create mode 100644 web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts create mode 100644 web_ui/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css diff --git a/web_ui/package-lock.json b/web_ui/package-lock.json index 269c3839..8279ebd0 100644 --- a/web_ui/package-lock.json +++ b/web_ui/package-lock.json @@ -11,6 +11,7 @@ "@ant-design/v5-patch-for-react-19": "^1.0.3", "antd": "^5.24.6", "axios": "^1.8.4", + "lodash": "^4.17.21", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -18,6 +19,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/lodash": "^4.17.16", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1094,6 +1096,12 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "dev": true + }, "node_modules/@types/node": { "version": "20.17.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.27.tgz", @@ -3837,6 +3845,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/web_ui/package.json b/web_ui/package.json index 9e7c20f7..e01e5464 100644 --- a/web_ui/package.json +++ b/web_ui/package.json @@ -12,6 +12,7 @@ "@ant-design/v5-patch-for-react-19": "^1.0.3", "antd": "^5.24.6", "axios": "^1.8.4", + "lodash": "^4.17.21", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -19,6 +20,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@types/lodash": "^4.17.16", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts b/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts index e83bd75c..768624d7 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts @@ -6,7 +6,7 @@ export interface IPluginCardVO { handlerCount: number, } -export class PluginCardVO implements IPluginCardVO{ +export class PluginCardVO implements IPluginCardVO { description: string; handlerCount: number; name: string; diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index be137ded..1c4511c7 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -17,7 +17,7 @@ export default function PluginCardComponent({ {/* right icon & version */}
v{cardVO.version} diff --git a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index c6b7b540..90487cd5 100644 --- a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -1,9 +1,84 @@ "use client" -export default function PluginMarketComponent () { +import {useCallback, useEffect, useState} from "react"; +import styles from "@/app/home/plugins/plugins.module.css"; +import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; +import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; +import {Input} from "antd"; +import {debounce} from "lodash" + +export default function PluginInstalledComponent () { + const [marketPluginList, setMarketPluginList] = useState([]) + const [searchKeyword, setSearchKeyword] = useState("") + + useEffect(() => { + initData() + }, []) + + function initData() { + getPluginList().then((value) => { + setMarketPluginList(value) + }) + } + + function onInputSearchKeyword(keyword: string) { + setSearchKeyword(keyword) + debounceSearch(keyword) + } + + const debounceSearch = useCallback( + debounce((keyword: string) => { + console.log("debounce search", keyword) + searchPlugin(keyword).then(marketPluginList => { + setMarketPluginList(marketPluginList) + }) + }, 500), [] + ) + + async function searchPlugin(keyword: string): Promise { + // TODO 实现搜索 + const demoResult: PluginMarketCardVO[] = [] + for (let i = 0; i < keyword.length; i ++) { + demoResult.push(new PluginMarketCardVO({ + author: "/hanahana", + description: "一个搜索测试的描述", + githubURL: "?", + name: "搜索插件" + i, + pluginId: `${i}`, + starCount: 19 + i, + version: `0.${i}`, + })) + } + return demoResult + } + + async function getPluginList(): Promise { + return [ + new PluginMarketCardVO({ + pluginId: "aaa", + description: "一般的描述", + name: "插件AAA", + author: "/hana", + version: "0.1", + githubURL: "", + starCount: 23 + }), + ] + } + return ( -
- plugin-market +
+ onInputSearchKeyword(e.target.value)} + /> + { + marketPluginList.map((vo, index) => { + return
+ +
+ }) + }
) } diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx index b2a94b74..5d96c57f 100644 --- a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx @@ -1,7 +1,56 @@ -export function PluginMarketCardComponent() { +import styles from "./pluginMarketCard.module.css" +import {GithubOutlined, StarOutlined} from '@ant-design/icons'; +import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; +import {Button} from "antd"; + +export default function PluginMarketCardComponent({ + cardVO +}: { + cardVO: PluginMarketCardVO +}) { + + + function handleInstallClick (pluginId: string) { + console.log("Install plugin: ", pluginId) + } + return ( -
- plugin market card +
+ {/* header */} +
+ {/* left author */} +
{cardVO.author}
+ {/* right icon */} + +
+ {/* content */} +
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */} +
+
+
+ + {cardVO.starCount} +
+
+ +
- ) + ); } diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts new file mode 100644 index 00000000..4a806ec4 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts @@ -0,0 +1,31 @@ +export interface IPluginMarketCardVO { + pluginId: string; + author: string, + version: string, + name: string, + description: string, + starCount: number, + githubURL: string, +} + +export class PluginMarketCardVO implements IPluginMarketCardVO { + pluginId: string; + description: string; + name: string; + author: string; + version: string; + githubURL: string; + starCount: number; + + constructor(prop: IPluginMarketCardVO) { + this.description = prop.description + this.name = prop.name + this.author = prop.author + this.version = prop.version + this.githubURL = prop.githubURL + this.starCount = prop.starCount + this.pluginId = prop.pluginId + } + + +} diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css new file mode 100644 index 00000000..f611f335 --- /dev/null +++ b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css @@ -0,0 +1,77 @@ +.cardContainer { + width: 360px; + height: 140px; + box-sizing: border-box; + background-color: #FFF; + border-radius: 9px; + padding-top: 10px; + padding-bottom: 10px; + box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; +} + +.cardHeader { + width: 90%; + height: 30px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .iconVersionContainer { + width: 90px; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.cardContent { + width: 90%; + + height: 70px; +} + +.cardFooter { + width: 90%; + height: 30px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + + +.fontGray { + color: #6C6C6C; +} + +.boldFont { + font-size: 22px; + font-weight: bold; +} + +.linkSettingContainer { + width: 80px; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .link { + width: 32px; + cursor: pointer; + text-align: center; + display: flex; + flex-direction: row; + color: #6062E7; + align-self: center; + justify-content: space-between; + } +} From 3003f39e34be39498f0582afaf654c018c4da24e Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 16:48:27 +0800 Subject: [PATCH 016/121] perf: reorder sidebar --- .../home/components/home-sidebar/sidbarConfigList.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts b/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts index ff78a281..4c83ea79 100644 --- a/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts +++ b/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts @@ -13,16 +13,16 @@ export const sidebarConfigList = [ icon: "", route: "/home/bots", }), + new SidebarChildVO({ + id: "pipelines", + name: "流水线", + icon: "", + route: "/home/pipelines", + }), new SidebarChildVO({ id: "plugins", name: "插件管理", icon: "", route: "/home/plugins", }), - new SidebarChildVO({ - id: "pipelines", - name: "流水线", - icon: "", - route: "/home/pipelines", - }) ] From b85f798364b12683c320c0450ee9e7aa3525e0b4 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 17:04:56 +0800 Subject: [PATCH 017/121] perf: llm model definition --- .../home/bots/components/bot-form/BotForm.tsx | 1 - .../models/component/llm-card/LLMCard.tsx | 3 --- .../models/component/llm-card/LLMCardVO.ts | 3 --- .../models/component/llm-form/LLMForm.tsx | 20 +++++++++++++------ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index b3fa4999..62f83957 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -9,7 +9,6 @@ import { } from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; import {UUID} from 'uuidjs' import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; -import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import {httpClient} from "@/app/infra/http/HttpClient"; import { Bot } from "@/app/infra/api/api-types"; diff --git a/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx b/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx index 3cad394d..a7672445 100644 --- a/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx +++ b/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx @@ -19,9 +19,6 @@ export default function LLMCard({
{cardVO.name}
-
- 使用模型:{cardVO.model} -
厂商:{cardVO.company}
diff --git a/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts b/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts index 6b0b07d3..6d62cb3c 100644 --- a/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts +++ b/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts @@ -1,7 +1,6 @@ export interface ILLMCardVO { id: string; name: string; - model: string; company: string; URL: string; } @@ -9,14 +8,12 @@ export interface ILLMCardVO { export class LLMCardVO implements ILLMCardVO { id: string; name: string; - model: string; company: string; URL: string; constructor(props: ILLMCardVO) { this.id = props.id; this.name = props.name; - this.model = props.model; this.company = props.company; this.URL = props.URL; } diff --git a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx index c580031e..f52756ce 100644 --- a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx +++ b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx @@ -54,13 +54,21 @@ export default function LLMForm({ } async function getLLMConfig(id: string): Promise { + + const llmModel = await httpClient.getProviderLLMModel(id) + + let fakeExtraArgs = [] + const extraArgs = llmModel.model.extra_args as Record + for (const key in extraArgs) { + fakeExtraArgs.push(`${key}:${extraArgs[key]}`) + } return { - name: id, - model_provider: "OpenAI", - url: "www.aaa.com", - api_key: "", - abilities: [], - extra_args: [], + name: llmModel.model.name, + model_provider: llmModel.model.requester, + url: llmModel.model.requester_config?.base_url, + api_key: llmModel.model.api_keys[0], + abilities: llmModel.model.abilities, + extra_args: fakeExtraArgs, } } From 1175cf9bbf2fa6c5a07790d0ea0ef408490fb05b Mon Sep 17 00:00:00 2001 From: HYana Date: Sun, 27 Apr 2025 19:26:54 +0800 Subject: [PATCH 018/121] feat: improve plugin market style, finish pagination --- web_ui/src/app/home/plugins/page.tsx | 4 +- .../plugin-market/PluginMarketComponent.tsx | 83 +++++++++++++------ .../src/app/home/plugins/plugins.module.css | 15 +++- 3 files changed, 73 insertions(+), 29 deletions(-) diff --git a/web_ui/src/app/home/plugins/page.tsx b/web_ui/src/app/home/plugins/page.tsx index c7448c75..e28db69d 100644 --- a/web_ui/src/app/home/plugins/page.tsx +++ b/web_ui/src/app/home/plugins/page.tsx @@ -14,7 +14,7 @@ export default function PluginConfigPage() { const [nowPageType, setNowPageType] = useState(PageType.INSTALLED) return ( -
+
-
+
{ nowPageType === PageType.INSTALLED && } diff --git a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index 90487cd5..64f73f7e 100644 --- a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -4,10 +4,10 @@ import {useCallback, useEffect, useState} from "react"; import styles from "@/app/home/plugins/plugins.module.css"; import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; -import {Input} from "antd"; +import {Input, Pagination} from "antd"; import {debounce} from "lodash" -export default function PluginInstalledComponent () { +export default function PluginMarketComponent () { const [marketPluginList, setMarketPluginList] = useState([]) const [searchKeyword, setSearchKeyword] = useState("") @@ -16,9 +16,7 @@ export default function PluginInstalledComponent () { }, []) function initData() { - getPluginList().then((value) => { - setMarketPluginList(value) - }) + getPluginList() } function onInputSearchKeyword(keyword: string) { @@ -35,7 +33,7 @@ export default function PluginInstalledComponent () { }, 500), [] ) - async function searchPlugin(keyword: string): Promise { + async function searchPlugin(keyword: string, pageNumber: number = 1): Promise { // TODO 实现搜索 const demoResult: PluginMarketCardVO[] = [] for (let i = 0; i < keyword.length; i ++) { @@ -52,33 +50,66 @@ export default function PluginInstalledComponent () { return demoResult } - async function getPluginList(): Promise { - return [ - new PluginMarketCardVO({ - pluginId: "aaa", - description: "一般的描述", - name: "插件AAA", - author: "/hana", - version: "0.1", - githubURL: "", - starCount: 23 - }), - ] + function getPluginList(pageNumber: number = 1) { + new Promise((resolve, reject) => { + const result = [ + new PluginMarketCardVO({ + pluginId: "aaa", + description: "一般的描述", + name: "插件AAA", + author: "/hana", + version: "0.1", + githubURL: "", + starCount: 23 + }), + ] + for (let i = 0; i < pageNumber; i ++) { + result.push( + new PluginMarketCardVO({ + pluginId: "aaa", + description: "一般的描述", + name: "插件AAA", + author: "/hana", + version: "0.1", + githubURL: "", + starCount: 23 + }) + ) + } + resolve(result) + }).then((value) => { + setMarketPluginList(value) + }) } return ( -
+
onInputSearchKeyword(e.target.value)} /> - { - marketPluginList.map((vo, index) => { - return
- -
- }) - } +
+ { + marketPluginList.map((vo, index) => { + return
+ +
+ }) + } +
+ { + getPluginList(pageNumber) + }} + />
+ ) } diff --git a/web_ui/src/app/home/plugins/plugins.module.css b/web_ui/src/app/home/plugins/plugins.module.css index 438ce546..1393c15b 100644 --- a/web_ui/src/app/home/plugins/plugins.module.css +++ b/web_ui/src/app/home/plugins/plugins.module.css @@ -1,7 +1,19 @@ +.pageContainer { + width: 100%; + height: calc(100% - 30px); +} + +.marketComponentBody { + width: 100%; + height: calc(100% - 60px); +} + .pluginListContainer { align-self: flex-start; justify-self: flex-start; width: calc(100% - 60px); + height: 100%; + max-height: 100%; margin: auto; display: grid; grid-template-rows: repeat(auto-fill, minmax(160px, 1fr)); @@ -9,6 +21,7 @@ gap: 15px; justify-items: center; align-items: center; + overflow-y: scroll; } .modalTitle { @@ -22,4 +35,4 @@ display: flex; flex-direction: column; justify-content: space-around; -} \ No newline at end of file +} From 90a3f17a8f98730e1578eb8e42fa1b4f7fa2bc02 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 20:16:35 +0800 Subject: [PATCH 019/121] perf: sidebar style --- .../components/home-sidebar/HomeSidebar.module.css | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css b/web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css index 9c1f942e..65fadc80 100644 --- a/web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css +++ b/web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css @@ -1,6 +1,6 @@ .sidebarContainer { box-sizing: border-box; - width: 240px; + width: 200px; height: 100vh; background-color: #FFF; display: flex; @@ -9,7 +9,7 @@ } .langbotIconContainer { - width: 240px; + width: 200px; height: 70px; display: flex; flex-direction: row; @@ -20,7 +20,7 @@ width: 54px; height: 54px; border-radius: 12px; - background: #6062E7; + background: #2288ee; color: #fbfbfb; font-weight: 600; font-size: 36px; @@ -36,7 +36,7 @@ .sidebarChildContainer { box-sizing: border-box; - width: 198px; + width: 160px; height: 48px; margin: 12px 0; font-size: 16px; @@ -49,8 +49,9 @@ } .sidebarSelected { - background-color: #6062E7; + background-color: #2288ee; color: white; + box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); } .sidebarUnselected { From a623f79d977371e0a704be2bf1a1183ac1c1b84f Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 21:32:25 +0800 Subject: [PATCH 020/121] typo: delete model field --- web_ui/src/app/home/models/page.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web_ui/src/app/home/models/page.tsx b/web_ui/src/app/home/models/page.tsx index 24536fd3..7321637e 100644 --- a/web_ui/src/app/home/models/page.tsx +++ b/web_ui/src/app/home/models/page.tsx @@ -31,9 +31,8 @@ export default function LLMConfigPage() { return new LLMCardVO({ id: model.uuid, name: model.name, - model: model.name, company: model.requester, - URL: model.requester_config.base_url, + URL: model.requester_config?.base_url, }) }) resolve(llmModelList) From 2b09591524b2fcef00ca1b58a9d5b91731081b41 Mon Sep 17 00:00:00 2001 From: BaiCai <101706274+baicai99@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:02:53 +0800 Subject: [PATCH 021/121] Update page.tsx --- web_ui/src/app/login/page.tsx | 109 +++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/web_ui/src/app/login/page.tsx b/web_ui/src/app/login/page.tsx index ec5499d9..cf225989 100644 --- a/web_ui/src/app/login/page.tsx +++ b/web_ui/src/app/login/page.tsx @@ -1,7 +1,112 @@ +'use client'; +import { Button, Input, Form, Checkbox, Divider } from 'antd'; +import { GoogleOutlined, AppleOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import styles from './login.module.css'; +import { useState } from 'react'; + export default function Home() { + const [form] = Form.useForm(); + const [rememberMe, setRememberMe] = useState(false); + return ( -
-

loginpage

+ // 使用 Ant Design 的组件库,使用 antd 的样式 + // 仅前端样式,无交互功能。 + +
+ {/* login 类是整个 container,使用 flex 左右布局 */} +
+ {/* left 为注册的表单,需要填入的内容有:邮箱,密码 */} +
+
+

欢迎回来

+
+ + } + /> + + + + } + /> + + +
+ setRememberMe(e.target.checked)} + > + 30天内自动登录 + + 忘记密码? +
+ + + + + +
+ +
+
+
+ +
+
+
+
+ {/* right 为左侧布局,显示的是应用截图,测试阶段使用 picsum.photos 代替 */} +
+ 应用预览 + {/* 在右上角添加logo */} +
+ Logo +
+
+
); } From 18152fe04b6d513d10ef8fe09d3d60955df2b956 Mon Sep 17 00:00:00 2001 From: BaiCai <101706274+baicai99@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:03:23 +0800 Subject: [PATCH 022/121] Create login.module.css --- web_ui/src/app/login/login.module.css | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 web_ui/src/app/login/login.module.css diff --git a/web_ui/src/app/login/login.module.css b/web_ui/src/app/login/login.module.css new file mode 100644 index 00000000..e8d2acbf --- /dev/null +++ b/web_ui/src/app/login/login.module.css @@ -0,0 +1,102 @@ +.container { + width: 100%; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: #f5f5f5; +} + +.login { + display: flex; + width: 80%; + height: 80vh; + background-color: white; + border-radius: 16px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); +} + +.left { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 2rem; +} + +.right { + flex: 1.5; + background-color: #f9f9f9; + position: relative; + overflow: hidden; + border-top-right-radius: 16px; + border-bottom-right-radius: 16px; +} + +.loginForm { + width: 100%; + max-width: 360px; +} + +.title { + font-size: 2rem; + font-weight: 600; + margin-bottom: 2rem; + text-align: center; +} + +.loginButton { + width: 100%; + height: 40px; + margin-top: 1rem; +} + +.divider { + margin: 1.5rem 0; + text-align: center; +} + +.socialLogin { + display: flex; + justify-content: space-between; + gap: 1rem; +} + +.socialButton { + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.rememberMe { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +/* 修改Logo样式,调整位置确保正确对齐 */ +.logoContainer { + position: absolute; + top: 20px; + right: 20px; + z-index: 10; + display: flex; + justify-content: center; + align-items: center; + width: 70px; + height: 70px; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 50%; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.logo { + width: 60px; + height: 60px; + border-radius: 50%; + object-fit: cover; +} From ef207f9435e951d9af436020131e54ae7f13de5e Mon Sep 17 00:00:00 2001 From: BaiCai <101706274+baicai99@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:03:43 +0800 Subject: [PATCH 023/121] Update layout.tsx --- web_ui/src/app/login/layout.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/web_ui/src/app/login/layout.tsx b/web_ui/src/app/login/layout.tsx index e868ff78..34b431cc 100644 --- a/web_ui/src/app/login/layout.tsx +++ b/web_ui/src/app/login/layout.tsx @@ -1,4 +1,7 @@ +'use client'; + import React from "react"; +import { ConfigProvider, theme } from 'antd'; export default function LoginLayout({ children @@ -6,8 +9,18 @@ export default function LoginLayout({ children: React.ReactNode; }>) { return ( -
-
{children}
-
+ +
+
{children}
+
+
) } From 7d904afd3927b4a658637626c4a46a473188d454 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 22:23:24 +0800 Subject: [PATCH 024/121] perf(webui): btn color in empty component --- .../empty-and-create-component/emptyAndCreate.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css b/web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css index ea393c3f..ba3d041f 100644 --- a/web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css +++ b/web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css @@ -23,7 +23,7 @@ width: 200px; height: 50px; border-radius: 20px; - background-color: #6062E7; + background-color: #2288ee; color: #FFF; font-size: 20px; font-weight: bold; @@ -33,7 +33,7 @@ } .emptyCreateButton:hover { - background-color: #4b4de3; + background-color: #1b77d2; } .emptyInfoContainer { From deb9e24c4252c91a08992e1a648f7cdeb5d02403 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 22:45:16 +0800 Subject: [PATCH 025/121] doc(README): remove core team list from readme --- README.md | 7 ------- README_EN.md | 7 ------- README_JP.md | 7 ------- 3 files changed, 21 deletions(-) diff --git a/README.md b/README.md index 0b7df23d..d0e9696c 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,3 @@ - -以及 LangBot 核心团队成员: - -- [RockChinQ](https://github.com/RockChinQ) -- [the-lazy-me](https://github.com/the-lazy-me) -- [wangcham](https://github.com/wangcham) -- [KaedeSAMA](https://github.com/KaedeSAMA) \ No newline at end of file diff --git a/README_EN.md b/README_EN.md index 47df5ec1..a5755f83 100644 --- a/README_EN.md +++ b/README_EN.md @@ -122,10 +122,3 @@ Thank you for the following [code contributors](https://github.com/RockChinQ/Lan - -And the core team members of LangBot: - -- [RockChinQ](https://github.com/RockChinQ) -- [the-lazy-me](https://github.com/the-lazy-me) -- [wangcham](https://github.com/wangcham) -- [KaedeSAMA](https://github.com/KaedeSAMA) diff --git a/README_JP.md b/README_JP.md index db4b0cf7..6ddb6655 100644 --- a/README_JP.md +++ b/README_JP.md @@ -121,10 +121,3 @@ LangBot への貢献に対して、以下の [コード貢献者](https://github - -LangBot の核心チームメンバー: - -- [RockChinQ](https://github.com/RockChinQ) -- [the-lazy-me](https://github.com/the-lazy-me) -- [wangcham](https://github.com/wangcham) -- [KaedeSAMA](https://github.com/KaedeSAMA) From af8f07218a06732a103eb314a6959caeda716969 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 22:49:05 +0800 Subject: [PATCH 026/121] chore: favicon.ico --- web_ui/src/app/favicon.ico | Bin 25931 -> 9662 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/web_ui/src/app/favicon.ico b/web_ui/src/app/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..00a756f5cedd93bf5228625c6ddeb185eb09e441 100644 GIT binary patch literal 9662 zcmeHNdvH|M8NUexq$trkrKnZ03e-*oERISmZ6_fF2tE*HM5!o4he5CdtzczJhk;r| z1VY{z0t66HK&HGBAPR|j$fJ!s5=e4)H`&du*}Suxglu*A#}Pwe`|O^c3x@mbgM-VSJ`3ohh7TJoUE4I7 zZ{7HNZT~2elU}5n;v8~zbdb~Gpti;;YA8A-bkU{FQSht2@wXPf?7a>A#t+`e+g_(8 za|Ll>9XiAr&K-7Yt7RG?G*2ki31KAMz&2I}igtdzZ>HH46E)U=|1H-;X5A! z!k_p8)yNk_-yC-|@zK6Ibi>)6=A?!>8W&CFr;~&qm`gk#gdJ|_*MI!rdcq8`r%TLkU)|a!ipr8+BJ=fpF%HjZ?bca7eff=_$3y(IROv*& zwU>^e&s*%zZNRY##6rWt_eh)aa;LuM=X5*Xq>BCTf*-H?wt?=tqw5gYY~(;(G937; zj}QFEKbdrgJ|NqTR&qKWA_t3ggxjycj`&n_Ig{FNG?T-2gPO|nsC@g}Tku7bA!7rz z)EdZ+cFl$>h&4;R@o_xZxqz(oW?`%Ezlu|Gyn8HVKj_!`2R6~4P9TSqsJ+!f6?-|p zVScK8;#{A(iR{SBo~1f}*G>G~9`VLnQ|Z}+8cT9WlQ_epFU*5aU!%tFHKJYB*<_MA zpQ~e`4#fWYtR&=J66*%`zbAef+%e?tA*kn7>aU!<$uq{kZz;95SiRb`n6;$!_=PTqR#CIH$&Jn4-cH7o8*jD)E;;pes;y9R2sh+4NKN4p z#4?Xq06lg4mr+}bMd9l45Bq>Wld0lVJowW=cIJ=Bj~@JiPvwLd(q+a1UlBD_mXI+! zP0VNSI0PL&e-H8HTku0m=E4(VP3sxsu?Hbj{%1nBwno@0ef~`wxHuFDe>4Z)ryH%! z)PePiDdQs_n=n6lY&qo>vDV;N?v35`K7;*$IK>>76Q%y^1qZ}Db~@Ur`kPeP2zy8`E_mSVj5{1hu_mawn&lO59Y`(~#C$_0VwT7U zU2`x{{EV|~+gz&F6be5(Y^_wCw}Z6FFLugPZ{9Fp6uZ}6IfdBh#6G8kEM^^-U}WNyh%o zAC7m1TfNrj_*FePkBWK7^H&wuJnk_k_Yim8V|e6W*Zafko1-L1IP^QO>KM#5+yD6A zCgz3>2zm79!-qm`?{YZWSBj|M&7|kKq0J@L@v+N+rN=Or9#?{zkP;FxO>x zcZP40Yk*&TPsO^Iv9tUE(iqIx`5WGq4UzhlGsjFMDuN1s zU#)s>6<`m-cXffE55TO-eKNX*;_~N5-BXNjMxJHHQzKOTT>k{J4q^-v zJ)C7*qDsJvwR2sSj{!cYH|Gkp|32U+Vd=T R2s@W^jbA>raUCv6{{yx2ON;;j literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m From 7bfe8b3f5bf0655ee7684c2959baacc9aea9fd15 Mon Sep 17 00:00:00 2001 From: hanachan Date: Sun, 27 Apr 2025 23:12:35 +0800 Subject: [PATCH 027/121] feat: finish login page --- web_ui/src/app/infra/api/api-types/index.ts | 4 +- web_ui/src/app/infra/http/HttpClient.ts | 6 +- web_ui/src/app/login/login.module.css | 14 +- web_ui/src/app/login/page.tsx | 144 +++++++++++++++----- 4 files changed, 123 insertions(+), 45 deletions(-) diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index a68c022f..69aa4515 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -46,8 +46,8 @@ export interface LLMModel { extra_args: object; api_keys: string[]; abilities: string[]; - created_at: string; - updated_at: string; + // created_at: string; + // updated_at: string; } export interface ApiRespPipelines { diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index f02bbff2..e8d51237 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -62,7 +62,7 @@ class HttpClient { // 同步获取Session private getSessionSync() { // NOT IMPLEMENT - return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicm9ja2NoaW5xQGdtYWlsLmNvbSIsImlzcyI6IkxhbmdCb3QtY29tbXVuaXR5IiwiZXhwIjoyMzUwNDk1NTQ3fQ.d6r0lNGud1OecOLMM-ADDDwiABmek3hkMIFH7ZBkaX4" + return localStorage.getItem("token") } // 拦截器配置 @@ -323,7 +323,7 @@ class HttpClient { } // ============ User API ============ - public checkIfInited(): Promise { + public checkIfInited(): Promise<{initialized: boolean}> { return this.get('/api/v1/user/init') } @@ -340,4 +340,4 @@ class HttpClient { } } -export const httpClient = new HttpClient() +export const httpClient = new HttpClient("https://version-4.langbot.dev") diff --git a/web_ui/src/app/login/login.module.css b/web_ui/src/app/login/login.module.css index e8d2acbf..b6435980 100644 --- a/web_ui/src/app/login/login.module.css +++ b/web_ui/src/app/login/login.module.css @@ -9,7 +9,7 @@ .login { display: flex; - width: 80%; + width: 30%; height: 80vh; background-color: white; border-radius: 16px; @@ -26,14 +26,6 @@ padding: 2rem; } -.right { - flex: 1.5; - background-color: #f9f9f9; - position: relative; - overflow: hidden; - border-top-right-radius: 16px; - border-bottom-right-radius: 16px; -} .loginForm { width: 100%; @@ -76,6 +68,10 @@ justify-content: space-between; align-items: center; margin-bottom: 1rem; + + .forgetPassword { + margin-right: 1rem; + } } /* 修改Logo样式,调整位置确保正确对齐 */ diff --git a/web_ui/src/app/login/page.tsx b/web_ui/src/app/login/page.tsx index cf225989..c40274ee 100644 --- a/web_ui/src/app/login/page.tsx +++ b/web_ui/src/app/login/page.tsx @@ -1,12 +1,62 @@ 'use client'; import { Button, Input, Form, Checkbox, Divider } from 'antd'; -import { GoogleOutlined, AppleOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import {GoogleOutlined, AppleOutlined, LockOutlined, UserOutlined, QqCircleFilled, QqOutlined} from '@ant-design/icons'; import styles from './login.module.css'; -import { useState } from 'react'; +import {useEffect, useState} from 'react'; + + +import {httpClient} from "@/app/infra/http/HttpClient"; +import '@ant-design/v5-patch-for-react-19'; +import {useRouter} from "next/navigation"; + export default function Home() { - const [form] = Form.useForm(); + const router = useRouter(); + const [form] = Form.useForm(); const [rememberMe, setRememberMe] = useState(false); + const [isRegisterMode, setIsRegisterMode] = useState(false); + const [isInitialized, setIsInitialized] = useState(false) + + useEffect(() => { + getIsInitialized() + }, []) + + + // 检查是否为首次启动项目,只为首次启动的用户提供注册资格 + function getIsInitialized() { + httpClient.checkIfInited().then(res => { + setIsInitialized(res.initialized) + }).catch(err => { + console.log("error at getIsInitialized: ", err) + }) + } + + function handleFormSubmit(formField: LoginField) { + if (isRegisterMode) { + handleRegister(formField.email, formField.password); + } else { + handleLogin(formField.email, formField.password) + } + } + + function handleRegister(username: string, password: string) { + httpClient.initUser(username, password).then(res => { + console.log("init user success: ", res) + }).catch(err => { + console.log("init user error: ", err) + }) + } + + function handleLogin(username: string, password: string) { + httpClient.authUser(username, password).then(res => { + localStorage.setItem("token", res.token) + console.log("login success: ", res) + router.push("/home") + }).catch(err => { + console.log("login error: ", err) + }) + } + return ( // 使用 Ant Design 的组件库,使用 antd 的样式 @@ -18,8 +68,21 @@ export default function Home() { {/* left 为注册的表单,需要填入的内容有:邮箱,密码 */}
-

欢迎回来

-
+ { + isRegisterMode && +

注册 LangBot 账号

+ } + { + !isRegisterMode && +

欢迎回到 LangBot

+ } + { + handleFormSubmit(values) + }} + > 30天内自动登录 - 忘记密码? + + 忘记密码? + { + !isRegisterMode && + { + setIsRegisterMode(true) + event.preventDefault() + }} + >去注册? + } + { + isRegisterMode && + { + setIsRegisterMode(false) + event.preventDefault() + }} + >去登录 + } +
- +
@@ -68,6 +164,7 @@ export default function Home() { className={styles.socialButton} icon={} size="large" + disabled={true} > 使用谷歌账号登录 @@ -76,37 +173,22 @@ export default function Home() {
- {/* right 为左侧布局,显示的是应用截图,测试阶段使用 picsum.photos 代替 */} -
- 应用预览 - {/* 在右上角添加logo */} -
- Logo -
-
); } + +interface LoginField { + email: string; + password: string; +} \ No newline at end of file From 3a4890778ffee6bb06f606d4606c43582fbe9b97 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 27 Apr 2025 23:24:15 +0800 Subject: [PATCH 028/121] feat: primary color of login --- web_ui/src/app/login/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/app/login/layout.tsx b/web_ui/src/app/login/layout.tsx index 34b431cc..452c4f65 100644 --- a/web_ui/src/app/login/layout.tsx +++ b/web_ui/src/app/login/layout.tsx @@ -12,7 +12,7 @@ export default function LoginLayout({ Date: Mon, 28 Apr 2025 00:08:11 +0800 Subject: [PATCH 029/121] feat: finish all llm models page --- .../models/component/llm-form/LLMForm.tsx | 117 ++++++++++++++++-- web_ui/src/app/home/models/page.tsx | 20 +-- 2 files changed, 119 insertions(+), 18 deletions(-) diff --git a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx index f52756ce..8e23d8bf 100644 --- a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx +++ b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx @@ -1,24 +1,29 @@ import styles from "@/app/home/models/LLMConfig.module.css"; -import {Button, Form, Input, Select, SelectProps, Space} from "antd"; +import {Button, Form, Input, Select, SelectProps, Space, Popconfirm, Modal} from "antd"; import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import {useEffect, useState} from "react"; import {IChooseRequesterEntity} from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; import { httpClient } from "@/app/infra/http/HttpClient"; +import {LLMModel} from "@/app/infra/api/api-types"; +import {UUID} from "uuidjs"; export default function LLMForm({ editMode, initLLMId, onFormSubmit, onFormCancel, + onLLMDeleted, }: { editMode: boolean; initLLMId?: string; onFormSubmit: (value: ICreateLLMField) => void; onFormCancel: (value: ICreateLLMField) => void; + onLLMDeleted: () => void; }) { const [form] = Form.useForm(); const extraOptions: SelectProps['options'] = [] const [initValue, setInitValue] = useState() + const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false) const abilityOptions: SelectProps['options'] = [ { label: '函数调用', @@ -50,7 +55,6 @@ export default function LLMForm({ value: item.name } })) - // TODO 拉取初始化表单信息 } async function getLLMConfig(id: string): Promise { @@ -74,27 +78,100 @@ export default function LLMForm({ function handleFormSubmit(value: ICreateLLMField) { if (editMode) { - onSaveEdit(value) + // 暂不支持更改模型 + // onSaveEdit(value) } else { onCreateLLM(value) } - onFormSubmit(value) form.resetFields() } function onSaveEdit(value: ICreateLLMField) { - console.log("edit save", value) + const requestParam: LLMModel = { + uuid: UUID.generate(), + name: value.name, + description: "", + requester: value.model_provider, + requester_config: { + "base_url": value.url, + "timeout": 120 + }, + extra_args: value.extra_args, + api_keys: [value.api_key], + abilities: value.abilities, + // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + }; + httpClient.createProviderLLMModel(requestParam).then(r => console.log(r)) } function onCreateLLM(value: ICreateLLMField) { console.log("create llm", value) + const requestParam: LLMModel = { + uuid: UUID.generate(), + name: value.name, + description: "", + requester: value.model_provider, + requester_config: { + "base_url": value.url, + "timeout": 120 + }, + extra_args: value.extra_args, + api_keys: [value.api_key], + abilities: value.abilities, + // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + }; + httpClient.createProviderLLMModel(requestParam).then(r => { + onFormSubmit(value) + }) } function handleAbilitiesChange() { } + + function deleteModel() { + if (initLLMId) { + httpClient.deleteProviderLLMModel(initLLMId).then(res => { + onLLMDeleted() + }) + } + } + return (
+ setShowDeleteConfirmModal(false)} + footer={ +
+ + +
+ } + + > + 你确定要删除这个模型吗? +
label={"模型名称"} @@ -141,6 +219,18 @@ export default function LLMForm({ > + + label={"API Key"} + name={"api_key"} + rules={[{required: true, message: "该项为必填项哦~"}]} + > + + + + label={"开启能力"} name={"abilities"} @@ -179,13 +269,22 @@ export default function LLMForm({ } { editMode && - } - diff --git a/web_ui/src/app/home/models/page.tsx b/web_ui/src/app/home/models/page.tsx index 7321637e..0f487909 100644 --- a/web_ui/src/app/home/models/page.tsx +++ b/web_ui/src/app/home/models/page.tsx @@ -18,13 +18,10 @@ export default function LLMConfigPage() { const [nowSelectedLLM, setNowSelectedLLM] = useState(null) useEffect(() => { - getLLMModelList().then((llmModelList) => { - setCardList(llmModelList) - }) + getLLMModelList() }, []) - function getLLMModelList(): Promise { - return new Promise((resolve) => { + function getLLMModelList() { httpClient.getProviderLLMModels().then((resp) => { const llmModelList: LLMCardVO[] = resp.models.map((model: LLMModel) => { console.log("model", model) @@ -35,13 +32,12 @@ export default function LLMConfigPage() { URL: model.requester_config?.base_url, }) }) - resolve(llmModelList) + console.log("get llmModelList", llmModelList) + setCardList(llmModelList) }).catch((err) => { // TODO error toast console.error("get LLM model list error", err) - resolve([]) }) - }) } function selectLLM(cardVO: LLMCardVO) { @@ -59,9 +55,10 @@ export default function LLMConfigPage() { return (
setModalOpen(false)} onCancel={() => setModalOpen(false)} width={700} @@ -72,10 +69,15 @@ export default function LLMConfigPage() { initLLMId={nowSelectedLLM?.id} onFormSubmit={() => { setModalOpen(false); + getLLMModelList() }} onFormCancel={() => { setModalOpen(false); }} + onLLMDeleted={() => { + setModalOpen(false) + getLLMModelList() + }} /> { From 1765fd5ff26671f04d6f224a881c9ba305257b84 Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 01:15:03 +0800 Subject: [PATCH 030/121] bugfix: fix bot page form bug --- .../home/bots/components/bot-form/BotForm.tsx | 8 +-- web_ui/src/app/home/bots/page.tsx | 54 +++++++++---------- .../models/component/llm-form/LLMForm.tsx | 2 +- 3 files changed, 29 insertions(+), 35 deletions(-) diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index 62f83957..fa60fde6 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -69,8 +69,8 @@ export default function BotForm({ if (initBotId) { getBotFieldById(initBotId).then(val => { form.setFieldsValue(val) - // TODO 这里有个bug,adapter config 并没有被设置到表单中,表单一直都只显示默认值 handleAdapterSelect(val.adapter) + dynamicForm.setFieldsValue(val.adapter_config) }) } else { form.resetFields() @@ -98,8 +98,10 @@ export default function BotForm({ } function handleAdapterSelect(adapterName: string) { + console.log("Select adapter: ", adapterName) if (adapterName) { const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName) + console.log(dynamicFormConfigList) if (dynamicFormConfigList) { setDynamicFormConfigList(dynamicFormConfigList) } @@ -153,12 +155,10 @@ export default function BotForm({ console.log(err) }) } - onFormSubmit(form.getFieldsValue()) setShowDynamicForm(false) + onFormSubmit(form.getFieldsValue()) form.resetFields() dynamicForm.resetFields() - // TODO 刷新bot列表 - // TODO 关闭当前弹窗 } function handleSaveButton() { diff --git a/web_ui/src/app/home/bots/page.tsx b/web_ui/src/app/home/bots/page.tsx index 1cb64f0f..65108e71 100644 --- a/web_ui/src/app/home/bots/page.tsx +++ b/web_ui/src/app/home/bots/page.tsx @@ -24,17 +24,7 @@ export default function BotConfigPage() { // TODO:补齐加载转圈逻辑 checkHasLLM().then((hasLLM) => { if (hasLLM) { - getBotList().then((botList) => { - if (botList.length === 0) { - setPageShowRule(BotConfigPageShowRule.NO_BOT) - } else { - setPageShowRule(BotConfigPageShowRule.HAVE_BOT) - } - setBotList(botList) - }).catch((err) => { - // TODO error toast - console.error("get bot list error (useEffect)", err) - }) + getBotList() } else { setPageShowRule(BotConfigPageShowRule.NO_LLM) } @@ -46,26 +36,27 @@ export default function BotConfigPage() { return true } - function getBotList(): Promise { - - return new Promise((resolve) => { - httpClient.getBots().then((resp) => { - const botList: BotCardVO[] = resp.bots.map((bot: Bot) => { - return new BotCardVO({ - adapter: bot.adapter, - description: bot.description, - id: bot.uuid || "", - name: bot.name, - updateTime: bot.updated_at || "", - pipelineName: bot.use_pipeline_name || "", - }) + function getBotList() { + httpClient.getBots().then((resp) => { + const botList: BotCardVO[] = resp.bots.map((bot: Bot) => { + return new BotCardVO({ + adapter: bot.adapter, + description: bot.description, + id: bot.uuid || "", + name: bot.name, + updateTime: bot.updated_at || "", + pipelineName: bot.use_pipeline_name || "", }) - resolve(botList) - }).catch((err) => { - // TODO error toast - console.error("get bot list error", err) - resolve([]) }) + if (botList.length === 0) { + setPageShowRule(BotConfigPageShowRule.NO_BOT) + } else { + setPageShowRule(BotConfigPageShowRule.HAVE_BOT) + } + setBotList(botList) + }).catch((err) => { + // TODO error toast + console.error("get bot list error", err) }) } @@ -100,7 +91,10 @@ export default function BotConfigPage() { > setIsEditForm(false)} + onFormSubmit={() => { + getBotList() + setModalOpen(false) + }} onFormCancel={() => setModalOpen(false)} /> diff --git a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx index 8e23d8bf..ccb00730 100644 --- a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx +++ b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx @@ -1,5 +1,5 @@ import styles from "@/app/home/models/LLMConfig.module.css"; -import {Button, Form, Input, Select, SelectProps, Space, Popconfirm, Modal} from "antd"; +import {Button, Form, Input, Select, SelectProps, Space, Modal} from "antd"; import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; import {useEffect, useState} from "react"; import {IChooseRequesterEntity} from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; From 5562148327888b00c9af2020bd2a6444de981277 Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 02:49:11 +0800 Subject: [PATCH 031/121] feat: change pipeline form --- .../home/bots/components/bot-form/BotForm.tsx | 3 +- .../pipeline-card/PipelineCardComponent.tsx | 33 ++ .../pipeline-card/PipelineCardVO.ts | 23 ++ .../pipeline-card/pipelineCard.module.css | 59 +++ .../pipeline-form/PipelineFormComponent.tsx | 379 ++++++++++++------ web_ui/src/app/home/pipelines/page.tsx | 27 +- 6 files changed, 393 insertions(+), 131 deletions(-) create mode 100644 web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx create mode 100644 web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts create mode 100644 web_ui/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index fa60fde6..4ab9d608 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -134,6 +134,7 @@ export default function BotForm({ httpClient.updateBot(initBotId, updateBot).then(res => { // TODO success toast console.log("update bot success", res) + onFormSubmit(form.getFieldsValue()) }).catch(err => { // TODO error toast console.log("update bot error", err) @@ -150,13 +151,13 @@ export default function BotForm({ httpClient.createBot(newBot).then(res => { // TODO success toast console.log(res) + onFormSubmit(form.getFieldsValue()) }).catch(err => { // TODO error toast console.log(err) }) } setShowDynamicForm(false) - onFormSubmit(form.getFieldsValue()) form.resetFields() dynamicForm.resetFields() } diff --git a/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx b/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx new file mode 100644 index 00000000..124e1d02 --- /dev/null +++ b/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx @@ -0,0 +1,33 @@ +import styles from "./pipelineCard.module.css"; +import {PipelineCardVO} from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; + +export default function PipelineCardComponent({ + cardVO +}: { + cardVO: PipelineCardVO +}) { + return ( +
+ {/* icon和基本信息 */} +
+ {/* icon */} +
+ ICO +
+ {/* 基本信息 */} +
+
+ {cardVO.name} +
+
+ 描述:{cardVO.description} +
+
+
+ {/* URL和创建时间 */} +
+ 版本:{cardVO.version} +
+
+ ); +} \ No newline at end of file diff --git a/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts b/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts new file mode 100644 index 00000000..03dbeb53 --- /dev/null +++ b/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts @@ -0,0 +1,23 @@ +export interface IPipelineCardVO { + id: string; + name: string; + description: string; + createTime: string; + version: string; +} + +export class PipelineCardVO implements IPipelineCardVO { + createTime: string; + description: string; + id: string; + name: string; + version: string; + + constructor(props: IPipelineCardVO) { + this.id = props.id; + this.name = props.name; + this.description = props.description; + this.createTime = props.createTime; + this.version = props.version; + } +} \ No newline at end of file diff --git a/web_ui/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css b/web_ui/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css new file mode 100644 index 00000000..33fe3aa5 --- /dev/null +++ b/web_ui/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css @@ -0,0 +1,59 @@ +.iconBasicInfoContainer { + width: 300px; + height: 100px; + margin-left: 20px; + display: flex; + flex-direction: row; +} + +.cardContainer { + width: 360px; + height: 200px; + background-color: #FFF; + border-radius: 9px; + box-shadow: rgba(0, 0, 0, 0.4) 0 1px 1px -1px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-evenly; +} + +.icon { + width: 90px; + height: 90px; + border-radius: 5px; + font-size: 40px; + line-height: 90px; + text-align: center; + color: #ffffff; + background: rgba(96, 149, 209, 0.31); + border: 1px solid rgba(96, 149, 209, 0.31); +} + +.basicInfoContainer { + width: 200px; + height: 90px; + padding-left: 20px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; +} + +.basicInfoText { + +} + +.bigText { + font-size: 20px; +} + +.urlAndUpdateText { + margin-left: 20px; +} + +.createCardContainer { + font-size: 90px; + background: #6062E7; + color: white; +} \ No newline at end of file diff --git a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index 5eec0ebf..5e81219f 100644 --- a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -1,7 +1,11 @@ -import {Form, Button, Switch, Select, Input, InputNumber} from "antd"; +import {Form, Button, Switch, Select, Input, InputNumber, SelectProps} from "antd"; import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'; -import {useState} from "react"; +import {useEffect, useState} from "react"; import styles from "./pipelineFormStyle.module.css" +import {httpClient} from "@/app/infra/http/HttpClient"; +import {LLMModel, Pipeline} from "@/app/infra/api/api-types"; +import {LLMCardVO} from "@/app/home/models/component/llm-card/LLMCardVO"; +import {UUID} from "uuidjs"; export default function PipelineFormComponent({ onFinish, @@ -11,13 +15,38 @@ export default function PipelineFormComponent({ onCancel: () => void; }) { const [nowFormIndex, setNowFormIndex] = useState(0) + const [nowAIRunner, setNowAIRunner] = useState("") + const [llmModelList, setLlmModelList] = useState([]) // 这里不好,可以改成enum等 const formLabelList: FormLabel[] = [ + {label: "基础", name: "basic"}, {label: "AI能力", name: "ai"}, {label: "触发条件", name: "trigger"}, {label: "安全能力", name: "safety"}, {label: "输出处理", name: "output"}, ] + const [basicForm] = Form.useForm() + const [aiForm] = Form.useForm() + const [triggerForm] = Form.useForm() + const [safetyForm] = Form.useForm() + const [outputForm] = Form.useForm() + + useEffect(() => { + getLLMModelList() + }, []) + + function getLLMModelList() { + httpClient.getProviderLLMModels().then((resp) => { + setLlmModelList(resp.models.map((model: LLMModel) => { + return { + value: model.uuid, + label: model.name, + } + })) + }).catch((err) => { + console.error("get LLM model list error", err) + }) + } function getNowFormLabel() { return formLabelList[nowFormIndex] @@ -52,6 +81,50 @@ export default function PipelineFormComponent({ } } + function handleCommit() { + Promise.all([ + basicForm.validateFields(), + aiForm.validateFields(), + triggerForm.validateFields(), + safetyForm.validateFields(), + outputForm.validateFields(), + ]).then(() => { + const pipeline = assembleForm() + httpClient.createPipeline(pipeline).then(r => + onFinish() + ) + }).catch(e => { + console.error(e) + }) + } + + // TODO 类型混乱,需要优化 + function assembleForm(): Pipeline { + console.log("basicForm:", basicForm.getFieldsValue()) + console.log("aiForm:", aiForm.getFieldsValue()) + console.log("triggerForm:", triggerForm.getFieldsValue()) + console.log("safetyForm:", safetyForm.getFieldsValue()) + console.log("outputForm:", outputForm.getFieldsValue()) + const config: object = { + ai: aiForm.getFieldsValue(), + trigger: triggerForm.getFieldsValue(), + safety: safetyForm.getFieldsValue(), + output: outputForm.getFieldsValue(), + } + + return { + config, + created_at: "", + description: basicForm.getFieldsValue().description, + for_version: "", + name: basicForm.getFieldsValue().name, + stages: [], + updated_at: "", + uuid: UUID.generate(), + } + } + + return (
{getNowFormLabel().label} + + + + + + + + + {/* AI能力表单 ai */}
{/* Runner 配置区块 */}
运行器
@@ -77,163 +175,179 @@ export default function PipelineFormComponent({ { label: "Dify 服务 API", value: "dify-service-api" }, { label: "阿里云百炼平台 API", value: "dashscope-app-api" } ]} + onChange={value => setNowAIRunner(value)} /> {/* 内置 Agent 配置区块 */} -
配置内置Agent
- {/* TODO 这里要拉模型 */} - - + + + + + {/* TODO 这里要做转换处理 */} + + + + + } {/* Dify 服务 API 区块 */} -
配置Dify服务API
- - - - - ...\<\/think\>", value: "plain" }, - { label: "原始", value: "original" }, - { label: "移除", value: "remove" } - ]} - /> - - + { + nowAIRunner === "dify-service-api" && + <> +
配置Dify服务API
+ + + + + ...\<\/think\>", value: "plain"}, + {label: "原始", value: "original"}, + {label: "移除", value: "remove"} + ]} + /> + + + } {/* 阿里云百炼区块 */} -
配置阿里云百炼平台 API
- - - - - - + { + nowAIRunner === "dashscope-app-api" && + <> +
配置阿里云百炼平台 API
+ + + + + + + + }
{/* 触发条件表单 trigger */}
{/* 群响应规则块 */} -
群响应规则
+
群响应规则
- + {/* 内容过滤块 content-filter */}
内容过滤
@@ -382,6 +497,7 @@ export default function PipelineFormComponent({ {/* 长文本处理区块 */}
长文本处理
@@ -486,7 +602,7 @@ export default function PipelineFormComponent({ @@ -510,3 +626,8 @@ interface FormLabel { label: string, name: string, } + +interface LLMSelectList { + label: string, + name: string, +} \ No newline at end of file diff --git a/web_ui/src/app/home/pipelines/page.tsx b/web_ui/src/app/home/pipelines/page.tsx index f9363e2e..c9be078a 100644 --- a/web_ui/src/app/home/pipelines/page.tsx +++ b/web_ui/src/app/home/pipelines/page.tsx @@ -3,13 +3,33 @@ import {Modal} from "antd"; import {useState} from "react"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent"; +import {httpClient} from "@/app/infra/http/HttpClient"; +import {PipelineCardVO} from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; export default function PluginConfigPage() { const [modalOpen, setModalOpen] = useState(false); const [isEditForm, setIsEditForm] = useState(false) + const [pipelineList, setPipelineList] = useState([]) + function getPipelines() { + httpClient.getPipelines().then(value => { + value.pipelines.map(pipeline => { + return new PipelineCardVO({ + createTime: pipeline.created_at, + description: pipeline.description, + id: pipeline.uuid, + name: pipeline.name, + version: pipeline.for_version + }) + }) + }).catch(error => { + // TODO toast + console.log(error) + }) + } + return (
- {}} onCancel={() => {}}/> + { + getPipelines() + setModalOpen(false) + }} + onCancel={() => {}}/> {setModalOpen(true)}}/> From 8d37447146df16cdb1289873ab06d97db3bd2a24 Mon Sep 17 00:00:00 2001 From: Lightwing Date: Mon, 28 Apr 2025 10:35:39 +0800 Subject: [PATCH 032/121] feat: notification and spinning display step 1 (#1345) * feat: notification and loading display step 1 * chore: linter with husky and prettier, specifying rules needed --- web_ui/.gitignore | 2 + web_ui/.husky/pre-commit | 3 ++ web_ui/.lintstagedrc.json | 3 ++ web_ui/.prettierrc.mjs | 9 ++++ web_ui/package.json | 7 +++- .../home/bots/components/bot-form/BotForm.tsx | 41 ++++++++++++++++--- web_ui/src/app/home/bots/page.tsx | 17 +++++++- web_ui/src/app/infra/http/HttpClient.ts | 7 ++++ 8 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 web_ui/.husky/pre-commit create mode 100644 web_ui/.lintstagedrc.json create mode 100644 web_ui/.prettierrc.mjs diff --git a/web_ui/.gitignore b/web_ui/.gitignore index 5ef6a520..c77c37c5 100644 --- a/web_ui/.gitignore +++ b/web_ui/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +pnpm-lock.yaml \ No newline at end of file diff --git a/web_ui/.husky/pre-commit b/web_ui/.husky/pre-commit new file mode 100644 index 00000000..8eb362e8 --- /dev/null +++ b/web_ui/.husky/pre-commit @@ -0,0 +1,3 @@ +cd web_ui +pnpm lint-staged +pnpm test \ No newline at end of file diff --git a/web_ui/.lintstagedrc.json b/web_ui/.lintstagedrc.json new file mode 100644 index 00000000..f7a24c1f --- /dev/null +++ b/web_ui/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix", "eslint"] +} \ No newline at end of file diff --git a/web_ui/.prettierrc.mjs b/web_ui/.prettierrc.mjs new file mode 100644 index 00000000..0ce90351 --- /dev/null +++ b/web_ui/.prettierrc.mjs @@ -0,0 +1,9 @@ +/** + * @see https://prettier.io/docs/configuration + * @type {import("prettier").Config} + */ +const config = { + trailingComma: "none", +}; + +export default config; \ No newline at end of file diff --git a/web_ui/package.json b/web_ui/package.json index e01e5464..09fb91e3 100644 --- a/web_ui/package.json +++ b/web_ui/package.json @@ -6,7 +6,9 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "prepare": "cd .. && husky web_ui/.husky", + "lint-staged": "lint-staged" }, "dependencies": { "@ant-design/v5-patch-for-react-19": "^1.0.3", @@ -26,6 +28,9 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.4", + "husky": "^9.1.7", + "lint-staged": "^15.5.1", + "prettier": "^3.5.3", "typescript": "^5" } } diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index 4ab9d608..8f5bdf29 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -11,6 +11,8 @@ import {UUID} from 'uuidjs' import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; import {httpClient} from "@/app/infra/http/HttpClient"; import { Bot } from "@/app/infra/api/api-types"; +import { notification } from "antd"; + export default function BotForm({ initBotId, @@ -27,6 +29,7 @@ export default function BotForm({ const [dynamicForm] = Form.useForm(); const [adapterNameList, setAdapterNameList] = useState([]) const [dynamicFormConfigList, setDynamicFormConfigList] = useState([]) + const [isLoading, setIsLoading] = useState(false) useEffect(() => { initBotFormComponent() @@ -121,6 +124,8 @@ export default function BotForm({ // 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 function onDynamicFormSubmit(value: object) { + setIsLoading(true) + console.log('setloading', true) if (initBotId) { // 编辑提交 console.log('submit edit', form.getFieldsValue() ,value) @@ -135,9 +140,20 @@ export default function BotForm({ // TODO success toast console.log("update bot success", res) onFormSubmit(form.getFieldsValue()) + notification.success({ + message: "更新成功", + description: "机器人更新成功" + }) }).catch(err => { // TODO error toast - console.log("update bot error", err) + notification.error({ + message: "更新失败", + description: "机器人更新失败" + }) + }).finally(() => { + setIsLoading(false) + form.resetFields() + dynamicForm.resetFields() }) } else { // 创建提交 @@ -150,16 +166,28 @@ export default function BotForm({ } httpClient.createBot(newBot).then(res => { // TODO success toast + notification.success({ + message: "创建成功", + description: "机器人创建成功" + }) console.log(res) onFormSubmit(form.getFieldsValue()) }).catch(err => { // TODO error toast - console.log(err) + notification.error({ + message: "创建失败", + description: "机器人创建失败" + }) + }).finally(() => { + setIsLoading(false) + form.resetFields() + dynamicForm.resetFields() }) } setShowDynamicForm(false) - form.resetFields() - dynamicForm.resetFields() + console.log('setloading', false) + // TODO 刷新bot列表 + // TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)? } function handleSaveButton() { @@ -174,6 +202,7 @@ export default function BotForm({ wrapperCol={{span: 18}} layout='vertical' onFinish={handleFormFinish} + disabled={isLoading} > label={"机器人名称"} @@ -225,6 +254,7 @@ export default function BotForm({ type="primary" htmlType="button" onClick={handleSubmitButton} + loading={isLoading} > 提交 @@ -235,13 +265,14 @@ export default function BotForm({ type="primary" htmlType="submit" onClick={handleSaveButton} + loading={isLoading} > 保存 } diff --git a/web_ui/src/app/home/bots/page.tsx b/web_ui/src/app/home/bots/page.tsx index 65108e71..fdd32f59 100644 --- a/web_ui/src/app/home/bots/page.tsx +++ b/web_ui/src/app/home/bots/page.tsx @@ -5,7 +5,7 @@ import styles from "./botConfig.module.css"; import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent"; import {useRouter} from "next/navigation"; import {BotCardVO} from "@/app/home/bots/components/bot-card/BotCardVO"; -import {Modal} from "antd"; +import {Modal, notification, Spin} from "antd"; import BotForm from "@/app/home/bots/components/bot-form/BotForm"; import BotCard from "@/app/home/bots/components/bot-card/BotCard"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent" @@ -19,14 +19,18 @@ export default function BotConfigPage() { const [botList, setBotList] = useState([]) const [isEditForm, setIsEditForm] = useState(false) const [nowSelectedBotCard, setNowSelectedBotCard] = useState() + const [isLoading, setIsLoading] = useState(false) + useEffect(() => { // TODO:补齐加载转圈逻辑 + setIsLoading(true) checkHasLLM().then((hasLLM) => { if (hasLLM) { getBotList() } else { setPageShowRule(BotConfigPageShowRule.NO_LLM) + setIsLoading(false) } }) }, []) @@ -55,8 +59,15 @@ export default function BotConfigPage() { } setBotList(botList) }).catch((err) => { - // TODO error toast console.error("get bot list error", err) + // TODO HACK: need refactor to hook mode Notification, but it's not working under render + notification.error({ + message: "获取机器人列表失败", + description: err.message, + placement: "bottomRight", + }) + }).finally(() => { + setIsLoading(false) }) } @@ -78,6 +89,7 @@ export default function BotConfigPage() { } return ( +
}
+
) } diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index e8d51237..f930078a 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -6,6 +6,7 @@ import { ApiRespPluginConfig, PluginReorderElement, AsyncTaskCreatedResp, ApiRespSystemInfo, ApiRespAsyncTasks, AsyncTask, ApiRespAsyncTask, ApiRespUserToken } from '../api/api-types' +import { notification } from 'antd' type JSONValue = string | number | boolean | JSONObject | JSONArray | null interface JSONObject { [key: string]: JSONValue } @@ -111,6 +112,12 @@ class HttpClient { break case 500: // TODO 弹Toast窗 + // NOTE: move to component layer for customized message? + notification.error({ + message: "服务器错误", + description: errMessage, + placement: "bottomRight", + }) console.error('Server error:', errMessage) break } From 3950fc39bc46ddb5af0ddfbc7c4c4948c89d2920 Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 12:25:25 +0800 Subject: [PATCH 033/121] feat: redirect login when error 401 --- web_ui/src/app/infra/http/HttpClient.ts | 611 +++++++++++++----------- 1 file changed, 330 insertions(+), 281 deletions(-) diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index f930078a..c092f08d 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -1,350 +1,399 @@ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' +import axios, { + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, + AxiosError +} from "axios"; import { - ApiResponse, ApiRespProviderRequesters, ApiRespProviderRequester, ApiRespProviderLLMModels, - ApiRespProviderLLMModel, LLMModel, ApiRespPipelines, ApiRespPipeline, Pipeline, ApiRespPlatformAdapters, - ApiRespPlatformAdapter, ApiRespPlatformBots, ApiRespPlatformBot, Bot, ApiRespPlugins, ApiRespPlugin, Plugin, - ApiRespPluginConfig, PluginReorderElement, AsyncTaskCreatedResp, ApiRespSystemInfo, ApiRespAsyncTasks, AsyncTask, - ApiRespAsyncTask, ApiRespUserToken -} from '../api/api-types' -import { notification } from 'antd' + ApiRespProviderRequesters, + ApiRespProviderRequester, + ApiRespProviderLLMModels, + ApiRespProviderLLMModel, + LLMModel, + ApiRespPipelines, + ApiRespPipeline, + Pipeline, + ApiRespPlatformAdapters, + ApiRespPlatformAdapter, + ApiRespPlatformBots, + ApiRespPlatformBot, + Bot, + ApiRespPlugins, + ApiRespPlugin, + ApiRespPluginConfig, + PluginReorderElement, + AsyncTaskCreatedResp, + ApiRespSystemInfo, + ApiRespAsyncTasks, + ApiRespAsyncTask, + ApiRespUserToken +} from "../api/api-types"; +import { notification } from "antd"; -type JSONValue = string | number | boolean | JSONObject | JSONArray | null -interface JSONObject { [key: string]: JSONValue } -interface JSONArray extends Array { } +type JSONValue = string | number | boolean | JSONObject | JSONArray | null; +interface JSONObject { + [key: string]: JSONValue; +} +type JSONArray = Array; export interface ResponseData { - code: number - message: string - data: T - timestamp: number + code: number; + message: string; + data: T; + timestamp: number; } export interface RequestConfig extends AxiosRequestConfig { - isSSR?: boolean // 服务端渲染标识 - retry?: number // 重试次数 + isSSR?: boolean; // 服务端渲染标识 + retry?: number; // 重试次数 } class HttpClient { - private instance: AxiosInstance - // 暂不需要SSR - // private ssrInstance: AxiosInstance | null = null + private instance: AxiosInstance; + // 暂不需要SSR + // private ssrInstance: AxiosInstance | null = null - constructor(baseURL?: string) { - this.instance = axios.create({ - baseURL: baseURL || this.getBaseUrl(), - timeout: 15000, - headers: { - 'Content-Type': 'application/json', - 'X-Requested-With': 'XMLHttpRequest' - } - }) + constructor(baseURL?: string) { + this.instance = axios.create({ + baseURL: baseURL || this.getBaseUrl(), + timeout: 15000, + headers: { + "Content-Type": "application/json", + "X-Requested-With": "XMLHttpRequest" + } + }); - this.initInterceptors() + this.initInterceptors(); + } + + // 兜底URL,如果使用未配置会走到这里 + private getBaseUrl(): string { + return "http://localhost:5300"; + // NOT IMPLEMENT + if (typeof window === "undefined") { + // 服务端环境 + return ""; } + // 客户端环境 + return ""; + } - // 兜底URL,如果使用未配置会走到这里 - private getBaseUrl(): string { - return "http://localhost:5300" - // NOT IMPLEMENT - if (typeof window === 'undefined') { - // 服务端环境 - return "" + // 获取Session + private async getSession() { + // NOT IMPLEMENT + return ""; + } + + // 同步获取Session + private getSessionSync() { + // NOT IMPLEMENT + return localStorage.getItem("token"); + } + + // 拦截器配置 + private initInterceptors() { + // 请求拦截 + this.instance.interceptors.request.use( + async (config) => { + // 服务端请求自动携带 cookie, Langbot暂时用不到SSR相关 + // if (typeof window === 'undefined' && config.isSSR) { } + // cookie not required + // const { cookies } = await import('next/headers') + // config.headers.Cookie = cookies().toString() + + // 客户端添加认证头 + if (typeof window !== "undefined") { + const session = this.getSessionSync(); + config.headers.Authorization = `Bearer ${session}`; } - // 客户端环境 - return "" - } - // 获取Session - private async getSession() { - // NOT IMPLEMENT - return "" - } + return config; + }, + (error) => Promise.reject(error) + ); - // 同步获取Session - private getSessionSync() { - // NOT IMPLEMENT - return localStorage.getItem("token") - } + // 响应拦截 + this.instance.interceptors.response.use( + (response: AxiosResponse) => { + // 响应拦截处理写在这里,暂无业务需要 - // 拦截器配置 - private initInterceptors() { - // 请求拦截 - this.instance.interceptors.request.use( - async (config) => { - // 服务端请求自动携带 cookie, Langbot暂时用不到SSR相关 - // if (typeof window === 'undefined' && config.isSSR) { } - // cookie not required - // const { cookies } = await import('next/headers') - // config.headers.Cookie = cookies().toString() + return response; + }, + (error: AxiosError) => { + // 统一错误处理 + if (error.response) { + const { status, data } = error.response; + const errMessage = data?.message || error.message; - // 客户端添加认证头 - if (typeof window !== 'undefined') { - // NOT IMPLEMENT 从本地取Session,为空跳转到登陆页 - // const session = await this.getSession() - const session = this.getSessionSync() - config.headers.Authorization = `Bearer ${session}` - } + switch (status) { + case 401: + window.location.href = "/login"; + break; + case 403: + console.error("Permission denied:", errMessage); + break; + case 500: + // TODO 弹Toast窗 + // NOTE: move to component layer for customized message? + notification.error({ + message: "服务器错误", + description: errMessage, + placement: "bottomRight" + }); + console.error("Server error:", errMessage); + break; + } - return config - }, - (error) => Promise.reject(error) - ) - - // 响应拦截 - this.instance.interceptors.response.use( - (response: AxiosResponse) => { - // 响应拦截处理写在这里,暂无业务需要 - - return response - }, - (error: AxiosError) => { - // 统一错误处理 - if (error.response) { - const { status, data } = error.response - const errMessage = data?.message || error.message - - switch (status) { - case 401: - // 401 处理 - break - case 403: - console.error('Permission denied:', errMessage) - break - case 500: - // TODO 弹Toast窗 - // NOTE: move to component layer for customized message? - notification.error({ - message: "服务器错误", - description: errMessage, - placement: "bottomRight", - }) - console.error('Server error:', errMessage) - break - } - - return Promise.reject({ - code: data?.code || status, - message: errMessage, - data: data?.data || null - }) - } - - return Promise.reject({ - code: -1, - message: error.message || 'Network Error', - data: null - }) - } - ) - } - - - // 转换下划线为驼峰 - private convertKeysToCamel(obj: JSONValue): JSONValue { - if (Array.isArray(obj)) { - return obj.map(v => this.convertKeysToCamel(v)) - } else if (obj !== null && typeof obj === 'object') { - return Object.keys(obj).reduce((acc, key) => { - const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) - acc[camelKey] = this.convertKeysToCamel((obj as JSONObject)[key]) - return acc - }, {} as JSONObject) + return Promise.reject({ + code: data?.code || status, + message: errMessage, + data: data?.data || null + }); } - return obj - } - // 核心请求方法 - public async request(config: RequestConfig): Promise { - try { - // 这里未来如果需要SSR可以将前面替换为SSR的instance - const instance = config.isSSR ? this.instance : this.instance - const response = await instance.request>(config) - return response.data.data - } catch (error) { - return this.handleError(error) - } - } + return Promise.reject({ + code: -1, + message: error.message || "Network Error", + data: null + }); + } + ); + } - private handleError(error: any): never { - if (axios.isCancel(error)) { - throw { code: -2, message: 'Request canceled', data: null } - } - throw error + // 转换下划线为驼峰 + private convertKeysToCamel(obj: JSONValue): JSONValue { + if (Array.isArray(obj)) { + return obj.map((v) => this.convertKeysToCamel(v)); + } else if (obj !== null && typeof obj === "object") { + return Object.keys(obj).reduce((acc, key) => { + const camelKey = key.replace(/_([a-z])/g, (_, letter) => + letter.toUpperCase() + ); + acc[camelKey] = this.convertKeysToCamel((obj as JSONObject)[key]); + return acc; + }, {} as JSONObject); } + return obj; + } - // 快捷方法 - public get(url: string, params?: object, config?: RequestConfig) { - return this.request({ method: 'get', url, params, ...config }) + // 核心请求方法 + public async request(config: RequestConfig): Promise { + try { + // 这里未来如果需要SSR可以将前面替换为SSR的instance + const instance = config.isSSR ? this.instance : this.instance; + const response = await instance.request>(config); + return response.data.data; + } catch (error) { + return this.handleError(error as object); } + } - public post(url: string, data?: object, config?: RequestConfig) { - return this.request({ method: 'post', url, data, ...config }) + private handleError(error: object): never { + if (axios.isCancel(error)) { + throw { code: -2, message: "Request canceled", data: null }; } + throw error; + } - public put(url: string, data?: object, config?: RequestConfig) { - return this.request({ method: 'put', url, data, ...config }) - } + // 快捷方法 + public get( + url: string, + params?: object, + config?: RequestConfig + ) { + return this.request({ method: "get", url, params, ...config }); + } - public delete(url: string, config?: RequestConfig) { - return this.request({ method: 'delete', url, ...config }) - } + public post(url: string, data?: object, config?: RequestConfig) { + return this.request({ method: "post", url, data, ...config }); + } - // real api request implementation - // ============ Provider API ============ - public getProviderRequesters(): Promise { - return this.get('/api/v1/provider/requesters') - } + public put(url: string, data?: object, config?: RequestConfig) { + return this.request({ method: "put", url, data, ...config }); + } - public getProviderRequester(name: string): Promise { - return this.get(`/api/v1/provider/requesters/${name}`) - } + public delete(url: string, config?: RequestConfig) { + return this.request({ method: "delete", url, ...config }); + } - public getProviderRequesterIconURL(name: string): string { - return `/api/v1/provider/requesters/${name}/icon` - } + // real api request implementation + // ============ Provider API ============ + public getProviderRequesters(): Promise { + return this.get("/api/v1/provider/requesters"); + } - // ============ Provider Model LLM ============ - public getProviderLLMModels(): Promise { - return this.get('/api/v1/provider/models/llm') - } + public getProviderRequester(name: string): Promise { + return this.get(`/api/v1/provider/requesters/${name}`); + } - public getProviderLLMModel(uuid: string): Promise { - return this.get(`/api/v1/provider/models/llm/${uuid}`) - } + public getProviderRequesterIconURL(name: string): string { + return `/api/v1/provider/requesters/${name}/icon`; + } - public createProviderLLMModel(model: LLMModel): Promise { - return this.post('/api/v1/provider/models/llm', model) - } + // ============ Provider Model LLM ============ + public getProviderLLMModels(): Promise { + return this.get("/api/v1/provider/models/llm"); + } - public deleteProviderLLMModel(uuid: string): Promise { - return this.delete(`/api/v1/provider/models/llm/${uuid}`) - } + public getProviderLLMModel(uuid: string): Promise { + return this.get(`/api/v1/provider/models/llm/${uuid}`); + } - // ============ Pipeline API ============ - public getGeneralPipelineMetadata(): Promise { // as designed, this method will be deprecated, and only for developer to check the prefered config schema - return this.get('/api/v1/pipelines/_/metadata') - } + public createProviderLLMModel(model: LLMModel): Promise { + return this.post("/api/v1/provider/models/llm", model); + } - public getPipelines(): Promise { - return this.get('/api/v1/pipelines') - } + public deleteProviderLLMModel(uuid: string): Promise { + return this.delete(`/api/v1/provider/models/llm/${uuid}`); + } - public getPipeline(uuid: string): Promise { - return this.get(`/api/v1/pipelines/${uuid}`) - } + // ============ Pipeline API ============ + public getGeneralPipelineMetadata(): Promise { + // as designed, this method will be deprecated, and only for developer to check the prefered config schema + return this.get("/api/v1/pipelines/_/metadata"); + } - public createPipeline(pipeline: Pipeline): Promise { - return this.post('/api/v1/pipelines', pipeline) - } + public getPipelines(): Promise { + return this.get("/api/v1/pipelines"); + } - public updatePipeline(uuid: string, pipeline: Pipeline): Promise { - return this.put(`/api/v1/pipelines/${uuid}`, pipeline) - } + public getPipeline(uuid: string): Promise { + return this.get(`/api/v1/pipelines/${uuid}`); + } - public deletePipeline(uuid: string): Promise { - return this.delete(`/api/v1/pipelines/${uuid}`) - } + public createPipeline(pipeline: Pipeline): Promise { + return this.post("/api/v1/pipelines", pipeline); + } - // ============ Platform API ============ - public getAdapters(): Promise { - return this.get('/api/v1/platform/adapters') - } + public updatePipeline(uuid: string, pipeline: Pipeline): Promise { + return this.put(`/api/v1/pipelines/${uuid}`, pipeline); + } - public getAdapter(name: string): Promise { - return this.get(`/api/v1/platform/adapters/${name}`) - } + public deletePipeline(uuid: string): Promise { + return this.delete(`/api/v1/pipelines/${uuid}`); + } - public getAdapterIconURL(name: string): string { - return `/api/v1/platform/adapters/${name}/icon` - } + // ============ Platform API ============ + public getAdapters(): Promise { + return this.get("/api/v1/platform/adapters"); + } - // ============ Platform Bots ============ - public getBots(): Promise { - return this.get('/api/v1/platform/bots') - } + public getAdapter(name: string): Promise { + return this.get(`/api/v1/platform/adapters/${name}`); + } - public getBot(uuid: string): Promise { - return this.get(`/api/v1/platform/bots/${uuid}`) - } + public getAdapterIconURL(name: string): string { + return `/api/v1/platform/adapters/${name}/icon`; + } - public createBot(bot: Bot): Promise { - return this.post('/api/v1/platform/bots', bot) - } + // ============ Platform Bots ============ + public getBots(): Promise { + return this.get("/api/v1/platform/bots"); + } - public updateBot(uuid: string, bot: Bot): Promise { - return this.put(`/api/v1/platform/bots/${uuid}`, bot) - } + public getBot(uuid: string): Promise { + return this.get(`/api/v1/platform/bots/${uuid}`); + } - public deleteBot(uuid: string): Promise { - return this.delete(`/api/v1/platform/bots/${uuid}`) - } + public createBot(bot: Bot): Promise { + return this.post("/api/v1/platform/bots", bot); + } - // ============ Plugins API ============ - public getPlugins(): Promise { - return this.get('/api/v1/plugins') - } + public updateBot(uuid: string, bot: Bot): Promise { + return this.put(`/api/v1/platform/bots/${uuid}`, bot); + } - public getPlugin(author: string, name: string): Promise { - return this.get(`/api/v1/plugins/${author}/${name}`) - } + public deleteBot(uuid: string): Promise { + return this.delete(`/api/v1/platform/bots/${uuid}`); + } - public getPluginConfig(author: string, name: string): Promise { - return this.get(`/api/v1/plugins/${author}/${name}/config`) - } + // ============ Plugins API ============ + public getPlugins(): Promise { + return this.get("/api/v1/plugins"); + } - public updatePluginConfig(author: string, name: string, config: object): Promise { - return this.put(`/api/v1/plugins/${author}/${name}/config`, config) - } + public getPlugin(author: string, name: string): Promise { + return this.get(`/api/v1/plugins/${author}/${name}`); + } - public togglePlugin(author: string, name: string, target_enabled: boolean): Promise { - return this.post(`/api/v1/plugins/${author}/${name}/toggle`, { target_enabled }) - } + public getPluginConfig( + author: string, + name: string + ): Promise { + return this.get(`/api/v1/plugins/${author}/${name}/config`); + } - public reorderPlugins(plugins: PluginReorderElement[]): Promise { - return this.post('/api/v1/plugins/reorder', plugins) - } + public updatePluginConfig( + author: string, + name: string, + config: object + ): Promise { + return this.put(`/api/v1/plugins/${author}/${name}/config`, config); + } - public updatePlugin(author: string, name: string): Promise { - return this.post(`/api/v1/plugins/${author}/${name}/update`) - } + public togglePlugin( + author: string, + name: string, + target_enabled: boolean + ): Promise { + return this.post(`/api/v1/plugins/${author}/${name}/toggle`, { + target_enabled + }); + } - public installPluginFromGithub(source: string): Promise { - return this.post('/api/v1/plugins/install/github', { source }) - } + public reorderPlugins(plugins: PluginReorderElement[]): Promise { + return this.post("/api/v1/plugins/reorder", plugins); + } - public removePlugin(author: string, name: string): Promise { - return this.delete(`/api/v1/plugins/${author}/${name}`) - } + public updatePlugin( + author: string, + name: string + ): Promise { + return this.post(`/api/v1/plugins/${author}/${name}/update`); + } - // ============ System API ============ - public getSystemInfo(): Promise { - return this.get('/api/v1/system/info') - } + public installPluginFromGithub( + source: string + ): Promise { + return this.post("/api/v1/plugins/install/github", { source }); + } - public getAsyncTasks(): Promise { - return this.get('/api/v1/system/tasks') - } + public removePlugin( + author: string, + name: string + ): Promise { + return this.delete(`/api/v1/plugins/${author}/${name}`); + } - public getAsyncTask(id: number): Promise { - return this.get(`/api/v1/system/tasks/${id}`) - } + // ============ System API ============ + public getSystemInfo(): Promise { + return this.get("/api/v1/system/info"); + } - // ============ User API ============ - public checkIfInited(): Promise<{initialized: boolean}> { - return this.get('/api/v1/user/init') - } + public getAsyncTasks(): Promise { + return this.get("/api/v1/system/tasks"); + } - public initUser(user: string, password: string): Promise { - return this.post('/api/v1/user/init', { user, password }) - } + public getAsyncTask(id: number): Promise { + return this.get(`/api/v1/system/tasks/${id}`); + } - public authUser(user: string, password: string): Promise { - return this.post('/api/v1/user/auth', { user, password }) - } + // ============ User API ============ + public checkIfInited(): Promise<{ initialized: boolean }> { + return this.get("/api/v1/user/init"); + } - public checkUserToken(): Promise { - return this.get('/api/v1/user/check-token') - } + public initUser(user: string, password: string): Promise { + return this.post("/api/v1/user/init", { user, password }); + } + + public authUser(user: string, password: string): Promise { + return this.post("/api/v1/user/auth", { user, password }); + } + + public checkUserToken(): Promise { + return this.get("/api/v1/user/check-token"); + } } -export const httpClient = new HttpClient("https://version-4.langbot.dev") +export const httpClient = new HttpClient("https://version-4.langbot.dev"); From 2a030622a987ba4d8834ded380d5fa0925a9d66a Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 28 Apr 2025 14:41:18 +0800 Subject: [PATCH 034/121] feat: fetch pipelines --- web_ui/src/app/home/pipelines/page.tsx | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/web_ui/src/app/home/pipelines/page.tsx b/web_ui/src/app/home/pipelines/page.tsx index c9be078a..e6a8a331 100644 --- a/web_ui/src/app/home/pipelines/page.tsx +++ b/web_ui/src/app/home/pipelines/page.tsx @@ -1,21 +1,25 @@ "use client" import {Modal} from "antd"; -import {useState} from "react"; +import {useState, useEffect} from "react"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent"; import {httpClient} from "@/app/infra/http/HttpClient"; import {PipelineCardVO} from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; - +import PipelineCardComponent from "@/app/home/pipelines/components/pipeline-card/PipelineCardComponent"; export default function PluginConfigPage() { const [modalOpen, setModalOpen] = useState(false); const [isEditForm, setIsEditForm] = useState(false) - const [pipelineList, setPipelineList] = useState([]) + const [pipelineList, setPipelineList] = useState([]) + + useEffect(() => { + getPipelines() + }, []) function getPipelines() { httpClient.getPipelines().then(value => { - value.pipelines.map(pipeline => { + const pipelineList = value.pipelines.map(pipeline => { return new PipelineCardVO({ createTime: pipeline.created_at, description: pipeline.description, @@ -24,6 +28,7 @@ export default function PluginConfigPage() { version: pipeline.for_version }) }) + setPipelineList(pipelineList) }).catch(error => { // TODO toast console.log(error) @@ -49,6 +54,14 @@ export default function PluginConfigPage() { onCancel={() => {}}/> + { + pipelineList.length > 0 && +
+ {pipelineList.map(pipeline => { + return + })} +
+ } {setModalOpen(true)}}/> ); From 3d31ace50bb6400ec013dcd910e0cde5aa7bd83f Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 14:58:08 +0800 Subject: [PATCH 035/121] feat: plugin list installed finish --- .../PluginInstalledComponent.tsx | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index aaa5d0e9..34333887 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -7,6 +7,8 @@ import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card import styles from "@/app/home/plugins/plugins.module.css"; import {Modal, Input} from "antd"; import {GithubOutlined} from "@ant-design/icons"; +import {httpClient} from "@/app/infra/http/HttpClient"; +import * as http from "node:http"; export default function PluginInstalledComponent () { const [pluginList, setPluginList] = useState([]) @@ -19,42 +21,21 @@ export default function PluginInstalledComponent () { }, []) function initData() { - getPluginList().then((value) => { - setPluginList(value) - }) + getPluginList() } - async function getPluginList() { - return [ - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1" - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1" - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1" - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1" - }) - ] + function getPluginList() { + httpClient.getPlugins().then((value) => { + setPluginList(value.plugins.map(plugin => { + return new PluginCardVO({ + author: plugin.author, + description: plugin.description.zh_CN, + handlerCount: 0, + name: plugin.name, + version: plugin.version + }) + })) + }) } function handleModalConfirm() { @@ -63,8 +44,12 @@ export default function PluginInstalledComponent () { } function installPlugin(url: string) { - // TODO 接安装Plugin的接口 - console.log("installPlugin: ", url) + httpClient.installPluginFromGithub(url).then(res => { + // 安装后重新拉取 + getPluginList() + }).catch(err => { + console.log("error when install plugin:", err) + }) } return (
From 9850a0c2bf5b8f4fd4e29c06c3aaeb5d129eaa61 Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 19:06:41 +0800 Subject: [PATCH 036/121] feat: plugin market pagination access api --- .../plugin-market/PluginMarketComponent.tsx | 76 +++++-------------- .../plugin-market-card/PluginMarketCardVO.ts | 5 -- web_ui/src/app/infra/api/api-types/index.ts | 24 +++++- web_ui/src/app/infra/http/HttpClient.ts | 29 +++++-- 4 files changed, 67 insertions(+), 67 deletions(-) diff --git a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index 64f73f7e..f7ba31be 100644 --- a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -6,9 +6,12 @@ import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; import {Input, Pagination} from "antd"; import {debounce} from "lodash" +import {httpClient, spaceClient} from "@/app/infra/http/HttpClient"; export default function PluginMarketComponent () { const [marketPluginList, setMarketPluginList] = useState([]) + const [totalCount, setTotalCount] = useState(0) + const [nowPage, setNowPage] = useState(1) const [searchKeyword, setSearchKeyword] = useState("") useEffect(() => { @@ -20,65 +23,25 @@ export default function PluginMarketComponent () { } function onInputSearchKeyword(keyword: string) { + // 这里记得加防抖,暂时没加 setSearchKeyword(keyword) - debounceSearch(keyword) + setNowPage(1) + getPluginList(1, keyword) } - const debounceSearch = useCallback( - debounce((keyword: string) => { - console.log("debounce search", keyword) - searchPlugin(keyword).then(marketPluginList => { - setMarketPluginList(marketPluginList) - }) - }, 500), [] - ) - async function searchPlugin(keyword: string, pageNumber: number = 1): Promise { - // TODO 实现搜索 - const demoResult: PluginMarketCardVO[] = [] - for (let i = 0; i < keyword.length; i ++) { - demoResult.push(new PluginMarketCardVO({ - author: "/hanahana", - description: "一个搜索测试的描述", - githubURL: "?", - name: "搜索插件" + i, - pluginId: `${i}`, - starCount: 19 + i, - version: `0.${i}`, - })) - } - return demoResult - } - - function getPluginList(pageNumber: number = 1) { - new Promise((resolve, reject) => { - const result = [ - new PluginMarketCardVO({ - pluginId: "aaa", - description: "一般的描述", - name: "插件AAA", - author: "/hana", - version: "0.1", - githubURL: "", - starCount: 23 - }), - ] - for (let i = 0; i < pageNumber; i ++) { - result.push( - new PluginMarketCardVO({ - pluginId: "aaa", - description: "一般的描述", - name: "插件AAA", - author: "/hana", - version: "0.1", - githubURL: "", - starCount: 23 - }) - ) - } - resolve(result) - }).then((value) => { - setMarketPluginList(value) + function getPluginList(page: number = nowPage, keyword: string = searchKeyword) { + spaceClient.getMarketPlugins(page, 10, keyword).then(res => { + setMarketPluginList(res.plugins.map(marketPlugin => new PluginMarketCardVO({ + author: marketPlugin.author, + description: marketPlugin.description, + githubURL: marketPlugin.repository, + name: marketPlugin.name, + pluginId: String(marketPlugin.ID), + starCount: marketPlugin.stars, + }))) + setTotalCount(res.total) + console.log("market plugins:", res) }) } @@ -104,8 +67,9 @@ export default function PluginMarketComponent () {
{ + setNowPage(pageNumber) getPluginList(pageNumber) }} /> diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts index 4a806ec4..6af3f199 100644 --- a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts +++ b/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts @@ -1,7 +1,6 @@ export interface IPluginMarketCardVO { pluginId: string; author: string, - version: string, name: string, description: string, starCount: number, @@ -13,7 +12,6 @@ export class PluginMarketCardVO implements IPluginMarketCardVO { description: string; name: string; author: string; - version: string; githubURL: string; starCount: number; @@ -21,11 +19,8 @@ export class PluginMarketCardVO implements IPluginMarketCardVO { this.description = prop.description this.name = prop.name this.author = prop.author - this.version = prop.version this.githubURL = prop.githubURL this.starCount = prop.starCount this.pluginId = prop.pluginId } - - } diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index 69aa4515..9a94fbad 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -179,4 +179,26 @@ export interface AsyncTask { export interface ApiRespUserToken { token: string; -} \ No newline at end of file +} + +export interface MarketPlugin { + ID: number + CreatedAt: string // ISO 8601 格式日期 + UpdatedAt: string + DeletedAt: string | null + name: string + author: string + description: string + repository: string // GitHub 仓库路径 + artifacts_path: string + stars: number + downloads: number + status: "synced" | string // 可根据实际状态值扩展联合类型 + synced_at: string + pushed_at: string // 最后一次代码推送时间 +} + +export interface MarketPluginResponse { + plugins: MarketPlugin[] + total: number +} diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index c092f08d..15efe49e 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -26,7 +26,7 @@ import { ApiRespSystemInfo, ApiRespAsyncTasks, ApiRespAsyncTask, - ApiRespUserToken + ApiRespUserToken, MarketPluginResponse } from "../api/api-types"; import { notification } from "antd"; @@ -50,19 +50,22 @@ export interface RequestConfig extends AxiosRequestConfig { class HttpClient { private instance: AxiosInstance; + private disableToken: boolean = false // 暂不需要SSR // private ssrInstance: AxiosInstance | null = null - constructor(baseURL?: string) { + constructor( + baseURL?: string, + disableToken?: boolean + ) { this.instance = axios.create({ baseURL: baseURL || this.getBaseUrl(), timeout: 15000, headers: { "Content-Type": "application/json", - "X-Requested-With": "XMLHttpRequest" } }); - + this.disableToken = disableToken || false this.initInterceptors(); } @@ -102,7 +105,7 @@ class HttpClient { // config.headers.Cookie = cookies().toString() // 客户端添加认证头 - if (typeof window !== "undefined") { + if (typeof window !== "undefined" && !this.disableToken) { const session = this.getSessionSync(); config.headers.Authorization = `Bearer ${session}`; } @@ -352,6 +355,19 @@ class HttpClient { return this.post(`/api/v1/plugins/${author}/${name}/update`); } + public getMarketPlugins( + page: number, + page_size: number, + query: string, + ): Promise { + return this.post(`/api/v1/market/plugins`, { + page, + page_size, + query, + sort_by: "stars", + sort_order: "DESC" + }) + } public installPluginFromGithub( source: string ): Promise { @@ -397,3 +413,6 @@ class HttpClient { } export const httpClient = new HttpClient("https://version-4.langbot.dev"); + +// 临时写法,未来两种Client都继承自HttpClient父类,不允许共享方法 +export const spaceClient = new HttpClient("https://space.langbot.app") From a6836c723ad359d36c6190ef8945ecafd2fd88fb Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 20:45:06 +0800 Subject: [PATCH 037/121] feat: finish toggle plugin --- .../plugins/plugin-installed/PluginCardVO.ts | 3 +++ .../PluginInstalledComponent.tsx | 4 ++-- .../plugin-card/PluginCardComponent.tsx | 23 ++++++++++++++++++- .../plugin-card/pluginCard.module.css | 4 ++++ web_ui/src/app/infra/api/api-types/index.ts | 2 +- web_ui/src/app/infra/http/HttpClient.ts | 2 +- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts b/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts index 768624d7..00f05543 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts @@ -4,6 +4,7 @@ export interface IPluginCardVO { name: string, description: string, handlerCount: number, + isInitialized: boolean, } export class PluginCardVO implements IPluginCardVO { @@ -12,6 +13,7 @@ export class PluginCardVO implements IPluginCardVO { name: string; author: string; version: string; + isInitialized: boolean; constructor(prop: IPluginCardVO) { this.description = prop.description @@ -19,6 +21,7 @@ export class PluginCardVO implements IPluginCardVO { this.name = prop.name this.author = prop.author this.version = prop.version + this.isInitialized = prop.isInitialized } } diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 34333887..8aa8b8f0 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -8,7 +8,6 @@ import styles from "@/app/home/plugins/plugins.module.css"; import {Modal, Input} from "antd"; import {GithubOutlined} from "@ant-design/icons"; import {httpClient} from "@/app/infra/http/HttpClient"; -import * as http from "node:http"; export default function PluginInstalledComponent () { const [pluginList, setPluginList] = useState([]) @@ -32,7 +31,8 @@ export default function PluginInstalledComponent () { description: plugin.description.zh_CN, handlerCount: 0, name: plugin.name, - version: plugin.version + version: plugin.version, + isInitialized: plugin.status === "initialized", }) })) }) diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index 1c4511c7..b8a2c784 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -1,13 +1,28 @@ import styles from "./pluginCard.module.css" import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; import {GithubOutlined, LinkOutlined, ToolOutlined} from '@ant-design/icons'; -import {Tag} from 'antd' +import {Switch, Tag} from 'antd' +import {useState} from "react"; +import {httpClient} from "@/app/infra/http/HttpClient"; export default function PluginCardComponent({ cardVO }: { cardVO: PluginCardVO }) { + const [initialized, setInitialized] = useState(cardVO.isInitialized) + const [switchEnable, setSwitchEnable] = useState(true) + + function handleEnable() { + setSwitchEnable(false) + httpClient.togglePlugin(cardVO.author, cardVO.name, !initialized).then(result => { + setInitialized(!initialized) + }).catch(err => { + console.log("error: ", err) + }).finally(() => { + setSwitchEnable(true) + }) + } return (
{/* header */} @@ -41,6 +56,12 @@ export default function PluginCardComponent({ style={{fontSize: '22px'}} />
+ + ); diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css index 44ed2665..81ad489a 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css @@ -40,6 +40,10 @@ .cardFooter { width: 90%; height: 30px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; } diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index 9a94fbad..5059529d 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -193,7 +193,7 @@ export interface MarketPlugin { artifacts_path: string stars: number downloads: number - status: "synced" | string // 可根据实际状态值扩展联合类型 + status: "initialized" | "mounted" // 可根据实际状态值扩展联合类型 synced_at: string pushed_at: string // 最后一次代码推送时间 } diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web_ui/src/app/infra/http/HttpClient.ts index 15efe49e..a8bb53cc 100644 --- a/web_ui/src/app/infra/http/HttpClient.ts +++ b/web_ui/src/app/infra/http/HttpClient.ts @@ -339,7 +339,7 @@ class HttpClient { name: string, target_enabled: boolean ): Promise { - return this.post(`/api/v1/plugins/${author}/${name}/toggle`, { + return this.put(`/api/v1/plugins/${author}/${name}/toggle`, { target_enabled }); } From 32f138bff52d3e2e72100da71ab4484ba0f4a163 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 28 Apr 2025 20:50:30 +0800 Subject: [PATCH 038/121] fix(plugin mgr): bad params for dump settings --- pkg/plugin/manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/plugin/manager.py b/pkg/plugin/manager.py index 2ecd01c2..f5b5c28f 100644 --- a/pkg/plugin/manager.py +++ b/pkg/plugin/manager.py @@ -339,7 +339,7 @@ class PluginManager: plugin.enabled = new_status - await self.dump_plugin_container_setting(self.plugin_containers) + await self.dump_plugin_container_setting(plugin) break @@ -360,7 +360,8 @@ class PluginManager: self.plugin_containers.sort(key=lambda x: x.priority, reverse=True) - await self.dump_plugin_container_setting(self.plugin_containers) + for plugin in self.plugin_containers: + await self.dump_plugin_container_setting(plugin) async def set_plugin_config(self, plugin_container: context.RuntimeContainer, new_config: dict): plugin_container.plugin_config = new_config From 5c74bb41c9e1c0b3928e18183b77df0c4175037a Mon Sep 17 00:00:00 2001 From: HYana Date: Mon, 28 Apr 2025 21:35:26 +0800 Subject: [PATCH 039/121] feat: fix eslint limits to build --- web_ui/src/app/home/bots/ICreateBotField.ts | 3 - .../home/bots/components/bot-form/BotForm.tsx | 535 ++++---- .../components/home-sidebar/HomeSidebar.tsx | 179 ++- .../home-sidebar/HomeSidebarChild.tsx | 61 +- web_ui/src/app/home/layout.tsx | 48 +- .../models/component/llm-form/LLMForm.tsx | 541 ++++---- .../pipeline-form/PipelineChildFormEntity.ts | 27 +- .../pipeline-form/PipelineFormComponent.tsx | 1169 ++++++++--------- web_ui/src/app/home/pipelines/page.tsx | 122 +- .../PluginInstalledComponent.tsx | 183 +-- .../plugin-card/PluginCardComponent.tsx | 119 +- .../plugin-market/PluginMarketComponent.tsx | 144 +- web_ui/src/app/infra/api/api-types/index.ts | 227 ++-- .../pipelines/GetMetaDataResponse.ts | 57 +- web_ui/src/app/login/page.tsx | 358 ++--- 15 files changed, 1886 insertions(+), 1887 deletions(-) diff --git a/web_ui/src/app/home/bots/ICreateBotField.ts b/web_ui/src/app/home/bots/ICreateBotField.ts index fea53c17..e69de29b 100644 --- a/web_ui/src/app/home/bots/ICreateBotField.ts +++ b/web_ui/src/app/home/bots/ICreateBotField.ts @@ -1,3 +0,0 @@ -export interface ICreateBotField { - -} \ No newline at end of file diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx index 8f5bdf29..5c3e804d 100644 --- a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx @@ -1,281 +1,292 @@ -import {BotFormEntity, IBotFormEntity} from "@/app/home/bots/components/bot-form/BotFormEntity"; -import {Button, Form, Input, Select, Space} from "antd"; -import {useEffect, useState} from "react"; -import {IChooseAdapterEntity} from "@/app/home/bots/components/bot-form/ChooseAdapterEntity"; import { - DynamicFormItemConfig, - IDynamicFormItemConfig, - parseDynamicFormItemType + BotFormEntity, + IBotFormEntity +} from "@/app/home/bots/components/bot-form/BotFormEntity"; +import { Button, Form, Input, notification, Select, Space } from "antd"; +import { useEffect, useState } from "react"; +import { IChooseAdapterEntity } from "@/app/home/bots/components/bot-form/ChooseAdapterEntity"; +import { + DynamicFormItemConfig, + IDynamicFormItemConfig, + parseDynamicFormItemType } from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; -import {UUID} from 'uuidjs' +import { UUID } from "uuidjs"; import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; -import {httpClient} from "@/app/infra/http/HttpClient"; +import { httpClient } from "@/app/infra/http/HttpClient"; import { Bot } from "@/app/infra/api/api-types"; -import { notification } from "antd"; - export default function BotForm({ - initBotId, - onFormSubmit, - onFormCancel, + initBotId, + onFormSubmit, + onFormCancel }: { - initBotId?: string; - onFormSubmit: (value: IBotFormEntity) => void; - onFormCancel: (value: IBotFormEntity) => void; + initBotId?: string; + onFormSubmit: (value: IBotFormEntity) => void; + onFormCancel: (value: IBotFormEntity) => void; }) { - const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = useState(new Map()) - const [form] = Form.useForm(); - const [showDynamicForm, setShowDynamicForm] = useState(false) - const [dynamicForm] = Form.useForm(); - const [adapterNameList, setAdapterNameList] = useState([]) - const [dynamicFormConfigList, setDynamicFormConfigList] = useState([]) - const [isLoading, setIsLoading] = useState(false) + const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = + useState(new Map()); + const [form] = Form.useForm(); + const [showDynamicForm, setShowDynamicForm] = useState(false); + const [dynamicForm] = Form.useForm(); + const [adapterNameList, setAdapterNameList] = useState< + IChooseAdapterEntity[] + >([]); + const [dynamicFormConfigList, setDynamicFormConfigList] = useState< + IDynamicFormItemConfig[] + >([]); + const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - initBotFormComponent() - if (initBotId) { - onEditMode() - } else { - onCreateMode() - } - }, []) + useEffect(() => { + initBotFormComponent(); + if (initBotId) { + onEditMode(); + } else { + onCreateMode(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - async function initBotFormComponent() { - // 拉取adapter - const rawAdapterList = await httpClient.getAdapters() - // 初始化适配器选择列表 - setAdapterNameList( - rawAdapterList.adapters.map(item => { - return { - label: item.label.zh_CN, - value: item.name - } + async function initBotFormComponent() { + // 拉取adapter + const rawAdapterList = await httpClient.getAdapters(); + // 初始化适配器选择列表 + setAdapterNameList( + rawAdapterList.adapters.map((item) => { + return { + label: item.label.zh_CN, + value: item.name + }; + }) + ); + // 初始化适配器表单map + rawAdapterList.adapters.forEach((rawAdapter) => { + adapterNameToDynamicConfigMap.set( + rawAdapter.name, + rawAdapter.spec.config.map( + (item) => + new DynamicFormItemConfig({ + default: item.default, + id: UUID.generate(), + label: item.label, + name: item.name, + required: item.required, + type: parseDynamicFormItemType(item.type) }) ) - // 初始化适配器表单map - rawAdapterList.adapters.forEach(rawAdapter => { - adapterNameToDynamicConfigMap.set( - rawAdapter.name, - rawAdapter.spec.config.map(item => - new DynamicFormItemConfig({ - default: item.default, - id: UUID.generate(), - label: item.label, - name: item.name, - required: item.required, - type: parseDynamicFormItemType(item.type) - }) - ) - ) + ); + }); + // 拉取初始化表单信息 + if (initBotId) { + getBotFieldById(initBotId).then((val) => { + form.setFieldsValue(val); + handleAdapterSelect(val.adapter); + dynamicForm.setFieldsValue(val.adapter_config); + }); + } else { + form.resetFields(); + } + setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap); + } + + async function onCreateMode() {} + + function onEditMode() {} + + async function getBotFieldById(botId: string): Promise { + const bot = (await httpClient.getBot(botId)).bot; + return new BotFormEntity({ + adapter: bot.adapter, + description: bot.description, + name: bot.name, + adapter_config: bot.adapter_config + }); + } + + function handleAdapterSelect(adapterName: string) { + console.log("Select adapter: ", adapterName); + if (adapterName) { + const dynamicFormConfigList = + adapterNameToDynamicConfigMap.get(adapterName); + console.log(dynamicFormConfigList); + if (dynamicFormConfigList) { + setDynamicFormConfigList(dynamicFormConfigList); + } + setShowDynamicForm(true); + } else { + setShowDynamicForm(false); + } + } + + function handleSubmitButton() { + form.submit(); + } + + function handleFormFinish() { + dynamicForm.submit(); + } + + // 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 + function onDynamicFormSubmit(value: object) { + setIsLoading(true); + console.log("set loading", true); + if (initBotId) { + // 编辑提交 + console.log("submit edit", form.getFieldsValue(), value); + const updateBot: Bot = { + uuid: initBotId, + name: form.getFieldsValue().name, + description: form.getFieldsValue().description, + adapter: form.getFieldsValue().adapter, + adapter_config: value + }; + httpClient + .updateBot(initBotId, updateBot) + .then((res) => { + // TODO success toast + console.log("update bot success", res); + onFormSubmit(form.getFieldsValue()); + notification.success({ + message: "更新成功", + description: "机器人更新成功" + }); }) - // 拉取初始化表单信息 - if (initBotId) { - getBotFieldById(initBotId).then(val => { - form.setFieldsValue(val) - handleAdapterSelect(val.adapter) - dynamicForm.setFieldsValue(val.adapter_config) - }) - } else { - form.resetFields() - } - setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap) - } - - async function onCreateMode() { - - } - - function onEditMode() { - - } - - async function getBotFieldById(botId: string): Promise { - const bot = (await httpClient.getBot(botId)).bot - let botFormEntity = new BotFormEntity({ - adapter: bot.adapter, - description: bot.description, - name: bot.name, - adapter_config: bot.adapter_config + .catch(() => { + // TODO error toast + notification.error({ + message: "更新失败", + description: "机器人更新失败" + }); }) - return botFormEntity + .finally(() => { + setIsLoading(false); + form.resetFields(); + dynamicForm.resetFields(); + }); + } else { + // 创建提交 + console.log("submit create", form.getFieldsValue(), value); + const newBot: Bot = { + name: form.getFieldsValue().name, + description: form.getFieldsValue().description, + adapter: form.getFieldsValue().adapter, + adapter_config: value + }; + httpClient + .createBot(newBot) + .then((res) => { + // TODO success toast + notification.success({ + message: "创建成功", + description: "机器人创建成功" + }); + console.log(res); + onFormSubmit(form.getFieldsValue()); + }) + .catch(() => { + // TODO error toast + notification.error({ + message: "创建失败", + description: "机器人创建失败" + }); + }) + .finally(() => { + setIsLoading(false); + form.resetFields(); + dynamicForm.resetFields(); + }); } + setShowDynamicForm(false); + console.log("set loading", false); + // TODO 刷新bot列表 + // TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)? + } - function handleAdapterSelect(adapterName: string) { - console.log("Select adapter: ", adapterName) - if (adapterName) { - const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName) - console.log(dynamicFormConfigList) - if (dynamicFormConfigList) { - setDynamicFormConfigList(dynamicFormConfigList) - } - setShowDynamicForm(true) - } else { - setShowDynamicForm(false) - } - } + function handleSaveButton() { + form.submit(); + } - function handleSubmitButton() { - form.submit() - } + return ( +
+ + + label={"机器人名称"} + name={"name"} + rules={[{ required: true, message: "该项为必填项哦~" }]} + > + + - function handleFormFinish(value: IBotFormEntity) { - dynamicForm.submit() - } + + label={"描述"} + name={"description"} + rules={[{ required: true, message: "该项为必填项哦~" }]} + > + + - // 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 - function onDynamicFormSubmit(value: object) { - setIsLoading(true) - console.log('setloading', true) - if (initBotId) { - // 编辑提交 - console.log('submit edit', form.getFieldsValue() ,value) - let updateBot: Bot = { - uuid: initBotId, - name: form.getFieldsValue().name, - description: form.getFieldsValue().description, - adapter: form.getFieldsValue().adapter, - adapter_config: value - } - httpClient.updateBot(initBotId, updateBot).then(res => { - // TODO success toast - console.log("update bot success", res) - onFormSubmit(form.getFieldsValue()) - notification.success({ - message: "更新成功", - description: "机器人更新成功" - }) - }).catch(err => { - // TODO error toast - notification.error({ - message: "更新失败", - description: "机器人更新失败" - }) - }).finally(() => { - setIsLoading(false) - form.resetFields() - dynamicForm.resetFields() - }) - } else { - // 创建提交 - console.log('submit create', form.getFieldsValue() ,value) - let newBot: Bot = { - name: form.getFieldsValue().name, - description: form.getFieldsValue().description, - adapter: form.getFieldsValue().adapter, - adapter_config: value - } - httpClient.createBot(newBot).then(res => { - // TODO success toast - notification.success({ - message: "创建成功", - description: "机器人创建成功" - }) - console.log(res) - onFormSubmit(form.getFieldsValue()) - }).catch(err => { - // TODO error toast - notification.error({ - message: "创建失败", - description: "机器人创建失败" - }) - }).finally(() => { - setIsLoading(false) - form.resetFields() - dynamicForm.resetFields() - }) - } - setShowDynamicForm(false) - console.log('setloading', false) - // TODO 刷新bot列表 - // TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)? - } - - function handleSaveButton() { - form.submit() - } - - return ( -
- - - label={"机器人名称"} - name={"name"} - rules={[{required: true, message: "该项为必填项哦~"}]} - > - - - - - label={"描述"} - name={"description"} - rules={[{required: true, message: "该项为必填项哦~"}]} - > - - - - - label={"平台/适配器选择"} - name={"adapter"} - rules={[{required: true, message: "该项为必填项哦~"}]} - > - { + handleAdapterSelect(value); + }} + options={adapterNameList} + /> + + + {showDynamicForm && ( + + )} + + {!initBotId && ( + + )} + {initBotId && ( + + )} + + +
+ ); +} diff --git a/web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx index 0cc76182..12813b29 100644 --- a/web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -1,107 +1,100 @@ -"use client" +"use client"; -import styles from "./HomeSidebar.module.css" -import {useEffect, useState} from "react"; -import {SidebarChild, SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChild"; -import {useRouter, usePathname, useSearchParams} from "next/navigation"; -import {sidebarConfigList} from "@/app/home/components/home-sidebar/sidbarConfigList"; +import styles from "./HomeSidebar.module.css"; +import { useEffect, useState } from "react"; +import { + SidebarChild, + SidebarChildVO +} from "@/app/home/components/home-sidebar/HomeSidebarChild"; +import { useRouter, usePathname } from "next/navigation"; +import { sidebarConfigList } from "@/app/home/components/home-sidebar/sidbarConfigList"; // TODO 侧边导航栏要加动画 export default function HomeSidebar({ - onSelectedChange + onSelectedChangeAction }: { - onSelectedChange: (sidebarChild: SidebarChildVO) => void + onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; }) { - // 路由相关 - const router = useRouter() - const pathname = usePathname(); - const searchParams = useSearchParams(); - // 路由被动变化时处理 - useEffect(() => { - handleRouteChange(pathname) - }, [pathname, searchParams]); + // 路由相关 + const router = useRouter(); + const pathname = usePathname(); + // 路由被动变化时处理 + useEffect(() => { + handleRouteChange(pathname); + }, [pathname]); - const [selectedChild, setSelectedChild] = useState(sidebarConfigList[0]) + const [selectedChild, setSelectedChild] = useState( + sidebarConfigList[0] + ); - useEffect(() => { - console.log('HomeSidebar挂载完成'); - initSelect() - return () => console.log('HomeSidebar卸载'); - }, []); + useEffect(() => { + console.log("HomeSidebar挂载完成"); + initSelect(); + return () => console.log("HomeSidebar卸载"); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + function handleChildClick(child: SidebarChildVO) { + setSelectedChild(child); + handleRoute(child); + onSelectedChangeAction(child); + } + function initSelect() { + handleChildClick(sidebarConfigList[0]); + } - function handleChildClick(child: SidebarChildVO) { - setSelectedChild(child) - handleRoute(child) - onSelectedChange(child) + function handleRoute(child: SidebarChildVO) { + console.log(child); + router.push(`${child.route}`); + } + + function handleRouteChange(pathname: string) { + // TODO 这段逻辑并不好,未来router封装好后改掉 + // 判断在home下,并且路由更改的是自己的路由子组件则更新UI + const routeList = pathname.split("/"); + if ( + routeList[1] === "home" && + sidebarConfigList.find((childConfig) => childConfig.route === pathname) + ) { + console.log("find success"); + const routeSelectChild = sidebarConfigList.find( + (childConfig) => childConfig.route === pathname + ); + if (routeSelectChild) { + setSelectedChild(routeSelectChild); + } } + } - function initSelect() { - handleChildClick(sidebarConfigList[0]) - } - - function handleRoute(child: SidebarChildVO) { - console.log(child) - router.push(`${child.route}`) - } - - function handleRouteChange(pathname: string) { - // TODO 这段逻辑并不好,未来router封装好后改掉 - // 判断在home下,并且路由更改的是自己的路由子组件则更新UI - const routeList = pathname.split('/') - if ( - routeList[1] === "home" && - sidebarConfigList.find(childConfig => - childConfig.route === pathname - ) - ) { - console.log("find success") - const routeSelectChild = sidebarConfigList.find(childConfig => - childConfig.route === pathname - ) - if (routeSelectChild) { - setSelectedChild(routeSelectChild) - } - } - } - - - return ( -
- {/* LangBot、ICON区域 */} -
- {/* icon */} -
- L -
-
- Langbot -
+ return ( +
+ {/* LangBot、ICON区域 */} +
+ {/* icon */} +
L
+
Langbot
+
+ {/* 菜单列表,后期可升级成配置驱动 */} +
+ {sidebarConfigList.map((config) => { + return ( +
{ + console.log("click:", config.id); + handleChildClick(config); + }} + > +
- {/* 菜单列表,后期可升级成配置驱动 */} -
- { - sidebarConfigList.map(config => { - return ( -
{ - console.log('click:', config.id) - handleChildClick(config) - }} - > - -
- ) - }) - } - -
-
- ); -} \ No newline at end of file + ); + })} +
+
+ ); +} diff --git a/web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx b/web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx index 448d0ab8..e1089eba 100644 --- a/web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx +++ b/web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx @@ -1,41 +1,44 @@ import styles from "./HomeSidebar.module.css"; export interface ISidebarChildVO { - id: string; - icon: string; - name: string; - route: string; + id: string; + icon: string; + name: string; + route: string; } export class SidebarChildVO { - id: string; - icon: string; - name: string; - route: string; + id: string; + icon: string; + name: string; + route: string; - constructor(props: ISidebarChildVO) { - this.id = props.id; - this.icon = props.icon; - this.name = props.name; - this.route = props.route; - } + constructor(props: ISidebarChildVO) { + this.id = props.id; + this.icon = props.icon; + this.name = props.name; + this.route = props.route; + } } - - export function SidebarChild({ - icon, - name, - isSelected, + icon, + name, + isSelected }: { - icon: string; - name: string; - isSelected: boolean; + icon: string; + name: string; + isSelected: boolean; }) { - return ( -
-
-
{name}
-
- ); -} \ No newline at end of file + return ( +
+
+
+ {icon} + {name} +
+
+ ); +} diff --git a/web_ui/src/app/home/layout.tsx b/web_ui/src/app/home/layout.tsx index 57f21bac..7985dae6 100644 --- a/web_ui/src/app/home/layout.tsx +++ b/web_ui/src/app/home/layout.tsx @@ -1,36 +1,30 @@ -"use client" +"use client"; -import '@ant-design/v5-patch-for-react-19'; -import styles from "./layout.module.css" +import "@ant-design/v5-patch-for-react-19"; +import styles from "./layout.module.css"; import HomeSidebar from "@/app/home/components/home-sidebar/HomeSidebar"; import HomeTitleBar from "@/app/home/components/home-titlebar/HomeTitleBar"; -import React, {useState} from "react"; -import {SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChild"; -import { useRouter } from 'next/navigation'; +import React, { useState } from "react"; +import { SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild"; export default function HomeLayout({ - children + children }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - const router = useRouter(); - const [title, setTitle] = useState("") - const onSelectedChange = (child: SidebarChildVO) => { - setTitle(child.name) - } + const [title, setTitle] = useState(""); + const onSelectedChange = (child: SidebarChildVO) => { + setTitle(child.name); + }; - return ( -
- -
- - {/* 主页面 */} -
- {children} -
-
-
- ) + return ( +
+ +
+ + {/* 主页面 */} +
{children}
+
+
+ ); } diff --git a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx index ccb00730..4832cc6c 100644 --- a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx +++ b/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx @@ -1,296 +1,287 @@ import styles from "@/app/home/models/LLMConfig.module.css"; -import {Button, Form, Input, Select, SelectProps, Space, Modal} from "antd"; -import {ICreateLLMField} from "@/app/home/models/ICreateLLMField"; -import {useEffect, useState} from "react"; -import {IChooseRequesterEntity} from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; +import { Button, Form, Input, Select, SelectProps, Space, Modal } from "antd"; +import { ICreateLLMField } from "@/app/home/models/ICreateLLMField"; +import { useEffect, useState } from "react"; +import { IChooseRequesterEntity } from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; import { httpClient } from "@/app/infra/http/HttpClient"; -import {LLMModel} from "@/app/infra/api/api-types"; -import {UUID} from "uuidjs"; +import { LLMModel } from "@/app/infra/api/api-types"; +import { UUID } from "uuidjs"; export default function LLMForm({ - editMode, - initLLMId, - onFormSubmit, - onFormCancel, - onLLMDeleted, + editMode, + initLLMId, + onFormSubmit, + onFormCancel, + onLLMDeleted }: { - editMode: boolean; - initLLMId?: string; - onFormSubmit: (value: ICreateLLMField) => void; - onFormCancel: (value: ICreateLLMField) => void; - onLLMDeleted: () => void; + editMode: boolean; + initLLMId?: string; + onFormSubmit: (value: ICreateLLMField) => void; + onFormCancel: (value: ICreateLLMField) => void; + onLLMDeleted: () => void; }) { - const [form] = Form.useForm(); - const extraOptions: SelectProps['options'] = [] - const [initValue, setInitValue] = useState() - const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false) - const abilityOptions: SelectProps['options'] = [ - { - label: '函数调用', - value: 'func_call', - }, - { - label: '图像识别', - value: 'vision', - }, - ]; - const [requesterNameList, setRequesterNameList] = useState([]) - - useEffect(() => { - initLLMModelFormComponent() - if (editMode && initLLMId) { - getLLMConfig(initLLMId).then(val => { - form.setFieldsValue(val) - }) - } else { - form.resetFields() - } - }, []) - - async function initLLMModelFormComponent() { - const requesterNameList = await httpClient.getProviderRequesters() - setRequesterNameList(requesterNameList.requesters.map(item => { - return { - label: item.label.zh_CN, - value: item.name - } - })) + const [form] = Form.useForm(); + const extraOptions: SelectProps["options"] = []; + const [initValue] = useState(); + const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); + const abilityOptions: SelectProps["options"] = [ + { + label: "函数调用", + value: "func_call" + }, + { + label: "图像识别", + value: "vision" } + ]; + const [requesterNameList, setRequesterNameList] = useState< + IChooseRequesterEntity[] + >([]); - async function getLLMConfig(id: string): Promise { + useEffect(() => { + initLLMModelFormComponent(); + if (editMode && initLLMId) { + getLLMConfig(initLLMId).then((val) => { + form.setFieldsValue(val); + }); + } else { + form.resetFields(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - const llmModel = await httpClient.getProviderLLMModel(id) - - let fakeExtraArgs = [] - const extraArgs = llmModel.model.extra_args as Record - for (const key in extraArgs) { - fakeExtraArgs.push(`${key}:${extraArgs[key]}`) - } + async function initLLMModelFormComponent() { + const requesterNameList = await httpClient.getProviderRequesters(); + setRequesterNameList( + requesterNameList.requesters.map((item) => { return { - name: llmModel.model.name, - model_provider: llmModel.model.requester, - url: llmModel.model.requester_config?.base_url, - api_key: llmModel.model.api_keys[0], - abilities: llmModel.model.abilities, - extra_args: fakeExtraArgs, - } - } - - function handleFormSubmit(value: ICreateLLMField) { - if (editMode) { - // 暂不支持更改模型 - // onSaveEdit(value) - } else { - onCreateLLM(value) - } - form.resetFields() - } - - function onSaveEdit(value: ICreateLLMField) { - const requestParam: LLMModel = { - uuid: UUID.generate(), - name: value.name, - description: "", - requester: value.model_provider, - requester_config: { - "base_url": value.url, - "timeout": 120 - }, - extra_args: value.extra_args, - api_keys: [value.api_key], - abilities: value.abilities, - // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', - // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + label: item.label.zh_CN, + value: item.name }; - httpClient.createProviderLLMModel(requestParam).then(r => console.log(r)) + }) + ); + } + + async function getLLMConfig(id: string): Promise { + const llmModel = await httpClient.getProviderLLMModel(id); + + const fakeExtraArgs = []; + const extraArgs = llmModel.model.extra_args as Record; + for (const key in extraArgs) { + fakeExtraArgs.push(`${key}:${extraArgs[key]}`); } + return { + name: llmModel.model.name, + model_provider: llmModel.model.requester, + url: llmModel.model.requester_config?.base_url, + api_key: llmModel.model.api_keys[0], + abilities: llmModel.model.abilities, + extra_args: fakeExtraArgs + }; + } - function onCreateLLM(value: ICreateLLMField) { - console.log("create llm", value) - const requestParam: LLMModel = { - uuid: UUID.generate(), - name: value.name, - description: "", - requester: value.model_provider, - requester_config: { - "base_url": value.url, - "timeout": 120 - }, - extra_args: value.extra_args, - api_keys: [value.api_key], - abilities: value.abilities, - // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', - // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', - }; - httpClient.createProviderLLMModel(requestParam).then(r => { - onFormSubmit(value) - }) + function handleFormSubmit(value: ICreateLLMField) { + if (editMode) { + // 暂不支持更改模型 + // onSaveEdit(value) + } else { + onCreateLLM(value); } + form.resetFields(); + } - function handleAbilitiesChange() { + // function onSaveEdit(value: ICreateLLMField) { + // const requestParam: LLMModel = { + // uuid: UUID.generate(), + // name: value.name, + // description: "", + // requester: value.model_provider, + // requester_config: { + // "base_url": value.url, + // "timeout": 120 + // }, + // extra_args: value.extra_args, + // api_keys: [value.api_key], + // abilities: value.abilities, + // // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + // // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + // }; + // httpClient.createProviderLLMModel(requestParam).then(r => console.log(r)) + // } + function onCreateLLM(value: ICreateLLMField) { + console.log("create llm", value); + const requestParam: LLMModel = { + uuid: UUID.generate(), + name: value.name, + description: "", + requester: value.model_provider, + requester_config: { + base_url: value.url, + timeout: 120 + }, + extra_args: value.extra_args, + api_keys: [value.api_key], + abilities: value.abilities + // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', + }; + httpClient.createProviderLLMModel(requestParam).then(() => { + onFormSubmit(value); + }); + } + + function handleAbilitiesChange() {} + + function deleteModel() { + if (initLLMId) { + httpClient.deleteProviderLLMModel(initLLMId).then(() => { + onLLMDeleted(); + }); } + } - function deleteModel() { - if (initLLMId) { - httpClient.deleteProviderLLMModel(initLLMId).then(res => { - onLLMDeleted() - }) - } - } - - return ( -
- setShowDeleteConfirmModal(false)} - footer={ -
- - -
- } - + return ( +
+ setShowDeleteConfirmModal(false)} + footer={ +
+ +
+ } + > + 你确定要删除这个模型吗? +
+ + + label={"模型名称"} + name={"name"} + rules={[{ required: true, message: "该项为必填项哦~" }]} + > + + + + + label={"模型供应商"} + name={"model_provider"} + rules={[{ required: true, message: "该项为必填项哦~" }]} + > + + + + + label={"API Key"} + name={"api_key"} + rules={[{ required: true, message: "该项为必填项哦~" }]} + > + + + + label={"开启能力"} name={"abilities"}> + + + + + + {!editMode && ( + + )} + {editMode && ( + + )} + - } - { - editMode && - - } - - - - -
- - ) -} \ No newline at end of file + 取消 + + + + +
+ ); +} diff --git a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts index a2038c35..4f9770dd 100644 --- a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts +++ b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts @@ -1,20 +1,19 @@ -import {DynamicFormItemConfig} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; +import { DynamicFormItemConfig } from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; export interface IPipelineChildFormEntity { - name: string; - label: string; - formItems: DynamicFormItemConfig[] + name: string; + label: string; + formItems: DynamicFormItemConfig[]; } export class PipelineChildFormEntity implements IPipelineChildFormEntity { - formItems: DynamicFormItemConfig[]; - label: string; - name: string; + formItems: DynamicFormItemConfig[]; + label: string; + name: string; - constructor(props: IPipelineChildFormEntity) { - this.form = props.form; - this.label = props.label; - this.name = props.name; - this.formItems = props.formItems; - } -} \ No newline at end of file + constructor(props: IPipelineChildFormEntity) { + this.label = props.label; + this.name = props.name; + this.formItems = props.formItems; + } +} diff --git a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index 5e81219f..32ca5405 100644 --- a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -1,633 +1,604 @@ -import {Form, Button, Switch, Select, Input, InputNumber, SelectProps} from "antd"; -import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'; -import {useEffect, useState} from "react"; -import styles from "./pipelineFormStyle.module.css" -import {httpClient} from "@/app/infra/http/HttpClient"; -import {LLMModel, Pipeline} from "@/app/infra/api/api-types"; -import {LLMCardVO} from "@/app/home/models/component/llm-card/LLMCardVO"; -import {UUID} from "uuidjs"; +import { + Form, + Button, + Switch, + Select, + Input, + InputNumber, + SelectProps +} from "antd"; +import { CaretLeftOutlined, CaretRightOutlined } from "@ant-design/icons"; +import { useEffect, useState } from "react"; +import styles from "./pipelineFormStyle.module.css"; +import { httpClient } from "@/app/infra/http/HttpClient"; +import { LLMModel, Pipeline } from "@/app/infra/api/api-types"; +import { UUID } from "uuidjs"; export default function PipelineFormComponent({ - onFinish, - onCancel, + onFinish }: { - onFinish: () => void; - onCancel: () => void; + onFinish: () => void; }) { - const [nowFormIndex, setNowFormIndex] = useState(0) - const [nowAIRunner, setNowAIRunner] = useState("") - const [llmModelList, setLlmModelList] = useState([]) - // 这里不好,可以改成enum等 - const formLabelList: FormLabel[] = [ - {label: "基础", name: "basic"}, - {label: "AI能力", name: "ai"}, - {label: "触发条件", name: "trigger"}, - {label: "安全能力", name: "safety"}, - {label: "输出处理", name: "output"}, - ] - const [basicForm] = Form.useForm() - const [aiForm] = Form.useForm() - const [triggerForm] = Form.useForm() - const [safetyForm] = Form.useForm() - const [outputForm] = Form.useForm() + const [nowFormIndex, setNowFormIndex] = useState(0); + const [nowAIRunner, setNowAIRunner] = useState(""); + const [llmModelList, setLlmModelList] = useState([]); + // 这里不好,可以改成enum等 + const formLabelList: FormLabel[] = [ + { label: "基础", name: "basic" }, + { label: "AI能力", name: "ai" }, + { label: "触发条件", name: "trigger" }, + { label: "安全能力", name: "safety" }, + { label: "输出处理", name: "output" } + ]; + const [basicForm] = Form.useForm(); + const [aiForm] = Form.useForm(); + const [triggerForm] = Form.useForm(); + const [safetyForm] = Form.useForm(); + const [outputForm] = Form.useForm(); - useEffect(() => { - getLLMModelList() - }, []) + useEffect(() => { + getLLMModelList(); + }, []); - function getLLMModelList() { - httpClient.getProviderLLMModels().then((resp) => { - setLlmModelList(resp.models.map((model: LLMModel) => { - return { - value: model.uuid, - label: model.name, - } - })) - }).catch((err) => { - console.error("get LLM model list error", err) - }) + function getLLMModelList() { + httpClient + .getProviderLLMModels() + .then((resp) => { + setLlmModelList( + resp.models.map((model: LLMModel) => { + return { + value: model.uuid, + label: model.name + }; + }) + ); + }) + .catch((err) => { + console.error("get LLM model list error", err); + }); + } + + function getNowFormLabel() { + return formLabelList[nowFormIndex]; + } + + function getPreFormLabel(): undefined | FormLabel { + if (nowFormIndex !== undefined && nowFormIndex > 0) { + return formLabelList[nowFormIndex - 1]; + } else { + return undefined; } + } - function getNowFormLabel() { - return formLabelList[nowFormIndex] + function getNextFormLabel(): undefined | FormLabel { + if (nowFormIndex !== undefined && nowFormIndex < formLabelList.length - 1) { + return formLabelList[nowFormIndex + 1]; + } else { + return undefined; } + } - - function getPreFormLabel(): undefined | FormLabel { - if (nowFormIndex !== undefined && nowFormIndex > 0) { - return formLabelList[nowFormIndex - 1] - } else { - return undefined - } + function addFormLabelIndex() { + if (nowFormIndex < formLabelList.length - 1) { + setNowFormIndex(nowFormIndex + 1); } + } - function getNextFormLabel(): undefined | FormLabel { - if (nowFormIndex !== undefined && nowFormIndex < formLabelList.length - 1) { - return formLabelList[nowFormIndex + 1] - } else { - return undefined - } + function reduceFormLabelIndex() { + if (nowFormIndex > 0) { + setNowFormIndex(nowFormIndex - 1); } + } - function addFormLabelIndex() { - if (nowFormIndex < formLabelList.length - 1) { - setNowFormIndex(nowFormIndex + 1) - } - } + function handleCommit() { + Promise.all([ + basicForm.validateFields(), + aiForm.validateFields(), + triggerForm.validateFields(), + safetyForm.validateFields(), + outputForm.validateFields() + ]) + .then(() => { + const pipeline = assembleForm(); + httpClient.createPipeline(pipeline).then(() => onFinish()); + }) + .catch((e) => { + console.error(e); + }); + } - function reduceFormLabelIndex() { - if (nowFormIndex > 0) { - setNowFormIndex(nowFormIndex - 1) - } - } + // TODO 类型混乱,需要优化 + function assembleForm(): Pipeline { + console.log("basicForm:", basicForm.getFieldsValue()); + console.log("aiForm:", aiForm.getFieldsValue()); + console.log("triggerForm:", triggerForm.getFieldsValue()); + console.log("safetyForm:", safetyForm.getFieldsValue()); + console.log("outputForm:", outputForm.getFieldsValue()); + const config: object = { + ai: aiForm.getFieldsValue(), + trigger: triggerForm.getFieldsValue(), + safety: safetyForm.getFieldsValue(), + output: outputForm.getFieldsValue() + }; - function handleCommit() { - Promise.all([ - basicForm.validateFields(), - aiForm.validateFields(), - triggerForm.validateFields(), - safetyForm.validateFields(), - outputForm.validateFields(), - ]).then(() => { - const pipeline = assembleForm() - httpClient.createPipeline(pipeline).then(r => - onFinish() - ) - }).catch(e => { - console.error(e) - }) - } + return { + config, + created_at: "", + description: basicForm.getFieldsValue().description, + for_version: "", + name: basicForm.getFieldsValue().name, + stages: [], + updated_at: "", + uuid: UUID.generate() + }; + } - // TODO 类型混乱,需要优化 - function assembleForm(): Pipeline { - console.log("basicForm:", basicForm.getFieldsValue()) - console.log("aiForm:", aiForm.getFieldsValue()) - console.log("triggerForm:", triggerForm.getFieldsValue()) - console.log("safetyForm:", safetyForm.getFieldsValue()) - console.log("outputForm:", outputForm.getFieldsValue()) - const config: object = { - ai: aiForm.getFieldsValue(), - trigger: triggerForm.getFieldsValue(), - safety: safetyForm.getFieldsValue(), - output: outputForm.getFieldsValue(), - } - - return { - config, - created_at: "", - description: basicForm.getFieldsValue().description, - for_version: "", - name: basicForm.getFieldsValue().name, - stages: [], - updated_at: "", - uuid: UUID.generate(), - } - } - - - return ( -
+

{getNowFormLabel().label}

+
+ -

- {getNowFormLabel().label} -

- - - - + +
- - - -
- {/* AI能力表单 ai */} -
+ + +
+ {/* AI能力表单 ai */} +
+ {/* Runner 配置区块 */} +
运行器
+ + setNowAIRunner(value)} - /> - - - {/* 内置 Agent 配置区块 */} + - - - - - {/* TODO 这里要做转换处理 */} - - - - - + required: true } - {/* Dify 服务 API 区块 */} - { - nowAIRunner === "dify-service-api" && - <> -
配置Dify服务API
- - - - - ...\<\/think\>", value: "plain"}, - {label: "原始", value: "original"}, - {label: "移除", value: "remove"} - ]} - /> - - - } - {/* 阿里云百炼区块 */} - { - nowAIRunner === "dashscope-app-api" && - <> -
配置阿里云百炼平台 API
- - - - - - - - } -
- - {/* 触发条件表单 trigger */} -
- {/* 群响应规则块 */} -
群响应规则
- - - - - - - - - -
访问控制
- - - - - - - - - - - - - - - - - {/* 速率限制块 rate-limit */} -
速率限制
- - - - - - - - - - - - - - {/* 强制延迟区块 */} -
强制延迟
- - - - - - - - {/* 杂项区块 */} -
杂项
- - - - - - - - - - - - -
- -
- - - - + + + + ...\<\/think\>", value: "plain" }, + { label: "原始", value: "original" }, + { label: "移除", value: "remove" } + ]} + /> + + + )} + {/* 阿里云百炼区块 */} + {nowAIRunner === "dashscope-app-api" && ( + <> +
+ 配置阿里云百炼平台 API
+ + + + + + + + )} + -
- ) -} + {/* 触发条件表单 trigger */} +
+ {/* 群响应规则块 */} +
群响应规则
+ + + + + + + + + +
访问控制
+ + + -} + + + + + + + + + + + + + {/* 速率限制块 rate-limit */} +
速率限制
+ + + + + + + + + + + + + + {/* 强制延迟区块 */} +
强制延迟
+ + + + + + + + {/* 杂项区块 */} +
杂项
+ + + + + + + + + + + + +
+ +
+ + + + +
+
+ ); } interface FormLabel { - label: string, - name: string, + label: string; + name: string; } - -interface LLMSelectList { - label: string, - name: string, -} \ No newline at end of file diff --git a/web_ui/src/app/home/pipelines/page.tsx b/web_ui/src/app/home/pipelines/page.tsx index e6a8a331..fc1036f3 100644 --- a/web_ui/src/app/home/pipelines/page.tsx +++ b/web_ui/src/app/home/pipelines/page.tsx @@ -1,68 +1,78 @@ -"use client" -import {Modal} from "antd"; -import {useState, useEffect} from "react"; +"use client"; +import { Modal } from "antd"; +import { useState, useEffect } from "react"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import PipelineFormComponent from "./components/pipeline-form/PipelineFormComponent"; -import {httpClient} from "@/app/infra/http/HttpClient"; -import {PipelineCardVO} from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; +import { httpClient } from "@/app/infra/http/HttpClient"; +import { PipelineCardVO } from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; import PipelineCardComponent from "@/app/home/pipelines/components/pipeline-card/PipelineCardComponent"; export default function PluginConfigPage() { - const [modalOpen, setModalOpen] = useState(false); - const [isEditForm, setIsEditForm] = useState(false) - const [pipelineList, setPipelineList] = useState([]) + const [modalOpen, setModalOpen] = useState(false); + const [isEditForm] = useState(false); + const [pipelineList, setPipelineList] = useState([]); - useEffect(() => { - getPipelines() - }, []) + useEffect(() => { + getPipelines(); + }, []); + function getPipelines() { + httpClient + .getPipelines() + .then((value) => { + const pipelineList = value.pipelines.map((pipeline) => { + return new PipelineCardVO({ + createTime: pipeline.created_at, + description: pipeline.description, + id: pipeline.uuid, + name: pipeline.name, + version: pipeline.for_version + }); + }); + setPipelineList(pipelineList); + }) + .catch((error) => { + // TODO toast + console.log(error); + }); + } - function getPipelines() { - httpClient.getPipelines().then(value => { - const pipelineList = value.pipelines.map(pipeline => { - return new PipelineCardVO({ - createTime: pipeline.created_at, - description: pipeline.description, - id: pipeline.uuid, - name: pipeline.name, - version: pipeline.for_version - }) - }) - setPipelineList(pipelineList) - }).catch(error => { - // TODO toast - console.log(error) - }) - } + return ( +
+ setModalOpen(false)} + onCancel={() => setModalOpen(false)} + width={700} + footer={null} + > + { + getPipelines(); + setModalOpen(false); + }} + /> + - return ( + {pipelineList.length > 0 && (
- setModalOpen(false)} - onCancel={() => setModalOpen(false)} - width={700} - footer={null} - > - { - getPipelines() - setModalOpen(false) - }} - onCancel={() => {}}/> - - - { - pipelineList.length > 0 && -
- {pipelineList.map(pipeline => { - return - })} -
- } - {setModalOpen(true)}}/> + {pipelineList.map((pipeline) => { + return ( + + ); + })}
- ); + )} + { + setModalOpen(true); + }} + /> +
+ ); } diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 8aa8b8f0..1b5cea43 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -1,104 +1,107 @@ -"use client" +"use client"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; -import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; -import {useEffect, useState} from "react"; +import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; +import { useEffect, useState } from "react"; import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent"; import styles from "@/app/home/plugins/plugins.module.css"; -import {Modal, Input} from "antd"; -import {GithubOutlined} from "@ant-design/icons"; -import {httpClient} from "@/app/infra/http/HttpClient"; +import { Modal, Input } from "antd"; +import { GithubOutlined } from "@ant-design/icons"; +import { httpClient } from "@/app/infra/http/HttpClient"; -export default function PluginInstalledComponent () { - const [pluginList, setPluginList] = useState([]) - const [modalOpen, setModalOpen] = useState(false) - const [githubURL, setGithubURL] = useState("") +export default function PluginInstalledComponent() { + const [pluginList, setPluginList] = useState([]); + const [modalOpen, setModalOpen] = useState(false); + const [githubURL, setGithubURL] = useState(""); + useEffect(() => { + initData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - useEffect(() => { - initData() - }, []) + function initData() { + getPluginList(); + } - function initData() { - getPluginList() - } - - function getPluginList() { - httpClient.getPlugins().then((value) => { - setPluginList(value.plugins.map(plugin => { - return new PluginCardVO({ - author: plugin.author, - description: plugin.description.zh_CN, - handlerCount: 0, - name: plugin.name, - version: plugin.version, - isInitialized: plugin.status === "initialized", - }) - })) + function getPluginList() { + httpClient.getPlugins().then((value) => { + setPluginList( + value.plugins.map((plugin) => { + return new PluginCardVO({ + author: plugin.author, + description: plugin.description.zh_CN, + handlerCount: 0, + name: plugin.name, + version: plugin.version, + isInitialized: plugin.status === "initialized" + }); }) - } + ); + }); + } - function handleModalConfirm() { - installPlugin(githubURL) - setModalOpen(false) - } + function handleModalConfirm() { + installPlugin(githubURL); + setModalOpen(false); + } - function installPlugin(url: string) { - httpClient.installPluginFromGithub(url).then(res => { - // 安装后重新拉取 - getPluginList() - }).catch(err => { - console.log("error when install plugin:", err) - }) - } - return ( -
- - - 从 GitHub 安装插件 -
- } - centered - open={modalOpen} - onOk={() => handleModalConfirm()} - onCancel={() => setModalOpen(false)} - width={500} - destroyOnClose={true} - > -
-
- 目前仅支持从 GitHub 安装 -
- setGithubURL(e.target.value)} - /> -
- - { - pluginList.map((vo, index) => { - return
- -
- }) - } - { - setModalOpen(true) - }} + function installPlugin(url: string) { + httpClient + .installPluginFromGithub(url) + .then(() => { + // 安装后重新拉取 + getPluginList(); + }) + .catch((err) => { + console.log("error when install plugin:", err); + }); + } + return ( +
+ + + 从 GitHub 安装插件 +
+ } + centered + open={modalOpen} + onOk={() => handleModalConfirm()} + onCancel={() => setModalOpen(false)} + width={500} + destroyOnClose={true} + > +
+
目前仅支持从 GitHub 安装
+ setGithubURL(e.target.value)} + />
- ) + + {pluginList.map((vo, index) => { + return ( +
+ +
+ ); + })} + { + setModalOpen(true); + }} + /> +
+ ); } diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index b8a2c784..fcae4000 100644 --- a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -1,68 +1,65 @@ -import styles from "./pluginCard.module.css" -import {PluginCardVO} from "@/app/home/plugins/plugin-installed/PluginCardVO"; -import {GithubOutlined, LinkOutlined, ToolOutlined} from '@ant-design/icons'; -import {Switch, Tag} from 'antd' -import {useState} from "react"; -import {httpClient} from "@/app/infra/http/HttpClient"; +import styles from "./pluginCard.module.css"; +import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; +import { GithubOutlined, LinkOutlined, ToolOutlined } from "@ant-design/icons"; +import { Switch, Tag } from "antd"; +import { useState } from "react"; +import { httpClient } from "@/app/infra/http/HttpClient"; export default function PluginCardComponent({ - cardVO + cardVO }: { - cardVO: PluginCardVO + cardVO: PluginCardVO; }) { - const [initialized, setInitialized] = useState(cardVO.isInitialized) - const [switchEnable, setSwitchEnable] = useState(true) + const [initialized, setInitialized] = useState(cardVO.isInitialized); + const [switchEnable, setSwitchEnable] = useState(true); - function handleEnable() { - setSwitchEnable(false) - httpClient.togglePlugin(cardVO.author, cardVO.name, !initialized).then(result => { - setInitialized(!initialized) - }).catch(err => { - console.log("error: ", err) - }).finally(() => { - setSwitchEnable(true) - }) - } - return ( -
- {/* header */} -
- {/* left author */} -
{cardVO.author}
- {/* right icon & version */} -
- - v{cardVO.version} -
-
- {/* content */} -
-
{cardVO.name}
-
{cardVO.description}
-
- {/* footer */} -
-
-
- - 1 -
- -
- - -
+ function handleEnable() { + setSwitchEnable(false); + httpClient + .togglePlugin(cardVO.author, cardVO.name, !initialized) + .then(() => { + setInitialized(!initialized); + }) + .catch((err) => { + console.log("error: ", err); + }) + .finally(() => { + setSwitchEnable(true); + }); + } + return ( +
+ {/* header */} +
+ {/* left author */} +
{cardVO.author}
+ {/* right icon & version */} +
+ + v{cardVO.version}
- ); +
+ {/* content */} +
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */} +
+
+
+ + 1 +
+ +
+ + +
+
+ ); } diff --git a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index f7ba31be..d01aeb6e 100644 --- a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -1,79 +1,87 @@ -"use client" +"use client"; -import {useCallback, useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import styles from "@/app/home/plugins/plugins.module.css"; -import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; +import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; -import {Input, Pagination} from "antd"; -import {debounce} from "lodash" -import {httpClient, spaceClient} from "@/app/infra/http/HttpClient"; +import { Input, Pagination } from "antd"; +import { spaceClient } from "@/app/infra/http/HttpClient"; -export default function PluginMarketComponent () { - const [marketPluginList, setMarketPluginList] = useState([]) - const [totalCount, setTotalCount] = useState(0) - const [nowPage, setNowPage] = useState(1) - const [searchKeyword, setSearchKeyword] = useState("") +export default function PluginMarketComponent() { + const [marketPluginList, setMarketPluginList] = useState< + PluginMarketCardVO[] + >([]); + const [totalCount, setTotalCount] = useState(0); + const [nowPage, setNowPage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(""); - useEffect(() => { - initData() - }, []) + useEffect(() => { + initData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - function initData() { - getPluginList() - } + function initData() { + getPluginList(); + } - function onInputSearchKeyword(keyword: string) { - // 这里记得加防抖,暂时没加 - setSearchKeyword(keyword) - setNowPage(1) - getPluginList(1, keyword) - } + function onInputSearchKeyword(keyword: string) { + // 这里记得加防抖,暂时没加 + setSearchKeyword(keyword); + setNowPage(1); + getPluginList(1, keyword); + } + function getPluginList( + page: number = nowPage, + keyword: string = searchKeyword + ) { + spaceClient.getMarketPlugins(page, 10, keyword).then((res) => { + setMarketPluginList( + res.plugins.map( + (marketPlugin) => + new PluginMarketCardVO({ + author: marketPlugin.author, + description: marketPlugin.description, + githubURL: marketPlugin.repository, + name: marketPlugin.name, + pluginId: String(marketPlugin.ID), + starCount: marketPlugin.stars + }) + ) + ); + setTotalCount(res.total); + console.log("market plugins:", res); + }); + } - function getPluginList(page: number = nowPage, keyword: string = searchKeyword) { - spaceClient.getMarketPlugins(page, 10, keyword).then(res => { - setMarketPluginList(res.plugins.map(marketPlugin => new PluginMarketCardVO({ - author: marketPlugin.author, - description: marketPlugin.description, - githubURL: marketPlugin.repository, - name: marketPlugin.name, - pluginId: String(marketPlugin.ID), - starCount: marketPlugin.stars, - }))) - setTotalCount(res.total) - console.log("market plugins:", res) - }) - } - - return ( -
- onInputSearchKeyword(e.target.value)} - /> -
- { - marketPluginList.map((vo, index) => { - return
- -
- }) - } + return ( +
+ onInputSearchKeyword(e.target.value)} + /> +
+ {marketPluginList.map((vo, index) => { + return ( +
+
- { - setNowPage(pageNumber) - getPluginList(pageNumber) - }} - /> -
- - ) + ); + })} +
+ { + setNowPage(pageNumber); + getPluginList(pageNumber); + }} + /> +
+ ); } diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web_ui/src/app/infra/api/api-types/index.ts index 5059529d..f3113468 100644 --- a/web_ui/src/app/infra/api/api-types/index.ts +++ b/web_ui/src/app/infra/api/api-types/index.ts @@ -1,204 +1,217 @@ export interface ApiResponse { - code: number; - data: T; - msg: string; + code: number; + data: T; + msg: string; } export interface I18nText { - en_US: string; - zh_CN: string; + en_US: string; + zh_CN: string; } export interface AsyncTaskCreatedResp { - task_id: number; + task_id: number; } export interface ApiRespProviderRequesters { - requesters: Requester[]; + requesters: Requester[]; } export interface ApiRespProviderRequester { - requester: Requester; + requester: Requester; } export interface Requester { - name: string; - label: I18nText; - description: I18nText; - icon?: string; - spec: object; + name: string; + label: I18nText; + description: I18nText; + icon?: string; + spec: object; } export interface ApiRespProviderLLMModels { - models: LLMModel[]; + models: LLMModel[]; } export interface ApiRespProviderLLMModel { - model: LLMModel; + model: LLMModel; } export interface LLMModel { - name: string; - description: string; - uuid: string; - requester: string; - requester_config: object; - extra_args: object; - api_keys: string[]; - abilities: string[]; - // created_at: string; - // updated_at: string; + name: string; + description: string; + uuid: string; + requester: string; + requester_config: { + base_url: string; + timeout: number; + }; + extra_args: object; + api_keys: string[]; + abilities: string[]; + // created_at: string; + // updated_at: string; } export interface ApiRespPipelines { - pipelines: Pipeline[]; + pipelines: Pipeline[]; } export interface ApiRespPipeline { - pipeline: Pipeline; + pipeline: Pipeline; } export interface Pipeline { - uuid: string; - name: string; - description: string; - for_version: string; - config: object; - stages: string[]; - created_at: string; - updated_at: string; + uuid: string; + name: string; + description: string; + for_version: string; + config: object; + stages: string[]; + created_at: string; + updated_at: string; } export interface ApiRespPlatformAdapters { - adapters: Adapter[]; + adapters: Adapter[]; } export interface ApiRespPlatformAdapter { - adapter: Adapter; + adapter: Adapter; } export interface Adapter { - name: string; - label: I18nText; - description: I18nText; - icon?: string; - spec: object; + name: string; + label: I18nText; + description: I18nText; + icon?: string; + spec: { + config: AdapterSpecConfig[]; + }; +} + +export interface AdapterSpecConfig { + default: string | number | boolean | Array; + label: I18nText; + name: string; + required: boolean; + type: string; } export interface ApiRespPlatformBots { - bots: Bot[]; + bots: Bot[]; } export interface ApiRespPlatformBot { - bot: Bot; + bot: Bot; } export interface Bot { - uuid?: string; - name: string; - description: string; - enable?: boolean; - adapter: string; - adapter_config: object; - use_pipeline_name?: string; - use_pipeline_uuid?: string; - created_at?: string; - updated_at?: string; + uuid?: string; + name: string; + description: string; + enable?: boolean; + adapter: string; + adapter_config: object; + use_pipeline_name?: string; + use_pipeline_uuid?: string; + created_at?: string; + updated_at?: string; } // plugins export interface ApiRespPlugins { - plugins: Plugin[]; + plugins: Plugin[]; } export interface ApiRespPlugin { - plugin: Plugin; + plugin: Plugin; } export interface Plugin { - author: string; - name: string; - description: I18nText; - label: I18nText; - version: string; - enabled: boolean; - priority: number; - status: string; - tools: object[]; - event_handlers: object; - main_file: string; - pkg_path: string; - repository: string; - config_schema: object; + author: string; + name: string; + description: I18nText; + label: I18nText; + version: string; + enabled: boolean; + priority: number; + status: string; + tools: object[]; + event_handlers: object; + main_file: string; + pkg_path: string; + repository: string; + config_schema: object; } export interface ApiRespPluginConfig { - config: object; + config: object; } export interface PluginReorderElement { - author: string; - name: string; - priority: number; + author: string; + name: string; + priority: number; } // system export interface ApiRespSystemInfo { - debug: boolean; - version: string; + debug: boolean; + version: string; } export interface ApiRespAsyncTasks { - tasks: AsyncTask[]; + tasks: AsyncTask[]; } export interface ApiRespAsyncTask { - task: AsyncTask; + task: AsyncTask; } export interface AsyncTaskRuntimeInfo { - done: boolean; - exception?: string; - result?: object; - state: string; + done: boolean; + exception?: string; + result?: object; + state: string; } export interface AsyncTaskTaskContext { - current_action: string; - log: string; + current_action: string; + log: string; } export interface AsyncTask { - id: number; - kind: string; - name: string; - task_type: string; // system or user - runtime: AsyncTaskRuntimeInfo; - task_context: AsyncTaskTaskContext; + id: number; + kind: string; + name: string; + task_type: string; // system or user + runtime: AsyncTaskRuntimeInfo; + task_context: AsyncTaskTaskContext; } export interface ApiRespUserToken { - token: string; + token: string; } export interface MarketPlugin { - ID: number - CreatedAt: string // ISO 8601 格式日期 - UpdatedAt: string - DeletedAt: string | null - name: string - author: string - description: string - repository: string // GitHub 仓库路径 - artifacts_path: string - stars: number - downloads: number - status: "initialized" | "mounted" // 可根据实际状态值扩展联合类型 - synced_at: string - pushed_at: string // 最后一次代码推送时间 + ID: number; + CreatedAt: string; // ISO 8601 格式日期 + UpdatedAt: string; + DeletedAt: string | null; + name: string; + author: string; + description: string; + repository: string; // GitHub 仓库路径 + artifacts_path: string; + stars: number; + downloads: number; + status: "initialized" | "mounted"; // 可根据实际状态值扩展联合类型 + synced_at: string; + pushed_at: string; // 最后一次代码推送时间 } export interface MarketPluginResponse { - plugins: MarketPlugin[] - total: number + plugins: MarketPlugin[]; + total: number; } diff --git a/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts b/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts index 41a1b8dc..026d1a07 100644 --- a/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts +++ b/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts @@ -1,47 +1,46 @@ export interface GetMetaDataResponse { - configs: Config[] + configs: Config[]; } - interface Label { - en_US: string; - zh_CN: string; + en_US: string; + zh_CN: string; } interface Option { - label: Label; - name: string; + label: Label; + name: string; } interface ConfigItem { - default?: boolean | Array | number | string; - description?: Label; - items?: { - type?: string; - properties?: { - [key: string]: { - type: string; - default?: any; - }; - }; + default?: boolean | Array | number | string; + description?: Label; + items?: { + type?: string; + properties?: { + [key: string]: { + type: string; + default?: object | string; + }; }; - label: Label; - name: string; - options?: Option[]; - required: boolean; - scope?: string; - type: string; + }; + label: Label; + name: string; + options?: Option[]; + required: boolean; + scope?: string; + type: string; } interface Stage { - config: ConfigItem[]; - description?: Label; - label: Label; - name: string; + config: ConfigItem[]; + description?: Label; + label: Label; + name: string; } interface Config { - label: Label; - name: string; - stages: Stage[]; + label: Label; + name: string; + stages: Stage[]; } diff --git a/web_ui/src/app/login/page.tsx b/web_ui/src/app/login/page.tsx index c40274ee..f6ce8bea 100644 --- a/web_ui/src/app/login/page.tsx +++ b/web_ui/src/app/login/page.tsx @@ -1,194 +1,204 @@ -'use client'; -import { Button, Input, Form, Checkbox, Divider } from 'antd'; -import {GoogleOutlined, AppleOutlined, LockOutlined, UserOutlined, QqCircleFilled, QqOutlined} from '@ant-design/icons'; -import styles from './login.module.css'; -import {useEffect, useState} from 'react'; - - -import {httpClient} from "@/app/infra/http/HttpClient"; -import '@ant-design/v5-patch-for-react-19'; -import {useRouter} from "next/navigation"; +"use client"; +import { Button, Input, Form, Checkbox, Divider } from "antd"; +import { + GoogleOutlined, + LockOutlined, + UserOutlined, + QqOutlined +} from "@ant-design/icons"; +import styles from "./login.module.css"; +import { useEffect, useState } from "react"; +import { httpClient } from "@/app/infra/http/HttpClient"; +import "@ant-design/v5-patch-for-react-19"; +import { useRouter } from "next/navigation"; export default function Home() { - const router = useRouter(); - const [form] = Form.useForm(); - const [rememberMe, setRememberMe] = useState(false); - const [isRegisterMode, setIsRegisterMode] = useState(false); - const [isInitialized, setIsInitialized] = useState(false) + const router = useRouter(); + const [form] = Form.useForm(); + const [rememberMe, setRememberMe] = useState(false); + const [isRegisterMode, setIsRegisterMode] = useState(false); + const [isInitialized, setIsInitialized] = useState(false); - useEffect(() => { - getIsInitialized() - }, []) + useEffect(() => { + getIsInitialized(); + }, []); + // 检查是否为首次启动项目,只为首次启动的用户提供注册资格 + function getIsInitialized() { + httpClient + .checkIfInited() + .then((res) => { + setIsInitialized(res.initialized); + }) + .catch((err) => { + console.log("error at getIsInitialized: ", err); + }); + } - // 检查是否为首次启动项目,只为首次启动的用户提供注册资格 - function getIsInitialized() { - httpClient.checkIfInited().then(res => { - setIsInitialized(res.initialized) - }).catch(err => { - console.log("error at getIsInitialized: ", err) - }) + function handleFormSubmit(formField: LoginField) { + if (isRegisterMode) { + handleRegister(formField.email, formField.password); + } else { + handleLogin(formField.email, formField.password); } + } - function handleFormSubmit(formField: LoginField) { - if (isRegisterMode) { - handleRegister(formField.email, formField.password); - } else { - handleLogin(formField.email, formField.password) - } - } + function handleRegister(username: string, password: string) { + httpClient + .initUser(username, password) + .then((res) => { + console.log("init user success: ", res); + }) + .catch((err) => { + console.log("init user error: ", err); + }); + } - function handleRegister(username: string, password: string) { - httpClient.initUser(username, password).then(res => { - console.log("init user success: ", res) - }).catch(err => { - console.log("init user error: ", err) - }) - } + function handleLogin(username: string, password: string) { + httpClient + .authUser(username, password) + .then((res) => { + localStorage.setItem("token", res.token); + console.log("login success: ", res); + router.push("/home"); + }) + .catch((err) => { + console.log("login error: ", err); + }); + } - function handleLogin(username: string, password: string) { - httpClient.authUser(username, password).then(res => { - localStorage.setItem("token", res.token) - console.log("login success: ", res) - router.push("/home") - }).catch(err => { - console.log("login error: ", err) - }) - } + return ( + // 使用 Ant Design 的组件库,使用 antd 的样式 + // 仅前端样式,无交互功能。 +
+ {/* login 类是整个 container,使用 flex 左右布局 */} +
+ {/* left 为注册的表单,需要填入的内容有:邮箱,密码 */} +
+
+ {isRegisterMode && ( +

注册 LangBot 账号

+ )} + {!isRegisterMode && ( +

欢迎回到 LangBot

+ )} +
{ + handleFormSubmit(values); + }} + > + + } + /> + - return ( - // 使用 Ant Design 的组件库,使用 antd 的样式 - // 仅前端样式,无交互功能。 + + } + /> + -
- {/* login 类是整个 container,使用 flex 左右布局 */} -
- {/* left 为注册的表单,需要填入的内容有:邮箱,密码 */} -
-
- { - isRegisterMode && -

注册 LangBot 账号

- } - { - !isRegisterMode && -

欢迎回到 LangBot

- } - { - handleFormSubmit(values) - }} - > - - } - /> - +
+ setRememberMe(e.target.checked)} + > + 30天内自动登录 + + + + 忘记密码? + + {!isRegisterMode && ( + { + setIsRegisterMode(true); + event.preventDefault(); + }} + > + 去注册? + + )} + {isRegisterMode && ( + { + setIsRegisterMode(false); + event.preventDefault(); + }} + > + 去登录 + + )} + +
- - } - /> - + -
- setRememberMe(e.target.checked)} - > - 30天内自动登录 - - - 忘记密码? - { - !isRegisterMode && - { - setIsRegisterMode(true) - event.preventDefault() - }} - >去注册? - } - { - isRegisterMode && - { - setIsRegisterMode(false) - event.preventDefault() - }} - >去登录 - } - -
+ - - - - - - -
- -
-
-
- -
- -
-
-
+
+ +
+
+
+ +
+ +
- ); +
+
+ ); } interface LoginField { - email: string; - password: string; -} \ No newline at end of file + email: string; + password: string; +} From 2eaac168dc1fe6f85100732a14eb4e28d7a250c5 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 28 Apr 2025 21:41:03 +0800 Subject: [PATCH 040/121] chore: rename `web_ui` dir to `web` --- .gitignore | 1 - {web_ui => web}/.gitignore | 0 {web_ui => web}/.husky/pre-commit | 0 {web_ui => web}/.lintstagedrc.json | 0 {web_ui => web}/.prettierrc.mjs | 0 {web_ui => web}/README.md | 0 {web_ui => web}/eslint.config.mjs | 0 {web_ui => web}/next.config.ts | 0 {web_ui => web}/package-lock.json | 0 {web_ui => web}/package.json | 0 {web_ui => web}/public/file.svg | 0 {web_ui => web}/public/globe.svg | 0 {web_ui => web}/public/next.svg | 0 {web_ui => web}/public/vercel.svg | 0 {web_ui => web}/public/window.svg | 0 {web_ui => web}/src/app/favicon.ico | Bin {web_ui => web}/src/app/global.css | 0 .../src/app/home/bots/ICreateBotField.ts | 0 .../src/app/home/bots/botConfig.module.css | 0 .../app/home/bots/components/bot-card/BotCard.tsx | 0 .../app/home/bots/components/bot-card/BotCardVO.ts | 0 .../components/bot-card/CreateBotCardComponent.tsx | 0 .../bots/components/bot-card/botCard.module.css | 0 .../app/home/bots/components/bot-form/BotForm.tsx | 0 .../home/bots/components/bot-form/BotFormEntity.ts | 0 .../bots/components/bot-form/ChooseAdapterEntity.ts | 0 {web_ui => web}/src/app/home/bots/page.tsx | 0 .../dynamic-form/DynamicFormComponent.tsx | 0 .../dynamic-form/DynamicFormItemComponent.tsx | 0 .../dynamic-form/DynamicFormItemConfig.ts | 0 .../dynamic-form/testDynamicConfigList.ts | 0 .../EmptyAndCreateComponent.tsx | 0 .../emptyAndCreate.module.css | 0 .../components/home-sidebar/HomeSidebar.module.css | 0 .../home/components/home-sidebar/HomeSidebar.tsx | 0 .../components/home-sidebar/HomeSidebarChild.tsx | 0 .../components/home-sidebar/sidbarConfigList.ts | 0 .../home/components/home-titlebar/HomeTitleBar.tsx | 0 .../home-titlebar/HomeTittleBar.module.css | 0 {web_ui => web}/src/app/home/layout.module.css | 0 {web_ui => web}/src/app/home/layout.tsx | 0 {web_ui => web}/src/app/home/mock-api/index.ts | 0 .../src/app/home/models/ICreateLLMField.ts | 0 .../src/app/home/models/LLMConfig.module.css | 0 .../app/home/models/component/llm-card/LLMCard.tsx | 0 .../app/home/models/component/llm-card/LLMCardVO.ts | 0 .../component/llm-form/ChooseAdapterEntity.ts | 0 .../app/home/models/component/llm-form/LLMForm.tsx | 0 {web_ui => web}/src/app/home/models/page.tsx | 0 {web_ui => web}/src/app/home/page.tsx | 0 .../pipeline-card/PipelineCardComponent.tsx | 0 .../components/pipeline-card/PipelineCardVO.ts | 0 .../pipeline-card/pipelineCard.module.css | 0 .../pipeline-form/PipelineChildFormEntity.ts | 0 .../pipeline-form/PipelineFormComponent.tsx | 0 .../pipeline-form/pipelineFormStyle.module.css | 0 {web_ui => web}/src/app/home/pipelines/page.tsx | 0 .../app/home/pipelines/pipelineConfig.module.css | 0 {web_ui => web}/src/app/home/plugins/page.tsx | 0 .../home/plugins/plugin-installed/PluginCardVO.ts | 0 .../plugin-installed/PluginInstalledComponent.tsx | 0 .../plugin-card/PluginCardComponent.tsx | 0 .../plugin-card/pluginCard.module.css | 0 .../plugins/plugin-market/PluginMarketComponent.tsx | 0 .../PluginMarketCardComponent.tsx | 0 .../plugin-market-card/PluginMarketCardVO.ts | 0 .../plugin-market-card/pluginMarketCard.module.css | 0 .../src/app/home/plugins/plugins.module.css | 0 .../src/app/infra/api/api-types/index.ts | 0 .../api/api-types/pipelines/GetMetaDataResponse.ts | 0 .../create-card-component/CreateCardComponent.tsx | 0 .../createCartComponent.module.css | 0 {web_ui => web}/src/app/infra/basic-types/I18N.ts | 0 {web_ui => web}/src/app/infra/http/HttpClient.ts | 0 {web_ui => web}/src/app/layout.tsx | 0 {web_ui => web}/src/app/login/layout.tsx | 0 {web_ui => web}/src/app/login/login.module.css | 0 {web_ui => web}/src/app/login/page.tsx | 0 {web_ui => web}/src/app/not-found.tsx | 0 {web_ui => web}/src/app/page.tsx | 0 {web_ui => web}/tsconfig.json | 0 81 files changed, 1 deletion(-) rename {web_ui => web}/.gitignore (100%) rename {web_ui => web}/.husky/pre-commit (100%) rename {web_ui => web}/.lintstagedrc.json (100%) rename {web_ui => web}/.prettierrc.mjs (100%) rename {web_ui => web}/README.md (100%) rename {web_ui => web}/eslint.config.mjs (100%) rename {web_ui => web}/next.config.ts (100%) rename {web_ui => web}/package-lock.json (100%) rename {web_ui => web}/package.json (100%) rename {web_ui => web}/public/file.svg (100%) rename {web_ui => web}/public/globe.svg (100%) rename {web_ui => web}/public/next.svg (100%) rename {web_ui => web}/public/vercel.svg (100%) rename {web_ui => web}/public/window.svg (100%) rename {web_ui => web}/src/app/favicon.ico (100%) rename {web_ui => web}/src/app/global.css (100%) rename {web_ui => web}/src/app/home/bots/ICreateBotField.ts (100%) rename {web_ui => web}/src/app/home/bots/botConfig.module.css (100%) rename {web_ui => web}/src/app/home/bots/components/bot-card/BotCard.tsx (100%) rename {web_ui => web}/src/app/home/bots/components/bot-card/BotCardVO.ts (100%) rename {web_ui => web}/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx (100%) rename {web_ui => web}/src/app/home/bots/components/bot-card/botCard.module.css (100%) rename {web_ui => web}/src/app/home/bots/components/bot-form/BotForm.tsx (100%) rename {web_ui => web}/src/app/home/bots/components/bot-form/BotFormEntity.ts (100%) rename {web_ui => web}/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts (100%) rename {web_ui => web}/src/app/home/bots/page.tsx (100%) rename {web_ui => web}/src/app/home/components/dynamic-form/DynamicFormComponent.tsx (100%) rename {web_ui => web}/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx (100%) rename {web_ui => web}/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts (100%) rename {web_ui => web}/src/app/home/components/dynamic-form/testDynamicConfigList.ts (100%) rename {web_ui => web}/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx (100%) rename {web_ui => web}/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css (100%) rename {web_ui => web}/src/app/home/components/home-sidebar/HomeSidebar.module.css (100%) rename {web_ui => web}/src/app/home/components/home-sidebar/HomeSidebar.tsx (100%) rename {web_ui => web}/src/app/home/components/home-sidebar/HomeSidebarChild.tsx (100%) rename {web_ui => web}/src/app/home/components/home-sidebar/sidbarConfigList.ts (100%) rename {web_ui => web}/src/app/home/components/home-titlebar/HomeTitleBar.tsx (100%) rename {web_ui => web}/src/app/home/components/home-titlebar/HomeTittleBar.module.css (100%) rename {web_ui => web}/src/app/home/layout.module.css (100%) rename {web_ui => web}/src/app/home/layout.tsx (100%) rename {web_ui => web}/src/app/home/mock-api/index.ts (100%) rename {web_ui => web}/src/app/home/models/ICreateLLMField.ts (100%) rename {web_ui => web}/src/app/home/models/LLMConfig.module.css (100%) rename {web_ui => web}/src/app/home/models/component/llm-card/LLMCard.tsx (100%) rename {web_ui => web}/src/app/home/models/component/llm-card/LLMCardVO.ts (100%) rename {web_ui => web}/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts (100%) rename {web_ui => web}/src/app/home/models/component/llm-form/LLMForm.tsx (100%) rename {web_ui => web}/src/app/home/models/page.tsx (100%) rename {web_ui => web}/src/app/home/page.tsx (100%) rename {web_ui => web}/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx (100%) rename {web_ui => web}/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts (100%) rename {web_ui => web}/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css (100%) rename {web_ui => web}/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts (100%) rename {web_ui => web}/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx (100%) rename {web_ui => web}/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css (100%) rename {web_ui => web}/src/app/home/pipelines/page.tsx (100%) rename {web_ui => web}/src/app/home/pipelines/pipelineConfig.module.css (100%) rename {web_ui => web}/src/app/home/plugins/page.tsx (100%) rename {web_ui => web}/src/app/home/plugins/plugin-installed/PluginCardVO.ts (100%) rename {web_ui => web}/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx (100%) rename {web_ui => web}/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx (100%) rename {web_ui => web}/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css (100%) rename {web_ui => web}/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx (100%) rename {web_ui => web}/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx (100%) rename {web_ui => web}/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts (100%) rename {web_ui => web}/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css (100%) rename {web_ui => web}/src/app/home/plugins/plugins.module.css (100%) rename {web_ui => web}/src/app/infra/api/api-types/index.ts (100%) rename {web_ui => web}/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts (100%) rename {web_ui => web}/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx (100%) rename {web_ui => web}/src/app/infra/basic-component/create-card-component/createCartComponent.module.css (100%) rename {web_ui => web}/src/app/infra/basic-types/I18N.ts (100%) rename {web_ui => web}/src/app/infra/http/HttpClient.ts (100%) rename {web_ui => web}/src/app/layout.tsx (100%) rename {web_ui => web}/src/app/login/layout.tsx (100%) rename {web_ui => web}/src/app/login/login.module.css (100%) rename {web_ui => web}/src/app/login/page.tsx (100%) rename {web_ui => web}/src/app/not-found.tsx (100%) rename {web_ui => web}/src/app/page.tsx (100%) rename {web_ui => web}/tsconfig.json (100%) diff --git a/.gitignore b/.gitignore index bfd0e3f2..17271201 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,3 @@ botpy.log* /libs/wecom_api/test.py /venv /jp-tyo-churros-05.rockchin.top -web/ \ No newline at end of file diff --git a/web_ui/.gitignore b/web/.gitignore similarity index 100% rename from web_ui/.gitignore rename to web/.gitignore diff --git a/web_ui/.husky/pre-commit b/web/.husky/pre-commit similarity index 100% rename from web_ui/.husky/pre-commit rename to web/.husky/pre-commit diff --git a/web_ui/.lintstagedrc.json b/web/.lintstagedrc.json similarity index 100% rename from web_ui/.lintstagedrc.json rename to web/.lintstagedrc.json diff --git a/web_ui/.prettierrc.mjs b/web/.prettierrc.mjs similarity index 100% rename from web_ui/.prettierrc.mjs rename to web/.prettierrc.mjs diff --git a/web_ui/README.md b/web/README.md similarity index 100% rename from web_ui/README.md rename to web/README.md diff --git a/web_ui/eslint.config.mjs b/web/eslint.config.mjs similarity index 100% rename from web_ui/eslint.config.mjs rename to web/eslint.config.mjs diff --git a/web_ui/next.config.ts b/web/next.config.ts similarity index 100% rename from web_ui/next.config.ts rename to web/next.config.ts diff --git a/web_ui/package-lock.json b/web/package-lock.json similarity index 100% rename from web_ui/package-lock.json rename to web/package-lock.json diff --git a/web_ui/package.json b/web/package.json similarity index 100% rename from web_ui/package.json rename to web/package.json diff --git a/web_ui/public/file.svg b/web/public/file.svg similarity index 100% rename from web_ui/public/file.svg rename to web/public/file.svg diff --git a/web_ui/public/globe.svg b/web/public/globe.svg similarity index 100% rename from web_ui/public/globe.svg rename to web/public/globe.svg diff --git a/web_ui/public/next.svg b/web/public/next.svg similarity index 100% rename from web_ui/public/next.svg rename to web/public/next.svg diff --git a/web_ui/public/vercel.svg b/web/public/vercel.svg similarity index 100% rename from web_ui/public/vercel.svg rename to web/public/vercel.svg diff --git a/web_ui/public/window.svg b/web/public/window.svg similarity index 100% rename from web_ui/public/window.svg rename to web/public/window.svg diff --git a/web_ui/src/app/favicon.ico b/web/src/app/favicon.ico similarity index 100% rename from web_ui/src/app/favicon.ico rename to web/src/app/favicon.ico diff --git a/web_ui/src/app/global.css b/web/src/app/global.css similarity index 100% rename from web_ui/src/app/global.css rename to web/src/app/global.css diff --git a/web_ui/src/app/home/bots/ICreateBotField.ts b/web/src/app/home/bots/ICreateBotField.ts similarity index 100% rename from web_ui/src/app/home/bots/ICreateBotField.ts rename to web/src/app/home/bots/ICreateBotField.ts diff --git a/web_ui/src/app/home/bots/botConfig.module.css b/web/src/app/home/bots/botConfig.module.css similarity index 100% rename from web_ui/src/app/home/bots/botConfig.module.css rename to web/src/app/home/bots/botConfig.module.css diff --git a/web_ui/src/app/home/bots/components/bot-card/BotCard.tsx b/web/src/app/home/bots/components/bot-card/BotCard.tsx similarity index 100% rename from web_ui/src/app/home/bots/components/bot-card/BotCard.tsx rename to web/src/app/home/bots/components/bot-card/BotCard.tsx diff --git a/web_ui/src/app/home/bots/components/bot-card/BotCardVO.ts b/web/src/app/home/bots/components/bot-card/BotCardVO.ts similarity index 100% rename from web_ui/src/app/home/bots/components/bot-card/BotCardVO.ts rename to web/src/app/home/bots/components/bot-card/BotCardVO.ts diff --git a/web_ui/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx b/web/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx similarity index 100% rename from web_ui/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx rename to web/src/app/home/bots/components/bot-card/CreateBotCardComponent.tsx diff --git a/web_ui/src/app/home/bots/components/bot-card/botCard.module.css b/web/src/app/home/bots/components/bot-card/botCard.module.css similarity index 100% rename from web_ui/src/app/home/bots/components/bot-card/botCard.module.css rename to web/src/app/home/bots/components/bot-card/botCard.module.css diff --git a/web_ui/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx similarity index 100% rename from web_ui/src/app/home/bots/components/bot-form/BotForm.tsx rename to web/src/app/home/bots/components/bot-form/BotForm.tsx diff --git a/web_ui/src/app/home/bots/components/bot-form/BotFormEntity.ts b/web/src/app/home/bots/components/bot-form/BotFormEntity.ts similarity index 100% rename from web_ui/src/app/home/bots/components/bot-form/BotFormEntity.ts rename to web/src/app/home/bots/components/bot-form/BotFormEntity.ts diff --git a/web_ui/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts b/web/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts similarity index 100% rename from web_ui/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts rename to web/src/app/home/bots/components/bot-form/ChooseAdapterEntity.ts diff --git a/web_ui/src/app/home/bots/page.tsx b/web/src/app/home/bots/page.tsx similarity index 100% rename from web_ui/src/app/home/bots/page.tsx rename to web/src/app/home/bots/page.tsx diff --git a/web_ui/src/app/home/components/dynamic-form/DynamicFormComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx similarity index 100% rename from web_ui/src/app/home/components/dynamic-form/DynamicFormComponent.tsx rename to web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx diff --git a/web_ui/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx similarity index 100% rename from web_ui/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx rename to web/src/app/home/components/dynamic-form/DynamicFormItemComponent.tsx diff --git a/web_ui/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts b/web/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts similarity index 100% rename from web_ui/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts rename to web/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts diff --git a/web_ui/src/app/home/components/dynamic-form/testDynamicConfigList.ts b/web/src/app/home/components/dynamic-form/testDynamicConfigList.ts similarity index 100% rename from web_ui/src/app/home/components/dynamic-form/testDynamicConfigList.ts rename to web/src/app/home/components/dynamic-form/testDynamicConfigList.ts diff --git a/web_ui/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx b/web/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx similarity index 100% rename from web_ui/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx rename to web/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx diff --git a/web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css b/web/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css similarity index 100% rename from web_ui/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css rename to web/src/app/home/components/empty-and-create-component/emptyAndCreate.module.css diff --git a/web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css b/web/src/app/home/components/home-sidebar/HomeSidebar.module.css similarity index 100% rename from web_ui/src/app/home/components/home-sidebar/HomeSidebar.module.css rename to web/src/app/home/components/home-sidebar/HomeSidebar.module.css diff --git a/web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx similarity index 100% rename from web_ui/src/app/home/components/home-sidebar/HomeSidebar.tsx rename to web/src/app/home/components/home-sidebar/HomeSidebar.tsx diff --git a/web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx b/web/src/app/home/components/home-sidebar/HomeSidebarChild.tsx similarity index 100% rename from web_ui/src/app/home/components/home-sidebar/HomeSidebarChild.tsx rename to web/src/app/home/components/home-sidebar/HomeSidebarChild.tsx diff --git a/web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts b/web/src/app/home/components/home-sidebar/sidbarConfigList.ts similarity index 100% rename from web_ui/src/app/home/components/home-sidebar/sidbarConfigList.ts rename to web/src/app/home/components/home-sidebar/sidbarConfigList.ts diff --git a/web_ui/src/app/home/components/home-titlebar/HomeTitleBar.tsx b/web/src/app/home/components/home-titlebar/HomeTitleBar.tsx similarity index 100% rename from web_ui/src/app/home/components/home-titlebar/HomeTitleBar.tsx rename to web/src/app/home/components/home-titlebar/HomeTitleBar.tsx diff --git a/web_ui/src/app/home/components/home-titlebar/HomeTittleBar.module.css b/web/src/app/home/components/home-titlebar/HomeTittleBar.module.css similarity index 100% rename from web_ui/src/app/home/components/home-titlebar/HomeTittleBar.module.css rename to web/src/app/home/components/home-titlebar/HomeTittleBar.module.css diff --git a/web_ui/src/app/home/layout.module.css b/web/src/app/home/layout.module.css similarity index 100% rename from web_ui/src/app/home/layout.module.css rename to web/src/app/home/layout.module.css diff --git a/web_ui/src/app/home/layout.tsx b/web/src/app/home/layout.tsx similarity index 100% rename from web_ui/src/app/home/layout.tsx rename to web/src/app/home/layout.tsx diff --git a/web_ui/src/app/home/mock-api/index.ts b/web/src/app/home/mock-api/index.ts similarity index 100% rename from web_ui/src/app/home/mock-api/index.ts rename to web/src/app/home/mock-api/index.ts diff --git a/web_ui/src/app/home/models/ICreateLLMField.ts b/web/src/app/home/models/ICreateLLMField.ts similarity index 100% rename from web_ui/src/app/home/models/ICreateLLMField.ts rename to web/src/app/home/models/ICreateLLMField.ts diff --git a/web_ui/src/app/home/models/LLMConfig.module.css b/web/src/app/home/models/LLMConfig.module.css similarity index 100% rename from web_ui/src/app/home/models/LLMConfig.module.css rename to web/src/app/home/models/LLMConfig.module.css diff --git a/web_ui/src/app/home/models/component/llm-card/LLMCard.tsx b/web/src/app/home/models/component/llm-card/LLMCard.tsx similarity index 100% rename from web_ui/src/app/home/models/component/llm-card/LLMCard.tsx rename to web/src/app/home/models/component/llm-card/LLMCard.tsx diff --git a/web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts b/web/src/app/home/models/component/llm-card/LLMCardVO.ts similarity index 100% rename from web_ui/src/app/home/models/component/llm-card/LLMCardVO.ts rename to web/src/app/home/models/component/llm-card/LLMCardVO.ts diff --git a/web_ui/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts b/web/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts similarity index 100% rename from web_ui/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts rename to web/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts diff --git a/web_ui/src/app/home/models/component/llm-form/LLMForm.tsx b/web/src/app/home/models/component/llm-form/LLMForm.tsx similarity index 100% rename from web_ui/src/app/home/models/component/llm-form/LLMForm.tsx rename to web/src/app/home/models/component/llm-form/LLMForm.tsx diff --git a/web_ui/src/app/home/models/page.tsx b/web/src/app/home/models/page.tsx similarity index 100% rename from web_ui/src/app/home/models/page.tsx rename to web/src/app/home/models/page.tsx diff --git a/web_ui/src/app/home/page.tsx b/web/src/app/home/page.tsx similarity index 100% rename from web_ui/src/app/home/page.tsx rename to web/src/app/home/page.tsx diff --git a/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx b/web/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx similarity index 100% rename from web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx rename to web/src/app/home/pipelines/components/pipeline-card/PipelineCardComponent.tsx diff --git a/web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts b/web/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts similarity index 100% rename from web_ui/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts rename to web/src/app/home/pipelines/components/pipeline-card/PipelineCardVO.ts diff --git a/web_ui/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css b/web/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css similarity index 100% rename from web_ui/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css rename to web/src/app/home/pipelines/components/pipeline-card/pipelineCard.module.css diff --git a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts b/web/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts similarity index 100% rename from web_ui/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts rename to web/src/app/home/pipelines/components/pipeline-form/PipelineChildFormEntity.ts diff --git a/web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx similarity index 100% rename from web_ui/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx rename to web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx diff --git a/web_ui/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css b/web/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css similarity index 100% rename from web_ui/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css rename to web/src/app/home/pipelines/components/pipeline-form/pipelineFormStyle.module.css diff --git a/web_ui/src/app/home/pipelines/page.tsx b/web/src/app/home/pipelines/page.tsx similarity index 100% rename from web_ui/src/app/home/pipelines/page.tsx rename to web/src/app/home/pipelines/page.tsx diff --git a/web_ui/src/app/home/pipelines/pipelineConfig.module.css b/web/src/app/home/pipelines/pipelineConfig.module.css similarity index 100% rename from web_ui/src/app/home/pipelines/pipelineConfig.module.css rename to web/src/app/home/pipelines/pipelineConfig.module.css diff --git a/web_ui/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx similarity index 100% rename from web_ui/src/app/home/plugins/page.tsx rename to web/src/app/home/plugins/page.tsx diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts b/web/src/app/home/plugins/plugin-installed/PluginCardVO.ts similarity index 100% rename from web_ui/src/app/home/plugins/plugin-installed/PluginCardVO.ts rename to web/src/app/home/plugins/plugin-installed/PluginCardVO.ts diff --git a/web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx similarity index 100% rename from web_ui/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx rename to web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx similarity index 100% rename from web_ui/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx rename to web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx diff --git a/web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css similarity index 100% rename from web_ui/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css rename to web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css diff --git a/web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx similarity index 100% rename from web_ui/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx rename to web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx similarity index 100% rename from web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx rename to web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts similarity index 100% rename from web_ui/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts rename to web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts diff --git a/web_ui/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css similarity index 100% rename from web_ui/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css rename to web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css diff --git a/web_ui/src/app/home/plugins/plugins.module.css b/web/src/app/home/plugins/plugins.module.css similarity index 100% rename from web_ui/src/app/home/plugins/plugins.module.css rename to web/src/app/home/plugins/plugins.module.css diff --git a/web_ui/src/app/infra/api/api-types/index.ts b/web/src/app/infra/api/api-types/index.ts similarity index 100% rename from web_ui/src/app/infra/api/api-types/index.ts rename to web/src/app/infra/api/api-types/index.ts diff --git a/web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts b/web/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts similarity index 100% rename from web_ui/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts rename to web/src/app/infra/api/api-types/pipelines/GetMetaDataResponse.ts diff --git a/web_ui/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx b/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx similarity index 100% rename from web_ui/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx rename to web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx diff --git a/web_ui/src/app/infra/basic-component/create-card-component/createCartComponent.module.css b/web/src/app/infra/basic-component/create-card-component/createCartComponent.module.css similarity index 100% rename from web_ui/src/app/infra/basic-component/create-card-component/createCartComponent.module.css rename to web/src/app/infra/basic-component/create-card-component/createCartComponent.module.css diff --git a/web_ui/src/app/infra/basic-types/I18N.ts b/web/src/app/infra/basic-types/I18N.ts similarity index 100% rename from web_ui/src/app/infra/basic-types/I18N.ts rename to web/src/app/infra/basic-types/I18N.ts diff --git a/web_ui/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts similarity index 100% rename from web_ui/src/app/infra/http/HttpClient.ts rename to web/src/app/infra/http/HttpClient.ts diff --git a/web_ui/src/app/layout.tsx b/web/src/app/layout.tsx similarity index 100% rename from web_ui/src/app/layout.tsx rename to web/src/app/layout.tsx diff --git a/web_ui/src/app/login/layout.tsx b/web/src/app/login/layout.tsx similarity index 100% rename from web_ui/src/app/login/layout.tsx rename to web/src/app/login/layout.tsx diff --git a/web_ui/src/app/login/login.module.css b/web/src/app/login/login.module.css similarity index 100% rename from web_ui/src/app/login/login.module.css rename to web/src/app/login/login.module.css diff --git a/web_ui/src/app/login/page.tsx b/web/src/app/login/page.tsx similarity index 100% rename from web_ui/src/app/login/page.tsx rename to web/src/app/login/page.tsx diff --git a/web_ui/src/app/not-found.tsx b/web/src/app/not-found.tsx similarity index 100% rename from web_ui/src/app/not-found.tsx rename to web/src/app/not-found.tsx diff --git a/web_ui/src/app/page.tsx b/web/src/app/page.tsx similarity index 100% rename from web_ui/src/app/page.tsx rename to web/src/app/page.tsx diff --git a/web_ui/tsconfig.json b/web/tsconfig.json similarity index 100% rename from web_ui/tsconfig.json rename to web/tsconfig.json From 1949ebb304961531e66b7e9f9eb3725e6adf8cfb Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 28 Apr 2025 21:41:55 +0800 Subject: [PATCH 041/121] fix(rename): typo --- web/.husky/pre-commit | 2 +- web/package-lock.json | 4 ++-- web/package.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/.husky/pre-commit b/web/.husky/pre-commit index 8eb362e8..832da580 100644 --- a/web/.husky/pre-commit +++ b/web/.husky/pre-commit @@ -1,3 +1,3 @@ -cd web_ui +cd web pnpm lint-staged pnpm test \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 8279ebd0..f8c3b1b4 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,11 +1,11 @@ { - "name": "web_ui", + "name": "web", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "web_ui", + "name": "web", "version": "0.1.0", "dependencies": { "@ant-design/v5-patch-for-react-19": "^1.0.3", diff --git a/web/package.json b/web/package.json index 09fb91e3..2fc93f2e 100644 --- a/web/package.json +++ b/web/package.json @@ -1,5 +1,5 @@ { - "name": "web_ui", + "name": "web", "version": "0.1.0", "private": true, "scripts": { @@ -7,7 +7,7 @@ "build": "next build", "start": "next start", "lint": "next lint", - "prepare": "cd .. && husky web_ui/.husky", + "prepare": "cd .. && husky web/.husky", "lint-staged": "lint-staged" }, "dependencies": { From 23321ce8e6f77a5a58a85e10be54ffb0b3a391bb Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 28 Apr 2025 21:59:37 +0800 Subject: [PATCH 042/121] ci: adapt for current webui --- Dockerfile | 2 +- pkg/api/http/controller/main.py | 10 +++++++++- pkg/core/app.py | 2 +- web/next.config.ts | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 428ad14d..5b6d4bff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ WORKDIR /app COPY . . -COPY --from=node /app/web/dist ./web/dist +COPY --from=node /app/web/out ./web/out RUN apt update \ && apt install gcc -y \ diff --git a/pkg/api/http/controller/main.py b/pkg/api/http/controller/main.py index 79838d7a..a727a7f9 100644 --- a/pkg/api/http/controller/main.py +++ b/pkg/api/http/controller/main.py @@ -65,11 +65,19 @@ class HTTPController: ginst = g(self.ap, self.quart_app) await ginst.initialize() - frontend_path = "web/dist" + frontend_path = "web/out" @self.quart_app.route("/") async def index(): return await quart.send_from_directory(frontend_path, "index.html") + + @self.quart_app.route("/login") + async def login(): + return await quart.send_from_directory(frontend_path, "login.html") + + @self.quart_app.route("/home") + async def home(): + return await quart.send_from_directory(frontend_path, "home.html") @self.quart_app.route("/") async def static_file(path: str): diff --git a/pkg/core/app.py b/pkg/core/app.py index 1c4042f8..9e337efb 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -145,7 +145,7 @@ class Application: async def print_web_access_info(self): """打印访问 webui 的提示""" - if not os.path.exists(os.path.join(".", "web/dist")): + if not os.path.exists(os.path.join(".", "web/out")): self.logger.warning("WebUI 文件缺失,请根据文档获取:https://docs.langbot.app/webui/intro.html") return diff --git a/web/next.config.ts b/web/next.config.ts index e9ffa308..9d1f4a25 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + output: 'export' }; export default nextConfig; From 8eca2cba58fd833e8ceeba31ce6c978997e1dd72 Mon Sep 17 00:00:00 2001 From: Chris <1637083533@qq.com> Date: Mon, 28 Apr 2025 22:23:48 +0800 Subject: [PATCH 043/121] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84404=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=BF=94=E5=9B=9E=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E5=92=8C=E6=94=AF=E6=8C=81=E8=81=94=E7=B3=BB=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/not-found.tsx | 75 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/web/src/app/not-found.tsx b/web/src/app/not-found.tsx index 9a023999..0d5f0770 100644 --- a/web/src/app/not-found.tsx +++ b/web/src/app/not-found.tsx @@ -1,8 +1,75 @@ +"use client"; + +import { Button, Typography, Space, Layout, Row, Col, Result } from 'antd'; +import { useRouter } from 'next/navigation'; +import Image from 'next/image'; + +const { Title, Paragraph } = Typography; + export default function NotFound() { + const router = useRouter(); + return ( -
- {/* TODO: @qidongrui 这里404页面有时间要更新*/} -

Langbot没有找到该页面喔~

-
+ +
+
+ {/* Ant Design 图标,可以换成 Langbot 的 Logo */} +
+ +
+ +
+ + 404 + + + 页面不存在 + + + 您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。 + +
+ +
+ + + + +
+ +
+ + 需要帮助吗?您可以联系 support@qq.com + +
+
+
+
+
); } From 9d6a56b496e9a8c9a883c3d610ddef88106434b7 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Mon, 28 Apr 2025 23:05:36 +0800 Subject: [PATCH 044/121] perf: apply mimetype judging in server --- pkg/api/http/controller/main.py | 47 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/pkg/api/http/controller/main.py b/pkg/api/http/controller/main.py index a727a7f9..0d6bcd15 100644 --- a/pkg/api/http/controller/main.py +++ b/pkg/api/http/controller/main.py @@ -69,16 +69,43 @@ class HTTPController: @self.quart_app.route("/") async def index(): - return await quart.send_from_directory(frontend_path, "index.html") - - @self.quart_app.route("/login") - async def login(): - return await quart.send_from_directory(frontend_path, "login.html") - - @self.quart_app.route("/home") - async def home(): - return await quart.send_from_directory(frontend_path, "home.html") + return await quart.send_from_directory(frontend_path, "index.html", mimetype="text/html") @self.quart_app.route("/") async def static_file(path: str): - return await quart.send_from_directory(frontend_path, path) + if not os.path.exists(os.path.join(frontend_path, path)): + if os.path.exists(os.path.join(frontend_path, path+".html")): + path += '.html' + else: + return await quart.send_from_directory(frontend_path, '404.html') + + mimetype = None + + if path.endswith(".html"): + mimetype = "text/html" + elif path.endswith(".js"): + mimetype = "application/javascript" + elif path.endswith(".css"): + mimetype = "text/css" + elif path.endswith(".png"): + mimetype = "image/png" + elif path.endswith(".jpg"): + mimetype = "image/jpeg" + elif path.endswith(".jpeg"): + mimetype = "image/jpeg" + elif path.endswith(".gif"): + mimetype = "image/gif" + elif path.endswith(".svg"): + mimetype = "image/svg+xml" + elif path.endswith(".ico"): + mimetype = "image/x-icon" + elif path.endswith(".json"): + mimetype = "application/json" + elif path.endswith(".txt"): + mimetype = "text/plain" + + return await quart.send_from_directory( + frontend_path, + path, + mimetype=mimetype + ) From ea1a24fd1ee2997435a8313fddac02869fb76e62 Mon Sep 17 00:00:00 2001 From: Chris <1637083533@qq.com> Date: Mon, 28 Apr 2025 23:10:33 +0800 Subject: [PATCH 045/121] Refactor and enhance UI components across the application - Improved formatting and consistency in BotConfigPage, HomeSidebar, and Plugin components. - Removed unnecessary Spin component to prevent layout collapse in BotConfigPage. - Enhanced sidebar selection logic to reflect current URL path in HomeSidebar. - Updated layout styles for better responsiveness and visual appeal. - Implemented mock data fetching in PluginMarketComponent for improved testing and development. - Added pagination and search functionality in PluginMarketComponent. - Refactored PluginInstalledComponent to streamline plugin list rendering and modal handling. - Adjusted CSS styles for better alignment and spacing in various components. - Removed commented-out code in HttpClient for cleaner codebase. - Enhanced NotFound component layout for better user experience. --- web/src/app/home/bots/botConfig.module.css | 2 +- web/src/app/home/bots/page.tsx | 62 ++--- .../components/home-sidebar/HomeSidebar.tsx | 186 +++++++------ web/src/app/home/layout.module.css | 13 +- web/src/app/home/layout.tsx | 46 +++- web/src/app/home/plugins/page.tsx | 45 ++- .../PluginInstalledComponent.tsx | 260 ++++++++++++++---- .../plugin-card/PluginCardComponent.tsx | 98 +++---- .../plugin-card/pluginCard.module.css | 9 +- .../plugin-market/PluginMarketComponent.tsx | 186 ++++++++----- .../plugin-market-card/PluginMarketCardVO.ts | 3 + .../pluginMarketCard.module.css | 4 +- web/src/app/home/plugins/plugins.module.css | 12 +- .../CreateCardComponent.tsx | 4 +- web/src/app/infra/http/HttpClient.ts | 6 +- web/src/app/not-found.tsx | 115 ++++---- 16 files changed, 631 insertions(+), 420 deletions(-) diff --git a/web/src/app/home/bots/botConfig.module.css b/web/src/app/home/bots/botConfig.module.css index ae201324..80fa5f2c 100644 --- a/web/src/app/home/bots/botConfig.module.css +++ b/web/src/app/home/bots/botConfig.module.css @@ -13,7 +13,7 @@ align-self: flex-start; justify-self: flex-start; width: calc(100% - 60px); - margin: auto; + margin: auto; display: grid; grid-template-rows: repeat(auto-fill, minmax(220px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); diff --git a/web/src/app/home/bots/page.tsx b/web/src/app/home/bots/page.tsx index fdd32f59..a3570eaf 100644 --- a/web/src/app/home/bots/page.tsx +++ b/web/src/app/home/bots/page.tsx @@ -1,15 +1,15 @@ "use client" -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import styles from "./botConfig.module.css"; import EmptyAndCreateComponent from "@/app/home/components/empty-and-create-component/EmptyAndCreateComponent"; -import {useRouter} from "next/navigation"; -import {BotCardVO} from "@/app/home/bots/components/bot-card/BotCardVO"; -import {Modal, notification, Spin} from "antd"; +import { useRouter } from "next/navigation"; +import { BotCardVO } from "@/app/home/bots/components/bot-card/BotCardVO"; +import { Modal, notification, Spin } from "antd"; import BotForm from "@/app/home/bots/components/bot-form/BotForm"; import BotCard from "@/app/home/bots/components/bot-card/BotCard"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent" -import {httpClient} from "@/app/infra/http/HttpClient"; +import { httpClient } from "@/app/infra/http/HttpClient"; import { Bot } from "@/app/infra/api/api-types"; export default function BotConfigPage() { @@ -89,8 +89,9 @@ export default function BotConfigPage() { } return ( -
+ + {/* 删除 spin,使用 spin 会导致盒子塌陷。 */} + } {pageShowRule === BotConfigPageShowRule.HAVE_BOT && -
- {botList.map(cardVO => { - return ( -
{selectBot(cardVO)}} - > - -
) - })} - -
+
+ {botList.map(cardVO => { + return ( +
{ selectBot(cardVO) }} + > + +
) + })} + +
}
-
+ ) } diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 12813b29..56a6d6f5 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -1,100 +1,118 @@ -"use client"; +"use client" -import styles from "./HomeSidebar.module.css"; +import styles from "./HomeSidebar.module.css" import { useEffect, useState } from "react"; -import { - SidebarChild, - SidebarChildVO -} from "@/app/home/components/home-sidebar/HomeSidebarChild"; -import { useRouter, usePathname } from "next/navigation"; +import { SidebarChild, SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild"; +import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { sidebarConfigList } from "@/app/home/components/home-sidebar/sidbarConfigList"; // TODO 侧边导航栏要加动画 export default function HomeSidebar({ - onSelectedChangeAction + onSelectedChange }: { - onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; + onSelectedChange: (sidebarChild: SidebarChildVO) => void }) { - // 路由相关 - const router = useRouter(); - const pathname = usePathname(); - // 路由被动变化时处理 - useEffect(() => { - handleRouteChange(pathname); - }, [pathname]); + // 路由相关 + const router = useRouter() + const pathname = usePathname(); + const searchParams = useSearchParams(); + // 路由被动变化时处理 + useEffect(() => { + handleRouteChange(pathname) + }, [pathname, searchParams]); - const [selectedChild, setSelectedChild] = useState( - sidebarConfigList[0] - ); + const [selectedChild, setSelectedChild] = useState(sidebarConfigList[0]) - useEffect(() => { - console.log("HomeSidebar挂载完成"); - initSelect(); - return () => console.log("HomeSidebar卸载"); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(() => { + console.log('HomeSidebar挂载完成'); + initSelect() + return () => console.log('HomeSidebar卸载'); + }, []); - function handleChildClick(child: SidebarChildVO) { - setSelectedChild(child); - handleRoute(child); - onSelectedChangeAction(child); - } - function initSelect() { - handleChildClick(sidebarConfigList[0]); - } - function handleRoute(child: SidebarChildVO) { - console.log(child); - router.push(`${child.route}`); - } - - function handleRouteChange(pathname: string) { - // TODO 这段逻辑并不好,未来router封装好后改掉 - // 判断在home下,并且路由更改的是自己的路由子组件则更新UI - const routeList = pathname.split("/"); - if ( - routeList[1] === "home" && - sidebarConfigList.find((childConfig) => childConfig.route === pathname) - ) { - console.log("find success"); - const routeSelectChild = sidebarConfigList.find( - (childConfig) => childConfig.route === pathname - ); - if (routeSelectChild) { - setSelectedChild(routeSelectChild); - } + function handleChildClick(child: SidebarChildVO) { + setSelectedChild(child) + handleRoute(child) + onSelectedChange(child) } - } - return ( -
- {/* LangBot、ICON区域 */} -
- {/* icon */} -
L
-
Langbot
-
- {/* 菜单列表,后期可升级成配置驱动 */} -
- {sidebarConfigList.map((config) => { - return ( -
{ - console.log("click:", config.id); - handleChildClick(config); - }} - > - + function initSelect() { + // 根据当前URL路径选择相应的菜单项,而不是总是使用第一个菜单项 + const currentPath = pathname; + const matchedChild = sidebarConfigList.find(child => child.route === currentPath); + + if (matchedChild) { + // 如果找到匹配的菜单项,则选择它 + setSelectedChild(matchedChild); + onSelectedChange(matchedChild); + } else { + // 如果没有匹配项,则回退到默认选择第一个菜单项 + handleChildClick(sidebarConfigList[0]); + } + } + + function handleRoute(child: SidebarChildVO) { + console.log(child) + router.push(`${child.route}`) + } + + function handleRouteChange(pathname: string) { + // TODO 这段逻辑并不好,未来router封装好后改掉 + // 判断在home下,并且路由更改的是自己的路由子组件则更新UI + const routeList = pathname.split('/') + if ( + routeList[1] === "home" && + sidebarConfigList.find(childConfig => + childConfig.route === pathname + ) + ) { + console.log("find success") + const routeSelectChild = sidebarConfigList.find(childConfig => + childConfig.route === pathname + ) + if (routeSelectChild) { + setSelectedChild(routeSelectChild) + } + } + } + + + return ( +
+ {/* LangBot、ICON区域 */} +
+ {/* icon */} +
+ L +
+
+ Langbot +
- ); - })} -
-
- ); -} + {/* 菜单列表,后期可升级成配置驱动 */} +
+ { + sidebarConfigList.map(config => { + return ( +
{ + console.log('click:', config.id) + handleChildClick(config) + }} + > + +
+ ) + }) + } + +
+
+ ); +} \ No newline at end of file diff --git a/web/src/app/home/layout.module.css b/web/src/app/home/layout.module.css index bcbdc800..8bc33e35 100644 --- a/web/src/app/home/layout.module.css +++ b/web/src/app/home/layout.module.css @@ -1,3 +1,4 @@ +/* 主布局容器 */ .homeLayoutContainer { width: 100vw; height: 100vh; @@ -5,14 +6,12 @@ flex-direction: row; } +/* 主内容区域 */ .main { + background-color: #f5f5f7; + padding: 0; + overflow: auto; width: 100%; height: 100%; - background-color: #FAFBFB; -} - -.mainContent { - width: calc(100% - 40px); - height: calc(100% - 110px); - margin: 20px; + padding: 20px; } \ No newline at end of file diff --git a/web/src/app/home/layout.tsx b/web/src/app/home/layout.tsx index 7985dae6..8ac4efb4 100644 --- a/web/src/app/home/layout.tsx +++ b/web/src/app/home/layout.tsx @@ -1,30 +1,48 @@ -"use client"; +"use client" -import "@ant-design/v5-patch-for-react-19"; -import styles from "./layout.module.css"; +import '@ant-design/v5-patch-for-react-19'; +import styles from "./layout.module.css" import HomeSidebar from "@/app/home/components/home-sidebar/HomeSidebar"; import HomeTitleBar from "@/app/home/components/home-titlebar/HomeTitleBar"; import React, { useState } from "react"; import { SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild"; +import { useRouter } from 'next/navigation'; +import { Layout } from 'antd'; + +const { Sider, Content } = Layout; export default function HomeLayout({ children }: Readonly<{ children: React.ReactNode; }>) { - const [title, setTitle] = useState(""); + const router = useRouter(); + const [title, setTitle] = useState("") const onSelectedChange = (child: SidebarChildVO) => { - setTitle(child.name); - }; + setTitle(child.name) + } return ( -
- -
+ + {/* homeLayoutContainer 是整个容器的入口,使用 flex 的左右布局 */} + + + + {/* HomeSidebar 为侧边栏 */} + + + + {/* right 为内容显示区域,right使用 flex 上下布局,right 使用 flex 布局吃掉剩余部分 */} + - {/* 主页面 */} -
{children}
-
-
- ); + + + {/* mainContent 为主页面 */} + {children} + + + + ) } diff --git a/web/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx index e28db69d..10151b39 100644 --- a/web/src/app/home/plugins/page.tsx +++ b/web/src/app/home/plugins/page.tsx @@ -1,6 +1,6 @@ "use client" import { Radio } from 'antd'; -import {useState} from "react"; +import { useState } from "react"; import PluginInstalledComponent from "@/app/home/plugins/plugin-installed/PluginInstalledComponent"; import PluginMarketComponent from "@/app/home/plugins/plugin-market/PluginMarketComponent"; import styles from './plugins.module.css' @@ -14,33 +14,24 @@ export default function PluginConfigPage() { const [nowPageType, setNowPageType] = useState(PageType.INSTALLED) return ( -
-
- { - // 这里静态类型检测有问题 - setNowPageType(e.target.value) - }} - /> -
-
- { - nowPageType === PageType.INSTALLED && - } - { - nowPageType === PageType.MARKET && - } -
+
+ { + setNowPageType(e.target.value as PageType) + }} + /> + {nowPageType === PageType.INSTALLED ? : }
); } diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 1b5cea43..2897cef8 100644 --- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client" import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; @@ -7,54 +7,221 @@ import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card import styles from "@/app/home/plugins/plugins.module.css"; import { Modal, Input } from "antd"; import { GithubOutlined } from "@ant-design/icons"; -import { httpClient } from "@/app/infra/http/HttpClient"; export default function PluginInstalledComponent() { - const [pluginList, setPluginList] = useState([]); - const [modalOpen, setModalOpen] = useState(false); - const [githubURL, setGithubURL] = useState(""); + const [pluginList, setPluginList] = useState([]) + const [modalOpen, setModalOpen] = useState(false) + const [githubURL, setGithubURL] = useState("") + useEffect(() => { - initData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + initData() + }, []) function initData() { - getPluginList(); + getPluginList().then((value) => { + setPluginList(value) + }) } - function getPluginList() { - httpClient.getPlugins().then((value) => { - setPluginList( - value.plugins.map((plugin) => { - return new PluginCardVO({ - author: plugin.author, - description: plugin.description.zh_CN, - handlerCount: 0, - name: plugin.name, - version: plugin.version, - isInitialized: plugin.status === "initialized" - }); - }) - ); - }); + async function getPluginList() { + return [ + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), new PluginCardVO({ + description: "一般的描述", + handlerCount: 0, + name: "插件AAA", + author: "/hana", + version: "0.1", + isInitialized: false + }), + + + ] } function handleModalConfirm() { - installPlugin(githubURL); - setModalOpen(false); + installPlugin(githubURL) + setModalOpen(false) } function installPlugin(url: string) { - httpClient - .installPluginFromGithub(url) - .then(() => { - // 安装后重新拉取 - getPluginList(); - }) - .catch((err) => { - console.log("error when install plugin:", err); - }); + // TODO 接安装Plugin的接口 + console.log("installPlugin: ", url) } return (
@@ -63,8 +230,8 @@ export default function PluginInstalledComponent() {
@@ -79,7 +246,9 @@ export default function PluginInstalledComponent() { destroyOnClose={true} >
-
目前仅支持从 GitHub 安装
+
+ 目前仅支持从 GitHub 安装 +
- {pluginList.map((vo, index) => { - return ( -
+ { + pluginList.map((vo, index) => { + return
- ); - })} + }) + } { - setModalOpen(true); + setModalOpen(true) }} />
- ); + ) } diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index fcae4000..267bb3b7 100644 --- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -1,65 +1,47 @@ -import styles from "./pluginCard.module.css"; +import styles from "./pluginCard.module.css" import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; -import { GithubOutlined, LinkOutlined, ToolOutlined } from "@ant-design/icons"; -import { Switch, Tag } from "antd"; -import { useState } from "react"; -import { httpClient } from "@/app/infra/http/HttpClient"; +import { GithubOutlined, LinkOutlined, ToolOutlined } from '@ant-design/icons'; +import { Tag } from 'antd' export default function PluginCardComponent({ - cardVO + cardVO }: { - cardVO: PluginCardVO; + cardVO: PluginCardVO }) { - const [initialized, setInitialized] = useState(cardVO.isInitialized); - const [switchEnable, setSwitchEnable] = useState(true); - - function handleEnable() { - setSwitchEnable(false); - httpClient - .togglePlugin(cardVO.author, cardVO.name, !initialized) - .then(() => { - setInitialized(!initialized); - }) - .catch((err) => { - console.log("error: ", err); - }) - .finally(() => { - setSwitchEnable(true); - }); - } - return ( -
- {/* header */} -
- {/* left author */} -
{cardVO.author}
- {/* right icon & version */} -
- - v{cardVO.version} + return ( +
+ {/* header */} +
+ {/* left author */} +
{cardVO.author}
+ {/* right icon & version */} +
+ + v{cardVO.version} +
+
+ {/* content */} +
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */} +
+
+
+ + 1 +
+ +
+
-
- {/* content */} -
-
{cardVO.name}
-
{cardVO.description}
-
- {/* footer */} -
-
-
- - 1 -
- -
- - -
-
- ); + ); } diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css index 81ad489a..3ec44fd4 100644 --- a/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css +++ b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css @@ -1,5 +1,6 @@ .cardContainer { - width: 360px; + width: 100%; + /* 修改为 100% 以撑满整个网格单元 */ height: 140px; box-sizing: border-box; background-color: #FFF; @@ -40,10 +41,6 @@ .cardFooter { width: 90%; height: 30px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; } @@ -73,4 +70,4 @@ align-self: center; justify-content: space-between; } -} +} \ No newline at end of file diff --git a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index d01aeb6e..793db49c 100644 --- a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -1,87 +1,127 @@ -"use client"; +"use client" -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import styles from "@/app/home/plugins/plugins.module.css"; import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; import { Input, Pagination } from "antd"; -import { spaceClient } from "@/app/infra/http/HttpClient"; +import { debounce } from "lodash" export default function PluginMarketComponent() { - const [marketPluginList, setMarketPluginList] = useState< - PluginMarketCardVO[] - >([]); - const [totalCount, setTotalCount] = useState(0); - const [nowPage, setNowPage] = useState(1); - const [searchKeyword, setSearchKeyword] = useState(""); + const [marketPluginList, setMarketPluginList] = useState([]) + const [searchKeyword, setSearchKeyword] = useState("") + const [currentPage, setCurrentPage] = useState(1) + const [totalItems, setTotalItems] = useState(0) + const [loading, setLoading] = useState(false) + const pageSize = 10 // 每页显示的项目数量 - useEffect(() => { - initData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + useEffect(() => { + fetchPlugins(searchKeyword, currentPage) + }, [currentPage]) - function initData() { - getPluginList(); - } + // 获取插件列表,整合了搜索和分页功能 + async function fetchPlugins(keyword: string = "", page: number = 1): Promise { + setLoading(true) + try { + // 实际应用中,这里应该调用API获取数据 + const result = await mockFetchPlugins(keyword, page, pageSize) + setMarketPluginList(result.data) + setTotalItems(result.total) + } finally { + setLoading(false) + } + } - function onInputSearchKeyword(keyword: string) { - // 这里记得加防抖,暂时没加 - setSearchKeyword(keyword); - setNowPage(1); - getPluginList(1, keyword); - } + // 模拟从API获取数据 + async function mockFetchPlugins(keyword: string, page: number, pageSize: number): Promise<{ data: PluginMarketCardVO[], total: number }> { + // 模拟API延迟 + await new Promise(resolve => setTimeout(resolve, 300)) - function getPluginList( - page: number = nowPage, - keyword: string = searchKeyword - ) { - spaceClient.getMarketPlugins(page, 10, keyword).then((res) => { - setMarketPluginList( - res.plugins.map( - (marketPlugin) => - new PluginMarketCardVO({ - author: marketPlugin.author, - description: marketPlugin.description, - githubURL: marketPlugin.repository, - name: marketPlugin.name, - pluginId: String(marketPlugin.ID), - starCount: marketPlugin.stars - }) - ) - ); - setTotalCount(res.total); - console.log("market plugins:", res); - }); - } + // 创建模拟数据 + const allPlugins: PluginMarketCardVO[] = [] + const totalPlugins = 50 // 模拟总数据量 - return ( -
- onInputSearchKeyword(e.target.value)} - /> -
- {marketPluginList.map((vo, index) => { - return ( -
- + for (let i = 0; i < totalPlugins; i++) { + allPlugins.push(new PluginMarketCardVO({ + pluginId: `plugin-${i}`, + description: `这是插件 ${i} 的描述,包含一些详细信息`, + name: `插件 ${i}`, + author: `/author-${i % 5}`, // 模拟5个不同的作者 + version: `0.${i % 10}`, + githubURL: `https://github.com/author-${i % 5}/plugin-${i}`, + starCount: 10 + Math.floor(Math.random() * 100) + })) + } + + // 根据关键词过滤 + const filtered = keyword + ? allPlugins.filter(p => + p.name.toLowerCase().includes(keyword.toLowerCase()) || + p.description.toLowerCase().includes(keyword.toLowerCase())) + : allPlugins + + // 分页处理 + const start = (page - 1) * pageSize + const end = start + pageSize + const paginatedData = filtered.slice(start, end) + + return { + data: paginatedData, + total: filtered.length + } + } + + function onInputSearchKeyword(keyword: string) { + setSearchKeyword(keyword) + setCurrentPage(1) // 搜索时重置为第一页 + debounceSearch(keyword) + } + + const debounceSearch = useCallback( + debounce((keyword: string) => { + fetchPlugins(keyword, 1) + }, 500), [] + ) + + function handlePageChange(page: number) { + setCurrentPage(page) + } + + return ( +
+ onInputSearchKeyword(e.target.value)} + /> +
+ {loading ? ( +
加载中...
+ ) : marketPluginList.length === 0 ? ( +
没有找到匹配的插件
+ ) : ( + marketPluginList.map((vo, index) => ( +
+ +
+ )) + )}
- ); - })} -
- { - setNowPage(pageNumber); - getPluginList(pageNumber); - }} - /> -
- ); + {totalItems > 0 && ( +
+ +
+ )} +
+ ) } diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts index 6af3f199..9d508066 100644 --- a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts +++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts @@ -5,6 +5,7 @@ export interface IPluginMarketCardVO { description: string, starCount: number, githubURL: string, + version: string, } export class PluginMarketCardVO implements IPluginMarketCardVO { @@ -14,6 +15,7 @@ export class PluginMarketCardVO implements IPluginMarketCardVO { author: string; githubURL: string; starCount: number; + version: string; constructor(prop: IPluginMarketCardVO) { this.description = prop.description @@ -22,5 +24,6 @@ export class PluginMarketCardVO implements IPluginMarketCardVO { this.githubURL = prop.githubURL this.starCount = prop.starCount this.pluginId = prop.pluginId + this.version = prop.version } } diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css index f611f335..33297b09 100644 --- a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css +++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css @@ -1,6 +1,4 @@ .cardContainer { - width: 360px; - height: 140px; box-sizing: border-box; background-color: #FFF; border-radius: 9px; @@ -74,4 +72,4 @@ align-self: center; justify-content: space-between; } -} +} \ No newline at end of file diff --git a/web/src/app/home/plugins/plugins.module.css b/web/src/app/home/plugins/plugins.module.css index 1393c15b..24688273 100644 --- a/web/src/app/home/plugins/plugins.module.css +++ b/web/src/app/home/plugins/plugins.module.css @@ -1,8 +1,8 @@ .pageContainer { width: 100%; - height: calc(100% - 30px); } + .marketComponentBody { width: 100%; height: calc(100% - 60px); @@ -11,17 +11,13 @@ .pluginListContainer { align-self: flex-start; justify-self: flex-start; - width: calc(100% - 60px); - height: 100%; - max-height: 100%; - margin: auto; + margin: auto; display: grid; grid-template-rows: repeat(auto-fill, minmax(160px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 15px; - justify-items: center; + /* justify-items: center; */ align-items: center; - overflow-y: scroll; } .modalTitle { @@ -35,4 +31,4 @@ display: flex; flex-direction: column; justify-content: space-around; -} +} \ No newline at end of file diff --git a/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx b/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx index 3e33d712..f1a280fc 100644 --- a/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx +++ b/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx @@ -1,12 +1,10 @@ import styles from "./createCartComponent.module.css"; export default function CreateCardComponent({ - width, height, plusSize, onClick, }: { - width: number; height: number; plusSize: number; onClick: () => void @@ -15,7 +13,7 @@ export default function CreateCardComponent({
-
-
- {/* Ant Design 图标,可以换成 Langbot 的 Logo */} -
- -
+ + + +
+
+ {/* Ant Design 图标,可以换成 Langbot 的 Logo */} +
+ +
-
- - 404 - - - 页面不存在 - - - 您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。 - -
+
+ + 404 + + + 页面不存在 + + + 您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。 + +
-
- - - - -
+
+ + + + +
-
- - 需要帮助吗?您可以联系 support@qq.com - +
+ + 需要帮助吗?您可以联系 support@qq.com + +
+
-
-
- + + ); } From 4b5ac6ad03604f523f58ace02cf16d12fdc2f1be Mon Sep 17 00:00:00 2001 From: Chris <1637083533@qq.com> Date: Mon, 28 Apr 2025 23:14:35 +0800 Subject: [PATCH 046/121] http --- web/src/app/infra/http/HttpClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts index 192566a0..a8bb53cc 100644 --- a/web/src/app/infra/http/HttpClient.ts +++ b/web/src/app/infra/http/HttpClient.ts @@ -129,9 +129,9 @@ class HttpClient { const errMessage = data?.message || error.message; switch (status) { - // case 401: - // window.location.href = "/login"; - // break; + case 401: + window.location.href = "/login"; + break; case 403: console.error("Permission denied:", errMessage); break; From 9d724dbb8d28bd8eb29e203544ea3d79b28f5455 Mon Sep 17 00:00:00 2001 From: chris <1637083533@qq.com> Date: Tue, 29 Apr 2025 14:58:17 +0800 Subject: [PATCH 047/121] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/home-sidebar/HomeSidebar.tsx | 118 ++++++++-------- web/src/app/home/layout.tsx | 4 +- .../PluginInstalledComponent.tsx | 1 + .../plugin-card/PluginCardComponent.tsx | 22 ++- .../plugin-market/PluginMarketComponent.tsx | 126 ++++++++---------- 5 files changed, 131 insertions(+), 140 deletions(-) diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 56a6d6f5..7a950e91 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -1,81 +1,71 @@ -"use client" +"use client"; -import styles from "./HomeSidebar.module.css" +import styles from "./HomeSidebar.module.css"; import { useEffect, useState } from "react"; -import { SidebarChild, SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild"; -import { useRouter, usePathname, useSearchParams } from "next/navigation"; +import { + SidebarChild, + SidebarChildVO +} from "@/app/home/components/home-sidebar/HomeSidebarChild"; +import { useRouter, usePathname } from "next/navigation"; import { sidebarConfigList } from "@/app/home/components/home-sidebar/sidbarConfigList"; // TODO 侧边导航栏要加动画 export default function HomeSidebar({ - onSelectedChange + onSelectedChangeAction }: { - onSelectedChange: (sidebarChild: SidebarChildVO) => void + onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; }) { - // 路由相关 - const router = useRouter() - const pathname = usePathname(); - const searchParams = useSearchParams(); - // 路由被动变化时处理 - useEffect(() => { - handleRouteChange(pathname) - }, [pathname, searchParams]); + // 路由相关 + const router = useRouter(); + const pathname = usePathname(); + // 路由被动变化时处理 + useEffect(() => { + handleRouteChange(pathname); + }, [pathname]); - const [selectedChild, setSelectedChild] = useState(sidebarConfigList[0]) + const [selectedChild, setSelectedChild] = useState( + sidebarConfigList[0] + ); - useEffect(() => { - console.log('HomeSidebar挂载完成'); - initSelect() - return () => console.log('HomeSidebar卸载'); - }, []); + useEffect(() => { + console.log("HomeSidebar挂载完成"); + initSelect(); + return () => console.log("HomeSidebar卸载"); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + function handleChildClick(child: SidebarChildVO) { + setSelectedChild(child); + handleRoute(child); + onSelectedChangeAction(child); + } + function initSelect() { + handleChildClick(sidebarConfigList[0]); + } - function handleChildClick(child: SidebarChildVO) { - setSelectedChild(child) - handleRoute(child) - onSelectedChange(child) - } - - function initSelect() { - // 根据当前URL路径选择相应的菜单项,而不是总是使用第一个菜单项 - const currentPath = pathname; - const matchedChild = sidebarConfigList.find(child => child.route === currentPath); - - if (matchedChild) { - // 如果找到匹配的菜单项,则选择它 - setSelectedChild(matchedChild); - onSelectedChange(matchedChild); - } else { - // 如果没有匹配项,则回退到默认选择第一个菜单项 - handleChildClick(sidebarConfigList[0]); - } - } - - function handleRoute(child: SidebarChildVO) { - console.log(child) - router.push(`${child.route}`) - } - - function handleRouteChange(pathname: string) { - // TODO 这段逻辑并不好,未来router封装好后改掉 - // 判断在home下,并且路由更改的是自己的路由子组件则更新UI - const routeList = pathname.split('/') - if ( - routeList[1] === "home" && - sidebarConfigList.find(childConfig => - childConfig.route === pathname - ) - ) { - console.log("find success") - const routeSelectChild = sidebarConfigList.find(childConfig => - childConfig.route === pathname - ) - if (routeSelectChild) { - setSelectedChild(routeSelectChild) - } - } + function handleRoute(child: SidebarChildVO) { + console.log(child); + router.push(`${child.route}`); + } + + function handleRouteChange(pathname: string) { + // TODO 这段逻辑并不好,未来router封装好后改掉 + // 判断在home下,并且路由更改的是自己的路由子组件则更新UI + const routeList = pathname.split("/"); + if ( + routeList[1] === "home" && + sidebarConfigList.find((childConfig) => childConfig.route === pathname) + ) { + console.log("find success"); + const routeSelectChild = sidebarConfigList.find( + (childConfig) => childConfig.route === pathname + ); + if (routeSelectChild) { + setSelectedChild(routeSelectChild); + } } + } return ( diff --git a/web/src/app/home/layout.tsx b/web/src/app/home/layout.tsx index 8ac4efb4..c765aef0 100644 --- a/web/src/app/home/layout.tsx +++ b/web/src/app/home/layout.tsx @@ -18,7 +18,7 @@ export default function HomeLayout({ }>) { const router = useRouter(); const [title, setTitle] = useState("") - const onSelectedChange = (child: SidebarChildVO) => { + const onSelectedChangeAction = (child: SidebarChildVO) => { setTitle(child.name) } @@ -28,7 +28,7 @@ export default function HomeLayout({ {/* HomeSidebar 为侧边栏 */} diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 2897cef8..be770733 100644 --- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -16,6 +16,7 @@ export default function PluginInstalledComponent() { useEffect(() => { initData() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) function initData() { diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index 267bb3b7..9efbbe9e 100644 --- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -1,13 +1,33 @@ import styles from "./pluginCard.module.css" import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; import { GithubOutlined, LinkOutlined, ToolOutlined } from '@ant-design/icons'; -import { Tag } from 'antd' +import { Switch, Tag } from 'antd' +import { useState } from "react"; +import { httpClient } from "@/app/infra/http/HttpClient"; export default function PluginCardComponent({ cardVO }: { cardVO: PluginCardVO }) { + const [initialized, setInitialized] = useState(cardVO.isInitialized); + const [switchEnable, setSwitchEnable] = useState(true); + + function handleEnable() { + setSwitchEnable(false); + httpClient + .togglePlugin(cardVO.author, cardVO.name, !initialized) + .then(() => { + setInitialized(!initialized); + }) + .catch((err) => { + console.log("error: ", err); + }) + .finally(() => { + setSwitchEnable(true); + }); + } + return (
{/* header */} diff --git a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx index 793db49c..9e84c0ff 100644 --- a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx @@ -1,90 +1,70 @@ -"use client" +"use client"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import styles from "@/app/home/plugins/plugins.module.css"; import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent"; import { Input, Pagination } from "antd"; -import { debounce } from "lodash" +import { spaceClient } from "@/app/infra/http/HttpClient"; export default function PluginMarketComponent() { - const [marketPluginList, setMarketPluginList] = useState([]) - const [searchKeyword, setSearchKeyword] = useState("") - const [currentPage, setCurrentPage] = useState(1) - const [totalItems, setTotalItems] = useState(0) - const [loading, setLoading] = useState(false) - const pageSize = 10 // 每页显示的项目数量 + const [marketPluginList, setMarketPluginList] = useState< + PluginMarketCardVO[] + >([]); + const [totalCount, setTotalCount] = useState(0); + const [nowPage, setNowPage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(""); + const [loading, setLoading] = useState(false); + const pageSize = 10; useEffect(() => { - fetchPlugins(searchKeyword, currentPage) - }, [currentPage]) + initData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - // 获取插件列表,整合了搜索和分页功能 - async function fetchPlugins(keyword: string = "", page: number = 1): Promise { - setLoading(true) - try { - // 实际应用中,这里应该调用API获取数据 - const result = await mockFetchPlugins(keyword, page, pageSize) - setMarketPluginList(result.data) - setTotalItems(result.total) - } finally { - setLoading(false) - } - } - - // 模拟从API获取数据 - async function mockFetchPlugins(keyword: string, page: number, pageSize: number): Promise<{ data: PluginMarketCardVO[], total: number }> { - // 模拟API延迟 - await new Promise(resolve => setTimeout(resolve, 300)) - - // 创建模拟数据 - const allPlugins: PluginMarketCardVO[] = [] - const totalPlugins = 50 // 模拟总数据量 - - for (let i = 0; i < totalPlugins; i++) { - allPlugins.push(new PluginMarketCardVO({ - pluginId: `plugin-${i}`, - description: `这是插件 ${i} 的描述,包含一些详细信息`, - name: `插件 ${i}`, - author: `/author-${i % 5}`, // 模拟5个不同的作者 - version: `0.${i % 10}`, - githubURL: `https://github.com/author-${i % 5}/plugin-${i}`, - starCount: 10 + Math.floor(Math.random() * 100) - })) - } - - // 根据关键词过滤 - const filtered = keyword - ? allPlugins.filter(p => - p.name.toLowerCase().includes(keyword.toLowerCase()) || - p.description.toLowerCase().includes(keyword.toLowerCase())) - : allPlugins - - // 分页处理 - const start = (page - 1) * pageSize - const end = start + pageSize - const paginatedData = filtered.slice(start, end) - - return { - data: paginatedData, - total: filtered.length - } + function initData() { + getPluginList(); } function onInputSearchKeyword(keyword: string) { - setSearchKeyword(keyword) - setCurrentPage(1) // 搜索时重置为第一页 - debounceSearch(keyword) + // 这里记得加防抖,暂时没加 + setSearchKeyword(keyword); + setNowPage(1); + getPluginList(1, keyword); } - const debounceSearch = useCallback( - debounce((keyword: string) => { - fetchPlugins(keyword, 1) - }, 500), [] - ) + function getPluginList( + page: number = nowPage, + keyword: string = searchKeyword + ) { + setLoading(true); + spaceClient.getMarketPlugins(page, pageSize, keyword).then((res) => { + setMarketPluginList( + res.plugins.map( + (marketPlugin) => + new PluginMarketCardVO({ + author: marketPlugin.author, + description: marketPlugin.description, + githubURL: marketPlugin.repository, + name: marketPlugin.name, + pluginId: String(marketPlugin.ID), + starCount: marketPlugin.stars, + version: "version" in marketPlugin ? String(marketPlugin.version) : "1.0.0", // Default version if not provided + }) + ) + ); + setTotalCount(res.total); + setLoading(false); + console.log("market plugins:", res); + }).catch(error => { + console.error("获取插件列表失败:", error); + setLoading(false); + }); + } function handlePageChange(page: number) { - setCurrentPage(page) + setNowPage(page); + getPluginList(page); } return ( @@ -111,11 +91,11 @@ export default function PluginMarketComponent() { )) )}
- {totalItems > 0 && ( + {totalCount > 0 && (
Date: Tue, 29 Apr 2025 15:05:15 +0800 Subject: [PATCH 048/121] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/plugins/plugin-installed/PluginInstalledComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index be770733..39c2be86 100644 --- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -15,7 +15,7 @@ export default function PluginInstalledComponent() { useEffect(() => { - initData() + initData(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []) From 44b005ffdd53acd8c52dd8b0c63c9b07ab2649d9 Mon Sep 17 00:00:00 2001 From: chris <1637083533@qq.com> Date: Tue, 29 Apr 2025 15:32:06 +0800 Subject: [PATCH 049/121] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/home/bots/page.tsx | 280 +++++++++--------- .../PluginInstalledComponent.tsx | 264 +++-------------- web/src/app/infra/http/HttpClient.ts | 26 +- 3 files changed, 200 insertions(+), 370 deletions(-) diff --git a/web/src/app/home/bots/page.tsx b/web/src/app/home/bots/page.tsx index a3570eaf..c2760b01 100644 --- a/web/src/app/home/bots/page.tsx +++ b/web/src/app/home/bots/page.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useEffect, useState } from "react"; import styles from "./botConfig.module.css"; @@ -8,155 +8,161 @@ import { BotCardVO } from "@/app/home/bots/components/bot-card/BotCardVO"; import { Modal, notification, Spin } from "antd"; import BotForm from "@/app/home/bots/components/bot-form/BotForm"; import BotCard from "@/app/home/bots/components/bot-card/BotCard"; -import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent" +import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import { httpClient } from "@/app/infra/http/HttpClient"; import { Bot } from "@/app/infra/api/api-types"; export default function BotConfigPage() { - const router = useRouter(); - const [pageShowRule, setPageShowRule] = useState(BotConfigPageShowRule.NO_BOT) - const [modalOpen, setModalOpen] = useState(false); - const [botList, setBotList] = useState([]) - const [isEditForm, setIsEditForm] = useState(false) - const [nowSelectedBotCard, setNowSelectedBotCard] = useState() - const [isLoading, setIsLoading] = useState(false) + const router = useRouter(); + const [pageShowRule, setPageShowRule] = useState( + BotConfigPageShowRule.NO_BOT + ); + const [modalOpen, setModalOpen] = useState(false); + const [botList, setBotList] = useState([]); + const [isEditForm, setIsEditForm] = useState(false); + const [nowSelectedBotCard, setNowSelectedBotCard] = useState(); + const [isLoading, setIsLoading] = useState(false); + useEffect(() => { + // TODO:补齐加载转圈逻辑 + setIsLoading(true); + checkHasLLM().then((hasLLM) => { + if (hasLLM) { + getBotList(); + } else { + setPageShowRule(BotConfigPageShowRule.NO_LLM); + setIsLoading(false); + } + }); + }, []); - useEffect(() => { - // TODO:补齐加载转圈逻辑 - setIsLoading(true) - checkHasLLM().then((hasLLM) => { - if (hasLLM) { - getBotList() - } else { - setPageShowRule(BotConfigPageShowRule.NO_LLM) - setIsLoading(false) - } - }) - }, []) + async function checkHasLLM(): Promise { + // NOT IMPL + return true; + } - async function checkHasLLM(): Promise { - // NOT IMPL - return true - } + function getBotList() { + httpClient + .getBots() + .then((resp) => { + const botList: BotCardVO[] = resp.bots.map((bot: Bot) => { + return new BotCardVO({ + adapter: bot.adapter, + description: bot.description, + id: bot.uuid || "", + name: bot.name, + updateTime: bot.updated_at || "", + pipelineName: bot.use_pipeline_name || "" + }); + }); + if (botList.length === 0) { + setPageShowRule(BotConfigPageShowRule.NO_BOT); + } else { + setPageShowRule(BotConfigPageShowRule.HAVE_BOT); + } + setBotList(botList); + }) + .catch((err) => { + console.error("get bot list error", err); + // TODO HACK: need refactor to hook mode Notification, but it's not working under render + notification.error({ + message: "获取机器人列表失败", + description: err.message, + placement: "bottomRight" + }); + }) + .finally(() => { + setIsLoading(false); + }); + } - function getBotList() { - httpClient.getBots().then((resp) => { - const botList: BotCardVO[] = resp.bots.map((bot: Bot) => { - return new BotCardVO({ - adapter: bot.adapter, - description: bot.description, - id: bot.uuid || "", - name: bot.name, - updateTime: bot.updated_at || "", - pipelineName: bot.use_pipeline_name || "", - }) - }) - if (botList.length === 0) { - setPageShowRule(BotConfigPageShowRule.NO_BOT) - } else { - setPageShowRule(BotConfigPageShowRule.HAVE_BOT) - } - setBotList(botList) - }).catch((err) => { - console.error("get bot list error", err) - // TODO HACK: need refactor to hook mode Notification, but it's not working under render - notification.error({ - message: "获取机器人列表失败", - description: err.message, - placement: "bottomRight", - }) - }).finally(() => { - setIsLoading(false) - }) - } + function handleCreateBotClick() { + setIsEditForm(false); + setNowSelectedCard(undefined); + setModalOpen(true); + } - function handleCreateBotClick() { - setIsEditForm(false) - setNowSelectedCard(undefined) - setModalOpen(true); - } + function setNowSelectedCard(cardVO: BotCardVO | undefined) { + setNowSelectedBotCard(cardVO); + } - function setNowSelectedCard(cardVO: BotCardVO | undefined) { - setNowSelectedBotCard(cardVO) - } + function selectBot(cardVO: BotCardVO) { + setIsEditForm(true); + setNowSelectedCard(cardVO); + console.log("set now vo", cardVO); + setModalOpen(true); + } - function selectBot(cardVO: BotCardVO) { - setIsEditForm(true) - setNowSelectedCard(cardVO) - console.log("set now vo", cardVO) - setModalOpen(true) - } + return ( +
+ + setModalOpen(false)} + onCancel={() => setModalOpen(false)} + width={700} + footer={null} + destroyOnClose={true} + > + { + getBotList(); + setModalOpen(false); + }} + onFormCancel={() => setModalOpen(false)} + /> + + {pageShowRule === BotConfigPageShowRule.NO_LLM && ( + { + router.push("/home/models"); + }} + /> + )} - return ( -
- - {/* 删除 spin,使用 spin 会导致盒子塌陷。 */} - setModalOpen(false)} - onCancel={() => setModalOpen(false)} - width={700} - footer={null} - destroyOnClose={true} - > - { - getBotList() - setModalOpen(false) - }} - onFormCancel={() => setModalOpen(false)} - /> - - {pageShowRule === BotConfigPageShowRule.NO_LLM && - { - router.push("/home/models"); - }} - /> - } - - {pageShowRule === BotConfigPageShowRule.NO_BOT && - - } - - {pageShowRule === BotConfigPageShowRule.HAVE_BOT && -
- {botList.map(cardVO => { - return ( -
{ selectBot(cardVO) }} - > - -
) - })} - -
- } + {pageShowRule === BotConfigPageShowRule.NO_BOT && ( + + )} + + {/* 注意:其余的返回内容需要保持在Spin组件外部 */} + {pageShowRule === BotConfigPageShowRule.HAVE_BOT && ( +
+ {botList.map((cardVO) => { + return ( +
{ + selectBot(cardVO); + }} + > + +
+ ); + })} +
- - ) + )} +
+ ); } enum BotConfigPageShowRule { - NO_LLM, - NO_BOT, - HAVE_BOT, -} \ No newline at end of file + NO_LLM, + NO_BOT, + HAVE_BOT +} diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx index 39c2be86..b6e149ae 100644 --- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx @@ -1,228 +1,60 @@ -"use client" +"use client"; +import { useState, useEffect } from "react"; import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"; import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; -import { useEffect, useState } from "react"; import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent"; import styles from "@/app/home/plugins/plugins.module.css"; import { Modal, Input } from "antd"; import { GithubOutlined } from "@ant-design/icons"; +import { httpClient } from "@/app/infra/http/HttpClient"; export default function PluginInstalledComponent() { - const [pluginList, setPluginList] = useState([]) - const [modalOpen, setModalOpen] = useState(false) - const [githubURL, setGithubURL] = useState("") - + const [pluginList, setPluginList] = useState([]); + const [modalOpen, setModalOpen] = useState(false); + const [githubURL, setGithubURL] = useState(""); useEffect(() => { initData(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, []); function initData() { - getPluginList().then((value) => { - setPluginList(value) - }) + getPluginList(); } - async function getPluginList() { - return [ - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), new PluginCardVO({ - description: "一般的描述", - handlerCount: 0, - name: "插件AAA", - author: "/hana", - version: "0.1", - isInitialized: false - }), - - - ] + function getPluginList() { + httpClient.getPlugins().then((value) => { + setPluginList( + value.plugins.map((plugin) => { + return new PluginCardVO({ + author: plugin.author, + description: plugin.description.zh_CN, + handlerCount: 0, + name: plugin.name, + version: plugin.version, + isInitialized: plugin.status === "initialized" + }); + }) + ); + }); } function handleModalConfirm() { - installPlugin(githubURL) - setModalOpen(false) + installPlugin(githubURL); + setModalOpen(false); } function installPlugin(url: string) { - // TODO 接安装Plugin的接口 - console.log("installPlugin: ", url) + httpClient + .installPluginFromGithub(url) + .then(() => { + // 安装后重新拉取 + getPluginList(); + }) + .catch((err) => { + console.log("error when install plugin:", err); + }); } return (
@@ -231,25 +63,19 @@ export default function PluginInstalledComponent() {
- 从 GitHub 安装插件
} - centered open={modalOpen} - onOk={() => handleModalConfirm()} + onOk={handleModalConfirm} onCancel={() => setModalOpen(false)} - width={500} destroyOnClose={true} >
-
- 目前仅支持从 GitHub 安装 -
+
目前仅支持从 GitHub 安装
- { - pluginList.map((vo, index) => { - return
+ {pluginList.map((vo, index) => { + return ( +
- }) - } + ); + })} { - setModalOpen(true) + setModalOpen(true); }} />
- ) + ); } diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts index a8bb53cc..58462059 100644 --- a/web/src/app/infra/http/HttpClient.ts +++ b/web/src/app/infra/http/HttpClient.ts @@ -26,7 +26,8 @@ import { ApiRespSystemInfo, ApiRespAsyncTasks, ApiRespAsyncTask, - ApiRespUserToken, MarketPluginResponse + ApiRespUserToken, + MarketPluginResponse } from "../api/api-types"; import { notification } from "antd"; @@ -50,22 +51,19 @@ export interface RequestConfig extends AxiosRequestConfig { class HttpClient { private instance: AxiosInstance; - private disableToken: boolean = false + private disableToken: boolean = false; // 暂不需要SSR // private ssrInstance: AxiosInstance | null = null - constructor( - baseURL?: string, - disableToken?: boolean - ) { + constructor(baseURL?: string, disableToken?: boolean) { this.instance = axios.create({ baseURL: baseURL || this.getBaseUrl(), timeout: 15000, headers: { - "Content-Type": "application/json", + "Content-Type": "application/json" } }); - this.disableToken = disableToken || false + this.disableToken = disableToken || false; this.initInterceptors(); } @@ -129,9 +127,9 @@ class HttpClient { const errMessage = data?.message || error.message; switch (status) { - case 401: - window.location.href = "/login"; - break; + // case 401: + // window.location.href = "/login"; + // break; case 403: console.error("Permission denied:", errMessage); break; @@ -358,7 +356,7 @@ class HttpClient { public getMarketPlugins( page: number, page_size: number, - query: string, + query: string ): Promise { return this.post(`/api/v1/market/plugins`, { page, @@ -366,7 +364,7 @@ class HttpClient { query, sort_by: "stars", sort_order: "DESC" - }) + }); } public installPluginFromGithub( source: string @@ -415,4 +413,4 @@ class HttpClient { export const httpClient = new HttpClient("https://version-4.langbot.dev"); // 临时写法,未来两种Client都继承自HttpClient父类,不允许共享方法 -export const spaceClient = new HttpClient("https://space.langbot.app") +export const spaceClient = new HttpClient("https://space.langbot.app"); From db547fb3788bf1f4ae703196e3324f97c2a48e89 Mon Sep 17 00:00:00 2001 From: chris <1637083533@qq.com> Date: Tue, 29 Apr 2025 15:36:03 +0800 Subject: [PATCH 050/121] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin-card/PluginCardComponent.tsx | 114 +++++++++--------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index 9efbbe9e..53b3ecf9 100644 --- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -1,67 +1,65 @@ -import styles from "./pluginCard.module.css" +import styles from "./pluginCard.module.css"; import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO"; -import { GithubOutlined, LinkOutlined, ToolOutlined } from '@ant-design/icons'; -import { Switch, Tag } from 'antd' +import { GithubOutlined, LinkOutlined, ToolOutlined } from "@ant-design/icons"; +import { Switch, Tag } from "antd"; import { useState } from "react"; import { httpClient } from "@/app/infra/http/HttpClient"; export default function PluginCardComponent({ - cardVO + cardVO }: { - cardVO: PluginCardVO + cardVO: PluginCardVO; }) { - const [initialized, setInitialized] = useState(cardVO.isInitialized); - const [switchEnable, setSwitchEnable] = useState(true); + const [initialized, setInitialized] = useState(cardVO.isInitialized); + const [switchEnable, setSwitchEnable] = useState(true); - function handleEnable() { - setSwitchEnable(false); - httpClient - .togglePlugin(cardVO.author, cardVO.name, !initialized) - .then(() => { - setInitialized(!initialized); - }) - .catch((err) => { - console.log("error: ", err); - }) - .finally(() => { - setSwitchEnable(true); - }); - } - - return ( -
- {/* header */} -
- {/* left author */} -
{cardVO.author}
- {/* right icon & version */} -
- - v{cardVO.version} -
-
- {/* content */} -
-
{cardVO.name}
-
{cardVO.description}
-
- {/* footer */} -
-
-
- - 1 -
- -
-
+ function handleEnable() { + setSwitchEnable(false); + httpClient + .togglePlugin(cardVO.author, cardVO.name, !initialized) + .then(() => { + setInitialized(!initialized); + }) + .catch((err) => { + console.log("error: ", err); + }) + .finally(() => { + setSwitchEnable(true); + }); + } + return ( +
+ {/* header */} +
+ {/* left author */} +
{cardVO.author}
+ {/* right icon & version */} +
+ + v{cardVO.version}
- ); -} +
+ {/* content */} +
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */} +
+
+
+ + 1 +
+ +
+ + +
+
+ ); +} \ No newline at end of file From 5c162009eee90d2594cbc879be7bb7d970ebe675 Mon Sep 17 00:00:00 2001 From: chris <1637083533@qq.com> Date: Tue, 29 Apr 2025 15:41:17 +0800 Subject: [PATCH 051/121] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app/infra/http/HttpClient.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts index 58462059..7183ba59 100644 --- a/web/src/app/infra/http/HttpClient.ts +++ b/web/src/app/infra/http/HttpClient.ts @@ -127,9 +127,9 @@ class HttpClient { const errMessage = data?.message || error.message; switch (status) { - // case 401: - // window.location.href = "/login"; - // break; + case 401: + window.location.href = "/login"; + break; case 403: console.error("Permission denied:", errMessage); break; From f1beb108930f3f93aaf64bd0e680efd9e816a209 Mon Sep 17 00:00:00 2001 From: chris <1637083533@qq.com> Date: Tue, 29 Apr 2025 16:25:58 +0800 Subject: [PATCH 052/121] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8D=A1=E7=89=87=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin-card/PluginCardComponent.tsx | 27 +++--- .../plugin-card/pluginCard.module.css | 21 +++++ .../PluginMarketCardComponent.tsx | 91 +++++++++---------- .../pluginMarketCard.module.css | 1 + 4 files changed, 79 insertions(+), 61 deletions(-) diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx index 53b3ecf9..8999534f 100644 --- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx +++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx @@ -46,20 +46,23 @@ export default function PluginCardComponent({
{/* footer */}
-
-
- - 1 +
+
+
+ + 1 +
+ +
+
+
-
- -
); -} \ No newline at end of file +} diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css index 3ec44fd4..1d09d462 100644 --- a/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css +++ b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css @@ -41,8 +41,29 @@ .cardFooter { width: 90%; height: 30px; + position: relative; } +.footerContainer { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.linkAndToolContainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +} + +.switchContainer { + display: flex; + justify-content: flex-end; +} .fontGray { color: #6C6C6C; diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx index 5d96c57f..dcc1dfd0 100644 --- a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx +++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx @@ -1,56 +1,49 @@ -import styles from "./pluginMarketCard.module.css" -import {GithubOutlined, StarOutlined} from '@ant-design/icons'; -import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; -import {Button} from "antd"; +import styles from "./pluginMarketCard.module.css"; +import { GithubOutlined, StarOutlined } from "@ant-design/icons"; +import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO"; +import { Button } from "antd"; export default function PluginMarketCardComponent({ - cardVO + cardVO }: { - cardVO: PluginMarketCardVO + cardVO: PluginMarketCardVO; }) { + function handleInstallClick(pluginId: string) { + console.log("Install plugin: ", pluginId); + } - - function handleInstallClick (pluginId: string) { - console.log("Install plugin: ", pluginId) - } - - return ( -
- {/* header */} -
- {/* left author */} -
{cardVO.author}
- {/* right icon */} - -
- {/* content */} -
-
{cardVO.name}
-
{cardVO.description}
-
- {/* footer */} -
-
-
- - {cardVO.starCount} -
-
- -
+ return ( +
+ {/* header */} +
+ {/* left author */} +
{cardVO.author}
+ {/* right icon */} + +
+ {/* content */} +
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */} +
+
+
+ + {cardVO.starCount} +
- ); + +
+
+ ); } diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css index 33297b09..97a5e224 100644 --- a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css +++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css @@ -71,5 +71,6 @@ color: #6062E7; align-self: center; justify-content: space-between; + align-items: center; } } \ No newline at end of file From 209f16af7609ea80b7f8f442bdbffbed4dc82a17 Mon Sep 17 00:00:00 2001 From: "Junyan Qin (Chin)" Date: Tue, 29 Apr 2025 17:24:07 +0800 Subject: [PATCH 053/121] style: introduce ruff as linter and formatter (#1356) * style: remove necessary imports * style: fix F841 * style: fix F401 * style: fix F811 * style: fix E402 * style: fix E721 * style: fix E722 * style: fix E722 * style: fix F541 * style: ruff format * style: all passed * style: add ruff in deps * style: more ignores in ruff.toml * style: add pre-commit --- .gitignore | 1 + .pre-commit-config.yaml | 9 + libs/dify_service_api/__init__.py | 6 +- libs/dify_service_api/test.py | 27 +- libs/dify_service_api/v1/client.py | 85 ++--- libs/dify_service_api/v1/client_test.py | 6 +- libs/dingtalk_api/EchoHandler.py | 5 +- libs/dingtalk_api/api.py | 171 +++++---- libs/dingtalk_api/dingtalkevent.py | 32 +- libs/official_account_api/api.py | 234 +++++++------ libs/official_account_api/oaevent.py | 31 +- libs/qq_official_api/api.py | 210 ++++++------ libs/qq_official_api/qqofficialevent.py | 64 ++-- libs/wecom_api/WXBizMsgCrypt3.py | 33 +- libs/wecom_api/api.py | 280 ++++++++------- libs/wecom_api/ierror.py | 4 +- libs/wecom_api/wecomevent.py | 34 +- main.py | 29 +- pkg/api/http/controller/group.py | 46 ++- pkg/api/http/controller/groups/logs.py | 17 +- pkg/api/http/controller/groups/pipelines.py | 30 +- .../controller/groups/platform/adapters.py | 28 +- .../http/controller/groups/platform/bots.py | 17 +- pkg/api/http/controller/groups/plugins.py | 87 ++--- .../http/controller/groups/provider/models.py | 17 +- .../controller/groups/provider/requesters.py | 26 +- pkg/api/http/controller/groups/stats.py | 22 +- pkg/api/http/controller/groups/system.py | 45 ++- pkg/api/http/controller/groups/user.py | 29 +- pkg/api/http/controller/main.py | 87 ++--- pkg/api/http/service/bot.py | 34 +- pkg/api/http/service/model.py | 37 +- pkg/api/http/service/pipeline.py | 67 ++-- pkg/api/http/service/user.py | 12 +- pkg/audit/__init__.py | 2 +- pkg/audit/center/apigroup.py | 18 +- pkg/audit/center/groups/main.py | 38 +- pkg/audit/center/groups/plugin.py | 50 ++- pkg/audit/center/groups/usage.py | 73 ++-- pkg/audit/center/v2.py | 15 +- pkg/audit/identifier.py | 30 +- pkg/command/cmdmgr.py | 59 ++-- pkg/command/entities.py | 12 +- pkg/command/errors.py | 17 +- pkg/command/operator.py | 17 +- pkg/command/operators/cmd.py | 39 +-- pkg/command/operators/delc.py | 56 ++- pkg/command/operators/func.py | 9 +- pkg/command/operators/help.py | 12 +- pkg/command/operators/last.py | 43 ++- pkg/command/operators/list.py | 32 +- pkg/command/operators/model.py | 70 ++-- pkg/command/operators/next.py | 43 ++- pkg/command/operators/ollama.py | 71 ++-- pkg/command/operators/plugin.py | 195 ++++++----- pkg/command/operators/prompt.py | 23 +- pkg/command/operators/resend.py | 16 +- pkg/command/operators/reset.py | 17 +- pkg/command/operators/update.py | 24 +- pkg/command/operators/version.py | 20 +- pkg/config/impls/json.py | 23 +- pkg/config/impls/pymodule.py | 6 +- pkg/config/impls/yaml.py | 25 +- pkg/config/manager.py | 45 +-- pkg/config/model.py | 2 +- pkg/core/app.py | 64 +++- pkg/core/boot.py | 29 +- pkg/core/bootutils/config.py | 4 +- pkg/core/bootutils/deps.py | 83 ++--- pkg/core/bootutils/files.py | 18 +- pkg/core/bootutils/log.py | 36 +- pkg/core/entities.py | 45 ++- pkg/core/migration.py | 18 +- .../m001_sensitive_word_migration.py | 20 +- .../m002_openai_config_migration.py | 19 +- ...m003_anthropic_requester_cfg_completion.py | 19 +- .../m004_moonshot_cfg_completion.py | 15 +- .../m005_deepseek_cfg_completion.py | 17 +- pkg/core/migrations/m006_vision_config.py | 8 +- pkg/core/migrations/m007_qcg_center_url.py | 14 +- .../m008_ad_fixwin_config_migrate.py | 20 +- pkg/core/migrations/m009_msg_truncator_cfg.py | 8 +- .../m010_ollama_requester_config.py | 10 +- .../migrations/m011_command_prefix_config.py | 8 +- pkg/core/migrations/m012_runner_config.py | 4 +- pkg/core/migrations/m013_http_api_config.py | 23 +- .../migrations/m014_force_delay_config.py | 8 +- pkg/core/migrations/m015_gitee_ai_config.py | 17 +- pkg/core/migrations/m016_dify_service_api.py | 15 +- .../m017_dify_api_timeout_params.py | 12 +- pkg/core/migrations/m018_xai_config.py | 12 +- pkg/core/migrations/m019_zhipuai_config.py | 12 +- pkg/core/migrations/m020_wecom_config.py | 28 +- pkg/core/migrations/m021_lark_config.py | 26 +- pkg/core/migrations/m022_lmstudio_config.py | 10 +- .../migrations/m023_siliconflow_config.py | 18 +- pkg/core/migrations/m024_discord_config.py | 18 +- pkg/core/migrations/m025_gewechat_config.py | 26 +- pkg/core/migrations/m026_qqofficial_config.py | 22 +- .../m027_wx_official_account_config.py | 26 +- .../m028_aliyun_requester_config.py | 14 +- .../m029_dashscope_app_api_config.py | 22 +- pkg/core/migrations/m030_lark_config_cmpl.py | 6 +- pkg/core/migrations/m031_dingtalk_config.py | 22 +- pkg/core/migrations/m032_volcark_config.py | 14 +- .../migrations/m033_dify_thinking_config.py | 17 +- .../m034_gewechat_file_url_config.py | 6 +- pkg/core/migrations/m035_wxoa_mode.py | 2 +- .../migrations/m036_wxoa_loading_message.py | 2 +- pkg/core/migrations/m037_mcp_config.py | 6 +- pkg/core/note.py | 15 +- pkg/core/notes/n001_classic_msgs.py | 10 +- .../notes/n002_selection_mode_on_windows.py | 14 +- pkg/core/notes/n003_print_version.py | 12 +- pkg/core/stage.py | 12 +- pkg/core/stages/build_app.py | 27 +- pkg/core/stages/genkeys.py | 11 +- pkg/core/stages/load_config.py | 85 +++-- pkg/core/stages/migrate.py | 39 +-- pkg/core/stages/setup_logger.py | 13 +- pkg/core/stages/show_notes.py | 14 +- pkg/core/taskmgr.py | 114 +++--- pkg/discover/engine.py | 103 ++++-- pkg/entity/persistence/base.py | 1 - pkg/entity/persistence/bot.py | 12 +- pkg/entity/persistence/metadata.py | 1 + pkg/entity/persistence/model.py | 13 +- pkg/entity/persistence/pipeline.py | 24 +- pkg/entity/persistence/plugin.py | 12 +- pkg/entity/persistence/user.py | 12 +- pkg/persistence/database.py | 1 + pkg/persistence/databases/sqlite.py | 8 +- pkg/persistence/mgr.py | 80 +++-- pkg/persistence/migration.py | 2 + .../migrations/dbm001_migrate_v3_config.py | 4 +- pkg/pipeline/bansess/bansess.py | 26 +- pkg/pipeline/cntfilter/cntfilter.py | 78 ++--- pkg/pipeline/cntfilter/entities.py | 9 +- pkg/pipeline/cntfilter/filter.py | 18 +- .../cntfilter/filters/baiduexamine.py | 44 ++- pkg/pipeline/cntfilter/filters/banwords.py | 15 +- pkg/pipeline/cntfilter/filters/cntignore.py | 16 +- pkg/pipeline/controller.py | 51 +-- pkg/pipeline/entities.py | 11 +- pkg/pipeline/longtext/longtext.py | 69 ++-- pkg/pipeline/longtext/strategies/forward.py | 26 +- pkg/pipeline/longtext/strategies/image.py | 62 ++-- pkg/pipeline/longtext/strategy.py | 13 +- pkg/pipeline/msgtrun/msgtrun.py | 25 +- pkg/pipeline/msgtrun/truncator.py | 8 +- pkg/pipeline/msgtrun/truncators/round.py | 10 +- pkg/pipeline/pipelinemgr.py | 155 ++++++--- pkg/pipeline/pool.py | 2 +- pkg/pipeline/preproc/preproc.py | 49 +-- pkg/pipeline/process/handler.py | 1 - pkg/pipeline/process/handlers/chat.py | 54 +-- pkg/pipeline/process/handlers/command.py | 59 ++-- pkg/pipeline/process/process.py | 19 +- pkg/pipeline/ratelimit/algo.py | 27 +- pkg/pipeline/ratelimit/algos/fixedwin.py | 30 +- pkg/pipeline/ratelimit/ratelimit.py | 24 +- pkg/pipeline/respback/respback.py | 40 +-- pkg/pipeline/resprule/entities.py | 1 - pkg/pipeline/resprule/resprule.py | 32 +- pkg/pipeline/resprule/rule.py | 11 +- pkg/pipeline/resprule/rules/atbot.py | 20 +- pkg/pipeline/resprule/rules/prefix.py | 14 +- pkg/pipeline/resprule/rules/random.py | 12 +- pkg/pipeline/resprule/rules/regexp.py | 12 +- pkg/pipeline/stage.py | 12 +- pkg/pipeline/wrapper/wrapper.py | 98 +++--- pkg/platform/adapter.py | 30 +- pkg/platform/botmgr.py | 101 +++--- pkg/platform/sources/aiocqhttp.py | 131 ++++--- pkg/platform/sources/dingtalk.py | 113 +++--- pkg/platform/sources/discord.py | 104 +++--- pkg/platform/sources/gewechat.py | 324 ++++++++++-------- pkg/platform/sources/lark.py | 168 +++++---- pkg/platform/sources/nakuru.py | 154 +++++---- pkg/platform/sources/officialaccount.py | 108 +++--- pkg/platform/sources/qqbotpy.py | 202 ++++++----- pkg/platform/sources/qqofficial.py | 263 +++++++------- pkg/platform/sources/telegram.py | 134 ++++---- pkg/platform/sources/wecom.py | 143 ++++---- pkg/platform/types/base.py | 15 +- pkg/platform/types/entities.py | 16 +- pkg/platform/types/events.py | 25 +- pkg/platform/types/message.py | 213 +++++++----- pkg/plugin/__init__.py | 2 +- pkg/plugin/context.py | 86 +++-- pkg/plugin/errors.py | 9 +- pkg/plugin/events.py | 14 +- pkg/plugin/host.py | 6 +- pkg/plugin/installer.py | 12 +- pkg/plugin/installers/github.py | 92 ++--- pkg/plugin/loader.py | 5 +- pkg/plugin/loaders/classic.py | 114 +++--- pkg/plugin/loaders/manifest.py | 36 +- pkg/plugin/manager.py | 161 ++++----- pkg/plugin/models.py | 10 +- pkg/provider/entities.py | 27 +- pkg/provider/modelmgr/errors.py | 2 +- pkg/provider/modelmgr/modelmgr.py | 55 +-- pkg/provider/modelmgr/requester.py | 17 +- .../modelmgr/requesters/anthropicmsgs.py | 108 +++--- .../modelmgr/requesters/bailianchatcmpl.py | 2 - pkg/provider/modelmgr/requesters/chatcmpl.py | 68 ++-- .../modelmgr/requesters/deepseekchatcmpl.py | 16 +- .../modelmgr/requesters/giteeaichatcmpl.py | 18 +- .../modelmgr/requesters/lmstudiochatcmpl.py | 2 - .../modelmgr/requesters/moonshotchatcmpl.py | 19 +- .../modelmgr/requesters/ollamachat.py | 61 ++-- .../requesters/siliconflowchatcmpl.py | 2 - .../modelmgr/requesters/volcarkchatcmpl.py | 2 - .../modelmgr/requesters/xaichatcmpl.py | 2 - .../modelmgr/requesters/zhipuaichatcmpl.py | 2 - pkg/provider/modelmgr/token.py | 7 +- pkg/provider/runner.py | 16 +- pkg/provider/runners/dashscopeapi.py | 228 ++++++------ pkg/provider/runners/difysvapi.py | 200 ++++++----- pkg/provider/runners/localagent.py | 34 +- pkg/provider/session/sessionmgr.py | 21 +- pkg/provider/tools/entities.py | 4 - pkg/provider/tools/loader.py | 15 +- pkg/provider/tools/loaders/mcp.py | 74 ++-- pkg/provider/tools/loaders/plugin.py | 30 +- pkg/provider/tools/toolmgr.py | 34 +- pkg/utils/announce.py | 67 ++-- pkg/utils/constants.py | 4 +- pkg/utils/funcschema.py | 53 ++- pkg/utils/image.py | 100 +++--- pkg/utils/importutil.py | 43 +++ pkg/utils/ip.py | 11 +- pkg/utils/logcache.py | 9 +- pkg/utils/pkgmgr.py | 27 +- pkg/utils/proxy.py | 26 +- pkg/utils/version.py | 134 ++++---- requirements.txt | 4 +- res/scripts/publish_announcement.py | 24 +- ruff.toml | 38 ++ 240 files changed, 5307 insertions(+), 4689 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 pkg/utils/importutil.py create mode 100644 ruff.toml diff --git a/.gitignore b/.gitignore index 17271201..0a14ca5b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ qcapi claude.json bard.json /*yaml +!.pre-commit-config.yaml !components.yaml !/docker-compose.yaml data/labels/instance_id.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..6efb3e3e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.11.7 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format \ No newline at end of file diff --git a/libs/dify_service_api/__init__.py b/libs/dify_service_api/__init__.py index 5f178abb..bd6f6d4f 100644 --- a/libs/dify_service_api/__init__.py +++ b/libs/dify_service_api/__init__.py @@ -1,2 +1,4 @@ -from .v1 import client -from .v1 import errors \ No newline at end of file +from .v1 import client as client +from .v1 import errors as errors + +__all__ = ['client', 'errors'] diff --git a/libs/dify_service_api/test.py b/libs/dify_service_api/test.py index faf7571a..b7e2281c 100644 --- a/libs/dify_service_api/test.py +++ b/libs/dify_service_api/test.py @@ -8,25 +8,33 @@ import json class TestDifyClient: async def test_chat_messages(self): - cln = client.AsyncDifyServiceClient(api_key=os.getenv("DIFY_API_KEY"), base_url=os.getenv("DIFY_BASE_URL")) + cln = client.AsyncDifyServiceClient( + api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL') + ) - async for chunk in cln.chat_messages(inputs={}, query="调用工具查看现在几点?", user="test"): + async for chunk in cln.chat_messages( + inputs={}, query='调用工具查看现在几点?', user='test' + ): print(json.dumps(chunk, ensure_ascii=False, indent=4)) async def test_upload_file(self): - cln = client.AsyncDifyServiceClient(api_key=os.getenv("DIFY_API_KEY"), base_url=os.getenv("DIFY_BASE_URL")) + cln = client.AsyncDifyServiceClient( + api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL') + ) - file_bytes = open("img.png", "rb").read() + file_bytes = open('img.png', 'rb').read() print(type(file_bytes)) - file = ("img2.png", file_bytes, "image/png") + file = ('img2.png', file_bytes, 'image/png') - resp = await cln.upload_file(file=file, user="test") + resp = await cln.upload_file(file=file, user='test') print(json.dumps(resp, ensure_ascii=False, indent=4)) async def test_workflow_run(self): - cln = client.AsyncDifyServiceClient(api_key=os.getenv("DIFY_API_KEY"), base_url=os.getenv("DIFY_BASE_URL")) + cln = client.AsyncDifyServiceClient( + api_key=os.getenv('DIFY_API_KEY'), base_url=os.getenv('DIFY_BASE_URL') + ) # resp = await cln.workflow_run(inputs={}, user="test") # # print(json.dumps(resp, ensure_ascii=False, indent=4)) @@ -34,11 +42,12 @@ class TestDifyClient: chunks = [] ignored_events = ['text_chunk'] - async for chunk in cln.workflow_run(inputs={}, user="test"): + async for chunk in cln.workflow_run(inputs={}, user='test'): if chunk['event'] in ignored_events: continue chunks.append(chunk) print(json.dumps(chunks, ensure_ascii=False, indent=4)) -if __name__ == "__main__": + +if __name__ == '__main__': asyncio.run(TestDifyClient().test_chat_messages()) diff --git a/libs/dify_service_api/v1/client.py b/libs/dify_service_api/v1/client.py index 70a804b7..35defe2c 100644 --- a/libs/dify_service_api/v1/client.py +++ b/libs/dify_service_api/v1/client.py @@ -12,11 +12,11 @@ class AsyncDifyServiceClient: api_key: str base_url: str - + def __init__( self, api_key: str, - base_url: str = "https://api.dify.ai/v1", + base_url: str = 'https://api.dify.ai/v1', ) -> None: self.api_key = api_key self.base_url = base_url @@ -26,76 +26,81 @@ class AsyncDifyServiceClient: inputs: dict[str, typing.Any], query: str, user: str, - response_mode: str = "streaming", # 当前不支持 blocking - conversation_id: str = "", + response_mode: str = 'streaming', # 当前不支持 blocking + conversation_id: str = '', files: list[dict[str, typing.Any]] = [], timeout: float = 30.0, ) -> typing.AsyncGenerator[dict[str, typing.Any], None]: """发送消息""" - if response_mode != "streaming": - raise DifyAPIError("当前仅支持 streaming 模式") - + if response_mode != 'streaming': + raise DifyAPIError('当前仅支持 streaming 模式') + async with httpx.AsyncClient( base_url=self.base_url, trust_env=True, timeout=timeout, ) as client: async with client.stream( - "POST", - "/chat-messages", - headers={"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}, + 'POST', + '/chat-messages', + headers={ + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json', + }, json={ - "inputs": inputs, - "query": query, - "user": user, - "response_mode": response_mode, - "conversation_id": conversation_id, - "files": files, + 'inputs': inputs, + 'query': query, + 'user': user, + 'response_mode': response_mode, + 'conversation_id': conversation_id, + 'files': files, }, ) as r: async for chunk in r.aiter_lines(): if r.status_code != 200: - raise DifyAPIError(f"{r.status_code} {chunk}") - if chunk.strip() == "": + raise DifyAPIError(f'{r.status_code} {chunk}') + if chunk.strip() == '': continue - if chunk.startswith("data:"): + if chunk.startswith('data:'): yield json.loads(chunk[5:]) - + async def workflow_run( self, inputs: dict[str, typing.Any], user: str, - response_mode: str = "streaming", # 当前不支持 blocking + response_mode: str = 'streaming', # 当前不支持 blocking files: list[dict[str, typing.Any]] = [], timeout: float = 30.0, ) -> typing.AsyncGenerator[dict[str, typing.Any], None]: """运行工作流""" - if response_mode != "streaming": - raise DifyAPIError("当前仅支持 streaming 模式") - + if response_mode != 'streaming': + raise DifyAPIError('当前仅支持 streaming 模式') + async with httpx.AsyncClient( base_url=self.base_url, trust_env=True, timeout=timeout, ) as client: - async with client.stream( - "POST", - "/workflows/run", - headers={"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}, + 'POST', + '/workflows/run', + headers={ + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json', + }, json={ - "inputs": inputs, - "user": user, - "response_mode": response_mode, - "files": files, + 'inputs': inputs, + 'user': user, + 'response_mode': response_mode, + 'files': files, }, ) as r: async for chunk in r.aiter_lines(): if r.status_code != 200: - raise DifyAPIError(f"{r.status_code} {chunk}") - if chunk.strip() == "": + raise DifyAPIError(f'{r.status_code} {chunk}') + if chunk.strip() == '': continue - if chunk.startswith("data:"): + if chunk.startswith('data:'): yield json.loads(chunk[5:]) async def upload_file( @@ -112,15 +117,15 @@ class AsyncDifyServiceClient: ) as client: # multipart/form-data response = await client.post( - "/files/upload", - headers={"Authorization": f"Bearer {self.api_key}"}, + '/files/upload', + headers={'Authorization': f'Bearer {self.api_key}'}, files={ - "file": file, - "user": (None, user), + 'file': file, + 'user': (None, user), }, ) if response.status_code != 201: - raise DifyAPIError(f"{response.status_code} {response.text}") + raise DifyAPIError(f'{response.status_code} {response.text}') return response.json() diff --git a/libs/dify_service_api/v1/client_test.py b/libs/dify_service_api/v1/client_test.py index 58ef53b4..2695b2ea 100644 --- a/libs/dify_service_api/v1/client_test.py +++ b/libs/dify_service_api/v1/client_test.py @@ -7,11 +7,11 @@ import os class TestDifyClient: async def test_chat_messages(self): - cln = client.DifyClient(api_key=os.getenv("DIFY_API_KEY")) + cln = client.DifyClient(api_key=os.getenv('DIFY_API_KEY')) - resp = await cln.chat_messages(inputs={}, query="Who are you?", user_id="test") + resp = await cln.chat_messages(inputs={}, query='Who are you?', user_id='test') print(resp) -if __name__ == "__main__": +if __name__ == '__main__': asyncio.run(TestDifyClient().test_chat_messages()) diff --git a/libs/dingtalk_api/EchoHandler.py b/libs/dingtalk_api/EchoHandler.py index 4cf0f563..793c3d6d 100644 --- a/libs/dingtalk_api/EchoHandler.py +++ b/libs/dingtalk_api/EchoHandler.py @@ -1,8 +1,8 @@ import asyncio -import json import dingtalk_stream from dingtalk_stream import AckMessage + class EchoTextHandler(dingtalk_stream.ChatbotHandler): def __init__(self, client): self.msg_id = '' @@ -10,6 +10,7 @@ class EchoTextHandler(dingtalk_stream.ChatbotHandler): self.client = client # 用于更新 DingTalkClient 中的 incoming_message """处理钉钉消息""" + async def process(self, callback: dingtalk_stream.CallbackMessage): incoming_message = dingtalk_stream.ChatbotMessage.from_dict(callback.data) if incoming_message.message_id != self.msg_id: @@ -26,6 +27,8 @@ class EchoTextHandler(dingtalk_stream.ChatbotHandler): return self.incoming_message + async def get_dingtalk_client(client_id, client_secret): from api import DingTalkClient # 延迟导入,避免循环导入 + return DingTalkClient(client_id, client_secret) diff --git a/libs/dingtalk_api/api.py b/libs/dingtalk_api/api.py index fa4d0421..b908fd4f 100644 --- a/libs/dingtalk_api/api.py +++ b/libs/dingtalk_api/api.py @@ -10,7 +10,9 @@ import traceback class DingTalkClient: - def __init__(self, client_id: str, client_secret: str,robot_name:str,robot_code:str): + def __init__( + self, client_id: str, client_secret: str, robot_name: str, robot_code: str + ): """初始化 WebSocket 连接并自动启动""" self.credential = dingtalk_stream.Credential(client_id, client_secret) self.client = dingtalk_stream.DingTalkStreamClient(self.credential) @@ -18,106 +20,91 @@ class DingTalkClient: self.secret = client_secret # 在 DingTalkClient 中传入自己作为参数,避免循环导入 self.EchoTextHandler = EchoTextHandler(self) - self.client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler) + self.client.register_callback_handler( + dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler + ) self._message_handlers = { - "example":[], + 'example': [], } self.access_token = '' self.robot_name = robot_name self.robot_code = robot_code self.access_token_expiry_time = '' - - async def get_access_token(self): - url = "https://api.dingtalk.com/v1.0/oauth2/accessToken" - headers = { - "Content-Type": "application/json" - } - data = { - "appKey": self.key, - "appSecret": self.secret - } + url = 'https://api.dingtalk.com/v1.0/oauth2/accessToken' + headers = {'Content-Type': 'application/json'} + data = {'appKey': self.key, 'appSecret': self.secret} async with httpx.AsyncClient() as client: try: - response = await client.post(url,json=data,headers=headers) + response = await client.post(url, json=data, headers=headers) if response.status_code == 200: response_data = response.json() - self.access_token = response_data.get("accessToken") - expires_in = int(response_data.get("expireIn",7200)) + self.access_token = response_data.get('accessToken') + expires_in = int(response_data.get('expireIn', 7200)) self.access_token_expiry_time = time.time() + expires_in - 60 except Exception as e: raise Exception(e) - async def is_token_expired(self): """检查token是否过期""" if self.access_token_expiry_time is None: return True return time.time() > self.access_token_expiry_time - + async def check_access_token(self): if not self.access_token or await self.is_token_expired(): return False return bool(self.access_token and self.access_token.strip()) - async def download_image(self,download_code:str): + async def download_image(self, download_code: str): if not await self.check_access_token(): await self.get_access_token() url = 'https://api.dingtalk.com/v1.0/robot/messageFiles/download' - params = { - "downloadCode":download_code, - "robotCode":self.robot_code - } - headers ={ - "x-acs-dingtalk-access-token": self.access_token - } + params = {'downloadCode': download_code, 'robotCode': self.robot_code} + headers = {'x-acs-dingtalk-access-token': self.access_token} async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, json=params) if response.status_code == 200: result = response.json() - download_url = result.get("downloadUrl") + download_url = result.get('downloadUrl') else: - raise Exception(f"Error: {response.status_code}, {response.text}") + raise Exception(f'Error: {response.status_code}, {response.text}') if download_url: return await self.download_url_to_base64(download_url) - async def download_url_to_base64(self,download_url): + async def download_url_to_base64(self, download_url): async with httpx.AsyncClient() as client: response = await client.get(download_url) - + if response.status_code == 200: - file_bytes = response.content - base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式 + base64_str = base64.b64encode(file_bytes).decode( + 'utf-8' + ) # 返回字符串格式 return base64_str else: - raise Exception("获取文件失败") - - async def get_audio_url(self,download_code:str): + raise Exception('获取文件失败') + + async def get_audio_url(self, download_code: str): if not await self.check_access_token(): await self.get_access_token() url = 'https://api.dingtalk.com/v1.0/robot/messageFiles/download' - params = { - "downloadCode":download_code, - "robotCode":self.robot_code - } - headers ={ - "x-acs-dingtalk-access-token": self.access_token - } + params = {'downloadCode': download_code, 'robotCode': self.robot_code} + headers = {'x-acs-dingtalk-access-token': self.access_token} async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, json=params) if response.status_code == 200: result = response.json() - download_url = result.get("downloadUrl") + download_url = result.get('downloadUrl') if download_url: return await self.download_url_to_base64(download_url) else: - raise Exception("获取音频失败") + raise Exception('获取音频失败') else: - raise Exception(f"Error: {response.status_code}, {response.text}") - + raise Exception(f'Error: {response.status_code}, {response.text}') + async def update_incoming_message(self, message): """异步更新 DingTalkClient 中的 incoming_message""" message_data = await self.get_message(message) @@ -125,24 +112,21 @@ class DingTalkClient: event = DingTalkEvent.from_payload(message_data) if event: await self._handle_message(event) - - async def send_message(self,content:str,incoming_message): - self.EchoTextHandler.reply_text(content,incoming_message) - + async def send_message(self, content: str, incoming_message): + self.EchoTextHandler.reply_text(content, incoming_message) async def get_incoming_message(self): """获取收到的消息""" return await self.EchoTextHandler.get_incoming_message() - - def on_message(self, msg_type: str): def decorator(func: Callable[[DingTalkEvent], None]): if msg_type not in self._message_handlers: self._message_handlers[msg_type] = [] self._message_handlers[msg_type].append(func) return func + return decorator async def _handle_message(self, event: DingTalkEvent): @@ -154,40 +138,44 @@ class DingTalkClient: for handler in self._message_handlers[msg_type]: await handler(event) - - async def get_message(self,incoming_message:dingtalk_stream.chatbot.ChatbotMessage): + async def get_message( + self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage + ): try: - # print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False)) message_data = { - "IncomingMessage":incoming_message, + 'IncomingMessage': incoming_message, } if str(incoming_message.conversation_type) == '1': - message_data["conversation_type"] = 'FriendMessage' + message_data['conversation_type'] = 'FriendMessage' elif str(incoming_message.conversation_type) == '2': - message_data["conversation_type"] = 'GroupMessage' + message_data['conversation_type'] = 'GroupMessage' - if incoming_message.message_type == 'richText': - data = incoming_message.rich_text_content.to_dict() for item in data['richText']: if 'text' in item: - message_data["Content"] = item['text'] + message_data['Content'] = item['text'] if incoming_message.get_image_list()[0]: - message_data["Picture"] = await self.download_image(incoming_message.get_image_list()[0]) - message_data["Type"] = 'text' - + message_data['Picture'] = await self.download_image( + incoming_message.get_image_list()[0] + ) + message_data['Type'] = 'text' + elif incoming_message.message_type == 'text': message_data['Content'] = incoming_message.get_text_list()[0] - message_data["Type"] = 'text' + message_data['Type'] = 'text' elif incoming_message.message_type == 'picture': - message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0]) - + message_data['Picture'] = await self.download_image( + incoming_message.get_image_list()[0] + ) + message_data['Type'] = 'image' elif incoming_message.message_type == 'audio': - message_data['Audio'] = await self.get_audio_url(incoming_message.to_dict()['content']['downloadCode']) + message_data['Audio'] = await self.get_audio_url( + incoming_message.to_dict()['content']['downloadCode'] + ) message_data['Type'] = 'audio' @@ -196,56 +184,55 @@ class DingTalkClient: # print("message_data:", json.dumps(copy_message_data, indent=4, ensure_ascii=False)) except Exception: traceback.print_exc() - + return message_data - async def send_proactive_message_to_one(self,target_id:str,content:str): + async def send_proactive_message_to_one(self, target_id: str, content: str): if not await self.check_access_token(): await self.get_access_token() url = 'https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend' - headers ={ - "x-acs-dingtalk-access-token":self.access_token, - "Content-Type":"application/json", + headers = { + 'x-acs-dingtalk-access-token': self.access_token, + 'Content-Type': 'application/json', } - data ={ - "robotCode":self.robot_code, - "userIds":[target_id], - "msgKey": "sampleText", - "msgParam": json.dumps({"content":content}), + data = { + 'robotCode': self.robot_code, + 'userIds': [target_id], + 'msgKey': 'sampleText', + 'msgParam': json.dumps({'content': content}), } try: async with httpx.AsyncClient() as client: - response = await client.post(url,headers=headers,json=data) + await client.post(url, headers=headers, json=data) except Exception: traceback.print_exc() - - async def send_proactive_message_to_group(self,target_id:str,content:str): + async def send_proactive_message_to_group(self, target_id: str, content: str): if not await self.check_access_token(): await self.get_access_token() url = 'https://api.dingtalk.com/v1.0/robot/groupMessages/send' - headers ={ - "x-acs-dingtalk-access-token":self.access_token, - "Content-Type":"application/json", + headers = { + 'x-acs-dingtalk-access-token': self.access_token, + 'Content-Type': 'application/json', } - data ={ - "robotCode":self.robot_code, - "openConversationId":target_id, - "msgKey": "sampleText", - "msgParam": json.dumps({"content":content}), + data = { + 'robotCode': self.robot_code, + 'openConversationId': target_id, + 'msgKey': 'sampleText', + 'msgParam': json.dumps({'content': content}), } try: async with httpx.AsyncClient() as client: - response = await client.post(url,headers=headers,json=data) + await client.post(url, headers=headers, json=data) except Exception: traceback.print_exc() - + async def start(self): """启动 WebSocket 连接,监听消息""" - await self.client.start() + await self.client.start() diff --git a/libs/dingtalk_api/dingtalkevent.py b/libs/dingtalk_api/dingtalkevent.py index 4feca010..df968e74 100644 --- a/libs/dingtalk_api/dingtalkevent.py +++ b/libs/dingtalk_api/dingtalkevent.py @@ -1,41 +1,39 @@ from typing import Dict, Any, Optional import dingtalk_stream + class DingTalkEvent(dict): @staticmethod - def from_payload(payload: Dict[str, Any]) -> Optional["DingTalkEvent"]: + def from_payload(payload: Dict[str, Any]) -> Optional['DingTalkEvent']: try: event = DingTalkEvent(payload) return event except KeyError: return None - - - @property - def content(self): - return self.get("Content","") @property - def incoming_message(self) -> Optional["dingtalk_stream.chatbot.ChatbotMessage"]: - return self.get("IncomingMessage") + def content(self): + return self.get('Content', '') + + @property + def incoming_message(self) -> Optional['dingtalk_stream.chatbot.ChatbotMessage']: + return self.get('IncomingMessage') @property def type(self): - return self.get("Type","") - + return self.get('Type', '') + @property def picture(self): - return self.get("Picture","") - + return self.get('Picture', '') + @property def audio(self): - return self.get("Audio","") + return self.get('Audio', '') @property def conversation(self): - return self.get("conversation_type","") - - + return self.get('conversation_type', '') def __getattr__(self, key: str) -> Optional[Any]: """ @@ -66,4 +64,4 @@ class DingTalkEvent(dict): Returns: str: 字符串表示。 """ - return f"" + return f'' diff --git a/libs/official_account_api/api.py b/libs/official_account_api/api.py index a8d318dc..fc392c30 100644 --- a/libs/official_account_api/api.py +++ b/libs/official_account_api/api.py @@ -1,20 +1,14 @@ # 微信公众号的加解密算法与企业微信一样,所以直接使用企业微信的加解密算法文件 -from collections import deque import time import traceback from ..wecom_api.WXBizMsgCrypt3 import WXBizMsgCrypt import xml.etree.ElementTree as ET -from quart import Quart,request +from quart import Quart, request import hashlib -from typing import Callable, Dict, Any +from typing import Callable from .oaevent import OAEvent -import httpx import asyncio -import time -import xml.etree.ElementTree as ET -from pkg.platform.sources import officialaccount as oa - xml_template = """ @@ -28,9 +22,8 @@ xml_template = """ """ -class OAClient(): - - def __init__(self,token:str,EncodingAESKey:str,AppID:str,Appsecret:str): +class OAClient: + def __init__(self, token: str, EncodingAESKey: str, AppID: str, Appsecret: str): self.token = token self.aes = EncodingAESKey self.appid = AppID @@ -38,121 +31,130 @@ class OAClient(): self.base_url = 'https://api.weixin.qq.com' self.access_token = '' self.app = Quart(__name__) - self.app.add_url_rule('/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST']) + self.app.add_url_rule( + '/callback/command', + 'handle_callback', + self.handle_callback_request, + methods=['GET', 'POST'], + ) self._message_handlers = { - "example":[], + 'example': [], } self.access_token_expiry_time = None self.msg_id_map = {} self.generated_content = {} async def handle_callback_request(self): - try: # 每隔100毫秒查询是否生成ai回答 start_time = time.time() - signature = request.args.get("signature", "") - timestamp = request.args.get("timestamp", "") - nonce = request.args.get("nonce", "") - echostr = request.args.get("echostr", "") - msg_signature = request.args.get("msg_signature","") + signature = request.args.get('signature', '') + timestamp = request.args.get('timestamp', '') + nonce = request.args.get('nonce', '') + echostr = request.args.get('echostr', '') + msg_signature = request.args.get('msg_signature', '') if msg_signature is None: - raise Exception("msg_signature不在请求体中") + raise Exception('msg_signature不在请求体中') if request.method == 'GET': # 校验签名 - check_str = "".join(sorted([self.token, timestamp, nonce])) - check_signature = hashlib.sha1(check_str.encode("utf-8")).hexdigest() - + check_str = ''.join(sorted([self.token, timestamp, nonce])) + check_signature = hashlib.sha1(check_str.encode('utf-8')).hexdigest() + if check_signature == signature: return echostr # 验证成功返回echostr else: - raise Exception("拒绝请求") - elif request.method == "POST": + raise Exception('拒绝请求') + elif request.method == 'POST': encryt_msg = await request.data - wxcpt = WXBizMsgCrypt(self.token,self.aes,self.appid) - ret,xml_msg = wxcpt.DecryptMsg(encryt_msg,msg_signature,timestamp,nonce) + wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid) + ret, xml_msg = wxcpt.DecryptMsg( + encryt_msg, msg_signature, timestamp, nonce + ) xml_msg = xml_msg.decode('utf-8') if ret != 0: - raise Exception("消息解密失败") + raise Exception('消息解密失败') message_data = await self.get_message(xml_msg) - if message_data : + if message_data: event = OAEvent.from_payload(message_data) if event: await self._handle_message(event) root = ET.fromstring(xml_msg) - from_user = root.find("FromUserName").text # 发送者 - to_user = root.find("ToUserName").text # 机器人 - + from_user = root.find('FromUserName').text # 发送者 + to_user = root.find('ToUserName').text # 机器人 + timeout = 4.80 interval = 0.1 while True: - content = self.generated_content.pop(message_data["MsgId"], None) + content = self.generated_content.pop(message_data['MsgId'], None) if content: response_xml = xml_template.format( to_user=from_user, from_user=to_user, create_time=int(time.time()), - content = content + content=content, ) return response_xml - + if time.time() - start_time >= timeout: break - + await asyncio.sleep(interval) - if self.msg_id_map.get(message_data["MsgId"], 1) == 3: - + if self.msg_id_map.get(message_data['MsgId'], 1) == 3: # response_xml = xml_template.format( # to_user=from_user, # from_user=to_user, # create_time=int(time.time()), # content = "请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。" # ) - print("请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。") + print( + '请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。' + ) return '' - except Exception as e: + except Exception: traceback.print_exc() - async def get_message(self, xml_msg: str): - root = ET.fromstring(xml_msg) message_data = { - "ToUserName": root.find("ToUserName").text, - "FromUserName": root.find("FromUserName").text, - "CreateTime": int(root.find("CreateTime").text), - "MsgType": root.find("MsgType").text, - "Content": root.find("Content").text if root.find("Content") is not None else None, - "MsgId": int(root.find("MsgId").text) if root.find("MsgId") is not None else None, + 'ToUserName': root.find('ToUserName').text, + 'FromUserName': root.find('FromUserName').text, + 'CreateTime': int(root.find('CreateTime').text), + 'MsgType': root.find('MsgType').text, + 'Content': root.find('Content').text + if root.find('Content') is not None + else None, + 'MsgId': int(root.find('MsgId').text) + if root.find('MsgId') is not None + else None, } return message_data - async def run_task(self, host: str, port: int, *args, **kwargs): """ 启动 Quart 应用。 """ await self.app.run_task(host=host, port=port, *args, **kwargs) - def on_message(self, msg_type: str): """ 注册消息类型处理器。 """ + def decorator(func: Callable[[OAEvent], None]): if msg_type not in self._message_handlers: self._message_handlers[msg_type] = [] self._message_handlers[msg_type].append(func) return func + return decorator async def _handle_message(self, event: OAEvent): @@ -170,14 +172,19 @@ class OAClient(): for handler in self._message_handlers[msg_type]: await handler(event) - async def set_message(self,msg_id:int,content:str): + async def set_message(self, msg_id: int, content: str): self.generated_content[msg_id] = content - -class OAClientForLongerResponse(): - - def __init__(self,token:str,EncodingAESKey:str,AppID:str,Appsecret:str,LoadingMessage:str): +class OAClientForLongerResponse: + def __init__( + self, + token: str, + EncodingAESKey: str, + AppID: str, + Appsecret: str, + LoadingMessage: str, + ): self.token = token self.aes = EncodingAESKey self.appid = AppID @@ -185,9 +192,14 @@ class OAClientForLongerResponse(): self.base_url = 'https://api.weixin.qq.com' self.access_token = '' self.app = Quart(__name__) - self.app.add_url_rule('/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST']) + self.app.add_url_rule( + '/callback/command', + 'handle_callback', + self.handle_callback_request, + methods=['GET', 'POST'], + ) self._message_handlers = { - "example":[], + 'example': [], } self.access_token_expiry_time = None self.loading_message = LoadingMessage @@ -196,50 +208,55 @@ class OAClientForLongerResponse(): async def handle_callback_request(self): try: - start_time = time.time() - signature = request.args.get("signature", "") - timestamp = request.args.get("timestamp", "") - nonce = request.args.get("nonce", "") - echostr = request.args.get("echostr", "") - msg_signature = request.args.get("msg_signature", "") + signature = request.args.get('signature', '') + timestamp = request.args.get('timestamp', '') + nonce = request.args.get('nonce', '') + echostr = request.args.get('echostr', '') + msg_signature = request.args.get('msg_signature', '') if msg_signature is None: - raise Exception("msg_signature不在请求体中") + raise Exception('msg_signature不在请求体中') if request.method == 'GET': - check_str = "".join(sorted([self.token, timestamp, nonce])) - check_signature = hashlib.sha1(check_str.encode("utf-8")).hexdigest() - return echostr if check_signature == signature else "拒绝请求" + check_str = ''.join(sorted([self.token, timestamp, nonce])) + check_signature = hashlib.sha1(check_str.encode('utf-8')).hexdigest() + return echostr if check_signature == signature else '拒绝请求' - elif request.method == "POST": + elif request.method == 'POST': encryt_msg = await request.data wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid) - ret, xml_msg = wxcpt.DecryptMsg(encryt_msg, msg_signature, timestamp, nonce) + ret, xml_msg = wxcpt.DecryptMsg( + encryt_msg, msg_signature, timestamp, nonce + ) xml_msg = xml_msg.decode('utf-8') if ret != 0: - raise Exception("消息解密失败") + raise Exception('消息解密失败') # 解析 XML root = ET.fromstring(xml_msg) - from_user = root.find("FromUserName").text - to_user = root.find("ToUserName").text - - - if self.msg_queue.get(from_user) and self.msg_queue[from_user][0]["content"]: + from_user = root.find('FromUserName').text + to_user = root.find('ToUserName').text + if ( + self.msg_queue.get(from_user) + and self.msg_queue[from_user][0]['content'] + ): queue_top = self.msg_queue[from_user].pop(0) - queue_content = queue_top["content"] + queue_content = queue_top['content'] # 弹出用户消息 - if self.user_msg_queue.get(from_user) and self.user_msg_queue[from_user]: + if ( + self.user_msg_queue.get(from_user) + and self.user_msg_queue[from_user] + ): self.user_msg_queue[from_user].pop(0) response_xml = xml_template.format( to_user=from_user, from_user=to_user, create_time=int(time.time()), - content=queue_content + content=queue_content, ) return response_xml @@ -248,65 +265,67 @@ class OAClientForLongerResponse(): to_user=from_user, from_user=to_user, create_time=int(time.time()), - content=self.loading_message + content=self.loading_message, ) - - if self.user_msg_queue.get(from_user) and self.user_msg_queue[from_user][0]["content"]: + + if ( + self.user_msg_queue.get(from_user) + and self.user_msg_queue[from_user][0]['content'] + ): return response_xml else: message_data = await self.get_message(xml_msg) - + if message_data: event = OAEvent.from_payload(message_data) if event: - self.user_msg_queue.setdefault(from_user,[]).append( + self.user_msg_queue.setdefault(from_user, []).append( { - "content":event.message, + 'content': event.message, } ) await self._handle_message(event) return response_xml - except Exception as e: + except Exception: traceback.print_exc() - - async def get_message(self, xml_msg: str): - root = ET.fromstring(xml_msg) message_data = { - "ToUserName": root.find("ToUserName").text, - "FromUserName": root.find("FromUserName").text, - "CreateTime": int(root.find("CreateTime").text), - "MsgType": root.find("MsgType").text, - "Content": root.find("Content").text if root.find("Content") is not None else None, - "MsgId": int(root.find("MsgId").text) if root.find("MsgId") is not None else None, + 'ToUserName': root.find('ToUserName').text, + 'FromUserName': root.find('FromUserName').text, + 'CreateTime': int(root.find('CreateTime').text), + 'MsgType': root.find('MsgType').text, + 'Content': root.find('Content').text + if root.find('Content') is not None + else None, + 'MsgId': int(root.find('MsgId').text) + if root.find('MsgId') is not None + else None, } return message_data - - async def run_task(self, host: str, port: int, *args, **kwargs): """ 启动 Quart 应用。 """ await self.app.run_task(host=host, port=port, *args, **kwargs) - - def on_message(self, msg_type: str): """ 注册消息类型处理器。 """ + def decorator(func: Callable[[OAEvent], None]): if msg_type not in self._message_handlers: self._message_handlers[msg_type] = [] self._message_handlers[msg_type].append(func) return func + return decorator async def _handle_message(self, event: OAEvent): @@ -319,22 +338,13 @@ class OAClientForLongerResponse(): for handler in self._message_handlers[msg_type]: await handler(event) - async def set_message(self,from_user:int,message_id:int,content:str): - if from_user not in self.msg_queue: + async def set_message(self, from_user: int, message_id: int, content: str): + if from_user not in self.msg_queue: self.msg_queue[from_user] = [] - + self.msg_queue[from_user].append( { - "msg_id":message_id, - "content":content, + 'msg_id': message_id, + 'content': content, } ) - - - - - - - - - diff --git a/libs/official_account_api/oaevent.py b/libs/official_account_api/oaevent.py index ebbccd7e..d4de3914 100644 --- a/libs/official_account_api/oaevent.py +++ b/libs/official_account_api/oaevent.py @@ -9,7 +9,7 @@ class OAEvent(dict): """ @staticmethod - def from_payload(payload: Dict[str, Any]) -> Optional["OAEvent"]: + def from_payload(payload: Dict[str, Any]) -> Optional['OAEvent']: """ 从微信公众号事件数据构造 `WecomEvent` 对象。 @@ -34,14 +34,14 @@ class OAEvent(dict): Returns: str: 事件类型。 """ - return self.get("MsgType", "") - + return self.get('MsgType', '') + @property def picurl(self) -> str: """ 图片链接 """ - return self.get("PicUrl","") + return self.get('PicUrl', '') @property def detail_type(self) -> str: @@ -53,8 +53,8 @@ class OAEvent(dict): Returns: str: 事件详细类型。 """ - if self.type == "event": - return self.get("Event", "") + if self.type == 'event': + return self.get('Event', '') return self.type @property @@ -65,15 +65,14 @@ class OAEvent(dict): Returns: str: 事件名。 """ - return f"{self.type}.{self.detail_type}" + return f'{self.type}.{self.detail_type}' @property def user_id(self) -> Optional[str]: """ 发送方账号 """ - return self.get("FromUserName") - + return self.get('FromUserName') @property def receiver_id(self) -> Optional[str]: @@ -83,7 +82,7 @@ class OAEvent(dict): Returns: Optional[str]: 接收者 ID。 """ - return self.get("ToUserName") + return self.get('ToUserName') @property def message_id(self) -> Optional[str]: @@ -93,7 +92,7 @@ class OAEvent(dict): Returns: Optional[str]: 消息 ID。 """ - return self.get("MsgId") + return self.get('MsgId') @property def message(self) -> Optional[str]: @@ -103,7 +102,7 @@ class OAEvent(dict): Returns: Optional[str]: 消息内容。 """ - return self.get("Content") + return self.get('Content') @property def media_id(self) -> Optional[str]: @@ -113,7 +112,7 @@ class OAEvent(dict): Returns: Optional[str]: 媒体文件 ID。 """ - return self.get("MediaId") + return self.get('MediaId') @property def timestamp(self) -> Optional[int]: @@ -123,7 +122,7 @@ class OAEvent(dict): Returns: Optional[int]: 时间戳。 """ - return self.get("CreateTime") + return self.get('CreateTime') @property def event_key(self) -> Optional[str]: @@ -133,7 +132,7 @@ class OAEvent(dict): Returns: Optional[str]: 事件 Key。 """ - return self.get("EventKey") + return self.get('EventKey') def __getattr__(self, key: str) -> Optional[Any]: """ @@ -164,4 +163,4 @@ class OAEvent(dict): Returns: str: 字符串表示。 """ - return f"" + return f'' diff --git a/libs/qq_official_api/api.py b/libs/qq_official_api/api.py index 62f252df..89360881 100644 --- a/libs/qq_official_api/api.py +++ b/libs/qq_official_api/api.py @@ -1,24 +1,16 @@ import time from quart import request -import base64 -import binascii import httpx from quart import Quart -import xml.etree.ElementTree as ET from typing import Callable, Dict, Any -from pkg.platform.types import events as platform_events, message as platform_message -import aiofiles +from pkg.platform.types import events as platform_events from .qqofficialevent import QQOfficialEvent import json -import hmac -import base64 -import hashlib import traceback from cryptography.hazmat.primitives.asymmetric import ed25519 -from .qqofficialevent import QQOfficialEvent + def handle_validation(body: dict, bot_secret: str): - # bot正确的secert是32位的,此处仅为了适配演示demo while len(bot_secret) < 32: bot_secret = bot_secret * 2 @@ -36,29 +28,26 @@ def handle_validation(body: dict, bot_secret: str): signature_hex = signature.hex() - response = { - "plain_token": body['d']['plain_token'], - "signature": signature_hex - } + response = {'plain_token': body['d']['plain_token'], 'signature': signature_hex} return response + class QQOfficialClient: def __init__(self, secret: str, token: str, app_id: str): self.app = Quart(__name__) self.app.add_url_rule( - "/callback/command", - "handle_callback", + '/callback/command', + 'handle_callback', self.handle_callback_request, - methods=["GET", "POST"], + methods=['GET', 'POST'], ) self.secret = secret self.token = token self.app_id = app_id - self._message_handlers = { - } - self.base_url = "https://api.sgroup.qq.com" - self.access_token = "" + self._message_handlers = {} + self.base_url = 'https://api.sgroup.qq.com' + self.access_token = '' self.access_token_expiry_time = None async def check_access_token(self): @@ -66,30 +55,29 @@ class QQOfficialClient: if not self.access_token or await self.is_token_expired(): return False return bool(self.access_token and self.access_token.strip()) - + async def get_access_token(self): """获取access_token""" - url = "https://bots.qq.com/app/getAppAccessToken" + url = 'https://bots.qq.com/app/getAppAccessToken' async with httpx.AsyncClient() as client: params = { - "appId":self.app_id, - "clientSecret":self.secret, + 'appId': self.app_id, + 'clientSecret': self.secret, } headers = { - "content-type":"application/json", + 'content-type': 'application/json', } try: - response = await client.post(url,json=params,headers=headers) + response = await client.post(url, json=params, headers=headers) if response.status_code == 200: response_data = response.json() - access_token = response_data.get("access_token") - expires_in = int(response_data.get("expires_in",7200)) + access_token = response_data.get('access_token') + expires_in = int(response_data.get('expires_in', 7200)) self.access_token_expiry_time = time.time() + expires_in - 60 if access_token: self.access_token = access_token except Exception as e: - raise Exception(f"获取access_token失败: {e}") - + raise Exception(f'获取access_token失败: {e}') async def handle_callback_request(self): """处理回调请求""" @@ -98,27 +86,24 @@ class QQOfficialClient: body = await request.get_data() payload = json.loads(body) - # 验证是否为回调验证请求 - if payload.get("op") == 13: + if payload.get('op') == 13: # 生成签名 response = handle_validation(payload, self.secret) return response - if payload.get("op") == 0: - message_data = await self.get_message(payload) - if message_data: - event = QQOfficialEvent.from_payload(message_data) - await self._handle_message(event) - - return {"code": 0, "message": "success"} + if payload.get('op') == 0: + message_data = await self.get_message(payload) + if message_data: + event = QQOfficialEvent.from_payload(message_data) + await self._handle_message(event) + + return {'code': 0, 'message': 'success'} except Exception as e: traceback.print_exc() - return {"error": str(e)}, 400 - - + return {'error': str(e)}, 400 async def run_task(self, host: str, port: int, *args, **kwargs): """启动 Quart 应用""" @@ -135,133 +120,140 @@ class QQOfficialClient: return decorator - async def _handle_message(self, event:QQOfficialEvent): + async def _handle_message(self, event: QQOfficialEvent): """处理消息事件""" msg_type = event.t if msg_type in self._message_handlers: for handler in self._message_handlers[msg_type]: await handler(event) - - async def get_message(self,msg:dict) -> Dict[str,Any]: + async def get_message(self, msg: dict) -> Dict[str, Any]: """获取消息""" message_data = { - "t": msg.get("t",{}), - "user_openid": msg.get("d",{}).get("author",{}).get("user_openid",{}), - "timestamp": msg.get("d",{}).get("timestamp",{}), - "d_author_id": msg.get("d",{}).get("author",{}).get("id",{}), - "content": msg.get("d",{}).get("content",{}), - "d_id": msg.get("d",{}).get("id",{}), - "id": msg.get("id",{}), - "channel_id": msg.get("d",{}).get("channel_id",{}), - "username": msg.get("d",{}).get("author",{}).get("username",{}), - "guild_id": msg.get("d",{}).get("guild_id",{}), - "member_openid": msg.get("d",{}).get("author",{}).get("openid",{}), - "group_openid": msg.get("d",{}).get("group_openid",{}) + 't': msg.get('t', {}), + 'user_openid': msg.get('d', {}).get('author', {}).get('user_openid', {}), + 'timestamp': msg.get('d', {}).get('timestamp', {}), + 'd_author_id': msg.get('d', {}).get('author', {}).get('id', {}), + 'content': msg.get('d', {}).get('content', {}), + 'd_id': msg.get('d', {}).get('id', {}), + 'id': msg.get('id', {}), + 'channel_id': msg.get('d', {}).get('channel_id', {}), + 'username': msg.get('d', {}).get('author', {}).get('username', {}), + 'guild_id': msg.get('d', {}).get('guild_id', {}), + 'member_openid': msg.get('d', {}).get('author', {}).get('openid', {}), + 'group_openid': msg.get('d', {}).get('group_openid', {}), } - attachments = msg.get("d", {}).get("attachments", []) - image_attachments = [attachment['url'] for attachment in attachments if await self.is_image(attachment)] - image_attachments_type = [attachment['content_type'] for attachment in attachments if await self.is_image(attachment)] + attachments = msg.get('d', {}).get('attachments', []) + image_attachments = [ + attachment['url'] + for attachment in attachments + if await self.is_image(attachment) + ] + image_attachments_type = [ + attachment['content_type'] + for attachment in attachments + if await self.is_image(attachment) + ] if image_attachments: - message_data["image_attachments"] = image_attachments[0] - message_data["content_type"] = image_attachments_type[0] + message_data['image_attachments'] = image_attachments[0] + message_data['content_type'] = image_attachments_type[0] else: - - message_data["image_attachments"] = None - - return message_data - + message_data['image_attachments'] = None - async def is_image(self,attachment:dict) -> bool: + return message_data + + async def is_image(self, attachment: dict) -> bool: """判断是否为图片附件""" - content_type = attachment.get("content_type","") - return content_type.startswith("image/") - - - async def send_private_text_msg(self,user_openid:str,content:str,msg_id:str): + content_type = attachment.get('content_type', '') + return content_type.startswith('image/') + + async def send_private_text_msg(self, user_openid: str, content: str, msg_id: str): """发送私聊消息""" if not await self.check_access_token(): - await self.get_access_token() + await self.get_access_token() - url = self.base_url + "/v2/users/" + user_openid + "/messages" + url = self.base_url + '/v2/users/' + user_openid + '/messages' async with httpx.AsyncClient() as client: headers = { - "Authorization": f"QQBot {self.access_token}", - "Content-Type": "application/json", + 'Authorization': f'QQBot {self.access_token}', + 'Content-Type': 'application/json', } data = { - "content": content, - "msg_type": 0, - "msg_id": msg_id, + 'content': content, + 'msg_type': 0, + 'msg_id': msg_id, } - response = await client.post(url,headers=headers,json=data) + response = await client.post(url, headers=headers, json=data) if response.status_code == 200: return else: raise ValueError(response) - - async def send_group_text_msg(self,group_openid:str,content:str,msg_id:str): + async def send_group_text_msg(self, group_openid: str, content: str, msg_id: str): """发送群聊消息""" if not await self.check_access_token(): await self.get_access_token() - url = self.base_url + "/v2/groups/" + group_openid + "/messages" + url = self.base_url + '/v2/groups/' + group_openid + '/messages' async with httpx.AsyncClient() as client: headers = { - "Authorization": f"QQBot {self.access_token}", - "Content-Type": "application/json", + 'Authorization': f'QQBot {self.access_token}', + 'Content-Type': 'application/json', } data = { - "content": content, - "msg_type": 0, - "msg_id": msg_id, + 'content': content, + 'msg_type': 0, + 'msg_id': msg_id, } - response = await client.post(url,headers=headers,json=data) + response = await client.post(url, headers=headers, json=data) if response.status_code == 200: return else: raise Exception(response.read().decode()) - async def send_channle_group_text_msg(self,channel_id:str,content:str,msg_id:str): + async def send_channle_group_text_msg( + self, channel_id: str, content: str, msg_id: str + ): """发送频道群聊消息""" if not await self.check_access_token(): - await self.get_access_token() + await self.get_access_token() - url = self.base_url + "/channels/" + channel_id + "/messages" + url = self.base_url + '/channels/' + channel_id + '/messages' async with httpx.AsyncClient() as client: headers = { - "Authorization": f"QQBot {self.access_token}", - "Content-Type": "application/json", + 'Authorization': f'QQBot {self.access_token}', + 'Content-Type': 'application/json', } params = { - "content": content, - "msg_type": 0, - "msg_id": msg_id, + 'content': content, + 'msg_type': 0, + 'msg_id': msg_id, } - response = await client.post(url,headers=headers,json=params) + response = await client.post(url, headers=headers, json=params) if response.status_code == 200: return True else: raise Exception(response) - async def send_channle_private_text_msg(self,guild_id:str,content:str,msg_id:str): + async def send_channle_private_text_msg( + self, guild_id: str, content: str, msg_id: str + ): """发送频道私聊消息""" if not await self.check_access_token(): - await self.get_access_token() + await self.get_access_token() - url = self.base_url + "/dms/" + guild_id + "/messages" + url = self.base_url + '/dms/' + guild_id + '/messages' async with httpx.AsyncClient() as client: headers = { - "Authorization": f"QQBot {self.access_token}", - "Content-Type": "application/json", + 'Authorization': f'QQBot {self.access_token}', + 'Content-Type': 'application/json', } params = { - "content": content, - "msg_type": 0, - "msg_id": msg_id, + 'content': content, + 'msg_type': 0, + 'msg_id': msg_id, } - response = await client.post(url,headers=headers,json=params) + response = await client.post(url, headers=headers, json=params) if response.status_code == 200: return True else: diff --git a/libs/qq_official_api/qqofficialevent.py b/libs/qq_official_api/qqofficialevent.py index 41e842f1..7c29b9d8 100644 --- a/libs/qq_official_api/qqofficialevent.py +++ b/libs/qq_official_api/qqofficialevent.py @@ -1,114 +1,112 @@ from typing import Dict, Any, Optional + class QQOfficialEvent(dict): @staticmethod - def from_payload(payload: Dict[str, Any]) -> Optional["QQOfficialEvent"]: + def from_payload(payload: Dict[str, Any]) -> Optional['QQOfficialEvent']: try: event = QQOfficialEvent(payload) return event except KeyError: return None - @property def t(self) -> str: """ 事件类型 """ - return self.get("t", "") - + return self.get('t', '') + @property def user_openid(self) -> str: """ 用户openid """ - return self.get("user_openid",{}) - + return self.get('user_openid', {}) + @property def timestamp(self) -> str: """ 时间戳 """ - return self.get("timestamp",{}) - - + return self.get('timestamp', {}) + @property def d_author_id(self) -> str: """ 作者id """ - return self.get("id",{}) - + return self.get('id', {}) + @property def content(self) -> str: """ 内容 """ - return self.get("content",'') - + return self.get('content', '') + @property def d_id(self) -> str: """ d_id """ - return self.get("d_id",{}) - + return self.get('d_id', {}) + @property def id(self) -> str: """ 消息id,msg_id """ - return self.get("id",{}) - + return self.get('id', {}) + @property def channel_id(self) -> str: """ 频道id """ - return self.get("channel_id",{}) - + return self.get('channel_id', {}) + @property def username(self) -> str: """ 用户名 """ - return self.get("username",{}) - + return self.get('username', {}) + @property def guild_id(self) -> str: """ 频道id """ - return self.get("guild_id",{}) - + return self.get('guild_id', {}) + @property def member_openid(self) -> str: """ 成员openid """ - return self.get("openid",{}) - + return self.get('openid', {}) + @property def attachments(self) -> str: """ 附件url """ - url = self.get("image_attachments", "") - if url and not url.startswith("https://"): - url = "https://" + url + url = self.get('image_attachments', '') + if url and not url.startswith('https://'): + url = 'https://' + url return url - + @property def group_openid(self) -> str: """ 群组id """ - return self.get("group_openid",{}) - + return self.get('group_openid', {}) + @property def content_type(self) -> str: """ 文件类型 """ - return self.get("content_type","") - + return self.get('content_type', '') diff --git a/libs/wecom_api/WXBizMsgCrypt3.py b/libs/wecom_api/WXBizMsgCrypt3.py index 0123c7d1..a9a7bc89 100644 --- a/libs/wecom_api/WXBizMsgCrypt3.py +++ b/libs/wecom_api/WXBizMsgCrypt3.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # -*- encoding:utf-8 -*- -""" 对企业微信发送给企业后台的消息加解密示例代码. +"""对企业微信发送给企业后台的消息加解密示例代码. @copyright: Copyright (c) 1998-2014 Tencent Inc. """ + # ------------------------------------------------------------------------ import logging import base64 @@ -49,7 +50,7 @@ class SHA1: sortlist = [token, timestamp, nonce, encrypt] sortlist.sort() sha = hashlib.sha1() - sha.update("".join(sortlist).encode()) + sha.update(''.join(sortlist).encode()) return ierror.WXBizMsgCrypt_OK, sha.hexdigest() except Exception as e: logger = logging.getLogger() @@ -75,7 +76,7 @@ class XMLParse: """ try: xml_tree = ET.fromstring(xmltext) - encrypt = xml_tree.find("Encrypt") + encrypt = xml_tree.find('Encrypt') return ierror.WXBizMsgCrypt_OK, encrypt.text except Exception as e: logger = logging.getLogger() @@ -100,13 +101,13 @@ class XMLParse: return resp_xml -class PKCS7Encoder(): +class PKCS7Encoder: """提供基于PKCS7算法的加解密接口""" block_size = 32 def encode(self, text): - """ 对需要加密的明文进行填充补位 + """对需要加密的明文进行填充补位 @param text: 需要进行填充补位操作的明文 @return: 补齐明文字符串 """ @@ -134,7 +135,6 @@ class Prpcrypt(object): """提供接收和推送给企业微信消息的加解密接口""" def __init__(self, key): - # self.key = base64.b64decode(key+"=") self.key = key # 设置加解密模式为AES的CBC模式 @@ -147,7 +147,12 @@ class Prpcrypt(object): """ # 16位随机字符串添加到明文开头 text = text.encode() - text = self.get_random_str() + struct.pack("I", socket.htonl(len(text))) + text + receiveid.encode() + text = ( + self.get_random_str() + + struct.pack('I', socket.htonl(len(text))) + + text + + receiveid.encode() + ) # 使用自定义的填充方式对明文进行补位填充 pkcs7 = PKCS7Encoder() @@ -183,9 +188,9 @@ class Prpcrypt(object): # plain_text = pkcs7.encode(plain_text) # 去除16位随机字符串 content = plain_text[16:-pad] - xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0]) - xml_content = content[4: xml_len + 4] - from_receiveid = content[xml_len + 4:] + xml_len = socket.ntohl(struct.unpack('I', content[:4])[0]) + xml_content = content[4 : xml_len + 4] + from_receiveid = content[xml_len + 4 :] except Exception as e: logger = logging.getLogger() logger.error(e) @@ -196,7 +201,7 @@ class Prpcrypt(object): return 0, xml_content def get_random_str(self): - """ 随机生成16位字符串 + """随机生成16位字符串 @return: 16位字符串 """ return str(random.randint(1000000000000000, 9999999999999999)).encode() @@ -206,10 +211,10 @@ class WXBizMsgCrypt(object): # 构造函数 def __init__(self, sToken, sEncodingAESKey, sReceiveId): try: - self.key = base64.b64decode(sEncodingAESKey + "=") + self.key = base64.b64decode(sEncodingAESKey + '=') assert len(self.key) == 32 - except: - throw_exception("[error]: EncodingAESKey unvalid !", FormatException) + except Exception: + throw_exception('[error]: EncodingAESKey unvalid !', FormatException) # return ierror.WXBizMsgCrypt_IllegalAesKey,None self.m_sToken = sToken self.m_sReceiveId = sReceiveId diff --git a/libs/wecom_api/api.py b/libs/wecom_api/api.py index 61458f8e..8993885b 100644 --- a/libs/wecom_api/api.py +++ b/libs/wecom_api/api.py @@ -7,15 +7,22 @@ from quart import Quart import xml.etree.ElementTree as ET from typing import Callable, Dict, Any from .wecomevent import WecomEvent -from pkg.platform.types import events as platform_events, message as platform_message +from pkg.platform.types import message as platform_message import aiofiles -class WecomClient(): - def __init__(self,corpid:str,secret:str,token:str,EncodingAESKey:str,contacts_secret:str): +class WecomClient: + def __init__( + self, + corpid: str, + secret: str, + token: str, + EncodingAESKey: str, + contacts_secret: str, + ): self.corpid = corpid self.secret = secret - self.access_token_for_contacts ='' + self.access_token_for_contacts = '' self.token = token self.aes = EncodingAESKey self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin' @@ -23,19 +30,26 @@ class WecomClient(): self.secret_for_contacts = contacts_secret self.app = Quart(__name__) self.wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid) - self.app.add_url_rule('/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST']) + self.app.add_url_rule( + '/callback/command', + 'handle_callback', + self.handle_callback_request, + methods=['GET', 'POST'], + ) self._message_handlers = { - "example":[], + 'example': [], } - #access——token操作 + # access——token操作 async def check_access_token(self): return bool(self.access_token and self.access_token.strip()) async def check_access_token_for_contacts(self): - return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip()) + return bool( + self.access_token_for_contacts and self.access_token_for_contacts.strip() + ) - async def get_access_token(self,secret): + async def get_access_token(self, secret): url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}' async with httpx.AsyncClient() as client: response = await client.get(url) @@ -43,146 +57,163 @@ class WecomClient(): if 'access_token' in data: return data['access_token'] else: - raise Exception(f"未获取access token: {data}") + raise Exception(f'未获取access token: {data}') async def get_users(self): if not self.check_access_token_for_contacts(): - self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts) + self.access_token_for_contacts = await self.get_access_token( + self.secret_for_contacts + ) - url = self.base_url+'/user/list_id?access_token='+self.access_token_for_contacts + url = ( + self.base_url + + '/user/list_id?access_token=' + + self.access_token_for_contacts + ) async with httpx.AsyncClient() as client: params = { - "cursor":"", - "limit":10000, + 'cursor': '', + 'limit': 10000, } - response = await client.post(url,json=params) + response = await client.post(url, json=params) data = response.json() if data['errcode'] == 0: dept_users = data['dept_user'] userid = [] for user in dept_users: - userid.append(user["userid"]) + userid.append(user['userid']) return userid else: - raise Exception("未获取用户") - - async def send_to_all(self,content:str,agent_id:int): - if not self.check_access_token_for_contacts(): - self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts) + raise Exception('未获取用户') - url = self.base_url+'/message/send?access_token='+self.access_token_for_contacts + async def send_to_all(self, content: str, agent_id: int): + if not self.check_access_token_for_contacts(): + self.access_token_for_contacts = await self.get_access_token( + self.secret_for_contacts + ) + + url = ( + self.base_url + + '/message/send?access_token=' + + self.access_token_for_contacts + ) user_ids = await self.get_users() - user_ids_string = "|".join(user_ids) + user_ids_string = '|'.join(user_ids) async with httpx.AsyncClient() as client: params = { - "touser" : user_ids_string, - "msgtype" : "text", - "agentid" : agent_id, - "text" : { - "content" : content, - }, - "safe":0, - "enable_id_trans": 0, - "enable_duplicate_check": 0, - "duplicate_check_interval": 1800 + 'touser': user_ids_string, + 'msgtype': 'text', + 'agentid': agent_id, + 'text': { + 'content': content, + }, + 'safe': 0, + 'enable_id_trans': 0, + 'enable_duplicate_check': 0, + 'duplicate_check_interval': 1800, } - response = await client.post(url,json=params) + response = await client.post(url, json=params) data = response.json() if data['errcode'] != 0: - raise Exception("Failed to send message: "+str(data)) + raise Exception('Failed to send message: ' + str(data)) - async def send_image(self,user_id:str,agent_id:int,media_id:str): + async def send_image(self, user_id: str, agent_id: int, media_id: str): if not await self.check_access_token(): self.access_token = await self.get_access_token(self.secret) - url = self.base_url+'/media/upload?access_token='+self.access_token + url = self.base_url + '/media/upload?access_token=' + self.access_token async with httpx.AsyncClient() as client: params = { - "touser" : user_id, - "toparty" : "", - "totag":"", - "agentid" : agent_id, - "msgtype" : "image", - "image" : { - "media_id" : media_id, + 'touser': user_id, + 'toparty': '', + 'totag': '', + 'agentid': agent_id, + 'msgtype': 'image', + 'image': { + 'media_id': media_id, }, - "safe":0, - "enable_id_trans": 0, - "enable_duplicate_check": 0, - "duplicate_check_interval": 1800 + 'safe': 0, + 'enable_id_trans': 0, + 'enable_duplicate_check': 0, + 'duplicate_check_interval': 1800, } try: - response = await client.post(url,json=params) + response = await client.post(url, json=params) data = response.json() except Exception as e: - raise Exception("Failed to send image: "+str(e)) + raise Exception('Failed to send image: ' + str(e)) # 企业微信错误码40014和42001,代表accesstoken问题 if data['errcode'] == 40014 or data['errcode'] == 42001: self.access_token = await self.get_access_token(self.secret) - return await self.send_image(user_id,agent_id,media_id) + return await self.send_image(user_id, agent_id, media_id) if data['errcode'] != 0: - raise Exception("Failed to send image: "+str(data)) - - async def send_private_msg(self,user_id:str, agent_id:int,content:str): + raise Exception('Failed to send image: ' + str(data)) + + async def send_private_msg(self, user_id: str, agent_id: int, content: str): if not await self.check_access_token(): self.access_token = await self.get_access_token(self.secret) - url = self.base_url+'/message/send?access_token='+self.access_token + url = self.base_url + '/message/send?access_token=' + self.access_token async with httpx.AsyncClient() as client: - params={ - "touser" : user_id, - "msgtype" : "text", - "agentid" : agent_id, - "text" : { - "content" : content, + params = { + 'touser': user_id, + 'msgtype': 'text', + 'agentid': agent_id, + 'text': { + 'content': content, }, - "safe":0, - "enable_id_trans": 0, - "enable_duplicate_check": 0, - "duplicate_check_interval": 1800 + 'safe': 0, + 'enable_id_trans': 0, + 'enable_duplicate_check': 0, + 'duplicate_check_interval': 1800, } - response = await client.post(url,json=params) + response = await client.post(url, json=params) data = response.json() if data['errcode'] == 40014 or data['errcode'] == 42001: self.access_token = await self.get_access_token(self.secret) - return await self.send_private_msg(user_id,agent_id,content) + return await self.send_private_msg(user_id, agent_id, content) if data['errcode'] != 0: - raise Exception("Failed to send message: "+str(data)) + raise Exception('Failed to send message: ' + str(data)) async def handle_callback_request(self): """ 处理回调请求,包括 GET 验证和 POST 消息接收。 """ try: + msg_signature = request.args.get('msg_signature') + timestamp = request.args.get('timestamp') + nonce = request.args.get('nonce') - msg_signature = request.args.get("msg_signature") - timestamp = request.args.get("timestamp") - nonce = request.args.get("nonce") - - if request.method == "GET": - echostr = request.args.get("echostr") - ret, reply_echo_str = self.wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr) + if request.method == 'GET': + echostr = request.args.get('echostr') + ret, reply_echo_str = self.wxcpt.VerifyURL( + msg_signature, timestamp, nonce, echostr + ) if ret != 0: - raise Exception(f"验证失败,错误码: {ret}") + raise Exception(f'验证失败,错误码: {ret}') return reply_echo_str - elif request.method == "POST": + elif request.method == 'POST': encrypt_msg = await request.data - ret, xml_msg = self.wxcpt.DecryptMsg(encrypt_msg, msg_signature, timestamp, nonce) + ret, xml_msg = self.wxcpt.DecryptMsg( + encrypt_msg, msg_signature, timestamp, nonce + ) if ret != 0: - raise Exception(f"消息解密失败,错误码: {ret}") + raise Exception(f'消息解密失败,错误码: {ret}') # 解析消息并处理 message_data = await self.get_message(xml_msg) if message_data: - event = WecomEvent.from_payload(message_data) # 转换为 WecomEvent 对象 + event = WecomEvent.from_payload( + message_data + ) # 转换为 WecomEvent 对象 if event: await self._handle_message(event) - return "success" + return 'success' except Exception as e: - return f"Error processing request: {str(e)}", 400 + return f'Error processing request: {str(e)}', 400 async def run_task(self, host: str, port: int, *args, **kwargs): """ @@ -194,11 +225,13 @@ class WecomClient(): """ 注册消息类型处理器。 """ + def decorator(func: Callable[[WecomEvent], None]): if msg_type not in self._message_handlers: self._message_handlers[msg_type] = [] self._message_handlers[msg_type].append(func) return func + return decorator async def _handle_message(self, event: WecomEvent): @@ -216,38 +249,47 @@ class WecomClient(): """ root = ET.fromstring(xml_msg) message_data = { - "ToUserName": root.find("ToUserName").text, - "FromUserName": root.find("FromUserName").text, - "CreateTime": int(root.find("CreateTime").text), - "MsgType": root.find("MsgType").text, - "Content": root.find("Content").text if root.find("Content") is not None else None, - "MsgId": int(root.find("MsgId").text) if root.find("MsgId") is not None else None, - "AgentID": int(root.find("AgentID").text) if root.find("AgentID") is not None else None, + 'ToUserName': root.find('ToUserName').text, + 'FromUserName': root.find('FromUserName').text, + 'CreateTime': int(root.find('CreateTime').text), + 'MsgType': root.find('MsgType').text, + 'Content': root.find('Content').text + if root.find('Content') is not None + else None, + 'MsgId': int(root.find('MsgId').text) + if root.find('MsgId') is not None + else None, + 'AgentID': int(root.find('AgentID').text) + if root.find('AgentID') is not None + else None, } - if message_data["MsgType"] == "image": - message_data["MediaId"] = root.find("MediaId").text if root.find("MediaId") is not None else None - message_data["PicUrl"] = root.find("PicUrl").text if root.find("PicUrl") is not None else None - + if message_data['MsgType'] == 'image': + message_data['MediaId'] = ( + root.find('MediaId').text if root.find('MediaId') is not None else None + ) + message_data['PicUrl'] = ( + root.find('PicUrl').text if root.find('PicUrl') is not None else None + ) + return message_data - + @staticmethod async def get_image_type(image_bytes: bytes) -> str: """ 通过图片的magic numbers判断图片类型 """ magic_numbers = { - b'\xFF\xD8\xFF': 'jpg', - b'\x89\x50\x4E\x47': 'png', + b'\xff\xd8\xff': 'jpg', + b'\x89\x50\x4e\x47': 'png', b'\x47\x49\x46': 'gif', - b'\x42\x4D': 'bmp', - b'\x00\x00\x01\x00': 'ico' + b'\x42\x4d': 'bmp', + b'\x00\x00\x01\x00': 'ico', } - + for magic, ext in magic_numbers.items(): if image_bytes.startswith(magic): return ext return 'jpg' # 默认返回jpg - async def upload_to_work(self, image: platform_message.Image): """ @@ -256,9 +298,14 @@ class WecomClient(): if not await self.check_access_token(): self.access_token = await self.get_access_token(self.secret) - url = self.base_url + '/media/upload?access_token=' + self.access_token + '&type=file' + url = ( + self.base_url + + '/media/upload?access_token=' + + self.access_token + + '&type=file' + ) file_bytes = None - file_name = "uploaded_file.txt" + file_name = 'uploaded_file.txt' # 获取文件的二进制数据 if image.path: @@ -277,20 +324,22 @@ class WecomClient(): padded_base64 = base64_data + '=' * padding file_bytes = base64.b64decode(padded_base64) except binascii.Error as e: - raise ValueError(f"Invalid base64 string: {str(e)}") + raise ValueError(f'Invalid base64 string: {str(e)}') else: - raise ValueError("image对象出错") + raise ValueError('image对象出错') # 设置 multipart/form-data 格式的文件 - boundary = "-------------------------acebdf13572468" - headers = { - 'Content-Type': f'multipart/form-data; boundary={boundary}' - } + boundary = '-------------------------acebdf13572468' + headers = {'Content-Type': f'multipart/form-data; boundary={boundary}'} body = ( - f"--{boundary}\r\n" - f"Content-Disposition: form-data; name=\"media\"; filename=\"{file_name}\"; filelength={len(file_bytes)}\r\n" - f"Content-Type: application/octet-stream\r\n\r\n" - ).encode('utf-8') + file_bytes + f"\r\n--{boundary}--\r\n".encode('utf-8') + ( + f'--{boundary}\r\n' + f'Content-Disposition: form-data; name="media"; filename="{file_name}"; filelength={len(file_bytes)}\r\n' + f'Content-Type: application/octet-stream\r\n\r\n' + ).encode('utf-8') + + file_bytes + + f'\r\n--{boundary}--\r\n'.encode('utf-8') + ) # 上传文件 async with httpx.AsyncClient() as client: @@ -300,19 +349,18 @@ class WecomClient(): self.access_token = await self.get_access_token(self.secret) media_id = await self.upload_to_work(image) if data.get('errcode', 0) != 0: - raise Exception("failed to upload file") + raise Exception('failed to upload file') media_id = data.get('media_id') return media_id - async def download_image_to_bytes(self,url:str) -> bytes: + async def download_image_to_bytes(self, url: str) -> bytes: async with httpx.AsyncClient() as client: response = await client.get(url) response.raise_for_status() return response.content - #进行media_id的获取 + # 进行media_id的获取 async def get_media_id(self, image: platform_message.Image): - media_id = await self.upload_to_work(image=image) return media_id diff --git a/libs/wecom_api/ierror.py b/libs/wecom_api/ierror.py index 8985b886..6c7ca122 100644 --- a/libs/wecom_api/ierror.py +++ b/libs/wecom_api/ierror.py @@ -4,7 +4,7 @@ # Author: jonyqin # Created Time: Thu 11 Sep 2014 01:53:58 PM CST # File Name: ierror.py -# Description:定义错误码含义 +# Description:定义错误码含义 ######################################################################### WXBizMsgCrypt_OK = 0 WXBizMsgCrypt_ValidateSignature_Error = -40001 @@ -17,4 +17,4 @@ WXBizMsgCrypt_DecryptAES_Error = -40007 WXBizMsgCrypt_IllegalBuffer = -40008 WXBizMsgCrypt_EncodeBase64_Error = -40009 WXBizMsgCrypt_DecodeBase64_Error = -40010 -WXBizMsgCrypt_GenReturnXml_Error = -40011 \ No newline at end of file +WXBizMsgCrypt_GenReturnXml_Error = -40011 diff --git a/libs/wecom_api/wecomevent.py b/libs/wecom_api/wecomevent.py index 3606cdf5..a0c2c7da 100644 --- a/libs/wecom_api/wecomevent.py +++ b/libs/wecom_api/wecomevent.py @@ -9,7 +9,7 @@ class WecomEvent(dict): """ @staticmethod - def from_payload(payload: Dict[str, Any]) -> Optional["WecomEvent"]: + def from_payload(payload: Dict[str, Any]) -> Optional['WecomEvent']: """ 从企业微信事件数据构造 `WecomEvent` 对象。 @@ -34,14 +34,14 @@ class WecomEvent(dict): Returns: str: 事件类型。 """ - return self.get("MsgType", "") - + return self.get('MsgType', '') + @property def picurl(self) -> str: """ 图片链接 """ - return self.get("PicUrl") + return self.get('PicUrl') @property def detail_type(self) -> str: @@ -53,8 +53,8 @@ class WecomEvent(dict): Returns: str: 事件详细类型。 """ - if self.type == "event": - return self.get("Event", "") + if self.type == 'event': + return self.get('Event', '') return self.type @property @@ -65,7 +65,7 @@ class WecomEvent(dict): Returns: str: 事件名。 """ - return f"{self.type}.{self.detail_type}" + return f'{self.type}.{self.detail_type}' @property def user_id(self) -> Optional[str]: @@ -75,8 +75,8 @@ class WecomEvent(dict): Returns: Optional[str]: 用户 ID。 """ - return self.get("FromUserName") - + return self.get('FromUserName') + @property def agent_id(self) -> Optional[int]: """ @@ -85,7 +85,7 @@ class WecomEvent(dict): Returns: Optional[int]: 机器人 ID。 """ - return self.get("AgentID") + return self.get('AgentID') @property def receiver_id(self) -> Optional[str]: @@ -95,7 +95,7 @@ class WecomEvent(dict): Returns: Optional[str]: 接收者 ID。 """ - return self.get("ToUserName") + return self.get('ToUserName') @property def message_id(self) -> Optional[str]: @@ -105,7 +105,7 @@ class WecomEvent(dict): Returns: Optional[str]: 消息 ID。 """ - return self.get("MsgId") + return self.get('MsgId') @property def message(self) -> Optional[str]: @@ -115,7 +115,7 @@ class WecomEvent(dict): Returns: Optional[str]: 消息内容。 """ - return self.get("Content") + return self.get('Content') @property def media_id(self) -> Optional[str]: @@ -125,7 +125,7 @@ class WecomEvent(dict): Returns: Optional[str]: 媒体文件 ID。 """ - return self.get("MediaId") + return self.get('MediaId') @property def timestamp(self) -> Optional[int]: @@ -135,7 +135,7 @@ class WecomEvent(dict): Returns: Optional[int]: 时间戳。 """ - return self.get("CreateTime") + return self.get('CreateTime') @property def event_key(self) -> Optional[str]: @@ -145,7 +145,7 @@ class WecomEvent(dict): Returns: Optional[str]: 事件 Key。 """ - return self.get("EventKey") + return self.get('EventKey') def __getattr__(self, key: str) -> Optional[Any]: """ @@ -176,4 +176,4 @@ class WecomEvent(dict): Returns: str: 字符串表示。 """ - return f"" + return f'' diff --git a/main.py b/main.py index 5c86b7ca..8be603c6 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import asyncio # LangBot 终端启动入口 # 在此层级解决依赖项检查。 # LangBot/main.py @@ -14,9 +15,6 @@ asciiart = r""" """ -import asyncio - - async def main_entry(loop: asyncio.AbstractEventLoop): print(asciiart) @@ -29,20 +27,22 @@ async def main_entry(loop: asyncio.AbstractEventLoop): missing_deps = await deps.check_deps() if missing_deps: - print("以下依赖包未安装,将自动安装,请完成后重启程序:") + print('以下依赖包未安装,将自动安装,请完成后重启程序:') for dep in missing_deps: - print("-", dep) + print('-', dep) await deps.install_deps(missing_deps) - print("已自动安装缺失的依赖包,请重启程序。") + print('已自动安装缺失的依赖包,请重启程序。') sys.exit(0) - + # check plugin deps await deps.precheck_plugin_deps() # 检查pydantic版本,如果没有 pydantic.v1,则把 pydantic 映射为 v1 import pydantic.version + if pydantic.version.VERSION < '2.0': import pydantic + sys.modules['pydantic.v1'] = pydantic # 检查配置文件 @@ -52,11 +52,12 @@ async def main_entry(loop: asyncio.AbstractEventLoop): generated_files = await files.generate_files() if generated_files: - print("以下文件不存在,已自动生成:") + print('以下文件不存在,已自动生成:') for file in generated_files: - print("-", file) + print('-', file) from pkg.core import boot + await boot.main(loop) @@ -66,8 +67,8 @@ if __name__ == '__main__': # 必须大于 3.10.1 if sys.version_info < (3, 10, 1): - print("需要 Python 3.10.1 及以上版本,当前 Python 版本为:", sys.version) - input("按任意键退出...") + print('需要 Python 3.10.1 及以上版本,当前 Python 版本为:', sys.version) + input('按任意键退出...') exit(1) # 检查本目录是否有main.py,且包含LangBot字符串 @@ -78,11 +79,11 @@ if __name__ == '__main__': else: with open('main.py', 'r', encoding='utf-8') as f: content = f.read() - if "LangBot/main.py" not in content: + if 'LangBot/main.py' not in content: invalid_pwd = True if invalid_pwd: - print("请在 LangBot 项目根目录下以命令形式运行此程序。") - input("按任意键退出...") + print('请在 LangBot 项目根目录下以命令形式运行此程序。') + input('按任意键退出...') exit(1) loop = asyncio.new_event_loop() diff --git a/pkg/api/http/controller/group.py b/pkg/api/http/controller/group.py index 7186802f..efbb7247 100644 --- a/pkg/api/http/controller/group.py +++ b/pkg/api/http/controller/group.py @@ -13,6 +13,7 @@ from ....core import app preregistered_groups: list[type[RouterGroup]] = [] """RouterGroup 的预注册列表""" + def group_class(name: str, path: str) -> None: """注册一个 RouterGroup""" @@ -27,12 +28,12 @@ def group_class(name: str, path: str) -> None: class AuthType(enum.Enum): """认证类型""" + NONE = 'none' USER_TOKEN = 'user-token' class RouterGroup(abc.ABC): - name: str path: str @@ -49,17 +50,24 @@ class RouterGroup(abc.ABC): async def initialize(self) -> None: pass - def route(self, rule: str, auth_type: AuthType = AuthType.USER_TOKEN, **options: typing.Any) -> typing.Callable[[RouteCallable], RouteCallable]: # decorator + def route( + self, + rule: str, + auth_type: AuthType = AuthType.USER_TOKEN, + **options: typing.Any, + ) -> typing.Callable[[RouteCallable], RouteCallable]: # decorator """注册一个路由""" + def decorator(f: RouteCallable) -> RouteCallable: nonlocal rule rule = self.path + rule async def handler_error(*args, **kwargs): - if auth_type == AuthType.USER_TOKEN: # 从Authorization头中获取token - token = quart.request.headers.get('Authorization', '').replace('Bearer ', '') + token = quart.request.headers.get('Authorization', '').replace( + 'Bearer ', '' + ) if not token: return self.http_status(401, -1, '未提供有效的用户令牌') @@ -75,11 +83,11 @@ class RouterGroup(abc.ABC): try: return await f(*args, **kwargs) - except Exception as e: # 自动 500 + except Exception: # 自动 500 traceback.print_exc() # return self.http_status(500, -2, str(e)) return self.http_status(500, -2, 'internal server error') - + new_f = handler_error new_f.__name__ = (self.name + rule).replace('/', '__') new_f.__doc__ = f.__doc__ @@ -91,20 +99,24 @@ class RouterGroup(abc.ABC): def success(self, data: typing.Any = None) -> quart.Response: """返回一个 200 响应""" - return quart.jsonify({ - 'code': 0, - 'msg': 'ok', - 'data': data, - }) - + return quart.jsonify( + { + 'code': 0, + 'msg': 'ok', + 'data': data, + } + ) + def fail(self, code: int, msg: str) -> quart.Response: """返回一个异常响应""" - return quart.jsonify({ - 'code': code, - 'msg': msg, - }) - + return quart.jsonify( + { + 'code': code, + 'msg': msg, + } + ) + def http_status(self, status: int, code: int, msg: str) -> quart.Response: """返回一个指定状态码的响应""" return self.fail(code, msg), status diff --git a/pkg/api/http/controller/groups/logs.py b/pkg/api/http/controller/groups/logs.py index 4244d889..b0643cb6 100644 --- a/pkg/api/http/controller/groups/logs.py +++ b/pkg/api/http/controller/groups/logs.py @@ -1,32 +1,29 @@ from __future__ import annotations -import traceback import quart -from .....core import app from .. import group @group.group_class('logs', '/api/v1/logs') class LogsRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) async def _() -> str: - start_page_number = int(quart.request.args.get('start_page_number', 0)) start_offset = int(quart.request.args.get('start_offset', 0)) - logs_str, end_page_number, end_offset = self.ap.log_cache.get_log_by_pointer( - start_page_number=start_page_number, - start_offset=start_offset + logs_str, end_page_number, end_offset = ( + self.ap.log_cache.get_log_by_pointer( + start_page_number=start_page_number, start_offset=start_offset + ) ) return self.success( data={ - "logs": logs_str, - "end_page_number": end_page_number, - "end_offset": end_offset + 'logs': logs_str, + 'end_page_number': end_page_number, + 'end_offset': end_offset, } ) diff --git a/pkg/api/http/controller/groups/pipelines.py b/pkg/api/http/controller/groups/pipelines.py index 400da593..02564e58 100644 --- a/pkg/api/http/controller/groups/pipelines.py +++ b/pkg/api/http/controller/groups/pipelines.py @@ -3,46 +3,41 @@ from __future__ import annotations import quart from .. import group -from .....entity.persistence import pipeline @group.group_class('pipelines', '/api/v1/pipelines') class PipelinesRouterGroup(group.RouterGroup): - async def initialize(self) -> None: - @self.route('', methods=['GET', 'POST']) async def _() -> str: if quart.request.method == 'GET': - return self.success(data={ - 'pipelines': await self.ap.pipeline_service.get_pipelines() - }) + return self.success( + data={'pipelines': await self.ap.pipeline_service.get_pipelines()} + ) elif quart.request.method == 'POST': json_data = await quart.request.json - pipeline_uuid = await self.ap.pipeline_service.create_pipeline(json_data) + pipeline_uuid = await self.ap.pipeline_service.create_pipeline( + json_data + ) - return self.success(data={ - 'uuid': pipeline_uuid - }) + return self.success(data={'uuid': pipeline_uuid}) @self.route('/_/metadata', methods=['GET']) async def _() -> str: - return self.success(data={ - 'configs': await self.ap.pipeline_service.get_pipeline_metadata() - }) + return self.success( + data={'configs': await self.ap.pipeline_service.get_pipeline_metadata()} + ) @self.route('/', methods=['GET', 'PUT', 'DELETE']) async def _(pipeline_uuid: str) -> str: if quart.request.method == 'GET': pipeline = await self.ap.pipeline_service.get_pipeline(pipeline_uuid) - + if pipeline is None: return self.http_status(404, -1, 'pipeline not found') - return self.success(data={ - 'pipeline': pipeline - }) + return self.success(data={'pipeline': pipeline}) elif quart.request.method == 'PUT': json_data = await quart.request.json @@ -53,4 +48,3 @@ class PipelinesRouterGroup(group.RouterGroup): await self.ap.pipeline_service.delete_pipeline(pipeline_uuid) return self.success() - diff --git a/pkg/api/http/controller/groups/platform/adapters.py b/pkg/api/http/controller/groups/platform/adapters.py index fa8b2d9c..511ae003 100644 --- a/pkg/api/http/controller/groups/platform/adapters.py +++ b/pkg/api/http/controller/groups/platform/adapters.py @@ -5,29 +5,31 @@ from ... import group @group.group_class('adapters', '/api/v1/platform/adapters') class AdaptersRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('', methods=['GET']) async def _() -> str: - return self.success(data={ - 'adapters': self.ap.platform_mgr.get_available_adapters_info() - }) - + return self.success( + data={'adapters': self.ap.platform_mgr.get_available_adapters_info()} + ) + @self.route('/', methods=['GET']) async def _(adapter_name: str) -> str: - adapter_info = self.ap.platform_mgr.get_available_adapter_info_by_name(adapter_name) + adapter_info = self.ap.platform_mgr.get_available_adapter_info_by_name( + adapter_name + ) if adapter_info is None: return self.http_status(404, -1, 'adapter not found') - return self.success(data={ - 'adapter': adapter_info - }) - + return self.success(data={'adapter': adapter_info}) + @self.route('//icon', methods=['GET']) async def _(adapter_name: str) -> quart.Response: - - adapter_manifest = self.ap.platform_mgr.get_available_adapter_manifest_by_name(adapter_name) + adapter_manifest = ( + self.ap.platform_mgr.get_available_adapter_manifest_by_name( + adapter_name + ) + ) if adapter_manifest is None: return self.http_status(404, -1, 'adapter not found') @@ -37,4 +39,4 @@ class AdaptersRouterGroup(group.RouterGroup): if icon_path is None: return self.http_status(404, -1, 'icon not found') - return await quart.send_file(icon_path) \ No newline at end of file + return await quart.send_file(icon_path) diff --git a/pkg/api/http/controller/groups/platform/bots.py b/pkg/api/http/controller/groups/platform/bots.py index fe20aa53..af248fac 100644 --- a/pkg/api/http/controller/groups/platform/bots.py +++ b/pkg/api/http/controller/groups/platform/bots.py @@ -5,34 +5,27 @@ from ... import group @group.group_class('bots', '/api/v1/platform/bots') class BotsRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('', methods=['GET', 'POST']) async def _() -> str: if quart.request.method == 'GET': - return self.success(data={ - 'bots': await self.ap.bot_service.get_bots() - }) + return self.success(data={'bots': await self.ap.bot_service.get_bots()}) elif quart.request.method == 'POST': json_data = await quart.request.json bot_uuid = await self.ap.bot_service.create_bot(json_data) - return self.success(data={ - 'uuid': bot_uuid - }) - + return self.success(data={'uuid': bot_uuid}) + @self.route('/', methods=['GET', 'PUT', 'DELETE']) async def _(bot_uuid: str) -> str: if quart.request.method == 'GET': bot = await self.ap.bot_service.get_bot(bot_uuid) if bot is None: return self.http_status(404, -1, 'bot not found') - return self.success(data={ - 'bot': bot - }) + return self.success(data={'bot': bot}) elif quart.request.method == 'PUT': json_data = await quart.request.json await self.ap.bot_service.update_bot(bot_uuid, json_data) return self.success() elif quart.request.method == 'DELETE': await self.ap.bot_service.delete_bot(bot_uuid) - return self.success() \ No newline at end of file + return self.success() diff --git a/pkg/api/http/controller/groups/plugins.py b/pkg/api/http/controller/groups/plugins.py index 330231c2..1deecca6 100644 --- a/pkg/api/http/controller/groups/plugins.py +++ b/pkg/api/http/controller/groups/plugins.py @@ -1,17 +1,14 @@ from __future__ import annotations -import traceback - import quart -from .....core import app, taskmgr +from .....core import taskmgr from .. import group @group.group_class('plugins', '/api/v1/plugins') class PluginsRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) async def _() -> str: @@ -19,63 +16,69 @@ class PluginsRouterGroup(group.RouterGroup): plugins_data = [plugin.model_dump() for plugin in plugins] - return self.success(data={ - 'plugins': plugins_data - }) - - @self.route('///toggle', methods=['PUT'], auth_type=group.AuthType.USER_TOKEN) + return self.success(data={'plugins': plugins_data}) + + @self.route( + '///toggle', + methods=['PUT'], + auth_type=group.AuthType.USER_TOKEN, + ) async def _(author: str, plugin_name: str) -> str: data = await quart.request.json target_enabled = data.get('target_enabled') await self.ap.plugin_mgr.update_plugin_switch(plugin_name, target_enabled) return self.success() - - @self.route('///update', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) + + @self.route( + '///update', + methods=['POST'], + auth_type=group.AuthType.USER_TOKEN, + ) async def _(author: str, plugin_name: str) -> str: ctx = taskmgr.TaskContext.new() wrapper = self.ap.task_mgr.create_user_task( self.ap.plugin_mgr.update_plugin(plugin_name, task_context=ctx), - kind="plugin-operation", - name=f"plugin-update-{plugin_name}", - label=f"更新插件 {plugin_name}", - context=ctx + kind='plugin-operation', + name=f'plugin-update-{plugin_name}', + label=f'更新插件 {plugin_name}', + context=ctx, ) - return self.success(data={ - 'task_id': wrapper.id - }) - - @self.route('//', methods=['GET', 'DELETE'], auth_type=group.AuthType.USER_TOKEN) + return self.success(data={'task_id': wrapper.id}) + + @self.route( + '//', + methods=['GET', 'DELETE'], + auth_type=group.AuthType.USER_TOKEN, + ) async def _(author: str, plugin_name: str) -> str: if quart.request.method == 'GET': plugin = self.ap.plugin_mgr.get_plugin(author, plugin_name) if plugin is None: return self.http_status(404, -1, 'plugin not found') - return self.success(data={ - 'plugin': plugin.model_dump() - }) + return self.success(data={'plugin': plugin.model_dump()}) elif quart.request.method == 'DELETE': ctx = taskmgr.TaskContext.new() wrapper = self.ap.task_mgr.create_user_task( self.ap.plugin_mgr.uninstall_plugin(plugin_name, task_context=ctx), - kind="plugin-operation", + kind='plugin-operation', name=f'plugin-remove-{plugin_name}', label=f'删除插件 {plugin_name}', - context=ctx + context=ctx, ) - return self.success(data={ - 'task_id': wrapper.id - }) - - @self.route('///config', methods=['GET', 'PUT'], auth_type=group.AuthType.USER_TOKEN) + return self.success(data={'task_id': wrapper.id}) + + @self.route( + '///config', + methods=['GET', 'PUT'], + auth_type=group.AuthType.USER_TOKEN, + ) async def _(author: str, plugin_name: str) -> quart.Response: plugin = self.ap.plugin_mgr.get_plugin(author, plugin_name) if plugin is None: return self.http_status(404, -1, 'plugin not found') if quart.request.method == 'GET': - return self.success(data={ - 'config': plugin.plugin_config - }) + return self.success(data={'config': plugin.plugin_config}) elif quart.request.method == 'PUT': data = await quart.request.json @@ -88,21 +91,21 @@ class PluginsRouterGroup(group.RouterGroup): data = await quart.request.json await self.ap.plugin_mgr.reorder_plugins(data.get('plugins')) return self.success() - - @self.route('/install/github', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) + + @self.route( + '/install/github', methods=['POST'], auth_type=group.AuthType.USER_TOKEN + ) async def _() -> str: data = await quart.request.json - + ctx = taskmgr.TaskContext.new() short_source_str = data['source'][-8:] wrapper = self.ap.task_mgr.create_user_task( self.ap.plugin_mgr.install_plugin(data['source'], task_context=ctx), - kind="plugin-operation", - name=f'plugin-install-github', + kind='plugin-operation', + name='plugin-install-github', label=f'安装插件 ...{short_source_str}', - context=ctx + context=ctx, ) - return self.success(data={ - 'task_id': wrapper.id - }) + return self.success(data={'task_id': wrapper.id}) diff --git a/pkg/api/http/controller/groups/provider/models.py b/pkg/api/http/controller/groups/provider/models.py index 81e9078b..eaf96047 100644 --- a/pkg/api/http/controller/groups/provider/models.py +++ b/pkg/api/http/controller/groups/provider/models.py @@ -1,28 +1,23 @@ import quart -import uuid from ... import group -from ......entity.persistence import model @group.group_class('models/llm', '/api/v1/provider/models/llm') class LLMModelsRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('', methods=['GET', 'POST']) async def _() -> str: if quart.request.method == 'GET': - return self.success(data={ - 'models': await self.ap.model_service.get_llm_models() - }) + return self.success( + data={'models': await self.ap.model_service.get_llm_models()} + ) elif quart.request.method == 'POST': json_data = await quart.request.json model_uuid = await self.ap.model_service.create_llm_model(json_data) - return self.success(data={ - 'uuid': model_uuid - }) + return self.success(data={'uuid': model_uuid}) @self.route('/', methods=['GET', 'DELETE']) async def _(model_uuid: str) -> str: @@ -32,9 +27,7 @@ class LLMModelsRouterGroup(group.RouterGroup): if model is None: return self.http_status(404, -1, 'model not found') - return self.success(data={ - 'model': model - }) + return self.success(data={'model': model}) # elif quart.request.method == 'PUT': # json_data = await quart.request.json diff --git a/pkg/api/http/controller/groups/provider/requesters.py b/pkg/api/http/controller/groups/provider/requesters.py index 18939d32..f95dfdb4 100644 --- a/pkg/api/http/controller/groups/provider/requesters.py +++ b/pkg/api/http/controller/groups/provider/requesters.py @@ -5,29 +5,31 @@ from ... import group @group.group_class('provider/requesters', '/api/v1/provider/requesters') class RequestersRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('', methods=['GET']) async def _() -> quart.Response: - return self.success(data={ - 'requesters': self.ap.model_mgr.get_available_requesters_info() - }) - + return self.success( + data={'requesters': self.ap.model_mgr.get_available_requesters_info()} + ) + @self.route('/', methods=['GET']) async def _(requester_name: str) -> quart.Response: - - requester_info = self.ap.model_mgr.get_available_requester_info_by_name(requester_name) + requester_info = self.ap.model_mgr.get_available_requester_info_by_name( + requester_name + ) if requester_info is None: return self.http_status(404, -1, 'requester not found') - return self.success(data={ - 'requester': requester_info - }) - + return self.success(data={'requester': requester_info}) + @self.route('//icon', methods=['GET']) async def _(requester_name: str) -> quart.Response: - requester_manifest = self.ap.model_mgr.get_available_requester_manifest_by_name(requester_name) + requester_manifest = ( + self.ap.model_mgr.get_available_requester_manifest_by_name( + requester_name + ) + ) if requester_manifest is None: return self.http_status(404, -1, 'requester not found') diff --git a/pkg/api/http/controller/groups/stats.py b/pkg/api/http/controller/groups/stats.py index 43d56f27..7b1d4353 100644 --- a/pkg/api/http/controller/groups/stats.py +++ b/pkg/api/http/controller/groups/stats.py @@ -1,23 +1,21 @@ -import quart -import asyncio - -from .....core import app, taskmgr from .. import group @group.group_class('stats', '/api/v1/stats') class StatsRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('/basic', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) async def _() -> str: - conv_count = 0 for session in self.ap.sess_mgr.session_list: - conv_count += len(session.conversations if session.conversations is not None else []) + conv_count += len( + session.conversations if session.conversations is not None else [] + ) - return self.success(data={ - 'active_session_count': len(self.ap.sess_mgr.session_list), - 'conversation_count': conv_count, - 'query_count': self.ap.query_pool.query_id_counter, - }) + return self.success( + data={ + 'active_session_count': len(self.ap.sess_mgr.session_list), + 'conversation_count': conv_count, + 'query_count': self.ap.query_pool.query_id_counter, + } + ) diff --git a/pkg/api/http/controller/groups/system.py b/pkg/api/http/controller/groups/system.py index 04ace284..c586ea27 100644 --- a/pkg/api/http/controller/groups/system.py +++ b/pkg/api/http/controller/groups/system.py @@ -1,63 +1,62 @@ import quart -import asyncio -from .....core import app, taskmgr from .. import group from .....utils import constants @group.group_class('system', '/api/v1/system') class SystemRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('/info', methods=['GET'], auth_type=group.AuthType.NONE) async def _() -> str: return self.success( data={ - "version": constants.semantic_version, - "debug": constants.debug_mode, - "enabled_platform_count": len(self.ap.platform_mgr.get_running_adapters()) + 'version': constants.semantic_version, + 'debug': constants.debug_mode, + 'enabled_platform_count': len( + self.ap.platform_mgr.get_running_adapters() + ), } ) @self.route('/tasks', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) async def _() -> str: - task_type = quart.request.args.get("type") + task_type = quart.request.args.get('type') if task_type == '': task_type = None - return self.success( - data=self.ap.task_mgr.get_tasks_dict(task_type) - ) - - @self.route('/tasks/', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) + return self.success(data=self.ap.task_mgr.get_tasks_dict(task_type)) + + @self.route( + '/tasks/', methods=['GET'], auth_type=group.AuthType.USER_TOKEN + ) async def _(task_id: str) -> str: task = self.ap.task_mgr.get_task_by_id(int(task_id)) if task is None: - return self.http_status(404, 404, "Task not found") - + return self.http_status(404, 404, 'Task not found') + return self.success(data=task.to_dict()) - + @self.route('/reload', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) async def _() -> str: json_data = await quart.request.json - scope = json_data.get("scope") + scope = json_data.get('scope') - await self.ap.reload( - scope=scope - ) + await self.ap.reload(scope=scope) return self.success() - @self.route('/_debug/exec', methods=['POST'], auth_type=group.AuthType.USER_TOKEN) + @self.route( + '/_debug/exec', methods=['POST'], auth_type=group.AuthType.USER_TOKEN + ) async def _() -> str: if not constants.debug_mode: - return self.http_status(403, 403, "Forbidden") - + return self.http_status(403, 403, 'Forbidden') + py_code = await quart.request.data ap = self.ap - return self.success(data=exec(py_code, {"ap": ap})) + return self.success(data=exec(py_code, {'ap': ap})) diff --git a/pkg/api/http/controller/groups/user.py b/pkg/api/http/controller/groups/user.py index 3cd08240..4c330782 100644 --- a/pkg/api/http/controller/groups/user.py +++ b/pkg/api/http/controller/groups/user.py @@ -1,22 +1,19 @@ import quart -import jwt import argon2 from .. import group -from .....entity.persistence import user @group.group_class('user', '/api/v1/user') class UserRouterGroup(group.RouterGroup): - async def initialize(self) -> None: @self.route('/init', methods=['GET', 'POST'], auth_type=group.AuthType.NONE) async def _() -> str: if quart.request.method == 'GET': - return self.success(data={ - 'initialized': await self.ap.user_service.is_initialized() - }) - + return self.success( + data={'initialized': await self.ap.user_service.is_initialized()} + ) + if await self.ap.user_service.is_initialized(): return self.fail(1, '系统已初始化') @@ -28,24 +25,24 @@ class UserRouterGroup(group.RouterGroup): await self.ap.user_service.create_user(user_email, password) return self.success() - + @self.route('/auth', methods=['POST'], auth_type=group.AuthType.NONE) async def _() -> str: json_data = await quart.request.json try: - token = await self.ap.user_service.authenticate(json_data['user'], json_data['password']) + token = await self.ap.user_service.authenticate( + json_data['user'], json_data['password'] + ) except argon2.exceptions.VerifyMismatchError: return self.fail(1, '用户名或密码错误') - return self.success(data={ - 'token': token - }) + return self.success(data={'token': token}) - @self.route('/check-token', methods=['GET'], auth_type=group.AuthType.USER_TOKEN) + @self.route( + '/check-token', methods=['GET'], auth_type=group.AuthType.USER_TOKEN + ) async def _(user_email: str) -> str: token = await self.ap.user_service.generate_jwt_token(user_email) - return self.success(data={ - 'token': token - }) + return self.success(data={'token': token}) diff --git a/pkg/api/http/controller/main.py b/pkg/api/http/controller/main.py index 0d6bcd15..e5e42df6 100644 --- a/pkg/api/http/controller/main.py +++ b/pkg/api/http/controller/main.py @@ -7,15 +7,19 @@ import quart import quart_cors from ....core import app, entities as core_entities +from ....utils import importutil -from .groups import logs, system, plugins, stats, user, pipelines -from .groups.provider import models, requesters -from .groups.platform import bots, adapters +from . import groups from . import group +from .groups import provider as groups_provider +from .groups import platform as groups_platform + +importutil.import_modules_in_pkg(groups) +importutil.import_modules_in_pkg(groups_provider) +importutil.import_modules_in_pkg(groups_platform) class HTTPController: - ap: app.Application quart_app: quart.Quart @@ -23,7 +27,7 @@ class HTTPController: def __init__(self, ap: app.Application) -> None: self.ap = ap self.quart_app = quart.Quart(__name__) - quart_cors.cors(self.quart_app, allow_origin="*") + quart_cors.cors(self.quart_app, allow_origin='*') async def initialize(self) -> None: await self.register_routes() @@ -37,11 +41,9 @@ class HTTPController: async def exception_handler(*args, **kwargs): try: - await self.quart_app.run_task( - *args, **kwargs - ) + await self.quart_app.run_task(*args, **kwargs) except Exception as e: - self.ap.logger.error(f"启动 HTTP 服务失败: {e}") + self.ap.logger.error(f'启动 HTTP 服务失败: {e}') self.ap.task_mgr.create_task( exception_handler( @@ -49,63 +51,62 @@ class HTTPController: port=self.ap.instance_config.data['api']['port'], shutdown_trigger=shutdown_trigger_placeholder, ), - name="http-api-quart", + name='http-api-quart', scopes=[core_entities.LifecycleControlScope.APPLICATION], ) # await asyncio.sleep(5) async def register_routes(self) -> None: - - @self.quart_app.route("/healthz") + @self.quart_app.route('/healthz') async def healthz(): - return {"code": 0, "msg": "ok"} + return {'code': 0, 'msg': 'ok'} for g in group.preregistered_groups: ginst = g(self.ap, self.quart_app) await ginst.initialize() - frontend_path = "web/out" + frontend_path = 'web/out' - @self.quart_app.route("/") + @self.quart_app.route('/') async def index(): - return await quart.send_from_directory(frontend_path, "index.html", mimetype="text/html") + return await quart.send_from_directory( + frontend_path, 'index.html', mimetype='text/html' + ) - @self.quart_app.route("/") + @self.quart_app.route('/') async def static_file(path: str): if not os.path.exists(os.path.join(frontend_path, path)): - if os.path.exists(os.path.join(frontend_path, path+".html")): + if os.path.exists(os.path.join(frontend_path, path + '.html')): path += '.html' else: return await quart.send_from_directory(frontend_path, '404.html') mimetype = None - if path.endswith(".html"): - mimetype = "text/html" - elif path.endswith(".js"): - mimetype = "application/javascript" - elif path.endswith(".css"): - mimetype = "text/css" - elif path.endswith(".png"): - mimetype = "image/png" - elif path.endswith(".jpg"): - mimetype = "image/jpeg" - elif path.endswith(".jpeg"): - mimetype = "image/jpeg" - elif path.endswith(".gif"): - mimetype = "image/gif" - elif path.endswith(".svg"): - mimetype = "image/svg+xml" - elif path.endswith(".ico"): - mimetype = "image/x-icon" - elif path.endswith(".json"): - mimetype = "application/json" - elif path.endswith(".txt"): - mimetype = "text/plain" + if path.endswith('.html'): + mimetype = 'text/html' + elif path.endswith('.js'): + mimetype = 'application/javascript' + elif path.endswith('.css'): + mimetype = 'text/css' + elif path.endswith('.png'): + mimetype = 'image/png' + elif path.endswith('.jpg'): + mimetype = 'image/jpeg' + elif path.endswith('.jpeg'): + mimetype = 'image/jpeg' + elif path.endswith('.gif'): + mimetype = 'image/gif' + elif path.endswith('.svg'): + mimetype = 'image/svg+xml' + elif path.endswith('.ico'): + mimetype = 'image/x-icon' + elif path.endswith('.json'): + mimetype = 'application/json' + elif path.endswith('.txt'): + mimetype = 'text/plain' return await quart.send_from_directory( - frontend_path, - path, - mimetype=mimetype + frontend_path, path, mimetype=mimetype ) diff --git a/pkg/api/http/service/bot.py b/pkg/api/http/service/bot.py index 557b63f1..23e9fa5b 100644 --- a/pkg/api/http/service/bot.py +++ b/pkg/api/http/service/bot.py @@ -1,7 +1,6 @@ from __future__ import annotations import uuid -import datetime import sqlalchemy from ....core import app @@ -29,13 +28,15 @@ class BotService: self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot) for bot in bots ] - + async def get_bot(self, bot_uuid: str) -> dict | None: """获取机器人""" result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid) + sqlalchemy.select(persistence_bot.Bot).where( + persistence_bot.Bot.uuid == bot_uuid + ) ) - + bot = result.first() if bot is None: @@ -50,7 +51,9 @@ class BotService: # checkout the default pipeline result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(persistence_pipeline.LegacyPipeline.is_default == True) + sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( + persistence_pipeline.LegacyPipeline.is_default == True + ) ) pipeline = result.first() if pipeline is not None: @@ -64,7 +67,7 @@ class BotService: bot = await self.get_bot(bot_data['uuid']) await self.ap.platform_mgr.load_bot(bot) - + return bot_data['uuid'] async def update_bot(self, bot_uuid: str, bot_data: dict) -> None: @@ -75,19 +78,24 @@ class BotService: # set use_pipeline_name if 'use_pipeline_uuid' in bot_data: result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(persistence_pipeline.LegacyPipeline.uuid == bot_data['use_pipeline_uuid']) + sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( + persistence_pipeline.LegacyPipeline.uuid + == bot_data['use_pipeline_uuid'] + ) ) pipeline = result.first() if pipeline is not None: bot_data['use_pipeline_name'] = pipeline.name else: - raise Exception("Pipeline not found") + raise Exception('Pipeline not found') await self.ap.persistence_mgr.execute_async( - sqlalchemy.update(persistence_bot.Bot).values(bot_data).where(persistence_bot.Bot.uuid == bot_uuid) + sqlalchemy.update(persistence_bot.Bot) + .values(bot_data) + .where(persistence_bot.Bot.uuid == bot_uuid) ) await self.ap.platform_mgr.remove_bot(bot_uuid) - + # select from db bot = await self.get_bot(bot_uuid) @@ -100,7 +108,7 @@ class BotService: """删除机器人""" await self.ap.platform_mgr.remove_bot(bot_uuid) await self.ap.persistence_mgr.execute_async( - sqlalchemy.delete(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid) + sqlalchemy.delete(persistence_bot.Bot).where( + persistence_bot.Bot.uuid == bot_uuid + ) ) - - diff --git a/pkg/api/http/service/model.py b/pkg/api/http/service/model.py index e74e975d..8a71bf1c 100644 --- a/pkg/api/http/service/model.py +++ b/pkg/api/http/service/model.py @@ -1,7 +1,6 @@ from __future__ import annotations import uuid -import datetime import sqlalchemy from ....core import app @@ -10,7 +9,6 @@ from ....entity.persistence import pipeline as persistence_pipeline class ModelsService: - ap: app.Application def __init__(self, ap: app.Application) -> None: @@ -26,15 +24,12 @@ class ModelsService: self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) for model in models ] - - async def create_llm_model(self, model_data: dict) -> str: + async def create_llm_model(self, model_data: dict) -> str: model_data['uuid'] = str(uuid.uuid4()) await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(persistence_model.LLMModel).values( - **model_data - ) + sqlalchemy.insert(persistence_model.LLMModel).values(**model_data) ) llm_model = await self.get_llm_model(model_data['uuid']) @@ -43,22 +38,24 @@ class ModelsService: # check if default pipeline has no model bound result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(persistence_pipeline.LegacyPipeline.is_default == True) + sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( + persistence_pipeline.LegacyPipeline.is_default == True + ) ) pipeline = result.first() if pipeline is not None and pipeline.config['ai']['local-agent']['model'] == '': pipeline_config = pipeline.config pipeline_config['ai']['local-agent']['model'] = model_data['uuid'] - pipeline_data = { - "config": pipeline_config - } - await self.ap.pipeline_service.update_pipeline(pipeline.uuid, pipeline_data) + pipeline_data = {'config': pipeline_config} + await self.ap.pipeline_service.update_pipeline(pipeline.uuid, pipeline_data) return model_data['uuid'] async def get_llm_model(self, model_uuid: str) -> dict | None: result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid) + sqlalchemy.select(persistence_model.LLMModel).where( + persistence_model.LLMModel.uuid == model_uuid + ) ) model = result.first() @@ -66,14 +63,18 @@ class ModelsService: if model is None: return None - return self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) + return self.ap.persistence_mgr.serialize_model( + persistence_model.LLMModel, model + ) async def update_llm_model(self, model_uuid: str, model_data: dict) -> None: if 'uuid' in model_data: del model_data['uuid'] - + await self.ap.persistence_mgr.execute_async( - sqlalchemy.update(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid).values(**model_data) + sqlalchemy.update(persistence_model.LLMModel) + .where(persistence_model.LLMModel.uuid == model_uuid) + .values(**model_data) ) await self.ap.model_mgr.remove_llm_model(model_uuid) @@ -84,7 +85,9 @@ class ModelsService: async def delete_llm_model(self, model_uuid: str) -> None: await self.ap.persistence_mgr.execute_async( - sqlalchemy.delete(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid) + sqlalchemy.delete(persistence_model.LLMModel).where( + persistence_model.LLMModel.uuid == model_uuid + ) ) await self.ap.model_mgr.remove_llm_model(model_uuid) diff --git a/pkg/api/http/service/pipeline.py b/pkg/api/http/service/pipeline.py index 6e8a66dc..0dd73ef2 100644 --- a/pkg/api/http/service/pipeline.py +++ b/pkg/api/http/service/pipeline.py @@ -2,7 +2,6 @@ from __future__ import annotations import uuid import json -import datetime import sqlalchemy from ....core import app @@ -10,69 +9,79 @@ from ....entity.persistence import pipeline as persistence_pipeline default_stage_order = [ - "GroupRespondRuleCheckStage", # 群响应规则检查 - "BanSessionCheckStage", # 封禁会话检查 - "PreContentFilterStage", # 内容过滤前置阶段 - "PreProcessor", # 预处理器 - "ConversationMessageTruncator", # 会话消息截断器 - "RequireRateLimitOccupancy", # 请求速率限制占用 - "MessageProcessor", # 处理器 - "ReleaseRateLimitOccupancy", # 释放速率限制占用 - "PostContentFilterStage", # 内容过滤后置阶段 - "ResponseWrapper", # 响应包装器 - "LongTextProcessStage", # 长文本处理 - "SendResponseBackStage", # 发送响应 + 'GroupRespondRuleCheckStage', # 群响应规则检查 + 'BanSessionCheckStage', # 封禁会话检查 + 'PreContentFilterStage', # 内容过滤前置阶段 + 'PreProcessor', # 预处理器 + 'ConversationMessageTruncator', # 会话消息截断器 + 'RequireRateLimitOccupancy', # 请求速率限制占用 + 'MessageProcessor', # 处理器 + 'ReleaseRateLimitOccupancy', # 释放速率限制占用 + 'PostContentFilterStage', # 内容过滤后置阶段 + 'ResponseWrapper', # 响应包装器 + 'LongTextProcessStage', # 长文本处理 + 'SendResponseBackStage', # 发送响应 ] class PipelineService: ap: app.Application - + def __init__(self, ap: app.Application) -> None: self.ap = ap - + async def get_pipeline_metadata(self) -> dict: return [ self.ap.pipeline_config_meta_trigger.data, self.ap.pipeline_config_meta_safety.data, self.ap.pipeline_config_meta_ai.data, - self.ap.pipeline_config_meta_output.data + self.ap.pipeline_config_meta_output.data, ] async def get_pipelines(self) -> list[dict]: result = await self.ap.persistence_mgr.execute_async( sqlalchemy.select(persistence_pipeline.LegacyPipeline) ) - + pipelines = result.all() return [ - self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline) + self.ap.persistence_mgr.serialize_model( + persistence_pipeline.LegacyPipeline, pipeline + ) for pipeline in pipelines ] - + async def get_pipeline(self, pipeline_uuid: str) -> dict | None: result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(persistence_pipeline.LegacyPipeline.uuid == pipeline_uuid) + sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( + persistence_pipeline.LegacyPipeline.uuid == pipeline_uuid + ) ) - + pipeline = result.first() if pipeline is None: return None - return self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline) + return self.ap.persistence_mgr.serialize_model( + persistence_pipeline.LegacyPipeline, pipeline + ) async def create_pipeline(self, pipeline_data: dict, default: bool = False) -> str: pipeline_data['uuid'] = str(uuid.uuid4()) pipeline_data['for_version'] = self.ap.ver_mgr.get_current_version() pipeline_data['stages'] = default_stage_order.copy() pipeline_data['is_default'] = default - pipeline_data['config'] = json.load(open('templates/default-pipeline-config.json', 'r', encoding='utf-8')) + pipeline_data['config'] = json.load( + open('templates/default-pipeline-config.json', 'r', encoding='utf-8') + ) await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(persistence_pipeline.LegacyPipeline).values(**pipeline_data) + sqlalchemy.insert(persistence_pipeline.LegacyPipeline).values( + **pipeline_data + ) ) - + pipeline = await self.get_pipeline(pipeline_data['uuid']) await self.ap.pipeline_mgr.load_pipeline(pipeline) @@ -90,7 +99,9 @@ class PipelineService: del pipeline_data['is_default'] await self.ap.persistence_mgr.execute_async( - sqlalchemy.update(persistence_pipeline.LegacyPipeline).where(persistence_pipeline.LegacyPipeline.uuid == pipeline_uuid).values(**pipeline_data) + sqlalchemy.update(persistence_pipeline.LegacyPipeline) + .where(persistence_pipeline.LegacyPipeline.uuid == pipeline_uuid) + .values(**pipeline_data) ) await self.ap.pipeline_mgr.remove_pipeline(pipeline_uuid) @@ -101,6 +112,8 @@ class PipelineService: async def delete_pipeline(self, pipeline_uuid: str) -> None: await self.ap.persistence_mgr.execute_async( - sqlalchemy.delete(persistence_pipeline.LegacyPipeline).where(persistence_pipeline.LegacyPipeline.uuid == pipeline_uuid) + sqlalchemy.delete(persistence_pipeline.LegacyPipeline).where( + persistence_pipeline.LegacyPipeline.uuid == pipeline_uuid + ) ) await self.ap.pipeline_mgr.remove_pipeline(pipeline_uuid) diff --git a/pkg/api/http/service/user.py b/pkg/api/http/service/user.py index 4b31f1c0..2edfd874 100644 --- a/pkg/api/http/service/user.py +++ b/pkg/api/http/service/user.py @@ -11,7 +11,6 @@ from ....utils import constants class UserService: - ap: app.Application def __init__(self, ap: app.Application) -> None: @@ -24,7 +23,7 @@ class UserService: result_list = result.all() return result_list is not None and len(result_list) > 0 - + async def create_user(self, user_email: str, password: str) -> None: ph = argon2.PasswordHasher() @@ -32,8 +31,7 @@ class UserService: await self.ap.persistence_mgr.execute_async( sqlalchemy.insert(user.User).values( - user=user_email, - password=hashed_password + user=user_email, password=hashed_password ) ) @@ -61,12 +59,12 @@ class UserService: payload = { 'user': user_email, - 'iss': 'LangBot-'+constants.edition, - 'exp': datetime.datetime.now() + datetime.timedelta(seconds=jwt_expire) + 'iss': 'LangBot-' + constants.edition, + 'exp': datetime.datetime.now() + datetime.timedelta(seconds=jwt_expire), } return jwt.encode(payload, jwt_secret, algorithm='HS256') - + async def verify_jwt_token(self, token: str) -> str: jwt_secret = self.ap.instance_config.data['system']['jwt']['secret'] diff --git a/pkg/audit/__init__.py b/pkg/audit/__init__.py index c1a8353b..5f89c8b8 100644 --- a/pkg/audit/__init__.py +++ b/pkg/audit/__init__.py @@ -1,3 +1,3 @@ """ 审计相关操作 -""" \ No newline at end of file +""" diff --git a/pkg/audit/center/apigroup.py b/pkg/audit/center/apigroup.py index 4b20a09a..ac5e5117 100644 --- a/pkg/audit/center/apigroup.py +++ b/pkg/audit/center/apigroup.py @@ -3,11 +3,9 @@ from __future__ import annotations import abc import uuid import json -import logging import asyncio import aiohttp -import requests from ...core import app, entities as core_entities @@ -38,22 +36,22 @@ class APIGroup(metaclass=abc.ABCMeta): """ 执行请求 """ - self._runtime_info["account_id"] = "-1" + self._runtime_info['account_id'] = '-1' url = self.prefix + path data = json.dumps(data) - headers["Content-Type"] = "application/json" + headers['Content-Type'] = 'application/json' try: async with aiohttp.ClientSession() as session: async with session.request( method, url, data=data, params=params, headers=headers, **kwargs ) as resp: - self.ap.logger.debug("data: %s", data) - self.ap.logger.debug("ret: %s", await resp.text()) + self.ap.logger.debug('data: %s', data) + self.ap.logger.debug('ret: %s', await resp.text()) except Exception as e: - self.ap.logger.debug(f"上报失败: {e}") + self.ap.logger.debug(f'上报失败: {e}') async def do( self, @@ -68,8 +66,8 @@ class APIGroup(metaclass=abc.ABCMeta): return self.ap.task_mgr.create_task( self._do(method, path, data, params, headers, **kwargs), - kind="telemetry-operation", - name=f"{method} {path}", + kind='telemetry-operation', + name=f'{method} {path}', scopes=[core_entities.LifecycleControlScope.APPLICATION], ).task @@ -80,7 +78,7 @@ class APIGroup(metaclass=abc.ABCMeta): def basic_info(self): """获取基本信息""" basic_info = APIGroup._basic_info.copy() - basic_info["rid"] = self.gen_rid() + basic_info['rid'] = self.gen_rid() return basic_info def runtime_info(self): diff --git a/pkg/audit/center/groups/main.py b/pkg/audit/center/groups/main.py index 854437a1..2c2302d1 100644 --- a/pkg/audit/center/groups/main.py +++ b/pkg/audit/center/groups/main.py @@ -9,7 +9,7 @@ class V2MainDataAPI(apigroup.APIGroup): def __init__(self, prefix: str, ap: app.Application): self.ap = ap - super().__init__(prefix+"/main", ap) + super().__init__(prefix + '/main', ap) async def do(self, *args, **kwargs): if not self.ap.instance_config.data['telemetry']['report']: @@ -25,31 +25,31 @@ class V2MainDataAPI(apigroup.APIGroup): ): """提交更新记录""" return await self.do( - "POST", - "/update", + 'POST', + '/update', data={ - "basic": self.basic_info(), - "update_info": { - "spent_seconds": spent_seconds, - "infer_reason": infer_reason, - "old_version": old_version, - "new_version": new_version, - } - } + 'basic': self.basic_info(), + 'update_info': { + 'spent_seconds': spent_seconds, + 'infer_reason': infer_reason, + 'old_version': old_version, + 'new_version': new_version, + }, + }, ) - + async def post_announcement_showed( self, ids: list[int], ): """提交公告已阅""" return await self.do( - "POST", - "/announcement", + 'POST', + '/announcement', data={ - "basic": self.basic_info(), - "announcement_info": { - "ids": ids, - } - } + 'basic': self.basic_info(), + 'announcement_info': { + 'ids': ids, + }, + }, ) diff --git a/pkg/audit/center/groups/plugin.py b/pkg/audit/center/groups/plugin.py index d6ed0b02..4978e5d0 100644 --- a/pkg/audit/center/groups/plugin.py +++ b/pkg/audit/center/groups/plugin.py @@ -9,39 +9,33 @@ class V2PluginDataAPI(apigroup.APIGroup): def __init__(self, prefix: str, ap: app.Application): self.ap = ap - super().__init__(prefix+"/plugin", ap) + super().__init__(prefix + '/plugin', ap) async def do(self, *args, **kwargs): if not self.ap.instance_config.data['telemetry']['report']: return None return await super().do(*args, **kwargs) - async def post_install_record( - self, - plugin: dict - ): + async def post_install_record(self, plugin: dict): """提交插件安装记录""" return await self.do( - "POST", - "/install", + 'POST', + '/install', data={ - "basic": self.basic_info(), - "plugin": plugin, - } + 'basic': self.basic_info(), + 'plugin': plugin, + }, ) - async def post_remove_record( - self, - plugin: dict - ): + async def post_remove_record(self, plugin: dict): """提交插件卸载记录""" return await self.do( - "POST", - "/remove", + 'POST', + '/remove', data={ - "basic": self.basic_info(), - "plugin": plugin, - } + 'basic': self.basic_info(), + 'plugin': plugin, + }, ) async def post_update_record( @@ -52,14 +46,14 @@ class V2PluginDataAPI(apigroup.APIGroup): ): """提交插件更新记录""" return await self.do( - "POST", - "/update", + 'POST', + '/update', data={ - "basic": self.basic_info(), - "plugin": plugin, - "update_info": { - "old_version": old_version, - "new_version": new_version, - } - } + 'basic': self.basic_info(), + 'plugin': plugin, + 'update_info': { + 'old_version': old_version, + 'new_version': new_version, + }, + }, ) diff --git a/pkg/audit/center/groups/usage.py b/pkg/audit/center/groups/usage.py index 79bc56f5..bdbb27eb 100644 --- a/pkg/audit/center/groups/usage.py +++ b/pkg/audit/center/groups/usage.py @@ -9,7 +9,7 @@ class V2UsageDataAPI(apigroup.APIGroup): def __init__(self, prefix: str, ap: app.Application): self.ap = ap - super().__init__(prefix+"/usage", ap) + super().__init__(prefix + '/usage', ap) async def do(self, *args, **kwargs): if not self.ap.instance_config.data['telemetry']['report']: @@ -28,25 +28,25 @@ class V2UsageDataAPI(apigroup.APIGroup): ): """提交请求记录""" return await self.do( - "POST", - "/query", + 'POST', + '/query', data={ - "basic": self.basic_info(), - "runtime": self.runtime_info(), - "session_info": { - "type": session_type, - "id": session_id, + 'basic': self.basic_info(), + 'runtime': self.runtime_info(), + 'session_info': { + 'type': session_type, + 'id': session_id, }, - "query_info": { - "ability_provider": query_ability_provider, - "usage": usage, - "model_name": model_name, - "response_seconds": response_seconds, - "retry_times": retry_times, - } - } + 'query_info': { + 'ability_provider': query_ability_provider, + 'usage': usage, + 'model_name': model_name, + 'response_seconds': response_seconds, + 'retry_times': retry_times, + }, + }, ) - + async def post_event_record( self, plugins: list[dict], @@ -54,18 +54,18 @@ class V2UsageDataAPI(apigroup.APIGroup): ): """提交事件触发记录""" return await self.do( - "POST", - "/event", + 'POST', + '/event', data={ - "basic": self.basic_info(), - "runtime": self.runtime_info(), - "plugins": plugins, - "event_info": { - "name": event_name, - } - } + 'basic': self.basic_info(), + 'runtime': self.runtime_info(), + 'plugins': plugins, + 'event_info': { + 'name': event_name, + }, + }, ) - + async def post_function_record( self, plugin: dict, @@ -74,15 +74,14 @@ class V2UsageDataAPI(apigroup.APIGroup): ): """提交内容函数使用记录""" return await self.do( - "POST", - "/function", + 'POST', + '/function', data={ - "basic": self.basic_info(), - "plugin": plugin, - "function_info": { - "name": function_name, - "description": function_description, - } - } + 'basic': self.basic_info(), + 'plugin': plugin, + 'function_info': { + 'name': function_name, + 'description': function_description, + }, + }, ) - diff --git a/pkg/audit/center/v2.py b/pkg/audit/center/v2.py index 234e6d22..e9df6f91 100644 --- a/pkg/audit/center/v2.py +++ b/pkg/audit/center/v2.py @@ -11,7 +11,7 @@ from ...core import app class V2CenterAPI: """中央服务器 v2 API 交互类""" - + main: main.V2MainDataAPI = None """主 API 组""" @@ -21,15 +21,20 @@ class V2CenterAPI: plugin: plugin.V2PluginDataAPI = None """插件 API 组""" - def __init__(self, ap: app.Application, backend_url: str, basic_info: dict = None, runtime_info: dict = None): + def __init__( + self, + ap: app.Application, + backend_url: str, + basic_info: dict = None, + runtime_info: dict = None, + ): """初始化""" - logging.debug("basic_info: %s, runtime_info: %s", basic_info, runtime_info) - + logging.debug('basic_info: %s, runtime_info: %s', basic_info, runtime_info) + apigroup.APIGroup._basic_info = basic_info apigroup.APIGroup._runtime_info = runtime_info self.main = main.V2MainDataAPI(backend_url, ap) self.usage = usage.V2UsageDataAPI(backend_url, ap) self.plugin = plugin.V2PluginDataAPI(backend_url, ap) - diff --git a/pkg/audit/identifier.py b/pkg/audit/identifier.py index 3e2ec57d..b10d093c 100644 --- a/pkg/audit/identifier.py +++ b/pkg/audit/identifier.py @@ -16,6 +16,7 @@ identifier = { HOST_ID_FILE = os.path.expanduser('~/.langbot/host_id.json') INSTANCE_ID_FILE = 'data/labels/instance_id.json' + def init(): global identifier @@ -23,14 +24,11 @@ def init(): os.mkdir(os.path.expanduser('~/.langbot')) if not os.path.exists(HOST_ID_FILE): - new_host_id = 'host_'+str(uuid.uuid4()) + new_host_id = 'host_' + str(uuid.uuid4()) new_host_create_ts = int(time.time()) with open(HOST_ID_FILE, 'w') as f: - json.dump({ - 'host_id': new_host_id, - 'host_create_ts': new_host_create_ts - }, f) + json.dump({'host_id': new_host_id, 'host_create_ts': new_host_create_ts}, f) identifier['host_id'] = new_host_id identifier['host_create_ts'] = new_host_create_ts @@ -51,20 +49,25 @@ def init(): instance_id = {} with open(INSTANCE_ID_FILE, 'r') as f: instance_id = json.load(f) - - if instance_id['host_id'] != identifier['host_id']: # 如果实例 id 不是当前主机的,删除 + + if ( + instance_id['host_id'] != identifier['host_id'] + ): # 如果实例 id 不是当前主机的,删除 os.remove(INSTANCE_ID_FILE) if not os.path.exists(INSTANCE_ID_FILE): - new_instance_id = 'instance_'+str(uuid.uuid4()) + new_instance_id = 'instance_' + str(uuid.uuid4()) new_instance_create_ts = int(time.time()) with open(INSTANCE_ID_FILE, 'w') as f: - json.dump({ - 'host_id': identifier['host_id'], - 'instance_id': new_instance_id, - 'instance_create_ts': new_instance_create_ts - }, f) + json.dump( + { + 'host_id': identifier['host_id'], + 'instance_id': new_instance_id, + 'instance_create_ts': new_instance_create_ts, + }, + f, + ) identifier['instance_id'] = new_instance_id identifier['instance_create_ts'] = new_instance_create_ts @@ -80,6 +83,7 @@ def init(): identifier['instance_id'] = loaded_instance_id identifier['instance_create_ts'] = loaded_instance_create_ts + def print_out(): global identifier print(identifier) diff --git a/pkg/command/cmdmgr.py b/pkg/command/cmdmgr.py index 3275b6fc..10d76067 100644 --- a/pkg/command/cmdmgr.py +++ b/pkg/command/cmdmgr.py @@ -3,17 +3,17 @@ from __future__ import annotations import typing from ..core import app, entities as core_entities -from ..provider import entities as llm_entities from . import entities, operator, errors -from ..config import manager as cfg_mgr +from ..utils import importutil # 引入所有算子以便注册 -from .operators import func, plugin, reset, list as list_cmd, last, next, delc, resend, prompt, cmd, help, version, update, ollama, model +from . import operators + +importutil.import_modules_in_pkg(operators) class CommandManager: - """命令管理器 - """ + """命令管理器""" ap: app.Application @@ -26,14 +26,13 @@ class CommandManager: self.ap = ap async def initialize(self): - # 设置各个类的路径 def set_path(cls: operator.CommandOperator, ancestors: list[str]): cls.path = '.'.join(ancestors + [cls.name]) for op in operator.preregistered_operators: if op.parent_class == cls: set_path(op, ancestors + [cls.name]) - + for cls in operator.preregistered_operators: if cls.parent_class is None: set_path(cls, []) @@ -41,14 +40,18 @@ class CommandManager: # 应用命令权限配置 for cls in operator.preregistered_operators: if cls.path in self.ap.instance_config.data['command']['privilege']: - cls.lowest_privilege = self.ap.instance_config.data['command']['privilege'][cls.path] + cls.lowest_privilege = self.ap.instance_config.data['command'][ + 'privilege' + ][cls.path] # 实例化所有类 self.cmd_list = [cls(self.ap) for cls in operator.preregistered_operators] # 设置所有类的子节点 for cmd in self.cmd_list: - cmd.children = [child for child in self.cmd_list if child.parent_class == cmd.__class__] + cmd.children = [ + child for child in self.cmd_list if child.parent_class == cmd.__class__ + ] # 初始化所有类 for cmd in self.cmd_list: @@ -58,27 +61,25 @@ class CommandManager: self, context: entities.ExecuteContext, operator_list: list[operator.CommandOperator], - operator: operator.CommandOperator = None + operator: operator.CommandOperator = None, ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - """执行命令 - """ + """执行命令""" found = False if len(context.crt_params) > 0: # 查找下一个参数是否对应此节点的某个子节点名 for oper in operator_list: - if (context.crt_params[0] == oper.name \ - or context.crt_params[0] in oper.alias) \ - and (oper.parent_class is None or oper.parent_class == operator.__class__): + if ( + context.crt_params[0] == oper.name + or context.crt_params[0] in oper.alias + ) and ( + oper.parent_class is None or oper.parent_class == operator.__class__ + ): found = True context.crt_command = context.crt_params[0] context.crt_params = context.crt_params[1:] - async for ret in self._execute( - context, - oper.children, - oper - ): + async for ret in self._execute(context, oper.children, oper): yield ret break @@ -96,19 +97,20 @@ class CommandManager: async for ret in operator.execute(context): yield ret - async def execute( self, command_text: str, query: core_entities.Query, - session: core_entities.Session + session: core_entities.Session, ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - """执行命令 - """ + """执行命令""" privilege = 1 - if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']: + if ( + f'{query.launcher_type.value}_{query.launcher_id}' + in self.ap.instance_config.data['admins'] + ): privilege = 2 ctx = entities.ExecuteContext( @@ -119,11 +121,8 @@ class CommandManager: crt_command='', params=command_text.split(' '), crt_params=command_text.split(' '), - privilege=privilege + privilege=privilege, ) - async for ret in self._execute( - ctx, - self.cmd_list - ): + async for ret in self._execute(ctx, self.cmd_list): yield ret diff --git a/pkg/command/entities.py b/pkg/command/entities.py index 538766bf..cccd588e 100644 --- a/pkg/command/entities.py +++ b/pkg/command/entities.py @@ -4,14 +4,13 @@ import typing import pydantic.v1 as pydantic -from ..core import app, entities as core_entities -from . import errors, operator +from ..core import entities as core_entities +from . import errors from ..platform.types import message as platform_message class CommandReturn(pydantic.BaseModel): - """命令返回值 - """ + """命令返回值""" text: typing.Optional[str] = None """文本 @@ -24,7 +23,7 @@ class CommandReturn(pydantic.BaseModel): """图片链接 """ - error: typing.Optional[errors.CommandError]= None + error: typing.Optional[errors.CommandError] = None """错误 """ @@ -33,8 +32,7 @@ class CommandReturn(pydantic.BaseModel): class ExecuteContext(pydantic.BaseModel): - """单次命令执行上下文 - """ + """单次命令执行上下文""" query: core_entities.Query """本次消息的请求对象""" diff --git a/pkg/command/errors.py b/pkg/command/errors.py index 5bc253f6..df05b3d1 100644 --- a/pkg/command/errors.py +++ b/pkg/command/errors.py @@ -1,33 +1,26 @@ - - class CommandError(Exception): - def __init__(self, message: str = None): self.message = message - + def __str__(self): return self.message class CommandNotFoundError(CommandError): - def __init__(self, message: str = None): - super().__init__("未知命令: "+message) + super().__init__('未知命令: ' + message) class CommandPrivilegeError(CommandError): - def __init__(self, message: str = None): - super().__init__("权限不足: "+message) + super().__init__('权限不足: ' + message) class ParamNotEnoughError(CommandError): - def __init__(self, message: str = None): - super().__init__("参数不足: "+message) + super().__init__('参数不足: ' + message) class CommandOperationError(CommandError): - def __init__(self, message: str = None): - super().__init__("操作失败: "+message) + super().__init__('操作失败: ' + message) diff --git a/pkg/command/operator.py b/pkg/command/operator.py index 5e3b1a8f..7072edf7 100644 --- a/pkg/command/operator.py +++ b/pkg/command/operator.py @@ -3,7 +3,7 @@ from __future__ import annotations import typing import abc -from ..core import app, entities as core_entities +from ..core import app from . import entities @@ -13,14 +13,14 @@ preregistered_operators: list[typing.Type[CommandOperator]] = [] def operator_class( name: str, - help: str = "", + help: str = '', usage: str = None, alias: list[str] = [], - privilege: int=1, # 1为普通用户,2为管理员 - parent_class: typing.Type[CommandOperator] = None + privilege: int = 1, # 1为普通用户,2为管理员 + parent_class: typing.Type[CommandOperator] = None, ) -> typing.Callable[[typing.Type[CommandOperator]], typing.Type[CommandOperator]]: """命令类装饰器 - + Args: name (str): 名称 help (str, optional): 帮助信息. Defaults to "". @@ -35,7 +35,7 @@ def operator_class( def decorator(cls: typing.Type[CommandOperator]) -> typing.Type[CommandOperator]: assert issubclass(cls, CommandOperator) - + cls.name = name cls.alias = alias cls.help = help @@ -96,14 +96,13 @@ class CommandOperator(metaclass=abc.ABCMeta): @abc.abstractmethod async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: """实现此方法以执行命令 支持多次yield以返回多个结果。 例如:一个安装插件的命令,可能会有下载、解压、安装等多个步骤,每个步骤都可以返回一个结果。 - + Args: context (entities.ExecuteContext): 命令执行上下文 diff --git a/pkg/command/operators/cmd.py b/pkg/command/operators/cmd.py index 17b5ed08..a13d5b35 100644 --- a/pkg/command/operators/cmd.py +++ b/pkg/command/operators/cmd.py @@ -2,49 +2,46 @@ from __future__ import annotations import typing -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors -@operator.operator_class( - name="cmd", - help='显示命令列表', - usage='!cmd\n!cmd <命令名称>' -) +@operator.operator_class(name='cmd', help='显示命令列表', usage='!cmd\n!cmd <命令名称>') class CmdOperator(operator.CommandOperator): - """命令列表 - """ + """命令列表""" async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - """执行 - """ + """执行""" if len(context.crt_params) == 0: - reply_str = "当前所有命令: \n\n" + reply_str = '当前所有命令: \n\n' for cmd in self.ap.cmd_mgr.cmd_list: if cmd.parent_class is None: - reply_str += f"{cmd.name}: {cmd.help}\n" - - reply_str += "\n使用 !cmd <命令名称> 查看命令的详细帮助" + reply_str += f'{cmd.name}: {cmd.help}\n' + + reply_str += '\n使用 !cmd <命令名称> 查看命令的详细帮助' yield entities.CommandReturn(text=reply_str.strip()) - + else: cmd_name = context.crt_params[0] cmd = None for _cmd in self.ap.cmd_mgr.cmd_list: - if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and (_cmd.parent_class is None): + if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and ( + _cmd.parent_class is None + ): cmd = _cmd break if cmd is None: - yield entities.CommandReturn(error=errors.CommandNotFoundError(cmd_name)) + yield entities.CommandReturn( + error=errors.CommandNotFoundError(cmd_name) + ) else: - reply_str = f"{cmd.name}: {cmd.help}\n\n" - reply_str += f"使用方法: \n{cmd.usage}" + reply_str = f'{cmd.name}: {cmd.help}\n\n' + reply_str += f'使用方法: \n{cmd.usage}' yield entities.CommandReturn(text=reply_str.strip()) diff --git a/pkg/command/operators/delc.py b/pkg/command/operators/delc.py index db865ff7..9ae507f5 100644 --- a/pkg/command/operators/delc.py +++ b/pkg/command/operators/delc.py @@ -1,62 +1,60 @@ from __future__ import annotations import typing -import datetime -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors @operator.operator_class( - name="del", - help="删除当前会话的历史记录", - usage='!del <序号>\n!del all' + name='del', help='删除当前会话的历史记录', usage='!del <序号>\n!del all' ) class DelOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if context.session.conversations: delete_index = 0 if len(context.crt_params) > 0: try: delete_index = int(context.crt_params[0]) - except: - yield entities.CommandReturn(error=errors.CommandOperationError('索引必须是整数')) + except Exception: + yield entities.CommandReturn( + error=errors.CommandOperationError('索引必须是整数') + ) return - - if delete_index < 0 or delete_index >= len(context.session.conversations): - yield entities.CommandReturn(error=errors.CommandOperationError('索引超出范围')) - return - - # 倒序 - to_delete_index = len(context.session.conversations)-1-delete_index - if context.session.conversations[to_delete_index] == context.session.using_conversation: + if delete_index < 0 or delete_index >= len(context.session.conversations): + yield entities.CommandReturn( + error=errors.CommandOperationError('索引超出范围') + ) + return + + # 倒序 + to_delete_index = len(context.session.conversations) - 1 - delete_index + + if ( + context.session.conversations[to_delete_index] + == context.session.using_conversation + ): context.session.using_conversation = None del context.session.conversations[to_delete_index] - yield entities.CommandReturn(text=f"已删除对话: {delete_index}") + yield entities.CommandReturn(text=f'已删除对话: {delete_index}') else: - yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话')) + yield entities.CommandReturn( + error=errors.CommandOperationError('当前没有对话') + ) @operator.operator_class( - name="all", - help="删除此会话的所有历史记录", - parent_class=DelOperator + name='all', help='删除此会话的所有历史记录', parent_class=DelOperator ) class DelAllOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - context.session.conversations = [] context.session.using_conversation = None - yield entities.CommandReturn(text="已删除所有对话") \ No newline at end of file + yield entities.CommandReturn(text='已删除所有对话') diff --git a/pkg/command/operators/func.py b/pkg/command/operators/func.py index ae2ba4c1..9cb3fd32 100644 --- a/pkg/command/operators/func.py +++ b/pkg/command/operators/func.py @@ -1,16 +1,15 @@ from __future__ import annotations from typing import AsyncGenerator -from .. import operator, entities, cmdmgr -from ...plugin import context as plugin_context +from .. import operator, entities -@operator.operator_class(name="func", help="查看所有已注册的内容函数", usage='!func') +@operator.operator_class(name='func', help='查看所有已注册的内容函数', usage='!func') class FuncOperator(operator.CommandOperator): async def execute( self, context: entities.ExecuteContext ) -> AsyncGenerator[entities.CommandReturn, None]: - reply_str = "当前已启用的内容函数: \n\n" + reply_str = '当前已启用的内容函数: \n\n' index = 1 @@ -19,7 +18,7 @@ class FuncOperator(operator.CommandOperator): ) for func in all_functions: - reply_str += "{}. {}:\n{}\n\n".format( + reply_str += '{}. {}:\n{}\n\n'.format( index, func.name, func.description, diff --git a/pkg/command/operators/help.py b/pkg/command/operators/help.py index d8b42137..c718d4b9 100644 --- a/pkg/command/operators/help.py +++ b/pkg/command/operators/help.py @@ -2,19 +2,13 @@ from __future__ import annotations import typing -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities -@operator.operator_class( - name='help', - help='显示帮助', - usage='!help\n!help <命令名称>' -) +@operator.operator_class(name='help', help='显示帮助', usage='!help\n!help <命令名称>') class HelpOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: help = 'LangBot - 大语言模型原生即时通信机器人平台\n链接:https://langbot.app' diff --git a/pkg/command/operators/last.py b/pkg/command/operators/last.py index e7a14c83..7e2f2453 100644 --- a/pkg/command/operators/last.py +++ b/pkg/command/operators/last.py @@ -1,36 +1,43 @@ from __future__ import annotations import typing -import datetime -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors -@operator.operator_class( - name="last", - help="切换到前一个对话", - usage='!last' -) +@operator.operator_class(name='last', help='切换到前一个对话', usage='!last') class LastOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if context.session.conversations: # 找到当前会话的上一个会话 - for index in range(len(context.session.conversations)-1, -1, -1): - if context.session.conversations[index] == context.session.using_conversation: + for index in range(len(context.session.conversations) - 1, -1, -1): + if ( + context.session.conversations[index] + == context.session.using_conversation + ): if index == 0: - yield entities.CommandReturn(error=errors.CommandOperationError('已经是第一个对话了')) + yield entities.CommandReturn( + error=errors.CommandOperationError('已经是第一个对话了') + ) return else: - context.session.using_conversation = context.session.conversations[index-1] - time_str = context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S") + context.session.using_conversation = ( + context.session.conversations[index - 1] + ) + time_str = ( + context.session.using_conversation.create_time.strftime( + '%Y-%m-%d %H:%M:%S' + ) + ) - yield entities.CommandReturn(text=f"已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}") + yield entities.CommandReturn( + text=f'已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}' + ) return else: - yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话')) \ No newline at end of file + yield entities.CommandReturn( + error=errors.CommandOperationError('当前没有对话') + ) diff --git a/pkg/command/operators/list.py b/pkg/command/operators/list.py index ff90d4dd..1aa63c94 100644 --- a/pkg/command/operators/list.py +++ b/pkg/command/operators/list.py @@ -1,30 +1,26 @@ from __future__ import annotations import typing -import datetime -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors @operator.operator_class( - name="list", - help="列出此会话中的所有历史对话", - usage='!list\n!list <页码>' + name='list', help='列出此会话中的所有历史对话', usage='!list\n!list <页码>' ) class ListOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - page = 0 if len(context.crt_params) > 0: try: - page = int(context.crt_params[0]-1) - except: - yield entities.CommandReturn(error=errors.CommandOperationError('页码应为整数')) + page = int(context.crt_params[0] - 1) + except Exception: + yield entities.CommandReturn( + error=errors.CommandOperationError('页码应为整数') + ) return record_per_page = 10 @@ -36,21 +32,21 @@ class ListOperator(operator.CommandOperator): using_conv_index = 0 for conv in context.session.conversations[::-1]: - time_str = conv.create_time.strftime("%Y-%m-%d %H:%M:%S") + time_str = conv.create_time.strftime('%Y-%m-%d %H:%M:%S') if conv == context.session.using_conversation: using_conv_index = index if index >= page * record_per_page and index < (page + 1) * record_per_page: - content += f"{index} {time_str}: {conv.messages[0].readable_str() if len(conv.messages) > 0 else '无内容'}\n" + content += f'{index} {time_str}: {conv.messages[0].readable_str() if len(conv.messages) > 0 else "无内容"}\n' index += 1 if content == '': content = '无' else: if context.session.using_conversation is None: - content += "\n当前处于新会话" + content += '\n当前处于新会话' else: - content += f"\n当前会话: {using_conv_index} {context.session.using_conversation.create_time.strftime('%Y-%m-%d %H:%M:%S')}: {context.session.using_conversation.messages[0].readable_str() if len(context.session.using_conversation.messages) > 0 else '无内容'}" - - yield entities.CommandReturn(text=f"第 {page + 1} 页 (时间倒序):\n{content}") + content += f'\n当前会话: {using_conv_index} {context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S")}: {context.session.using_conversation.messages[0].readable_str() if len(context.session.using_conversation.messages) > 0 else "无内容"}' + + yield entities.CommandReturn(text=f'第 {page + 1} 页 (时间倒序):\n{content}') diff --git a/pkg/command/operators/model.py b/pkg/command/operators/model.py index f46c9590..cc3ef5b9 100644 --- a/pkg/command/operators/model.py +++ b/pkg/command/operators/model.py @@ -2,42 +2,44 @@ from __future__ import annotations import typing -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors + @operator.operator_class( - name="model", + name='model', help='显示和切换模型列表', usage='!model\n!model show <模型名>\n!model set <模型名>', - privilege=2 + privilege=2, ) class ModelOperator(operator.CommandOperator): """Model命令""" - - async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]: + + async def execute( + self, context: entities.ExecuteContext + ) -> typing.AsyncGenerator[entities.CommandReturn, None]: content = '模型列表:\n' model_list = self.ap.model_mgr.model_list for model in model_list: - content += f"\n名称: {model.name}\n" - content += f"请求器: {model.requester.name}\n" + content += f'\n名称: {model.name}\n' + content += f'请求器: {model.requester.name}\n' - content += f"\n当前对话使用模型: {context.query.use_model.name}\n" - content += f"新对话默认使用模型: {self.ap.provider_cfg.data.get('model')}\n" + content += f'\n当前对话使用模型: {context.query.use_model.name}\n' + content += f'新对话默认使用模型: {self.ap.provider_cfg.data.get("model")}\n' yield entities.CommandReturn(text=content.strip()) @operator.operator_class( - name="show", - help='显示模型详情', - privilege=2, - parent_class=ModelOperator + name='show', help='显示模型详情', privilege=2, parent_class=ModelOperator ) class ModelShowOperator(operator.CommandOperator): """Model Show命令""" - - async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]: + + async def execute( + self, context: entities.ExecuteContext + ) -> typing.AsyncGenerator[entities.CommandReturn, None]: model_name = context.crt_params[0] model = None @@ -47,29 +49,31 @@ class ModelShowOperator(operator.CommandOperator): break if model is None: - yield entities.CommandReturn(error=errors.CommandError(f"未找到模型 {model_name}")) + yield entities.CommandReturn( + error=errors.CommandError(f'未找到模型 {model_name}') + ) else: - content = f"模型详情\n" - content += f"名称: {model.name}\n" + content = '模型详情\n' + content += f'名称: {model.name}\n' if model.model_name is not None: - content += f"请求模型名称: {model.model_name}\n" - content += f"请求器: {model.requester.name}\n" - content += f"密钥组: {model.token_mgr.name}\n" - content += f"支持视觉: {model.vision_supported}\n" - content += f"支持工具: {model.tool_call_supported}\n" + content += f'请求模型名称: {model.model_name}\n' + content += f'请求器: {model.requester.name}\n' + content += f'密钥组: {model.token_mgr.name}\n' + content += f'支持视觉: {model.vision_supported}\n' + content += f'支持工具: {model.tool_call_supported}\n' yield entities.CommandReturn(text=content.strip()) + @operator.operator_class( - name="set", - help='设置默认使用模型', - privilege=2, - parent_class=ModelOperator + name='set', help='设置默认使用模型', privilege=2, parent_class=ModelOperator ) class ModelSetOperator(operator.CommandOperator): """Model Set命令""" - - async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]: + + async def execute( + self, context: entities.ExecuteContext + ) -> typing.AsyncGenerator[entities.CommandReturn, None]: model_name = context.crt_params[0] model = None @@ -79,8 +83,12 @@ class ModelSetOperator(operator.CommandOperator): break if model is None: - yield entities.CommandReturn(error=errors.CommandError(f"未找到模型 {model_name}")) + yield entities.CommandReturn( + error=errors.CommandError(f'未找到模型 {model_name}') + ) else: self.ap.provider_cfg.data['model'] = model_name await self.ap.provider_cfg.dump_config() - yield entities.CommandReturn(text=f"已设置当前使用模型为 {model_name},重置会话以生效") + yield entities.CommandReturn( + text=f'已设置当前使用模型为 {model_name},重置会话以生效' + ) diff --git a/pkg/command/operators/next.py b/pkg/command/operators/next.py index 8f4b5a5a..ef5ae103 100644 --- a/pkg/command/operators/next.py +++ b/pkg/command/operators/next.py @@ -1,35 +1,42 @@ from __future__ import annotations import typing -import datetime -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors -@operator.operator_class( - name="next", - help="切换到后一个对话", - usage='!next' -) +@operator.operator_class(name='next', help='切换到后一个对话', usage='!next') class NextOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if context.session.conversations: # 找到当前会话的下一个会话 for index in range(len(context.session.conversations)): - if context.session.conversations[index] == context.session.using_conversation: - if index == len(context.session.conversations)-1: - yield entities.CommandReturn(error=errors.CommandOperationError('已经是最后一个对话了')) + if ( + context.session.conversations[index] + == context.session.using_conversation + ): + if index == len(context.session.conversations) - 1: + yield entities.CommandReturn( + error=errors.CommandOperationError('已经是最后一个对话了') + ) return else: - context.session.using_conversation = context.session.conversations[index+1] - time_str = context.session.using_conversation.create_time.strftime("%Y-%m-%d %H:%M:%S") + context.session.using_conversation = ( + context.session.conversations[index + 1] + ) + time_str = ( + context.session.using_conversation.create_time.strftime( + '%Y-%m-%d %H:%M:%S' + ) + ) - yield entities.CommandReturn(text=f"已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}") + yield entities.CommandReturn( + text=f'已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}' + ) return else: - yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话')) \ No newline at end of file + yield entities.CommandReturn( + error=errors.CommandOperationError('当前没有对话') + ) diff --git a/pkg/command/operators/ollama.py b/pkg/command/operators/ollama.py index f5ed382d..7e65d440 100644 --- a/pkg/command/operators/ollama.py +++ b/pkg/command/operators/ollama.py @@ -2,31 +2,32 @@ from __future__ import annotations import json import typing -import traceback import ollama from .. import operator, entities, errors @operator.operator_class( - name="ollama", - help="ollama平台操作", - usage="!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>" + name='ollama', + help='ollama平台操作', + usage='!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>', ) class OllamaOperator(operator.CommandOperator): async def execute( - self, context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: try: content: str = '模型列表:\n' model_list: list = ollama.list().get('models', []) for model in model_list: - content += f"名称: {model['name']}\n" - content += f"修改时间: {model['modified_at']}\n" - content += f"大小: {bytes_to_mb(model['size'])}MB\n\n" - yield entities.CommandReturn(text=f"{content.strip()}") - except ollama.ResponseError as e: - yield entities.CommandReturn(error=errors.CommandError(f"无法获取模型列表,请确认 Ollama 服务正常")) + content += f'名称: {model["name"]}\n' + content += f'修改时间: {model["modified_at"]}\n' + content += f'大小: {bytes_to_mb(model["size"])}MB\n\n' + yield entities.CommandReturn(text=f'{content.strip()}') + except ollama.ResponseError: + yield entities.CommandReturn( + error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常') + ) def bytes_to_mb(num_bytes): @@ -35,14 +36,11 @@ def bytes_to_mb(num_bytes): @operator.operator_class( - name="show", - help="ollama模型详情", - privilege=2, - parent_class=OllamaOperator + name='show', help='ollama模型详情', privilege=2, parent_class=OllamaOperator ) class OllamaShowOperator(operator.CommandOperator): async def execute( - self, context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: content: str = '模型详情:\n' try: @@ -53,31 +51,36 @@ class OllamaShowOperator(operator.CommandOperator): for key in ['license', 'modelfile']: show[key] = ignore_show - for key in ['tokenizer.chat_template.rag', 'tokenizer.chat_template.tool_use']: + for key in [ + 'tokenizer.chat_template.rag', + 'tokenizer.chat_template.tool_use', + ]: model_info[key] = ignore_show content += json.dumps(show, indent=4) yield entities.CommandReturn(text=content.strip()) - except ollama.ResponseError as e: - yield entities.CommandReturn(error=errors.CommandError(f"无法获取模型详情,请确认 Ollama 服务正常")) + except ollama.ResponseError: + yield entities.CommandReturn( + error=errors.CommandError('无法获取模型详情,请确认 Ollama 服务正常') + ) + @operator.operator_class( - name="pull", - help="ollama模型拉取", - privilege=2, - parent_class=OllamaOperator + name='pull', help='ollama模型拉取', privilege=2, parent_class=OllamaOperator ) class OllamaPullOperator(operator.CommandOperator): async def execute( - self, context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: try: model_list: list = ollama.list().get('models', []) if context.crt_params[0] in [model['name'] for model in model_list]: - yield entities.CommandReturn(text="模型已存在") + yield entities.CommandReturn(text='模型已存在') return - except ollama.ResponseError as e: - yield entities.CommandReturn(error=errors.CommandError(f"无法获取模型列表,请确认 Ollama 服务正常")) + except ollama.ResponseError: + yield entities.CommandReturn( + error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常') + ) return on_progress: bool = False @@ -99,23 +102,21 @@ class OllamaPullOperator(operator.CommandOperator): if percentage_completed > progress_count: progress_count += 10 yield entities.CommandReturn( - text=f"下载进度: {completed}/{total} ({percentage_completed:.2f}%)") + text=f'下载进度: {completed}/{total} ({percentage_completed:.2f}%)' + ) except ollama.ResponseError as e: - yield entities.CommandReturn(text=f"拉取失败: {e.error}") + yield entities.CommandReturn(text=f'拉取失败: {e.error}') @operator.operator_class( - name="del", - help="ollama模型删除", - privilege=2, - parent_class=OllamaOperator + name='del', help='ollama模型删除', privilege=2, parent_class=OllamaOperator ) class OllamaDelOperator(operator.CommandOperator): async def execute( - self, context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: try: ret: str = ollama.delete(model=context.crt_params[0])['status'] except ollama.ResponseError as e: - ret = f"{e.error}" + ret = f'{e.error}' yield entities.CommandReturn(text=ret) diff --git a/pkg/command/operators/plugin.py b/pkg/command/operators/plugin.py index c36fbbc3..1bf4c7af 100644 --- a/pkg/command/operators/plugin.py +++ b/pkg/command/operators/plugin.py @@ -2,31 +2,30 @@ from __future__ import annotations import typing import traceback -from .. import operator, entities, cmdmgr, errors -from ...core import app +from .. import operator, entities, errors @operator.operator_class( - name="plugin", - help="插件操作", - usage="!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>" + name='plugin', + help='插件操作', + usage='!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>', ) class PluginOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - plugin_list = self.ap.plugin_mgr.plugins() - reply_str = "所有插件({}):\n".format(len(plugin_list)) + reply_str = '所有插件({}):\n'.format(len(plugin_list)) idx = 0 for plugin in plugin_list: - reply_str += "\n#{} {} {}\n{}\nv{}\n作者: {}\n"\ - .format((idx+1), plugin.plugin_name, - "[已禁用]" if not plugin.enabled else "", - plugin.plugin_description, - plugin.plugin_version, plugin.plugin_author) + reply_str += '\n#{} {} {}\n{}\nv{}\n作者: {}\n'.format( + (idx + 1), + plugin.plugin_name, + '[已禁用]' if not plugin.enabled else '', + plugin.plugin_description, + plugin.plugin_version, + plugin.plugin_author, + ) idx += 1 @@ -34,48 +33,42 @@ class PluginOperator(operator.CommandOperator): @operator.operator_class( - name="get", - help="安装插件", - privilege=2, - parent_class=PluginOperator + name='get', help='安装插件', privilege=2, parent_class=PluginOperator ) class PluginGetOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if len(context.crt_params) == 0: - yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件仓库地址')) + yield entities.CommandReturn( + error=errors.ParamNotEnoughError('请提供插件仓库地址') + ) else: repo = context.crt_params[0] - yield entities.CommandReturn(text="正在安装插件...") + yield entities.CommandReturn(text='正在安装插件...') try: await self.ap.plugin_mgr.install_plugin(repo) - yield entities.CommandReturn(text="插件安装成功,请重启程序以加载插件") + yield entities.CommandReturn(text='插件安装成功,请重启程序以加载插件') except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件安装失败: "+str(e))) + yield entities.CommandReturn( + error=errors.CommandError('插件安装失败: ' + str(e)) + ) @operator.operator_class( - name="update", - help="更新插件", - privilege=2, - parent_class=PluginOperator + name='update', help='更新插件', privilege=2, parent_class=PluginOperator ) class PluginUpdateOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if len(context.crt_params) == 0: - yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称')) + yield entities.CommandReturn( + error=errors.ParamNotEnoughError('请提供插件名称') + ) else: plugin_name = context.crt_params[0] @@ -83,36 +76,34 @@ class PluginUpdateOperator(operator.CommandOperator): plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name) if plugin_container is not None: - yield entities.CommandReturn(text="正在更新插件...") + yield entities.CommandReturn(text='正在更新插件...') await self.ap.plugin_mgr.update_plugin(plugin_name) - yield entities.CommandReturn(text="插件更新成功,请重启程序以加载插件") + yield entities.CommandReturn( + text='插件更新成功,请重启程序以加载插件' + ) else: - yield entities.CommandReturn(error=errors.CommandError("插件更新失败: 未找到插件")) + yield entities.CommandReturn( + error=errors.CommandError('插件更新失败: 未找到插件') + ) except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e))) + yield entities.CommandReturn( + error=errors.CommandError('插件更新失败: ' + str(e)) + ) + @operator.operator_class( - name="all", - help="更新所有插件", - privilege=2, - parent_class=PluginUpdateOperator + name='all', help='更新所有插件', privilege=2, parent_class=PluginUpdateOperator ) class PluginUpdateAllOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - try: - plugins = [ - p.plugin_name - for p in self.ap.plugin_mgr.plugins() - ] + plugins = [p.plugin_name for p in self.ap.plugin_mgr.plugins()] if plugins: - yield entities.CommandReturn(text="正在更新插件...") + yield entities.CommandReturn(text='正在更新插件...') updated = [] try: for plugin_name in plugins: @@ -120,30 +111,32 @@ class PluginUpdateAllOperator(operator.CommandOperator): updated.append(plugin_name) except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e))) - yield entities.CommandReturn(text="已更新插件: {}".format(", ".join(updated))) + yield entities.CommandReturn( + error=errors.CommandError('插件更新失败: ' + str(e)) + ) + yield entities.CommandReturn( + text='已更新插件: {}'.format(', '.join(updated)) + ) else: - yield entities.CommandReturn(text="没有可更新的插件") + yield entities.CommandReturn(text='没有可更新的插件') except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件更新失败: "+str(e))) + yield entities.CommandReturn( + error=errors.CommandError('插件更新失败: ' + str(e)) + ) @operator.operator_class( - name="del", - help="删除插件", - privilege=2, - parent_class=PluginOperator + name='del', help='删除插件', privilege=2, parent_class=PluginOperator ) class PluginDelOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if len(context.crt_params) == 0: - yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称')) + yield entities.CommandReturn( + error=errors.ParamNotEnoughError('请提供插件名称') + ) else: plugin_name = context.crt_params[0] @@ -151,67 +144,81 @@ class PluginDelOperator(operator.CommandOperator): plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name) if plugin_container is not None: - yield entities.CommandReturn(text="正在删除插件...") + yield entities.CommandReturn(text='正在删除插件...') await self.ap.plugin_mgr.uninstall_plugin(plugin_name) - yield entities.CommandReturn(text="插件删除成功,请重启程序以加载插件") + yield entities.CommandReturn( + text='插件删除成功,请重启程序以加载插件' + ) else: - yield entities.CommandReturn(error=errors.CommandError("插件删除失败: 未找到插件")) + yield entities.CommandReturn( + error=errors.CommandError('插件删除失败: 未找到插件') + ) except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件删除失败: "+str(e))) + yield entities.CommandReturn( + error=errors.CommandError('插件删除失败: ' + str(e)) + ) @operator.operator_class( - name="on", - help="启用插件", - privilege=2, - parent_class=PluginOperator + name='on', help='启用插件', privilege=2, parent_class=PluginOperator ) class PluginEnableOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if len(context.crt_params) == 0: - yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称')) + yield entities.CommandReturn( + error=errors.ParamNotEnoughError('请提供插件名称') + ) else: plugin_name = context.crt_params[0] try: if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, True): - yield entities.CommandReturn(text="已启用插件: {}".format(plugin_name)) + yield entities.CommandReturn( + text='已启用插件: {}'.format(plugin_name) + ) else: - yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name))) + yield entities.CommandReturn( + error=errors.CommandError( + '插件状态修改失败: 未找到插件 {}'.format(plugin_name) + ) + ) except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: "+str(e))) + yield entities.CommandReturn( + error=errors.CommandError('插件状态修改失败: ' + str(e)) + ) @operator.operator_class( - name="off", - help="禁用插件", - privilege=2, - parent_class=PluginOperator + name='off', help='禁用插件', privilege=2, parent_class=PluginOperator ) class PluginDisableOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - if len(context.crt_params) == 0: - yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称')) + yield entities.CommandReturn( + error=errors.ParamNotEnoughError('请提供插件名称') + ) else: plugin_name = context.crt_params[0] try: if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, False): - yield entities.CommandReturn(text="已禁用插件: {}".format(plugin_name)) + yield entities.CommandReturn( + text='已禁用插件: {}'.format(plugin_name) + ) else: - yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: 未找到插件 {}".format(plugin_name))) + yield entities.CommandReturn( + error=errors.CommandError( + '插件状态修改失败: 未找到插件 {}'.format(plugin_name) + ) + ) except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("插件状态修改失败: "+str(e))) + yield entities.CommandReturn( + error=errors.CommandError('插件状态修改失败: ' + str(e)) + ) diff --git a/pkg/command/operators/prompt.py b/pkg/command/operators/prompt.py index 29d688a6..41f42de4 100644 --- a/pkg/command/operators/prompt.py +++ b/pkg/command/operators/prompt.py @@ -2,28 +2,23 @@ from __future__ import annotations import typing -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors -@operator.operator_class( - name="prompt", - help="查看当前对话的前文", - usage='!prompt' -) +@operator.operator_class(name='prompt', help='查看当前对话的前文', usage='!prompt') class PromptOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - """执行 - """ + """执行""" if context.session.using_conversation is None: - yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话')) + yield entities.CommandReturn( + error=errors.CommandOperationError('当前没有对话') + ) else: reply_str = '当前对话所有内容:\n\n' for msg in context.session.using_conversation.messages: - reply_str += f"{msg.role}: {msg.content}\n" + reply_str += f'{msg.role}: {msg.content}\n' - yield entities.CommandReturn(text=reply_str) \ No newline at end of file + yield entities.CommandReturn(text=reply_str) diff --git a/pkg/command/operators/resend.py b/pkg/command/operators/resend.py index 6d930413..44e5a35c 100644 --- a/pkg/command/operators/resend.py +++ b/pkg/command/operators/resend.py @@ -2,26 +2,22 @@ from __future__ import annotations import typing -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors @operator.operator_class( - name="resend", - help="重发当前会话的最后一条消息", - usage='!resend' + name='resend', help='重发当前会话的最后一条消息', usage='!resend' ) class ResendOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: # 回滚到最后一条用户message前 if context.session.using_conversation is None: - yield entities.CommandReturn(error=errors.CommandError("当前没有对话")) + yield entities.CommandReturn(error=errors.CommandError('当前没有对话')) else: conv_msg = context.session.using_conversation.messages - + # 倒序一直删到最后一条用户message while len(conv_msg) > 0 and conv_msg[-1].role != 'user': conv_msg.pop() @@ -31,4 +27,4 @@ class ResendOperator(operator.CommandOperator): conv_msg.pop() # 不重发了,提示用户已删除就行了 - yield entities.CommandReturn(text="已删除最后一次请求记录") + yield entities.CommandReturn(text='已删除最后一次请求记录') diff --git a/pkg/command/operators/reset.py b/pkg/command/operators/reset.py index 5d1402ac..7ef54e08 100644 --- a/pkg/command/operators/reset.py +++ b/pkg/command/operators/reset.py @@ -2,22 +2,15 @@ from __future__ import annotations import typing -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities -@operator.operator_class( - name="reset", - help="重置当前会话", - usage='!reset' -) +@operator.operator_class(name='reset', help='重置当前会话', usage='!reset') class ResetOperator(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - """执行 - """ + """执行""" context.session.using_conversation = None - yield entities.CommandReturn(text="已重置当前会话") + yield entities.CommandReturn(text='已重置当前会话') diff --git a/pkg/command/operators/update.py b/pkg/command/operators/update.py index 524a26dd..775ee26a 100644 --- a/pkg/command/operators/update.py +++ b/pkg/command/operators/update.py @@ -3,28 +3,22 @@ from __future__ import annotations import typing import traceback -from .. import operator, entities, cmdmgr, errors +from .. import operator, entities, errors -@operator.operator_class( - name="update", - help="更新程序", - usage='!update', - privilege=2 -) +@operator.operator_class(name='update', help='更新程序', usage='!update', privilege=2) class UpdateCommand(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - try: - yield entities.CommandReturn(text="正在进行更新...") + yield entities.CommandReturn(text='正在进行更新...') if await self.ap.ver_mgr.update_all(): - yield entities.CommandReturn(text="更新完成,请重启程序以应用更新") + yield entities.CommandReturn(text='更新完成,请重启程序以应用更新') else: - yield entities.CommandReturn(text="当前已是最新版本") + yield entities.CommandReturn(text='当前已是最新版本') except Exception as e: traceback.print_exc() - yield entities.CommandReturn(error=errors.CommandError("更新失败: "+str(e))) \ No newline at end of file + yield entities.CommandReturn( + error=errors.CommandError('更新失败: ' + str(e)) + ) diff --git a/pkg/command/operators/version.py b/pkg/command/operators/version.py index a5d7a81b..267b1113 100644 --- a/pkg/command/operators/version.py +++ b/pkg/command/operators/version.py @@ -2,26 +2,20 @@ from __future__ import annotations import typing -from .. import operator, cmdmgr, entities, errors +from .. import operator, entities -@operator.operator_class( - name="version", - help="显示版本信息", - usage='!version' -) +@operator.operator_class(name='version', help='显示版本信息', usage='!version') class VersionCommand(operator.CommandOperator): - async def execute( - self, - context: entities.ExecuteContext + self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: - reply_str = f"当前版本: \n{self.ap.ver_mgr.get_current_version()}" + reply_str = f'当前版本: \n{self.ap.ver_mgr.get_current_version()}' try: if await self.ap.ver_mgr.is_new_version_available(): - reply_str += "\n\n有新版本可用。" - except: + reply_str += '\n\n有新版本可用。' + except Exception: pass - yield entities.CommandReturn(text=reply_str.strip()) \ No newline at end of file + yield entities.CommandReturn(text=reply_str.strip()) diff --git a/pkg/config/impls/json.py b/pkg/config/impls/json.py index e414e451..07fc533c 100644 --- a/pkg/config/impls/json.py +++ b/pkg/config/impls/json.py @@ -9,7 +9,10 @@ class JSONConfigFile(file_model.ConfigFile): """JSON配置文件""" def __init__( - self, config_file_name: str, template_file_name: str = None, template_data: dict = None + self, + config_file_name: str, + template_file_name: str = None, + template_data: dict = None, ) -> None: self.config_file_name = config_file_name self.template_file_name = template_file_name @@ -22,28 +25,26 @@ class JSONConfigFile(file_model.ConfigFile): if self.template_file_name is not None: shutil.copyfile(self.template_file_name, self.config_file_name) elif self.template_data is not None: - with open(self.config_file_name, "w", encoding="utf-8") as f: + with open(self.config_file_name, 'w', encoding='utf-8') as f: json.dump(self.template_data, f, indent=4, ensure_ascii=False) else: - raise ValueError("template_file_name or template_data must be provided") - - async def load(self, completion: bool=True) -> dict: + raise ValueError('template_file_name or template_data must be provided') + async def load(self, completion: bool = True) -> dict: if not self.exists(): await self.create() if self.template_file_name is not None: - with open(self.template_file_name, "r", encoding="utf-8") as f: + with open(self.template_file_name, 'r', encoding='utf-8') as f: self.template_data = json.load(f) - with open(self.config_file_name, "r", encoding="utf-8") as f: + with open(self.config_file_name, 'r', encoding='utf-8') as f: try: cfg = json.load(f) except json.JSONDecodeError as e: - raise Exception(f"配置文件 {self.config_file_name} 语法错误: {e}") + raise Exception(f'配置文件 {self.config_file_name} 语法错误: {e}') if completion: - for key in self.template_data: if key not in cfg: cfg[key] = self.template_data[key] @@ -51,9 +52,9 @@ class JSONConfigFile(file_model.ConfigFile): return cfg async def save(self, cfg: dict): - with open(self.config_file_name, "w", encoding="utf-8") as f: + with open(self.config_file_name, 'w', encoding='utf-8') as f: json.dump(cfg, f, indent=4, ensure_ascii=False) def save_sync(self, cfg: dict): - with open(self.config_file_name, "w", encoding="utf-8") as f: + with open(self.config_file_name, 'w', encoding='utf-8') as f: json.dump(cfg, f, indent=4, ensure_ascii=False) diff --git a/pkg/config/impls/pymodule.py b/pkg/config/impls/pymodule.py index 67e5867d..2311992e 100644 --- a/pkg/config/impls/pymodule.py +++ b/pkg/config/impls/pymodule.py @@ -25,10 +25,10 @@ class PythonModuleConfigFile(file_model.ConfigFile): async def create(self): shutil.copyfile(self.template_file_name, self.config_file_name) - async def load(self, completion: bool=True) -> dict: + async def load(self, completion: bool = True) -> dict: module_name = os.path.splitext(os.path.basename(self.config_file_name))[0] module = importlib.import_module(module_name) - + cfg = {} allowed_types = (int, float, str, bool, list, dict) @@ -63,4 +63,4 @@ class PythonModuleConfigFile(file_model.ConfigFile): logging.warning('Python模块配置文件不支持保存') def save_sync(self, data: dict): - logging.warning('Python模块配置文件不支持保存') \ No newline at end of file + logging.warning('Python模块配置文件不支持保存') diff --git a/pkg/config/impls/yaml.py b/pkg/config/impls/yaml.py index f4518003..55045186 100644 --- a/pkg/config/impls/yaml.py +++ b/pkg/config/impls/yaml.py @@ -9,7 +9,10 @@ class YAMLConfigFile(file_model.ConfigFile): """YAML配置文件""" def __init__( - self, config_file_name: str, template_file_name: str = None, template_data: dict = None + self, + config_file_name: str, + template_file_name: str = None, + template_data: dict = None, ) -> None: self.config_file_name = config_file_name self.template_file_name = template_file_name @@ -22,28 +25,26 @@ class YAMLConfigFile(file_model.ConfigFile): if self.template_file_name is not None: shutil.copyfile(self.template_file_name, self.config_file_name) elif self.template_data is not None: - with open(self.config_file_name, "w", encoding="utf-8") as f: + with open(self.config_file_name, 'w', encoding='utf-8') as f: yaml.dump(self.template_data, f, indent=4, allow_unicode=True) else: - raise ValueError("template_file_name or template_data must be provided") - - async def load(self, completion: bool=True) -> dict: + raise ValueError('template_file_name or template_data must be provided') + async def load(self, completion: bool = True) -> dict: if not self.exists(): await self.create() if self.template_file_name is not None: - with open(self.template_file_name, "r", encoding="utf-8") as f: + with open(self.template_file_name, 'r', encoding='utf-8') as f: self.template_data = yaml.load(f, Loader=yaml.FullLoader) - with open(self.config_file_name, "r", encoding="utf-8") as f: + with open(self.config_file_name, 'r', encoding='utf-8') as f: try: cfg = yaml.load(f, Loader=yaml.FullLoader) except yaml.YAMLError as e: - raise Exception(f"配置文件 {self.config_file_name} 语法错误: {e}") + raise Exception(f'配置文件 {self.config_file_name} 语法错误: {e}') if completion: - for key in self.template_data: if key not in cfg: cfg[key] = self.template_data[key] @@ -51,9 +52,9 @@ class YAMLConfigFile(file_model.ConfigFile): return cfg async def save(self, cfg: dict): - with open(self.config_file_name, "w", encoding="utf-8") as f: + with open(self.config_file_name, 'w', encoding='utf-8') as f: yaml.dump(cfg, f, indent=4, allow_unicode=True) def save_sync(self, cfg: dict): - with open(self.config_file_name, "w", encoding="utf-8") as f: - yaml.dump(cfg, f, indent=4, allow_unicode=True) \ No newline at end of file + with open(self.config_file_name, 'w', encoding='utf-8') as f: + yaml.dump(cfg, f, indent=4, allow_unicode=True) diff --git a/pkg/config/manager.py b/pkg/config/manager.py index 4421003c..2385c6b5 100644 --- a/pkg/config/manager.py +++ b/pkg/config/manager.py @@ -6,7 +6,7 @@ from .impls import pymodule, json as json_file, yaml as yaml_file class ConfigManager: """配置文件管理器""" - + name: str = None """配置管理器名""" @@ -31,7 +31,7 @@ class ConfigManager: self.file = cfg_file self.data = {} - async def load_config(self, completion: bool=True): + async def load_config(self, completion: bool = True): self.data = await self.file.load(completion=completion) async def dump_config(self): @@ -41,9 +41,11 @@ class ConfigManager: self.file.save_sync(self.data) -async def load_python_module_config(config_name: str, template_name: str, completion: bool=True) -> ConfigManager: +async def load_python_module_config( + config_name: str, template_name: str, completion: bool = True +) -> ConfigManager: """加载Python模块配置文件 - + Args: config_name (str): 配置文件名 template_name (str): 模板文件名 @@ -52,10 +54,7 @@ async def load_python_module_config(config_name: str, template_name: str, comple Returns: ConfigManager: 配置文件管理器 """ - cfg_inst = pymodule.PythonModuleConfigFile( - config_name, - template_name - ) + cfg_inst = pymodule.PythonModuleConfigFile(config_name, template_name) cfg_mgr = ConfigManager(cfg_inst) await cfg_mgr.load_config(completion=completion) @@ -63,20 +62,21 @@ async def load_python_module_config(config_name: str, template_name: str, comple return cfg_mgr -async def load_json_config(config_name: str, template_name: str=None, template_data: dict=None, completion: bool=True) -> ConfigManager: +async def load_json_config( + config_name: str, + template_name: str = None, + template_data: dict = None, + completion: bool = True, +) -> ConfigManager: """加载JSON配置文件 - + Args: config_name (str): 配置文件名 template_name (str): 模板文件名 template_data (dict): 模板数据 completion (bool): 是否自动补全内存中的配置文件 """ - cfg_inst = json_file.JSONConfigFile( - config_name, - template_name, - template_data - ) + cfg_inst = json_file.JSONConfigFile(config_name, template_name, template_data) cfg_mgr = ConfigManager(cfg_inst) await cfg_mgr.load_config(completion=completion) @@ -84,9 +84,14 @@ async def load_json_config(config_name: str, template_name: str=None, template_d return cfg_mgr -async def load_yaml_config(config_name: str, template_name: str=None, template_data: dict=None, completion: bool=True) -> ConfigManager: +async def load_yaml_config( + config_name: str, + template_name: str = None, + template_data: dict = None, + completion: bool = True, +) -> ConfigManager: """加载YAML配置文件 - + Args: config_name (str): 配置文件名 template_name (str): 模板文件名 @@ -96,11 +101,7 @@ async def load_yaml_config(config_name: str, template_name: str=None, template_d Returns: ConfigManager: 配置文件管理器 """ - cfg_inst = yaml_file.YAMLConfigFile( - config_name, - template_name, - template_data - ) + cfg_inst = yaml_file.YAMLConfigFile(config_name, template_name, template_data) cfg_mgr = ConfigManager(cfg_inst) await cfg_mgr.load_config(completion=completion) diff --git a/pkg/config/model.py b/pkg/config/model.py index 153123e3..f3536804 100644 --- a/pkg/config/model.py +++ b/pkg/config/model.py @@ -22,7 +22,7 @@ class ConfigFile(metaclass=abc.ABCMeta): pass @abc.abstractmethod - async def load(self, completion: bool=True) -> dict: + async def load(self, completion: bool = True) -> dict: pass @abc.abstractmethod diff --git a/pkg/core/app.py b/pkg/core/app.py index 9e337efb..6e631e5c 100644 --- a/pkg/core/app.py +++ b/pkg/core/app.py @@ -2,9 +2,7 @@ from __future__ import annotations import logging import asyncio -import threading import traceback -import enum import sys import os @@ -29,7 +27,6 @@ from ..discover import engine as discover_engine from ..utils import logcache, ip from . import taskmgr from . import entities as core_entities -from .bootutils import config class Application: @@ -123,33 +120,55 @@ class Application: async def run(self): try: await self.plugin_mgr.initialize_plugins() + # 后续可能会允许动态重启其他任务 # 故为了防止程序在非 Ctrl-C 情况下退出,这里创建一个不会结束的协程 async def never_ending(): while True: await asyncio.sleep(1) - self.task_mgr.create_task(self.platform_mgr.run(), name="platform-manager", scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM]) - self.task_mgr.create_task(self.ctrl.run(), name="query-controller", scopes=[core_entities.LifecycleControlScope.APPLICATION]) - self.task_mgr.create_task(self.http_ctrl.run(), name="http-api-controller", scopes=[core_entities.LifecycleControlScope.APPLICATION]) - self.task_mgr.create_task(never_ending(), name="never-ending-task", scopes=[core_entities.LifecycleControlScope.APPLICATION]) + self.task_mgr.create_task( + self.platform_mgr.run(), + name='platform-manager', + scopes=[ + core_entities.LifecycleControlScope.APPLICATION, + core_entities.LifecycleControlScope.PLATFORM, + ], + ) + self.task_mgr.create_task( + self.ctrl.run(), + name='query-controller', + scopes=[core_entities.LifecycleControlScope.APPLICATION], + ) + self.task_mgr.create_task( + self.http_ctrl.run(), + name='http-api-controller', + scopes=[core_entities.LifecycleControlScope.APPLICATION], + ) + self.task_mgr.create_task( + never_ending(), + name='never-ending-task', + scopes=[core_entities.LifecycleControlScope.APPLICATION], + ) await self.print_web_access_info() await self.task_mgr.wait_all() except asyncio.CancelledError: pass except Exception as e: - self.logger.error(f"应用运行致命异常: {e}") - self.logger.debug(f"Traceback: {traceback.format_exc()}") + self.logger.error(f'应用运行致命异常: {e}') + self.logger.debug(f'Traceback: {traceback.format_exc()}') async def print_web_access_info(self): """打印访问 webui 的提示""" - if not os.path.exists(os.path.join(".", "web/out")): - self.logger.warning("WebUI 文件缺失,请根据文档获取:https://docs.langbot.app/webui/intro.html") + if not os.path.exists(os.path.join('.', 'web/out')): + self.logger.warning( + 'WebUI 文件缺失,请根据文档获取:https://docs.langbot.app/webui/intro.html' + ) return - host_ip = "127.0.0.1" + host_ip = '127.0.0.1' public_ip = await ip.get_myip() @@ -170,7 +189,7 @@ class Application: 🤯 WebUI 仍处于 Beta 测试阶段,如有问题或建议请反馈到 https://github.com/RockChinQ/LangBot/issues ======================================= """.strip() - for line in tips.split("\n"): + for line in tips.split('\n'): self.logger.info(line) async def reload( @@ -179,21 +198,28 @@ class Application: ): match scope: case core_entities.LifecycleControlScope.PLATFORM.value: - self.logger.info("执行热重载 scope="+scope) + self.logger.info('执行热重载 scope=' + scope) await self.platform_mgr.shutdown() self.platform_mgr = im_mgr.PlatformManager(self) await self.platform_mgr.initialize() - self.task_mgr.create_task(self.platform_mgr.run(), name="platform-manager", scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM]) + self.task_mgr.create_task( + self.platform_mgr.run(), + name='platform-manager', + scopes=[ + core_entities.LifecycleControlScope.APPLICATION, + core_entities.LifecycleControlScope.PLATFORM, + ], + ) case core_entities.LifecycleControlScope.PLUGIN.value: - self.logger.info("执行热重载 scope="+scope) + self.logger.info('执行热重载 scope=' + scope) await self.plugin_mgr.destroy_plugins() # 删除 sys.module 中所有的 plugins/* 下的模块 for mod in list(sys.modules.keys()): - if mod.startswith("plugins."): + if mod.startswith('plugins.'): del sys.modules[mod] self.plugin_mgr = plugin_mgr.PluginManager(self) @@ -204,7 +230,7 @@ class Application: await self.plugin_mgr.load_plugins() await self.plugin_mgr.initialize_plugins() case core_entities.LifecycleControlScope.PROVIDER.value: - self.logger.info("执行热重载 scope="+scope) + self.logger.info('执行热重载 scope=' + scope) await self.tool_mgr.shutdown() @@ -220,4 +246,4 @@ class Application: await llm_tool_mgr_inst.initialize() self.tool_mgr = llm_tool_mgr_inst case _: - pass \ No newline at end of file + pass diff --git a/pkg/core/boot.py b/pkg/core/boot.py index 307fa95c..e3f2a9da 100644 --- a/pkg/core/boot.py +++ b/pkg/core/boot.py @@ -7,29 +7,30 @@ import os from . import app from ..audit import identifier from . import stage -from ..utils import constants +from ..utils import constants, importutil # 引入启动阶段实现以便注册 -from .stages import load_config, setup_logger, build_app, migrate, show_notes, genkeys +from . import stages + +importutil.import_modules_in_pkg(stages) stage_order = [ - "LoadConfigStage", - "MigrationStage", - "GenKeysStage", - "SetupLoggerStage", - "BuildAppStage", - "ShowNotesStage" + 'LoadConfigStage', + 'MigrationStage', + 'GenKeysStage', + 'SetupLoggerStage', + 'BuildAppStage', + 'ShowNotesStage', ] async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application: - # 生成标识符 identifier.init() # 确定是否为调试模式 - if "DEBUG" in os.environ and os.environ["DEBUG"] in ["true", "1"]: + if 'DEBUG' in os.environ and os.environ['DEBUG'] in ['true', '1']: constants.debug_mode = True ap = app.Application() @@ -50,21 +51,17 @@ async def make_app(loop: asyncio.AbstractEventLoop) -> app.Application: async def main(loop: asyncio.AbstractEventLoop): try: - # 挂系统信号处理 import signal - ap: app.Application - def signal_handler(sig, frame): - print("[Signal] 程序退出.") + print('[Signal] 程序退出.') # ap.shutdown() os._exit(0) signal.signal(signal.SIGINT, signal_handler) app_inst = await make_app(loop) - ap = app_inst await app_inst.run() - except Exception as e: + except Exception: traceback.print_exc() diff --git a/pkg/core/bootutils/config.py b/pkg/core/bootutils/config.py index 794c329a..cea4af45 100644 --- a/pkg/core/bootutils/config.py +++ b/pkg/core/bootutils/config.py @@ -1,11 +1,9 @@ from __future__ import annotations -import json from ...config import manager as config_mgr -from ...config.impls import pymodule load_python_module_config = config_mgr.load_python_module_config load_json_config = config_mgr.load_json_config -load_yaml_config = config_mgr.load_yaml_config \ No newline at end of file +load_yaml_config = config_mgr.load_yaml_config diff --git a/pkg/core/bootutils/deps.py b/pkg/core/bootutils/deps.py index 8d8e7e00..b0ba7983 100644 --- a/pkg/core/bootutils/deps.py +++ b/pkg/core/bootutils/deps.py @@ -5,39 +5,39 @@ from ...utils import pkgmgr # 检查依赖,防止用户未安装 # 左边为引入名称,右边为依赖名称 required_deps = { - "requests": "requests", - "openai": "openai", - "anthropic": "anthropic", - "colorlog": "colorlog", - "aiocqhttp": "aiocqhttp", - "botpy": "qq-botpy-rc", - "PIL": "pillow", - "nakuru": "nakuru-project-idk", - "tiktoken": "tiktoken", - "yaml": "pyyaml", - "aiohttp": "aiohttp", - "psutil": "psutil", - "async_lru": "async-lru", - "ollama": "ollama", - "quart": "quart", - "quart_cors": "quart-cors", - "sqlalchemy": "sqlalchemy[asyncio]", - "aiosqlite": "aiosqlite", - "aiofiles": "aiofiles", - "aioshutil": "aioshutil", - "argon2": "argon2-cffi", - "jwt": "pyjwt", - "Crypto": "pycryptodome", - "lark_oapi": "lark-oapi", - "discord": "discord.py", - "cryptography": "cryptography", - "gewechat_client": "gewechat-client", - "dingtalk_stream": "dingtalk_stream", - "dashscope": "dashscope", - "telegram": "python-telegram-bot", - "certifi": "certifi", - "mcp": "mcp", - "sqlmodel": "sqlmodel", + 'requests': 'requests', + 'openai': 'openai', + 'anthropic': 'anthropic', + 'colorlog': 'colorlog', + 'aiocqhttp': 'aiocqhttp', + 'botpy': 'qq-botpy-rc', + 'PIL': 'pillow', + 'nakuru': 'nakuru-project-idk', + 'tiktoken': 'tiktoken', + 'yaml': 'pyyaml', + 'aiohttp': 'aiohttp', + 'psutil': 'psutil', + 'async_lru': 'async-lru', + 'ollama': 'ollama', + 'quart': 'quart', + 'quart_cors': 'quart-cors', + 'sqlalchemy': 'sqlalchemy[asyncio]', + 'aiosqlite': 'aiosqlite', + 'aiofiles': 'aiofiles', + 'aioshutil': 'aioshutil', + 'argon2': 'argon2-cffi', + 'jwt': 'pyjwt', + 'Crypto': 'pycryptodome', + 'lark_oapi': 'lark-oapi', + 'discord': 'discord.py', + 'cryptography': 'cryptography', + 'gewechat_client': 'gewechat-client', + 'dingtalk_stream': 'dingtalk_stream', + 'dashscope': 'dashscope', + 'telegram': 'python-telegram-bot', + 'certifi': 'certifi', + 'mcp': 'mcp', + 'sqlmodel': 'sqlmodel', } @@ -52,20 +52,25 @@ async def check_deps() -> list[str]: missing_deps.append(dep) return missing_deps + async def install_deps(deps: list[str]): global required_deps - + for dep in deps: - pip.main(["install", required_deps[dep]]) + pip.main(['install', required_deps[dep]]) + async def precheck_plugin_deps(): print('[Startup] Prechecking plugin dependencies...') # 只有在plugins目录存在时才执行插件依赖安装 - if os.path.exists("plugins"): - for dir in os.listdir("plugins"): - subdir = os.path.join("plugins", dir) + if os.path.exists('plugins'): + for dir in os.listdir('plugins'): + subdir = os.path.join('plugins', dir) if not os.path.isdir(subdir): continue if 'requirements.txt' in os.listdir(subdir): - pkgmgr.install_requirements(os.path.join(subdir, 'requirements.txt'), extra_params=['-q', '-q', '-q']) + pkgmgr.install_requirements( + os.path.join(subdir, 'requirements.txt'), + extra_params=['-q', '-q', '-q'], + ) diff --git a/pkg/core/bootutils/files.py b/pkg/core/bootutils/files.py index 9a2dff71..3599e41b 100644 --- a/pkg/core/bootutils/files.py +++ b/pkg/core/bootutils/files.py @@ -2,23 +2,23 @@ from __future__ import annotations import os import shutil -import sys required_files = { - "plugins/__init__.py": "templates/__init__.py", - "data/config.yaml": "templates/config.yaml", + 'plugins/__init__.py': 'templates/__init__.py', + 'data/config.yaml': 'templates/config.yaml', } required_paths = [ - "temp", - "data", - "data/metadata", - "data/logs", - "data/labels", - "plugins" + 'temp', + 'data', + 'data/metadata', + 'data/logs', + 'data/labels', + 'plugins', ] + async def generate_files() -> list[str]: global required_files, required_paths diff --git a/pkg/core/bootutils/log.py b/pkg/core/bootutils/log.py index 7cbb7412..df65e1ba 100644 --- a/pkg/core/bootutils/log.py +++ b/pkg/core/bootutils/log.py @@ -1,5 +1,4 @@ import logging -import os import sys import time @@ -9,11 +8,11 @@ from ...utils import constants log_colors_config = { - "DEBUG": "green", # cyan white - "INFO": "white", - "WARNING": "yellow", - "ERROR": "red", - "CRITICAL": "cyan", + 'DEBUG': 'green', # cyan white + 'INFO': 'white', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'cyan', } @@ -27,26 +26,31 @@ async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging. if constants.debug_mode: level = logging.DEBUG - log_file_name = "data/logs/langbot-%s.log" % time.strftime( - "%Y-%m-%d", time.localtime() + log_file_name = 'data/logs/langbot-%s.log' % time.strftime( + '%Y-%m-%d', time.localtime() ) - qcg_logger = logging.getLogger("langbot") + qcg_logger = logging.getLogger('langbot') qcg_logger.setLevel(level) color_formatter = colorlog.ColoredFormatter( - fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s", - datefmt="%m-%d %H:%M:%S", + fmt='%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s', + datefmt='%m-%d %H:%M:%S', log_colors=log_colors_config, ) stream_handler = logging.StreamHandler(sys.stdout) # stream_handler.setLevel(level) # stream_handler.setFormatter(color_formatter) - stream_handler.stream = open(sys.stdout.fileno(), mode='w', encoding='utf-8', buffering=1) + stream_handler.stream = open( + sys.stdout.fileno(), mode='w', encoding='utf-8', buffering=1 + ) - log_handlers: list[logging.Handler] = [stream_handler, logging.FileHandler(log_file_name, encoding='utf-8')] + log_handlers: list[logging.Handler] = [ + stream_handler, + logging.FileHandler(log_file_name, encoding='utf-8'), + ] log_handlers += extra_handlers if extra_handlers is not None else [] for handler in log_handlers: @@ -54,13 +58,13 @@ async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging. handler.setFormatter(color_formatter) qcg_logger.addHandler(handler) - qcg_logger.debug("日志初始化完成,日志级别:%s" % level) + qcg_logger.debug('日志初始化完成,日志级别:%s' % level) logging.basicConfig( level=logging.CRITICAL, # 设置日志输出格式 - format="[DEPR][%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s", + format='[DEPR][%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s', # 日志输出的格式 # -8表示占位符,让输出左对齐,输出长度都为8位 - datefmt="%Y-%m-%d %H:%M:%S", # 时间输出的格式 + datefmt='%Y-%m-%d %H:%M:%S', # 时间输出的格式 handlers=[logging.NullHandler()], ) diff --git a/pkg/core/entities.py b/pkg/core/entities.py index 1753495b..5ffd0029 100644 --- a/pkg/core/entities.py +++ b/pkg/core/entities.py @@ -8,21 +8,18 @@ import asyncio import pydantic.v1 as pydantic from ..provider import entities as llm_entities -from ..provider.modelmgr import entities, modelmgr, requester +from ..provider.modelmgr import requester from ..provider.tools import entities as tools_entities from ..platform import adapter as msadapter from ..platform.types import message as platform_message from ..platform.types import events as platform_events -from ..platform.types import entities as platform_entities - class LifecycleControlScope(enum.Enum): - - APPLICATION = "application" - PLATFORM = "platform" - PLUGIN = "plugin" - PROVIDER = "provider" + APPLICATION = 'application' + PLATFORM = 'platform' + PLUGIN = 'plugin' + PROVIDER = 'provider' class LauncherTypes(enum.Enum): @@ -89,14 +86,17 @@ class Query(pydantic.BaseModel): use_funcs: typing.Optional[list[tools_entities.LLMFunction]] = None """使用的函数,由前置处理器阶段设置""" - resp_messages: typing.Optional[list[llm_entities.Message]] | typing.Optional[list[platform_message.MessageChain]] = [] + resp_messages: ( + typing.Optional[list[llm_entities.Message]] + | typing.Optional[list[platform_message.MessageChain]] + ) = [] """由Process阶段生成的回复消息对象列表""" resp_message_chain: typing.Optional[list[platform_message.MessageChain]] = None """回复消息链,从resp_messages包装而得""" # ======= 内部保留 ======= - current_stage: "pkg.pipeline.pipelinemgr.StageInstContainer" = None + current_stage = None # pkg.pipeline.pipelinemgr.StageInstContainer """当前所处阶段""" class Config: @@ -109,13 +109,13 @@ class Query(pydantic.BaseModel): if self.variables is None: self.variables = {} self.variables[key] = value - + def get_variable(self, key: str) -> typing.Any: """获取变量""" if self.variables is None: return None return self.variables.get(key) - + def get_variables(self) -> dict[str, typing.Any]: """获取所有变量""" if self.variables is None: @@ -130,9 +130,13 @@ class Conversation(pydantic.BaseModel): messages: list[llm_entities.Message] - create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now) + create_time: typing.Optional[datetime.datetime] = pydantic.Field( + default_factory=datetime.datetime.now + ) - update_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now) + update_time: typing.Optional[datetime.datetime] = pydantic.Field( + default_factory=datetime.datetime.now + ) use_llm_model: requester.RuntimeLLMModel @@ -147,6 +151,7 @@ class Conversation(pydantic.BaseModel): class Session(pydantic.BaseModel): """会话,一个 Session 对应一个 {launcher_type.value}_{launcher_id}""" + launcher_type: LauncherTypes launcher_id: typing.Union[int, str] @@ -157,11 +162,17 @@ class Session(pydantic.BaseModel): using_conversation: typing.Optional[Conversation] = None - conversations: typing.Optional[list[Conversation]] = pydantic.Field(default_factory=list) + conversations: typing.Optional[list[Conversation]] = pydantic.Field( + default_factory=list + ) - create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now) + create_time: typing.Optional[datetime.datetime] = pydantic.Field( + default_factory=datetime.datetime.now + ) - update_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now) + update_time: typing.Optional[datetime.datetime] = pydantic.Field( + default_factory=datetime.datetime.now + ) semaphore: typing.Optional[asyncio.Semaphore] = None """当前会话的信号量,用于限制并发""" diff --git a/pkg/core/migration.py b/pkg/core/migration.py index 2c5c7597..e97c0cf3 100644 --- a/pkg/core/migration.py +++ b/pkg/core/migration.py @@ -9,21 +9,21 @@ from . import app preregistered_migrations: list[typing.Type[Migration]] = [] """当前阶段暂不支持扩展""" + def migration_class(name: str, number: int): - """注册一个迁移 - """ + """注册一个迁移""" + def decorator(cls: typing.Type[Migration]) -> typing.Type[Migration]: cls.name = name cls.number = number preregistered_migrations.append(cls) return cls - + return decorator class Migration(abc.ABC): - """一个版本的迁移 - """ + """一个版本的迁移""" name: str @@ -33,15 +33,13 @@ class Migration(abc.ABC): def __init__(self, ap: app.Application): self.ap = ap - + @abc.abstractmethod async def need_migrate(self) -> bool: - """判断当前环境是否需要运行此迁移 - """ + """判断当前环境是否需要运行此迁移""" pass @abc.abstractmethod async def run(self): - """执行迁移 - """ + """执行迁移""" pass diff --git a/pkg/core/migrations/m001_sensitive_word_migration.py b/pkg/core/migrations/m001_sensitive_word_migration.py index 6e435eeb..72200346 100644 --- a/pkg/core/migrations/m001_sensitive_word_migration.py +++ b/pkg/core/migrations/m001_sensitive_word_migration.py @@ -1,26 +1,26 @@ from __future__ import annotations import os -import sys from .. import migration -@migration.migration_class("sensitive-word-migration", 1) +@migration.migration_class('sensitive-word-migration', 1) class SensitiveWordMigration(migration.Migration): - """敏感词迁移 - """ + """敏感词迁移""" async def need_migrate(self) -> bool: - """判断当前环境是否需要运行此迁移 - """ - return os.path.exists("data/config/sensitive-words.json") and not os.path.exists("data/metadata/sensitive-words.json") + """判断当前环境是否需要运行此迁移""" + return os.path.exists( + 'data/config/sensitive-words.json' + ) and not os.path.exists('data/metadata/sensitive-words.json') async def run(self): - """执行迁移 - """ + """执行迁移""" # 移动文件 - os.rename("data/config/sensitive-words.json", "data/metadata/sensitive-words.json") + os.rename( + 'data/config/sensitive-words.json', 'data/metadata/sensitive-words.json' + ) # 重新加载配置 await self.ap.sensitive_meta.load_config() diff --git a/pkg/core/migrations/m002_openai_config_migration.py b/pkg/core/migrations/m002_openai_config_migration.py index 2f2553ef..6892110f 100644 --- a/pkg/core/migrations/m002_openai_config_migration.py +++ b/pkg/core/migrations/m002_openai_config_migration.py @@ -3,19 +3,16 @@ from __future__ import annotations from .. import migration -@migration.migration_class("openai-config-migration", 2) +@migration.migration_class('openai-config-migration', 2) class OpenAIConfigMigration(migration.Migration): - """OpenAI配置迁移 - """ + """OpenAI配置迁移""" async def need_migrate(self) -> bool: - """判断当前环境是否需要运行此迁移 - """ + """判断当前环境是否需要运行此迁移""" return 'openai-config' in self.ap.provider_cfg.data async def run(self): - """执行迁移 - """ + """执行迁移""" old_openai_config = self.ap.provider_cfg.data['openai-config'].copy() if 'keys' not in self.ap.provider_cfg.data: @@ -26,7 +23,9 @@ class OpenAIConfigMigration(migration.Migration): self.ap.provider_cfg.data['keys']['openai'] = old_openai_config['api-keys'] - self.ap.provider_cfg.data['model'] = old_openai_config['chat-completions-params']['model'] + self.ap.provider_cfg.data['model'] = old_openai_config[ + 'chat-completions-params' + ]['model'] del old_openai_config['chat-completions-params']['model'] @@ -35,7 +34,7 @@ class OpenAIConfigMigration(migration.Migration): if 'openai-chat-completions' not in self.ap.provider_cfg.data['requester']: self.ap.provider_cfg.data['requester']['openai-chat-completions'] = {} - + self.ap.provider_cfg.data['requester']['openai-chat-completions'] = { 'base-url': old_openai_config['base_url'], 'args': old_openai_config['chat-completions-params'], @@ -44,4 +43,4 @@ class OpenAIConfigMigration(migration.Migration): del self.ap.provider_cfg.data['openai-config'] - await self.ap.provider_cfg.dump_config() \ No newline at end of file + await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m003_anthropic_requester_cfg_completion.py b/pkg/core/migrations/m003_anthropic_requester_cfg_completion.py index 101b03d0..19369679 100644 --- a/pkg/core/migrations/m003_anthropic_requester_cfg_completion.py +++ b/pkg/core/migrations/m003_anthropic_requester_cfg_completion.py @@ -3,26 +3,23 @@ from __future__ import annotations from .. import migration -@migration.migration_class("anthropic-requester-config-completion", 3) +@migration.migration_class('anthropic-requester-config-completion', 3) class AnthropicRequesterConfigCompletionMigration(migration.Migration): - """OpenAI配置迁移 - """ + """OpenAI配置迁移""" async def need_migrate(self) -> bool: - """判断当前环境是否需要运行此迁移 - """ - return 'anthropic-messages' not in self.ap.provider_cfg.data['requester'] \ + """判断当前环境是否需要运行此迁移""" + return ( + 'anthropic-messages' not in self.ap.provider_cfg.data['requester'] or 'anthropic' not in self.ap.provider_cfg.data['keys'] + ) async def run(self): - """执行迁移 - """ + """执行迁移""" if 'anthropic-messages' not in self.ap.provider_cfg.data['requester']: self.ap.provider_cfg.data['requester']['anthropic-messages'] = { 'base-url': 'https://api.anthropic.com', - 'args': { - 'max_tokens': 1024 - }, + 'args': {'max_tokens': 1024}, 'timeout': 120, } diff --git a/pkg/core/migrations/m004_moonshot_cfg_completion.py b/pkg/core/migrations/m004_moonshot_cfg_completion.py index b1f7e9ed..de086159 100644 --- a/pkg/core/migrations/m004_moonshot_cfg_completion.py +++ b/pkg/core/migrations/m004_moonshot_cfg_completion.py @@ -3,20 +3,19 @@ from __future__ import annotations from .. import migration -@migration.migration_class("moonshot-config-completion", 4) +@migration.migration_class('moonshot-config-completion', 4) class MoonshotConfigCompletionMigration(migration.Migration): - """OpenAI配置迁移 - """ + """OpenAI配置迁移""" async def need_migrate(self) -> bool: - """判断当前环境是否需要运行此迁移 - """ - return 'moonshot-chat-completions' not in self.ap.provider_cfg.data['requester'] \ + """判断当前环境是否需要运行此迁移""" + return ( + 'moonshot-chat-completions' not in self.ap.provider_cfg.data['requester'] or 'moonshot' not in self.ap.provider_cfg.data['keys'] + ) async def run(self): - """执行迁移 - """ + """执行迁移""" if 'moonshot-chat-completions' not in self.ap.provider_cfg.data['requester']: self.ap.provider_cfg.data['requester']['moonshot-chat-completions'] = { 'base-url': 'https://api.moonshot.cn/v1', diff --git a/pkg/core/migrations/m005_deepseek_cfg_completion.py b/pkg/core/migrations/m005_deepseek_cfg_completion.py index bd8aa2ee..d4d82e3f 100644 --- a/pkg/core/migrations/m005_deepseek_cfg_completion.py +++ b/pkg/core/migrations/m005_deepseek_cfg_completion.py @@ -3,20 +3,19 @@ from __future__ import annotations from .. import migration -@migration.migration_class("deepseek-config-completion", 5) +@migration.migration_class('deepseek-config-completion', 5) class DeepseekConfigCompletionMigration(migration.Migration): - """OpenAI配置迁移 - """ + """OpenAI配置迁移""" async def need_migrate(self) -> bool: - """判断当前环境是否需要运行此迁移 - """ - return 'deepseek-chat-completions' not in self.ap.provider_cfg.data['requester'] \ + """判断当前环境是否需要运行此迁移""" + return ( + 'deepseek-chat-completions' not in self.ap.provider_cfg.data['requester'] or 'deepseek' not in self.ap.provider_cfg.data['keys'] + ) async def run(self): - """执行迁移 - """ + """执行迁移""" if 'deepseek-chat-completions' not in self.ap.provider_cfg.data['requester']: self.ap.provider_cfg.data['requester']['deepseek-chat-completions'] = { 'base-url': 'https://api.deepseek.com', @@ -27,4 +26,4 @@ class DeepseekConfigCompletionMigration(migration.Migration): if 'deepseek' not in self.ap.provider_cfg.data['keys']: self.ap.provider_cfg.data['keys']['deepseek'] = [] - await self.ap.provider_cfg.dump_config() \ No newline at end of file + await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m006_vision_config.py b/pkg/core/migrations/m006_vision_config.py index 8084611e..ea824d44 100644 --- a/pkg/core/migrations/m006_vision_config.py +++ b/pkg/core/migrations/m006_vision_config.py @@ -3,17 +3,17 @@ from __future__ import annotations from .. import migration -@migration.migration_class("vision-config", 6) +@migration.migration_class('vision-config', 6) class VisionConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - return "enable-vision" not in self.ap.provider_cfg.data + return 'enable-vision' not in self.ap.provider_cfg.data async def run(self): """执行迁移""" - if "enable-vision" not in self.ap.provider_cfg.data: - self.ap.provider_cfg.data["enable-vision"] = False + if 'enable-vision' not in self.ap.provider_cfg.data: + self.ap.provider_cfg.data['enable-vision'] = False await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m007_qcg_center_url.py b/pkg/core/migrations/m007_qcg_center_url.py index cecd6b11..b3fcd853 100644 --- a/pkg/core/migrations/m007_qcg_center_url.py +++ b/pkg/core/migrations/m007_qcg_center_url.py @@ -3,18 +3,20 @@ from __future__ import annotations from .. import migration -@migration.migration_class("qcg-center-url-config", 7) +@migration.migration_class('qcg-center-url-config', 7) class QCGCenterURLConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - return "qcg-center-url" not in self.ap.system_cfg.data + return 'qcg-center-url' not in self.ap.system_cfg.data async def run(self): """执行迁移""" - - if "qcg-center-url" not in self.ap.system_cfg.data: - self.ap.system_cfg.data["qcg-center-url"] = "https://api.qchatgpt.rockchin.top/api/v2" - + + if 'qcg-center-url' not in self.ap.system_cfg.data: + self.ap.system_cfg.data['qcg-center-url'] = ( + 'https://api.qchatgpt.rockchin.top/api/v2' + ) + await self.ap.system_cfg.dump_config() diff --git a/pkg/core/migrations/m008_ad_fixwin_config_migrate.py b/pkg/core/migrations/m008_ad_fixwin_config_migrate.py index ccd6fbd7..96fd58e7 100644 --- a/pkg/core/migrations/m008_ad_fixwin_config_migrate.py +++ b/pkg/core/migrations/m008_ad_fixwin_config_migrate.py @@ -3,27 +3,27 @@ from __future__ import annotations from .. import migration -@migration.migration_class("ad-fixwin-cfg-migration", 8) +@migration.migration_class('ad-fixwin-cfg-migration', 8) class AdFixwinConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" return isinstance( - self.ap.pipeline_cfg.data["rate-limit"]["fixwin"]["default"], - int + self.ap.pipeline_cfg.data['rate-limit']['fixwin']['default'], int ) async def run(self): """执行迁移""" - - for session_name in self.ap.pipeline_cfg.data["rate-limit"]["fixwin"]: + for session_name in self.ap.pipeline_cfg.data['rate-limit']['fixwin']: temp_dict = { - "window-size": 60, - "limit": self.ap.pipeline_cfg.data["rate-limit"]["fixwin"][session_name] + 'window-size': 60, + 'limit': self.ap.pipeline_cfg.data['rate-limit']['fixwin'][ + session_name + ], } - - self.ap.pipeline_cfg.data["rate-limit"]["fixwin"][session_name] = temp_dict - await self.ap.pipeline_cfg.dump_config() \ No newline at end of file + self.ap.pipeline_cfg.data['rate-limit']['fixwin'][session_name] = temp_dict + + await self.ap.pipeline_cfg.dump_config() diff --git a/pkg/core/migrations/m009_msg_truncator_cfg.py b/pkg/core/migrations/m009_msg_truncator_cfg.py index 369b60eb..066af126 100644 --- a/pkg/core/migrations/m009_msg_truncator_cfg.py +++ b/pkg/core/migrations/m009_msg_truncator_cfg.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("msg-truncator-cfg-migration", 9) +@migration.migration_class('msg-truncator-cfg-migration', 9) class MsgTruncatorConfigMigration(migration.Migration): """迁移""" @@ -13,12 +13,10 @@ class MsgTruncatorConfigMigration(migration.Migration): async def run(self): """执行迁移""" - + self.ap.pipeline_cfg.data['msg-truncate'] = { 'method': 'round', - 'round': { - 'max-round': 10 - } + 'round': {'max-round': 10}, } await self.ap.pipeline_cfg.dump_config() diff --git a/pkg/core/migrations/m010_ollama_requester_config.py b/pkg/core/migrations/m010_ollama_requester_config.py index 56e49663..8e2e15eb 100644 --- a/pkg/core/migrations/m010_ollama_requester_config.py +++ b/pkg/core/migrations/m010_ollama_requester_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("ollama-requester-config", 10) +@migration.migration_class('ollama-requester-config', 10) class MsgTruncatorConfigMigration(migration.Migration): """迁移""" @@ -13,11 +13,11 @@ class MsgTruncatorConfigMigration(migration.Migration): async def run(self): """执行迁移""" - + self.ap.provider_cfg.data['requester']['ollama-chat'] = { - "base-url": "http://127.0.0.1:11434", - "args": {}, - "timeout": 600 + 'base-url': 'http://127.0.0.1:11434', + 'args': {}, + 'timeout': 600, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m011_command_prefix_config.py b/pkg/core/migrations/m011_command_prefix_config.py index 6a9e1118..6165ae47 100644 --- a/pkg/core/migrations/m011_command_prefix_config.py +++ b/pkg/core/migrations/m011_command_prefix_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("command-prefix-config", 11) +@migration.migration_class('command-prefix-config', 11) class CommandPrefixConfigMigration(migration.Migration): """迁移""" @@ -13,9 +13,7 @@ class CommandPrefixConfigMigration(migration.Migration): async def run(self): """执行迁移""" - - self.ap.command_cfg.data['command-prefix'] = [ - "!", "!" - ] + + self.ap.command_cfg.data['command-prefix'] = ['!', '!'] await self.ap.command_cfg.dump_config() diff --git a/pkg/core/migrations/m012_runner_config.py b/pkg/core/migrations/m012_runner_config.py index fa236bb7..e7f0e67a 100644 --- a/pkg/core/migrations/m012_runner_config.py +++ b/pkg/core/migrations/m012_runner_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("runner-config", 12) +@migration.migration_class('runner-config', 12) class RunnerConfigMigration(migration.Migration): """迁移""" @@ -13,7 +13,7 @@ class RunnerConfigMigration(migration.Migration): async def run(self): """执行迁移""" - + self.ap.provider_cfg.data['runner'] = 'local-agent' await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m013_http_api_config.py b/pkg/core/migrations/m013_http_api_config.py index c5fe55ba..55aff2b9 100644 --- a/pkg/core/migrations/m013_http_api_config.py +++ b/pkg/core/migrations/m013_http_api_config.py @@ -3,29 +3,30 @@ from __future__ import annotations from .. import migration -@migration.migration_class("http-api-config", 13) +@migration.migration_class('http-api-config', 13) class HttpApiConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - return 'http-api' not in self.ap.system_cfg.data or "persistence" not in self.ap.system_cfg.data + return ( + 'http-api' not in self.ap.system_cfg.data + or 'persistence' not in self.ap.system_cfg.data + ) async def run(self): """执行迁移""" - + self.ap.system_cfg.data['http-api'] = { - "enable": True, - "host": "0.0.0.0", - "port": 5300, - "jwt-expire": 604800 + 'enable': True, + 'host': '0.0.0.0', + 'port': 5300, + 'jwt-expire': 604800, } self.ap.system_cfg.data['persistence'] = { - "sqlite": { - "path": "data/persistence.db" - }, - "use": "sqlite" + 'sqlite': {'path': 'data/persistence.db'}, + 'use': 'sqlite', } await self.ap.system_cfg.dump_config() diff --git a/pkg/core/migrations/m014_force_delay_config.py b/pkg/core/migrations/m014_force_delay_config.py index 55521c9c..005a2ca2 100644 --- a/pkg/core/migrations/m014_force_delay_config.py +++ b/pkg/core/migrations/m014_force_delay_config.py @@ -3,20 +3,20 @@ from __future__ import annotations from .. import migration -@migration.migration_class("force-delay-config", 14) +@migration.migration_class('force-delay-config', 14) class ForceDelayConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - return type(self.ap.platform_cfg.data['force-delay']) == list + return isinstance(self.ap.platform_cfg.data['force-delay'], list) async def run(self): """执行迁移""" self.ap.platform_cfg.data['force-delay'] = { - "min": self.ap.platform_cfg.data['force-delay'][0], - "max": self.ap.platform_cfg.data['force-delay'][1] + 'min': self.ap.platform_cfg.data['force-delay'][0], + 'max': self.ap.platform_cfg.data['force-delay'][1], } await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m015_gitee_ai_config.py b/pkg/core/migrations/m015_gitee_ai_config.py index b41071ad..7dd9b853 100644 --- a/pkg/core/migrations/m015_gitee_ai_config.py +++ b/pkg/core/migrations/m015_gitee_ai_config.py @@ -3,24 +3,25 @@ from __future__ import annotations from .. import migration -@migration.migration_class("gitee-ai-config", 15) +@migration.migration_class('gitee-ai-config', 15) class GiteeAIConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - return 'gitee-ai-chat-completions' not in self.ap.provider_cfg.data['requester'] or 'gitee-ai' not in self.ap.provider_cfg.data['keys'] + return ( + 'gitee-ai-chat-completions' not in self.ap.provider_cfg.data['requester'] + or 'gitee-ai' not in self.ap.provider_cfg.data['keys'] + ) async def run(self): """执行迁移""" self.ap.provider_cfg.data['requester']['gitee-ai-chat-completions'] = { - "base-url": "https://ai.gitee.com/v1", - "args": {}, - "timeout": 120 + 'base-url': 'https://ai.gitee.com/v1', + 'args': {}, + 'timeout': 120, } - self.ap.provider_cfg.data['keys']['gitee-ai'] = [ - "XXXXX" - ] + self.ap.provider_cfg.data['keys']['gitee-ai'] = ['XXXXX'] await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m016_dify_service_api.py b/pkg/core/migrations/m016_dify_service_api.py index 123879f8..e7c4dc6d 100644 --- a/pkg/core/migrations/m016_dify_service_api.py +++ b/pkg/core/migrations/m016_dify_service_api.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("dify-service-api-config", 16) +@migration.migration_class('dify-service-api-config', 16) class DifyServiceAPICfgMigration(migration.Migration): """迁移""" @@ -14,15 +14,10 @@ class DifyServiceAPICfgMigration(migration.Migration): async def run(self): """执行迁移""" self.ap.provider_cfg.data['dify-service-api'] = { - "base-url": "https://api.dify.ai/v1", - "app-type": "chat", - "chat": { - "api-key": "app-1234567890" - }, - "workflow": { - "api-key": "app-1234567890", - "output-key": "summary" - } + 'base-url': 'https://api.dify.ai/v1', + 'app-type': 'chat', + 'chat': {'api-key': 'app-1234567890'}, + 'workflow': {'api-key': 'app-1234567890', 'output-key': 'summary'}, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m017_dify_api_timeout_params.py b/pkg/core/migrations/m017_dify_api_timeout_params.py index a0e502a4..7ce9133c 100644 --- a/pkg/core/migrations/m017_dify_api_timeout_params.py +++ b/pkg/core/migrations/m017_dify_api_timeout_params.py @@ -3,22 +3,26 @@ from __future__ import annotations from .. import migration -@migration.migration_class("dify-api-timeout-params", 17) +@migration.migration_class('dify-api-timeout-params', 17) class DifyAPITimeoutParamsMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - return 'timeout' not in self.ap.provider_cfg.data['dify-service-api']['chat'] or 'timeout' not in self.ap.provider_cfg.data['dify-service-api']['workflow'] \ + return ( + 'timeout' not in self.ap.provider_cfg.data['dify-service-api']['chat'] + or 'timeout' + not in self.ap.provider_cfg.data['dify-service-api']['workflow'] or 'agent' not in self.ap.provider_cfg.data['dify-service-api'] + ) async def run(self): """执行迁移""" self.ap.provider_cfg.data['dify-service-api']['chat']['timeout'] = 120 self.ap.provider_cfg.data['dify-service-api']['workflow']['timeout'] = 120 self.ap.provider_cfg.data['dify-service-api']['agent'] = { - "api-key": "app-1234567890", - "timeout": 120 + 'api-key': 'app-1234567890', + 'timeout': 120, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m018_xai_config.py b/pkg/core/migrations/m018_xai_config.py index bf422451..db5ed5bf 100644 --- a/pkg/core/migrations/m018_xai_config.py +++ b/pkg/core/migrations/m018_xai_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("xai-config", 18) +@migration.migration_class('xai-config', 18) class XaiConfigMigration(migration.Migration): """迁移""" @@ -14,12 +14,10 @@ class XaiConfigMigration(migration.Migration): async def run(self): """执行迁移""" self.ap.provider_cfg.data['requester']['xai-chat-completions'] = { - "base-url": "https://api.x.ai/v1", - "args": {}, - "timeout": 120 + 'base-url': 'https://api.x.ai/v1', + 'args': {}, + 'timeout': 120, } - self.ap.provider_cfg.data['keys']['xai'] = [ - "xai-1234567890" - ] + self.ap.provider_cfg.data['keys']['xai'] = ['xai-1234567890'] await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m019_zhipuai_config.py b/pkg/core/migrations/m019_zhipuai_config.py index 67f33340..081d8dcf 100644 --- a/pkg/core/migrations/m019_zhipuai_config.py +++ b/pkg/core/migrations/m019_zhipuai_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("zhipuai-config", 19) +@migration.migration_class('zhipuai-config', 19) class ZhipuaiConfigMigration(migration.Migration): """迁移""" @@ -14,12 +14,10 @@ class ZhipuaiConfigMigration(migration.Migration): async def run(self): """执行迁移""" self.ap.provider_cfg.data['requester']['zhipuai-chat-completions'] = { - "base-url": "https://open.bigmodel.cn/api/paas/v4", - "args": {}, - "timeout": 120 + 'base-url': 'https://open.bigmodel.cn/api/paas/v4', + 'args': {}, + 'timeout': 120, } - self.ap.provider_cfg.data['keys']['zhipuai'] = [ - "xxxxxxx" - ] + self.ap.provider_cfg.data['keys']['zhipuai'] = ['xxxxxxx'] await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m020_wecom_config.py b/pkg/core/migrations/m020_wecom_config.py index 9581cb91..3e833d3e 100644 --- a/pkg/core/migrations/m020_wecom_config.py +++ b/pkg/core/migrations/m020_wecom_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("wecom-config", 20) +@migration.migration_class('wecom-config', 20) class WecomConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'wecom': # return False @@ -19,16 +19,18 @@ class WecomConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "wecom", - "enable": False, - "host": "0.0.0.0", - "port": 2290, - "corpid": "", - "secret": "", - "token": "", - "EncodingAESKey": "", - "contacts_secret": "" - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'wecom', + 'enable': False, + 'host': '0.0.0.0', + 'port': 2290, + 'corpid': '', + 'secret': '', + 'token': '', + 'EncodingAESKey': '', + 'contacts_secret': '', + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m021_lark_config.py b/pkg/core/migrations/m021_lark_config.py index 49d9bb8f..04f29db4 100644 --- a/pkg/core/migrations/m021_lark_config.py +++ b/pkg/core/migrations/m021_lark_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("lark-config", 21) +@migration.migration_class('lark-config', 21) class LarkConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'lark': # return False @@ -19,15 +19,17 @@ class LarkConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "lark", - "enable": False, - "app_id": "cli_abcdefgh", - "app_secret": "XXXXXXXXXX", - "bot_name": "LangBot", - "enable-webhook": False, - "port": 2285, - "encrypt-key": "xxxxxxxxx" - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'lark', + 'enable': False, + 'app_id': 'cli_abcdefgh', + 'app_secret': 'XXXXXXXXXX', + 'bot_name': 'LangBot', + 'enable-webhook': False, + 'port': 2285, + 'encrypt-key': 'xxxxxxxxx', + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m022_lmstudio_config.py b/pkg/core/migrations/m022_lmstudio_config.py index 5506b37b..bffc6bb8 100644 --- a/pkg/core/migrations/m022_lmstudio_config.py +++ b/pkg/core/migrations/m022_lmstudio_config.py @@ -3,21 +3,21 @@ from __future__ import annotations from .. import migration -@migration.migration_class("lmstudio-config", 22) +@migration.migration_class('lmstudio-config', 22) class LmStudioConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + return 'lmstudio-chat-completions' not in self.ap.provider_cfg.data['requester'] async def run(self): """执行迁移""" self.ap.provider_cfg.data['requester']['lmstudio-chat-completions'] = { - "base-url": "http://127.0.0.1:1234/v1", - "args": {}, - "timeout": 120 + 'base-url': 'http://127.0.0.1:1234/v1', + 'args': {}, + 'timeout': 120, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m023_siliconflow_config.py b/pkg/core/migrations/m023_siliconflow_config.py index a0e65c6a..fdf696eb 100644 --- a/pkg/core/migrations/m023_siliconflow_config.py +++ b/pkg/core/migrations/m023_siliconflow_config.py @@ -3,25 +3,25 @@ from __future__ import annotations from .. import migration -@migration.migration_class("siliconflow-config", 23) +@migration.migration_class('siliconflow-config', 23) class SiliconFlowConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - - return 'siliconflow-chat-completions' not in self.ap.provider_cfg.data['requester'] + + return ( + 'siliconflow-chat-completions' not in self.ap.provider_cfg.data['requester'] + ) async def run(self): """执行迁移""" - self.ap.provider_cfg.data['keys']['siliconflow'] = [ - "xxxxxxx" - ] + self.ap.provider_cfg.data['keys']['siliconflow'] = ['xxxxxxx'] self.ap.provider_cfg.data['requester']['siliconflow-chat-completions'] = { - "base-url": "https://api.siliconflow.cn/v1", - "args": {}, - "timeout": 120 + 'base-url': 'https://api.siliconflow.cn/v1', + 'args': {}, + 'timeout': 120, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m024_discord_config.py b/pkg/core/migrations/m024_discord_config.py index fcfac6e6..ebcae232 100644 --- a/pkg/core/migrations/m024_discord_config.py +++ b/pkg/core/migrations/m024_discord_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("discord-config", 24) +@migration.migration_class('discord-config', 24) class DiscordConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'discord': # return False @@ -19,11 +19,13 @@ class DiscordConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "discord", - "enable": False, - "client_id": "1234567890", - "token": "XXXXXXXXXX" - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'discord', + 'enable': False, + 'client_id': '1234567890', + 'token': 'XXXXXXXXXX', + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m025_gewechat_config.py b/pkg/core/migrations/m025_gewechat_config.py index 65b5c1d5..bb729854 100644 --- a/pkg/core/migrations/m025_gewechat_config.py +++ b/pkg/core/migrations/m025_gewechat_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("gewechat-config", 25) +@migration.migration_class('gewechat-config', 25) class GewechatConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'gewechat': # return False @@ -19,15 +19,17 @@ class GewechatConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "gewechat", - "enable": False, - "gewechat_url": "http://your-gewechat-server:2531", - "gewechat_file_url": "http://your-gewechat-server:2532", - "port": 2286, - "callback_url": "http://your-callback-url:2286/gewechat/callback", - "app_id": "", - "token": "" - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'gewechat', + 'enable': False, + 'gewechat_url': 'http://your-gewechat-server:2531', + 'gewechat_file_url': 'http://your-gewechat-server:2532', + 'port': 2286, + 'callback_url': 'http://your-callback-url:2286/gewechat/callback', + 'app_id': '', + 'token': '', + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m026_qqofficial_config.py b/pkg/core/migrations/m026_qqofficial_config.py index b4745806..90674341 100644 --- a/pkg/core/migrations/m026_qqofficial_config.py +++ b/pkg/core/migrations/m026_qqofficial_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("qqofficial-config", 26) +@migration.migration_class('qqofficial-config', 26) class QQOfficialConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'qqofficial': # return False @@ -19,13 +19,15 @@ class QQOfficialConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "qqofficial", - "enable": False, - "appid": "", - "secret": "", - "port": 2284, - "token": "" - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'qqofficial', + 'enable': False, + 'appid': '', + 'secret': '', + 'port': 2284, + 'token': '', + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m027_wx_official_account_config.py b/pkg/core/migrations/m027_wx_official_account_config.py index 5abaad87..7c5b0e35 100644 --- a/pkg/core/migrations/m027_wx_official_account_config.py +++ b/pkg/core/migrations/m027_wx_official_account_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("wx-official-account-config", 27) +@migration.migration_class('wx-official-account-config', 27) class WXOfficialAccountConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'officialaccount': # return False @@ -19,15 +19,17 @@ class WXOfficialAccountConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "officialaccount", - "enable": False, - "token": "", - "EncodingAESKey": "", - "AppID": "", - "AppSecret": "", - "host": "0.0.0.0", - "port": 2287 - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'officialaccount', + 'enable': False, + 'token': '', + 'EncodingAESKey': '', + 'AppID': '', + 'AppSecret': '', + 'host': '0.0.0.0', + 'port': 2287, + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m028_aliyun_requester_config.py b/pkg/core/migrations/m028_aliyun_requester_config.py index f28bc04f..8d80727a 100644 --- a/pkg/core/migrations/m028_aliyun_requester_config.py +++ b/pkg/core/migrations/m028_aliyun_requester_config.py @@ -3,25 +3,23 @@ from __future__ import annotations from .. import migration -@migration.migration_class("bailian-requester-config", 28) +@migration.migration_class('bailian-requester-config', 28) class BailianRequesterConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + return 'bailian-chat-completions' not in self.ap.provider_cfg.data['requester'] async def run(self): """执行迁移""" - self.ap.provider_cfg.data['keys']['bailian'] = [ - "sk-xxxxxxx" - ] + self.ap.provider_cfg.data['keys']['bailian'] = ['sk-xxxxxxx'] self.ap.provider_cfg.data['requester']['bailian-chat-completions'] = { - "base-url": "https://dashscope.aliyuncs.com/compatible-mode/v1", - "args": {}, - "timeout": 120 + 'base-url': 'https://dashscope.aliyuncs.com/compatible-mode/v1', + 'args': {}, + 'timeout': 120, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m029_dashscope_app_api_config.py b/pkg/core/migrations/m029_dashscope_app_api_config.py index 3a069bac..5a61fe0d 100644 --- a/pkg/core/migrations/m029_dashscope_app_api_config.py +++ b/pkg/core/migrations/m029_dashscope_app_api_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("dashscope-app-api-config", 29) +@migration.migration_class('dashscope-app-api-config', 29) class DashscopeAppAPICfgMigration(migration.Migration): """迁移""" @@ -14,20 +14,14 @@ class DashscopeAppAPICfgMigration(migration.Migration): async def run(self): """执行迁移""" self.ap.provider_cfg.data['dashscope-app-api'] = { - "app-type": "agent", - "api-key": "sk-1234567890", - "agent": { - "app-id": "Your_app_id", - "references_quote": "参考资料来自:" + 'app-type': 'agent', + 'api-key': 'sk-1234567890', + 'agent': {'app-id': 'Your_app_id', 'references_quote': '参考资料来自:'}, + 'workflow': { + 'app-id': 'Your_app_id', + 'references_quote': '参考资料来自:', + 'biz_params': {'city': '北京', 'date': '2023-08-10'}, }, - "workflow": { - "app-id": "Your_app_id", - "references_quote": "参考资料来自:", - "biz_params": { - "city": "北京", - "date": "2023-08-10" - } - } } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m030_lark_config_cmpl.py b/pkg/core/migrations/m030_lark_config_cmpl.py index e016af7b..37e8fabe 100644 --- a/pkg/core/migrations/m030_lark_config_cmpl.py +++ b/pkg/core/migrations/m030_lark_config_cmpl.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("lark-config-cmpl", 30) +@migration.migration_class('lark-config-cmpl', 30) class LarkConfigCmplMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + for adapter in self.ap.platform_cfg.data['platform-adapters']: if adapter['adapter'] == 'lark': if 'enable-webhook' not in adapter: @@ -26,6 +26,6 @@ class LarkConfigCmplMigration(migration.Migration): if 'port' not in adapter: adapter['port'] = 2285 if 'encrypt-key' not in adapter: - adapter['encrypt-key'] = "xxxxxxxxx" + adapter['encrypt-key'] = 'xxxxxxxxx' await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m031_dingtalk_config.py b/pkg/core/migrations/m031_dingtalk_config.py index 7dbc4735..22ba0bbf 100644 --- a/pkg/core/migrations/m031_dingtalk_config.py +++ b/pkg/core/migrations/m031_dingtalk_config.py @@ -3,13 +3,13 @@ from __future__ import annotations from .. import migration -@migration.migration_class("dingtalk-config", 31) +@migration.migration_class('dingtalk-config', 31) class DingTalkConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + # for adapter in self.ap.platform_cfg.data['platform-adapters']: # if adapter['adapter'] == 'dingtalk': # return False @@ -19,13 +19,15 @@ class DingTalkConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.platform_cfg.data['platform-adapters'].append({ - "adapter": "dingtalk", - "enable": False, - "client_id": "", - "client_secret": "", - "robot_code": "", - "robot_name": "" - }) + self.ap.platform_cfg.data['platform-adapters'].append( + { + 'adapter': 'dingtalk', + 'enable': False, + 'client_id': '', + 'client_secret': '', + 'robot_code': '', + 'robot_name': '', + } + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m032_volcark_config.py b/pkg/core/migrations/m032_volcark_config.py index a07e5686..ae8feb52 100644 --- a/pkg/core/migrations/m032_volcark_config.py +++ b/pkg/core/migrations/m032_volcark_config.py @@ -3,25 +3,23 @@ from __future__ import annotations from .. import migration -@migration.migration_class("volcark-requester-config", 32) +@migration.migration_class('volcark-requester-config', 32) class VolcArkRequesterConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - + return 'volcark-chat-completions' not in self.ap.provider_cfg.data['requester'] async def run(self): """执行迁移""" - self.ap.provider_cfg.data['keys']['volcark'] = [ - "xxxxxxxx" - ] + self.ap.provider_cfg.data['keys']['volcark'] = ['xxxxxxxx'] self.ap.provider_cfg.data['requester']['volcark-chat-completions'] = { - "base-url": "https://ark.cn-beijing.volces.com/api/v3", - "args": {}, - "timeout": 120 + 'base-url': 'https://ark.cn-beijing.volces.com/api/v3', + 'args': {}, + 'timeout': 120, } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m033_dify_thinking_config.py b/pkg/core/migrations/m033_dify_thinking_config.py index 1f663b46..d25a4aad 100644 --- a/pkg/core/migrations/m033_dify_thinking_config.py +++ b/pkg/core/migrations/m033_dify_thinking_config.py @@ -3,24 +3,27 @@ from __future__ import annotations from .. import migration -@migration.migration_class("dify-thinking-config", 33) +@migration.migration_class('dify-thinking-config', 33) class DifyThinkingConfigMigration(migration.Migration): """迁移""" async def need_migrate(self) -> bool: """判断当前环境是否需要运行此迁移""" - - if 'options' not in self.ap.provider_cfg.data["dify-service-api"]: + + if 'options' not in self.ap.provider_cfg.data['dify-service-api']: return True - if 'convert-thinking-tips' not in self.ap.provider_cfg.data["dify-service-api"]["options"]: + if ( + 'convert-thinking-tips' + not in self.ap.provider_cfg.data['dify-service-api']['options'] + ): return True return False - + async def run(self): """执行迁移""" - self.ap.provider_cfg.data["dify-service-api"]["options"] = { - "convert-thinking-tips": "plain" + self.ap.provider_cfg.data['dify-service-api']['options'] = { + 'convert-thinking-tips': 'plain' } await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/migrations/m034_gewechat_file_url_config.py b/pkg/core/migrations/m034_gewechat_file_url_config.py index 44bbd65e..8c3e0a83 100644 --- a/pkg/core/migrations/m034_gewechat_file_url_config.py +++ b/pkg/core/migrations/m034_gewechat_file_url_config.py @@ -5,7 +5,7 @@ from urllib.parse import urlparse from .. import migration -@migration.migration_class("gewechat-file-url-config", 34) +@migration.migration_class('gewechat-file-url-config', 34) class GewechatFileUrlConfigMigration(migration.Migration): """迁移""" @@ -24,6 +24,8 @@ class GewechatFileUrlConfigMigration(migration.Migration): if adapter['adapter'] == 'gewechat': if 'gewechat_file_url' not in adapter: parsed_url = urlparse(adapter['gewechat_url']) - adapter['gewechat_file_url'] = f"{parsed_url.scheme}://{parsed_url.hostname}:2532" + adapter['gewechat_file_url'] = ( + f'{parsed_url.scheme}://{parsed_url.hostname}:2532' + ) await self.ap.platform_cfg.dump_config() diff --git a/pkg/core/migrations/m035_wxoa_mode.py b/pkg/core/migrations/m035_wxoa_mode.py index ce0ce628..6b675e30 100644 --- a/pkg/core/migrations/m035_wxoa_mode.py +++ b/pkg/core/migrations/m035_wxoa_mode.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("wxoa-mode", 35) +@migration.migration_class('wxoa-mode', 35) class WxoaModeMigration(migration.Migration): """迁移""" diff --git a/pkg/core/migrations/m036_wxoa_loading_message.py b/pkg/core/migrations/m036_wxoa_loading_message.py index 682be435..29ecba20 100644 --- a/pkg/core/migrations/m036_wxoa_loading_message.py +++ b/pkg/core/migrations/m036_wxoa_loading_message.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("wxoa-loading-message", 36) +@migration.migration_class('wxoa-loading-message', 36) class WxoaLoadingMessageMigration(migration.Migration): """迁移""" diff --git a/pkg/core/migrations/m037_mcp_config.py b/pkg/core/migrations/m037_mcp_config.py index f045f0ff..3752193e 100644 --- a/pkg/core/migrations/m037_mcp_config.py +++ b/pkg/core/migrations/m037_mcp_config.py @@ -3,7 +3,7 @@ from __future__ import annotations from .. import migration -@migration.migration_class("mcp-config", 37) +@migration.migration_class('mcp-config', 37) class MCPConfigMigration(migration.Migration): """迁移""" @@ -13,8 +13,6 @@ class MCPConfigMigration(migration.Migration): async def run(self): """执行迁移""" - self.ap.provider_cfg.data['mcp'] = { - "servers": [] - } + self.ap.provider_cfg.data['mcp'] = {'servers': []} await self.ap.provider_cfg.dump_config() diff --git a/pkg/core/note.py b/pkg/core/note.py index 6ffbff51..07171581 100644 --- a/pkg/core/note.py +++ b/pkg/core/note.py @@ -7,9 +7,10 @@ from . import app preregistered_notes: list[typing.Type[LaunchNote]] = [] + def note_class(name: str, number: int): - """注册一个启动信息 - """ + """注册一个启动信息""" + def decorator(cls: typing.Type[LaunchNote]) -> typing.Type[LaunchNote]: cls.name = name cls.number = number @@ -20,8 +21,8 @@ def note_class(name: str, number: int): class LaunchNote(abc.ABC): - """启动信息 - """ + """启动信息""" + name: str number: int @@ -33,12 +34,10 @@ class LaunchNote(abc.ABC): @abc.abstractmethod async def need_show(self) -> bool: - """判断当前环境是否需要显示此启动信息 - """ + """判断当前环境是否需要显示此启动信息""" pass @abc.abstractmethod async def yield_note(self) -> typing.AsyncGenerator[typing.Tuple[str, int], None]: - """生成启动信息 - """ + """生成启动信息""" pass diff --git a/pkg/core/notes/n001_classic_msgs.py b/pkg/core/notes/n001_classic_msgs.py index bdc5c44e..3f3bd8e0 100644 --- a/pkg/core/notes/n001_classic_msgs.py +++ b/pkg/core/notes/n001_classic_msgs.py @@ -2,19 +2,17 @@ from __future__ import annotations import typing -from .. import note, app +from .. import note -@note.note_class("ClassicNotes", 1) +@note.note_class('ClassicNotes', 1) class ClassicNotes(note.LaunchNote): - """经典启动信息 - """ + """经典启动信息""" async def need_show(self) -> bool: return True async def yield_note(self) -> typing.AsyncGenerator[typing.Tuple[str, int], None]: - yield await self.ap.ann_mgr.show_announcements() - yield await self.ap.ver_mgr.show_version_update() \ No newline at end of file + yield await self.ap.ver_mgr.show_version_update() diff --git a/pkg/core/notes/n002_selection_mode_on_windows.py b/pkg/core/notes/n002_selection_mode_on_windows.py index 961d697d..23bff24a 100644 --- a/pkg/core/notes/n002_selection_mode_on_windows.py +++ b/pkg/core/notes/n002_selection_mode_on_windows.py @@ -2,20 +2,20 @@ from __future__ import annotations import typing import os -import sys import logging -from .. import note, app +from .. import note -@note.note_class("SelectionModeOnWindows", 2) +@note.note_class('SelectionModeOnWindows', 2) class SelectionModeOnWindows(note.LaunchNote): - """Windows 上的选择模式提示信息 - """ + """Windows 上的选择模式提示信息""" async def need_show(self) -> bool: return os.name == 'nt' async def yield_note(self) -> typing.AsyncGenerator[typing.Tuple[str, int], None]: - - yield """您正在使用 Windows 系统,若窗口左上角显示处于”选择“模式,程序将被暂停运行,此时请右键窗口中空白区域退出选择模式。""", logging.INFO + yield ( + """您正在使用 Windows 系统,若窗口左上角显示处于”选择“模式,程序将被暂停运行,此时请右键窗口中空白区域退出选择模式。""", + logging.INFO, + ) diff --git a/pkg/core/notes/n003_print_version.py b/pkg/core/notes/n003_print_version.py index 91208fdf..18eebf4f 100644 --- a/pkg/core/notes/n003_print_version.py +++ b/pkg/core/notes/n003_print_version.py @@ -1,21 +1,17 @@ from __future__ import annotations import typing -import os -import sys import logging -from .. import note, app +from .. import note -@note.note_class("PrintVersion", 3) +@note.note_class('PrintVersion', 3) class PrintVersion(note.LaunchNote): - """Print Version Information - """ + """Print Version Information""" async def need_show(self) -> bool: return True async def yield_note(self) -> typing.AsyncGenerator[typing.Tuple[str, int], None]: - - yield f"Current Version: {self.ap.ver_mgr.get_current_version()}", logging.INFO + yield f'Current Version: {self.ap.ver_mgr.get_current_version()}', logging.INFO diff --git a/pkg/core/stage.py b/pkg/core/stage.py index f1c65295..220c474d 100644 --- a/pkg/core/stage.py +++ b/pkg/core/stage.py @@ -12,9 +12,8 @@ preregistered_stages: dict[str, typing.Type[BootingStage]] = {} 当前阶段暂不支持扩展 """ -def stage_class( - name: str -): + +def stage_class(name: str): def decorator(cls: typing.Type[BootingStage]) -> typing.Type[BootingStage]: preregistered_stages[name] = cls return cls @@ -23,12 +22,11 @@ def stage_class( class BootingStage(abc.ABC): - """启动阶段 - """ + """启动阶段""" + name: str = None @abc.abstractmethod async def run(self, ap: app.Application): - """启动 - """ + """启动""" pass diff --git a/pkg/core/stages/build_app.py b/pkg/core/stages/build_app.py index 7d62e9c9..5dee9386 100644 --- a/pkg/core/stages/build_app.py +++ b/pkg/core/stages/build_app.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys from .. import stage, app from ...utils import version, proxy, announce, platform @@ -24,26 +23,22 @@ from ...utils import logcache from .. import taskmgr -@stage.stage_class("BuildAppStage") +@stage.stage_class('BuildAppStage') class BuildAppStage(stage.BootingStage): - """构建应用阶段 - """ + """构建应用阶段""" async def run(self, ap: app.Application): - """构建app对象的各个组件对象并初始化 - """ + """构建app对象的各个组件对象并初始化""" ap.task_mgr = taskmgr.AsyncTaskManager(ap) discover = discover_engine.ComponentDiscoveryEngine(ap) - discover.discover_blueprint( - "components.yaml" - ) + discover.discover_blueprint('components.yaml') ap.discover = discover proxy_mgr = proxy.ProxyManager(ap) await proxy_mgr.initialize() ap.proxy_mgr = proxy_mgr - + ver_mgr = version.VersionManager(ap) await ver_mgr.initialize() ap.ver_mgr = ver_mgr @@ -52,14 +47,14 @@ class BuildAppStage(stage.BootingStage): ap, backend_url=ap.instance_config.data['telemetry']['url'], basic_info={ - "host_id": identifier.identifier["host_id"], - "instance_id": identifier.identifier["instance_id"], - "semantic_version": ver_mgr.get_current_version(), - "platform": platform.get_platform(), + 'host_id': identifier.identifier['host_id'], + 'instance_id': identifier.identifier['instance_id'], + 'semantic_version': ver_mgr.get_current_version(), + 'platform': platform.get_platform(), }, runtime_info={ - "admin_id": "{}".format(ap.instance_config.data["admins"]), - "msg_source": str([]), + 'admin_id': '{}'.format(ap.instance_config.data['admins']), + 'msg_source': str([]), }, ) ap.ctr_mgr = center_v2_api diff --git a/pkg/core/stages/genkeys.py b/pkg/core/stages/genkeys.py index 843f1532..c24ebd70 100644 --- a/pkg/core/stages/genkeys.py +++ b/pkg/core/stages/genkeys.py @@ -1,20 +1,17 @@ from __future__ import annotations import secrets -import os from .. import stage, app -@stage.stage_class("GenKeysStage") +@stage.stage_class('GenKeysStage') class GenKeysStage(stage.BootingStage): - """生成密钥阶段 - """ + """生成密钥阶段""" async def run(self, ap: app.Application): - """启动 - """ - + """启动""" + if not ap.instance_config.data['system']['jwt']['secret']: ap.instance_config.data['system']['jwt']['secret'] = secrets.token_hex(16) await ap.instance_config.dump_config() diff --git a/pkg/core/stages/load_config.py b/pkg/core/stages/load_config.py index edfe5915..ac2f0c37 100644 --- a/pkg/core/stages/load_config.py +++ b/pkg/core/stages/load_config.py @@ -7,45 +7,80 @@ from .. import stage, app from ..bootutils import config -@stage.stage_class("LoadConfigStage") +@stage.stage_class('LoadConfigStage') class LoadConfigStage(stage.BootingStage): - """加载配置文件阶段 - """ + """加载配置文件阶段""" async def run(self, ap: app.Application): - """启动 - """ + """启动""" # ======= deprecated ======= - if os.path.exists("data/config/command.json"): - ap.command_cfg = await config.load_json_config("data/config/command.json", "templates/legacy/command.json", completion=False) + if os.path.exists('data/config/command.json'): + ap.command_cfg = await config.load_json_config( + 'data/config/command.json', + 'templates/legacy/command.json', + completion=False, + ) - if os.path.exists("data/config/pipeline.json"): - ap.pipeline_cfg = await config.load_json_config("data/config/pipeline.json", "templates/legacy/pipeline.json", completion=False) + if os.path.exists('data/config/pipeline.json'): + ap.pipeline_cfg = await config.load_json_config( + 'data/config/pipeline.json', + 'templates/legacy/pipeline.json', + completion=False, + ) - if os.path.exists("data/config/platform.json"): - ap.platform_cfg = await config.load_json_config("data/config/platform.json", "templates/legacy/platform.json", completion=False) + if os.path.exists('data/config/platform.json'): + ap.platform_cfg = await config.load_json_config( + 'data/config/platform.json', + 'templates/legacy/platform.json', + completion=False, + ) - if os.path.exists("data/config/provider.json"): - ap.provider_cfg = await config.load_json_config("data/config/provider.json", "templates/legacy/provider.json", completion=False) + if os.path.exists('data/config/provider.json'): + ap.provider_cfg = await config.load_json_config( + 'data/config/provider.json', + 'templates/legacy/provider.json', + completion=False, + ) - if os.path.exists("data/config/system.json"): - ap.system_cfg = await config.load_json_config("data/config/system.json", "templates/legacy/system.json", completion=False) + if os.path.exists('data/config/system.json'): + ap.system_cfg = await config.load_json_config( + 'data/config/system.json', + 'templates/legacy/system.json', + completion=False, + ) - if os.path.exists("data/metadata/instance-secret.json"): - ap.instance_secret_meta = await config.load_json_config("data/metadata/instance-secret.json", template_data={ - 'jwt_secret': secrets.token_hex(16) - }) + if os.path.exists('data/metadata/instance-secret.json'): + ap.instance_secret_meta = await config.load_json_config( + 'data/metadata/instance-secret.json', + template_data={'jwt_secret': secrets.token_hex(16)}, + ) await ap.instance_secret_meta.dump_config() # ======= deprecated ======= - ap.instance_config = await config.load_yaml_config("data/config.yaml", "templates/config.yaml", completion=False) + ap.instance_config = await config.load_yaml_config( + 'data/config.yaml', 'templates/config.yaml', completion=False + ) await ap.instance_config.dump_config() - ap.sensitive_meta = await config.load_json_config("data/metadata/sensitive-words.json", "templates/metadata/sensitive-words.json") + ap.sensitive_meta = await config.load_json_config( + 'data/metadata/sensitive-words.json', + 'templates/metadata/sensitive-words.json', + ) await ap.sensitive_meta.dump_config() - ap.pipeline_config_meta_trigger = await config.load_yaml_config("templates/metadata/pipeline/trigger.yaml", "templates/metadata/pipeline/trigger.yaml") - ap.pipeline_config_meta_safety = await config.load_yaml_config("templates/metadata/pipeline/safety.yaml", "templates/metadata/pipeline/safety.yaml") - ap.pipeline_config_meta_ai = await config.load_yaml_config("templates/metadata/pipeline/ai.yaml", "templates/metadata/pipeline/ai.yaml") - ap.pipeline_config_meta_output = await config.load_yaml_config("templates/metadata/pipeline/output.yaml", "templates/metadata/pipeline/output.yaml") + ap.pipeline_config_meta_trigger = await config.load_yaml_config( + 'templates/metadata/pipeline/trigger.yaml', + 'templates/metadata/pipeline/trigger.yaml', + ) + ap.pipeline_config_meta_safety = await config.load_yaml_config( + 'templates/metadata/pipeline/safety.yaml', + 'templates/metadata/pipeline/safety.yaml', + ) + ap.pipeline_config_meta_ai = await config.load_yaml_config( + 'templates/metadata/pipeline/ai.yaml', 'templates/metadata/pipeline/ai.yaml' + ) + ap.pipeline_config_meta_output = await config.load_yaml_config( + 'templates/metadata/pipeline/output.yaml', + 'templates/metadata/pipeline/output.yaml', + ) diff --git a/pkg/core/stages/migrate.py b/pkg/core/stages/migrate.py index e902431a..02b03256 100644 --- a/pkg/core/stages/migrate.py +++ b/pkg/core/stages/migrate.py @@ -1,37 +1,30 @@ from __future__ import annotations -import importlib -import os from .. import stage, app from .. import migration -from ..migrations import m001_sensitive_word_migration, m002_openai_config_migration, m003_anthropic_requester_cfg_completion, m004_moonshot_cfg_completion -from ..migrations import m005_deepseek_cfg_completion, m006_vision_config, m007_qcg_center_url, m008_ad_fixwin_config_migrate, m009_msg_truncator_cfg -from ..migrations import m010_ollama_requester_config, m011_command_prefix_config, m012_runner_config, m013_http_api_config, m014_force_delay_config -from ..migrations import m015_gitee_ai_config, m016_dify_service_api, m017_dify_api_timeout_params, m018_xai_config, m019_zhipuai_config -from ..migrations import m020_wecom_config, m021_lark_config, m022_lmstudio_config, m023_siliconflow_config, m024_discord_config, m025_gewechat_config -from ..migrations import m026_qqofficial_config, m027_wx_official_account_config, m028_aliyun_requester_config -from ..migrations import m029_dashscope_app_api_config, m030_lark_config_cmpl, m031_dingtalk_config, m032_volcark_config -from ..migrations import m033_dify_thinking_config, m034_gewechat_file_url_config, m035_wxoa_mode, m036_wxoa_loading_message -from ..migrations import m037_mcp_config +from ...utils import importutil +from .. import migrations + +importutil.import_modules_in_pkg(migrations) -@stage.stage_class("MigrationStage") +@stage.stage_class('MigrationStage') class MigrationStage(stage.BootingStage): - """迁移阶段 - """ + """迁移阶段""" async def run(self, ap: app.Application): - """启动 - """ + """启动""" - if any([ - ap.command_cfg is None, - ap.pipeline_cfg is None, - ap.platform_cfg is None, - ap.provider_cfg is None, - ap.system_cfg is None, - ]): # only run migration when version is 3.x + if any( + [ + ap.command_cfg is None, + ap.pipeline_cfg is None, + ap.platform_cfg is None, + ap.provider_cfg is None, + ap.system_cfg is None, + ] + ): # only run migration when version is 3.x return migrations = migration.preregistered_migrations diff --git a/pkg/core/stages/setup_logger.py b/pkg/core/stages/setup_logger.py index 8f385d1f..0c630175 100644 --- a/pkg/core/stages/setup_logger.py +++ b/pkg/core/stages/setup_logger.py @@ -1,8 +1,6 @@ from __future__ import annotations import logging -import asyncio -from datetime import datetime from .. import stage, app from ..bootutils import log @@ -12,6 +10,7 @@ class PersistenceHandler(logging.Handler, object): """ 保存日志到数据库 """ + ap: app.Application def __init__(self, name, ap: app.Application): @@ -28,19 +27,17 @@ class PersistenceHandler(logging.Handler, object): msg = self.format(record) if self.ap.log_cache is not None: self.ap.log_cache.add_log(msg) - + except Exception: self.handleError(record) -@stage.stage_class("SetupLoggerStage") +@stage.stage_class('SetupLoggerStage') class SetupLoggerStage(stage.BootingStage): - """设置日志器阶段 - """ + """设置日志器阶段""" async def run(self, ap: app.Application): - """启动 - """ + """启动""" persistence_handler = PersistenceHandler('LoggerHandler', ap) extra_handlers = [] diff --git a/pkg/core/stages/show_notes.py b/pkg/core/stages/show_notes.py index 63d8f580..e7c98b42 100644 --- a/pkg/core/stages/show_notes.py +++ b/pkg/core/stages/show_notes.py @@ -1,16 +1,18 @@ from __future__ import annotations from .. import stage, app, note -from ..notes import n001_classic_msgs, n002_selection_mode_on_windows, n003_print_version +from ...utils import importutil + +from .. import notes + +importutil.import_modules_in_pkg(notes) -@stage.stage_class("ShowNotesStage") +@stage.stage_class('ShowNotesStage') class ShowNotesStage(stage.BootingStage): - """显示启动信息阶段 - """ + """显示启动信息阶段""" async def run(self, ap: app.Application): - # 排序 note.preregistered_notes.sort(key=lambda x: x.number) @@ -24,5 +26,5 @@ class ShowNotesStage(stage.BootingStage): msg, level = ret if msg: ap.logger.log(level, msg) - except Exception as e: + except Exception: continue diff --git a/pkg/core/taskmgr.py b/pkg/core/taskmgr.py index d5887019..ae2394cf 100644 --- a/pkg/core/taskmgr.py +++ b/pkg/core/taskmgr.py @@ -3,7 +3,6 @@ from __future__ import annotations import asyncio import typing import datetime -import traceback from . import app from . import entities as core_entities @@ -19,11 +18,11 @@ class TaskContext: """记录日志""" def __init__(self): - self.current_action = "default" - self.log = "" + self.current_action = 'default' + self.log = '' def _log(self, msg: str): - self.log += msg + "\n" + self.log += msg + '\n' def set_current_action(self, action: str): self.current_action = action @@ -37,16 +36,16 @@ class TaskContext: self.set_current_action(action) self._log( - f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | {self.current_action} | {msg}" + f'{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | {self.current_action} | {msg}' ) def to_dict(self) -> dict: - return {"current_action": self.current_action, "log": self.log} - + return {'current_action': self.current_action, 'log': self.log} + @staticmethod def new() -> TaskContext: return TaskContext() - + @staticmethod def placeholder() -> TaskContext: global placeholder_context @@ -69,16 +68,16 @@ class TaskWrapper: id: int """任务ID""" - task_type: str = "system" # 任务类型: system 或 user + task_type: str = 'system' # 任务类型: system 或 user """任务类型""" - kind: str = "system_task" # 由发起者确定任务种类,通常同质化的任务种类相同 + kind: str = 'system_task' # 由发起者确定任务种类,通常同质化的任务种类相同 """任务种类""" - name: str = "" + name: str = '' """任务唯一名称""" - label: str = "" + label: str = '' """任务显示名称""" task_context: TaskContext @@ -100,12 +99,14 @@ class TaskWrapper: self, ap: app.Application, coro: typing.Coroutine, - task_type: str = "system", - kind: str = "system_task", - name: str = "", - label: str = "", + task_type: str = 'system', + kind: str = 'system_task', + name: str = '', + label: str = '', context: TaskContext = None, - scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION], + scopes: list[core_entities.LifecycleControlScope] = [ + core_entities.LifecycleControlScope.APPLICATION + ], ): self.id = TaskWrapper._id_index TaskWrapper._id_index += 1 @@ -115,7 +116,7 @@ class TaskWrapper: self.task_type = task_type self.kind = kind self.name = name - self.label = label if label != "" else name + self.label = label if label != '' else name self.task.set_name(name) self.scopes = scopes @@ -125,43 +126,46 @@ class TaskWrapper: if self.task_stack is None: self.task_stack = self.task.get_stack() return exception - except: + except Exception: return None def assume_result(self): try: return self.task.result() - except: + except Exception: return None def to_dict(self) -> dict: - exception_traceback = None if self.assume_exception() is not None: exception_traceback = 'Traceback (most recent call last):\n' for frame in self.task_stack: - exception_traceback += f" File \"{frame.f_code.co_filename}\", line {frame.f_lineno}, in {frame.f_code.co_name}\n" + exception_traceback += f' File "{frame.f_code.co_filename}", line {frame.f_lineno}, in {frame.f_code.co_name}\n' - exception_traceback += f" {self.assume_exception().__str__()}\n" + exception_traceback += f' {self.assume_exception().__str__()}\n' return { - "id": self.id, - "task_type": self.task_type, - "kind": self.kind, - "name": self.name, - "label": self.label, - "scopes": [scope.value for scope in self.scopes], - "task_context": self.task_context.to_dict(), - "runtime": { - "done": self.task.done(), - "state": self.task._state, - "exception": self.assume_exception().__str__() if self.assume_exception() is not None else None, - "exception_traceback": exception_traceback, - "result": self.assume_result().__str__() if self.assume_result() is not None else None, + 'id': self.id, + 'task_type': self.task_type, + 'kind': self.kind, + 'name': self.name, + 'label': self.label, + 'scopes': [scope.value for scope in self.scopes], + 'task_context': self.task_context.to_dict(), + 'runtime': { + 'done': self.task.done(), + 'state': self.task._state, + 'exception': self.assume_exception().__str__() + if self.assume_exception() is not None + else None, + 'exception_traceback': exception_traceback, + 'result': self.assume_result().__str__() + if self.assume_result() is not None + else None, }, } - + def cancel(self): self.task.cancel() @@ -182,27 +186,33 @@ class AsyncTaskManager: def create_task( self, coro: typing.Coroutine, - task_type: str = "system", - kind: str = "system-task", - name: str = "", - label: str = "", + task_type: str = 'system', + kind: str = 'system-task', + name: str = '', + label: str = '', context: TaskContext = None, - scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION], + scopes: list[core_entities.LifecycleControlScope] = [ + core_entities.LifecycleControlScope.APPLICATION + ], ) -> TaskWrapper: - wrapper = TaskWrapper(self.ap, coro, task_type, kind, name, label, context, scopes) + wrapper = TaskWrapper( + self.ap, coro, task_type, kind, name, label, context, scopes + ) self.tasks.append(wrapper) return wrapper def create_user_task( self, coro: typing.Coroutine, - kind: str = "user-task", - name: str = "", - label: str = "", + kind: str = 'user-task', + name: str = '', + label: str = '', context: TaskContext = None, - scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION], + scopes: list[core_entities.LifecycleControlScope] = [ + core_entities.LifecycleControlScope.APPLICATION + ], ) -> TaskWrapper: - return self.create_task(coro, "user", kind, name, label, context, scopes) + return self.create_task(coro, 'user', kind, name, label, context, scopes) async def wait_all(self): await asyncio.gather(*[t.task for t in self.tasks], return_exceptions=True) @@ -215,12 +225,12 @@ class AsyncTaskManager: type: str = None, ) -> dict: return { - "tasks": [ + 'tasks': [ t.to_dict() for t in self.tasks if type is None or t.task_type == type ], - "id_index": TaskWrapper._id_index, + 'id_index': TaskWrapper._id_index, } - + def get_task_by_id(self, id: int) -> TaskWrapper | None: for t in self.tasks: if t.id == id: @@ -229,9 +239,7 @@ class AsyncTaskManager: def cancel_by_scope(self, scope: core_entities.LifecycleControlScope): for wrapper in self.tasks: - if not wrapper.task.done() and scope in wrapper.scopes: - wrapper.task.cancel() def cancel_task(self, task_id: int): diff --git a/pkg/discover/engine.py b/pkg/discover/engine.py index e79a97ad..be23a4ac 100644 --- a/pkg/discover/engine.py +++ b/pkg/discover/engine.py @@ -3,8 +3,6 @@ from __future__ import annotations import typing import importlib import os -import inspect -import mimetypes import yaml import pydantic @@ -61,11 +59,9 @@ class Metadata(pydantic.BaseModel): def __init__(self, **kwargs): super().__init__(**kwargs) - + if self.description is None: - self.description = I18nString( - en_US='' - ) + self.description = I18nString(en_US='') if self.icon is None: self.icon = '' @@ -118,47 +114,60 @@ class Component(pydantic.BaseModel): _execution: Execution """组件执行""" - def __init__(self, owner: str, manifest: typing.Dict[str, typing.Any], rel_path: str): + def __init__( + self, owner: str, manifest: typing.Dict[str, typing.Any], rel_path: str + ): super().__init__( owner=owner, manifest=manifest, rel_path=rel_path, - rel_dir=os.path.dirname(rel_path) + rel_dir=os.path.dirname(rel_path), ) self._metadata = Metadata(**manifest['metadata']) self._spec = manifest['spec'] - self._execution = Execution(**manifest['execution']) if 'execution' in manifest else None + self._execution = ( + Execution(**manifest['execution']) if 'execution' in manifest else None + ) @classmethod def is_component_manifest(cls, manifest: typing.Dict[str, typing.Any]) -> bool: """判断是否为组件清单""" - return 'apiVersion' in manifest and 'kind' in manifest and 'metadata' in manifest and 'spec' in manifest + return ( + 'apiVersion' in manifest + and 'kind' in manifest + and 'metadata' in manifest + and 'spec' in manifest + ) @property def kind(self) -> str: """组件类型""" return self.manifest['kind'] - + @property def metadata(self) -> Metadata: """组件元数据""" return self._metadata - + @property def spec(self) -> typing.Dict[str, typing.Any]: """组件规格""" return self._spec - + @property def execution(self) -> Execution: """组件可执行文件信息""" return self._execution - + @property def icon_rel_path(self) -> str: """图标相对路径""" - return os.path.join(self.rel_dir, self.metadata.icon) if self.metadata.icon is not None and self.metadata.icon.strip() != '' else None - + return ( + os.path.join(self.rel_dir, self.metadata.icon) + if self.metadata.icon is not None and self.metadata.icon.strip() != '' + else None + ) + def get_python_component_class(self) -> typing.Type[typing.Any]: """获取Python组件类""" module_path = os.path.join(self.rel_dir, self.execution.python.path) @@ -167,7 +176,7 @@ class Component(pydantic.BaseModel): module_path = module_path.replace('/', '.').replace('\\', '.') module = importlib.import_module(module_path) return getattr(module, self.execution.python.attr) - + def to_plain_dict(self) -> dict: """转换为平铺字典""" return { @@ -175,7 +184,7 @@ class Component(pydantic.BaseModel): 'label': self.metadata.label.to_dict(), 'description': self.metadata.description.to_dict(), 'icon': self.metadata.icon, - 'spec': self.spec + 'spec': self.spec, } @@ -191,24 +200,28 @@ class ComponentDiscoveryEngine: def __init__(self, ap: app.Application): self.ap = ap - def load_component_manifest(self, path: str, owner: str = 'builtin', no_save: bool = False) -> Component | None: + def load_component_manifest( + self, path: str, owner: str = 'builtin', no_save: bool = False + ) -> Component | None: """加载组件清单""" with open(path, 'r', encoding='utf-8') as f: manifest = yaml.safe_load(f) if not Component.is_component_manifest(manifest): return None - comp = Component( - owner=owner, - manifest=manifest, - rel_path=path - ) + comp = Component(owner=owner, manifest=manifest, rel_path=path) if not no_save: if comp.kind not in self.components: self.components[comp.kind] = [] self.components[comp.kind].append(comp) return comp - - def load_component_manifests_in_dir(self, path: str, owner: str = 'builtin', no_save: bool = False, max_depth: int = 1) -> typing.List[Component]: + + def load_component_manifests_in_dir( + self, + path: str, + owner: str = 'builtin', + no_save: bool = False, + max_depth: int = 1, + ) -> typing.List[Component]: """加载目录中的组件清单""" components: typing.List[Component] = [] @@ -216,17 +229,25 @@ class ComponentDiscoveryEngine: if depth > max_depth: return for file in os.listdir(path): - if (not os.path.isdir(os.path.join(path, file))) and (file.endswith('.yaml') or file.endswith('.yml')): - comp = self.load_component_manifest(os.path.join(path, file), owner, no_save) + if (not os.path.isdir(os.path.join(path, file))) and ( + file.endswith('.yaml') or file.endswith('.yml') + ): + comp = self.load_component_manifest( + os.path.join(path, file), owner, no_save + ) if comp is not None: components.append(comp) elif os.path.isdir(os.path.join(path, file)): - recursive_load_component_manifests_in_dir(os.path.join(path, file), depth + 1) + recursive_load_component_manifests_in_dir( + os.path.join(path, file), depth + 1 + ) recursive_load_component_manifests_in_dir(path) return components - - def load_blueprint_comp_group(self, group: dict, owner: str = 'builtin', no_save: bool = False) -> typing.List[Component]: + + def load_blueprint_comp_group( + self, group: dict, owner: str = 'builtin', no_save: bool = False + ) -> typing.List[Component]: """加载蓝图组件组""" components: typing.List[Component] = [] if 'fromFiles' in group: @@ -238,12 +259,18 @@ class ComponentDiscoveryEngine: for dir in group['fromDirs']: path = dir['path'] max_depth = dir['maxDepth'] if 'maxDepth' in dir else 1 - components.extend(self.load_component_manifests_in_dir(path, owner, no_save, max_depth)) + components.extend( + self.load_component_manifests_in_dir( + path, owner, no_save, max_depth + ) + ) return components def discover_blueprint(self, blueprint_manifest_path: str, owner: str = 'builtin'): """发现蓝图""" - blueprint_manifest = self.load_component_manifest(blueprint_manifest_path, owner, no_save=True) + blueprint_manifest = self.load_component_manifest( + blueprint_manifest_path, owner, no_save=True + ) if blueprint_manifest is None: raise ValueError(f'Invalid blueprint manifest: {blueprint_manifest_path}') assert blueprint_manifest.kind == 'Blueprint', '`Kind` must be `Blueprint`' @@ -251,13 +278,15 @@ class ComponentDiscoveryEngine: # load ComponentTemplate first if 'ComponentTemplate' in blueprint_manifest.spec['components']: - components['ComponentTemplate'] = self.load_blueprint_comp_group(blueprint_manifest.spec['components']['ComponentTemplate'], owner) + components['ComponentTemplate'] = self.load_blueprint_comp_group( + blueprint_manifest.spec['components']['ComponentTemplate'], owner + ) for name, component in blueprint_manifest.spec['components'].items(): if name == 'ComponentTemplate': continue components[name] = self.load_blueprint_comp_group(component, owner) - + self.ap.logger.debug(f'Components: {components}') return blueprint_manifest, components @@ -268,7 +297,9 @@ class ComponentDiscoveryEngine: return [] return self.components[kind] - def find_components(self, kind: str, component_list: typing.List[Component]) -> typing.List[Component]: + def find_components( + self, kind: str, component_list: typing.List[Component] + ) -> typing.List[Component]: """查找组件""" result: typing.List[Component] = [] for component in component_list: diff --git a/pkg/entity/persistence/base.py b/pkg/entity/persistence/base.py index 9d9ea759..b0d8b5db 100644 --- a/pkg/entity/persistence/base.py +++ b/pkg/entity/persistence/base.py @@ -1,5 +1,4 @@ import sqlalchemy.orm -import pydantic class Base(sqlalchemy.orm.DeclarativeBase): diff --git a/pkg/entity/persistence/bot.py b/pkg/entity/persistence/bot.py index 0cd7bce7..86932cac 100644 --- a/pkg/entity/persistence/bot.py +++ b/pkg/entity/persistence/bot.py @@ -5,6 +5,7 @@ from .base import Base class Bot(Base): """机器人""" + __tablename__ = 'bots' uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) @@ -15,5 +16,12 @@ class Bot(Base): enable = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False) use_pipeline_name = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) use_pipeline_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=True) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) + created_at = sqlalchemy.Column( + sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now() + ) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) diff --git a/pkg/entity/persistence/metadata.py b/pkg/entity/persistence/metadata.py index e1ebaefd..d9e03663 100644 --- a/pkg/entity/persistence/metadata.py +++ b/pkg/entity/persistence/metadata.py @@ -13,6 +13,7 @@ initial_metadata = [ class Metadata(Base): """数据库元数据""" + __tablename__ = 'metadata' key = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True) diff --git a/pkg/entity/persistence/model.py b/pkg/entity/persistence/model.py index 13700f25..65e016f3 100644 --- a/pkg/entity/persistence/model.py +++ b/pkg/entity/persistence/model.py @@ -1,11 +1,11 @@ import sqlalchemy -import datetime from .base import Base class LLMModel(Base): """LLM 模型""" + __tablename__ = 'llm_models' uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) @@ -16,5 +16,12 @@ class LLMModel(Base): api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) abilities = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[]) extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={}) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) \ No newline at end of file + created_at = sqlalchemy.Column( + sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now() + ) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) diff --git a/pkg/entity/persistence/pipeline.py b/pkg/entity/persistence/pipeline.py index aaa393a7..ca854203 100644 --- a/pkg/entity/persistence/pipeline.py +++ b/pkg/entity/persistence/pipeline.py @@ -5,13 +5,21 @@ from .base import Base class LegacyPipeline(Base): """旧版流水线""" + __tablename__ = 'legacy_pipelines' uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) + created_at = sqlalchemy.Column( + sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now() + ) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) for_version = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) is_default = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False) @@ -21,13 +29,21 @@ class LegacyPipeline(Base): class PipelineRunRecord(Base): """流水线运行记录""" + __tablename__ = 'pipeline_run_records' uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True) pipeline_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) status = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) + created_at = sqlalchemy.Column( + sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now() + ) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) started_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False) finished_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False) result = sqlalchemy.Column(sqlalchemy.JSON, nullable=False) diff --git a/pkg/entity/persistence/plugin.py b/pkg/entity/persistence/plugin.py index b1e2cac4..94d6b8b4 100644 --- a/pkg/entity/persistence/plugin.py +++ b/pkg/entity/persistence/plugin.py @@ -5,6 +5,7 @@ from .base import Base class PluginSetting(Base): """插件配置""" + __tablename__ = 'plugin_settings' plugin_author = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True) @@ -12,5 +13,12 @@ class PluginSetting(Base): enabled = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=True) priority = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0) config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=dict) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) + created_at = sqlalchemy.Column( + sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now() + ) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) diff --git a/pkg/entity/persistence/user.py b/pkg/entity/persistence/user.py index 23d309c4..a0d9f168 100644 --- a/pkg/entity/persistence/user.py +++ b/pkg/entity/persistence/user.py @@ -1,5 +1,4 @@ import sqlalchemy -import sqlmodel from .base import Base @@ -10,5 +9,12 @@ class User(Base): id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) user = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) password = sqlalchemy.Column(sqlalchemy.String(255), nullable=False) - created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()) - updated_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now(), onupdate=sqlalchemy.func.now()) \ No newline at end of file + created_at = sqlalchemy.Column( + sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now() + ) + updated_at = sqlalchemy.Column( + sqlalchemy.DateTime, + nullable=False, + server_default=sqlalchemy.func.now(), + onupdate=sqlalchemy.func.now(), + ) diff --git a/pkg/persistence/database.py b/pkg/persistence/database.py index 0dd82817..528c6a34 100644 --- a/pkg/persistence/database.py +++ b/pkg/persistence/database.py @@ -9,6 +9,7 @@ from ..core import app preregistered_managers: list[type[BaseDatabaseManager]] = [] + def manager_class(name: str) -> None: """注册一个数据库管理类""" diff --git a/pkg/persistence/databases/sqlite.py b/pkg/persistence/databases/sqlite.py index 0bc3db32..1b12def8 100644 --- a/pkg/persistence/databases/sqlite.py +++ b/pkg/persistence/databases/sqlite.py @@ -5,10 +5,12 @@ import sqlalchemy.ext.asyncio as sqlalchemy_asyncio from .. import database -@database.manager_class("sqlite") +@database.manager_class('sqlite') class SQLiteDatabaseManager(database.BaseDatabaseManager): """SQLite 数据库管理类""" - + async def initialize(self) -> None: sqlite_path = 'data/langbot.db' - self.engine = sqlalchemy_asyncio.create_async_engine(f"sqlite+aiosqlite:///{sqlite_path}") + self.engine = sqlalchemy_asyncio.create_async_engine( + f'sqlite+aiosqlite:///{sqlite_path}' + ) diff --git a/pkg/persistence/mgr.py b/pkg/persistence/mgr.py index f17b169e..e8f953ab 100644 --- a/pkg/persistence/mgr.py +++ b/pkg/persistence/mgr.py @@ -1,6 +1,5 @@ from __future__ import annotations -import asyncio import datetime import typing import json @@ -10,12 +9,16 @@ import sqlalchemy.ext.asyncio as sqlalchemy_asyncio import sqlalchemy from . import database, migration -from ..entity.persistence import base, user, model, pipeline, bot, plugin, metadata +from ..entity.persistence import base, pipeline, metadata +from ..entity import persistence from ..core import app -from .databases import sqlite -from ..utils import constants -from .migrations import dbm001_migrate_v3_config +from ..utils import constants, importutil from ..api.http.service import pipeline as pipeline_service +from . import databases, migrations + +importutil.import_modules_in_pkg(databases) +importutil.import_modules_in_pkg(migrations) +importutil.import_modules_in_pkg(persistence) class PersistenceManager: @@ -33,9 +36,8 @@ class PersistenceManager: self.meta = base.Base.metadata async def initialize(self): + self.ap.logger.info('Initializing database...') - self.ap.logger.info("Initializing database...") - for manager in database.preregistered_managers: self.db = manager(self.ap) await self.db.initialize() @@ -43,7 +45,6 @@ class PersistenceManager: await self.create_tables() async def create_tables(self): - # create tables async with self.get_db_engine().connect() as conn: await conn.run_sync(self.meta.create_all) @@ -53,26 +54,28 @@ class PersistenceManager: # ======= write initial data ======= # write initial metadata - self.ap.logger.info("Creating initial metadata...") + self.ap.logger.info('Creating initial metadata...') for item in metadata.initial_metadata: # check if the item exists result = await self.execute_async( - sqlalchemy.select(metadata.Metadata).where(metadata.Metadata.key == item['key']) + sqlalchemy.select(metadata.Metadata).where( + metadata.Metadata.key == item['key'] + ) ) row = result.first() if row is None: await self.execute_async( sqlalchemy.insert(metadata.Metadata).values(item) ) - - # write default pipeline - result = await self.execute_async( - sqlalchemy.select(pipeline.LegacyPipeline) - ) - if result.first() is None: - self.ap.logger.info("Creating default pipeline...") - pipeline_config = json.load(open('templates/default-pipeline-config.json', 'r', encoding='utf-8')) + # write default pipeline + result = await self.execute_async(sqlalchemy.select(pipeline.LegacyPipeline)) + if result.first() is None: + self.ap.logger.info('Creating default pipeline...') + + pipeline_config = json.load( + open('templates/default-pipeline-config.json', 'r', encoding='utf-8') + ) pipeline_data = { 'uuid': str(uuid.uuid4()), @@ -91,7 +94,9 @@ class PersistenceManager: # run migrations database_version = await self.execute_async( - sqlalchemy.select(metadata.Metadata).where(metadata.Metadata.key == 'database_version') + sqlalchemy.select(metadata.Metadata).where( + metadata.Metadata.key == 'database_version' + ) ) database_version = int(database_version.fetchone()[1]) @@ -106,24 +111,27 @@ class PersistenceManager: for migration_cls in migrations: migration_instance = migration_cls(self.ap) - if migration_instance.number > database_version and migration_instance.number <= required_database_version: + if ( + migration_instance.number > database_version + and migration_instance.number <= required_database_version + ): await migration_instance.upgrade() await self.execute_async( - sqlalchemy.update(metadata.Metadata).where(metadata.Metadata.key == 'database_version').values( - { - 'value': str(migration_instance.number) - } - ) + sqlalchemy.update(metadata.Metadata) + .where(metadata.Metadata.key == 'database_version') + .values({'value': str(migration_instance.number)}) ) last_migration_number = migration_instance.number - self.ap.logger.info(f'Migration {migration_instance.number} completed.') - - self.ap.logger.info(f'Successfully upgraded database to version {last_migration_number}.') + self.ap.logger.info( + f'Migration {migration_instance.number} completed.' + ) + + self.ap.logger.info( + f'Successfully upgraded database to version {last_migration_number}.' + ) async def execute_async( - self, - *args, - **kwargs + self, *args, **kwargs ) -> sqlalchemy.engine.cursor.CursorResult: async with self.get_db_engine().connect() as conn: result = await conn.execute(*args, **kwargs) @@ -132,9 +140,13 @@ class PersistenceManager: def get_db_engine(self) -> sqlalchemy_asyncio.AsyncEngine: return self.db.get_engine() - - def serialize_model(self, model: typing.Type[sqlalchemy.Base], data: sqlalchemy.Base) -> dict: + + def serialize_model( + self, model: typing.Type[sqlalchemy.Base], data: sqlalchemy.Base + ) -> dict: return { - column.name: getattr(data, column.name) if not isinstance(getattr(data, column.name), (datetime.datetime)) else getattr(data, column.name).isoformat() + column.name: getattr(data, column.name) + if not isinstance(getattr(data, column.name), (datetime.datetime)) + else getattr(data, column.name).isoformat() for column in model.__table__.columns } diff --git a/pkg/persistence/migration.py b/pkg/persistence/migration.py index 81a3aac3..c191b686 100644 --- a/pkg/persistence/migration.py +++ b/pkg/persistence/migration.py @@ -8,6 +8,7 @@ from ..core import app preregistered_db_migrations: list[typing.Type[DBMigration]] = [] + def migration_class(number: int): """迁移类装饰器""" @@ -15,6 +16,7 @@ def migration_class(number: int): cls.number = number preregistered_db_migrations.append(cls) return cls + return wrapper diff --git a/pkg/persistence/migrations/dbm001_migrate_v3_config.py b/pkg/persistence/migrations/dbm001_migrate_v3_config.py index afed5eea..6aee2854 100644 --- a/pkg/persistence/migrations/dbm001_migrate_v3_config.py +++ b/pkg/persistence/migrations/dbm001_migrate_v3_config.py @@ -1,5 +1,3 @@ -from .. import migration - # TODO fill this # @migration.migration_class(1) # class DBMigrationV3(migration.DBMigration): @@ -10,4 +8,4 @@ from .. import migration # pass # async def downgrade(self): -# """降级""" \ No newline at end of file +# """降级""" diff --git a/pkg/pipeline/bansess/bansess.py b/pkg/pipeline/bansess/bansess.py index 38fb9794..dad6a3ab 100644 --- a/pkg/pipeline/bansess/bansess.py +++ b/pkg/pipeline/bansess/bansess.py @@ -1,15 +1,13 @@ from __future__ import annotations -import re from .. import stage, entities from ...core import entities as core_entities -from ...config import manager as cfg_mgr @stage.stage_class('BanSessionCheckStage') class BanSessionCheckStage(stage.PipelineStage): """访问控制处理阶段 - + 仅检查query中群号或个人号是否在访问控制列表中。 """ @@ -17,26 +15,24 @@ class BanSessionCheckStage(stage.PipelineStage): pass async def process( - self, - query: core_entities.Query, - stage_inst_name: str + self, query: core_entities.Query, stage_inst_name: str ) -> entities.StageProcessResult: - found = False mode = query.pipeline_config['trigger']['access-control']['mode'] sess_list = query.pipeline_config['trigger']['access-control'][mode] - if (query.launcher_type.value == 'group' and 'group_*' in sess_list) \ - or (query.launcher_type.value == 'person' and 'person_*' in sess_list): + if (query.launcher_type.value == 'group' and 'group_*' in sess_list) or ( + query.launcher_type.value == 'person' and 'person_*' in sess_list + ): found = True else: for sess in sess_list: - if sess == f"{query.launcher_type.value}_{query.launcher_id}": + if sess == f'{query.launcher_type.value}_{query.launcher_id}': found = True break - + ctn = False if mode == 'whitelist': @@ -45,7 +41,11 @@ class BanSessionCheckStage(stage.PipelineStage): ctn = not found return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE if ctn else entities.ResultType.INTERRUPT, + result_type=entities.ResultType.CONTINUE + if ctn + else entities.ResultType.INTERRUPT, new_query=query, - console_notice=f'根据访问控制忽略消息: {query.launcher_type.value}_{query.launcher_id}' if not ctn else '' + console_notice=f'根据访问控制忽略消息: {query.launcher_type.value}_{query.launcher_id}' + if not ctn + else '', ) diff --git a/pkg/pipeline/cntfilter/cntfilter.py b/pkg/pipeline/cntfilter/cntfilter.py index dbf7c52e..6547cb16 100644 --- a/pkg/pipeline/cntfilter/cntfilter.py +++ b/pkg/pipeline/cntfilter/cntfilter.py @@ -4,20 +4,21 @@ from ...core import app from .. import stage, entities from ...core import entities as core_entities -from ...config import manager as cfg_mgr from . import filter as filter_model, entities as filter_entities -from .filters import cntignore, banwords, baiduexamine from ...provider import entities as llm_entities from ...platform.types import message as platform_message -from ...platform.types import events as platform_events -from ...platform.types import entities as platform_entities +from ...utils import importutil + +from . import filters + +importutil.import_modules_in_pkg(filters) @stage.stage_class('PostContentFilterStage') @stage.stage_class('PreContentFilterStage') class ContentFilterStage(stage.PipelineStage): """内容过滤阶段 - + 前置: 检查消息是否符合规则,不符合则拦截。 改写: @@ -36,13 +37,12 @@ class ContentFilterStage(stage.PipelineStage): super().__init__(ap) async def initialize(self, pipeline_config: dict): - filters_required = [ - "content-ignore", + 'content-ignore', ] if pipeline_config['safety']['content-filter']['check-sensitive-words']: - filters_required.append("ban-word-filter") + filters_required.append('ban-word-filter') # TODO revert it # if self.ap.pipeline_cfg.data['baidu-cloud-examine']['enable']: @@ -50,9 +50,7 @@ class ContentFilterStage(stage.PipelineStage): for filter in filter_model.preregistered_filters: if filter.name in filters_required: - self.filter_chain.append( - filter(self.ap) - ) + self.filter_chain.append(filter(self.ap)) for filter in self.filter_chain: await filter.initialize() @@ -68,8 +66,7 @@ class ContentFilterStage(stage.PipelineStage): if query.pipeline_config['safety']['content-filter']['scope'] == 'output-msg': return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: for filter in self.filter_chain: @@ -78,26 +75,25 @@ class ContentFilterStage(stage.PipelineStage): if result.level in [ filter_entities.ResultLevel.BLOCK, - filter_entities.ResultLevel.MASKED + filter_entities.ResultLevel.MASKED, ]: return entities.StageProcessResult( result_type=entities.ResultType.INTERRUPT, new_query=query, user_notice=result.user_notice, - console_notice=result.console_notice + console_notice=result.console_notice, ) elif result.level == filter_entities.ResultLevel.PASS: # 传到下一个 message = result.replacement - + query.message_chain = platform_message.MessageChain( platform_message.Plain(message) ) return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) - + async def _post_process( self, message: str, @@ -108,8 +104,7 @@ class ContentFilterStage(stage.PipelineStage): """ if query.pipeline_config['safety']['content-filter']['scope'] == 'income-msg': return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: message = message.strip() @@ -122,30 +117,25 @@ class ContentFilterStage(stage.PipelineStage): result_type=entities.ResultType.INTERRUPT, new_query=query, user_notice=result.user_notice, - console_notice=result.console_notice + console_notice=result.console_notice, ) elif result.level in [ filter_entities.ResultLevel.PASS, - filter_entities.ResultLevel.MASKED + filter_entities.ResultLevel.MASKED, ]: message = result.replacement query.resp_messages[-1].content = message return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) async def process( - self, - query: core_entities.Query, - stage_inst_name: str + self, query: core_entities.Query, stage_inst_name: str ) -> entities.StageProcessResult: - """处理 - """ + """处理""" if stage_inst_name == 'PreContentFilterStage': - contain_non_text = False text_components = [platform_message.Plain, platform_message.Source] @@ -156,28 +146,24 @@ class ContentFilterStage(stage.PipelineStage): break if contain_non_text: - self.ap.logger.debug(f"消息中包含非文本消息,跳过内容过滤器检查。") + self.ap.logger.debug('消息中包含非文本消息,跳过内容过滤器检查。') return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) - return await self._pre_process( - str(query.message_chain).strip(), - query - ) + return await self._pre_process(str(query.message_chain).strip(), query) elif stage_inst_name == 'PostContentFilterStage': # 仅处理 query.resp_messages[-1].content 是 str 的情况 - if isinstance(query.resp_messages[-1], llm_entities.Message) and isinstance(query.resp_messages[-1].content, str): - return await self._post_process( - query.resp_messages[-1].content, - query - ) + if isinstance(query.resp_messages[-1], llm_entities.Message) and isinstance( + query.resp_messages[-1].content, str + ): + return await self._post_process(query.resp_messages[-1].content, query) else: - self.ap.logger.debug(f"resp_messages[-1] 不是 Message 类型或 query.resp_messages[-1].content 不是 str 类型,跳过内容过滤器检查。") + self.ap.logger.debug( + 'resp_messages[-1] 不是 Message 类型或 query.resp_messages[-1].content 不是 str 类型,跳过内容过滤器检查。' + ) return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: raise ValueError(f'未知的 stage_inst_name: {stage_inst_name}') diff --git a/pkg/pipeline/cntfilter/entities.py b/pkg/pipeline/cntfilter/entities.py index b4bc0f7e..5e804c0d 100644 --- a/pkg/pipeline/cntfilter/entities.py +++ b/pkg/pipeline/cntfilter/entities.py @@ -1,14 +1,11 @@ - -import typing import enum import pydantic.v1 as pydantic -from ...provider import entities as llm_entities - class ResultLevel(enum.Enum): """结果等级""" + PASS = enum.auto() """通过""" @@ -24,6 +21,7 @@ class ResultLevel(enum.Enum): class EnableStage(enum.Enum): """启用阶段""" + PRE = enum.auto() """预处理""" @@ -55,14 +53,15 @@ class FilterResult(pydantic.BaseModel): class ManagerResultLevel(enum.Enum): """处理器结果等级""" + CONTINUE = enum.auto() """继续""" INTERRUPT = enum.auto() """中断""" -class FilterManagerResult(pydantic.BaseModel): +class FilterManagerResult(pydantic.BaseModel): level: ManagerResultLevel replacement: str diff --git a/pkg/pipeline/cntfilter/filter.py b/pkg/pipeline/cntfilter/filter.py index 970e11f1..ae7ceb79 100644 --- a/pkg/pipeline/cntfilter/filter.py +++ b/pkg/pipeline/cntfilter/filter.py @@ -5,14 +5,13 @@ import typing from ...core import app, entities as core_entities from . import entities -from ...provider import entities as llm_entities preregistered_filters: list[typing.Type[ContentFilter]] = [] def filter_class( - name: str + name: str, ) -> typing.Callable[[typing.Type[ContentFilter]], typing.Type[ContentFilter]]: """内容过滤器类装饰器 @@ -22,6 +21,7 @@ def filter_class( Returns: typing.Callable[[typing.Type[ContentFilter]], typing.Type[ContentFilter]]: 装饰器 """ + def decorator(cls: typing.Type[ContentFilter]) -> typing.Type[ContentFilter]: assert issubclass(cls, ContentFilter) @@ -53,23 +53,21 @@ class ContentFilter(metaclass=abc.ABCMeta): entity.EnableStage.PRE: 消息请求AI前,此时需要检查的内容是用户的输入消息。 entity.EnableStage.POST: 消息请求AI后,此时需要检查的内容是AI的回复消息。 """ - return [ - entities.EnableStage.PRE, - entities.EnableStage.POST - ] + return [entities.EnableStage.PRE, entities.EnableStage.POST] async def initialize(self): - """初始化过滤器 - """ + """初始化过滤器""" pass @abc.abstractmethod - async def process(self, query: core_entities.Query, message: str=None, image_url=None) -> entities.FilterResult: + async def process( + self, query: core_entities.Query, message: str = None, image_url=None + ) -> entities.FilterResult: """处理消息 分为前后阶段,具体取决于 enable_stages 的值。 对于内容过滤器来说,不需要考虑消息所处的阶段,只需要检查消息内容即可。 - + Args: message (str): 需要检查的内容 image_url (str): 要检查的图片的 URL diff --git a/pkg/pipeline/cntfilter/filters/baiduexamine.py b/pkg/pipeline/cntfilter/filters/baiduexamine.py index 800f0099..c3776bc9 100644 --- a/pkg/pipeline/cntfilter/filters/baiduexamine.py +++ b/pkg/pipeline/cntfilter/filters/baiduexamine.py @@ -7,11 +7,11 @@ from .. import filter as filter_model from ....core import entities as core_entities -BAIDU_EXAMINE_URL = "https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={}" -BAIDU_EXAMINE_TOKEN_URL = "https://aip.baidubce.com/oauth/2.0/token" +BAIDU_EXAMINE_URL = 'https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={}' +BAIDU_EXAMINE_TOKEN_URL = 'https://aip.baidubce.com/oauth/2.0/token' -@filter_model.filter_class("baidu-cloud-examine") +@filter_model.filter_class('baidu-cloud-examine') class BaiduCloudExamine(filter_model.ContentFilter): """百度云内容审核""" @@ -20,44 +20,52 @@ class BaiduCloudExamine(filter_model.ContentFilter): async with session.post( BAIDU_EXAMINE_TOKEN_URL, params={ - "grant_type": "client_credentials", - "client_id": self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-key'], - "client_secret": self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-secret'] - } + 'grant_type': 'client_credentials', + 'client_id': self.ap.pipeline_cfg.data['baidu-cloud-examine'][ + 'api-key' + ], + 'client_secret': self.ap.pipeline_cfg.data['baidu-cloud-examine'][ + 'api-secret' + ], + }, ) as resp: return (await resp.json())['access_token'] - async def process(self, query: core_entities.Query, message: str) -> entities.FilterResult: - + async def process( + self, query: core_entities.Query, message: str + ) -> entities.FilterResult: async with aiohttp.ClientSession() as session: async with session.post( BAIDU_EXAMINE_URL.format(await self._get_token()), - headers={'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'}, - data=f"text={message}".encode('utf-8') + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json', + }, + data=f'text={message}'.encode('utf-8'), ) as resp: result = await resp.json() - if "error_code" in result: + if 'error_code' in result: return entities.FilterResult( level=entities.ResultLevel.BLOCK, replacement=message, user_notice='', - console_notice=f"百度云判定出错,错误信息:{result['error_msg']}" + console_notice=f'百度云判定出错,错误信息:{result["error_msg"]}', ) else: - conclusion = result["conclusion"] + conclusion = result['conclusion'] - if conclusion in ("合规"): + if conclusion in ('合规'): return entities.FilterResult( level=entities.ResultLevel.PASS, replacement=message, user_notice='', - console_notice=f"百度云判定结果:{conclusion}" + console_notice=f'百度云判定结果:{conclusion}', ) else: return entities.FilterResult( level=entities.ResultLevel.BLOCK, replacement=message, - user_notice="消息中存在不合适的内容, 请修改", - console_notice=f"百度云判定结果:{conclusion}" + user_notice='消息中存在不合适的内容, 请修改', + console_notice=f'百度云判定结果:{conclusion}', ) diff --git a/pkg/pipeline/cntfilter/filters/banwords.py b/pkg/pipeline/cntfilter/filters/banwords.py index cd3d412c..598fa299 100644 --- a/pkg/pipeline/cntfilter/filters/banwords.py +++ b/pkg/pipeline/cntfilter/filters/banwords.py @@ -6,14 +6,16 @@ from .. import entities from ....core import entities as core_entities -@filter_model.filter_class("ban-word-filter") +@filter_model.filter_class('ban-word-filter') class BanWordFilter(filter_model.ContentFilter): """根据内容过滤""" async def initialize(self): pass - async def process(self, query: core_entities.Query, message: str) -> entities.FilterResult: + async def process( + self, query: core_entities.Query, message: str + ) -> entities.FilterResult: found = False for word in self.ap.sensitive_meta.data['words']: @@ -23,9 +25,10 @@ class BanWordFilter(filter_model.ContentFilter): found = True for i in range(len(match)): - if self.ap.sensitive_meta.data['mask_word'] == "": + if self.ap.sensitive_meta.data['mask_word'] == '': message = message.replace( - match[i], self.ap.sensitive_meta.data['mask'] * len(match[i]) + match[i], + self.ap.sensitive_meta.data['mask'] * len(match[i]), ) else: message = message.replace( @@ -36,5 +39,5 @@ class BanWordFilter(filter_model.ContentFilter): level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS, replacement=message, user_notice='消息中存在不合适的内容, 请修改' if found else '', - console_notice='' - ) \ No newline at end of file + console_notice='', + ) diff --git a/pkg/pipeline/cntfilter/filters/cntignore.py b/pkg/pipeline/cntfilter/filters/cntignore.py index 381d5c51..cb563593 100644 --- a/pkg/pipeline/cntfilter/filters/cntignore.py +++ b/pkg/pipeline/cntfilter/filters/cntignore.py @@ -6,7 +6,7 @@ from .. import filter as filter_model from ....core import entities as core_entities -@filter_model.filter_class("content-ignore") +@filter_model.filter_class('content-ignore') class ContentIgnore(filter_model.ContentFilter): """根据内容忽略消息""" @@ -16,7 +16,9 @@ class ContentIgnore(filter_model.ContentFilter): entities.EnableStage.PRE, ] - async def process(self, query: core_entities.Query, message: str) -> entities.FilterResult: + async def process( + self, query: core_entities.Query, message: str + ) -> entities.FilterResult: if 'prefix' in query.pipeline_config['trigger']['ignore-rules']: for rule in query.pipeline_config['trigger']['ignore-rules']['prefix']: if message.startswith(rule): @@ -24,9 +26,9 @@ class ContentIgnore(filter_model.ContentFilter): level=entities.ResultLevel.BLOCK, replacement='', user_notice='', - console_notice='根据 ignore_rules 中的 prefix 规则,忽略消息' + console_notice='根据 ignore_rules 中的 prefix 规则,忽略消息', ) - + if 'regexp' in query.pipeline_config['trigger']['ignore-rules']: for rule in query.pipeline_config['trigger']['ignore-rules']['regexp']: if re.search(rule, message): @@ -34,12 +36,12 @@ class ContentIgnore(filter_model.ContentFilter): level=entities.ResultLevel.BLOCK, replacement='', user_notice='', - console_notice='根据 ignore_rules 中的 regexp 规则,忽略消息' + console_notice='根据 ignore_rules 中的 regexp 规则,忽略消息', ) return entities.FilterResult( level=entities.ResultLevel.PASS, replacement=message, user_notice='', - console_notice='' - ) \ No newline at end of file + console_notice='', + ) diff --git a/pkg/pipeline/controller.py b/pkg/pipeline/controller.py index a7cf2153..2ad1690f 100644 --- a/pkg/pipeline/controller.py +++ b/pkg/pipeline/controller.py @@ -1,18 +1,14 @@ from __future__ import annotations import asyncio -import typing import traceback from ..core import app, entities -from . import entities as pipeline_entities -from ..plugin import events -from ..platform.types import message as platform_message class Controller: - """总控制器 - """ + """总控制器""" + ap: app.Application semaphore: asyncio.Semaphore = None @@ -20,11 +16,12 @@ class Controller: def __init__(self, ap: app.Application): self.ap = ap - self.semaphore = asyncio.Semaphore(self.ap.instance_config.data['concurrency']['pipeline']) + self.semaphore = asyncio.Semaphore( + self.ap.instance_config.data['concurrency']['pipeline'] + ) async def consumer(self): - """事件处理循环 - """ + """事件处理循环""" try: while True: selected_query: entities.Query = None @@ -35,7 +32,9 @@ class Controller: for query in queries: session = await self.ap.sess_mgr.get_session(query) - self.ap.logger.debug(f"Checking query {query} session {session}") + self.ap.logger.debug( + f'Checking query {query} session {session}' + ) if not session.semaphore.locked(): selected_query = query @@ -56,30 +55,40 @@ class Controller: # find pipeline # Here firstly find the bot, then find the pipeline, in case the bot adapter's config is not the latest one. # Like aiocqhttp, once a client is connected, even the adapter was updated and restarted, the existing client connection will not be affected. - bot = await self.ap.platform_mgr.get_bot_by_uuid(selected_query.bot_uuid) + bot = await self.ap.platform_mgr.get_bot_by_uuid( + selected_query.bot_uuid + ) if bot: - pipeline = await self.ap.pipeline_mgr.get_pipeline_by_uuid(bot.bot_entity.use_pipeline_uuid) + pipeline = ( + await self.ap.pipeline_mgr.get_pipeline_by_uuid( + bot.bot_entity.use_pipeline_uuid + ) + ) if pipeline: await pipeline.run(selected_query) - + async with self.ap.query_pool: - (await self.ap.sess_mgr.get_session(selected_query)).semaphore.release() + ( + await self.ap.sess_mgr.get_session(selected_query) + ).semaphore.release() # 通知其他协程,有新的请求可以处理了 self.ap.query_pool.condition.notify_all() self.ap.task_mgr.create_task( _process_query(selected_query), - kind="query", - name=f"query-{selected_query.query_id}", - scopes=[entities.LifecycleControlScope.APPLICATION, entities.LifecycleControlScope.PLATFORM], + kind='query', + name=f'query-{selected_query.query_id}', + scopes=[ + entities.LifecycleControlScope.APPLICATION, + entities.LifecycleControlScope.PLATFORM, + ], ) except Exception as e: # traceback.print_exc() - self.ap.logger.error(f"控制器循环出错: {e}") - self.ap.logger.error(f"Traceback: {traceback.format_exc()}") + self.ap.logger.error(f'控制器循环出错: {e}') + self.ap.logger.error(f'Traceback: {traceback.format_exc()}') async def run(self): - """运行控制器 - """ + """运行控制器""" await self.consumer() diff --git a/pkg/pipeline/entities.py b/pkg/pipeline/entities.py index ffcc4654..dd6434c0 100644 --- a/pkg/pipeline/entities.py +++ b/pkg/pipeline/entities.py @@ -10,7 +10,6 @@ from ..core import entities class ResultType(enum.Enum): - CONTINUE = enum.auto() """继续流水线""" @@ -19,12 +18,18 @@ class ResultType(enum.Enum): class StageProcessResult(pydantic.BaseModel): - result_type: ResultType new_query: entities.Query - user_notice: typing.Optional[typing.Union[str, list[platform_message.MessageComponent], platform_message.MessageChain, None]] = [] + user_notice: typing.Optional[ + typing.Union[ + str, + list[platform_message.MessageComponent], + platform_message.MessageChain, + None, + ] + ] = [] """只要设置了就会发送给用户""" console_notice: typing.Optional[str] = '' diff --git a/pkg/pipeline/longtext/longtext.py b/pkg/pipeline/longtext/longtext.py index ac03ad42..ab20f3eb 100644 --- a/pkg/pipeline/longtext/longtext.py +++ b/pkg/pipeline/longtext/longtext.py @@ -2,18 +2,19 @@ from __future__ import annotations import os import traceback -from PIL import Image, ImageDraw, ImageFont -from ...core import app from . import strategy -from .strategies import image, forward from .. import stage, entities from ...core import entities as core_entities -from ...config import manager as cfg_mgr from ...platform.types import message as platform_message +from ...utils import importutil + +from . import strategies + +importutil.import_modules_in_pkg(strategies) -@stage.stage_class("LongTextProcessStage") +@stage.stage_class('LongTextProcessStage') class LongTextProcessStage(stage.PipelineStage): """长消息处理阶段 @@ -31,34 +32,48 @@ class LongTextProcessStage(stage.PipelineStage): # 检查是否存在 if not os.path.exists(use_font): # 若是windows系统,使用微软雅黑 - if os.name == "nt": - use_font = "C:/Windows/Fonts/msyh.ttc" + if os.name == 'nt': + use_font = 'C:/Windows/Fonts/msyh.ttc' if not os.path.exists(use_font): - self.ap.logger.warn("未找到字体文件,且无法使用Windows自带字体,更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。") - config['blob_message_strategy'] = "forward" + self.ap.logger.warn( + '未找到字体文件,且无法使用Windows自带字体,更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。' + ) + config['blob_message_strategy'] = 'forward' else: - self.ap.logger.info("使用Windows自带字体:" + use_font) + self.ap.logger.info('使用Windows自带字体:' + use_font) config['font-path'] = use_font else: - self.ap.logger.warn("未找到字体文件,且无法使用系统自带字体,更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。") + self.ap.logger.warn( + '未找到字体文件,且无法使用系统自带字体,更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。' + ) - pipeline_config['output']['long-text-processing']['strategy'] = "forward" - except: + pipeline_config['output']['long-text-processing'][ + 'strategy' + ] = 'forward' + except Exception: traceback.print_exc() - self.ap.logger.error("加载字体文件失败({}),更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。".format(use_font)) + self.ap.logger.error( + '加载字体文件失败({}),更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。'.format( + use_font + ) + ) - pipeline_config['output']['long-text-processing']['strategy'] = "forward" + pipeline_config['output']['long-text-processing']['strategy'] = ( + 'forward' + ) for strategy_cls in strategy.preregistered_strategies: if strategy_cls.name == config['strategy']: self.strategy_impl = strategy_cls(self.ap) break else: - raise ValueError(f"未找到名为 {config['strategy']} 的长消息处理策略") + raise ValueError(f'未找到名为 {config["strategy"]} 的长消息处理策略') await self.strategy_impl.initialize() - - async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult: + + async def process( + self, query: core_entities.Query, stage_inst_name: str + ) -> entities.StageProcessResult: # 检查是否包含非 Plain 组件 contains_non_plain = False @@ -66,13 +81,19 @@ class LongTextProcessStage(stage.PipelineStage): if not isinstance(msg, platform_message.Plain): contains_non_plain = True break - + if contains_non_plain: - self.ap.logger.debug("消息中包含非 Plain 组件,跳过长消息处理。") - elif len(str(query.resp_message_chain[-1])) > query.pipeline_config['output']['long-text-processing']['threshold']: - query.resp_message_chain[-1] = platform_message.MessageChain(await self.strategy_impl.process(str(query.resp_message_chain[-1]), query)) + self.ap.logger.debug('消息中包含非 Plain 组件,跳过长消息处理。') + elif ( + len(str(query.resp_message_chain[-1])) + > query.pipeline_config['output']['long-text-processing']['threshold'] + ): + query.resp_message_chain[-1] = platform_message.MessageChain( + await self.strategy_impl.process( + str(query.resp_message_chain[-1]), query + ) + ) return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) diff --git a/pkg/pipeline/longtext/strategies/forward.py b/pkg/pipeline/longtext/strategies/forward.py index 7abb9c6e..57084d76 100644 --- a/pkg/pipeline/longtext/strategies/forward.py +++ b/pkg/pipeline/longtext/strategies/forward.py @@ -1,8 +1,6 @@ # 转发消息组件 from __future__ import annotations -import typing -import pydantic.v1 as pydantic from .. import strategy as strategy_model from ....core import entities as core_entities @@ -13,29 +11,27 @@ ForwardMessageDiaplay = platform_message.ForwardMessageDiaplay Forward = platform_message.Forward -@strategy_model.strategy_class("forward") +@strategy_model.strategy_class('forward') class ForwardComponentStrategy(strategy_model.LongTextStrategy): - - async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]: + async def process( + self, message: str, query: core_entities.Query + ) -> list[platform_message.MessageComponent]: display = ForwardMessageDiaplay( - title="群聊的聊天记录", - brief="[聊天记录]", - source="聊天记录", - preview=["QQ用户: "+message], - summary="查看1条转发消息" + title='群聊的聊天记录', + brief='[聊天记录]', + source='聊天记录', + preview=['QQ用户: ' + message], + summary='查看1条转发消息', ) node_list = [ platform_message.ForwardMessageNode( sender_id=query.adapter.bot_account_id, sender_name='QQ用户', - message_chain=platform_message.MessageChain([message]) + message_chain=platform_message.MessageChain([message]), ) ] - forward = Forward( - display=display, - node_list=node_list - ) + forward = Forward(display=display, node_list=node_list) return [forward] diff --git a/pkg/pipeline/longtext/strategies/image.py b/pkg/pipeline/longtext/strategies/image.py index b30d3a81..26c4b731 100644 --- a/pkg/pipeline/longtext/strategies/image.py +++ b/pkg/pipeline/longtext/strategies/image.py @@ -1,6 +1,5 @@ from __future__ import annotations -import typing import os import base64 import time @@ -15,26 +14,30 @@ from .. import strategy as strategy_model from ....core import entities as core_entities -@strategy_model.strategy_class("image") +@strategy_model.strategy_class('image') class Text2ImageStrategy(strategy_model.LongTextStrategy): - async def initialize(self): pass @functools.lru_cache(maxsize=16) def get_font(self, query: core_entities.Query): - return ImageFont.truetype(query.pipeline_config['output']['long-text-processing']['font-path'], 32, encoding="utf-8") - - async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]: + return ImageFont.truetype( + query.pipeline_config['output']['long-text-processing']['font-path'], + 32, + encoding='utf-8', + ) + + async def process( + self, message: str, query: core_entities.Query + ) -> list[platform_message.MessageComponent]: img_path = self.text_to_image( text_str=message, save_as='temp/{}.png'.format(int(time.time())), - query=query + query=query, ) compressed_path, size = self.compress_image( - img_path, - outfile="temp/{}_compressed.png".format(int(time.time())) + img_path, outfile='temp/{}_compressed.png'.format(int(time.time())) ) with open(compressed_path, 'rb') as f: @@ -93,13 +96,11 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): resultIndex.append(v) return resultIndex - def get_size(self, file): # 获取文件大小:KB size = os.path.getsize(file) return size / 1024 - def get_outfile(self, infile, outfile): if outfile: return outfile @@ -107,7 +108,6 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): outfile = '{}-out{}'.format(dir, suffix) return outfile - def compress_image(self, infile, outfile='', kb=100, step=20, quality=90): """不改变图片尺寸压缩到指定大小 :param infile: 压缩源文件 @@ -130,24 +130,28 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): o_size = self.get_size(outfile) return outfile, self.get_size(outfile) + def text_to_image( + self, + text_str: str, + save_as='temp.png', + width=800, + query: core_entities.Query = None, + ): + text_str = text_str.replace('\t', ' ') - def text_to_image(self, text_str: str, save_as="temp.png", width=800, query: core_entities.Query = None): - - text_str = text_str.replace("\t", " ") - # 分行 lines = text_str.split('\n') # 计算并分割 final_lines = [] - text_width = width-80 + text_width = width - 80 - self.ap.logger.debug("lines: {}, text_width: {}".format(lines, text_width)) + self.ap.logger.debug('lines: {}, text_width: {}'.format(lines, text_width)) for line in lines: # 如果长了就分割 line_width = self.get_font(query).getlength(line) - self.ap.logger.debug("line_width: {}".format(line_width)) + self.ap.logger.debug('line_width: {}'.format(line_width)) if line_width < text_width: final_lines.append(line) continue @@ -161,7 +165,10 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): numbers = self.indexNumber(rest_text) for number in numbers: - if number[1] < point < number[1] + len(number[0]) and number[1] != 0: + if ( + number[1] < point < number[1] + len(number[0]) + and number[1] != 0 + ): point = number[1] break @@ -174,16 +181,23 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): else: continue # 准备画布 - img = Image.new('RGBA', (width, max(280, len(final_lines) * 35 + 65)), (255, 255, 255, 255)) + img = Image.new( + 'RGBA', (width, max(280, len(final_lines) * 35 + 65)), (255, 255, 255, 255) + ) draw = ImageDraw.Draw(img, mode='RGBA') - self.ap.logger.debug("正在绘制图片...") + self.ap.logger.debug('正在绘制图片...') # 绘制正文 line_number = 0 offset_x = 20 offset_y = 30 for final_line in final_lines: - draw.text((offset_x, offset_y + 35 * line_number), final_line, fill=(0, 0, 0), font=self.text_render_font) + draw.text( + (offset_x, offset_y + 35 * line_number), + final_line, + fill=(0, 0, 0), + font=self.text_render_font, + ) # 遍历此行,检查是否有emoji idx_in_line = 0 for ch in final_line: @@ -196,7 +210,7 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy): line_number += 1 - self.ap.logger.debug("正在保存图片...") + self.ap.logger.debug('正在保存图片...') img.save(save_as) return save_as diff --git a/pkg/pipeline/longtext/strategy.py b/pkg/pipeline/longtext/strategy.py index 6f66bbff..4e141045 100644 --- a/pkg/pipeline/longtext/strategy.py +++ b/pkg/pipeline/longtext/strategy.py @@ -12,7 +12,7 @@ preregistered_strategies: list[typing.Type[LongTextStrategy]] = [] def strategy_class( - name: str + name: str, ) -> typing.Callable[[typing.Type[LongTextStrategy]], typing.Type[LongTextStrategy]]: """长文本处理策略类装饰器 @@ -36,8 +36,7 @@ def strategy_class( class LongTextStrategy(metaclass=abc.ABCMeta): - """长文本处理策略抽象类 - """ + """长文本处理策略抽象类""" name: str @@ -45,12 +44,14 @@ class LongTextStrategy(metaclass=abc.ABCMeta): def __init__(self, ap: app.Application): self.ap = ap - + async def initialize(self): pass - + @abc.abstractmethod - async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]: + async def process( + self, message: str, query: core_entities.Query + ) -> list[platform_message.MessageComponent]: """处理长文本 在 platform.json 中配置 long-text-process 字段,只要 文本长度超过了 threshold 就会调用此方法 diff --git a/pkg/pipeline/msgtrun/msgtrun.py b/pkg/pipeline/msgtrun/msgtrun.py index b3fb593a..2595e289 100644 --- a/pkg/pipeline/msgtrun/msgtrun.py +++ b/pkg/pipeline/msgtrun/msgtrun.py @@ -3,33 +3,38 @@ from __future__ import annotations from .. import stage, entities from ...core import entities as core_entities from . import truncator -from .truncators import round +from ...utils import importutil + +from . import truncators + +importutil.import_modules_in_pkg(truncators) -@stage.stage_class("ConversationMessageTruncator") +@stage.stage_class('ConversationMessageTruncator') class ConversationMessageTruncator(stage.PipelineStage): """会话消息截断器 用于截断会话消息链,以适应平台消息长度限制。 """ + trun: truncator.Truncator async def initialize(self, pipeline_config: dict): - use_method = "round" + use_method = 'round' for trun in truncator.preregistered_truncators: if trun.name == use_method: self.trun = trun(self.ap) break else: - raise ValueError(f"未知的截断器: {use_method}") + raise ValueError(f'未知的截断器: {use_method}') - async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult: - """处理 - """ + async def process( + self, query: core_entities.Query, stage_inst_name: str + ) -> entities.StageProcessResult: + """处理""" query = await self.trun.truncate(query) return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query - ) \ No newline at end of file + result_type=entities.ResultType.CONTINUE, new_query=query + ) diff --git a/pkg/pipeline/msgtrun/truncator.py b/pkg/pipeline/msgtrun/truncator.py index 4afaf9fb..9e8b8a6c 100644 --- a/pkg/pipeline/msgtrun/truncator.py +++ b/pkg/pipeline/msgtrun/truncator.py @@ -10,7 +10,7 @@ preregistered_truncators: list[typing.Type[Truncator]] = [] def truncator_class( - name: str + name: str, ) -> typing.Callable[[typing.Type[Truncator]], typing.Type[Truncator]]: """截断器类装饰器 @@ -20,6 +20,7 @@ def truncator_class( Returns: typing.Callable[[typing.Type[Truncator]], typing.Type[Truncator]]: 装饰器 """ + def decorator(cls: typing.Type[Truncator]) -> typing.Type[Truncator]: assert issubclass(cls, Truncator) @@ -33,13 +34,12 @@ def truncator_class( class Truncator(abc.ABC): - """消息截断器基类 - """ + """消息截断器基类""" name: str ap: app.Application - + def __init__(self, ap: app.Application): self.ap = ap diff --git a/pkg/pipeline/msgtrun/truncators/round.py b/pkg/pipeline/msgtrun/truncators/round.py index 46fce5f3..fa72a0e1 100644 --- a/pkg/pipeline/msgtrun/truncators/round.py +++ b/pkg/pipeline/msgtrun/truncators/round.py @@ -4,14 +4,12 @@ from .. import truncator from ....core import entities as core_entities -@truncator.truncator_class("round") +@truncator.truncator_class('round') class RoundTruncator(truncator.Truncator): - """前文回合数阶段器 - """ + """前文回合数阶段器""" async def truncate(self, query: core_entities.Query) -> core_entities.Query: - """截断 - """ + """截断""" max_round = query.pipeline_config['ai']['local-agent']['max-round'] temp_messages = [] @@ -26,7 +24,7 @@ class RoundTruncator(truncator.Truncator): current_round += 1 else: break - + query.messages = temp_messages[::-1] return query diff --git a/pkg/pipeline/pipelinemgr.py b/pkg/pipeline/pipelinemgr.py index b7eaaab4..8ca0d592 100644 --- a/pkg/pipeline/pipelinemgr.py +++ b/pkg/pipeline/pipelinemgr.py @@ -11,22 +11,39 @@ from ..entity.persistence import pipeline as persistence_pipeline from . import stage from ..platform.types import message as platform_message, events as platform_events from ..plugin import events +from ..utils import importutil -from .resprule import resprule -from .bansess import bansess -from .cntfilter import cntfilter -from .process import process -from .longtext import longtext -from .respback import respback -from .wrapper import wrapper -from .preproc import preproc -from .ratelimit import ratelimit -from .msgtrun import msgtrun +from . import ( + resprule, + bansess, + cntfilter, + process, + longtext, + respback, + wrapper, + preproc, + ratelimit, + msgtrun, +) + +importutil.import_modules_in_pkgs( + [ + resprule, + bansess, + cntfilter, + process, + longtext, + respback, + wrapper, + preproc, + ratelimit, + msgtrun, + ] +) -class StageInstContainer(): - """阶段实例容器 - """ +class StageInstContainer: + """阶段实例容器""" inst_name: str @@ -48,7 +65,12 @@ class RuntimePipeline: stage_containers: list[StageInstContainer] """阶段实例容器""" - def __init__(self, ap: app.Application, pipeline_entity: persistence_pipeline.LegacyPipeline, stage_containers: list[StageInstContainer]): + def __init__( + self, + ap: app.Application, + pipeline_entity: persistence_pipeline.LegacyPipeline, + stage_containers: list[StageInstContainer], + ): self.ap = ap self.pipeline_entity = pipeline_entity self.stage_containers = stage_containers @@ -57,9 +79,10 @@ class RuntimePipeline: query.pipeline_config = self.pipeline_entity.config await self.process_query(query) - async def _check_output(self, query: entities.Query, result: pipeline_entities.StageProcessResult): - """检查输出 - """ + async def _check_output( + self, query: entities.Query, result: pipeline_entities.StageProcessResult + ): + """检查输出""" if result.user_notice: # 处理str类型 @@ -68,22 +91,19 @@ class RuntimePipeline: platform_message.Plain(result.user_notice) ) elif isinstance(result.user_notice, list): - result.user_notice = platform_message.MessageChain( - *result.user_notice - ) + result.user_notice = platform_message.MessageChain(*result.user_notice) - if query.pipeline_config['output']['misc']['at-sender'] and isinstance(query.message_event, platform_events.GroupMessage): + if query.pipeline_config['output']['misc']['at-sender'] and isinstance( + query.message_event, platform_events.GroupMessage + ): result.user_notice.insert( - 0, - platform_message.At( - query.message_event.sender.id - ) + 0, platform_message.At(query.message_event.sender.id) ) await query.adapter.reply_message( message_source=query.message_event, message=result.user_notice, - quote_origin=query.pipeline_config['output']['misc']['quote-origin'] + quote_origin=query.pipeline_config['output']['misc']['quote-origin'], ) if result.debug_notice: self.ap.logger.debug(result.debug_notice) @@ -123,32 +143,44 @@ class RuntimePipeline: stage_container = self.stage_containers[i] query.current_stage = stage_container # 标记到 Query 对象里 - + result = stage_container.inst.process(query, stage_container.inst_name) if isinstance(result, typing.Coroutine): result = await result if isinstance(result, pipeline_entities.StageProcessResult): # 直接返回结果 - self.ap.logger.debug(f"Stage {stage_container.inst_name} processed query {query} res {result}") + self.ap.logger.debug( + f'Stage {stage_container.inst_name} processed query {query} res {result}' + ) await self._check_output(query, result) if result.result_type == pipeline_entities.ResultType.INTERRUPT: - self.ap.logger.debug(f"Stage {stage_container.inst_name} interrupted query {query}") + self.ap.logger.debug( + f'Stage {stage_container.inst_name} interrupted query {query}' + ) break elif result.result_type == pipeline_entities.ResultType.CONTINUE: query = result.new_query elif isinstance(result, typing.AsyncGenerator): # 生成器 - self.ap.logger.debug(f"Stage {stage_container.inst_name} processed query {query} gen") + self.ap.logger.debug( + f'Stage {stage_container.inst_name} processed query {query} gen' + ) async for sub_result in result: - self.ap.logger.debug(f"Stage {stage_container.inst_name} processed query {query} res {sub_result}") + self.ap.logger.debug( + f'Stage {stage_container.inst_name} processed query {query} res {sub_result}' + ) await self._check_output(query, sub_result) if sub_result.result_type == pipeline_entities.ResultType.INTERRUPT: - self.ap.logger.debug(f"Stage {stage_container.inst_name} interrupted query {query}") + self.ap.logger.debug( + f'Stage {stage_container.inst_name} interrupted query {query}' + ) break - elif sub_result.result_type == pipeline_entities.ResultType.CONTINUE: + elif ( + sub_result.result_type == pipeline_entities.ResultType.CONTINUE + ): query = sub_result.new_query await self._execute_from_stage(i + 1, query) break @@ -156,12 +188,14 @@ class RuntimePipeline: i += 1 async def process_query(self, query: entities.Query): - """处理请求 - """ + """处理请求""" try: - # ======== 触发 MessageReceived 事件 ======== - event_type = events.PersonMessageReceived if query.launcher_type == entities.LauncherTypes.PERSON else events.GroupMessageReceived + event_type = ( + events.PersonMessageReceived + if query.launcher_type == entities.LauncherTypes.PERSON + else events.GroupMessageReceived + ) event_ctx = await self.ap.plugin_mgr.emit_event( event=event_type( @@ -169,22 +203,26 @@ class RuntimePipeline: launcher_id=query.launcher_id, sender_id=query.sender_id, message_chain=query.message_chain, - query=query + query=query, ) ) if event_ctx.is_prevented_default(): return - - self.ap.logger.debug(f"Processing query {query}") + + self.ap.logger.debug(f'Processing query {query}') await self._execute_from_stage(0, query) except Exception as e: - inst_name = query.current_stage.inst_name if query.current_stage else 'unknown' - self.ap.logger.error(f"处理请求时出错 query_id={query.query_id} stage={inst_name} : {e}") - self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") + inst_name = ( + query.current_stage.inst_name if query.current_stage else 'unknown' + ) + self.ap.logger.error( + f'处理请求时出错 query_id={query.query_id} stage={inst_name} : {e}' + ) + self.ap.logger.debug(f'Traceback: {traceback.format_exc()}') finally: - self.ap.logger.debug(f"Query {query} processed") + self.ap.logger.debug(f'Query {query} processed') class PipelineManager: @@ -203,7 +241,9 @@ class PipelineManager: self.pipelines = [] async def initialize(self): - self.stage_dict = {name: cls for name, cls in stage.preregistered_stages.items()} + self.stage_dict = { + name: cls for name, cls in stage.preregistered_stages.items() + } await self.load_pipelines_from_db() @@ -220,24 +260,31 @@ class PipelineManager: for pipeline in pipelines: await self.load_pipeline(pipeline) - async def load_pipeline(self, pipeline_entity: persistence_pipeline.LegacyPipeline | sqlalchemy.Row[persistence_pipeline.LegacyPipeline] | dict): - + async def load_pipeline( + self, + pipeline_entity: persistence_pipeline.LegacyPipeline + | sqlalchemy.Row[persistence_pipeline.LegacyPipeline] + | dict, + ): if isinstance(pipeline_entity, sqlalchemy.Row): - pipeline_entity = persistence_pipeline.LegacyPipeline(**pipeline_entity._mapping) + pipeline_entity = persistence_pipeline.LegacyPipeline( + **pipeline_entity._mapping + ) elif isinstance(pipeline_entity, dict): pipeline_entity = persistence_pipeline.LegacyPipeline(**pipeline_entity) # initialize stage containers according to pipeline_entity.stages stage_containers: list[StageInstContainer] = [] for stage_name in pipeline_entity.stages: - stage_containers.append(StageInstContainer( - inst_name=stage_name, - inst=self.stage_dict[stage_name](self.ap) - )) + stage_containers.append( + StageInstContainer( + inst_name=stage_name, inst=self.stage_dict[stage_name](self.ap) + ) + ) for stage_container in stage_containers: await stage_container.inst.initialize(pipeline_entity.config) - + runtime_pipeline = RuntimePipeline(self.ap, pipeline_entity, stage_containers) self.pipelines.append(runtime_pipeline) @@ -251,4 +298,4 @@ class PipelineManager: for pipeline in self.pipelines: if pipeline.pipeline_entity.uuid == uuid: self.pipelines.remove(pipeline) - return \ No newline at end of file + return diff --git a/pkg/pipeline/pool.py b/pkg/pipeline/pool.py index df4d0741..3da4e19b 100644 --- a/pkg/pipeline/pool.py +++ b/pkg/pipeline/pool.py @@ -47,7 +47,7 @@ class QueryPool: message_chain=message_chain, resp_messages=[], resp_message_chain=[], - adapter=adapter + adapter=adapter, ) self.queries.append(query) self.query_id_counter += 1 diff --git a/pkg/pipeline/preproc/preproc.py b/pkg/pipeline/preproc/preproc.py index 42bb5b4c..bab1127d 100644 --- a/pkg/pipeline/preproc/preproc.py +++ b/pkg/pipeline/preproc/preproc.py @@ -9,7 +9,7 @@ from ...plugin import events from ...platform.types import message as platform_message -@stage.stage_class("PreProcessor") +@stage.stage_class('PreProcessor') class PreProcessor(stage.PipelineStage): """请求预处理阶段 @@ -29,11 +29,12 @@ class PreProcessor(stage.PipelineStage): query: core_entities.Query, stage_inst_name: str, ) -> entities.StageProcessResult: - """处理 - """ + """处理""" session = await self.ap.sess_mgr.get_session(query) - conversation = await self.ap.sess_mgr.get_conversation(query, session, query.pipeline_config['ai']['local-agent']['prompt']) + conversation = await self.ap.sess_mgr.get_conversation( + query, session, query.pipeline_config['ai']['local-agent']['prompt'] + ) # 设置query query.session = session @@ -42,17 +43,26 @@ class PreProcessor(stage.PipelineStage): query.use_llm_model = conversation.use_llm_model - query.use_funcs = conversation.use_funcs if query.use_llm_model.model_entity.abilities.__contains__('tool_call') else None + query.use_funcs = ( + conversation.use_funcs + if query.use_llm_model.model_entity.abilities.__contains__('tool_call') + else None + ) query.variables = { - "session_id": f"{query.session.launcher_type.value}_{query.session.launcher_id}", - "conversation_id": conversation.uuid, - "msg_create_time": int(query.message_event.time) if query.message_event.time else int(datetime.datetime.now().timestamp()), + 'session_id': f'{query.session.launcher_type.value}_{query.session.launcher_id}', + 'conversation_id': conversation.uuid, + 'msg_create_time': int(query.message_event.time) + if query.message_event.time + else int(datetime.datetime.now().timestamp()), } # Check if this model supports vision, if not, remove all images # TODO this checking should be performed in runner, and in this stage, the image should be reserved - if query.pipeline_config['ai']['runner']['runner'] == 'local-agent' and not query.use_llm_model.model_entity.abilities.__contains__('vision'): + if ( + query.pipeline_config['ai']['runner']['runner'] == 'local-agent' + and not query.use_llm_model.model_entity.abilities.__contains__('vision') + ): for msg in query.messages: if isinstance(msg.content, list): for me in msg.content: @@ -61,16 +71,17 @@ class PreProcessor(stage.PipelineStage): content_list = [] - plain_text = "" + plain_text = '' for me in query.message_chain: if isinstance(me, platform_message.Plain): - content_list.append( - llm_entities.ContentElement.from_text(me.text) - ) + content_list.append(llm_entities.ContentElement.from_text(me.text)) plain_text += me.text elif isinstance(me, platform_message.Image): - if query.pipeline_config['ai']['runner']['runner'] != 'local-agent' or query.use_llm_model.model_entity.abilities.__contains__('vision'): + if ( + query.pipeline_config['ai']['runner']['runner'] != 'local-agent' + or query.use_llm_model.model_entity.abilities.__contains__('vision') + ): if me.base64 is not None: content_list.append( llm_entities.ContentElement.from_image_base64(me.base64) @@ -78,10 +89,7 @@ class PreProcessor(stage.PipelineStage): query.variables['user_message_text'] = plain_text - query.user_message = llm_entities.Message( - role='user', - content=content_list - ) + query.user_message = llm_entities.Message(role='user', content=content_list) # =========== 触发事件 PromptPreProcessing event_ctx = await self.ap.plugin_mgr.emit_event( @@ -89,7 +97,7 @@ class PreProcessor(stage.PipelineStage): session_name=f'{query.session.launcher_type.value}_{query.session.launcher_id}', default_prompt=query.prompt.messages, prompt=query.messages, - query=query + query=query, ) ) @@ -97,6 +105,5 @@ class PreProcessor(stage.PipelineStage): query.messages = event_ctx.event.prompt return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) diff --git a/pkg/pipeline/process/handler.py b/pkg/pipeline/process/handler.py index 879b4cfe..8a32bcfb 100644 --- a/pkg/pipeline/process/handler.py +++ b/pkg/pipeline/process/handler.py @@ -8,7 +8,6 @@ from .. import entities class MessageHandler(metaclass=abc.ABCMeta): - ap: app.Application def __init__(self, ap: app.Application): diff --git a/pkg/pipeline/process/handlers/chat.py b/pkg/pipeline/process/handlers/chat.py index 9d231dda..7943d8d1 100644 --- a/pkg/pipeline/process/handlers/chat.py +++ b/pkg/pipeline/process/handlers/chat.py @@ -3,33 +3,36 @@ from __future__ import annotations import typing import time import traceback -import json from .. import handler from ... import entities from ....core import entities as core_entities -from ....provider import entities as llm_entities from ....provider import runner as runner_module -from ....provider.runners import localagent, difysvapi, dashscopeapi from ....plugin import events from ....platform.types import message as platform_message +from ....utils import importutil +from ....provider import runners + +importutil.import_modules_in_pkg(runners) class ChatMessageHandler(handler.MessageHandler): - async def handle( self, query: core_entities.Query, ) -> typing.AsyncGenerator[entities.StageProcessResult, None]: - """处理 - """ + """处理""" # 调API # 生成器 # 触发插件事件 - event_class = events.PersonNormalMessageReceived if query.launcher_type == core_entities.LauncherTypes.PERSON else events.GroupNormalMessageReceived + event_class = ( + events.PersonNormalMessageReceived + if query.launcher_type == core_entities.LauncherTypes.PERSON + else events.GroupNormalMessageReceived + ) event_ctx = await self.ap.plugin_mgr.emit_event( event=event_class( @@ -37,7 +40,7 @@ class ChatMessageHandler(handler.MessageHandler): launcher_id=query.launcher_id, sender_id=query.sender_id, text_message=str(query.message_chain), - query=query + query=query, ) ) @@ -48,16 +51,13 @@ class ChatMessageHandler(handler.MessageHandler): query.resp_messages.append(mc) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: yield entities.StageProcessResult( - result_type=entities.ResultType.INTERRUPT, - new_query=query + result_type=entities.ResultType.INTERRUPT, new_query=query ) else: - if event_ctx.event.alter is not None: # if isinstance(event_ctx.event, str): # 现在暂时不考虑多模态alter query.user_message.content = event_ctx.event.alter @@ -67,48 +67,52 @@ class ChatMessageHandler(handler.MessageHandler): start_time = time.time() try: - for r in runner_module.preregistered_runners: - if r.name == query.pipeline_config["ai"]["runner"]["runner"]: + if r.name == query.pipeline_config['ai']['runner']['runner']: runner = r(self.ap, query.pipeline_config) break else: - raise ValueError(f"未找到请求运行器: {query.pipeline_config['ai']['runner']['runner']}") + raise ValueError( + f'未找到请求运行器: {query.pipeline_config["ai"]["runner"]["runner"]}' + ) async for result in runner.run(query): query.resp_messages.append(result) - self.ap.logger.info(f'对话({query.query_id})响应: {self.cut_str(result.readable_str())}') + self.ap.logger.info( + f'对话({query.query_id})响应: {self.cut_str(result.readable_str())}' + ) if result.content is not None: text_length += len(result.content) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) query.session.using_conversation.messages.append(query.user_message) query.session.using_conversation.messages.extend(query.resp_messages) except Exception as e: - - self.ap.logger.error(f'对话({query.query_id})请求失败: {type(e).__name__} {str(e)}') + self.ap.logger.error( + f'对话({query.query_id})请求失败: {type(e).__name__} {str(e)}' + ) - hide_exception_info = query.pipeline_config['output']['misc']['hide-exception'] + hide_exception_info = query.pipeline_config['output']['misc'][ + 'hide-exception' + ] yield entities.StageProcessResult( result_type=entities.ResultType.INTERRUPT, new_query=query, user_notice='请求失败' if hide_exception_info else f'{e}', error_notice=f'{e}', - debug_notice=traceback.format_exc() + debug_notice=traceback.format_exc(), ) finally: - await self.ap.ctr_mgr.usage.post_query_record( session_type=query.session.launcher_type.value, session_id=str(query.session.launcher_id), - query_ability_provider="LangBot.Chat", + query_ability_provider='LangBot.Chat', usage=text_length, model_name=query.use_model.name, response_seconds=int(time.time() - start_time), diff --git a/pkg/pipeline/process/handlers/command.py b/pkg/pipeline/process/handlers/command.py index b0316e1f..af1357b5 100644 --- a/pkg/pipeline/process/handlers/command.py +++ b/pkg/pipeline/process/handlers/command.py @@ -11,24 +11,29 @@ from ....platform.types import message as platform_message class CommandHandler(handler.MessageHandler): - async def handle( self, query: core_entities.Query, ) -> typing.AsyncGenerator[entities.StageProcessResult, None]: - """处理 - """ + """处理""" command_text = str(query.message_chain).strip()[1:] privilege = 1 - - if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']: + + if ( + f'{query.launcher_type.value}_{query.launcher_id}' + in self.ap.instance_config.data['admins'] + ): privilege = 2 spt = command_text.split(' ') - event_class = events.PersonCommandSent if query.launcher_type == core_entities.LauncherTypes.PERSON else events.GroupCommandSent + event_class = ( + events.PersonCommandSent + if query.launcher_type == core_entities.LauncherTypes.PERSON + else events.GroupCommandSent + ) event_ctx = await self.ap.plugin_mgr.emit_event( event=event_class( @@ -38,41 +43,35 @@ class CommandHandler(handler.MessageHandler): command=spt[0], params=spt[1:] if len(spt) > 1 else [], text_message=str(query.message_chain), - is_admin=(privilege==2), - query=query + is_admin=(privilege == 2), + query=query, ) ) if event_ctx.is_prevented_default(): - if event_ctx.event.reply is not None: mc = platform_message.MessageChain(event_ctx.event.reply) query.resp_messages.append(mc) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: yield entities.StageProcessResult( - result_type=entities.ResultType.INTERRUPT, - new_query=query + result_type=entities.ResultType.INTERRUPT, new_query=query ) else: - if event_ctx.event.alter is not None: - query.message_chain = platform_message.MessageChain([ - platform_message.Plain(event_ctx.event.alter) - ]) + query.message_chain = platform_message.MessageChain( + [platform_message.Plain(event_ctx.event.alter)] + ) session = await self.ap.sess_mgr.get_session(query) async for ret in self.ap.cmd_mgr.execute( - command_text=command_text, - query=query, - session=session + command_text=command_text, query=query, session=session ): if ret.error is not None: query.resp_messages.append( @@ -82,20 +81,18 @@ class CommandHandler(handler.MessageHandler): ) ) - self.ap.logger.info(f'命令({query.query_id})报错: {self.cut_str(str(ret.error))}') + self.ap.logger.info( + f'命令({query.query_id})报错: {self.cut_str(str(ret.error))}' + ) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) elif ret.text is not None or ret.image_url is not None: - - content: list[llm_entities.ContentElement]= [] + content: list[llm_entities.ContentElement] = [] if ret.text is not None: - content.append( - llm_entities.ContentElement.from_text(ret.text) - ) + content.append(llm_entities.ContentElement.from_text(ret.text)) if ret.image_url is not None: content.append( @@ -112,11 +109,9 @@ class CommandHandler(handler.MessageHandler): self.ap.logger.info(f'命令返回: {self.cut_str(str(content[0]))}') yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: yield entities.StageProcessResult( - result_type=entities.ResultType.INTERRUPT, - new_query=query + result_type=entities.ResultType.INTERRUPT, new_query=query ) diff --git a/pkg/pipeline/process/process.py b/pkg/pipeline/process/process.py index 11f43d3c..64903552 100644 --- a/pkg/pipeline/process/process.py +++ b/pkg/pipeline/process/process.py @@ -1,18 +1,16 @@ from __future__ import annotations -from ...core import app, entities as core_entities +from ...core import entities as core_entities from . import handler from .handlers import chat, command from .. import entities -from .. import stage, entities -from ...core import entities as core_entities -from ...config import manager as cfg_mgr +from .. import stage -@stage.stage_class("MessageProcessor") +@stage.stage_class('MessageProcessor') class Processor(stage.PipelineStage): """请求实际处理阶段 - + 通过命令处理器和聊天处理器处理消息。 改写: @@ -35,11 +33,12 @@ class Processor(stage.PipelineStage): query: core_entities.Query, stage_inst_name: str, ) -> entities.StageProcessResult: - """处理 - """ + """处理""" message_text = str(query.message_chain).strip() - self.ap.logger.info(f"处理 {query.launcher_type.value}_{query.launcher_id} 的请求({query.query_id}): {message_text}") + self.ap.logger.info( + f'处理 {query.launcher_type.value}_{query.launcher_id} 的请求({query.query_id}): {message_text}' + ) async def generator(): cmd_prefix = self.ap.instance_config.data['command']['prefix'] @@ -50,5 +49,5 @@ class Processor(stage.PipelineStage): else: async for result in self.chat_handler.handle(query): yield result - + return generator() diff --git a/pkg/pipeline/ratelimit/algo.py b/pkg/pipeline/ratelimit/algo.py index d9baa801..3bcc347a 100644 --- a/pkg/pipeline/ratelimit/algo.py +++ b/pkg/pipeline/ratelimit/algo.py @@ -7,19 +7,19 @@ from ...core import app, entities as core_entities preregistered_algos: list[typing.Type[ReteLimitAlgo]] = [] + def algo_class(name: str): - def decorator(cls: typing.Type[ReteLimitAlgo]) -> typing.Type[ReteLimitAlgo]: cls.name = name preregistered_algos.append(cls) return cls - + return decorator class ReteLimitAlgo(metaclass=abc.ABCMeta): """限流算法抽象类""" - + name: str = None ap: app.Application @@ -31,11 +31,16 @@ class ReteLimitAlgo(metaclass=abc.ABCMeta): pass @abc.abstractmethod - async def require_access(self, query: core_entities.Query, launcher_type: str, launcher_id: typing.Union[int, str]) -> bool: + async def require_access( + self, + query: core_entities.Query, + launcher_type: str, + launcher_id: typing.Union[int, str], + ) -> bool: """进入处理流程 这个方法对等待是友好的,意味着算法可以实现在这里等待一段时间以控制速率。 - + Args: launcher_type (str): 请求者类型 群聊为 group 私聊为 person launcher_id (int): 请求者ID @@ -44,15 +49,19 @@ class ReteLimitAlgo(metaclass=abc.ABCMeta): bool: 是否允许进入处理流程,若返回false,则直接丢弃该请求 """ raise NotImplementedError - + @abc.abstractmethod - async def release_access(self, query: core_entities.Query, launcher_type: str, launcher_id: typing.Union[int, str]): + async def release_access( + self, + query: core_entities.Query, + launcher_type: str, + launcher_id: typing.Union[int, str], + ): """退出处理流程 Args: launcher_type (str): 请求者类型 群聊为 group 私聊为 person launcher_id (int): 请求者ID """ - + raise NotImplementedError - \ No newline at end of file diff --git a/pkg/pipeline/ratelimit/algos/fixedwin.py b/pkg/pipeline/ratelimit/algos/fixedwin.py index f17e93b8..32079a97 100644 --- a/pkg/pipeline/ratelimit/algos/fixedwin.py +++ b/pkg/pipeline/ratelimit/algos/fixedwin.py @@ -5,9 +5,9 @@ import typing from .. import algo from ....core import entities as core_entities + # 固定窗口算法 class SessionContainer: - wait_lock: asyncio.Lock records: dict[int, int] @@ -18,9 +18,8 @@ class SessionContainer: self.records = {} -@algo.algo_class("fixwin") +@algo.algo_class('fixwin') class FixedWindowAlgo(algo.ReteLimitAlgo): - containers_lock: asyncio.Lock """访问记录容器锁""" @@ -31,7 +30,12 @@ class FixedWindowAlgo(algo.ReteLimitAlgo): self.containers_lock = asyncio.Lock() self.containers = {} - async def require_access(self, query: core_entities.Query, launcher_type: str, launcher_id: typing.Union[int, str]) -> bool: + async def require_access( + self, + query: core_entities.Query, + launcher_type: str, + launcher_id: typing.Union[int, str], + ) -> bool: # 加锁,找容器 container: SessionContainer = None @@ -46,7 +50,6 @@ class FixedWindowAlgo(algo.ReteLimitAlgo): # 等待锁 async with container.wait_lock: - # 获取窗口大小和限制 window_size = query.pipeline_config['safety']['rate-limit']['window-length'] limitation = query.pipeline_config['safety']['rate-limit']['limitation'] @@ -69,13 +72,15 @@ class FixedWindowAlgo(algo.ReteLimitAlgo): if count >= limitation: if query.pipeline_config['safety']['rate-limit']['strategy'] == 'drop': return False - elif query.pipeline_config['safety']['rate-limit']['strategy'] == 'wait': + elif ( + query.pipeline_config['safety']['rate-limit']['strategy'] == 'wait' + ): # 等待下一窗口 await asyncio.sleep(window_size - time.time() % window_size) - + now = int(time.time()) now = now - now % window_size - + if now not in container.records: container.records = {} container.records[now] = 1 @@ -85,6 +90,11 @@ class FixedWindowAlgo(algo.ReteLimitAlgo): # 返回True return True - - async def release_access(self, query: core_entities.Query, launcher_type: str, launcher_id: typing.Union[int, str]): + + async def release_access( + self, + query: core_entities.Query, + launcher_type: str, + launcher_id: typing.Union[int, str], + ): pass diff --git a/pkg/pipeline/ratelimit/ratelimit.py b/pkg/pipeline/ratelimit/ratelimit.py index c74db978..23de4ec6 100644 --- a/pkg/pipeline/ratelimit/ratelimit.py +++ b/pkg/pipeline/ratelimit/ratelimit.py @@ -4,22 +4,25 @@ import typing from .. import entities, stage from . import algo -from .algos import fixedwin from ...core import entities as core_entities +from ...utils import importutil + +from . import algos + +importutil.import_modules_in_pkg(algos) -@stage.stage_class("RequireRateLimitOccupancy") -@stage.stage_class("ReleaseRateLimitOccupancy") +@stage.stage_class('RequireRateLimitOccupancy') +@stage.stage_class('ReleaseRateLimitOccupancy') class RateLimit(stage.PipelineStage): """限速器控制阶段 - + 不改写query,只检查是否需要限速。 """ algo: algo.ReteLimitAlgo async def initialize(self, pipeline_config: dict): - algo_name = 'fixwin' algo_class = None @@ -42,9 +45,8 @@ class RateLimit(stage.PipelineStage): entities.StageProcessResult, typing.AsyncGenerator[entities.StageProcessResult, None], ]: - """处理 - """ - if stage_inst_name == "RequireRateLimitOccupancy": + """处理""" + if stage_inst_name == 'RequireRateLimitOccupancy': if await self.algo.require_access( query, query.launcher_type.value, @@ -58,10 +60,10 @@ class RateLimit(stage.PipelineStage): return entities.StageProcessResult( result_type=entities.ResultType.INTERRUPT, new_query=query, - console_notice=f"根据限速规则忽略 {query.launcher_type.value}:{query.launcher_id} 消息", - user_notice=f"请求数超过限速器设定值,已丢弃本消息。" + console_notice=f'根据限速规则忽略 {query.launcher_type.value}:{query.launcher_id} 消息', + user_notice='请求数超过限速器设定值,已丢弃本消息。', ) - elif stage_inst_name == "ReleaseRateLimitOccupancy": + elif stage_inst_name == 'ReleaseRateLimitOccupancy': await self.algo.release_access( query, query.launcher_type.value, diff --git a/pkg/pipeline/respback/respback.py b/pkg/pipeline/respback/respback.py index 8c074d89..42c141c8 100644 --- a/pkg/pipeline/respback/respback.py +++ b/pkg/pipeline/respback/respback.py @@ -4,41 +4,38 @@ import random import asyncio -from ...core import app from ...platform.types import events as platform_events from ...platform.types import message as platform_message from .. import stage, entities from ...core import entities as core_entities -from ...config import manager as cfg_mgr -@stage.stage_class("SendResponseBackStage") +@stage.stage_class('SendResponseBackStage') class SendResponseBackStage(stage.PipelineStage): - """发送响应消息 - """ + """发送响应消息""" - async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult: - """处理 - """ + async def process( + self, query: core_entities.Query, stage_inst_name: str + ) -> entities.StageProcessResult: + """处理""" - random_range = (query.pipeline_config['output']['force-delay']['min'], query.pipeline_config['output']['force-delay']['max']) + random_range = ( + query.pipeline_config['output']['force-delay']['min'], + query.pipeline_config['output']['force-delay']['max'], + ) random_delay = random.uniform(*random_range) - self.ap.logger.debug( - "根据规则强制延迟回复: %s s", - random_delay - ) + self.ap.logger.debug('根据规则强制延迟回复: %s s', random_delay) await asyncio.sleep(random_delay) - if query.pipeline_config['output']['misc']['at-sender'] and isinstance(query.message_event, platform_events.GroupMessage): + if query.pipeline_config['output']['misc']['at-sender'] and isinstance( + query.message_event, platform_events.GroupMessage + ): query.resp_message_chain[-1].insert( - 0, - platform_message.At( - query.message_event.sender.id - ) + 0, platform_message.At(query.message_event.sender.id) ) quote_origin = query.pipeline_config['output']['misc']['quote-origin'] @@ -46,10 +43,9 @@ class SendResponseBackStage(stage.PipelineStage): await query.adapter.reply_message( message_source=query.message_event, message=query.resp_message_chain[-1], - quote_origin=quote_origin + quote_origin=quote_origin, ) return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query - ) \ No newline at end of file + result_type=entities.ResultType.CONTINUE, new_query=query + ) diff --git a/pkg/pipeline/resprule/entities.py b/pkg/pipeline/resprule/entities.py index a334e843..a0ba7807 100644 --- a/pkg/pipeline/resprule/entities.py +++ b/pkg/pipeline/resprule/entities.py @@ -4,7 +4,6 @@ from ...platform.types import message as platform_message class RuleJudgeResult(pydantic.BaseModel): - matching: bool = False replacement: platform_message.MessageChain = None diff --git a/pkg/pipeline/resprule/resprule.py b/pkg/pipeline/resprule/resprule.py index 08ba49e8..99402351 100644 --- a/pkg/pipeline/resprule/resprule.py +++ b/pkg/pipeline/resprule/resprule.py @@ -1,16 +1,18 @@ from __future__ import annotations -from ...core import app -from . import entities as rule_entities, rule -from .rules import atbot, prefix, regexp, random +from . import rule from .. import stage, entities from ...core import entities as core_entities -from ...config import manager as cfg_mgr +from ...utils import importutil + +from . import rules + +importutil.import_modules_in_pkg(rules) -@stage.stage_class("GroupRespondRuleCheckStage") +@stage.stage_class('GroupRespondRuleCheckStage') class GroupRespondRuleCheckStage(stage.PipelineStage): """群组响应规则检查器 @@ -21,8 +23,7 @@ class GroupRespondRuleCheckStage(stage.PipelineStage): """检查器实例""" async def initialize(self, pipeline_config: dict): - """初始化检查器 - """ + """初始化检查器""" self.rule_matchers = [] @@ -31,12 +32,12 @@ class GroupRespondRuleCheckStage(stage.PipelineStage): await rule_inst.initialize() self.rule_matchers.append(rule_inst) - async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult: - + async def process( + self, query: core_entities.Query, stage_inst_name: str + ) -> entities.StageProcessResult: if query.launcher_type.value != 'group': # 只处理群消息 return entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) rules = query.pipeline_config['trigger']['group-respond-rules'] @@ -48,7 +49,9 @@ class GroupRespondRuleCheckStage(stage.PipelineStage): # use_rule = rules[str(query.launcher_id)] for rule_matcher in self.rule_matchers: # 任意一个匹配就放行 - res = await rule_matcher.match(str(query.message_chain), query.message_chain, use_rule, query) + res = await rule_matcher.match( + str(query.message_chain), query.message_chain, use_rule, query + ) if res.matching: query.message_chain = res.replacement @@ -56,8 +59,7 @@ class GroupRespondRuleCheckStage(stage.PipelineStage): result_type=entities.ResultType.CONTINUE, new_query=query, ) - + return entities.StageProcessResult( - result_type=entities.ResultType.INTERRUPT, - new_query=query + result_type=entities.ResultType.INTERRUPT, new_query=query ) diff --git a/pkg/pipeline/resprule/rule.py b/pkg/pipeline/resprule/rule.py index ad69d8a0..3fdb0386 100644 --- a/pkg/pipeline/resprule/rule.py +++ b/pkg/pipeline/resprule/rule.py @@ -10,17 +10,19 @@ from ...platform.types import message as platform_message preregisetered_rules: list[typing.Type[GroupRespondRule]] = [] + def rule_class(name: str): def decorator(cls: typing.Type[GroupRespondRule]) -> typing.Type[GroupRespondRule]: cls.name = name preregisetered_rules.append(cls) return cls + return decorator class GroupRespondRule(metaclass=abc.ABCMeta): - """群组响应规则的抽象类 - """ + """群组响应规则的抽象类""" + name: str ap: app.Application @@ -37,8 +39,7 @@ class GroupRespondRule(metaclass=abc.ABCMeta): message_text: str, message_chain: platform_message.MessageChain, rule_dict: dict, - query: core_entities.Query + query: core_entities.Query, ) -> entities.RuleJudgeResult: - """判断消息是否匹配规则 - """ + """判断消息是否匹配规则""" raise NotImplementedError diff --git a/pkg/pipeline/resprule/rules/atbot.py b/pkg/pipeline/resprule/rules/atbot.py index a0b7a7c8..0f4845f8 100644 --- a/pkg/pipeline/resprule/rules/atbot.py +++ b/pkg/pipeline/resprule/rules/atbot.py @@ -7,21 +7,24 @@ from ....core import entities as core_entities from ....platform.types import message as platform_message -@rule_model.rule_class("at-bot") +@rule_model.rule_class('at-bot') class AtBotRule(rule_model.GroupRespondRule): - async def match( self, message_text: str, message_chain: platform_message.MessageChain, rule_dict: dict, - query: core_entities.Query + query: core_entities.Query, ) -> entities.RuleJudgeResult: - - if message_chain.has(platform_message.At(query.adapter.bot_account_id)) and rule_dict['at']: + if ( + message_chain.has(platform_message.At(query.adapter.bot_account_id)) + and rule_dict['at'] + ): message_chain.remove(platform_message.At(query.adapter.bot_account_id)) - if message_chain.has(platform_message.At(query.adapter.bot_account_id)): # 回复消息时会at两次,检查并删除重复的 + if message_chain.has( + platform_message.At(query.adapter.bot_account_id) + ): # 回复消息时会at两次,检查并删除重复的 message_chain.remove(platform_message.At(query.adapter.bot_account_id)) return entities.RuleJudgeResult( @@ -29,7 +32,4 @@ class AtBotRule(rule_model.GroupRespondRule): replacement=message_chain, ) - return entities.RuleJudgeResult( - matching=False, - replacement = message_chain - ) + return entities.RuleJudgeResult(matching=False, replacement=message_chain) diff --git a/pkg/pipeline/resprule/rules/prefix.py b/pkg/pipeline/resprule/rules/prefix.py index fb7bbcfc..c712d3e8 100644 --- a/pkg/pipeline/resprule/rules/prefix.py +++ b/pkg/pipeline/resprule/rules/prefix.py @@ -1,36 +1,30 @@ - from .. import rule as rule_model from .. import entities from ....core import entities as core_entities from ....platform.types import message as platform_message -@rule_model.rule_class("prefix") +@rule_model.rule_class('prefix') class PrefixRule(rule_model.GroupRespondRule): - async def match( self, message_text: str, message_chain: platform_message.MessageChain, rule_dict: dict, - query: core_entities.Query + query: core_entities.Query, ) -> entities.RuleJudgeResult: prefixes = rule_dict['prefix'] for prefix in prefixes: if message_text.startswith(prefix): - # 查找第一个plain元素 for me in message_chain: if isinstance(me, platform_message.Plain): - me.text = me.text[len(prefix):] + me.text = me.text[len(prefix) :] return entities.RuleJudgeResult( matching=True, replacement=message_chain, ) - return entities.RuleJudgeResult( - matching=False, - replacement=message_chain - ) + return entities.RuleJudgeResult(matching=False, replacement=message_chain) diff --git a/pkg/pipeline/resprule/rules/random.py b/pkg/pipeline/resprule/rules/random.py index 0178f2c4..535bfe6b 100644 --- a/pkg/pipeline/resprule/rules/random.py +++ b/pkg/pipeline/resprule/rules/random.py @@ -7,19 +7,17 @@ from ....core import entities as core_entities from ....platform.types import message as platform_message -@rule_model.rule_class("random") +@rule_model.rule_class('random') class RandomRespRule(rule_model.GroupRespondRule): - async def match( self, message_text: str, message_chain: platform_message.MessageChain, rule_dict: dict, - query: core_entities.Query + query: core_entities.Query, ) -> entities.RuleJudgeResult: random_rate = rule_dict['random'] - + return entities.RuleJudgeResult( - matching=random.random() < random_rate, - replacement=message_chain - ) \ No newline at end of file + matching=random.random() < random_rate, replacement=message_chain + ) diff --git a/pkg/pipeline/resprule/rules/regexp.py b/pkg/pipeline/resprule/rules/regexp.py index f5f5b3f6..daac0869 100644 --- a/pkg/pipeline/resprule/rules/regexp.py +++ b/pkg/pipeline/resprule/rules/regexp.py @@ -7,15 +7,14 @@ from ....core import entities as core_entities from ....platform.types import message as platform_message -@rule_model.rule_class("regexp") +@rule_model.rule_class('regexp') class RegExpRule(rule_model.GroupRespondRule): - async def match( self, message_text: str, message_chain: platform_message.MessageChain, rule_dict: dict, - query: core_entities.Query + query: core_entities.Query, ) -> entities.RuleJudgeResult: regexps = rule_dict['regexp'] @@ -27,8 +26,5 @@ class RegExpRule(rule_model.GroupRespondRule): matching=True, replacement=message_chain, ) - - return entities.RuleJudgeResult( - matching=False, - replacement=message_chain - ) + + return entities.RuleJudgeResult(matching=False, replacement=message_chain) diff --git a/pkg/pipeline/stage.py b/pkg/pipeline/stage.py index 859286d9..18636e9f 100644 --- a/pkg/pipeline/stage.py +++ b/pkg/pipeline/stage.py @@ -11,17 +11,15 @@ preregistered_stages: dict[str, PipelineStage] = {} def stage_class(name: str): - def decorator(cls): preregistered_stages[name] = cls return cls - + return decorator class PipelineStage(metaclass=abc.ABCMeta): - """流水线阶段 - """ + """流水线阶段""" ap: app.Application @@ -29,8 +27,7 @@ class PipelineStage(metaclass=abc.ABCMeta): self.ap = ap async def initialize(self, pipeline_config: dict): - """初始化 - """ + """初始化""" pass @abc.abstractmethod @@ -42,6 +39,5 @@ class PipelineStage(metaclass=abc.ABCMeta): entities.StageProcessResult, typing.AsyncGenerator[entities.StageProcessResult, None], ]: - """处理 - """ + """处理""" raise NotImplementedError diff --git a/pkg/pipeline/wrapper/wrapper.py b/pkg/pipeline/wrapper/wrapper.py index 6b12ca65..bca02527 100644 --- a/pkg/pipeline/wrapper/wrapper.py +++ b/pkg/pipeline/wrapper/wrapper.py @@ -3,21 +3,19 @@ from __future__ import annotations import typing -from ...core import app, entities as core_entities -from .. import entities -from .. import stage, entities from ...core import entities as core_entities -from ...config import manager as cfg_mgr +from .. import entities +from .. import stage from ...plugin import events from ...platform.types import message as platform_message -@stage.stage_class("ResponseWrapper") +@stage.stage_class('ResponseWrapper') class ResponseWrapper(stage.PipelineStage): """回复包装阶段 把回复的 message 包装成人类识读的形式。 - + 改写: - resp_message_chain """ @@ -30,36 +28,36 @@ class ResponseWrapper(stage.PipelineStage): query: core_entities.Query, stage_inst_name: str, ) -> typing.AsyncGenerator[entities.StageProcessResult, None]: - """处理 - """ + """处理""" # 如果 resp_messages[-1] 已经是 MessageChain 了 if isinstance(query.resp_messages[-1], platform_message.MessageChain): query.resp_message_chain.append(query.resp_messages[-1]) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: - if query.resp_messages[-1].role == 'command': - query.resp_message_chain.append(query.resp_messages[-1].get_content_platform_message_chain(prefix_text='[bot] ')) + query.resp_message_chain.append( + query.resp_messages[-1].get_content_platform_message_chain( + prefix_text='[bot] ' + ) + ) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) elif query.resp_messages[-1].role == 'plugin': - query.resp_message_chain.append(query.resp_messages[-1].get_content_platform_message_chain()) + query.resp_message_chain.append( + query.resp_messages[-1].get_content_platform_message_chain() + ) yield entities.StageProcessResult( - result_type=entities.ResultType.CONTINUE, - new_query=query + result_type=entities.ResultType.CONTINUE, new_query=query ) else: - if query.resp_messages[-1].role == 'assistant': result = query.resp_messages[-1] session = await self.ap.sess_mgr.get_session(query) @@ -79,39 +77,51 @@ class ResponseWrapper(stage.PipelineStage): prefix='', response_text=reply_text, finish_reason='stop', - funcs_called=[fc.function.name for fc in result.tool_calls] if result.tool_calls is not None else [], - query=query + funcs_called=[ + fc.function.name for fc in result.tool_calls + ] + if result.tool_calls is not None + else [], + query=query, ) ) if event_ctx.is_prevented_default(): yield entities.StageProcessResult( result_type=entities.ResultType.INTERRUPT, - new_query=query + new_query=query, ) else: if event_ctx.event.reply is not None: - - query.resp_message_chain.append(platform_message.MessageChain(event_ctx.event.reply)) + query.resp_message_chain.append( + platform_message.MessageChain(event_ctx.event.reply) + ) else: - - query.resp_message_chain.append(result.get_content_platform_message_chain()) + query.resp_message_chain.append( + result.get_content_platform_message_chain() + ) yield entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, - new_query=query + new_query=query, ) - if result.tool_calls is not None and len(result.tool_calls) > 0: # 有函数调用 - + if ( + result.tool_calls is not None and len(result.tool_calls) > 0 + ): # 有函数调用 function_names = [tc.function.name for tc in result.tool_calls] reply_text = f'调用函数 {".".join(function_names)}...' - query.resp_message_chain.append(platform_message.MessageChain([platform_message.Plain(reply_text)])) + query.resp_message_chain.append( + platform_message.MessageChain( + [platform_message.Plain(reply_text)] + ) + ) - if query.pipeline_config['output']['misc']['track-function-calls']: - + if query.pipeline_config['output']['misc'][ + 'track-function-calls' + ]: event_ctx = await self.ap.plugin_mgr.emit_event( event=events.NormalMessageResponded( launcher_type=query.launcher_type.value, @@ -121,26 +131,36 @@ class ResponseWrapper(stage.PipelineStage): prefix='', response_text=reply_text, finish_reason='stop', - funcs_called=[fc.function.name for fc in result.tool_calls] if result.tool_calls is not None else [], - query=query + funcs_called=[ + fc.function.name for fc in result.tool_calls + ] + if result.tool_calls is not None + else [], + query=query, ) ) if event_ctx.is_prevented_default(): yield entities.StageProcessResult( result_type=entities.ResultType.INTERRUPT, - new_query=query + new_query=query, ) else: if event_ctx.event.reply is not None: - - query.resp_message_chain.append(platform_message.MessageChain(event_ctx.event.reply)) + query.resp_message_chain.append( + platform_message.MessageChain( + event_ctx.event.reply + ) + ) else: - - query.resp_message_chain.append(platform_message.MessageChain([platform_message.Plain(reply_text)])) + query.resp_message_chain.append( + platform_message.MessageChain( + [platform_message.Plain(reply_text)] + ) + ) yield entities.StageProcessResult( result_type=entities.ResultType.CONTINUE, - new_query=query + new_query=query, ) diff --git a/pkg/platform/adapter.py b/pkg/platform/adapter.py index 42ea75e0..61ff32cd 100644 --- a/pkg/platform/adapter.py +++ b/pkg/platform/adapter.py @@ -17,7 +17,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta): bot_account_id: int """机器人账号ID,需要在初始化时设置""" - + config: dict ap: app.Application @@ -32,14 +32,11 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta): self.config = config self.ap = ap - async def send_message( - self, - target_type: str, - target_id: str, - message: platform_message.MessageChain + async def send_message( + self, target_type: str, target_id: str, message: platform_message.MessageChain ): """主动发送消息 - + Args: target_type (str): 目标类型,`person`或`group` target_id (str): 目标ID @@ -51,7 +48,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta): self, message_source: platform_events.MessageEvent, message: platform_message.MessageChain, - quote_origin: bool = False + quote_origin: bool = False, ): """回复消息 @@ -69,23 +66,27 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta): def register_listener( self, event_type: typing.Type[platform_message.Event], - callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None] + callback: typing.Callable[ + [platform_message.Event, MessagePlatformAdapter], None + ], ): """注册事件监听器 - + Args: event_type (typing.Type[platform.types.Event]): 事件类型 callback (typing.Callable[[platform.types.Event], None]): 回调函数,接收一个参数,为事件 """ raise NotImplementedError - + def unregister_listener( self, event_type: typing.Type[platform_message.Event], - callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None] + callback: typing.Callable[ + [platform_message.Event, MessagePlatformAdapter], None + ], ): """注销事件监听器 - + Args: event_type (typing.Type[platform.types.Event]): 事件类型 callback (typing.Callable[[platform.types.Event], None]): 回调函数,接收一个参数,为事件 @@ -98,7 +99,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta): async def kill(self) -> bool: """关闭适配器 - + Returns: bool: 是否成功关闭,热重载时若此函数返回False则不会重载MessageSource底层 """ @@ -107,6 +108,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta): class MessageConverter: """消息链转换器基类""" + @staticmethod def yiri2target(message_chain: platform_message.MessageChain): """将源平台消息链转换为目标平台消息链 diff --git a/pkg/platform/botmgr.py b/pkg/platform/botmgr.py index 6073243d..507f067e 100644 --- a/pkg/platform/botmgr.py +++ b/pkg/platform/botmgr.py @@ -1,23 +1,16 @@ from __future__ import annotations -import json -import os import sys -import logging import asyncio import traceback import sqlalchemy -from .sources import qqofficial # FriendMessage, Image, MessageChain, Plain from . import adapter as msadapter from ..core import app, entities as core_entities, taskmgr -from ..plugin import events -from .types import message as platform_message from .types import events as platform_events -from .types import entities as platform_entities from ..discover import engine @@ -25,6 +18,7 @@ from ..entity.persistence import bot as persistence_bot # 处理 3.4 移除了 YiriMirai 之后,插件的兼容性问题 from . import types as mirai + sys.modules['mirai'] = mirai @@ -43,7 +37,12 @@ class RuntimeBot: task_context: taskmgr.TaskContext - def __init__(self, ap: app.Application, bot_entity: persistence_bot.Bot, adapter: msadapter.MessagePlatformAdapter): + def __init__( + self, + ap: app.Application, + bot_entity: persistence_bot.Bot, + adapter: msadapter.MessagePlatformAdapter, + ): self.ap = ap self.bot_entity = bot_entity self.enable = bot_entity.enable @@ -51,9 +50,10 @@ class RuntimeBot: self.task_context = taskmgr.TaskContext() async def initialize(self): - - async def on_friend_message(event: platform_events.FriendMessage, adapter: msadapter.MessagePlatformAdapter): - + async def on_friend_message( + event: platform_events.FriendMessage, + adapter: msadapter.MessagePlatformAdapter, + ): await self.ap.query_pool.add_query( bot_uuid=self.bot_entity.uuid, launcher_type=core_entities.LauncherTypes.PERSON, @@ -64,8 +64,10 @@ class RuntimeBot: adapter=adapter, ) - async def on_group_message(event: platform_events.GroupMessage, adapter: msadapter.MessagePlatformAdapter): - + async def on_group_message( + event: platform_events.GroupMessage, + adapter: msadapter.MessagePlatformAdapter, + ): await self.ap.query_pool.add_query( bot_uuid=self.bot_entity.uuid, launcher_type=core_entities.LauncherTypes.GROUP, @@ -76,17 +78,10 @@ class RuntimeBot: adapter=adapter, ) - self.adapter.register_listener( - platform_events.FriendMessage, - on_friend_message - ) - self.adapter.register_listener( - platform_events.GroupMessage, - on_group_message - ) + self.adapter.register_listener(platform_events.FriendMessage, on_friend_message) + self.adapter.register_listener(platform_events.GroupMessage, on_group_message) async def run(self): - async def exception_wrapper(): try: self.task_context.set_current_action('Running...') @@ -98,16 +93,19 @@ class RuntimeBot: return self.task_context.set_current_action('Exited with error.') self.task_context.log(f'平台适配器运行出错: {e}') - self.task_context.log(f"Traceback: {traceback.format_exc()}") + self.task_context.log(f'Traceback: {traceback.format_exc()}') self.ap.logger.error(f'平台适配器运行出错: {e}') - self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") + self.ap.logger.debug(f'Traceback: {traceback.format_exc()}') self.task_wrapper = self.ap.task_mgr.create_task( exception_wrapper(), - kind="platform-adapter", - name=f"platform-adapter-{self.adapter.__class__.__name__}", + kind='platform-adapter', + name=f'platform-adapter-{self.adapter.__class__.__name__}', context=self.task_context, - scopes=[core_entities.LifecycleControlScope.APPLICATION, core_entities.LifecycleControlScope.PLATFORM] + scopes=[ + core_entities.LifecycleControlScope.APPLICATION, + core_entities.LifecycleControlScope.PLATFORM, + ], ) async def shutdown(self): @@ -118,7 +116,6 @@ class RuntimeBot: # 控制QQ消息输入输出的类 class PlatformManager: - # ====== 4.0 ====== ap: app.Application = None @@ -129,18 +126,20 @@ class PlatformManager: adapter_dict: dict[str, type[msadapter.MessagePlatformAdapter]] def __init__(self, ap: app.Application = None): - self.ap = ap self.bots = [] self.adapter_components = [] self.adapter_dict = {} - - async def initialize(self): - self.adapter_components = self.ap.discover.get_components_by_kind('MessagePlatformAdapter') + async def initialize(self): + self.adapter_components = self.ap.discover.get_components_by_kind( + 'MessagePlatformAdapter' + ) adapter_dict: dict[str, type[msadapter.MessagePlatformAdapter]] = {} for component in self.adapter_components: - adapter_dict[component.metadata.name] = component.get_python_component_class() + adapter_dict[component.metadata.name] = ( + component.get_python_component_class() + ) self.adapter_dict = adapter_dict await self.load_bots_from_db() @@ -158,12 +157,15 @@ class PlatformManager: ) bots = result.all() - + for bot in bots: # load all bots here, enable or disable will be handled in runtime await self.load_bot(bot) - async def load_bot(self, bot_entity: persistence_bot.Bot | sqlalchemy.Row[persistence_bot.Bot] | dict) -> RuntimeBot: + async def load_bot( + self, + bot_entity: persistence_bot.Bot | sqlalchemy.Row[persistence_bot.Bot] | dict, + ) -> RuntimeBot: """加载机器人""" if isinstance(bot_entity, sqlalchemy.Row): bot_entity = persistence_bot.Bot(**bot_entity._mapping) @@ -171,14 +173,11 @@ class PlatformManager: bot_entity = persistence_bot.Bot(**bot_entity) adapter_inst = self.adapter_dict[bot_entity.adapter]( - bot_entity.adapter_config, - self.ap + bot_entity.adapter_config, self.ap ) runtime_bot = RuntimeBot( - ap=self.ap, - bot_entity=bot_entity, - adapter=adapter_inst + ap=self.ap, bot_entity=bot_entity, adapter=adapter_inst ) await runtime_bot.initialize() @@ -186,7 +185,7 @@ class PlatformManager: self.bots.append(runtime_bot) return runtime_bot - + async def get_bot_by_uuid(self, bot_uuid: str) -> RuntimeBot | None: for bot in self.bots: if bot.bot_entity.uuid == bot_uuid: @@ -202,24 +201,28 @@ class PlatformManager: return def get_available_adapters_info(self) -> list[dict]: - return [ - component.to_plain_dict() - for component in self.adapter_components - ] + return [component.to_plain_dict() for component in self.adapter_components] def get_available_adapter_info_by_name(self, name: str) -> dict | None: for component in self.adapter_components: if component.metadata.name == name: return component.to_plain_dict() return None - - def get_available_adapter_manifest_by_name(self, name: str) -> engine.Component | None: + + def get_available_adapter_manifest_by_name( + self, name: str + ) -> engine.Component | None: for component in self.adapter_components: if component.metadata.name == name: return component return None - async def write_back_config(self, adapter_name: str, adapter_inst: msadapter.MessagePlatformAdapter, config: dict): + async def write_back_config( + self, + adapter_name: str, + adapter_inst: msadapter.MessagePlatformAdapter, + config: dict, + ): # index = -2 # for i, adapter in enumerate(self.adapters): @@ -251,7 +254,7 @@ class PlatformManager: # TODO implement this pass - async def run(self): + async def run(self): # This method will only be called when the application launching for bot in self.bots: if bot.enable: diff --git a/pkg/platform/sources/aiocqhttp.py b/pkg/platform/sources/aiocqhttp.py index 9149e427..48116507 100644 --- a/pkg/platform/sources/aiocqhttp.py +++ b/pkg/platform/sources/aiocqhttp.py @@ -2,24 +2,23 @@ from __future__ import annotations import typing import asyncio import traceback -import time import datetime import aiocqhttp -import aiohttp from .. import adapter -from ...pipeline.longtext.strategies import forward from ...core import app from ..types import message as platform_message from ..types import events as platform_events from ..types import entities as platform_entities from ...utils import image -class AiocqhttpMessageConverter(adapter.MessageConverter): +class AiocqhttpMessageConverter(adapter.MessageConverter): @staticmethod - async def yiri2target(message_chain: platform_message.MessageChain) -> typing.Tuple[list, int, datetime.datetime]: + async def yiri2target( + message_chain: platform_message.MessageChain, + ) -> typing.Tuple[list, int, datetime.datetime]: msg_list = aiocqhttp.Message() msg_id = 0 @@ -35,7 +34,7 @@ class AiocqhttpMessageConverter(adapter.MessageConverter): arg = '' if msg.base64: arg = msg.base64 - msg_list.append(aiocqhttp.MessageSegment.image(f"base64://{arg}")) + msg_list.append(aiocqhttp.MessageSegment.image(f'base64://{arg}')) elif msg.url: arg = msg.url msg_list.append(aiocqhttp.MessageSegment.image(arg)) @@ -45,12 +44,12 @@ class AiocqhttpMessageConverter(adapter.MessageConverter): elif type(msg) is platform_message.At: msg_list.append(aiocqhttp.MessageSegment.at(msg.target)) elif type(msg) is platform_message.AtAll: - msg_list.append(aiocqhttp.MessageSegment.at("all")) + msg_list.append(aiocqhttp.MessageSegment.at('all')) elif type(msg) is platform_message.Voice: arg = '' if msg.base64: arg = msg.base64 - msg_list.append(aiocqhttp.MessageSegment.record(f"base64://{arg}")) + msg_list.append(aiocqhttp.MessageSegment.record(f'base64://{arg}')) elif msg.url: arg = msg.url msg_list.append(aiocqhttp.MessageSegment.record(arg)) @@ -58,10 +57,15 @@ class AiocqhttpMessageConverter(adapter.MessageConverter): arg = msg.path msg_list.append(aiocqhttp.MessageSegment.record(msg.path)) elif type(msg) is platform_message.Forward: - for node in msg.node_list: - msg_list.extend((await AiocqhttpMessageConverter.yiri2target(node.message_chain))[0]) - + msg_list.extend( + ( + await AiocqhttpMessageConverter.yiri2target( + node.message_chain + ) + )[0] + ) + else: msg_list.append(aiocqhttp.MessageSegment.text(str(msg))) @@ -78,20 +82,26 @@ class AiocqhttpMessageConverter(adapter.MessageConverter): ) for msg in message: - if msg.type == "at": - if msg.data["qq"] == "all": + if msg.type == 'at': + if msg.data['qq'] == 'all': yiri_msg_list.append(platform_message.AtAll()) else: yiri_msg_list.append( platform_message.At( - target=msg.data["qq"], + target=msg.data['qq'], ) ) - elif msg.type == "text": - yiri_msg_list.append(platform_message.Plain(text=msg.data["text"])) - elif msg.type == "image": - image_base64, image_format = await image.qq_image_url_to_base64(msg.data['url']) - yiri_msg_list.append(platform_message.Image(base64=f"data:image/{image_format};base64,{image_base64}")) + elif msg.type == 'text': + yiri_msg_list.append(platform_message.Plain(text=msg.data['text'])) + elif msg.type == 'image': + image_base64, image_format = await image.qq_image_url_to_base64( + msg.data['url'] + ) + yiri_msg_list.append( + platform_message.Image( + base64=f'data:image/{image_format};base64,{image_base64}' + ) + ) chain = platform_message.MessageChain(yiri_msg_list) @@ -99,7 +109,6 @@ class AiocqhttpMessageConverter(adapter.MessageConverter): class AiocqhttpEventConverter(adapter.EventConverter): - @staticmethod async def yiri2target(event: platform_events.MessageEvent, bot_account_id: int): return event.source_platform_object @@ -110,49 +119,50 @@ class AiocqhttpEventConverter(adapter.EventConverter): event.message, event.message_id ) - if event.message_type == "group": - permission = "MEMBER" + if event.message_type == 'group': + permission = 'MEMBER' - if "role" in event.sender: - if event.sender["role"] == "admin": - permission = "ADMINISTRATOR" - elif event.sender["role"] == "owner": - permission = "OWNER" + if 'role' in event.sender: + if event.sender['role'] == 'admin': + permission = 'ADMINISTRATOR' + elif event.sender['role'] == 'owner': + permission = 'OWNER' converted_event = platform_events.GroupMessage( sender=platform_entities.GroupMember( - id=event.sender["user_id"], # message_seq 放哪? - member_name=event.sender["nickname"], + id=event.sender['user_id'], # message_seq 放哪? + member_name=event.sender['nickname'], permission=permission, group=platform_entities.Group( id=event.group_id, - name=event.sender["nickname"], + name=event.sender['nickname'], permission=platform_entities.Permission.Member, ), - special_title=event.sender["title"] if "title" in event.sender else "", + special_title=event.sender['title'] + if 'title' in event.sender + else '', join_timestamp=0, last_speak_timestamp=0, mute_time_remaining=0, ), message_chain=yiri_chain, time=event.time, - source_platform_object=event + source_platform_object=event, ) return converted_event - elif event.message_type == "private": + elif event.message_type == 'private': return platform_events.FriendMessage( sender=platform_entities.Friend( - id=event.sender["user_id"], - nickname=event.sender["nickname"], - remark="", + id=event.sender['user_id'], + nickname=event.sender['nickname'], + remark='', ), message_chain=yiri_chain, time=event.time, - source_platform_object=event + source_platform_object=event, ) class AiocqhttpAdapter(adapter.MessagePlatformAdapter): - bot: aiocqhttp.CQHttp bot_account_id: int @@ -170,14 +180,14 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter): async def shutdown_trigger_placeholder(): while True: await asyncio.sleep(1) - + self.config['shutdown_trigger'] = shutdown_trigger_placeholder self.ap = ap - if "access-token" in config: - self.bot = aiocqhttp.CQHttp(access_token=config["access-token"]) - del self.config["access-token"] + if 'access-token' in config: + self.bot = aiocqhttp.CQHttp(access_token=config['access-token']) + del self.config['access-token'] else: self.bot = aiocqhttp.CQHttp() @@ -186,9 +196,9 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter): ): aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0] - if target_type == "group": + if target_type == 'group': await self.bot.send_group_msg(group_id=int(target_id), message=aiocq_msg) - elif target_type == "person": + elif target_type == 'person': await self.bot.send_private_msg(user_id=int(target_id), message=aiocq_msg) async def reply_message( @@ -196,16 +206,17 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter): message_source: platform_events.MessageEvent, message: platform_message.MessageChain, quote_origin: bool = False, - ): - aiocq_event = await AiocqhttpEventConverter.yiri2target(message_source, self.bot_account_id) + ): + aiocq_event = await AiocqhttpEventConverter.yiri2target( + message_source, self.bot_account_id + ) aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0] if quote_origin: - aiocq_msg = aiocqhttp.MessageSegment.reply(aiocq_event.message_id) + aiocq_msg + aiocq_msg = ( + aiocqhttp.MessageSegment.reply(aiocq_event.message_id) + aiocq_msg + ) - return await self.bot.send( - aiocq_event, - aiocq_msg - ) + return await self.bot.send(aiocq_event, aiocq_msg) async def is_muted(self, group_id: int) -> bool: return False @@ -213,24 +224,30 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter): def register_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): async def on_message(event: aiocqhttp.Event): self.bot_account_id = event.self_id try: - return await callback(await self.event_converter.target2yiri(event), self) - except: + return await callback( + await self.event_converter.target2yiri(event), self + ) + except Exception: traceback.print_exc() if event_type == platform_events.GroupMessage: - self.bot.on_message("group")(on_message) + self.bot.on_message('group')(on_message) elif event_type == platform_events.FriendMessage: - self.bot.on_message("private")(on_message) + self.bot.on_message('private')(on_message) def unregister_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): return super().unregister_listener(event_type, callback) diff --git a/pkg/platform/sources/dingtalk.py b/pkg/platform/sources/dingtalk.py index aa768039..52f4a832 100644 --- a/pkg/platform/sources/dingtalk.py +++ b/pkg/platform/sources/dingtalk.py @@ -1,37 +1,30 @@ - import traceback import typing from libs.dingtalk_api.dingtalkevent import DingTalkEvent from pkg.platform.types import message as platform_message from pkg.platform.adapter import MessagePlatformAdapter -from pkg.platform.types import events as platform_events, message as platform_message -from pkg.core import app from .. import adapter -from ...pipeline.longtext.strategies import forward from ...core import app -from ..types import message as platform_message from ..types import events as platform_events from ..types import entities as platform_entities -from ...command.errors import ParamNotEnoughError from libs.dingtalk_api.api import DingTalkClient import datetime class DingTalkMessageConverter(adapter.MessageConverter): - @staticmethod - async def yiri2target( - message_chain:platform_message.MessageChain - ): + async def yiri2target(message_chain: platform_message.MessageChain): for msg in message_chain: if type(msg) is platform_message.Plain: return msg.text @staticmethod - async def target2yiri(event:DingTalkEvent, bot_name:str): + async def target2yiri(event: DingTalkEvent, bot_name: str): yiri_msg_list = [] yiri_msg_list.append( - platform_message.Source(id = event.incoming_message.message_id,time=datetime.datetime.now()) + platform_message.Source( + id=event.incoming_message.message_id, time=datetime.datetime.now() + ) ) for atUser in event.incoming_message.at_users: @@ -39,7 +32,7 @@ class DingTalkMessageConverter(adapter.MessageConverter): yiri_msg_list.append(platform_message.At(target=bot_name)) if event.content: - text_content = event.content.replace("@"+bot_name, '') + text_content = event.content.replace('@' + bot_name, '') yiri_msg_list.append(platform_message.Plain(text=text_content)) if event.picture: yiri_msg_list.append(platform_message.Image(base64=event.picture)) @@ -47,60 +40,51 @@ class DingTalkMessageConverter(adapter.MessageConverter): yiri_msg_list.append(platform_message.Voice(base64=event.audio)) chain = platform_message.MessageChain(yiri_msg_list) - + return chain class DingTalkEventConverter(adapter.EventConverter): - @staticmethod - async def yiri2target( - event:platform_events.MessageEvent - ): + async def yiri2target(event: platform_events.MessageEvent): return event.source_platform_object @staticmethod - async def target2yiri( - event:DingTalkEvent, - bot_name:str - ): - + async def target2yiri(event: DingTalkEvent, bot_name: str): message_chain = await DingTalkMessageConverter.target2yiri(event, bot_name) - if event.conversation == 'FriendMessage': - return platform_events.FriendMessage( sender=platform_entities.Friend( id=event.incoming_message.sender_id, - nickname = event.incoming_message.sender_nick, - remark="" + nickname=event.incoming_message.sender_nick, + remark='', ), - message_chain = message_chain, - time = event.incoming_message.create_at, + message_chain=message_chain, + time=event.incoming_message.create_at, source_platform_object=event, ) elif event.conversation == 'GroupMessage': sender = platform_entities.GroupMember( - id = event.incoming_message.sender_id, + id=event.incoming_message.sender_id, member_name=event.incoming_message.sender_nick, - permission= 'MEMBER', - group = platform_entities.Group( - id = event.incoming_message.conversation_id, - name = event.incoming_message.conversation_title, - permission=platform_entities.Permission.Member + permission='MEMBER', + group=platform_entities.Group( + id=event.incoming_message.conversation_id, + name=event.incoming_message.conversation_title, + permission=platform_entities.Permission.Member, ), special_title='', join_timestamp=0, last_speak_timestamp=0, - mute_time_remaining=0 + mute_time_remaining=0, ) time = event.incoming_message.create_at return platform_events.GroupMessage( - sender =sender, - message_chain = message_chain, - time = time, - source_platform_object=event + sender=sender, + message_chain=message_chain, + time=time, + source_platform_object=event, ) @@ -112,28 +96,28 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter): event_converter: DingTalkEventConverter = DingTalkEventConverter() config: dict - def __init__(self,config:dict,ap:app.Application): + def __init__(self, config: dict, ap: app.Application): self.config = config self.ap = ap required_keys = [ - "client_id", - "client_secret", - "robot_name", - "robot_code", + 'client_id', + 'client_secret', + 'robot_name', + 'robot_code', ] missing_keys = [key for key in required_keys if key not in config] if missing_keys: - raise ParamNotEnoughError("钉钉缺少相关配置项,请查看文档或联系管理员") + raise Exception('钉钉缺少相关配置项,请查看文档或联系管理员') + + self.bot_account_id = self.config['robot_name'] - self.bot_account_id = self.config["robot_name"] - self.bot = DingTalkClient( - client_id=config["client_id"], - client_secret=config["client_secret"], - robot_name = config["robot_name"], - robot_code=config["robot_code"] + client_id=config['client_id'], + client_secret=config['client_secret'], + robot_name=config['robot_name'], + robot_code=config['robot_code'], ) - + async def reply_message( self, message_source: platform_events.MessageEvent, @@ -146,17 +130,16 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter): incoming_message = event.incoming_message content = await DingTalkMessageConverter.yiri2target(message) - await self.bot.send_message(content,incoming_message) - + await self.bot.send_message(content, incoming_message) async def send_message( self, target_type: str, target_id: str, message: platform_message.MessageChain ): content = await DingTalkMessageConverter.yiri2target(message) if target_type == 'person': - await self.bot.send_proactive_message_to_one(target_id,content) + await self.bot.send_proactive_message_to_one(target_id, content) if target_type == 'group': - await self.bot.send_proactive_message_to_group(target_id,content) + await self.bot.send_proactive_message_to_group(target_id, content) def register_listener( self, @@ -168,15 +151,18 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter): async def on_message(event: DingTalkEvent): try: return await callback( - await self.event_converter.target2yiri(event, self.config["robot_name"]), self + await self.event_converter.target2yiri( + event, self.config['robot_name'] + ), + self, ) - except: + except Exception: traceback.print_exc() if event_type == platform_events.FriendMessage: - self.bot.on_message("FriendMessage")(on_message) + self.bot.on_message('FriendMessage')(on_message) elif event_type == platform_events.GroupMessage: - self.bot.on_message("GroupMessage")(on_message) + self.bot.on_message('GroupMessage')(on_message) async def run_async(self): await self.bot.start() @@ -187,7 +173,8 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter): async def unregister_listener( self, event_type: type, - callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, MessagePlatformAdapter], None + ], ): return super().unregister_listener(event_type, callback) - diff --git a/pkg/platform/sources/discord.py b/pkg/platform/sources/discord.py index 961b031a..07dd586f 100644 --- a/pkg/platform/sources/discord.py +++ b/pkg/platform/sources/discord.py @@ -3,39 +3,32 @@ from __future__ import annotations import discord import typing -import asyncio -import traceback -import time import re import base64 import uuid -import json import os import datetime import aiohttp from .. import adapter -from ...pipeline.longtext.strategies import forward from ...core import app from ..types import message as platform_message from ..types import events as platform_events from ..types import entities as platform_entities -from ...utils import image class DiscordMessageConverter(adapter.MessageConverter): - @staticmethod async def yiri2target( - message_chain: platform_message.MessageChain + message_chain: platform_message.MessageChain, ) -> typing.Tuple[str, typing.List[discord.File]]: for ele in message_chain: if isinstance(ele, platform_message.At): message_chain.remove(ele) break - text_string = "" + text_string = '' image_files = [] for ele in message_chain: @@ -49,46 +42,49 @@ class DiscordMessageConverter(adapter.MessageConverter): async with session.get(ele.url) as response: image_bytes = await response.read() elif ele.path: - with open(ele.path, "rb") as f: + with open(ele.path, 'rb') as f: image_bytes = f.read() - image_files.append(discord.File(fp=image_bytes, filename=f"{uuid.uuid4()}.png")) + image_files.append( + discord.File(fp=image_bytes, filename=f'{uuid.uuid4()}.png') + ) elif isinstance(ele, platform_message.Plain): text_string += ele.text elif isinstance(ele, platform_message.Forward): for node in ele.node_list: - text_string, image_files = await DiscordMessageConverter.yiri2target(node.message_chain) + ( + text_string, + image_files, + ) = await DiscordMessageConverter.yiri2target(node.message_chain) text_string += text_string image_files.extend(image_files) return text_string, image_files @staticmethod - async def target2yiri( - message: discord.Message - ) -> platform_message.MessageChain: + async def target2yiri(message: discord.Message) -> platform_message.MessageChain: lb_msg_list = [] msg_create_time = datetime.datetime.fromtimestamp( int(message.created_at.timestamp()) ) - lb_msg_list.append( - platform_message.Source(id=message.id, time=msg_create_time) - ) + lb_msg_list.append(platform_message.Source(id=message.id, time=msg_create_time)) element_list = [] - def text_element_recur(text_ele: str) -> list[platform_message.MessageComponent]: - if text_ele == "": + def text_element_recur( + text_ele: str, + ) -> list[platform_message.MessageComponent]: + if text_ele == '': return [] # <@1234567890> # @everyone # @here - at_pattern = re.compile(r"(@everyone|@here|<@[\d]+>)") + at_pattern = re.compile(r'(@everyone|@here|<@[\d]+>)') at_matches = at_pattern.findall(text_ele) - + if len(at_matches) > 0: mid_at = at_matches[0] @@ -96,18 +92,19 @@ class DiscordMessageConverter(adapter.MessageConverter): mid_at_component = [] - if mid_at == "@everyone" or mid_at == "@here": + if mid_at == '@everyone' or mid_at == '@here': mid_at_component.append(platform_message.AtAll()) else: mid_at_component.append(platform_message.At(target=mid_at[2:-1])) - return text_element_recur(text_split[0]) + \ - mid_at_component + \ - text_element_recur(text_split[1]) + return ( + text_element_recur(text_split[0]) + + mid_at_component + + text_element_recur(text_split[1]) + ) else: return [platform_message.Plain(text=text_ele)] - element_list.extend(text_element_recur(message.content)) # attachments @@ -115,28 +112,27 @@ class DiscordMessageConverter(adapter.MessageConverter): async with aiohttp.ClientSession(trust_env=True) as session: async with session.get(attachment.url) as response: image_data = await response.read() - image_base64 = base64.b64encode(image_data).decode("utf-8") - image_format = response.headers["Content-Type"] - element_list.append(platform_message.Image(base64=f"data:{image_format};base64,{image_base64}")) + image_base64 = base64.b64encode(image_data).decode('utf-8') + image_format = response.headers['Content-Type'] + element_list.append( + platform_message.Image( + base64=f'data:{image_format};base64,{image_base64}' + ) + ) return platform_message.MessageChain(element_list) class DiscordEventConverter(adapter.EventConverter): - @staticmethod - async def yiri2target( - event: platform_events.Event - ) -> discord.Message: + async def yiri2target(event: platform_events.Event) -> discord.Message: pass @staticmethod - async def target2yiri( - event: discord.Message - ) -> platform_events.Event: + async def target2yiri(event: discord.Message) -> platform_events.Event: message_chain = await DiscordMessageConverter.target2yiri(event) - if type(event.channel) == discord.DMChannel: + if isinstance(event.channel, discord.DMChannel): return platform_events.FriendMessage( sender=platform_entities.Friend( id=event.author.id, @@ -147,7 +143,7 @@ class DiscordEventConverter(adapter.EventConverter): time=event.created_at.timestamp(), source_platform_object=event, ) - elif type(event.channel) == discord.TextChannel: + elif isinstance(event.channel, discord.TextChannel): return platform_events.GroupMessage( sender=platform_entities.GroupMember( id=event.author.id, @@ -158,7 +154,7 @@ class DiscordEventConverter(adapter.EventConverter): name=event.channel.name, permission=platform_entities.Permission.Member, ), - special_title="", + special_title='', join_timestamp=0, last_speak_timestamp=0, mute_time_remaining=0, @@ -170,7 +166,6 @@ class DiscordEventConverter(adapter.EventConverter): class DiscordAdapter(adapter.MessagePlatformAdapter): - bot: discord.Client bot_account_id: str # 用于在流水线中识别at是否是本bot,直接以bot_name作为标识 @@ -191,12 +186,11 @@ class DiscordAdapter(adapter.MessagePlatformAdapter): self.config = config self.ap = ap - self.bot_account_id = self.config["client_id"] + self.bot_account_id = self.config['client_id'] adapter_self = self class MyClient(discord.Client): - async def on_message(self: discord.Client, message: discord.Message): if message.author.id == self.user.id or message.author.bot: return @@ -209,11 +203,11 @@ class DiscordAdapter(adapter.MessagePlatformAdapter): args = {} - if os.getenv("http_proxy"): - args["proxy"] = os.getenv("http_proxy") + if os.getenv('http_proxy'): + args['proxy'] = os.getenv('http_proxy') self.bot = MyClient(intents=intents, **args) - + async def send_message( self, target_type: str, target_id: str, message: platform_message.MessageChain ): @@ -229,17 +223,17 @@ class DiscordAdapter(adapter.MessagePlatformAdapter): assert isinstance(message_source.source_platform_object, discord.Message) args = { - "content": msg_to_send, + 'content': msg_to_send, } if len(image_files) > 0: - args["files"] = image_files + args['files'] = image_files if quote_origin: - args["reference"] = message_source.source_platform_object + args['reference'] = message_source.source_platform_object if message.has(platform_message.At): - args["mention_author"] = True + args['mention_author'] = True await message_source.source_platform_object.channel.send(**args) @@ -249,20 +243,24 @@ class DiscordAdapter(adapter.MessagePlatformAdapter): def register_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): self.listeners[event_type] = callback def unregister_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): self.listeners.pop(event_type) async def run_async(self): async with self.bot: - await self.bot.start(self.config["token"], reconnect=True) + await self.bot.start(self.config['token'], reconnect=True) async def kill(self) -> bool: await self.bot.close() diff --git a/pkg/platform/sources/gewechat.py b/pkg/platform/sources/gewechat.py index 869e05c6..2c9e5733 100644 --- a/pkg/platform/sources/gewechat.py +++ b/pkg/platform/sources/gewechat.py @@ -8,18 +8,13 @@ import traceback import time import re import base64 -import uuid -import json -import os import copy -import datetime import threading import quart import aiohttp from .. import adapter -from ...pipeline.longtext.strategies import forward from ...core import app from ..types import message as platform_message from ..types import events as platform_events @@ -29,109 +24,123 @@ import xml.etree.ElementTree as ET class GewechatMessageConverter(adapter.MessageConverter): - def __init__(self, config: dict): self.config = config @staticmethod - async def yiri2target( - message_chain: platform_message.MessageChain - ) -> list[dict]: + async def yiri2target(message_chain: platform_message.MessageChain) -> list[dict]: content_list = [] for component in message_chain: if isinstance(component, platform_message.At): - content_list.append({"type": "at", "target": component.target}) + content_list.append({'type': 'at', 'target': component.target}) elif isinstance(component, platform_message.Plain): - content_list.append({"type": "text", "content": component.text}) + content_list.append({'type': 'text', 'content': component.text}) elif isinstance(component, platform_message.Image): if not component.url: pass - content_list.append({"type": "image", "image": component.url}) - + content_list.append({'type': 'image', 'image': component.url}) elif isinstance(component, platform_message.Voice): - content_list.append({"type": "voice", "url": component.url, "length": component.length}) + content_list.append( + {'type': 'voice', 'url': component.url, 'length': component.length} + ) elif isinstance(component, platform_message.Forward): for node in component.node_list: - content_list.extend(await GewechatMessageConverter.yiri2target(node.message_chain)) + content_list.extend( + await GewechatMessageConverter.yiri2target(node.message_chain) + ) return content_list async def target2yiri( - self, - message: dict, - bot_account_id: str + self, message: dict, bot_account_id: str ) -> platform_message.MessageChain: - - - - if message["Data"]["MsgType"] == 1: + if message['Data']['MsgType'] == 1: # 检查消息开头,如果有 wxid_sbitaz0mt65n22:\n 则删掉 - regex = re.compile(r"^wxid_.*:") + regex = re.compile(r'^wxid_.*:') # print(message) - line_split = message["Data"]["Content"]["string"].split("\n") + line_split = message['Data']['Content']['string'].split('\n') if len(line_split) > 0 and regex.match(line_split[0]): - message["Data"]["Content"]["string"] = "\n".join(line_split[1:]) - + message['Data']['Content']['string'] = '\n'.join(line_split[1:]) # 正则表达式模式,匹配'@'后跟任意数量的非空白字符 pattern = r'@\S+' - at_string = f"@{bot_account_id}" + at_string = f'@{bot_account_id}' content_list = [] - if at_string in message["Data"]["Content"]["string"]: + if at_string in message['Data']['Content']['string']: content_list.append(platform_message.At(target=bot_account_id)) - content_list.append(platform_message.Plain(message["Data"]["Content"]["string"].replace(at_string, '', 1))) + content_list.append( + platform_message.Plain( + message['Data']['Content']['string'].replace(at_string, '', 1) + ) + ) # 更优雅的替换改名后@机器人,仅仅限于单独AT的情况 - elif "PushContent" in message['Data'] and '在群聊中@了你' in message["Data"]["PushContent"]: - if '@所有人' in message["Data"]["Content"]["string"]: # at全员时候传入atll不当作at自己 + elif ( + 'PushContent' in message['Data'] + and '在群聊中@了你' in message['Data']['PushContent'] + ): + if ( + '@所有人' in message['Data']['Content']['string'] + ): # at全员时候传入atll不当作at自己 content_list.append(platform_message.AtAll()) else: content_list.append(platform_message.At(target=bot_account_id)) - content_list.append(platform_message.Plain(re.sub(pattern, '', message["Data"]["Content"]["string"]))) + content_list.append( + platform_message.Plain( + re.sub(pattern, '', message['Data']['Content']['string']) + ) + ) else: - content_list = [platform_message.Plain(message["Data"]["Content"]["string"])] + content_list = [ + platform_message.Plain(message['Data']['Content']['string']) + ] return platform_message.MessageChain(content_list) - - elif message["Data"]["MsgType"] == 3: - image_xml = message["Data"]["Content"]["string"] - if not image_xml: - return platform_message.MessageChain([ - platform_message.Plain(text="[图片内容为空]") - ]) + elif message['Data']['MsgType'] == 3: + image_xml = message['Data']['Content']['string'] + if not image_xml: + return platform_message.MessageChain( + [platform_message.Plain(text='[图片内容为空]')] + ) try: base64_str, image_format = await image.get_gewechat_image_base64( - gewechat_url=self.config["gewechat_url"], - gewechat_file_url=self.config["gewechat_file_url"], - app_id=self.config["app_id"], + gewechat_url=self.config['gewechat_url'], + gewechat_file_url=self.config['gewechat_file_url'], + app_id=self.config['app_id'], xml_content=image_xml, - token=self.config["token"], + token=self.config['token'], image_type=2, ) - return platform_message.MessageChain([ - platform_message.Image( - base64=f"data:image/{image_format};base64,{base64_str}" - ) - ]) + return platform_message.MessageChain( + [ + platform_message.Image( + base64=f'data:image/{image_format};base64,{base64_str}' + ) + ] + ) except Exception as e: - print(f"处理图片消息失败: {str(e)}") - return platform_message.MessageChain([ - platform_message.Plain(text=f"[图片处理失败]") - ]) - elif message["Data"]["MsgType"] == 34: - audio_base64 = message["Data"]["ImgBuf"]["buffer"] + print(f'处理图片消息失败: {str(e)}') + return platform_message.MessageChain( + [platform_message.Plain(text='[图片处理失败]')] + ) + elif message['Data']['MsgType'] == 34: + audio_base64 = message['Data']['ImgBuf']['buffer'] return platform_message.MessageChain( - [platform_message.Voice(base64=f"data:audio/silk;base64,{audio_base64}")] + [ + platform_message.Voice( + base64=f'data:audio/silk;base64,{audio_base64}' + ) + ] ) - elif message["Data"]["MsgType"] == 49: + elif message['Data']['MsgType'] == 49: # 支持微信聊天记录的消息类型,将 XML 内容转换为 MessageChain 传递 try: - content = message["Data"]["Content"]["string"] + content = message['Data']['Content']['string'] # 有三种可能的消息结构weid开头,私聊直接和直接 if content.startswith('wxid'): xml_list = content.split('\n')[2:] @@ -145,140 +154,145 @@ class GewechatMessageConverter(adapter.MessageConverter): content_data = ET.fromstring(xml_data) # print(xml_data) # 拿到细分消息类型,按照gewe接口中描述 - ''' + """ 小程序:33/36 引用消息:57 转账消息:2000 红包消息:2001 视频号消息:51 - ''' + """ appmsg_data = content_data.find('.//appmsg') data_type = appmsg_data.find('.//type').text if data_type == '57': user_data = appmsg_data.find('.//title').text # 拿到用户消息 - quote_data = appmsg_data.find('.//refermsg').find('.//content').text # 引用原文 - sender_id = appmsg_data.find('.//refermsg').find('.//chatusr').text # 引用用户id + quote_data = ( + appmsg_data.find('.//refermsg').find('.//content').text + ) # 引用原文 + sender_id = ( + appmsg_data.find('.//refermsg').find('.//chatusr').text + ) # 引用用户id from_name = message['Data']['FromUserName']['string'] - message_list =[] - if message['Wxid'] == sender_id and from_name.endswith('@chatroom'): # 因为引用机制暂时无法响应用户,所以当引用用户是机器人是构建一个at激活机器人 + message_list = [] + if ( + message['Wxid'] == sender_id and from_name.endswith('@chatroom') + ): # 因为引用机制暂时无法响应用户,所以当引用用户是机器人是构建一个at激活机器人 message_list.append(platform_message.At(target=bot_account_id)) - message_list.append(platform_message.Quote( + message_list.append( + platform_message.Quote( sender_id=sender_id, origin=platform_message.MessageChain( [platform_message.Plain(quote_data)] - ))) + ), + ) + ) message_list.append(platform_message.Plain(user_data)) return platform_message.MessageChain(message_list) elif data_type == '51': return platform_message.MessageChain( - [platform_message.Plain(text=f'[视频号消息]')] + [platform_message.Plain(text='[视频号消息]')] ) # print(content_data) elif data_type == '2000': return platform_message.MessageChain( - [platform_message.Plain(text=f'[转账消息]')] + [platform_message.Plain(text='[转账消息]')] ) elif data_type == '2001': return platform_message.MessageChain( - [platform_message.Plain(text=f'[红包消息]')] + [platform_message.Plain(text='[红包消息]')] ) elif data_type == '5': return platform_message.MessageChain( - [platform_message.Plain(text=f'[公众号消息]')] + [platform_message.Plain(text='[公众号消息]')] ) elif data_type == '33' or data_type == '36': return platform_message.MessageChain( - [platform_message.Plain(text=f'[小程序消息]')] + [platform_message.Plain(text='[小程序消息]')] ) # print(data_type.text) else: - - try: content_bytes = content.encode('utf-8') decoded_content = base64.b64decode(content_bytes) return platform_message.MessageChain( [platform_message.Unknown(content=decoded_content)] ) - except Exception as e: + except Exception: return platform_message.MessageChain( [platform_message.Plain(text=content)] ) except Exception as e: - print(f"Error processing type 49 message: {str(e)}") + print(f'Error processing type 49 message: {str(e)}') return platform_message.MessageChain( - [platform_message.Plain(text="[无法解析的消息]")] + [platform_message.Plain(text='[无法解析的消息]')] ) -class GewechatEventConverter(adapter.EventConverter): +class GewechatEventConverter(adapter.EventConverter): def __init__(self, config: dict): self.config = config self.message_converter = GewechatMessageConverter(config) @staticmethod - async def yiri2target( - event: platform_events.MessageEvent - ) -> dict: + async def yiri2target(event: platform_events.MessageEvent) -> dict: pass async def target2yiri( - self, - event: dict, - bot_account_id: str + self, event: dict, bot_account_id: str ) -> platform_events.MessageEvent: # print(event) # 排除自己发消息回调回答问题 if event['Wxid'] == event['Data']['FromUserName']['string']: return None # 排除公众号以及微信团队消息 - if event['Data']['FromUserName']['string'].startswith('gh_')\ - or event['Data']['FromUserName']['string'].startswith('weixin'): + if event['Data']['FromUserName']['string'].startswith('gh_') or event['Data'][ + 'FromUserName' + ]['string'].startswith('weixin'): return None - message_chain = await self.message_converter.target2yiri(copy.deepcopy(event), bot_account_id) + message_chain = await self.message_converter.target2yiri( + copy.deepcopy(event), bot_account_id + ) if not message_chain: return None - - if '@chatroom' in event["Data"]["FromUserName"]["string"]: + + if '@chatroom' in event['Data']['FromUserName']['string']: # 找出开头的 wxid_ 字符串,以:结尾 - sender_wxid = event["Data"]["Content"]["string"].split(":")[0] + sender_wxid = event['Data']['Content']['string'].split(':')[0] return platform_events.GroupMessage( sender=platform_entities.GroupMember( id=sender_wxid, - member_name=event["Data"]["FromUserName"]["string"], + member_name=event['Data']['FromUserName']['string'], permission=platform_entities.Permission.Member, group=platform_entities.Group( - id=event["Data"]["FromUserName"]["string"], - name=event["Data"]["FromUserName"]["string"], + id=event['Data']['FromUserName']['string'], + name=event['Data']['FromUserName']['string'], permission=platform_entities.Permission.Member, ), - special_title="", + special_title='', join_timestamp=0, last_speak_timestamp=0, mute_time_remaining=0, ), message_chain=message_chain, - time=event["Data"]["CreateTime"], + time=event['Data']['CreateTime'], source_platform_object=event, ) else: return platform_events.FriendMessage( sender=platform_entities.Friend( - id=event["Data"]["FromUserName"]["string"], - nickname=event["Data"]["FromUserName"]["string"], + id=event['Data']['FromUserName']['string'], + nickname=event['Data']['FromUserName']['string'], remark='', ), message_chain=message_chain, - time=event["Data"]["CreateTime"], + time=event['Data']['CreateTime'], source_platform_object=event, ) class GeWeChatAdapter(adapter.MessagePlatformAdapter): - - name: str = "gewechat" # 定义适配器名称 + name: str = 'gewechat' # 定义适配器名称 bot: gewechat_client.GewechatClient quart_app: quart.Quart @@ -296,7 +310,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter): typing.Type[platform_events.Event], typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], ] = {} - + def __init__(self, config: dict, ap: app.Application): self.config = config self.ap = ap @@ -310,21 +324,21 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter): async def gewechat_callback(): data = await quart.request.json # print(json.dumps(data, indent=4, ensure_ascii=False)) - + if 'data' in data: data['Data'] = data['data'] if 'type_name' in data: data['TypeName'] = data['type_name'] # print(json.dumps(data, indent=4, ensure_ascii=False)) - if 'testMsg' in data: return 'ok' elif 'TypeName' in data and data['TypeName'] == 'AddMsg': try: - - event = await self.event_converter.target2yiri(data.copy(), self.bot_account_id) - except Exception as e: + event = await self.event_converter.target2yiri( + data.copy(), self.bot_account_id + ) + except Exception: traceback.print_exc() if event.__class__ in self.listeners: @@ -333,65 +347,67 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter): return 'ok' async def send_message( - self, - target_type: str, - target_id: str, - message: platform_message.MessageChain + self, target_type: str, target_id: str, message: platform_message.MessageChain ): geweap_msg = await self.message_converter.yiri2target(message) # 此处加上群消息at处理 - ats = [item["target"] for item in geweap_msg if item["type"] == "at"] - + ats = [item['target'] for item in geweap_msg if item['type'] == 'at'] for msg in geweap_msg: # at主动发送消息 if msg['type'] == 'text': if ats: member_info = self.bot.get_chatroom_member_detail( - self.config["app_id"], - target_id, - ats[::-1] - )["data"] + self.config['app_id'], target_id, ats[::-1] + )['data'] for member in member_info: msg['content'] = f'@{member["nickName"]} {msg["content"]}' - self.bot.post_text(app_id=self.config['app_id'], to_wxid=target_id, content=msg['content'], - ats=",".join(ats)) + self.bot.post_text( + app_id=self.config['app_id'], + to_wxid=target_id, + content=msg['content'], + ats=','.join(ats), + ) elif msg['type'] == 'image': - - self.bot.post_image(app_id=self.config['app_id'], to_wxid=target_id, img_url=msg["image"]) - - + self.bot.post_image( + app_id=self.config['app_id'], + to_wxid=target_id, + img_url=msg['image'], + ) async def reply_message( self, message_source: platform_events.MessageEvent, message: platform_message.MessageChain, - quote_origin: bool = False + quote_origin: bool = False, ): content_list = await self.message_converter.yiri2target(message) - ats = [item["target"] for item in content_list if item["type"] == "at"] + ats = [item['target'] for item in content_list if item['type'] == 'at'] for msg in content_list: - if msg["type"] == "text": - + if msg['type'] == 'text': if ats: member_info = self.bot.get_chatroom_member_detail( - self.config["app_id"], - message_source.source_platform_object["Data"]["FromUserName"]["string"], - ats[::-1] - )["data"] + self.config['app_id'], + message_source.source_platform_object['Data']['FromUserName'][ + 'string' + ], + ats[::-1], + )['data'] for member in member_info: msg['content'] = f'@{member["nickName"]} {msg["content"]}' self.bot.post_text( - app_id=self.config["app_id"], - to_wxid=message_source.source_platform_object["Data"]["FromUserName"]["string"], - content=msg["content"], - ats=",".join(ats) + app_id=self.config['app_id'], + to_wxid=message_source.source_platform_object['Data'][ + 'FromUserName' + ]['string'], + content=msg['content'], + ats=','.join(ats), ) async def is_muted(self, group_id: int) -> bool: @@ -400,51 +416,57 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter): def register_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None] + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): self.listeners[event_type] = callback def unregister_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None] + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): pass async def run_async(self): - - if not self.config["token"]: + if not self.config['token']: async with aiohttp.ClientSession() as session: async with session.post( - f"{self.config['gewechat_url']}/v2/api/tools/getTokenId", - json={"app_id": self.config["app_id"]} + f'{self.config["gewechat_url"]}/v2/api/tools/getTokenId', + json={'app_id': self.config['app_id']}, ) as response: if response.status != 200: - raise Exception(f"获取gewechat token失败: {await response.text()}") - self.config["token"] = (await response.json())["data"] + raise Exception( + f'获取gewechat token失败: {await response.text()}' + ) + self.config['token'] = (await response.json())['data'] self.bot = gewechat_client.GewechatClient( - f"{self.config['gewechat_url']}/v2/api", - self.config["token"] + f'{self.config["gewechat_url"]}/v2/api', self.config['token'] ) - app_id, error_msg = self.bot.login(self.config["app_id"]) + app_id, error_msg = self.bot.login(self.config['app_id']) if error_msg: - raise Exception(f"Gewechat 登录失败: {error_msg}") + raise Exception(f'Gewechat 登录失败: {error_msg}') - self.config["app_id"] = app_id + self.config['app_id'] = app_id - self.ap.logger.info(f"Gewechat 登录成功,app_id: {app_id}") + self.ap.logger.info(f'Gewechat 登录成功,app_id: {app_id}') await self.ap.platform_mgr.write_back_config('gewechat', self, self.config) # 获取 nickname - profile = self.bot.get_profile(self.config["app_id"]) - self.bot_account_id = profile["data"]["nickName"] + profile = self.bot.get_profile(self.config['app_id']) + self.bot_account_id = profile['data']['nickName'] def thread_set_callback(): time.sleep(3) - ret = self.bot.set_callback(self.config["token"], self.config["callback_url"]) + ret = self.bot.set_callback( + self.config['token'], self.config['callback_url'] + ) print('设置 Gewechat 回调:', ret) threading.Thread(target=thread_set_callback).start() @@ -455,7 +477,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter): await self.quart_app.run_task( host='0.0.0.0', - port=self.config["port"], + port=self.config['port'], shutdown_trigger=shutdown_trigger_placeholder, ) diff --git a/pkg/platform/sources/lark.py b/pkg/platform/sources/lark.py index 4c87640b..1396ba3b 100644 --- a/pkg/platform/sources/lark.py +++ b/pkg/platform/sources/lark.py @@ -5,56 +5,53 @@ import lark_oapi import typing import asyncio import traceback -import time import re import base64 import uuid import json import datetime import hashlib -import base64 from Crypto.Cipher import AES import aiohttp import lark_oapi.ws.exception import quart -from flask import jsonify from lark_oapi.api.im.v1 import * -from lark_oapi.api.verification.v1 import GetVerificationRequest from .. import adapter -from ...pipeline.longtext.strategies import forward from ...core import app from ..types import message as platform_message from ..types import events as platform_events from ..types import entities as platform_entities -from ...utils import image -class AESCipher(object): +class AESCipher(object): def __init__(self, key): self.bs = AES.block_size - self.key=hashlib.sha256(AESCipher.str_to_bytes(key)).digest() + self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest() + @staticmethod def str_to_bytes(data): - u_type = type(b"".decode('utf8')) + u_type = type(b''.decode('utf8')) if isinstance(data, u_type): return data.encode('utf8') return data + @staticmethod def _unpad(s): - return s[:-ord(s[len(s) - 1:])] + return s[: -ord(s[len(s) - 1 :])] + def decrypt(self, enc): - iv = enc[:AES.block_size] + iv = enc[: AES.block_size] cipher = AES.new(self.key, AES.MODE_CBC, iv) - return self._unpad(cipher.decrypt(enc[AES.block_size:])) + return self._unpad(cipher.decrypt(enc[AES.block_size :])) + def decrypt_string(self, enc): enc = base64.b64decode(enc) - return self.decrypt(enc).decode('utf8') + return self.decrypt(enc).decode('utf8') class LarkMessageConverter(adapter.MessageConverter): - @staticmethod async def yiri2target( message_chain: platform_message.MessageChain, api_client: lark_oapi.Client @@ -65,15 +62,14 @@ class LarkMessageConverter(adapter.MessageConverter): for msg in message_chain: if isinstance(msg, platform_message.Plain): - pending_paragraph.append({"tag": "md", "text": msg.text}) + pending_paragraph.append({'tag': 'md', 'text': msg.text}) elif isinstance(msg, platform_message.At): pending_paragraph.append( - {"tag": "at", "user_id": msg.target, "style": []} + {'tag': 'at', 'user_id': msg.target, 'style': []} ) elif isinstance(msg, platform_message.AtAll): - pending_paragraph.append({"tag": "at", "user_id": "all", "style": []}) + pending_paragraph.append({'tag': 'at', 'user_id': 'all', 'style': []}) elif isinstance(msg, platform_message.Image): - image_bytes = None if msg.base64: @@ -83,14 +79,14 @@ class LarkMessageConverter(adapter.MessageConverter): async with session.get(msg.url) as response: image_bytes = await response.read() elif msg.path: - with open(msg.path, "rb") as f: + with open(msg.path, 'rb') as f: image_bytes = f.read() request: CreateImageRequest = ( CreateImageRequest.builder() .request_body( CreateImageRequestBody.builder() - .image_type("message") + .image_type('message') .image(image_bytes) .build() ) @@ -103,7 +99,7 @@ class LarkMessageConverter(adapter.MessageConverter): if not response.success(): raise Exception( - f"client.im.v1.image.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}" + f'client.im.v1.image.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}' ) image_key = response.data.image_key @@ -112,15 +108,19 @@ class LarkMessageConverter(adapter.MessageConverter): message_elements.append( [ { - "tag": "img", - "image_key": image_key, + 'tag': 'img', + 'image_key': image_key, } ] ) pending_paragraph = [] elif isinstance(msg, platform_message.Forward): for node in msg.node_list: - message_elements.extend(await LarkMessageConverter.yiri2target(node.message_chain, api_client)) + message_elements.extend( + await LarkMessageConverter.yiri2target( + node.message_chain, api_client + ) + ) if pending_paragraph: message_elements.append(pending_paragraph) @@ -144,15 +144,15 @@ class LarkMessageConverter(adapter.MessageConverter): platform_message.Source(id=message.message_id, time=msg_create_time) ) - if message.message_type == "text": + if message.message_type == 'text': element_list = [] def text_element_recur(text_ele: dict) -> list[dict]: - if text_ele["text"] == "": + if text_ele['text'] == '': return [] - at_pattern = re.compile(r"@_user_[\d]+") - at_matches = at_pattern.findall(text_ele["text"]) + at_pattern = re.compile(r'@_user_[\d]+') + at_matches = at_pattern.findall(text_ele['text']) name_mapping = {} for mathc in at_matches: @@ -165,7 +165,7 @@ class LarkMessageConverter(adapter.MessageConverter): return [text_ele] # 只处理第一个,剩下的递归处理 - text_split = text_ele["text"].split(list(name_mapping.keys())[0]) + text_split = text_ele['text'].split(list(name_mapping.keys())[0]) new_list = [] @@ -173,58 +173,58 @@ class LarkMessageConverter(adapter.MessageConverter): right_text = text_split[1] new_list.extend( - text_element_recur({"tag": "text", "text": left_text, "style": []}) + text_element_recur({'tag': 'text', 'text': left_text, 'style': []}) ) new_list.append( { - "tag": "at", - "user_id": list(name_mapping.keys())[0], - "user_name": name_mapping[list(name_mapping.keys())[0]], - "style": [], + 'tag': 'at', + 'user_id': list(name_mapping.keys())[0], + 'user_name': name_mapping[list(name_mapping.keys())[0]], + 'style': [], } ) new_list.extend( - text_element_recur({"tag": "text", "text": right_text, "style": []}) + text_element_recur({'tag': 'text', 'text': right_text, 'style': []}) ) return new_list element_list = text_element_recur( - {"tag": "text", "text": message_content["text"], "style": []} + {'tag': 'text', 'text': message_content['text'], 'style': []} ) - message_content = {"title": "", "content": element_list} + message_content = {'title': '', 'content': element_list} - elif message.message_type == "post": + elif message.message_type == 'post': new_list = [] - for ele in message_content["content"]: + for ele in message_content['content']: if type(ele) is dict: new_list.append(ele) elif type(ele) is list: new_list.extend(ele) - message_content["content"] = new_list - elif message.message_type == "image": - message_content["content"] = [ - {"tag": "img", "image_key": message_content["image_key"], "style": []} + message_content['content'] = new_list + elif message.message_type == 'image': + message_content['content'] = [ + {'tag': 'img', 'image_key': message_content['image_key'], 'style': []} ] - for ele in message_content["content"]: - if ele["tag"] == "text": - lb_msg_list.append(platform_message.Plain(text=ele["text"])) - elif ele["tag"] == "at": - lb_msg_list.append(platform_message.At(target=ele["user_name"])) - elif ele["tag"] == "img": - image_key = ele["image_key"] + for ele in message_content['content']: + if ele['tag'] == 'text': + lb_msg_list.append(platform_message.Plain(text=ele['text'])) + elif ele['tag'] == 'at': + lb_msg_list.append(platform_message.At(target=ele['user_name'])) + elif ele['tag'] == 'img': + image_key = ele['image_key'] request: GetMessageResourceRequest = ( GetMessageResourceRequest.builder() .message_id(message.message_id) .file_key(image_key) - .type("image") + .type('image') .build() ) @@ -234,17 +234,17 @@ class LarkMessageConverter(adapter.MessageConverter): if not response.success(): raise Exception( - f"client.im.v1.message_resource.get failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}" + f'client.im.v1.message_resource.get failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}' ) image_bytes = response.file.read() image_base64 = base64.b64encode(image_bytes).decode() - image_format = response.raw.headers["content-type"] + image_format = response.raw.headers['content-type'] lb_msg_list.append( platform_message.Image( - base64=f"data:{image_format};base64,{image_base64}" + base64=f'data:{image_format};base64,{image_base64}' ) ) @@ -252,7 +252,6 @@ class LarkMessageConverter(adapter.MessageConverter): class LarkEventConverter(adapter.EventConverter): - @staticmethod async def yiri2target( event: platform_events.MessageEvent, @@ -267,17 +266,17 @@ class LarkEventConverter(adapter.EventConverter): event.event.message, api_client ) - if event.event.message.chat_type == "p2p": + if event.event.message.chat_type == 'p2p': return platform_events.FriendMessage( sender=platform_entities.Friend( id=event.event.sender.sender_id.open_id, nickname=event.event.sender.sender_id.union_id, - remark="", + remark='', ), message_chain=message_chain, time=event.event.message.create_time, ) - elif event.event.message.chat_type == "group": + elif event.event.message.chat_type == 'group': return platform_events.GroupMessage( sender=platform_entities.GroupMember( id=event.event.sender.sender_id.open_id, @@ -285,10 +284,10 @@ class LarkEventConverter(adapter.EventConverter): permission=platform_entities.Permission.Member, group=platform_entities.Group( id=event.event.message.chat_id, - name="", + name='', permission=platform_entities.Permission.Member, ), - special_title="", + special_title='', join_timestamp=0, last_speak_timestamp=0, mute_time_remaining=0, @@ -299,7 +298,6 @@ class LarkEventConverter(adapter.EventConverter): class LarkAdapter(adapter.MessagePlatformAdapter): - bot: lark_oapi.ws.Client api_client: lark_oapi.Client @@ -333,17 +331,15 @@ class LarkAdapter(adapter.MessagePlatformAdapter): data = cipher.decrypt_string(data['encrypt']) data = json.loads(data) - type = data.get("type") - if type is None : + type = data.get('type') + if type is None: context = EventContext(data) type = context.header.event_type - + if 'url_verification' == type: - print(data.get("challenge")) + print(data.get('challenge')) # todo 验证verification token - return { - "challenge": data.get("challenge") - } + return {'challenge': data.get('challenge')} context = EventContext(data) type = context.header.event_type p2v1 = P2ImMessageReceiveV1() @@ -355,20 +351,21 @@ class LarkAdapter(adapter.MessagePlatformAdapter): p2v1.schema = context.schema if 'im.message.receive_v1' == type: try: - event = await self.event_converter.target2yiri(p2v1, self.api_client) - except Exception as e: + event = await self.event_converter.target2yiri( + p2v1, self.api_client + ) + except Exception: traceback.print_exc() if event.__class__ in self.listeners: await self.listeners[event.__class__](event, self) - return {"code": 200, "message": "ok"} - except Exception as e: + return {'code': 200, 'message': 'ok'} + except Exception: traceback.print_exc() - return {"code": 500, "message": "error"} + return {'code': 500, 'message': 'error'} async def on_message(event: lark_oapi.im.v1.P2ImMessageReceiveV1): - lb_event = await self.event_converter.target2yiri(event, self.api_client) await self.listeners[type(lb_event)](lb_event, self) @@ -377,20 +374,20 @@ class LarkAdapter(adapter.MessagePlatformAdapter): asyncio.create_task(on_message(event)) event_handler = ( - lark_oapi.EventDispatcherHandler.builder("", "") + lark_oapi.EventDispatcherHandler.builder('', '') .register_p2_im_message_receive_v1(sync_on_message) .build() ) - self.bot_account_id = config["bot_name"] + self.bot_account_id = config['bot_name'] self.bot = lark_oapi.ws.Client( - config["app_id"], config["app_secret"], event_handler=event_handler + config['app_id'], config['app_secret'], event_handler=event_handler ) self.api_client = ( lark_oapi.Client.builder() - .app_id(config["app_id"]) - .app_secret(config["app_secret"]) + .app_id(config['app_id']) + .app_secret(config['app_secret']) .build() ) @@ -405,7 +402,6 @@ class LarkAdapter(adapter.MessagePlatformAdapter): message: platform_message.MessageChain, quote_origin: bool = False, ): - # 不再需要了,因为message_id已经被包含到message_chain中 # lark_event = await self.event_converter.yiri2target(message_source) lark_message = await self.message_converter.yiri2target( @@ -413,9 +409,9 @@ class LarkAdapter(adapter.MessagePlatformAdapter): ) final_content = { - "zh_cn": { - "title": "", - "content": lark_message, + 'zh_cn': { + 'title': '', + 'content': lark_message, }, } @@ -425,7 +421,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter): .request_body( ReplyMessageRequestBody.builder() .content(json.dumps(final_content)) - .msg_type("post") + .msg_type('post') .reply_in_thread(False) .uuid(str(uuid.uuid4())) .build() @@ -439,7 +435,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter): if not response.success(): raise Exception( - f"client.im.v1.message.reply failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}" + f'client.im.v1.message.reply failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}' ) async def is_muted(self, group_id: int) -> bool: @@ -479,6 +475,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter): else: raise e else: + async def shutdown_trigger_placeholder(): while True: await asyncio.sleep(1) @@ -488,5 +485,6 @@ class LarkAdapter(adapter.MessagePlatformAdapter): port=port, shutdown_trigger=shutdown_trigger_placeholder, ) + async def kill(self) -> bool: return False diff --git a/pkg/platform/sources/nakuru.py b/pkg/platform/sources/nakuru.py index 8dcf6e52..7038af1d 100644 --- a/pkg/platform/sources/nakuru.py +++ b/pkg/platform/sources/nakuru.py @@ -4,7 +4,6 @@ import asyncio import typing import traceback -import logging import nakuru @@ -19,6 +18,7 @@ from ...platform.types import events as platform_events class NakuruProjectMessageConverter(adapter_model.MessageConverter): """消息转换器""" + @staticmethod def yiri2target(message_chain: platform_message.MessageChain) -> list: msg_list = [] @@ -29,10 +29,12 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter): elif type(message_chain) is str: msg_list = [platform_message.Plain(message_chain)] else: - raise Exception("Unknown message type: " + str(message_chain) + str(type(message_chain))) - + raise Exception( + 'Unknown message type: ' + str(message_chain) + str(type(message_chain)) + ) + nakuru_msg_list = [] - + # 遍历并转换 for component in msg_list: if type(component) is platform_message.Plain: @@ -61,33 +63,43 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter): # 遍历并转换 for yiri_forward_node in yiri_forward_node_list: try: - content_list = NakuruProjectMessageConverter.yiri2target(yiri_forward_node.message_chain) + content_list = NakuruProjectMessageConverter.yiri2target( + yiri_forward_node.message_chain + ) nakuru_forward_node = nkc.Node( name=yiri_forward_node.sender_name, uin=yiri_forward_node.sender_id, - time=int(yiri_forward_node.time.timestamp()) if yiri_forward_node.time is not None else None, - content=content_list + time=int(yiri_forward_node.time.timestamp()) + if yiri_forward_node.time is not None + else None, + content=content_list, ) nakuru_forward_node_list.append(nakuru_forward_node) - except Exception as e: + except Exception: import traceback + traceback.print_exc() nakuru_msg_list.append(nakuru_forward_node_list) else: nakuru_msg_list.append(nkc.Plain(str(component))) - + return nakuru_msg_list @staticmethod - def target2yiri(message_chain: typing.Any, message_id: int = -1) -> platform_message.MessageChain: + def target2yiri( + message_chain: typing.Any, message_id: int = -1 + ) -> platform_message.MessageChain: """将Yiri的消息链转换为YiriMirai的消息链""" assert type(message_chain) is list yiri_msg_list = [] import datetime + # 添加Source组件以标记message_id等信息 - yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now())) + yiri_msg_list.append( + platform_message.Source(id=message_id, time=datetime.datetime.now()) + ) for component in message_chain: if type(component) is nkc.Plain: yiri_msg_list.append(platform_message.Plain(text=component.text)) @@ -106,6 +118,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter): class NakuruProjectEventConverter(adapter_model.EventConverter): """事件转换器""" + @staticmethod def yiri2target(event: typing.Type[platform_events.Event]): if event is platform_events.GroupMessage: @@ -113,28 +126,30 @@ class NakuruProjectEventConverter(adapter_model.EventConverter): elif event is platform_events.FriendMessage: return nakuru.FriendMessage else: - raise Exception("未支持转换的事件类型: " + str(event)) + raise Exception('未支持转换的事件类型: ' + str(event)) @staticmethod def target2yiri(event: typing.Any) -> platform_events.Event: - yiri_chain = NakuruProjectMessageConverter.target2yiri(event.message, event.message_id) + yiri_chain = NakuruProjectMessageConverter.target2yiri( + event.message, event.message_id + ) if type(event) is nakuru.FriendMessage: # 私聊消息事件 return platform_events.FriendMessage( sender=platform_entities.Friend( id=event.sender.user_id, nickname=event.sender.nickname, - remark=event.sender.nickname + remark=event.sender.nickname, ), message_chain=yiri_chain, - time=event.time + time=event.time, ) elif type(event) is nakuru.GroupMessage: # 群聊消息事件 - permission = "MEMBER" + permission = 'MEMBER' - if event.sender.role == "admin": - permission = "ADMINISTRATOR" - elif event.sender.role == "owner": - permission = "OWNER" + if event.sender.role == 'admin': + permission = 'ADMINISTRATOR' + elif event.sender.role == 'owner': + permission = 'OWNER' return platform_events.GroupMessage( sender=platform_entities.GroupMember( @@ -144,7 +159,7 @@ class NakuruProjectEventConverter(adapter_model.EventConverter): group=platform_entities.Group( id=event.group_id, name=event.sender.nickname, - permission=platform_entities.Permission.Member + permission=platform_entities.Permission.Member, ), special_title=event.sender.title, join_timestamp=0, @@ -152,14 +167,15 @@ class NakuruProjectEventConverter(adapter_model.EventConverter): mute_time_remaining=0, ), message_chain=yiri_chain, - time=event.time + time=event.time, ) else: - raise Exception("未支持转换的事件类型: " + str(event)) + raise Exception('未支持转换的事件类型: ' + str(event)) class NakuruAdapter(adapter_model.MessagePlatformAdapter): """nakuru-project适配器""" + bot: nakuru.CQHTTP bot_account_id: int @@ -186,12 +202,14 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): target_type: str, target_id: str, message: typing.Union[platform_message.MessageChain, list], - converted: bool = False + converted: bool = False, ): task = None - converted_msg = self.message_converter.yiri2target(message) if not converted else message - + converted_msg = ( + self.message_converter.yiri2target(message) if not converted else message + ) + # 检查是否有转发消息 has_forward = False for msg in converted_msg: @@ -200,19 +218,19 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): converted_msg = msg break if has_forward: - if target_type == "group": + if target_type == 'group': task = self.bot.sendGroupForwardMessage(int(target_id), converted_msg) - elif target_type == "person": + elif target_type == 'person': task = self.bot.sendPrivateForwardMessage(int(target_id), converted_msg) else: - raise Exception("Unknown target type: " + target_type) + raise Exception('Unknown target type: ' + target_type) else: - if target_type == "group": + if target_type == 'group': task = self.bot.sendGroupMessage(int(target_id), converted_msg) - elif target_type == "person": + elif target_type == 'person': task = self.bot.sendFriendMessage(int(target_id), converted_msg) else: - raise Exception("Unknown target type: " + target_type) + raise Exception('Unknown target type: ' + target_type) await task @@ -220,45 +238,45 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): self, message_source: platform_events.MessageEvent, message: platform_message.MessageChain, - quote_origin: bool = False + quote_origin: bool = False, ): message = self.message_converter.yiri2target(message) if quote_origin: # 在前方添加引用组件 - message.insert(0, nkc.Reply( + message.insert( + 0, + nkc.Reply( id=message_source.message_chain.message_id, - ) + ), ) if type(message_source) is platform_events.GroupMessage: await self.send_message( - "group", - message_source.sender.group.id, - message, - converted=True + 'group', message_source.sender.group.id, message, converted=True ) elif type(message_source) is platform_events.FriendMessage: await self.send_message( - "person", - message_source.sender.id, - message, - converted=True + 'person', message_source.sender.id, message, converted=True ) else: - raise Exception("Unknown message source type: " + str(type(message_source))) + raise Exception('Unknown message source type: ' + str(type(message_source))) def is_muted(self, group_id: int) -> bool: import time + # 检查是否被禁言 - group_member_info = asyncio.run(self.bot.getGroupMemberInfo(group_id, self.bot_account_id)) + group_member_info = asyncio.run( + self.bot.getGroupMemberInfo(group_id, self.bot_account_id) + ) return group_member_info.shut_up_timestamp > int(time.time()) def register_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None] + callback: typing.Callable[ + [platform_events.Event, adapter_model.MessagePlatformAdapter], None + ], ): try: - source_cls = NakuruProjectEventConverter.yiri2target(event_type) # 包装函数 @@ -268,9 +286,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): # 将包装函数和原函数的对应关系存入列表 self.listener_list.append( { - "event_type": event_type, - "callable": callback, - "wrapper": listener_wrapper, + 'event_type': event_type, + 'callable': callback, + 'wrapper': listener_wrapper, } ) @@ -283,7 +301,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): def unregister_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None] + callback: typing.Callable[ + [platform_events.Event, adapter_model.MessagePlatformAdapter], None + ], ): nakuru_event_name = self.event_converter.yiri2target(event_type).__name__ @@ -292,13 +312,16 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): # 从本对象的监听器列表中查找并删除 target_wrapper = None for listener in self.listener_list: - if listener["event_type"] == event_type and listener["callable"] == callback: - target_wrapper = listener["wrapper"] + if ( + listener['event_type'] == event_type + and listener['callable'] == callback + ): + target_wrapper = listener['wrapper'] self.listener_list.remove(listener) break if target_wrapper is None: - raise Exception("未找到对应的监听器") + raise Exception('未找到对应的监听器') for func in self.bot.event[nakuru_event_name]: if func.callable != target_wrapper: @@ -309,23 +332,30 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter): async def run_async(self): try: import requests + resp = requests.get( - url="http://{}:{}/get_login_info".format(self.cfg['host'], self.cfg['http_port']), + url='http://{}:{}/get_login_info'.format( + self.cfg['host'], self.cfg['http_port'] + ), headers={ - 'Authorization': "Bearer " + self.cfg['token'] if 'token' in self.cfg else "" + 'Authorization': 'Bearer ' + self.cfg['token'] + if 'token' in self.cfg + else '' }, timeout=5, - proxies=None + proxies=None, ) if resp.status_code == 403: - raise Exception("go-cqhttp拒绝访问,请检查配置文件中nakuru适配器的配置") + raise Exception('go-cqhttp拒绝访问,请检查配置文件中nakuru适配器的配置') self.bot_account_id = int(resp.json()['data']['user_id']) - except Exception as e: - raise Exception("获取go-cqhttp账号信息失败, 请检查是否已启动go-cqhttp并配置正确") + except Exception: + raise Exception( + '获取go-cqhttp账号信息失败, 请检查是否已启动go-cqhttp并配置正确' + ) await self.bot._run() - self.ap.logger.info("运行 Nakuru 适配器") + self.ap.logger.info('运行 Nakuru 适配器') while True: await asyncio.sleep(1) async def kill(self) -> bool: - return False \ No newline at end of file + return False diff --git a/pkg/platform/sources/officialaccount.py b/pkg/platform/sources/officialaccount.py index 0816824f..6e7eaf2f 100644 --- a/pkg/platform/sources/officialaccount.py +++ b/pkg/platform/sources/officialaccount.py @@ -4,20 +4,13 @@ import asyncio import traceback import datetime -from pkg.core import app from pkg.platform.adapter import MessagePlatformAdapter from pkg.platform.types import events as platform_events, message as platform_message -from collections import deque from libs.official_account_api.oaevent import OAEvent -from pkg.platform.adapter import MessagePlatformAdapter -from pkg.platform.types import events as platform_events, message as platform_message from libs.official_account_api.api import OAClient from libs.official_account_api.api import OAClientForLongerResponse -from pkg.core import app from .. import adapter from ...core import app -from ..types import message as platform_message -from ..types import events as platform_events from ..types import entities as platform_entities from ...command.errors import ParamNotEnoughError @@ -28,10 +21,9 @@ class OAMessageConverter(adapter.MessageConverter): for msg in message_chain: if type(msg) is platform_message.Plain: return msg.text - @staticmethod - async def target2yiri(message:str,message_id =-1): + async def target2yiri(message: str, message_id=-1): yiri_msg_list = [] yiri_msg_list.append( platform_message.Source(id=message_id, time=datetime.datetime.now()) @@ -41,12 +33,12 @@ class OAMessageConverter(adapter.MessageConverter): chain = platform_message.MessageChain(yiri_msg_list) return chain - + class OAEventConverter(adapter.EventConverter): @staticmethod - async def target2yiri(event:OAEvent): - if event.type == "text": + async def target2yiri(event: OAEvent): + if event.type == 'text': yiri_chain = await OAMessageConverter.target2yiri( event.message, event.message_id ) @@ -54,91 +46,101 @@ class OAEventConverter(adapter.EventConverter): friend = platform_entities.Friend( id=event.user_id, nickname=str(event.user_id), - remark="", + remark='', ) return platform_events.FriendMessage( - sender=friend, message_chain=yiri_chain, time=event.timestamp, source_platform_object=event + sender=friend, + message_chain=yiri_chain, + time=event.timestamp, + source_platform_object=event, ) else: return None -class OfficialAccountAdapter(adapter.MessagePlatformAdapter): - bot : OAClient | OAClientForLongerResponse - ap : app.Application +class OfficialAccountAdapter(adapter.MessagePlatformAdapter): + bot: OAClient | OAClientForLongerResponse + ap: app.Application bot_account_id: str message_converter: OAMessageConverter = OAMessageConverter() event_converter: OAEventConverter = OAEventConverter() config: dict - def __init__(self, config: dict, ap: app.Application): self.config = config - + self.ap = ap required_keys = [ - "token", - "EncodingAESKey", - "AppSecret", - "AppID", - "Mode", + 'token', + 'EncodingAESKey', + 'AppSecret', + 'AppID', + 'Mode', ] missing_keys = [key for key in required_keys if key not in config] if missing_keys: - raise ParamNotEnoughError("微信公众号缺少相关配置项,请查看文档或联系管理员") - - - if self.config['Mode'] == "drop": + raise ParamNotEnoughError( + '微信公众号缺少相关配置项,请查看文档或联系管理员' + ) + + if self.config['Mode'] == 'drop': self.bot = OAClient( token=config['token'], EncodingAESKey=config['EncodingAESKey'], Appsecret=config['AppSecret'], - AppID=config['AppID'], + AppID=config['AppID'], ) - elif self.config['Mode'] == "passive": + elif self.config['Mode'] == 'passive': self.bot = OAClientForLongerResponse( token=config['token'], EncodingAESKey=config['EncodingAESKey'], Appsecret=config['AppSecret'], - AppID=config['AppID'], - LoadingMessage=config['LoadingMessage'] + AppID=config['AppID'], + LoadingMessage=config['LoadingMessage'], ) else: - raise KeyError("请设置微信公众号通信模式") + raise KeyError('请设置微信公众号通信模式') - - async def reply_message(self, message_source: platform_events.FriendMessage, message: platform_message.MessageChain, quote_origin: bool = False): - - content = await OAMessageConverter.yiri2target( - message - ) - if type(self.bot) == OAClient: - await self.bot.set_message(message_source.message_chain.message_id,content) - if type(self.bot) == OAClientForLongerResponse: + async def reply_message( + self, + message_source: platform_events.FriendMessage, + message: platform_message.MessageChain, + quote_origin: bool = False, + ): + content = await OAMessageConverter.yiri2target(message) + if isinstance(self.bot, OAClient): + await self.bot.set_message(message_source.message_chain.message_id, content) + elif isinstance(self.bot, OAClientForLongerResponse): from_user = message_source.sender.id - await self.bot.set_message(from_user,message_source.message_chain.message_id,content) + await self.bot.set_message( + from_user, message_source.message_chain.message_id, content + ) - async def send_message( self, target_type: str, target_id: str, message: platform_message.MessageChain ): pass - - def register_listener(self, event_type: type, callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None]): + def register_listener( + self, + event_type: type, + callback: typing.Callable[ + [platform_events.Event, MessagePlatformAdapter], None + ], + ): async def on_message(event: OAEvent): self.bot_account_id = event.receiver_id try: return await callback( await self.event_converter.target2yiri(event), self ) - except: + except Exception: traceback.print_exc() if event_type == platform_events.FriendMessage: - self.bot.on_message("text")(on_message) + self.bot.on_message('text')(on_message) elif event_type == platform_events.GroupMessage: pass @@ -148,8 +150,8 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter): await asyncio.sleep(1) await self.bot.run_task( - host=self.config["host"], - port=self.config["port"], + host=self.config['host'], + port=self.config['port'], shutdown_trigger=shutdown_trigger_placeholder, ) @@ -159,8 +161,8 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter): async def unregister_listener( self, event_type: type, - callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, MessagePlatformAdapter], None + ], ): return super().unregister_listener(event_type, callback) - - \ No newline at end of file diff --git a/pkg/platform/sources/qqbotpy.py b/pkg/platform/sources/qqbotpy.py index 716da80f..a91f86dd 100644 --- a/pkg/platform/sources/qqbotpy.py +++ b/pkg/platform/sources/qqbotpy.py @@ -22,12 +22,20 @@ from ...platform.types import message as platform_message class OfficialGroupMessage(platform_events.GroupMessage): pass + class OfficialFriendMessage(platform_events.FriendMessage): pass + event_handler_mapping = { - platform_events.GroupMessage: ["on_at_message_create", "on_group_at_message_create"], - platform_events.FriendMessage: ["on_direct_message_create", "on_c2c_message_create"], + platform_events.GroupMessage: [ + 'on_at_message_create', + 'on_group_at_message_create', + ], + platform_events.FriendMessage: [ + 'on_direct_message_create', + 'on_c2c_message_create', + ], } @@ -53,9 +61,10 @@ def char_to_value(char): return ord(char) - ord('0') elif 'A' <= char <= 'Z': return ord(char) - ord('A') + 10 - + return ord(char) - ord('a') + 36 + def digest(s: str) -> int: """计算字符串的hash值。""" # 取末尾的8位 @@ -69,19 +78,24 @@ def digest(s: str) -> int: return number -K = typing.TypeVar("K") -V = typing.TypeVar("V") + +K = typing.TypeVar('K') +V = typing.TypeVar('V') class OpenIDMapping(typing.Generic[K, V]): - map: dict[K, V] dump_func: typing.Callable digest_func: typing.Callable[[K], V] - def __init__(self, map: dict[K, V], dump_func: typing.Callable, digest_func: typing.Callable[[K], V] = digest): + def __init__( + self, + map: dict[K, V], + dump_func: typing.Callable, + digest_func: typing.Callable[[K], V] = digest, + ): self.map = map self.dump_func = dump_func @@ -104,12 +118,11 @@ class OpenIDMapping(typing.Generic[K, V]): def getkey(self, value: V) -> K: return list(self.map.keys())[list(self.map.values()).index(value)] - + def save_openid(self, key: K) -> V: - if key in self.map: return self.map[key] - + value = self.digest_func(key) self.map[key] = value @@ -135,7 +148,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter): msg_list = [platform_message.Plain(text=message_chain)] else: raise Exception( - "Unknown message type: " + str(message_chain) + str(type(message_chain)) + 'Unknown message type: ' + str(message_chain) + str(type(message_chain)) ) offcial_messages: list[dict] = [] @@ -154,23 +167,23 @@ class OfficialMessageConverter(adapter_model.MessageConverter): # 遍历并转换 for component in msg_list: if type(component) is platform_message.Plain: - offcial_messages.append({"type": "text", "content": component.text}) + offcial_messages.append({'type': 'text', 'content': component.text}) elif type(component) is platform_message.Image: if component.url is not None: - offcial_messages.append({"type": "image", "content": component.url}) + offcial_messages.append({'type': 'image', 'content': component.url}) elif component.path is not None: offcial_messages.append( - {"type": "file_image", "content": component.path} + {'type': 'file_image', 'content': component.path} ) elif type(component) is platform_message.At: - offcial_messages.append({"type": "at", "content": ""}) + offcial_messages.append({'type': 'at', 'content': ''}) elif type(component) is platform_message.AtAll: print( - "上层组件要求发送 AtAll 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。" + '上层组件要求发送 AtAll 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。' ) elif type(component) is platform_message.Voice: print( - "上层组件要求发送 Voice 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。" + '上层组件要求发送 Voice 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。' ) elif type(component) is forward.Forward: # 转发消息 @@ -185,7 +198,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter): offcial_messages.extend( OfficialMessageConverter.yiri2target(message_chain) ) - except Exception as e: + except Exception: import traceback traceback.print_exc() @@ -194,7 +207,12 @@ class OfficialMessageConverter(adapter_model.MessageConverter): @staticmethod def extract_message_chain_from_obj( - message: typing.Union[botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, botpy_message.C2CMessage], + message: typing.Union[ + botpy_message.Message, + botpy_message.DirectMessage, + botpy_message.GroupMessage, + botpy_message.C2CMessage, + ], message_id: str = None, bot_account_id: int = 0, ) -> platform_message.MessageChain: @@ -210,7 +228,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter): if type(message) not in [botpy_message.DirectMessage, botpy_message.C2CMessage]: yiri_msg_list.append(platform_message.At(target=bot_account_id)) - if hasattr(message, "mentions"): + if hasattr(message, 'mentions'): for mention in message.mentions: if mention.bot: continue @@ -218,15 +236,15 @@ class OfficialMessageConverter(adapter_model.MessageConverter): yiri_msg_list.append(platform_message.At(target=mention.id)) for attachment in message.attachments: - if attachment.content_type.startswith("image"): + if attachment.content_type.startswith('image'): yiri_msg_list.append(platform_message.Image(url=attachment.url)) else: logging.warning( - "不支持的附件类型:" + attachment.content_type + ",忽略此附件。" + '不支持的附件类型:' + attachment.content_type + ',忽略此附件。' ) - content = re.sub(r"<@!\d+>", "", str(message.content)) - if content.strip() != "": + content = re.sub(r'<@!\d+>', '', str(message.content)) + if content.strip() != '': yiri_msg_list.append(platform_message.Plain(text=content)) chain = platform_message.MessageChain(yiri_msg_list) @@ -247,21 +265,25 @@ class OfficialEventConverter(adapter_model.EventConverter): return botpy_message.DirectMessage else: raise Exception( - "未支持转换的事件类型(YiriMirai -> Official): " + str(event) + '未支持转换的事件类型(YiriMirai -> Official): ' + str(event) ) def target2yiri( self, - event: typing.Union[botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, botpy_message.C2CMessage], + event: typing.Union[ + botpy_message.Message, + botpy_message.DirectMessage, + botpy_message.GroupMessage, + botpy_message.C2CMessage, + ], ) -> platform_events.Event: + if isinstance(event, botpy_message.Message): # 频道内,转群聊事件 + permission = 'MEMBER' - if type(event) == botpy_message.Message: # 频道内,转群聊事件 - permission = "MEMBER" - - if "2" in event.member.roles: - permission = "ADMINISTRATOR" - elif "4" in event.member.roles: - permission = "OWNER" + if '2' in event.member.roles: + permission = 'ADMINISTRATOR' + elif '4' in event.member.roles: + permission = 'OWNER' return platform_events.GroupMessage( sender=platform_entities.GroupMember( @@ -273,10 +295,10 @@ class OfficialEventConverter(adapter_model.EventConverter): name=event.author.username, permission=platform_entities.Permission.Member, ), - special_title="", + special_title='', join_timestamp=int( datetime.datetime.strptime( - event.member.joined_at, "%Y-%m-%dT%H:%M:%S%z" + event.member.joined_at, '%Y-%m-%dT%H:%M:%S%z' ).timestamp() ), last_speak_timestamp=datetime.datetime.now().timestamp(), @@ -287,11 +309,11 @@ class OfficialEventConverter(adapter_model.EventConverter): ), time=int( datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' ).timestamp() ), ) - elif type(event) == botpy_message.DirectMessage: # 频道私聊,转私聊事件 + elif isinstance(event, botpy_message.DirectMessage): # 频道私聊,转私聊事件 return platform_events.FriendMessage( sender=platform_entities.Friend( id=event.guild_id, @@ -303,25 +325,24 @@ class OfficialEventConverter(adapter_model.EventConverter): ), time=int( datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' ).timestamp() ), ) - elif type(event) == botpy_message.GroupMessage: # 群聊,转群聊事件 - + elif isinstance(event, botpy_message.GroupMessage): # 群聊,转群聊事件 author_member_id = event.author.member_openid return OfficialGroupMessage( sender=platform_entities.GroupMember( id=author_member_id, member_name=author_member_id, - permission="MEMBER", + permission='MEMBER', group=platform_entities.Group( id=event.group_openid, name=author_member_id, permission=platform_entities.Permission.Member, ), - special_title="", + special_title='', join_timestamp=int(0), last_speak_timestamp=datetime.datetime.now().timestamp(), mute_time_remaining=0, @@ -331,12 +352,11 @@ class OfficialEventConverter(adapter_model.EventConverter): ), time=int( datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' ).timestamp() ), ) - elif type(event) == botpy_message.C2CMessage: # 私聊,转私聊事件 - + elif isinstance(event, botpy_message.C2CMessage): # 私聊,转私聊事件 user_id_alter = event.author.user_openid return OfficialFriendMessage( @@ -350,7 +370,7 @@ class OfficialEventConverter(adapter_model.EventConverter): ), time=int( datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' ).timestamp() ), ) @@ -391,10 +411,10 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): switchs = {} - for intent in cfg["intents"]: + for intent in cfg['intents']: switchs[intent] = True - del cfg["intents"] + del cfg['intents'] intents = botpy.Intents(**switchs) @@ -408,21 +428,21 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): for msg in message_list: args = {} - if msg["type"] == "text": - args["content"] = msg["content"] - elif msg["type"] == "image": - args["image"] = msg["content"] - elif msg["type"] == "file_image": - args["file_image"] = msg["content"] + if msg['type'] == 'text': + args['content'] = msg['content'] + elif msg['type'] == 'image': + args['image'] = msg['content'] + elif msg['type'] == 'file_image': + args['file_image'] = msg['content'] else: continue - if target_type == "group": - args["channel_id"] = str(target_id) + if target_type == 'group': + args['channel_id'] = str(target_id) await self.bot.api.post_message(**args) - elif target_type == "person": - args["guild_id"] = str(target_id) + elif target_type == 'person': + args['guild_id'] = str(target_id) await self.bot.api.post_dms(**args) @@ -432,86 +452,82 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): message: platform_message.MessageChain, quote_origin: bool = False, ): - message_list = self.message_converter.yiri2target(message) for msg in message_list: args = {} - if msg["type"] == "text": - args["content"] = msg["content"] - elif msg["type"] == "image": - args["image"] = msg["content"] - elif msg["type"] == "file_image": - args["file_image"] = msg["content"] + if msg['type'] == 'text': + args['content'] = msg['content'] + elif msg['type'] == 'image': + args['image'] = msg['content'] + elif msg['type'] == 'file_image': + args['file_image'] = msg['content'] else: continue if quote_origin: - args["message_reference"] = botpy_message_type.Reference( + args['message_reference'] = botpy_message_type.Reference( message_id=cached_message_ids[ str(message_source.message_chain.message_id) ] ) - if type(message_source) == platform_events.GroupMessage: - args["channel_id"] = str(message_source.sender.group.id) - args["msg_id"] = cached_message_ids[ + if isinstance(message_source, platform_events.GroupMessage): + args['channel_id'] = str(message_source.sender.group.id) + args['msg_id'] = cached_message_ids[ str(message_source.message_chain.message_id) ] await self.bot.api.post_message(**args) - elif type(message_source) == platform_events.FriendMessage: - args["guild_id"] = str(message_source.sender.id) - args["msg_id"] = cached_message_ids[ + elif isinstance(message_source, platform_events.FriendMessage): + args['guild_id'] = str(message_source.sender.id) + args['msg_id'] = cached_message_ids[ str(message_source.message_chain.message_id) ] await self.bot.api.post_dms(**args) - elif type(message_source) == OfficialGroupMessage: - - if "file_image" in args: # 暂不支持发送文件图片 + elif isinstance(message_source, OfficialGroupMessage): + if 'file_image' in args: # 暂不支持发送文件图片 continue - args["group_openid"] = message_source.sender.group.id + args['group_openid'] = message_source.sender.group.id - if "image" in args: + if 'image' in args: uploadMedia = await self.bot.api.post_group_file( - group_openid=args["group_openid"], + group_openid=args['group_openid'], file_type=1, - url=str(args['image']) + url=str(args['image']), ) del args['image'] args['media'] = uploadMedia args['msg_type'] = 7 - args["msg_id"] = cached_message_ids[ + args['msg_id'] = cached_message_ids[ str(message_source.message_chain.message_id) ] - args["msg_seq"] = self.group_msg_seq + args['msg_seq'] = self.group_msg_seq self.group_msg_seq += 1 await self.bot.api.post_group_message(**args) - elif type(message_source) == OfficialFriendMessage: - if "file_image" in args: + elif isinstance(message_source, OfficialFriendMessage): + if 'file_image' in args: continue - args["openid"] = message_source.sender.id + args['openid'] = message_source.sender.id - if "image" in args: + if 'image' in args: uploadMedia = await self.bot.api.post_c2c_file( - openid=args["openid"], - file_type=1, - url=str(args['image']) + openid=args['openid'], file_type=1, url=str(args['image']) ) del args['image'] args['media'] = uploadMedia args['msg_type'] = 7 - args["msg_id"] = cached_message_ids[ + args['msg_id'] = cached_message_ids[ str(message_source.message_chain.message_id) ] - args["msg_seq"] = self.c2c_msg_seq + args['msg_seq'] = self.c2c_msg_seq self.c2c_msg_seq += 1 await self.bot.api.post_c2c_message(**args) @@ -526,7 +542,6 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): [platform_events.Event, adapter_model.MessagePlatformAdapter], None ], ): - try: async def wrapper( @@ -534,7 +549,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, - ] + ], ): self.cached_official_messages[str(message.id)] = message await callback(self.event_converter.target2yiri(message), self) @@ -555,7 +570,6 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): delattr(self.bot, event_handler_mapping[event_type]) async def run_async(self): - self.metadata = self.ap.adapter_qq_botpy_meta self.message_converter = OfficialMessageConverter() @@ -563,7 +577,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter): self.cfg['ret_coro'] = True - self.ap.logger.info("运行 QQ 官方适配器") + self.ap.logger.info('运行 QQ 官方适配器') await (await self.bot.start(**self.cfg)) async def kill(self) -> bool: diff --git a/pkg/platform/sources/qqofficial.py b/pkg/platform/sources/qqofficial.py index bfef2135..06893485 100644 --- a/pkg/platform/sources/qqofficial.py +++ b/pkg/platform/sources/qqofficial.py @@ -7,12 +7,8 @@ import datetime from pkg.platform.adapter import MessagePlatformAdapter from pkg.platform.types import events as platform_events, message as platform_message -from pkg.core import app from .. import adapter - from ...core import app -from ..types import message as platform_message -from ..types import events as platform_events from ..types import entities as platform_entities from ...command.errors import ParamNotEnoughError from libs.qq_official_api.api import QQOfficialClient @@ -21,157 +17,164 @@ from ...utils import image class QQOfficialMessageConverter(adapter.MessageConverter): - @staticmethod async def yiri2target(message_chain: platform_message.MessageChain): content_list = [] - #只实现了发文字 + # 只实现了发文字 for msg in message_chain: if type(msg) is platform_message.Plain: - content_list.append({ - "type":"text", - "content":msg.text, - }) - + content_list.append( + { + 'type': 'text', + 'content': msg.text, + } + ) + return content_list - + @staticmethod - async def target2yiri(message:str,message_id:str,pic_url:str,content_type): + async def target2yiri(message: str, message_id: str, pic_url: str, content_type): yiri_msg_list = [] yiri_msg_list.append( - platform_message.Source(id=message_id,time=datetime.datetime.now()) + platform_message.Source(id=message_id, time=datetime.datetime.now()) ) if pic_url is not None: - base64_url = await image.get_qq_official_image_base64(pic_url=pic_url,content_type=content_type) - yiri_msg_list.append( - platform_message.Image(base64=base64_url) + base64_url = await image.get_qq_official_image_base64( + pic_url=pic_url, content_type=content_type ) + yiri_msg_list.append(platform_message.Image(base64=base64_url)) yiri_msg_list.append(platform_message.Plain(text=message)) chain = platform_message.MessageChain(yiri_msg_list) return chain + class QQOfficialEventConverter(adapter.EventConverter): + @staticmethod + async def yiri2target(event: platform_events.MessageEvent) -> QQOfficialEvent: + return event.source_platform_object @staticmethod - async def yiri2target(event:platform_events.MessageEvent) -> QQOfficialEvent: - return event.source_platform_object - - @staticmethod - async def target2yiri(event:QQOfficialEvent): + async def target2yiri(event: QQOfficialEvent): """ QQ官方消息转换为LB对象 """ yiri_chain = await QQOfficialMessageConverter.target2yiri( - message=event.content,message_id=event.d_id,pic_url=event.attachments,content_type=event.content_type + message=event.content, + message_id=event.d_id, + pic_url=event.attachments, + content_type=event.content_type, ) - + if event.t == 'C2C_MESSAGE_CREATE': friend = platform_entities.Friend( - id = event.user_openid, - nickname = event.t, - remark = "", + id=event.user_openid, + nickname=event.t, + remark='', ) return platform_events.FriendMessage( - sender = friend,message_chain = yiri_chain,time = int( + sender=friend, + message_chain=yiri_chain, + time=int( datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' ).timestamp() ), - source_platform_object=event + source_platform_object=event, ) - + if event.t == 'DIRECT_MESSAGE_CREATE': friend = platform_entities.Friend( - id = event.guild_id, - nickname = event.t, - remark = "", + id=event.guild_id, + nickname=event.t, + remark='', ) return platform_events.FriendMessage( - sender = friend,message_chain = yiri_chain, - source_platform_object=event + sender=friend, message_chain=yiri_chain, source_platform_object=event ) if event.t == 'GROUP_AT_MESSAGE_CREATE': - yiri_chain.insert(0, platform_message.At(target="justbot")) + yiri_chain.insert(0, platform_message.At(target='justbot')) sender = platform_entities.GroupMember( - id = event.group_openid, - member_name= event.t, - permission= 'MEMBER', - group = platform_entities.Group( - id = event.group_openid, - name = 'MEMBER', - permission= platform_entities.Permission.Member - ), - special_title='', - join_timestamp=0, - last_speak_timestamp=0, - mute_time_remaining=0 - ) - time = int( - datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" - ).timestamp() - ) - return platform_events.GroupMessage( - sender = sender, - message_chain=yiri_chain, - time = time, - source_platform_object=event - ) - if event.t =='AT_MESSAGE_CREATE': - yiri_chain.insert(0, platform_message.At(target="justbot")) - sender = platform_entities.GroupMember( - id = event.channel_id, + id=event.group_openid, member_name=event.t, - permission= 'MEMBER', - group = platform_entities.Group( - id = event.channel_id, - name = 'MEMBER', - permission=platform_entities.Permission.Member + permission='MEMBER', + group=platform_entities.Group( + id=event.group_openid, + name='MEMBER', + permission=platform_entities.Permission.Member, ), special_title='', join_timestamp=0, last_speak_timestamp=0, - mute_time_remaining=0 + mute_time_remaining=0, ) time = int( - datetime.datetime.strptime( - event.timestamp, "%Y-%m-%dT%H:%M:%S%z" - ).timestamp() - ) + datetime.datetime.strptime( + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' + ).timestamp() + ) return platform_events.GroupMessage( - sender =sender, - message_chain = yiri_chain, - time = time, - source_platform_object=event + sender=sender, + message_chain=yiri_chain, + time=time, + source_platform_object=event, + ) + if event.t == 'AT_MESSAGE_CREATE': + yiri_chain.insert(0, platform_message.At(target='justbot')) + sender = platform_entities.GroupMember( + id=event.channel_id, + member_name=event.t, + permission='MEMBER', + group=platform_entities.Group( + id=event.channel_id, + name='MEMBER', + permission=platform_entities.Permission.Member, + ), + special_title='', + join_timestamp=0, + last_speak_timestamp=0, + mute_time_remaining=0, + ) + time = int( + datetime.datetime.strptime( + event.timestamp, '%Y-%m-%dT%H:%M:%S%z' + ).timestamp() + ) + return platform_events.GroupMessage( + sender=sender, + message_chain=yiri_chain, + time=time, + source_platform_object=event, ) class QQOfficialAdapter(adapter.MessagePlatformAdapter): - bot:QQOfficialClient - ap:app.Application - config:dict - bot_account_id:str + bot: QQOfficialClient + ap: app.Application + config: dict + bot_account_id: str message_converter: QQOfficialMessageConverter = QQOfficialMessageConverter() event_converter: QQOfficialEventConverter = QQOfficialEventConverter() - def __init__(self, config:dict, ap:app.Application): + def __init__(self, config: dict, ap: app.Application): self.config = config self.ap = ap required_keys = [ - "appid", - "secret", + 'appid', + 'secret', ] missing_keys = [key for key in required_keys if key not in config] if missing_keys: - raise ParamNotEnoughError("QQ官方机器人缺少相关配置项,请查看文档或联系管理员") - + raise ParamNotEnoughError( + 'QQ官方机器人缺少相关配置项,请查看文档或联系管理员' + ) + self.bot = QQOfficialClient( - app_id=config["appid"], - secret=config["secret"], - token=config["token"], + app_id=config['appid'], + secret=config['secret'], + token=config['token'], ) async def reply_message( @@ -186,31 +189,45 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter): content_list = await QQOfficialMessageConverter.yiri2target(message) - #私聊消息 + # 私聊消息 if qq_official_event.t == 'C2C_MESSAGE_CREATE': for content in content_list: - if content["type"] == 'text': - await self.bot.send_private_text_msg(qq_official_event.user_openid,content['content'],qq_official_event.d_id) + if content['type'] == 'text': + await self.bot.send_private_text_msg( + qq_official_event.user_openid, + content['content'], + qq_official_event.d_id, + ) - #群聊消息 + # 群聊消息 if qq_official_event.t == 'GROUP_AT_MESSAGE_CREATE': for content in content_list: - if content["type"] == 'text': - await self.bot.send_group_text_msg(qq_official_event.group_openid,content['content'],qq_official_event.d_id) - - #频道群聊 + if content['type'] == 'text': + await self.bot.send_group_text_msg( + qq_official_event.group_openid, + content['content'], + qq_official_event.d_id, + ) + + # 频道群聊 if qq_official_event.t == 'AT_MESSAGE_CREATE': for content in content_list: - if content["type"] == 'text': - await self.bot.send_channle_group_text_msg(qq_official_event.channel_id,content['content'],qq_official_event.d_id) + if content['type'] == 'text': + await self.bot.send_channle_group_text_msg( + qq_official_event.channel_id, + content['content'], + qq_official_event.d_id, + ) - #频道私聊 + # 频道私聊 if qq_official_event.t == 'DIRECT_MESSAGE_CREATE': for content in content_list: - if content["type"] == 'text': - await self.bot.send_channle_private_text_msg(qq_official_event.guild_id,content['content'],qq_official_event.d_id) - - + if content['type'] == 'text': + await self.bot.send_channle_private_text_msg( + qq_official_event.guild_id, + content['content'], + qq_official_event.d_id, + ) async def send_message( self, target_type: str, target_id: str, message: platform_message.MessageChain @@ -224,22 +241,21 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter): [platform_events.Event, adapter.MessagePlatformAdapter], None ], ): - async def on_message(event:QQOfficialEvent): - self.bot_account_id = "justbot" + async def on_message(event: QQOfficialEvent): + self.bot_account_id = 'justbot' try: return await callback( - await self.event_converter.target2yiri(event),self + await self.event_converter.target2yiri(event), self ) - except: + except Exception: traceback.print_exc() - - if event_type == platform_events.FriendMessage: - self.bot.on_message("DIRECT_MESSAGE_CREATE")(on_message) - self.bot.on_message("C2C_MESSAGE_CREATE")(on_message) - elif event_type == platform_events.GroupMessage: - self.bot.on_message("GROUP_AT_MESSAGE_CREATE")(on_message) - self.bot.on_message("AT_MESSAGE_CREATE")(on_message) + if event_type == platform_events.FriendMessage: + self.bot.on_message('DIRECT_MESSAGE_CREATE')(on_message) + self.bot.on_message('C2C_MESSAGE_CREATE')(on_message) + elif event_type == platform_events.GroupMessage: + self.bot.on_message('GROUP_AT_MESSAGE_CREATE')(on_message) + self.bot.on_message('AT_MESSAGE_CREATE')(on_message) async def run_async(self): async def shutdown_trigger_placeholder(): @@ -248,17 +264,18 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter): await self.bot.run_task( host='0.0.0.0', - port=self.config["port"], + port=self.config['port'], shutdown_trigger=shutdown_trigger_placeholder, - ) - + ) + async def kill(self) -> bool: return False - + def unregister_listener( self, event_type: type, - callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, MessagePlatformAdapter], None + ], ): return super().unregister_listener(event_type, callback) - diff --git a/pkg/platform/sources/telegram.py b/pkg/platform/sources/telegram.py index 49822673..b35b7e7a 100644 --- a/pkg/platform/sources/telegram.py +++ b/pkg/platform/sources/telegram.py @@ -3,48 +3,33 @@ from __future__ import annotations import telegram import telegram.ext from telegram import Update -from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters +from telegram.ext import ApplicationBuilder, ContextTypes, MessageHandler, filters import typing -import asyncio import traceback -import time -import re -import base64 -import uuid -import json -import datetime -import hashlib import base64 import aiohttp -from Crypto.Cipher import AES -from flask import jsonify from lark_oapi.api.im.v1 import * -from lark_oapi.api.verification.v1 import GetVerificationRequest from .. import adapter -from ...pipeline.longtext.strategies import forward from ...core import app from ..types import message as platform_message from ..types import events as platform_events from ..types import entities as platform_entities -from ...utils import image class TelegramMessageConverter(adapter.MessageConverter): @staticmethod - async def yiri2target(message_chain: platform_message.MessageChain, bot: telegram.Bot) -> list[dict]: + async def yiri2target( + message_chain: platform_message.MessageChain, bot: telegram.Bot + ) -> list[dict]: components = [] for component in message_chain: if isinstance(component, platform_message.Plain): - components.append({ - "type": "text", - "text": component.text - }) + components.append({'type': 'text', 'text': component.text}) elif isinstance(component, platform_message.Image): - photo_bytes = None if component.base64: @@ -54,24 +39,25 @@ class TelegramMessageConverter(adapter.MessageConverter): async with session.get(component.url) as response: photo_bytes = await response.read() elif component.path: - with open(component.path, "rb") as f: + with open(component.path, 'rb') as f: photo_bytes = f.read() - - components.append({ - "type": "photo", - "photo": photo_bytes - }) + + components.append({'type': 'photo', 'photo': photo_bytes}) elif isinstance(component, platform_message.Forward): for node in component.node_list: - components.extend(await TelegramMessageConverter.yiri2target(node.message_chain, bot)) + components.extend( + await TelegramMessageConverter.yiri2target( + node.message_chain, bot + ) + ) return components - - @staticmethod - async def target2yiri(message: telegram.Message, bot: telegram.Bot, bot_account_id: str): - - message_components = [] + @staticmethod + async def target2yiri( + message: telegram.Message, bot: telegram.Bot, bot_account_id: str + ): + message_components = [] def parse_message_text(text: str) -> list[platform_message.MessageComponent]: msg_components = [] @@ -86,7 +72,7 @@ class TelegramMessageConverter(adapter.MessageConverter): if message.text: message_text = message.text message_components.extend(parse_message_text(message_text)) - + if message.photo: message_components.extend(parse_message_text(message.caption)) @@ -100,21 +86,26 @@ class TelegramMessageConverter(adapter.MessageConverter): file_bytes = await response.read() file_format = 'image/jpeg' - message_components.append(platform_message.Image(base64=f"data:{file_format};base64,{base64.b64encode(file_bytes).decode('utf-8')}")) - + message_components.append( + platform_message.Image( + base64=f'data:{file_format};base64,{base64.b64encode(file_bytes).decode("utf-8")}' + ) + ) + return platform_message.MessageChain(message_components) - + class TelegramEventConverter(adapter.EventConverter): @staticmethod async def yiri2target(event: platform_events.MessageEvent, bot: telegram.Bot): return event.source_platform_object - + @staticmethod async def target2yiri(event: Update, bot: telegram.Bot, bot_account_id: str): + lb_message = await TelegramMessageConverter.target2yiri( + event.message, bot, bot_account_id + ) - lb_message = await TelegramMessageConverter.target2yiri(event.message, bot, bot_account_id) - if event.effective_chat.type == 'private': return platform_events.FriendMessage( sender=platform_entities.Friend( @@ -124,7 +115,7 @@ class TelegramEventConverter(adapter.EventConverter): ), message_chain=lb_message, time=event.message.date.timestamp(), - source_platform_object=event + source_platform_object=event, ) elif event.effective_chat.type == 'group': return platform_events.GroupMessage( @@ -137,19 +128,18 @@ class TelegramEventConverter(adapter.EventConverter): name=event.effective_chat.title, permission=platform_entities.Permission.Member, ), - special_title="", + special_title='', join_timestamp=0, last_speak_timestamp=0, mute_time_remaining=0, ), message_chain=lb_message, time=event.message.date.timestamp(), - source_platform_object=event + source_platform_object=event, ) - + class TelegramAdapter(adapter.MessagePlatformAdapter): - bot: telegram.Bot application: telegram.ext.Application @@ -165,26 +155,31 @@ class TelegramAdapter(adapter.MessagePlatformAdapter): typing.Type[platform_events.Event], typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], ] = {} - + def __init__(self, config: dict, ap: app.Application): self.config = config self.ap = ap - - async def telegram_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): + async def telegram_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): if update.message.from_user.is_bot: return try: - lb_event = await self.event_converter.target2yiri(update, self.bot, self.bot_account_id) + lb_event = await self.event_converter.target2yiri( + update, self.bot, self.bot_account_id + ) await self.listeners[type(lb_event)](lb_event, self) - except Exception as e: + except Exception: print(traceback.format_exc()) - + self.application = ApplicationBuilder().token(self.config['token']).build() self.bot = self.application.bot - self.application.add_handler(MessageHandler(filters.TEXT | (filters.COMMAND) | filters.PHOTO , telegram_callback)) - + self.application.add_handler( + MessageHandler( + filters.TEXT | (filters.COMMAND) | filters.PHOTO, telegram_callback + ) + ) + async def send_message( self, target_type: str, target_id: str, message: platform_message.MessageChain ): @@ -198,45 +193,48 @@ class TelegramAdapter(adapter.MessagePlatformAdapter): ): assert isinstance(message_source.source_platform_object, Update) components = await TelegramMessageConverter.yiri2target(message, self.bot) - + for component in components: if component['type'] == 'text': - args = { - "chat_id": message_source.source_platform_object.effective_chat.id, - "text": component['text'], + 'chat_id': message_source.source_platform_object.effective_chat.id, + 'text': component['text'], } if quote_origin: - args['reply_to_message_id'] = message_source.source_platform_object.message.id + args['reply_to_message_id'] = ( + message_source.source_platform_object.message.id + ) await self.bot.send_message(**args) - + async def is_muted(self, group_id: int) -> bool: return False - + def register_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): self.listeners[event_type] = callback - + def unregister_listener( self, event_type: typing.Type[platform_events.Event], - callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, adapter.MessagePlatformAdapter], None + ], ): self.listeners.pop(event_type) - + async def run_async(self): await self.application.initialize() self.bot_account_id = (await self.bot.get_me()).username - await self.application.updater.start_polling( - allowed_updates=Update.ALL_TYPES - ) + await self.application.updater.start_polling(allowed_updates=Update.ALL_TYPES) await self.application.start() - + async def kill(self) -> bool: await self.application.stop() - return True \ No newline at end of file + return True diff --git a/pkg/platform/sources/wecom.py b/pkg/platform/sources/wecom.py index 40632595..53878062 100644 --- a/pkg/platform/sources/wecom.py +++ b/pkg/platform/sources/wecom.py @@ -9,17 +9,14 @@ from libs.wecom_api.api import WecomClient from pkg.platform.adapter import MessagePlatformAdapter from pkg.platform.types import events as platform_events, message as platform_message from libs.wecom_api.wecomevent import WecomEvent -from pkg.core import app from .. import adapter from ...core import app -from ..types import message as platform_message -from ..types import events as platform_events from ..types import entities as platform_entities from ...command.errors import ParamNotEnoughError from ...utils import image -class WecomMessageConverter(adapter.MessageConverter): +class WecomMessageConverter(adapter.MessageConverter): @staticmethod async def yiri2target( message_chain: platform_message.MessageChain, bot: WecomClient @@ -28,23 +25,35 @@ class WecomMessageConverter(adapter.MessageConverter): for msg in message_chain: if type(msg) is platform_message.Plain: - content_list.append({ - "type": "text", - "content": msg.text, - }) + content_list.append( + { + 'type': 'text', + 'content': msg.text, + } + ) elif type(msg) is platform_message.Image: - content_list.append({ - "type": "image", - "media_id": await bot.get_media_id(msg), - }) + content_list.append( + { + 'type': 'image', + 'media_id': await bot.get_media_id(msg), + } + ) elif type(msg) is platform_message.Forward: for node in msg.node_list: - content_list.extend((await WecomMessageConverter.yiri2target(node.message_chain, bot))) + content_list.extend( + ( + await WecomMessageConverter.yiri2target( + node.message_chain, bot + ) + ) + ) else: - content_list.append({ - "type": "text", - "content": str(msg), - }) + content_list.append( + { + 'type': 'text', + 'content': str(msg), + } + ) return content_list @@ -67,14 +76,17 @@ class WecomMessageConverter(adapter.MessageConverter): platform_message.Source(id=message_id, time=datetime.datetime.now()) ) image_base64, image_format = await image.get_wecom_image_base64(pic_url=picurl) - yiri_msg_list.append(platform_message.Image(base64=f"data:image/{image_format};base64,{image_base64}")) + yiri_msg_list.append( + platform_message.Image( + base64=f'data:image/{image_format};base64,{image_base64}' + ) + ) chain = platform_message.MessageChain(yiri_msg_list) - + return chain class WecomEventConverter: - @staticmethod async def yiri2target( event: platform_events.Event, bot_account_id: int, bot: WecomClient @@ -85,18 +97,17 @@ class WecomEventConverter: pass if type(event) is platform_events.FriendMessage: - payload = { - "MsgType": "text", - "Content": '', - "FromUserName": event.sender.id, - "ToUserName": bot_account_id, - "CreateTime": int(datetime.datetime.now().timestamp()), - "AgentID": event.sender.nickname, + 'MsgType': 'text', + 'Content': '', + 'FromUserName': event.sender.id, + 'ToUserName': bot_account_id, + 'CreateTime': int(datetime.datetime.now().timestamp()), + 'AgentID': event.sender.nickname, } wecom_event = WecomEvent.from_payload(payload=payload) if not wecom_event: - raise ValueError("无法从 message_data 构造 WecomEvent 对象") + raise ValueError('无法从 message_data 构造 WecomEvent 对象') return wecom_event @@ -112,24 +123,24 @@ class WecomEventConverter: platform_events.FriendMessage: 转换后的 FriendMessage 对象。 """ # 转换消息链 - if event.type == "text": + if event.type == 'text': yiri_chain = await WecomMessageConverter.target2yiri( event.message, event.message_id ) friend = platform_entities.Friend( - id=f"u{event.user_id}", + id=f'u{event.user_id}', nickname=str(event.agent_id), - remark="", + remark='', ) return platform_events.FriendMessage( sender=friend, message_chain=yiri_chain, time=event.timestamp ) - elif event.type == "image": + elif event.type == 'image': friend = platform_entities.Friend( - id=f"u{event.user_id}", + id=f'u{event.user_id}', nickname=str(event.agent_id), - remark="", + remark='', ) yiri_chain = await WecomMessageConverter.target2yiri_image( @@ -142,7 +153,6 @@ class WecomEventConverter: class WecomAdapter(adapter.MessagePlatformAdapter): - bot: WecomClient ap: app.Application bot_account_id: str @@ -156,22 +166,22 @@ class WecomAdapter(adapter.MessagePlatformAdapter): self.ap = ap required_keys = [ - "corpid", - "secret", - "token", - "EncodingAESKey", - "contacts_secret", + 'corpid', + 'secret', + 'token', + 'EncodingAESKey', + 'contacts_secret', ] missing_keys = [key for key in required_keys if key not in config] if missing_keys: - raise ParamNotEnoughError("企业微信缺少相关配置项,请查看文档或联系管理员") + raise ParamNotEnoughError('企业微信缺少相关配置项,请查看文档或联系管理员') self.bot = WecomClient( - corpid=config["corpid"], - secret=config["secret"], - token=config["token"], - EncodingAESKey=config["EncodingAESKey"], - contacts_secret=config["contacts_secret"], + corpid=config['corpid'], + secret=config['secret'], + token=config['token'], + EncodingAESKey=config['EncodingAESKey'], + contacts_secret=config['contacts_secret'], ) async def reply_message( @@ -180,7 +190,6 @@ class WecomAdapter(adapter.MessagePlatformAdapter): message: platform_message.MessageChain, quote_origin: bool = False, ): - Wecom_event = await WecomEventConverter.yiri2target( message_source, self.bot_account_id, self.bot ) @@ -189,11 +198,15 @@ class WecomAdapter(adapter.MessagePlatformAdapter): # 删掉开头的u fixed_user_id = fixed_user_id[1:] for content in content_list: - if content["type"] == "text": - await self.bot.send_private_msg(fixed_user_id, Wecom_event.agent_id, content["content"]) - elif content["type"] == "image": - await self.bot.send_image(fixed_user_id, Wecom_event.agent_id, content["media_id"]) - + if content['type'] == 'text': + await self.bot.send_private_msg( + fixed_user_id, Wecom_event.agent_id, content['content'] + ) + elif content['type'] == 'image': + await self.bot.send_image( + fixed_user_id, Wecom_event.agent_id, content['media_id'] + ) + async def send_message( self, target_type: str, target_id: str, message: platform_message.MessageChain ): @@ -201,15 +214,17 @@ class WecomAdapter(adapter.MessagePlatformAdapter): 构造target_id的方式为前半部分为账户id,后半部分为agent_id,中间使用“|”符号隔开。 """ content_list = await WecomMessageConverter.yiri2target(message, self.bot) - parts = target_id.split("|") + parts = target_id.split('|') user_id = parts[0] agent_id = int(parts[1]) if target_type == 'person': for content in content_list: - if content["type"] == "text": - await self.bot.send_private_msg(user_id,agent_id,content["content"]) - if content["type"] == "image": - await self.bot.send_image(user_id,agent_id,content["media"]) + if content['type'] == 'text': + await self.bot.send_private_msg( + user_id, agent_id, content['content'] + ) + if content['type'] == 'image': + await self.bot.send_image(user_id, agent_id, content['media']) def register_listener( self, @@ -224,12 +239,12 @@ class WecomAdapter(adapter.MessagePlatformAdapter): return await callback( await self.event_converter.target2yiri(event), self ) - except: + except Exception: traceback.print_exc() if event_type == platform_events.FriendMessage: - self.bot.on_message("text")(on_message) - self.bot.on_message("image")(on_message) + self.bot.on_message('text')(on_message) + self.bot.on_message('image')(on_message) elif event_type == platform_events.GroupMessage: pass @@ -239,8 +254,8 @@ class WecomAdapter(adapter.MessagePlatformAdapter): await asyncio.sleep(1) await self.bot.run_task( - host=self.config["host"], - port=self.config["port"], + host=self.config['host'], + port=self.config['port'], shutdown_trigger=shutdown_trigger_placeholder, ) @@ -250,6 +265,8 @@ class WecomAdapter(adapter.MessagePlatformAdapter): async def unregister_listener( self, event_type: type, - callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None], + callback: typing.Callable[ + [platform_events.Event, MessagePlatformAdapter], None + ], ): return super().unregister_listener(event_type, callback) diff --git a/pkg/platform/types/base.py b/pkg/platform/types/base.py index ce87d36c..9e31bafe 100644 --- a/pkg/platform/types/base.py +++ b/pkg/platform/types/base.py @@ -1,4 +1,3 @@ - from typing import Dict, List, Type import pydantic.v1.main as pdm @@ -25,14 +24,18 @@ class PlatformBaseModel(BaseModel, metaclass=PlatformMetaclass): 2. 允许通过别名访问字段。 3. 自动生成小驼峰风格的别名。 """ + def __init__(self, *args, **kwargs): """""" super().__init__(*args, **kwargs) def __repr__(self) -> str: - return self.__class__.__name__ + '(' + ', '.join( - (f'{k}={repr(v)}' for k, v in self.__dict__.items() if v) - ) + ')' + return ( + self.__class__.__name__ + + '(' + + ', '.join((f'{k}={repr(v)}' for k, v in self.__dict__.items() if v)) + + ')' + ) class Config: extra = 'allow' @@ -42,6 +45,7 @@ class PlatformBaseModel(BaseModel, metaclass=PlatformMetaclass): class PlatformIndexedMetaclass(PlatformMetaclass): """可以通过子类名获取子类的类的元类。""" + __indexedbases__: List[Type['PlatformIndexedModel']] = [] __indexedmodel__ = None @@ -69,6 +73,7 @@ class PlatformIndexedMetaclass(PlatformMetaclass): class PlatformIndexedModel(PlatformBaseModel, metaclass=PlatformIndexedMetaclass): """可以通过子类名获取子类的类。""" + __indexes__: Dict[str, Type['PlatformIndexedModel']] @classmethod @@ -86,7 +91,7 @@ class PlatformIndexedModel(PlatformBaseModel, metaclass=PlatformIndexedMetaclass if not (type_ and issubclass(type_, cls)): raise ValueError(f'`{name}` 不是 `{cls.__name__}` 的子类!') return type_ - except AttributeError as e: + except AttributeError: raise ValueError(f'`{name}` 不是 `{cls.__name__}` 的子类!') from None @classmethod diff --git a/pkg/platform/types/entities.py b/pkg/platform/types/entities.py index 33fbefe9..d989ffce 100644 --- a/pkg/platform/types/entities.py +++ b/pkg/platform/types/entities.py @@ -2,6 +2,7 @@ """ 此模块提供实体和配置项模型。 """ + import abc from datetime import datetime from enum import Enum @@ -12,8 +13,10 @@ import pydantic.v1 as pydantic class Entity(pydantic.BaseModel): """实体,表示一个用户或群。""" + id: int """ID。""" + @abc.abstractmethod def get_name(self) -> str: """名称。""" @@ -21,31 +24,35 @@ class Entity(pydantic.BaseModel): class Friend(Entity): """私聊对象。""" + id: typing.Union[int, str] """ID。""" nickname: typing.Optional[str] """昵称。""" remark: typing.Optional[str] """备注。""" + def get_name(self) -> str: return self.nickname or self.remark or '' - class Permission(str, Enum): """群成员身份权限。""" - Member = "MEMBER" + + Member = 'MEMBER' """成员。""" - Administrator = "ADMINISTRATOR" + Administrator = 'ADMINISTRATOR' """管理员。""" - Owner = "OWNER" + Owner = 'OWNER' """群主。""" + def __repr__(self) -> str: return repr(self.value) class Group(Entity): """群。""" + id: typing.Union[int, str] """群号。""" name: str @@ -59,6 +66,7 @@ class Group(Entity): class GroupMember(Entity): """群成员。""" + id: typing.Union[int, str] """群员 ID。""" member_name: str diff --git a/pkg/platform/types/events.py b/pkg/platform/types/events.py index 40507315..1a724beb 100644 --- a/pkg/platform/types/events.py +++ b/pkg/platform/types/events.py @@ -2,8 +2,7 @@ """ 此模块提供事件模型。 """ -from datetime import datetime -from enum import Enum + import typing import pydantic.v1 as pydantic @@ -18,15 +17,23 @@ class Event(pydantic.BaseModel): Args: type: 事件名。 """ + type: str """事件名。""" + def __repr__(self): - return self.__class__.__name__ + '(' + ', '.join( - ( - f'{k}={repr(v)}' - for k, v in self.__dict__.items() if k != 'type' and v + return ( + self.__class__.__name__ + + '(' + + ', '.join( + ( + f'{k}={repr(v)}' + for k, v in self.__dict__.items() + if k != 'type' and v + ) ) - ) + ')' + + ')' + ) @classmethod def parse_subtype(cls, obj: dict) -> 'Event': @@ -52,6 +59,7 @@ class MessageEvent(Event): type: 事件名。 message_chain: 消息内容。 """ + type: str """事件名。""" message_chain: platform_message.MessageChain @@ -74,6 +82,7 @@ class FriendMessage(MessageEvent): sender: 发送消息的好友。 message_chain: 消息内容。 """ + type: str = 'FriendMessage' """事件名。""" sender: platform_entities.Friend @@ -90,12 +99,14 @@ class GroupMessage(MessageEvent): sender: 发送消息的群成员。 message_chain: 消息内容。 """ + type: str = 'GroupMessage' """事件名。""" sender: platform_entities.GroupMember """发送消息的群成员。""" message_chain: platform_message.MessageChain """消息内容。""" + @property def group(self) -> platform_entities.Group: return self.sender.group diff --git a/pkg/platform/types/message.py b/pkg/platform/types/message.py index b99a28b3..529ce4c1 100644 --- a/pkg/platform/types/message.py +++ b/pkg/platform/types/message.py @@ -1,7 +1,6 @@ import itertools import logging from datetime import datetime -from enum import Enum from pathlib import Path import typing @@ -16,6 +15,7 @@ logger = logging.getLogger(__name__) class MessageComponentMetaclass(PlatformIndexedMetaclass): """消息组件元类。""" + __message_component__ = None def __new__(cls, name, bases, attrs, **kwargs): @@ -41,18 +41,26 @@ class MessageComponentMetaclass(PlatformIndexedMetaclass): class MessageComponent(PlatformIndexedModel, metaclass=MessageComponentMetaclass): """消息组件。""" + type: str """消息组件类型。""" + def __str__(self): return '' def __repr__(self): - return self.__class__.__name__ + '(' + ', '.join( - ( - f'{k}={repr(v)}' - for k, v in self.__dict__.items() if k != 'type' and v + return ( + self.__class__.__name__ + + '(' + + ', '.join( + ( + f'{k}={repr(v)}' + for k, v in self.__dict__.items() + if k != 'type' and v + ) ) - ) + ')' + + ')' + ) def __init__(self, *args, **kwargs): # 解析参数列表,将位置参数转化为具名参数 @@ -63,7 +71,9 @@ class MessageComponent(PlatformIndexedModel, metaclass=MessageComponentMetaclass ) for name, value in zip(parameter_names, args): if name in kwargs: - raise TypeError(f'在 `{self.type}` 中,具名参数 `{name}` 与位置参数重复。') + raise TypeError( + f'在 `{self.type}` 中,具名参数 `{name}` 与位置参数重复。' + ) kwargs[name] = value super().__init__(**kwargs) @@ -117,6 +127,7 @@ class MessageChain(PlatformBaseModel): ``` """ + __root__: typing.List[MessageComponent] @staticmethod @@ -131,10 +142,10 @@ class MessageChain(PlatformBaseModel): result.append(Plain(msg)) else: raise TypeError( - f"消息链中元素需为 dict 或 str 或 MessageComponent,当前类型:{type(msg)}" + f'消息链中元素需为 dict 或 str 或 MessageComponent,当前类型:{type(msg)}' ) return result - + @pydantic.validator('__root__', always=True, pre=True) def _parse_component(cls, msg_chain): if isinstance(msg_chain, (str, MessageComponent)): @@ -157,7 +168,7 @@ class MessageChain(PlatformBaseModel): super().__init__(__root__=__root__) def __str__(self): - return "".join(str(component) for component in self.__root__) + return ''.join(str(component) for component in self.__root__) def __repr__(self): return f'{self.__class__.__name__}({self.__root__!r})' @@ -165,8 +176,9 @@ class MessageChain(PlatformBaseModel): def __iter__(self): yield from self.__root__ - def get_first(self, - t: typing.Type[TMessageComponent]) -> typing.Optional[TMessageComponent]: + def get_first( + self, t: typing.Type[TMessageComponent] + ) -> typing.Optional[TMessageComponent]: """获取消息链中第一个符合类型的消息组件。""" for component in self: if isinstance(component, t): @@ -174,35 +186,40 @@ class MessageChain(PlatformBaseModel): return None @typing.overload - def __getitem__(self, index: int) -> MessageComponent: - ... + def __getitem__(self, index: int) -> MessageComponent: ... @typing.overload - def __getitem__(self, index: slice) -> typing.List[MessageComponent]: - ... + def __getitem__(self, index: slice) -> typing.List[MessageComponent]: ... @typing.overload - def __getitem__(self, - index: typing.Type[TMessageComponent]) -> typing.List[TMessageComponent]: - ... + def __getitem__( + self, index: typing.Type[TMessageComponent] + ) -> typing.List[TMessageComponent]: ... @typing.overload def __getitem__( self, index: typing.Tuple[typing.Type[TMessageComponent], int] - ) -> typing.List[TMessageComponent]: - ... + ) -> typing.List[TMessageComponent]: ... def __getitem__( - self, index: typing.Union[int, slice, typing.Type[TMessageComponent], - typing.Tuple[typing.Type[TMessageComponent], int]] - ) -> typing.Union[MessageComponent, typing.List[MessageComponent], - typing.List[TMessageComponent]]: + self, + index: typing.Union[ + int, + slice, + typing.Type[TMessageComponent], + typing.Tuple[typing.Type[TMessageComponent], int], + ], + ) -> typing.Union[ + MessageComponent, typing.List[MessageComponent], typing.List[TMessageComponent] + ]: return self.get(index) def __setitem__( - self, key: typing.Union[int, slice], - value: typing.Union[MessageComponent, str, typing.Iterable[typing.Union[MessageComponent, - str]]] + self, + key: typing.Union[int, slice], + value: typing.Union[ + MessageComponent, str, typing.Iterable[typing.Union[MessageComponent, str]] + ], ): if isinstance(value, str): value = Plain(value) @@ -217,8 +234,10 @@ class MessageChain(PlatformBaseModel): return reversed(self.__root__) def has( - self, sub: typing.Union[MessageComponent, typing.Type[MessageComponent], - 'MessageChain', str] + self, + sub: typing.Union[ + MessageComponent, typing.Type[MessageComponent], 'MessageChain', str + ], ) -> bool: """判断消息链中: 1. 是否有某个消息组件。 @@ -242,7 +261,7 @@ class MessageChain(PlatformBaseModel): if i == sub: return True return False - raise TypeError(f"类型不匹配,当前类型:{type(sub)}") + raise TypeError(f'类型不匹配,当前类型:{type(sub)}') def __contains__(self, sub) -> bool: return self.has(sub) @@ -293,7 +312,7 @@ class MessageChain(PlatformBaseModel): self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]], i: int = 0, - j: int = -1 + j: int = -1, ) -> int: """返回 x 在消息链中首次出现项的索引号(索引号在 i 或其后且在 j 之前)。 @@ -323,12 +342,14 @@ class MessageChain(PlatformBaseModel): for index in range(i, j): if type(self[index]) is x: return index - raise ValueError("消息链中不存在该类型的组件。") + raise ValueError('消息链中不存在该类型的组件。') if isinstance(x, MessageComponent): return self.__root__.index(x, i, j) - raise TypeError(f"类型不匹配,当前类型:{type(x)}") + raise TypeError(f'类型不匹配,当前类型:{type(x)}') - def count(self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]]) -> int: + def count( + self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]] + ) -> int: """返回消息链中 x 出现的次数。 Args: @@ -342,7 +363,7 @@ class MessageChain(PlatformBaseModel): return sum(1 for i in self if type(i) is x) if isinstance(x, MessageComponent): return self.__root__.count(x) - raise TypeError(f"类型不匹配,当前类型:{type(x)}") + raise TypeError(f'类型不匹配,当前类型:{type(x)}') def extend(self, x: typing.Iterable[typing.Union[MessageComponent, str]]): """将另一个消息链中的元素添加到消息链末尾。 @@ -394,7 +415,7 @@ class MessageChain(PlatformBaseModel): def exclude( self, x: typing.Union[MessageComponent, typing.Type[MessageComponent]], - count: int = -1 + count: int = -1, ) -> 'MessageChain': """返回移除指定元素或指定类型的元素后剩余的消息链。 @@ -405,6 +426,7 @@ class MessageChain(PlatformBaseModel): Returns: MessageChain: 剩余的消息链。 """ + def _exclude(): nonlocal count x_is_type = isinstance(x, type) @@ -423,8 +445,7 @@ class MessageChain(PlatformBaseModel): @classmethod def join(cls, *args: typing.Iterable[typing.Union[str, MessageComponent]]): return cls( - Plain(c) if isinstance(c, str) else c - for c in itertools.chain(*args) + Plain(c) if isinstance(c, str) else c for c in itertools.chain(*args) ) @property @@ -439,14 +460,19 @@ class MessageChain(PlatformBaseModel): return source.id if source else -1 -TMessage = typing.Union[MessageChain, typing.Iterable[typing.Union[MessageComponent, str]], - MessageComponent, str] +TMessage = typing.Union[ + MessageChain, + typing.Iterable[typing.Union[MessageComponent, str]], + MessageComponent, + str, +] """可以转化为 MessageChain 的类型。""" class Source(MessageComponent): """源。包含消息的基本信息。""" - type: str = "Source" + + type: str = 'Source' """消息组件类型。""" id: typing.Union[int, str] """消息的识别号,用于引用回复(Source 类型永远为 MessageChain 的第一个元素)。""" @@ -456,10 +482,12 @@ class Source(MessageComponent): class Plain(MessageComponent): """纯文本。""" - type: str = "Plain" + + type: str = 'Plain' """消息组件类型。""" text: str """文字消息。""" + def __str__(self): return self.text @@ -469,7 +497,8 @@ class Plain(MessageComponent): class Quote(MessageComponent): """引用。""" - type: str = "Quote" + + type: str = 'Quote' """消息组件类型。""" id: typing.Optional[int] = None """被引用回复的原消息的 message_id。""" @@ -482,37 +511,42 @@ class Quote(MessageComponent): origin: MessageChain """被引用回复的原消息的消息链对象。""" - @pydantic.validator("origin", always=True, pre=True) + @pydantic.validator('origin', always=True, pre=True) def origin_formater(cls, v): return MessageChain.parse_obj(v) class At(MessageComponent): """At某人。""" - type: str = "At" + + type: str = 'At' """消息组件类型。""" target: typing.Union[int, str] """群员 ID。""" display: typing.Optional[str] = None """At时显示的文字,发送消息时无效,自动使用群名片。""" + def __eq__(self, other): return isinstance(other, At) and self.target == other.target def __str__(self): - return f"@{self.display or self.target}" + return f'@{self.display or self.target}' class AtAll(MessageComponent): """At全体。""" - type: str = "AtAll" + + type: str = 'AtAll' """消息组件类型。""" + def __str__(self): - return "@全体成员" + return '@全体成员' class Image(MessageComponent): """图片。""" - type: str = "Image" + + type: str = 'Image' """消息组件类型。""" image_id: typing.Optional[str] = None """图片的 image_id,不为空时将忽略 url 属性。""" @@ -522,10 +556,13 @@ class Image(MessageComponent): """图片的路径,发送本地图片。""" base64: typing.Optional[str] = None """图片的 Base64 编码。""" + def __eq__(self, other): - return isinstance( - other, Image - ) and self.type == other.type and self.uuid == other.uuid + return ( + isinstance(other, Image) + and self.type == other.type + and self.uuid == other.uuid + ) def __str__(self): return '[图片]' @@ -537,7 +574,7 @@ class Image(MessageComponent): try: return str(Path(path).resolve(strict=True)) except FileNotFoundError: - raise ValueError(f"无效路径:{path}") + raise ValueError(f'无效路径:{path}') else: return path @@ -554,7 +591,7 @@ class Image(MessageComponent): self, filename: typing.Union[str, Path, None] = None, directory: typing.Union[str, Path, None] = None, - determine_type: bool = True + determine_type: bool = True, ): """下载图片到本地。 @@ -568,6 +605,7 @@ class Image(MessageComponent): return import httpx + async with httpx.AsyncClient() as client: response = await client.get(self.url) response.raise_for_status() @@ -577,19 +615,20 @@ class Image(MessageComponent): path = Path(filename) if determine_type: import imghdr - path = path.with_suffix( - '.' + str(imghdr.what(None, content)) - ) + + path = path.with_suffix('.' + str(imghdr.what(None, content))) path.parent.mkdir(parents=True, exist_ok=True) elif directory: import imghdr + path = Path(directory) path.mkdir(parents=True, exist_ok=True) path = path / f'{self.uuid}.{imghdr.what(None, content)}' else: - raise ValueError("请指定文件路径或文件夹路径!") + raise ValueError('请指定文件路径或文件夹路径!') import aiofiles + async with aiofiles.open(path, 'wb') as f: await f.write(content) @@ -600,7 +639,7 @@ class Image(MessageComponent): cls, filename: typing.Union[str, Path, None] = None, content: typing.Optional[bytes] = None, - ) -> "Image": + ) -> 'Image': """从本地文件路径加载图片,以 base64 的形式传递。 Args: @@ -615,16 +654,18 @@ class Image(MessageComponent): elif filename: path = Path(filename) import aiofiles + async with aiofiles.open(path, 'rb') as f: content = await f.read() else: - raise ValueError("请指定图片路径或图片内容!") + raise ValueError('请指定图片路径或图片内容!') import base64 + img = cls(base64=base64.b64encode(content).decode()) return img @classmethod - def from_unsafe_path(cls, path: typing.Union[str, Path]) -> "Image": + def from_unsafe_path(cls, path: typing.Union[str, Path]) -> 'Image': """从不安全的路径加载图片。 Args: @@ -638,7 +679,8 @@ class Image(MessageComponent): class Unknown(MessageComponent): """未知。""" - type: str = "Unknown" + + type: str = 'Unknown' """消息组件类型。""" text: str """文本。""" @@ -646,7 +688,8 @@ class Unknown(MessageComponent): class Voice(MessageComponent): """语音。""" - type: str = "Voice" + + type: str = 'Voice' """消息组件类型。""" voice_id: typing.Optional[str] = None """语音的 voice_id,不为空时将忽略 url 属性。""" @@ -658,6 +701,7 @@ class Voice(MessageComponent): """语音的 Base64 编码。""" length: typing.Optional[int] = None """语音的长度,单位为秒。""" + @pydantic.validator('path') def validate_path(cls, path: typing.Optional[str]): """修复 path 参数的行为,使之相对于 LangBot 的启动路径。""" @@ -665,7 +709,7 @@ class Voice(MessageComponent): try: return str(Path(path).resolve(strict=True)) except FileNotFoundError: - raise ValueError(f"无效路径:{path}") + raise ValueError(f'无效路径:{path}') else: return path @@ -675,7 +719,7 @@ class Voice(MessageComponent): async def download( self, filename: typing.Union[str, Path, None] = None, - directory: typing.Union[str, Path, None] = None + directory: typing.Union[str, Path, None] = None, ): """下载语音到本地。 @@ -688,6 +732,7 @@ class Voice(MessageComponent): return import httpx + async with httpx.AsyncClient() as client: response = await client.get(self.url) response.raise_for_status() @@ -701,9 +746,10 @@ class Voice(MessageComponent): path.mkdir(parents=True, exist_ok=True) path = path / f'{self.voice_id}.silk' else: - raise ValueError("请指定文件路径或文件夹路径!") + raise ValueError('请指定文件路径或文件夹路径!') import aiofiles + async with aiofiles.open(path, 'wb') as f: await f.write(content) @@ -712,7 +758,7 @@ class Voice(MessageComponent): cls, filename: typing.Union[str, Path, None] = None, content: typing.Optional[bytes] = None, - ) -> "Voice": + ) -> 'Voice': """从本地文件路径加载语音,以 base64 的形式传递。 Args: @@ -724,17 +770,20 @@ class Voice(MessageComponent): if filename: path = Path(filename) import aiofiles + async with aiofiles.open(path, 'rb') as f: content = await f.read() else: - raise ValueError("请指定语音路径或语音内容!") + raise ValueError('请指定语音路径或语音内容!') import base64 + img = cls(base64=base64.b64encode(content).decode()) return img class ForwardMessageNode(pydantic.BaseModel): """合并转发中的一条消息。""" + sender_id: typing.Optional[typing.Union[int, str]] = None """发送人ID。""" sender_name: typing.Optional[str] = None @@ -745,6 +794,7 @@ class ForwardMessageNode(pydantic.BaseModel): """消息的 message_id。""" time: typing.Optional[datetime] = None """发送时间。""" + @pydantic.validator('message_chain', check_fields=False) def _validate_message_chain(cls, value: typing.Union[MessageChain, list]): if isinstance(value, list): @@ -753,7 +803,9 @@ class ForwardMessageNode(pydantic.BaseModel): @classmethod def create( - cls, sender: typing.Union[platform_entities.Friend, platform_entities.GroupMember], message: MessageChain + cls, + sender: typing.Union[platform_entities.Friend, platform_entities.GroupMember], + message: MessageChain, ) -> 'ForwardMessageNode': """从消息链生成转发消息。 @@ -765,28 +817,28 @@ class ForwardMessageNode(pydantic.BaseModel): ForwardMessageNode: 生成的一条消息。 """ return ForwardMessageNode( - sender_id=sender.id, - sender_name=sender.get_name(), - message_chain=message + sender_id=sender.id, sender_name=sender.get_name(), message_chain=message ) class ForwardMessageDiaplay(pydantic.BaseModel): - title: str = "群聊的聊天记录" - brief: str = "[聊天记录]" - source: str = "聊天记录" + title: str = '群聊的聊天记录' + brief: str = '[聊天记录]' + source: str = '聊天记录' preview: typing.List[str] = [] - summary: str = "查看x条转发消息" + summary: str = '查看x条转发消息' class Forward(MessageComponent): """合并转发。""" - type: str = "Forward" + + type: str = 'Forward' """消息组件类型。""" display: ForwardMessageDiaplay """显示信息""" node_list: typing.List[ForwardMessageNode] """转发消息节点列表。""" + def __init__(self, *args, **kwargs): if len(args) == 1: self.node_list = args[0] @@ -799,7 +851,8 @@ class Forward(MessageComponent): class File(MessageComponent): """文件。""" - type: str = "File" + + type: str = 'File' """消息组件类型。""" id: str """文件识别 ID。""" @@ -807,6 +860,6 @@ class File(MessageComponent): """文件名称。""" size: int """文件大小。""" + def __str__(self): return f'[文件]{self.name}' - diff --git a/pkg/plugin/__init__.py b/pkg/plugin/__init__.py index c543161a..f6bf97d7 100644 --- a/pkg/plugin/__init__.py +++ b/pkg/plugin/__init__.py @@ -1,4 +1,4 @@ """插件支持包 包含插件基类、插件宿主以及部分API接口 -""" \ No newline at end of file +""" diff --git a/pkg/plugin/context.py b/pkg/plugin/context.py index fc06b9ec..cc95adaa 100644 --- a/pkg/plugin/context.py +++ b/pkg/plugin/context.py @@ -14,13 +14,10 @@ from ..platform import adapter as platform_adapter def register( - name: str, - description: str, - version: str, - author: str + name: str, description: str, version: str, author: str ) -> typing.Callable[[typing.Type[BasePlugin]], typing.Type[BasePlugin]]: """注册插件类 - + 使用示例: @register( @@ -34,15 +31,16 @@ def register( """ pass + def handler( - event: typing.Type[events.BaseEventModel] + event: typing.Type[events.BaseEventModel], ) -> typing.Callable[[typing.Callable], typing.Callable]: """注册事件监听器 - + 使用示例: class MyPlugin(BasePlugin): - + @handler(NormalMessageResponded) async def on_normal_message_responded(self, ctx: EventContext): pass @@ -51,14 +49,14 @@ def handler( def llm_func( - name: str=None, + name: str = None, ) -> typing.Callable: """注册内容函数 - + 使用示例: class MyPlugin(BasePlugin): - + @llm_func("access_the_web_page") async def _(self, query, url: str, brief_len: int): \"""Call this function to search about the question before you answer any questions. @@ -98,7 +96,7 @@ class BasePlugin(metaclass=abc.ABCMeta): async def initialize(self): """初始化阶段被调用""" pass - + async def destroy(self): """释放/禁用插件时被调用""" pass @@ -123,12 +121,12 @@ class APIHost: def get_platform_adapters(self) -> list[platform_adapter.MessagePlatformAdapter]: """获取已启用的消息平台适配器列表 - + Returns: list[platform.adapter.MessageSourceAdapter]: 已启用的消息平台适配器列表 """ return self.ap.platform_mgr.get_running_adapters() - + async def send_active_message( self, adapter: platform_adapter.MessagePlatformAdapter, @@ -137,7 +135,7 @@ class APIHost: message: platform_message.MessageChain, ): """发送主动消息 - + Args: adapter (platform.adapter.MessageSourceAdapter): 消息平台适配器对象,调用 host.get_platform_adapters() 获取并取用其中某个 target_type (str): 目标类型,`person`或`group` @@ -153,7 +151,7 @@ class APIHost: def require_ver( self, ge: str, - le: str='v999.999.999', + le: str = 'v999.999.999', ) -> bool: """插件版本要求装饰器 @@ -164,16 +162,23 @@ class APIHost: Returns: bool: 是否满足要求, False时为无法获取版本号,True时为满足要求,报错为不满足要求 """ - langbot_version = "" + langbot_version = '' try: - langbot_version = self.ap.ver_mgr.get_current_version() # 从updater模块获取版本号 - except: + langbot_version = ( + self.ap.ver_mgr.get_current_version() + ) # 从updater模块获取版本号 + except Exception: return False - if self.ap.ver_mgr.compare_version_str(langbot_version, ge) < 0 or \ - (self.ap.ver_mgr.compare_version_str(langbot_version, le) > 0): - raise Exception("LangBot 版本不满足要求,某些功能(可能是由插件提供的)无法正常使用。(要求版本:{}-{},但当前版本:{})".format(ge, le, langbot_version)) + if self.ap.ver_mgr.compare_version_str(langbot_version, ge) < 0 or ( + self.ap.ver_mgr.compare_version_str(langbot_version, le) > 0 + ): + raise Exception( + 'LangBot 版本不满足要求,某些功能(可能是由插件提供的)无法正常使用。(要求版本:{}-{},但当前版本:{})'.format( + ge, le, langbot_version + ) + ) return True @@ -220,36 +225,30 @@ class EventContext: if key not in self.__return_value__: self.__return_value__[key] = [] self.__return_value__[key].append(ret) - + async def reply(self, message_chain: platform_message.MessageChain): """回复此次消息请求 - + Args: message_chain (platform.types.MessageChain): 源平台的消息链,若用户使用的不是源平台适配器,程序也能自动转换为目标平台消息链 """ # TODO 添加 at_sender 和 quote_origin 参数 await self.event.query.adapter.reply_message( - message_source=self.event.query.message_event, - message=message_chain + message_source=self.event.query.message_event, message=message_chain ) - + async def send_message( - self, - target_type: str, - target_id: str, - message: platform_message.MessageChain + self, target_type: str, target_id: str, message: platform_message.MessageChain ): """主动发送消息 - + Args: target_type (str): 目标类型,`person`或`group` target_id (str): 目标ID message (platform.types.MessageChain): 源平台的消息链,若用户使用的不是源平台适配器,程序也能自动转换为目标平台消息链 """ await self.event.query.adapter.send_message( - target_type=target_type, - target_id=target_id, - message=message + target_type=target_type, target_id=target_id, message=message ) def prevent_postorder(self): @@ -281,10 +280,8 @@ class EventContext: def is_prevented_postorder(self): """是否阻止后序插件执行""" return self.__prevent_postorder__ - def __init__(self, host: APIHost, event: events.BaseEventModel): - self.eid = EventContext.eid self.host = host self.event = event @@ -297,16 +294,16 @@ class EventContext: class RuntimeContainerStatus(enum.Enum): """插件容器状态""" - MOUNTED = "mounted" + MOUNTED = 'mounted' """已加载进内存,所有位于运行时记录中的 RuntimeContainer 至少是这个状态""" - INITIALIZED = "initialized" + INITIALIZED = 'initialized' """已初始化""" class RuntimeContainer(pydantic.BaseModel): """运行时的插件容器 - + 运行期间存储单个插件的信息 """ @@ -352,9 +349,10 @@ class RuntimeContainer(pydantic.BaseModel): plugin_inst: typing.Optional[BasePlugin] = None """插件实例""" - event_handlers: dict[typing.Type[events.BaseEventModel], typing.Callable[ - [BasePlugin, EventContext], typing.Awaitable[None] - ]] = {} + event_handlers: dict[ + typing.Type[events.BaseEventModel], + typing.Callable[[BasePlugin, EventContext], typing.Awaitable[None]], + ] = {} """事件处理器""" tools: list[tools_entities.LLMFunction] = [] @@ -378,7 +376,7 @@ class RuntimeContainer(pydantic.BaseModel): 'pkg_path': self.pkg_path, 'enabled': self.enabled, 'priority': self.priority, - "config_schema": self.config_schema, + 'config_schema': self.config_schema, 'event_handlers': { event_name.__name__: handler.__name__ for event_name, handler in self.event_handlers.items() diff --git a/pkg/plugin/errors.py b/pkg/plugin/errors.py index bd6199e3..8da223db 100644 --- a/pkg/plugin/errors.py +++ b/pkg/plugin/errors.py @@ -2,7 +2,6 @@ from __future__ import annotations class PluginSystemError(Exception): - message: str def __init__(self, message: str): @@ -10,15 +9,13 @@ class PluginSystemError(Exception): def __str__(self): return self.message - + class PluginNotFoundError(PluginSystemError): - def __init__(self, message: str): - super().__init__(f"未找到插件: {message}") + super().__init__(f'未找到插件: {message}') class PluginInstallerError(PluginSystemError): - def __init__(self, message: str): - super().__init__(f"安装器操作错误: {message}") + super().__init__(f'安装器操作错误: {message}') diff --git a/pkg/plugin/events.py b/pkg/plugin/events.py index 152ac39f..61e84714 100644 --- a/pkg/plugin/events.py +++ b/pkg/plugin/events.py @@ -27,7 +27,7 @@ class PersonMessageReceived(BaseEventModel): launcher_id: typing.Union[int, str] """发起对象ID(群号/QQ号)""" - + sender_id: typing.Union[int, str] """发送者ID(QQ号)""" @@ -40,7 +40,7 @@ class GroupMessageReceived(BaseEventModel): launcher_type: str launcher_id: typing.Union[int, str] - + sender_id: typing.Union[int, str] message_chain: platform_message.MessageChain @@ -52,7 +52,7 @@ class PersonNormalMessageReceived(BaseEventModel): launcher_type: str launcher_id: typing.Union[int, str] - + sender_id: typing.Union[int, str] text_message: str @@ -70,7 +70,7 @@ class PersonCommandSent(BaseEventModel): launcher_type: str launcher_id: typing.Union[int, str] - + sender_id: typing.Union[int, str] command: str @@ -94,7 +94,7 @@ class GroupNormalMessageReceived(BaseEventModel): launcher_type: str launcher_id: typing.Union[int, str] - + sender_id: typing.Union[int, str] text_message: str @@ -112,7 +112,7 @@ class GroupCommandSent(BaseEventModel): launcher_type: str launcher_id: typing.Union[int, str] - + sender_id: typing.Union[int, str] command: str @@ -136,7 +136,7 @@ class NormalMessageResponded(BaseEventModel): launcher_type: str launcher_id: typing.Union[int, str] - + sender_id: typing.Union[int, str] session: core_entities.Session diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py index 2868875d..0adb0078 100644 --- a/pkg/plugin/host.py +++ b/pkg/plugin/host.py @@ -2,8 +2,8 @@ # 请从 pkg.plugin.context 引入 BasePlugin, EventContext 和 APIHost # 最早将于 v3.4 移除此模块 -from . events import * -from . context import EventContext, APIHost as PluginHost +from .events import * + def emit(*args, **kwargs): - print('插件调用了已弃用的函数 pkg.plugin.host.emit()') \ No newline at end of file + print('插件调用了已弃用的函数 pkg.plugin.host.emit()') diff --git a/pkg/plugin/installer.py b/pkg/plugin/installer.py index b9ffab8b..159967dc 100644 --- a/pkg/plugin/installer.py +++ b/pkg/plugin/installer.py @@ -1,6 +1,5 @@ from __future__ import annotations -import typing import abc from ..core import app, taskmgr @@ -23,8 +22,7 @@ class PluginInstaller(metaclass=abc.ABCMeta): plugin_source: str, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): - """安装插件 - """ + """安装插件""" raise NotImplementedError @abc.abstractmethod @@ -33,17 +31,15 @@ class PluginInstaller(metaclass=abc.ABCMeta): plugin_name: str, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): - """卸载插件 - """ + """卸载插件""" raise NotImplementedError @abc.abstractmethod async def update_plugin( self, plugin_name: str, - plugin_source: str=None, + plugin_source: str = None, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): - """更新插件 - """ + """更新插件""" raise NotImplementedError diff --git a/pkg/plugin/installers/github.py b/pkg/plugin/installers/github.py index ff36cb5b..a867b04d 100644 --- a/pkg/plugin/installers/github.py +++ b/pkg/plugin/installers/github.py @@ -2,7 +2,6 @@ from __future__ import annotations import re import os -import shutil import zipfile import ssl import certifi @@ -18,33 +17,37 @@ from ...core import taskmgr class GitHubRepoInstaller(installer.PluginInstaller): - """GitHub仓库插件安装器 - """ + """GitHub仓库插件安装器""" def get_github_plugin_repo_label(self, repo_url: str) -> list[str]: """获取username, repo""" repo = re.findall( - r"(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)", + r'(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git|/|$)', repo_url, ) if len(repo) > 0: - return repo[0].split("/") + return repo[0].split('/') else: return None - async def download_plugin_source_code(self, repo_url: str, target_path: str, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder()) -> str: + async def download_plugin_source_code( + self, + repo_url: str, + target_path: str, + task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), + ) -> str: """下载插件源码(全异步)""" repo = self.get_github_plugin_repo_label(repo_url) if repo is None: raise errors.PluginInstallerError('仅支持GitHub仓库地址') - + target_path += repo[1] - self.ap.logger.debug("正在下载源码...") - task_context.trace("下载源码...", "download-plugin-source-code") - - zipball_url = f"https://api.github.com/repos/{'/'.join(repo)}/zipball/HEAD" + self.ap.logger.debug('正在下载源码...') + task_context.trace('下载源码...', 'download-plugin-source-code') + + zipball_url = f'https://api.github.com/repos/{"/".join(repo)}/zipball/HEAD' zip_resp: bytes = None - + # 创建自定义SSL上下文,使用certifi提供的根证书 ssl_context = ssl.create_default_context(cafile=certifi.where()) @@ -52,41 +55,44 @@ class GitHubRepoInstaller(installer.PluginInstaller): async with session.get( url=zipball_url, timeout=aiohttp.ClientTimeout(total=300), - ssl=ssl_context # 使用自定义SSL上下文来验证证书 + ssl=ssl_context, # 使用自定义SSL上下文来验证证书 ) as resp: if resp.status != 200: - raise errors.PluginInstallerError(f"下载源码失败: {await resp.text()}") + raise errors.PluginInstallerError( + f'下载源码失败: {await resp.text()}' + ) zip_resp = await resp.read() - - if await aiofiles_os.path.exists("temp/" + target_path): - await aioshutil.rmtree("temp/" + target_path) + + if await aiofiles_os.path.exists('temp/' + target_path): + await aioshutil.rmtree('temp/' + target_path) if await aiofiles_os.path.exists(target_path): await aioshutil.rmtree(target_path) - await aiofiles_os.makedirs("temp/" + target_path) + await aiofiles_os.makedirs('temp/' + target_path) - async with aiofiles.open("temp/" + target_path + "/source.zip", "wb") as f: + async with aiofiles.open('temp/' + target_path + '/source.zip', 'wb') as f: await f.write(zip_resp) - self.ap.logger.debug("解压中...") - task_context.trace("解压中...", "unzip-plugin-source-code") - - with zipfile.ZipFile("temp/" + target_path + "/source.zip", "r") as zip_ref: - zip_ref.extractall("temp/" + target_path) - await aiofiles_os.remove("temp/" + target_path + "/source.zip") + self.ap.logger.debug('解压中...') + task_context.trace('解压中...', 'unzip-plugin-source-code') + + with zipfile.ZipFile('temp/' + target_path + '/source.zip', 'r') as zip_ref: + zip_ref.extractall('temp/' + target_path) + await aiofiles_os.remove('temp/' + target_path + '/source.zip') import glob - unzip_dir = glob.glob("temp/" + target_path + "/*")[0] - await aioshutil.copytree(unzip_dir, target_path + "/") + + unzip_dir = glob.glob('temp/' + target_path + '/*')[0] + await aioshutil.copytree(unzip_dir, target_path + '/') await aioshutil.rmtree(unzip_dir) - - self.ap.logger.debug("源码下载完成。") + + self.ap.logger.debug('源码下载完成。') return repo[1] async def install_requirements(self, path: str): - if os.path.exists(path + "/requirements.txt"): - pkgmgr.install_requirements(path + "/requirements.txt") + if os.path.exists(path + '/requirements.txt'): + pkgmgr.install_requirements(path + '/requirements.txt') async def install_plugin( self, @@ -94,12 +100,14 @@ class GitHubRepoInstaller(installer.PluginInstaller): task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """安装插件""" - task_context.trace("下载插件源码...", "install-plugin") - repo_label = await self.download_plugin_source_code(plugin_source, "plugins/", task_context) - task_context.trace("安装插件依赖...", "install-plugin") - await self.install_requirements("plugins/" + repo_label) - task_context.trace("完成.", "install-plugin") - + task_context.trace('下载插件源码...', 'install-plugin') + repo_label = await self.download_plugin_source_code( + plugin_source, 'plugins/', task_context + ) + task_context.trace('安装插件依赖...', 'install-plugin') + await self.install_requirements('plugins/' + repo_label) + task_context.trace('完成.', 'install-plugin') + # Caution: in the v4.0, plugin without manifest will not be able to be updated # await self.ap.plugin_mgr.setting.record_installed_plugin_source( # "plugins/" + repo_label + '/', plugin_source @@ -115,9 +123,9 @@ class GitHubRepoInstaller(installer.PluginInstaller): if plugin_container is None: raise errors.PluginInstallerError('插件不存在或未成功加载') else: - task_context.trace("删除插件目录...", "uninstall-plugin") + task_context.trace('删除插件目录...', 'uninstall-plugin') await aioshutil.rmtree(plugin_container.pkg_path) - task_context.trace("完成, 重新加载以生效.", "uninstall-plugin") + task_context.trace('完成, 重新加载以生效.', 'uninstall-plugin') async def update_plugin( self, @@ -126,14 +134,14 @@ class GitHubRepoInstaller(installer.PluginInstaller): task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): """更新插件""" - task_context.trace("更新插件...", "update-plugin") + task_context.trace('更新插件...', 'update-plugin') plugin_container = self.ap.plugin_mgr.get_plugin_by_name(plugin_name) if plugin_container is None: raise errors.PluginInstallerError('插件不存在或未成功加载') else: if plugin_container.plugin_repository: plugin_source = plugin_container.plugin_repository - task_context.trace("转交安装任务.", "update-plugin") + task_context.trace('转交安装任务.', 'update-plugin') await self.install_plugin(plugin_source, task_context) else: - raise errors.PluginInstallerError('插件无源码信息,无法更新') \ No newline at end of file + raise errors.PluginInstallerError('插件无源码信息,无法更新') diff --git a/pkg/plugin/loader.py b/pkg/plugin/loader.py index 44ded4ac..191d8bc1 100644 --- a/pkg/plugin/loader.py +++ b/pkg/plugin/loader.py @@ -1,11 +1,9 @@ from __future__ import annotations -from abc import ABCMeta -import typing import abc from ..core import app -from . import context, events +from . import context class PluginLoader(metaclass=abc.ABCMeta): @@ -25,4 +23,3 @@ class PluginLoader(metaclass=abc.ABCMeta): @abc.abstractmethod async def load_plugins(self): pass - diff --git a/pkg/plugin/loaders/classic.py b/pkg/plugin/loaders/classic.py index ae503ba3..857a7b9c 100644 --- a/pkg/plugin/loaders/classic.py +++ b/pkg/plugin/loaders/classic.py @@ -3,7 +3,6 @@ from __future__ import annotations import typing import pkgutil import importlib -import os import traceback from .. import loader, events, context, models @@ -11,7 +10,6 @@ from ...core import entities as core_entities from ...provider.tools import entities as tools_entities from ...utils import funcschema from ...discover import engine as discover_engine -from ...utils import pkgmgr class PluginLoader(loader.PluginLoader): @@ -36,17 +34,17 @@ class PluginLoader(loader.PluginLoader): """初始化""" def register( - self, - name: str, - description: str, - version: str, - author: str - ) -> typing.Callable[[typing.Type[context.BasePlugin]], typing.Type[context.BasePlugin]]: + self, name: str, description: str, version: str, author: str + ) -> typing.Callable[ + [typing.Type[context.BasePlugin]], typing.Type[context.BasePlugin] + ]: self.ap.logger.debug(f'注册插件 {name} {version} by {author}') container = context.RuntimeContainer( plugin_name=name, plugin_label=discover_engine.I18nString(en_US=name, zh_CN=name), - plugin_description=discover_engine.I18nString(en_US=description, zh_CN=description), + plugin_description=discover_engine.I18nString( + en_US=description, zh_CN=description + ), plugin_version=version, plugin_author=author, plugin_repository='', @@ -61,20 +59,21 @@ class PluginLoader(loader.PluginLoader): def wrapper(cls: context.BasePlugin) -> typing.Type[context.BasePlugin]: container.plugin_class = cls return cls - + return wrapper # 过时 # 最早将于 v3.4 版本移除 def on( - self, - event: typing.Type[events.BaseEventModel] + self, event: typing.Type[events.BaseEventModel] ) -> typing.Callable[[typing.Callable], typing.Callable]: """注册过时的事件处理器""" self.ap.logger.debug(f'注册事件处理器 {event.__name__}') + def wrapper(func: typing.Callable) -> typing.Callable: - - async def handler(plugin: context.BasePlugin, ctx: context.EventContext) -> None: + async def handler( + plugin: context.BasePlugin, ctx: context.EventContext + ) -> None: args = { 'host': ctx.host, 'event': ctx, @@ -82,12 +81,12 @@ class PluginLoader(loader.PluginLoader): # 把 ctx.event 所有的属性都放到 args 里 # for k, v in ctx.event.dict().items(): - # args[k] = v + # args[k] = v for attr_name in ctx.event.__dict__.keys(): args[attr_name] = getattr(ctx.event, attr_name) func(plugin, **args) - + self._current_container.event_handlers[event] = handler return func @@ -98,20 +97,21 @@ class PluginLoader(loader.PluginLoader): # 最早将于 v3.4 版本移除 def func( self, - name: str=None, + name: str = None, ) -> typing.Callable: """注册过时的内容函数""" self.ap.logger.debug(f'注册内容函数 {name}') + def wrapper(func: typing.Callable) -> typing.Callable: - function_schema = funcschema.get_func_schema(func) - function_name = self._current_container.plugin_name + '-' + (func.__name__ if name is None else name) + function_name = ( + self._current_container.plugin_name + + '-' + + (func.__name__ if name is None else name) + ) async def handler( - plugin: context.BasePlugin, - query: core_entities.Query, - *args, - **kwargs + plugin: context.BasePlugin, query: core_entities.Query, *args, **kwargs ): return func(*args, **kwargs) @@ -126,18 +126,19 @@ class PluginLoader(loader.PluginLoader): self._current_container.tools.append(llm_function) return func - + return wrapper - + def handler( - self, - event: typing.Type[events.BaseEventModel] + self, event: typing.Type[events.BaseEventModel] ) -> typing.Callable[[typing.Callable], typing.Callable]: """注册事件处理器""" self.ap.logger.debug(f'注册事件处理器 {event.__name__}') - def wrapper(func: typing.Callable) -> typing.Callable: - if self._current_container is None: # None indicates this plugin is registered through manifest, so ignore it here + def wrapper(func: typing.Callable) -> typing.Callable: + if ( + self._current_container is None + ): # None indicates this plugin is registered through manifest, so ignore it here return func self._current_container.event_handlers[event] = func @@ -148,17 +149,23 @@ class PluginLoader(loader.PluginLoader): def llm_func( self, - name: str=None, + name: str = None, ) -> typing.Callable: """注册内容函数""" self.ap.logger.debug(f'注册内容函数 {name}') + def wrapper(func: typing.Callable) -> typing.Callable: - - if self._current_container is None: # None indicates this plugin is registered through manifest, so ignore it here + if ( + self._current_container is None + ): # None indicates this plugin is registered through manifest, so ignore it here return func - + function_schema = funcschema.get_func_schema(func) - function_name = self._current_container.plugin_name + '-' + (func.__name__ if name is None else name) + function_name = ( + self._current_container.plugin_name + + '-' + + (func.__name__ if name is None else name) + ) llm_function = tools_entities.LLMFunction( name=function_name, @@ -171,43 +178,40 @@ class PluginLoader(loader.PluginLoader): self._current_container.tools.append(llm_function) return func - + return wrapper - - async def _walk_plugin_path( - self, - module, - prefix='', - path_prefix='' - ): - """遍历插件路径 - """ + + async def _walk_plugin_path(self, module, prefix='', path_prefix=''): + """遍历插件路径""" for item in pkgutil.iter_modules(module.__path__): if item.ispkg: await self._walk_plugin_path( - __import__(module.__name__ + "." + item.name, fromlist=[""]), - prefix + item.name + ".", - path_prefix + item.name + "/", + __import__(module.__name__ + '.' + item.name, fromlist=['']), + prefix + item.name + '.', + path_prefix + item.name + '/', ) else: try: - self._current_pkg_path = "plugins/" + path_prefix - self._current_module_path = "plugins/" + path_prefix + item.name + ".py" + self._current_pkg_path = 'plugins/' + path_prefix + self._current_module_path = ( + 'plugins/' + path_prefix + item.name + '.py' + ) self._current_container = None - importlib.import_module(module.__name__ + "." + item.name) + importlib.import_module(module.__name__ + '.' + item.name) if self._current_container is not None: self.plugins.append(self._current_container) self.ap.logger.debug(f'插件 {self._current_container} 已加载') - except: - self.ap.logger.error(f'加载插件模块 {prefix + item.name} 时发生错误') + except Exception: + self.ap.logger.error( + f'加载插件模块 {prefix + item.name} 时发生错误' + ) traceback.print_exc() async def load_plugins(self): - """加载插件 - """ + """加载插件""" setattr(models, 'register', self.register) setattr(models, 'on', self.on) setattr(models, 'func', self.func) @@ -215,4 +219,4 @@ class PluginLoader(loader.PluginLoader): setattr(context, 'register', self.register) setattr(context, 'handler', self.handler) setattr(context, 'llm_func', self.llm_func) - await self._walk_plugin_path(__import__("plugins", fromlist=[""])) + await self._walk_plugin_path(__import__('plugins', fromlist=[''])) diff --git a/pkg/plugin/loaders/manifest.py b/pkg/plugin/loaders/manifest.py index 101fdb3a..b634c5b5 100644 --- a/pkg/plugin/loaders/manifest.py +++ b/pkg/plugin/loaders/manifest.py @@ -1,12 +1,11 @@ from __future__ import annotations import typing -import abc import os import traceback from ...core import app -from .. import context, events, models +from .. import context, events from .. import loader from ...utils import funcschema from ...provider.tools import entities as tools_entities @@ -21,13 +20,12 @@ class PluginManifestLoader(loader.PluginLoader): super().__init__(ap) def handler( - self, - event: typing.Type[events.BaseEventModel] + self, event: typing.Type[events.BaseEventModel] ) -> typing.Callable[[typing.Callable], typing.Callable]: """注册事件处理器""" self.ap.logger.debug(f'注册事件处理器 {event.__name__}') + def wrapper(func: typing.Callable) -> typing.Callable: - self._current_container.event_handlers[event] = func return func @@ -36,14 +34,18 @@ class PluginManifestLoader(loader.PluginLoader): def llm_func( self, - name: str=None, + name: str = None, ) -> typing.Callable: """注册内容函数""" self.ap.logger.debug(f'注册内容函数 {name}') + def wrapper(func: typing.Callable) -> typing.Callable: - function_schema = funcschema.get_func_schema(func) - function_name = self._current_container.plugin_name + '-' + (func.__name__ if name is None else name) + function_name = ( + self._current_container.plugin_name + + '-' + + (func.__name__ if name is None else name) + ) llm_function = tools_entities.LLMFunction( name=function_name, @@ -56,7 +58,7 @@ class PluginManifestLoader(loader.PluginLoader): self._current_container.tools.append(llm_function) return func - + return wrapper async def load_plugins(self): @@ -68,7 +70,11 @@ class PluginManifestLoader(loader.PluginLoader): for plugin_manifest in plugin_manifests: try: - config_schema = plugin_manifest.spec['config'] if 'config' in plugin_manifest.spec else [] + config_schema = ( + plugin_manifest.spec['config'] + if 'config' in plugin_manifest.spec + else [] + ) current_plugin_container = context.RuntimeContainer( plugin_name=plugin_manifest.metadata.name, @@ -77,7 +83,9 @@ class PluginManifestLoader(loader.PluginLoader): plugin_version=plugin_manifest.metadata.version, plugin_author=plugin_manifest.metadata.author, plugin_repository=plugin_manifest.metadata.repository, - main_file=os.path.join(plugin_manifest.rel_dir, plugin_manifest.execution.python.path), + main_file=os.path.join( + plugin_manifest.rel_dir, plugin_manifest.execution.python.path + ), pkg_path=plugin_manifest.rel_dir, config_schema=config_schema, event_handlers={}, @@ -95,6 +103,8 @@ class PluginManifestLoader(loader.PluginLoader): # TODO load component extensions self.plugins.append(current_plugin_container) - except Exception as e: - self.ap.logger.error(f'加载插件 {plugin_manifest.metadata.name} 时发生错误') + except Exception: + self.ap.logger.error( + f'加载插件 {plugin_manifest.metadata.name} 时发生错误' + ) traceback.print_exc() diff --git a/pkg/plugin/manager.py b/pkg/plugin/manager.py index f5b5c28f..2322eb5c 100644 --- a/pkg/plugin/manager.py +++ b/pkg/plugin/manager.py @@ -1,10 +1,8 @@ from __future__ import annotations -import typing import traceback import sqlalchemy -import logging from ..core import app, taskmgr from . import context, loader, events, installer, models @@ -28,28 +26,26 @@ class PluginManager: def plugins( self, - enabled: bool=None, - status: context.RuntimeContainerStatus=None, + enabled: bool = None, + status: context.RuntimeContainerStatus = None, ) -> list[context.RuntimeContainer]: - """获取插件列表 - """ + """获取插件列表""" plugins = self.plugin_containers if enabled is not None: plugins = [plugin for plugin in plugins if plugin.enabled == enabled] - + if status is not None: plugins = [plugin for plugin in plugins if plugin.status == status] return plugins - + def get_plugin( self, author: str, plugin_name: str, ) -> context.RuntimeContainer: - """通过作者和插件名获取插件 - """ + """通过作者和插件名获取插件""" for plugin in self.plugins(): if plugin.plugin_author == author and plugin.plugin_name == plugin_name: return plugin @@ -88,20 +84,24 @@ class PluginManager: self.ap.logger.debug(f'优先级排序后的插件列表 {self.plugin_containers}') async def load_plugin_settings( - self, - plugin_containers: list[context.RuntimeContainer] + self, plugin_containers: list[context.RuntimeContainer] ): for plugin_container in plugin_containers: result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_plugin.PluginSetting) \ - .where(persistence_plugin.PluginSetting.plugin_author == plugin_container.plugin_author) - .where(persistence_plugin.PluginSetting.plugin_name == plugin_container.plugin_name) + sqlalchemy.select(persistence_plugin.PluginSetting) + .where( + persistence_plugin.PluginSetting.plugin_author + == plugin_container.plugin_author + ) + .where( + persistence_plugin.PluginSetting.plugin_name + == plugin_container.plugin_name + ) ) setting = result.first() if setting is None: - new_setting_data = { 'plugin_author': plugin_container.plugin_author, 'plugin_name': plugin_container.plugin_name, @@ -111,7 +111,9 @@ class PluginManager: } await self.ap.persistence_mgr.execute_async( - sqlalchemy.insert(persistence_plugin.PluginSetting).values(**new_setting_data) + sqlalchemy.insert(persistence_plugin.PluginSetting).values( + **new_setting_data + ) ) continue else: @@ -120,19 +122,23 @@ class PluginManager: plugin_container.plugin_config = setting.config async def dump_plugin_container_setting( - self, - plugin_container: context.RuntimeContainer + self, plugin_container: context.RuntimeContainer ): - """保存单个插件容器的设置到数据库 - """ + """保存单个插件容器的设置到数据库""" await self.ap.persistence_mgr.execute_async( sqlalchemy.update(persistence_plugin.PluginSetting) - .where(persistence_plugin.PluginSetting.plugin_author == plugin_container.plugin_author) - .where(persistence_plugin.PluginSetting.plugin_name == plugin_container.plugin_name) + .where( + persistence_plugin.PluginSetting.plugin_author + == plugin_container.plugin_author + ) + .where( + persistence_plugin.PluginSetting.plugin_name + == plugin_container.plugin_name + ) .values( enabled=plugin_container.enabled, priority=plugin_container.priority, - config=plugin_container.plugin_config + config=plugin_container.plugin_config, ) ) @@ -160,13 +166,13 @@ class PluginManager: async def destroy_plugin(self, plugin: context.RuntimeContainer): if plugin.status != context.RuntimeContainerStatus.INITIALIZED: return - + self.ap.logger.debug(f'释放插件 {plugin.plugin_name}') plugin.plugin_inst.__del__() await plugin.plugin_inst.destroy() plugin.plugin_inst = None plugin.status = context.RuntimeContainerStatus.MOUNTED - + async def destroy_plugins(self): for plugin in self.plugins(): if plugin.status != context.RuntimeContainerStatus.INITIALIZED: @@ -185,16 +191,15 @@ class PluginManager: plugin_source: str, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): - """安装插件 - """ + """安装插件""" await self.installer.install_plugin(plugin_source, task_context) await self.ap.ctr_mgr.plugin.post_install_record( { - "name": "unknown", - "remote": plugin_source, - "author": "unknown", - "version": "HEAD" + 'name': 'unknown', + 'remote': plugin_source, + 'author': 'unknown', + 'version': 'HEAD', } ) @@ -206,8 +211,7 @@ class PluginManager: plugin_name: str, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): - """卸载插件 - """ + """卸载插件""" plugin_container = self.get_plugin_by_name(plugin_name) @@ -219,10 +223,10 @@ class PluginManager: await self.ap.ctr_mgr.plugin.post_remove_record( { - "name": plugin_name, - "remote": plugin_container.plugin_repository, - "author": plugin_container.plugin_author, - "version": plugin_container.plugin_version + 'name': plugin_name, + 'remote': plugin_container.plugin_repository, + 'author': plugin_container.plugin_author, + 'version': plugin_container.plugin_version, } ) @@ -232,80 +236,82 @@ class PluginManager: async def update_plugin( self, plugin_name: str, - plugin_source: str=None, + plugin_source: str = None, task_context: taskmgr.TaskContext = taskmgr.TaskContext.placeholder(), ): - """更新插件 - """ + """更新插件""" await self.installer.update_plugin(plugin_name, plugin_source, task_context) - + plugin_container = self.get_plugin_by_name(plugin_name) await self.ap.ctr_mgr.plugin.post_update_record( plugin={ - "name": plugin_name, - "remote": plugin_container.plugin_repository, - "author": plugin_container.plugin_author, - "version": plugin_container.plugin_version + 'name': plugin_name, + 'remote': plugin_container.plugin_repository, + 'author': plugin_container.plugin_author, + 'version': plugin_container.plugin_version, }, old_version=plugin_container.plugin_version, - new_version="HEAD" + new_version='HEAD', ) task_context.trace('重载插件..', 'reload-plugin') await self.ap.reload(scope='plugin') def get_plugin_by_name(self, plugin_name: str) -> context.RuntimeContainer: - """通过插件名获取插件 - """ + """通过插件名获取插件""" for plugin in self.plugins(): if plugin.plugin_name == plugin_name: return plugin return None async def emit_event(self, event: events.BaseEventModel) -> context.EventContext: - """触发事件 - """ + """触发事件""" + + ctx = context.EventContext(host=self.api_host, event=event) - ctx = context.EventContext( - host=self.api_host, - event=event - ) - emitted_plugins: list[context.RuntimeContainer] = [] for plugin in self.plugins( - enabled=True, - status=context.RuntimeContainerStatus.INITIALIZED + enabled=True, status=context.RuntimeContainerStatus.INITIALIZED ): if event.__class__ in plugin.event_handlers: - self.ap.logger.debug(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__}') - + self.ap.logger.debug( + f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__}' + ) + is_prevented_default_before_call = ctx.is_prevented_default() try: await plugin.event_handlers[event.__class__]( - plugin.plugin_inst, - ctx + plugin.plugin_inst, ctx ) except Exception as e: - self.ap.logger.error(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__} 时发生错误: {e}') - self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") - + self.ap.logger.error( + f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__} 时发生错误: {e}' + ) + self.ap.logger.debug(f'Traceback: {traceback.format_exc()}') + emitted_plugins.append(plugin) if not is_prevented_default_before_call and ctx.is_prevented_default(): - self.ap.logger.debug(f'插件 {plugin.plugin_name} 阻止了默认行为执行') + self.ap.logger.debug( + f'插件 {plugin.plugin_name} 阻止了默认行为执行' + ) if ctx.is_prevented_postorder(): - self.ap.logger.debug(f'插件 {plugin.plugin_name} 阻止了后序插件的执行') + self.ap.logger.debug( + f'插件 {plugin.plugin_name} 阻止了后序插件的执行' + ) break for key in ctx.__return_value__.keys(): if hasattr(ctx.event, key): setattr(ctx.event, key, ctx.__return_value__[key][0]) - - self.ap.logger.debug(f'事件 {event.__class__.__name__}({ctx.eid}) 处理完成,返回值 {ctx.__return_value__}') + + self.ap.logger.debug( + f'事件 {event.__class__.__name__}({ctx.eid}) 处理完成,返回值 {ctx.__return_value__}' + ) if emitted_plugins: plugins_info: list[dict] = [ @@ -313,13 +319,13 @@ class PluginManager: 'name': plugin.plugin_name, 'remote': plugin.plugin_repository, 'version': plugin.plugin_version, - 'author': plugin.plugin_author - } for plugin in emitted_plugins + 'author': plugin.plugin_author, + } + for plugin in emitted_plugins ] await self.ap.ctr_mgr.usage.post_event_record( - plugins=plugins_info, - event_name=event.__class__.__name__ + plugins=plugins_info, event_name=event.__class__.__name__ ) return ctx @@ -330,7 +336,7 @@ class PluginManager: if plugin.plugin_name == plugin_name: if plugin.enabled == new_status: return False - + # 初始化/释放插件 if new_status: await self.initialize_plugin(plugin) @@ -338,7 +344,7 @@ class PluginManager: await self.destroy_plugin(plugin) plugin.enabled = new_status - + await self.dump_plugin_container_setting(plugin) break @@ -348,7 +354,6 @@ class PluginManager: return False async def reorder_plugins(self, plugins: list[dict]): - for plugin in plugins: plugin_name = plugin.get('name') plugin_priority = plugin.get('priority') @@ -363,7 +368,9 @@ class PluginManager: for plugin in self.plugin_containers: await self.dump_plugin_container_setting(plugin) - async def set_plugin_config(self, plugin_container: context.RuntimeContainer, new_config: dict): + async def set_plugin_config( + self, plugin_container: context.RuntimeContainer, new_config: dict + ): plugin_container.plugin_config = new_config plugin_container.plugin_inst.config = new_config diff --git a/pkg/plugin/models.py b/pkg/plugin/models.py index b8b499f5..dbde89a9 100644 --- a/pkg/plugin/models.py +++ b/pkg/plugin/models.py @@ -9,22 +9,20 @@ import typing from .context import BasePlugin as Plugin from .events import * + def register( - name: str, - description: str, - version: str, - author + name: str, description: str, version: str, author ) -> typing.Callable[[typing.Type[Plugin]], typing.Type[Plugin]]: pass def on( - event: typing.Type[BaseEventModel] + event: typing.Type[BaseEventModel], ) -> typing.Callable[[typing.Callable], typing.Callable]: pass def func( - name: str=None, + name: str = None, ) -> typing.Callable: pass diff --git a/pkg/provider/entities.py b/pkg/provider/entities.py index 0fb75f80..ff95b128 100644 --- a/pkg/provider/entities.py +++ b/pkg/provider/entities.py @@ -1,7 +1,6 @@ from __future__ import annotations import typing -import enum import pydantic.v1 as pydantic from pkg.provider import entities @@ -32,7 +31,6 @@ class ImageURLContentObject(pydantic.BaseModel): class ContentElement(pydantic.BaseModel): - type: str """内容类型""" @@ -57,7 +55,7 @@ class ContentElement(pydantic.BaseModel): @classmethod def from_image_url(cls, image_url: str): return cls(type='image_url', image_url=ImageURLContentObject(url=image_url)) - + @classmethod def from_image_base64(cls, image_base64: str): return cls(type='image_base64', image_base64=image_base64) @@ -82,15 +80,19 @@ class Message(pydantic.BaseModel): def readable_str(self) -> str: if self.content is not None: - return str(self.role) + ": " + str(self.get_content_platform_message_chain()) + return ( + str(self.role) + ': ' + str(self.get_content_platform_message_chain()) + ) elif self.tool_calls is not None: return f'调用工具: {self.tool_calls[0].id}' else: return '未知消息' - def get_content_platform_message_chain(self, prefix_text: str="") -> platform_message.MessageChain | None: + def get_content_platform_message_chain( + self, prefix_text: str = '' + ) -> platform_message.MessageChain | None: """将内容转换为平台消息 MessageChain 对象 - + Args: prefix_text (str): 首个文字组件的前缀文本 """ @@ -98,21 +100,22 @@ class Message(pydantic.BaseModel): if self.content is None: return None elif isinstance(self.content, str): - return platform_message.MessageChain([platform_message.Plain(prefix_text+self.content)]) + return platform_message.MessageChain( + [platform_message.Plain(prefix_text + self.content)] + ) elif isinstance(self.content, list): mc = [] for ce in self.content: if ce.type == 'text': mc.append(platform_message.Plain(ce.text)) elif ce.type == 'image_url': - if ce.image_url.url.startswith("http"): + if ce.image_url.url.startswith('http'): mc.append(platform_message.Image(url=ce.image_url.url)) else: # base64 - b64_str = ce.image_url.url - if b64_str.startswith("data:"): - b64_str = b64_str.split(",")[1] + if b64_str.startswith('data:'): + b64_str = b64_str.split(',')[1] mc.append(platform_message.Image(base64=b64_str)) @@ -120,7 +123,7 @@ class Message(pydantic.BaseModel): if prefix_text: for i, c in enumerate(mc): if isinstance(c, platform_message.Plain): - mc[i] = platform_message.Plain(prefix_text+c.text) + mc[i] = platform_message.Plain(prefix_text + c.text) break else: mc.insert(0, platform_message.Plain(prefix_text)) diff --git a/pkg/provider/modelmgr/errors.py b/pkg/provider/modelmgr/errors.py index d466cf11..dc3b35b6 100644 --- a/pkg/provider/modelmgr/errors.py +++ b/pkg/provider/modelmgr/errors.py @@ -2,4 +2,4 @@ class RequesterError(Exception): """Base class for all Requester errors.""" def __init__(self, message: str): - super().__init__("模型请求失败: "+message) + super().__init__('模型请求失败: ' + message) diff --git a/pkg/provider/modelmgr/modelmgr.py b/pkg/provider/modelmgr/modelmgr.py index 244d5753..25a79fec 100644 --- a/pkg/provider/modelmgr/modelmgr.py +++ b/pkg/provider/modelmgr/modelmgr.py @@ -1,8 +1,6 @@ from __future__ import annotations -import typing import sqlalchemy -import pydantic.v1 as pydantic from . import entities, requester from ...core import app @@ -12,10 +10,8 @@ from ..tools import entities as tools_entities from ...discover import engine from . import token from ...entity.persistence import model as persistence_model -from .requesters import bailianchatcmpl, chatcmpl, anthropicmsgs, moonshotchatcmpl, deepseekchatcmpl, ollamachat, giteeaichatcmpl, volcarkchatcmpl, xaichatcmpl, zhipuaichatcmpl, lmstudiochatcmpl, siliconflowchatcmpl, volcarkchatcmpl - -FETCH_MODEL_LIST_URL = "https://api.qchatgpt.rockchin.top/api/v2/fetch/model_list" +FETCH_MODEL_LIST_URL = 'https://api.qchatgpt.rockchin.top/api/v2/fetch/model_list' class ModelManager: @@ -36,7 +32,7 @@ class ModelManager: requester_components: list[engine.Component] requester_dict: dict[str, type[requester.LLMAPIRequester]] # cache - + def __init__(self, ap: app.Application): self.ap = ap self.model_list = [] @@ -45,14 +41,18 @@ class ModelManager: self.llm_models = [] self.requester_components = [] self.requester_dict = {} - + async def initialize(self): - self.requester_components = self.ap.discover.get_components_by_kind('LLMAPIRequester') + self.requester_components = self.ap.discover.get_components_by_kind( + 'LLMAPIRequester' + ) # forge requester class dict requester_dict: dict[str, type[requester.LLMAPIRequester]] = {} for component in self.requester_components: - requester_dict[component.metadata.name] = component.get_python_component_class() + requester_dict[component.metadata.name] = ( + component.get_python_component_class() + ) self.requester_dict = requester_dict @@ -74,18 +74,22 @@ class ModelManager: # load models for llm_model in llm_models: await self.load_llm_model(llm_model) - - async def load_llm_model(self, model_info: persistence_model.LLMModel | sqlalchemy.Row[persistence_model.LLMModel] | dict): + + async def load_llm_model( + self, + model_info: persistence_model.LLMModel + | sqlalchemy.Row[persistence_model.LLMModel] + | dict, + ): """加载模型""" - + if isinstance(model_info, sqlalchemy.Row): model_info = persistence_model.LLMModel(**model_info._mapping) elif isinstance(model_info, dict): model_info = persistence_model.LLMModel(**model_info) requester_inst = self.requester_dict[model_info.requester]( - ap=self.ap, - config=model_info.requester_config + ap=self.ap, config=model_info.requester_config ) await requester_inst.initialize() @@ -96,24 +100,23 @@ class ModelManager: name=model_info.uuid, tokens=model_info.api_keys, ), - requester=requester_inst + requester=requester_inst, ) self.llm_models.append(runtime_llm_model) async def get_model_by_name(self, name: str) -> entities.LLMModelInfo: # deprecated - """通过名称获取模型 - """ + """通过名称获取模型""" for model in self.model_list: if model.name == name: return model - raise ValueError(f"无法确定模型 {name} 的信息,请在元数据中配置") - + raise ValueError(f'无法确定模型 {name} 的信息,请在元数据中配置') + async def get_model_by_uuid(self, uuid: str) -> entities.LLMModelInfo: """通过uuid获取模型""" for model in self.llm_models: if model.model_entity.uuid == uuid: return model - raise ValueError(f"model {uuid} not found") + raise ValueError(f'model {uuid} not found') async def remove_llm_model(self, model_uuid: str): """移除模型""" @@ -124,10 +127,7 @@ class ModelManager: def get_available_requesters_info(self) -> list[dict]: """获取所有可用的请求器""" - return [ - component.to_plain_dict() - for component in self.requester_components - ] + return [component.to_plain_dict() for component in self.requester_components] def get_available_requester_info_by_name(self, name: str) -> dict | None: """通过名称获取请求器信息""" @@ -135,8 +135,10 @@ class ModelManager: if component.metadata.name == name: return component.to_plain_dict() return None - - def get_available_requester_manifest_by_name(self, name: str) -> engine.Component | None: + + def get_available_requester_manifest_by_name( + self, name: str + ) -> engine.Component | None: """通过名称获取请求器清单""" for component in self.requester_components: if component.metadata.name == name: @@ -151,4 +153,3 @@ class ModelManager: funcs: list[tools_entities.LLMFunction] = None, ) -> llm_entities.Message: pass - diff --git a/pkg/provider/modelmgr/requester.py b/pkg/provider/modelmgr/requester.py index 5ea8d23f..244f4c82 100644 --- a/pkg/provider/modelmgr/requester.py +++ b/pkg/provider/modelmgr/requester.py @@ -22,16 +22,21 @@ class RuntimeLLMModel: requester: LLMAPIRequester """请求器实例""" - - def __init__(self, model_entity: persistence_model.LLMModel, token_mgr: token.TokenManager, requester: LLMAPIRequester): + + def __init__( + self, + model_entity: persistence_model.LLMModel, + token_mgr: token.TokenManager, + requester: LLMAPIRequester, + ): self.model_entity = model_entity self.token_mgr = token_mgr self.requester = requester class LLMAPIRequester(metaclass=abc.ABCMeta): - """LLM API请求器 - """ + """LLM API请求器""" + name: str = None ap: app.Application @@ -42,9 +47,7 @@ class LLMAPIRequester(metaclass=abc.ABCMeta): def __init__(self, ap: app.Application, config: dict[str, typing.Any]): self.ap = ap - self.requester_cfg = { - **self.default_config - } + self.requester_cfg = {**self.default_config} self.requester_cfg.update(config) async def initialize(self): diff --git a/pkg/provider/modelmgr/requesters/anthropicmsgs.py b/pkg/provider/modelmgr/requesters/anthropicmsgs.py index 7edc4405..ca145e9e 100644 --- a/pkg/provider/modelmgr/requesters/anthropicmsgs.py +++ b/pkg/provider/modelmgr/requesters/anthropicmsgs.py @@ -2,16 +2,12 @@ from __future__ import annotations import typing import json -import traceback -import base64 import anthropic import httpx -from ....core import app -from .. import entities, errors, requester +from .. import errors, requester -from .. import entities, errors from ....core import entities as core_entities from ... import entities as llm_entities from ...tools import entities as tools_entities @@ -29,7 +25,6 @@ class AnthropicMessages(requester.LLMAPIRequester): } async def initialize(self): - httpx_client = anthropic._base_client.AsyncHttpxClientWrapper( base_url=self.requester_cfg['base_url'], # cast to a valid type because mypy doesn't understand our type narrowing @@ -40,7 +35,7 @@ class AnthropicMessages(requester.LLMAPIRequester): ) self.client = anthropic.AsyncAnthropic( - api_key="", + api_key='', http_client=httpx_client, ) @@ -55,7 +50,7 @@ class AnthropicMessages(requester.LLMAPIRequester): self.client.api_key = model.token_mgr.get_token() args = extra_args.copy() - args["model"] = model.model_entity.name + args['model'] = model.model_entity.name # 处理消息 @@ -63,14 +58,15 @@ class AnthropicMessages(requester.LLMAPIRequester): system_role_message = None for i, m in enumerate(messages): - if m.role == "system": + if m.role == 'system': system_role_message = m messages.pop(i) break - if isinstance(system_role_message, llm_entities.Message) \ - and isinstance(system_role_message.content, str): + if isinstance(system_role_message, llm_entities.Message) and isinstance( + system_role_message.content, str + ): args['system'] = system_role_message.content req_messages = [] @@ -79,67 +75,64 @@ class AnthropicMessages(requester.LLMAPIRequester): if m.role == 'tool': tool_call_id = m.tool_call_id - req_messages.append({ - "role": "user", - "content": [ - { - "type": "tool_result", - "tool_use_id": tool_call_id, - "content": m.content - } - ] - }) + req_messages.append( + { + 'role': 'user', + 'content': [ + { + 'type': 'tool_result', + 'tool_use_id': tool_call_id, + 'content': m.content, + } + ], + } + ) continue msg_dict = m.dict(exclude_none=True) - if isinstance(m.content, str) and m.content.strip() != "": - msg_dict["content"] = [ - { - "type": "text", - "text": m.content - } - ] + if isinstance(m.content, str) and m.content.strip() != '': + msg_dict['content'] = [{'type': 'text', 'text': m.content}] elif isinstance(m.content, list): - for i, ce in enumerate(m.content): - - if ce.type == "image_base64": - image_b64, image_format = await image.extract_b64_and_format(ce.image_base64) + if ce.type == 'image_base64': + image_b64, image_format = await image.extract_b64_and_format( + ce.image_base64 + ) alter_image_ele = { - "type": "image", - "source": { - "type": "base64", - "media_type": f"image/{image_format}", - "data": image_b64 - } + 'type': 'image', + 'source': { + 'type': 'base64', + 'media_type': f'image/{image_format}', + 'data': image_b64, + }, } - msg_dict["content"][i] = alter_image_ele + msg_dict['content'][i] = alter_image_ele if m.tool_calls: - for tool_call in m.tool_calls: - msg_dict["content"].append({ - "type": "tool_use", - "id": tool_call.id, - "name": tool_call.function.name, - "input": json.loads(tool_call.function.arguments) - }) + msg_dict['content'].append( + { + 'type': 'tool_use', + 'id': tool_call.id, + 'name': tool_call.function.name, + 'input': json.loads(tool_call.function.arguments), + } + ) - del msg_dict["tool_calls"] + del msg_dict['tool_calls'] req_messages.append(msg_dict) - - args["messages"] = req_messages - + args['messages'] = req_messages + if funcs: tools = await self.ap.tool_mgr.generate_tools_for_anthropic(funcs) if tools: - args["tools"] = tools + args['tools'] = tools try: # print(json.dumps(args, indent=4, ensure_ascii=False)) @@ -149,23 +142,24 @@ class AnthropicMessages(requester.LLMAPIRequester): 'content': '', 'role': resp.role, } - + assert type(resp) is anthropic.types.message.Message for block in resp.content: if block.type == 'thinking': - args['content'] = '' + block.thinking + '\n' + args['content'] + args['content'] = ( + '' + block.thinking + '\n' + args['content'] + ) elif block.type == 'text': args['content'] += block.text elif block.type == 'tool_use': assert type(block) is anthropic.types.tool_use_block.ToolUseBlock tool_call = llm_entities.ToolCall( id=block.id, - type="function", + type='function', function=llm_entities.FunctionCall( - name=block.name, - arguments=json.dumps(block.input) - ) + name=block.name, arguments=json.dumps(block.input) + ), ) if 'tool_calls' not in args: args['tool_calls'] = [] diff --git a/pkg/provider/modelmgr/requesters/bailianchatcmpl.py b/pkg/provider/modelmgr/requesters/bailianchatcmpl.py index e20e3376..287eb5b9 100644 --- a/pkg/provider/modelmgr/requesters/bailianchatcmpl.py +++ b/pkg/provider/modelmgr/requesters/bailianchatcmpl.py @@ -4,8 +4,6 @@ import typing import openai from . import chatcmpl -from .. import requester -from ....core import app class BailianChatCompletions(chatcmpl.OpenAIChatCompletions): diff --git a/pkg/provider/modelmgr/requesters/chatcmpl.py b/pkg/provider/modelmgr/requesters/chatcmpl.py index c54d466f..e341d0fb 100644 --- a/pkg/provider/modelmgr/requesters/chatcmpl.py +++ b/pkg/provider/modelmgr/requesters/chatcmpl.py @@ -2,22 +2,15 @@ from __future__ import annotations import asyncio import typing -import json -import base64 -from typing import AsyncGenerator import openai import openai.types.chat.chat_completion as chat_completion -import openai.types.chat.chat_completion_message_tool_call as chat_completion_message_tool_call import httpx -import aiohttp -import async_lru -from .. import entities, errors, requester -from ....core import entities as core_entities, app +from .. import errors, requester +from ....core import entities as core_entities from ... import entities as llm_entities from ...tools import entities as tools_entities -from ....utils import image class OpenAIChatCompletions(requester.LLMAPIRequester): @@ -26,18 +19,17 @@ class OpenAIChatCompletions(requester.LLMAPIRequester): client: openai.AsyncClient default_config: dict[str, typing.Any] = { - "base_url": "https://api.openai.com/v1", - "timeout": 120, + 'base_url': 'https://api.openai.com/v1', + 'timeout': 120, } async def initialize(self): - self.client = openai.AsyncClient( - api_key="", - base_url=self.requester_cfg["base_url"], - timeout=self.requester_cfg["timeout"], + api_key='', + base_url=self.requester_cfg['base_url'], + timeout=self.requester_cfg['timeout'], http_client=httpx.AsyncClient( - trust_env=True, timeout=self.requester_cfg["timeout"] + trust_env=True, timeout=self.requester_cfg['timeout'] ), ) @@ -54,8 +46,8 @@ class OpenAIChatCompletions(requester.LLMAPIRequester): chatcmpl_message = chat_completion.choices[0].message.model_dump() # 确保 role 字段存在且不为 None - if "role" not in chatcmpl_message or chatcmpl_message["role"] is None: - chatcmpl_message["role"] = "assistant" + if 'role' not in chatcmpl_message or chatcmpl_message['role'] is None: + chatcmpl_message['role'] = 'assistant' message = llm_entities.Message(**chatcmpl_message) @@ -72,27 +64,27 @@ class OpenAIChatCompletions(requester.LLMAPIRequester): self.client.api_key = use_model.token_mgr.get_token() args = extra_args.copy() - args["model"] = use_model.model_entity.name + args['model'] = use_model.model_entity.name if use_funcs: tools = await self.ap.tool_mgr.generate_tools_for_openai(use_funcs) if tools: - args["tools"] = tools + args['tools'] = tools # 设置此次请求中的messages messages = req_messages.copy() # 检查vision for msg in messages: - if "content" in msg and isinstance(msg["content"], list): - for me in msg["content"]: - if me["type"] == "image_base64": - me["image_url"] = {"url": me["image_base64"]} - me["type"] = "image_url" - del me["image_base64"] + if 'content' in msg and isinstance(msg['content'], list): + for me in msg['content']: + if me['type'] == 'image_base64': + me['image_url'] = {'url': me['image_base64']} + me['type'] = 'image_url' + del me['image_base64'] - args["messages"] = messages + args['messages'] = messages # 发送请求 resp = await self._req(args) @@ -113,15 +105,15 @@ class OpenAIChatCompletions(requester.LLMAPIRequester): req_messages = [] # req_messages 仅用于类内,外部同步由 query.messages 进行 for m in messages: msg_dict = m.dict(exclude_none=True) - content = msg_dict.get("content") + content = msg_dict.get('content') if isinstance(content, list): # 检查 content 列表中是否每个部分都是文本 if all( - isinstance(part, dict) and part.get("type") == "text" + isinstance(part, dict) and part.get('type') == 'text' for part in content ): # 将所有文本部分合并为一个字符串 - msg_dict["content"] = "\n".join(part["text"] for part in content) + msg_dict['content'] = '\n'.join(part['text'] for part in content) req_messages.append(msg_dict) try: @@ -133,17 +125,17 @@ class OpenAIChatCompletions(requester.LLMAPIRequester): extra_args=extra_args, ) except asyncio.TimeoutError: - raise errors.RequesterError("请求超时") + raise errors.RequesterError('请求超时') except openai.BadRequestError as e: - if "context_length_exceeded" in e.message: - raise errors.RequesterError(f"上文过长,请重置会话: {e.message}") + if 'context_length_exceeded' in e.message: + raise errors.RequesterError(f'上文过长,请重置会话: {e.message}') else: - raise errors.RequesterError(f"请求参数错误: {e.message}") + raise errors.RequesterError(f'请求参数错误: {e.message}') except openai.AuthenticationError as e: - raise errors.RequesterError(f"无效的 api-key: {e.message}") + raise errors.RequesterError(f'无效的 api-key: {e.message}') except openai.NotFoundError as e: - raise errors.RequesterError(f"请求路径错误: {e.message}") + raise errors.RequesterError(f'请求路径错误: {e.message}') except openai.RateLimitError as e: - raise errors.RequesterError(f"请求过于频繁或余额不足: {e.message}") + raise errors.RequesterError(f'请求过于频繁或余额不足: {e.message}') except openai.APIError as e: - raise errors.RequesterError(f"请求错误: {e.message}") + raise errors.RequesterError(f'请求错误: {e.message}') diff --git a/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py b/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py index ee17ac05..49457ac0 100644 --- a/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py +++ b/pkg/provider/modelmgr/requesters/deepseekchatcmpl.py @@ -3,8 +3,8 @@ from __future__ import annotations import typing from . import chatcmpl -from .. import entities, errors, requester -from ....core import entities as core_entities, app +from .. import errors, requester +from ....core import entities as core_entities from ... import entities as llm_entities from ...tools import entities as tools_entities @@ -28,23 +28,23 @@ class DeepseekChatCompletions(chatcmpl.OpenAIChatCompletions): self.client.api_key = use_model.token_mgr.get_token() args = extra_args.copy() - args["model"] = use_model.model_entity.name + args['model'] = use_model.model_entity.name if use_funcs: tools = await self.ap.tool_mgr.generate_tools_for_openai(use_funcs) if tools: - args["tools"] = tools + args['tools'] = tools # 设置此次请求中的messages messages = req_messages # deepseek 不支持多模态,把content都转换成纯文字 for m in messages: - if 'content' in m and isinstance(m["content"], list): - m["content"] = " ".join([c["text"] for c in m["content"]]) + if 'content' in m and isinstance(m['content'], list): + m['content'] = ' '.join([c['text'] for c in m['content']]) - args["messages"] = messages + args['messages'] = messages # 发送请求 resp = await self._req(args) @@ -55,4 +55,4 @@ class DeepseekChatCompletions(chatcmpl.OpenAIChatCompletions): # 处理请求结果 message = await self._make_msg(resp) - return message \ No newline at end of file + return message diff --git a/pkg/provider/modelmgr/requesters/giteeaichatcmpl.py b/pkg/provider/modelmgr/requesters/giteeaichatcmpl.py index 35052682..b85cc54d 100644 --- a/pkg/provider/modelmgr/requesters/giteeaichatcmpl.py +++ b/pkg/provider/modelmgr/requesters/giteeaichatcmpl.py @@ -1,17 +1,13 @@ from __future__ import annotations -import json -import asyncio -import aiohttp import typing from . import chatcmpl -from .. import entities, errors, requester -from ....core import app, entities as core_entities +from .. import requester +from ....core import entities as core_entities from ... import entities as llm_entities from ...tools import entities as tools_entities -from .. import entities as modelmgr_entities class GiteeAIChatCompletions(chatcmpl.OpenAIChatCompletions): @@ -33,20 +29,20 @@ class GiteeAIChatCompletions(chatcmpl.OpenAIChatCompletions): self.client.api_key = use_model.token_mgr.get_token() args = extra_args.copy() - args["model"] = use_model.model_entity.name + args['model'] = use_model.model_entity.name if use_funcs: tools = await self.ap.tool_mgr.generate_tools_for_openai(use_funcs) if tools: - args["tools"] = tools + args['tools'] = tools # gitee 不支持多模态,把content都转换成纯文字 for m in req_messages: - if 'content' in m and isinstance(m["content"], list): - m["content"] = " ".join([c["text"] for c in m["content"]]) + if 'content' in m and isinstance(m['content'], list): + m['content'] = ' '.join([c['text'] for c in m['content']]) - args["messages"] = req_messages + args['messages'] = req_messages resp = await self._req(args) diff --git a/pkg/provider/modelmgr/requesters/lmstudiochatcmpl.py b/pkg/provider/modelmgr/requesters/lmstudiochatcmpl.py index 6be76051..c9060c1b 100644 --- a/pkg/provider/modelmgr/requesters/lmstudiochatcmpl.py +++ b/pkg/provider/modelmgr/requesters/lmstudiochatcmpl.py @@ -4,8 +4,6 @@ import typing import openai from . import chatcmpl -from .. import requester -from ....core import app class LmStudioChatCompletions(chatcmpl.OpenAIChatCompletions): diff --git a/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py b/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py index 1ef7d9c9..ac565cdb 100644 --- a/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py +++ b/pkg/provider/modelmgr/requesters/moonshotchatcmpl.py @@ -2,11 +2,10 @@ from __future__ import annotations import typing -from ....core import app from . import chatcmpl -from .. import entities, errors, requester -from ....core import entities as core_entities, app +from .. import requester +from ....core import entities as core_entities from ... import entities as llm_entities from ...tools import entities as tools_entities @@ -30,26 +29,26 @@ class MoonshotChatCompletions(chatcmpl.OpenAIChatCompletions): self.client.api_key = use_model.token_mgr.get_token() args = extra_args.copy() - args["model"] = use_model.model_entity.name + args['model'] = use_model.model_entity.name if use_funcs: tools = await self.ap.tool_mgr.generate_tools_for_openai(use_funcs) if tools: - args["tools"] = tools + args['tools'] = tools # 设置此次请求中的messages messages = req_messages # deepseek 不支持多模态,把content都转换成纯文字 for m in messages: - if 'content' in m and isinstance(m["content"], list): - m["content"] = " ".join([c["text"] for c in m["content"]]) + if 'content' in m and isinstance(m['content'], list): + m['content'] = ' '.join([c['text'] for c in m['content']]) # 删除空的 - messages = [m for m in messages if m["content"].strip() != ""] + messages = [m for m in messages if m['content'].strip() != ''] - args["messages"] = messages + args['messages'] = messages # 发送请求 resp = await self._req(args) @@ -57,4 +56,4 @@ class MoonshotChatCompletions(chatcmpl.OpenAIChatCompletions): # 处理请求结果 message = await self._make_msg(resp) - return message \ No newline at end of file + return message diff --git a/pkg/provider/modelmgr/requesters/ollamachat.py b/pkg/provider/modelmgr/requesters/ollamachat.py index ee331036..995dd855 100644 --- a/pkg/provider/modelmgr/requesters/ollamachat.py +++ b/pkg/provider/modelmgr/requesters/ollamachat.py @@ -6,18 +6,15 @@ import typing from typing import Union, Mapping, Any, AsyncIterator import uuid import json -import base64 -import async_lru import ollama -from .. import entities, errors, requester +from .. import errors, requester from ... import entities as llm_entities from ...tools import entities as tools_entities -from ....core import app, entities as core_entities -from ....utils import image +from ....core import entities as core_entities -REQUESTER_NAME: str = "ollama-chat" +REQUESTER_NAME: str = 'ollama-chat' class OllamaChatCompletions(requester.LLMAPIRequester): @@ -26,13 +23,13 @@ class OllamaChatCompletions(requester.LLMAPIRequester): client: ollama.AsyncClient default_config: dict[str, typing.Any] = { - "base_url": "http://127.0.0.1:11434", - "timeout": 120, + 'base_url': 'http://127.0.0.1:11434', + 'timeout': 120, } async def initialize(self): - os.environ["OLLAMA_HOST"] = self.requester_cfg["base_url"] - self.client = ollama.AsyncClient(timeout=self.requester_cfg["timeout"]) + os.environ['OLLAMA_HOST'] = self.requester_cfg['base_url'] + self.client = ollama.AsyncClient(timeout=self.requester_cfg['timeout']) async def _req( self, @@ -49,35 +46,35 @@ class OllamaChatCompletions(requester.LLMAPIRequester): extra_args: dict[str, typing.Any] = {}, ) -> llm_entities.Message: args = extra_args.copy() - args["model"] = use_model.model_entity.name + args['model'] = use_model.model_entity.name messages: list[dict] = req_messages.copy() for msg in messages: - if "content" in msg and isinstance(msg["content"], list): + if 'content' in msg and isinstance(msg['content'], list): text_content: list = [] image_urls: list = [] - for me in msg["content"]: - if me["type"] == "text": - text_content.append(me["text"]) - elif me["type"] == "image_base64": - image_urls.append(me["image_base64"]) + for me in msg['content']: + if me['type'] == 'text': + text_content.append(me['text']) + elif me['type'] == 'image_base64': + image_urls.append(me['image_base64']) - msg["content"] = "\n".join(text_content) - msg["images"] = [url.split(",")[1] for url in image_urls] + msg['content'] = '\n'.join(text_content) + msg['images'] = [url.split(',')[1] for url in image_urls] if ( - "tool_calls" in msg + 'tool_calls' in msg ): # LangBot 内部以 str 存储 tool_calls 的参数,这里需要转换为 dict - for tool_call in msg["tool_calls"]: - tool_call["function"]["arguments"] = json.loads( - tool_call["function"]["arguments"] + for tool_call in msg['tool_calls']: + tool_call['function']['arguments'] = json.loads( + tool_call['function']['arguments'] ) - args["messages"] = messages + args['messages'] = messages - args["tools"] = [] + args['tools'] = [] if user_funcs: tools = await self.ap.tool_mgr.generate_tools_for_openai(user_funcs) if tools: - args["tools"] = tools + args['tools'] = tools resp = await self._req(args) message: llm_entities.Message = await self._make_msg(resp) @@ -93,7 +90,7 @@ class OllamaChatCompletions(requester.LLMAPIRequester): ret_msg: llm_entities.Message = None if message.content is not None: - ret_msg = llm_entities.Message(role="assistant", content=message.content) + ret_msg = llm_entities.Message(role='assistant', content=message.content) if message.tool_calls is not None and len(message.tool_calls) > 0: tool_calls: list[llm_entities.ToolCall] = [] @@ -101,7 +98,7 @@ class OllamaChatCompletions(requester.LLMAPIRequester): tool_calls.append( llm_entities.ToolCall( id=uuid.uuid4().hex, - type="function", + type='function', function=llm_entities.FunctionCall( name=tool_call.function.name, arguments=json.dumps(tool_call.function.arguments), @@ -123,13 +120,13 @@ class OllamaChatCompletions(requester.LLMAPIRequester): req_messages: list = [] for m in messages: msg_dict: dict = m.dict(exclude_none=True) - content: Any = msg_dict.get("content") + content: Any = msg_dict.get('content') if isinstance(content, list): if all( - isinstance(part, dict) and part.get("type") == "text" + isinstance(part, dict) and part.get('type') == 'text' for part in content ): - msg_dict["content"] = "\n".join(part["text"] for part in content) + msg_dict['content'] = '\n'.join(part['text'] for part in content) req_messages.append(msg_dict) try: return await self._closure( @@ -140,4 +137,4 @@ class OllamaChatCompletions(requester.LLMAPIRequester): extra_args=extra_args, ) except asyncio.TimeoutError: - raise errors.RequesterError("请求超时") + raise errors.RequesterError('请求超时') diff --git a/pkg/provider/modelmgr/requesters/siliconflowchatcmpl.py b/pkg/provider/modelmgr/requesters/siliconflowchatcmpl.py index dd5b9a14..3636d9d1 100644 --- a/pkg/provider/modelmgr/requesters/siliconflowchatcmpl.py +++ b/pkg/provider/modelmgr/requesters/siliconflowchatcmpl.py @@ -4,8 +4,6 @@ import typing import openai from . import chatcmpl -from .. import requester -from ....core import app class SiliconFlowChatCompletions(chatcmpl.OpenAIChatCompletions): diff --git a/pkg/provider/modelmgr/requesters/volcarkchatcmpl.py b/pkg/provider/modelmgr/requesters/volcarkchatcmpl.py index 9b5505e1..7eb68956 100644 --- a/pkg/provider/modelmgr/requesters/volcarkchatcmpl.py +++ b/pkg/provider/modelmgr/requesters/volcarkchatcmpl.py @@ -4,8 +4,6 @@ import typing import openai from . import chatcmpl -from .. import requester -from ....core import app class VolcArkChatCompletions(chatcmpl.OpenAIChatCompletions): diff --git a/pkg/provider/modelmgr/requesters/xaichatcmpl.py b/pkg/provider/modelmgr/requesters/xaichatcmpl.py index e08af875..db2022f1 100644 --- a/pkg/provider/modelmgr/requesters/xaichatcmpl.py +++ b/pkg/provider/modelmgr/requesters/xaichatcmpl.py @@ -4,8 +4,6 @@ import typing import openai from . import chatcmpl -from .. import requester -from ....core import app class XaiChatCompletions(chatcmpl.OpenAIChatCompletions): diff --git a/pkg/provider/modelmgr/requesters/zhipuaichatcmpl.py b/pkg/provider/modelmgr/requesters/zhipuaichatcmpl.py index 7bbca164..a1a07068 100644 --- a/pkg/provider/modelmgr/requesters/zhipuaichatcmpl.py +++ b/pkg/provider/modelmgr/requesters/zhipuaichatcmpl.py @@ -3,9 +3,7 @@ from __future__ import annotations import typing import openai -from ....core import app from . import chatcmpl -from .. import requester class ZhipuAIChatCompletions(chatcmpl.OpenAIChatCompletions): diff --git a/pkg/provider/modelmgr/token.py b/pkg/provider/modelmgr/token.py index eeec6986..9f477243 100644 --- a/pkg/provider/modelmgr/token.py +++ b/pkg/provider/modelmgr/token.py @@ -3,9 +3,8 @@ from __future__ import annotations import typing -class TokenManager(): - """鉴权 Token 管理器 - """ +class TokenManager: + """鉴权 Token 管理器""" name: str @@ -20,6 +19,6 @@ class TokenManager(): def get_token(self) -> str: return self.tokens[self.using_token_index] - + def next_token(self): self.using_token_index = (self.using_token_index + 1) % len(self.tokens) diff --git a/pkg/provider/runner.py b/pkg/provider/runner.py index 1762e546..ccfcee73 100644 --- a/pkg/provider/runner.py +++ b/pkg/provider/runner.py @@ -9,9 +9,10 @@ from . import entities as llm_entities preregistered_runners: list[typing.Type[RequestRunner]] = [] + def runner_class(name: str): - """注册一个请求运行器 - """ + """注册一个请求运行器""" + def decorator(cls: typing.Type[RequestRunner]) -> typing.Type[RequestRunner]: cls.name = name preregistered_runners.append(cls) @@ -21,8 +22,8 @@ def runner_class(name: str): class RequestRunner(abc.ABC): - """请求运行器 - """ + """请求运行器""" + name: str = None ap: app.Application @@ -34,7 +35,8 @@ class RequestRunner(abc.ABC): self.pipeline_config = pipeline_config @abc.abstractmethod - async def run(self, query: core_entities.Query) -> typing.AsyncGenerator[llm_entities.Message, None]: - """运行请求 - """ + async def run( + self, query: core_entities.Query + ) -> typing.AsyncGenerator[llm_entities.Message, None]: + """运行请求""" pass diff --git a/pkg/provider/runners/dashscopeapi.py b/pkg/provider/runners/dashscopeapi.py index d5e6a83d..92a1eb18 100644 --- a/pkg/provider/runners/dashscopeapi.py +++ b/pkg/provider/runners/dashscopeapi.py @@ -1,8 +1,6 @@ from __future__ import annotations import typing -import json -import base64 import re import dashscope @@ -10,7 +8,7 @@ import dashscope from .. import runner from ...core import app, entities as core_entities from .. import entities as llm_entities -from ...utils import image + class DashscopeAPIError(Exception): """Dashscope API 请求失败""" @@ -20,49 +18,49 @@ class DashscopeAPIError(Exception): super().__init__(self.message) -@runner.runner_class("dashscope-app-api") +@runner.runner_class('dashscope-app-api') class DashScopeAPIRunner(runner.RequestRunner): "阿里云百炼DashsscopeAPI对话请求器" - + # 运行器内部使用的配置 - app_type: str # 应用类型 - app_id: str # 应用ID - api_key: str # API Key - references_quote: str # 引用资料提示(当展示回答来源功能开启时,这个变量会作为引用资料名前的提示,可在provider.json中配置) + app_type: str # 应用类型 + app_id: str # 应用ID + api_key: str # API Key + references_quote: str # 引用资料提示(当展示回答来源功能开启时,这个变量会作为引用资料名前的提示,可在provider.json中配置) def __init__(self, ap: app.Application, pipeline_config: dict): """初始化""" self.ap = ap self.pipeline_config = pipeline_config - valid_app_types = ["agent", "workflow"] - self.app_type = self.pipeline_config["ai"]["dashscope-app-api"]["app-type"] - #检查配置文件中使用的应用类型是否支持 - if (self.app_type not in valid_app_types): - raise DashscopeAPIError( - f"不支持的 Dashscope 应用类型: {self.app_type}" - ) - - #初始化Dashscope 参数配置 - self.app_id = self.pipeline_config["ai"]["dashscope-app-api"]["app-id"] - self.api_key = self.pipeline_config["ai"]["dashscope-app-api"]["api-key"] - self.references_quote = self.pipeline_config["ai"]["dashscope-app-api"]["references_quote"] - + valid_app_types = ['agent', 'workflow'] + self.app_type = self.pipeline_config['ai']['dashscope-app-api']['app-type'] + # 检查配置文件中使用的应用类型是否支持 + if self.app_type not in valid_app_types: + raise DashscopeAPIError(f'不支持的 Dashscope 应用类型: {self.app_type}') + + # 初始化Dashscope 参数配置 + self.app_id = self.pipeline_config['ai']['dashscope-app-api']['app-id'] + self.api_key = self.pipeline_config['ai']['dashscope-app-api']['api-key'] + self.references_quote = self.pipeline_config['ai']['dashscope-app-api'][ + 'references_quote' + ] + def _replace_references(self, text, references_dict): """阿里云百炼平台的自定义应用支持资料引用,此函数可以将引用标签替换为参考资料""" - + # 匹配 [index_id] 形式的字符串 pattern = re.compile(r'\[(.*?)\]') def replacement(match): # 获取引用编号 - ref_key = match.group(1) + ref_key = match.group(1) if ref_key in references_dict: # 如果有对应的参考资料按照provider.json中的reference_quote返回提示,来自哪个参考资料文件 - return f"({self.references_quote} {references_dict[ref_key]})" + return f'({self.references_quote} {references_dict[ref_key]})' else: # 如果没有对应的参考资料,保留原样 - return match.group(0) + return match.group(0) # 使用 re.sub() 进行替换 return pattern.sub(replacement, text) @@ -71,14 +69,14 @@ class DashScopeAPIRunner(runner.RequestRunner): self, query: core_entities.Query ) -> tuple[str, list[str]]: """预处理用户消息,提取纯文本,阿里云提供的上传文件方法过于复杂,暂不支持上传文件(包括图片)""" - plain_text = "" + plain_text = '' image_ids = [] if isinstance(query.user_message.content, list): for ce in query.user_message.content: - if ce.type == "text": + if ce.type == 'text': plain_text += ce.text # 暂时不支持上传图片,保留代码以便后续扩展 - # elif ce.type == "image_base64": + # elif ce.type == "image_base64": # image_b64, image_format = await image.extract_b64_and_format(ce.image_base64) # file_bytes = base64.b64decode(image_b64) # file = ("img.png", file_bytes, f"image/{image_format}") @@ -92,147 +90,141 @@ class DashScopeAPIRunner(runner.RequestRunner): plain_text = query.user_message.content return plain_text, image_ids - - + async def _agent_messages( self, query: core_entities.Query ) -> typing.AsyncGenerator[llm_entities.Message, None]: """Dashscope 智能体对话请求""" - - #局部变量 - chunk = None # 流式传输的块 - pending_content = "" # 待处理的Agent输出内容 - references_dict = {} # 用于存储引用编号和对应的参考资料 - plain_text = "" # 用户输入的纯文本信息 - image_ids = [] # 用户输入的图片ID列表 (暂不支持) - + + # 局部变量 + chunk = None # 流式传输的块 + pending_content = '' # 待处理的Agent输出内容 + references_dict = {} # 用于存储引用编号和对应的参考资料 + plain_text = '' # 用户输入的纯文本信息 + image_ids = [] # 用户输入的图片ID列表 (暂不支持) + plain_text, image_ids = await self._preprocess_user_message(query) - - #发送对话请求 + + # 发送对话请求 response = dashscope.Application.call( - api_key=self.api_key, # 智能体应用的API Key - app_id=self.app_id, # 智能体应用的ID - prompt=plain_text, # 用户输入的文本信息 - stream=True, # 流式输出 - incremental_output=True, # 增量输出,使用流式输出需要开启增量输出 - session_id=query.session.using_conversation.uuid, # 会话ID用于,多轮对话 + api_key=self.api_key, # 智能体应用的API Key + app_id=self.app_id, # 智能体应用的ID + prompt=plain_text, # 用户输入的文本信息 + stream=True, # 流式输出 + incremental_output=True, # 增量输出,使用流式输出需要开启增量输出 + session_id=query.session.using_conversation.uuid, # 会话ID用于,多轮对话 # rag_options={ # 主要用于文件交互,暂不支持 # "session_file_ids": ["FILE_ID1"], # FILE_ID1 替换为实际的临时文件ID,逗号隔开多个 # } ) for chunk in response: - if chunk.get("status_code") != 200: + if chunk.get('status_code') != 200: raise DashscopeAPIError( - f"Dashscope API 请求失败: status_code={chunk.get('status_code')} message={chunk.get('message')} request_id={chunk.get('request_id')} " + f'Dashscope API 请求失败: status_code={chunk.get("status_code")} message={chunk.get("message")} request_id={chunk.get("request_id")} ' ) if not chunk: continue - - #获取流式传输的output - stream_output = chunk.get("output", {}) - if stream_output.get("text") is not None: - pending_content += stream_output.get("text") - - #保存当前会话的session_id用于下次对话的语境 - query.session.using_conversation.uuid = stream_output.get("session_id") - - #获取模型传出的参考资料列表 - references_dict_list = stream_output.get("doc_references", []) - - #从模型传出的参考资料信息中提取用于替换的字典 + + # 获取流式传输的output + stream_output = chunk.get('output', {}) + if stream_output.get('text') is not None: + pending_content += stream_output.get('text') + + # 保存当前会话的session_id用于下次对话的语境 + query.session.using_conversation.uuid = stream_output.get('session_id') + + # 获取模型传出的参考资料列表 + references_dict_list = stream_output.get('doc_references', []) + + # 从模型传出的参考资料信息中提取用于替换的字典 if references_dict_list is not None: for doc in references_dict_list: - if doc.get("index_id") is not None: - references_dict[doc.get("index_id")] = doc.get("doc_name") - - #将参考资料替换到文本中 + if doc.get('index_id') is not None: + references_dict[doc.get('index_id')] = doc.get('doc_name') + + # 将参考资料替换到文本中 pending_content = self._replace_references(pending_content, references_dict) - + yield llm_entities.Message( - role="assistant", + role='assistant', content=pending_content, ) - - + async def _workflow_messages( self, query: core_entities.Query ) -> typing.AsyncGenerator[llm_entities.Message, None]: """Dashscope 工作流对话请求""" - - #局部变量 - chunk = None # 流式传输的块 - pending_content = "" # 待处理的Agent输出内容 - references_dict = {} # 用于存储引用编号和对应的参考资料 - plain_text = "" # 用户输入的纯文本信息 - image_ids = [] # 用户输入的图片ID列表 (暂不支持) - + + # 局部变量 + chunk = None # 流式传输的块 + pending_content = '' # 待处理的Agent输出内容 + references_dict = {} # 用于存储引用编号和对应的参考资料 + plain_text = '' # 用户输入的纯文本信息 + image_ids = [] # 用户输入的图片ID列表 (暂不支持) + plain_text, image_ids = await self._preprocess_user_message(query) biz_params = {} biz_params.update(query.variables) - - #发送对话请求 + + # 发送对话请求 response = dashscope.Application.call( - api_key=self.api_key, # 智能体应用的API Key - app_id=self.app_id, # 智能体应用的ID - prompt=plain_text, # 用户输入的文本信息 - stream=True, # 流式输出 - incremental_output=True, # 增量输出,使用流式输出需要开启增量输出 - session_id=query.session.using_conversation.uuid, # 会话ID用于,多轮对话 - biz_params=biz_params, # 工作流应用的自定义输入参数传递 + api_key=self.api_key, # 智能体应用的API Key + app_id=self.app_id, # 智能体应用的ID + prompt=plain_text, # 用户输入的文本信息 + stream=True, # 流式输出 + incremental_output=True, # 增量输出,使用流式输出需要开启增量输出 + session_id=query.session.using_conversation.uuid, # 会话ID用于,多轮对话 + biz_params=biz_params, # 工作流应用的自定义输入参数传递 # rag_options={ # 主要用于文件交互,暂不支持 # "session_file_ids": ["FILE_ID1"], # FILE_ID1 替换为实际的临时文件ID,逗号隔开多个 # } ) - - #处理API返回的流式输出 + + # 处理API返回的流式输出 for chunk in response: - if chunk.get("status_code") != 200: + if chunk.get('status_code') != 200: raise DashscopeAPIError( - f"Dashscope API 请求失败: status_code={chunk.get('status_code')} message={chunk.get('message')} request_id={chunk.get('request_id')} " + f'Dashscope API 请求失败: status_code={chunk.get("status_code")} message={chunk.get("message")} request_id={chunk.get("request_id")} ' ) if not chunk: continue - - #获取流式传输的output - stream_output = chunk.get("output", {}) - if stream_output.get("text") is not None: - pending_content += stream_output.get("text") - - #保存当前会话的session_id用于下次对话的语境 - query.session.using_conversation.uuid = stream_output.get("session_id") - - #获取模型传出的参考资料列表 - references_dict_list = stream_output.get("doc_references", []) - - #从模型传出的参考资料信息中提取用于替换的字典 + + # 获取流式传输的output + stream_output = chunk.get('output', {}) + if stream_output.get('text') is not None: + pending_content += stream_output.get('text') + + # 保存当前会话的session_id用于下次对话的语境 + query.session.using_conversation.uuid = stream_output.get('session_id') + + # 获取模型传出的参考资料列表 + references_dict_list = stream_output.get('doc_references', []) + + # 从模型传出的参考资料信息中提取用于替换的字典 if references_dict_list is not None: for doc in references_dict_list: - if doc.get("index_id") is not None: - references_dict[doc.get("index_id")] = doc.get("doc_name") - - #将参考资料替换到文本中 + if doc.get('index_id') is not None: + references_dict[doc.get('index_id')] = doc.get('doc_name') + + # 将参考资料替换到文本中 pending_content = self._replace_references(pending_content, references_dict) - + yield llm_entities.Message( - role="assistant", + role='assistant', content=pending_content, ) - + async def run( self, query: core_entities.Query ) -> typing.AsyncGenerator[llm_entities.Message, None]: """运行""" - if self.app_type == "agent": + if self.app_type == 'agent': async for msg in self._agent_messages(query): yield msg - elif self.app_type == "workflow": + elif self.app_type == 'workflow': async for msg in self._workflow_messages(query): yield msg else: - raise DashscopeAPIError( - f"不支持的 Dashscope 应用类型: {self.app_type}" - ) - - + raise DashscopeAPIError(f'不支持的 Dashscope 应用类型: {self.app_type}') diff --git a/pkg/provider/runners/difysvapi.py b/pkg/provider/runners/difysvapi.py index f48cbd57..1d1576a6 100644 --- a/pkg/provider/runners/difysvapi.py +++ b/pkg/provider/runners/difysvapi.py @@ -5,9 +5,7 @@ import json import uuid import re import base64 -import datetime -import aiohttp from .. import runner from ...core import app, entities as core_entities @@ -17,7 +15,7 @@ from ...utils import image from libs.dify_service_api.v1 import client, errors -@runner.runner_class("dify-service-api") +@runner.runner_class('dify-service-api') class DifyServiceAPIRunner(runner.RequestRunner): """Dify Service API 对话请求器""" @@ -27,38 +25,54 @@ class DifyServiceAPIRunner(runner.RequestRunner): self.ap = ap self.pipeline_config = pipeline_config - valid_app_types = ["chat", "agent", "workflow"] + valid_app_types = ['chat', 'agent', 'workflow'] if ( - self.pipeline_config["ai"]["dify-service-api"]["app-type"] + self.pipeline_config['ai']['dify-service-api']['app-type'] not in valid_app_types ): raise errors.DifyAPIError( - f"不支持的 Dify 应用类型: {self.pipeline_config['ai']['dify-service-api']['app-type']}" + f'不支持的 Dify 应用类型: {self.pipeline_config["ai"]["dify-service-api"]["app-type"]}' ) - api_key = self.pipeline_config["ai"]["dify-service-api"]["api-key"] + api_key = self.pipeline_config['ai']['dify-service-api']['api-key'] self.dify_client = client.AsyncDifyServiceClient( api_key=api_key, - base_url=self.pipeline_config["ai"]["dify-service-api"]["base-url"], + base_url=self.pipeline_config['ai']['dify-service-api']['base-url'], ) def _try_convert_thinking(self, resp_text: str) -> str: """尝试转换 Dify 的思考提示""" - if not resp_text.startswith("
Thinking... "): + if not resp_text.startswith( + '
Thinking... ' + ): return resp_text - if self.pipeline_config["ai"]["dify-service-api"]["thinking-convert"] == "original": + if ( + self.pipeline_config['ai']['dify-service-api']['thinking-convert'] + == 'original' + ): return resp_text - - if self.pipeline_config["ai"]["dify-service-api"]["thinking-convert"] == "remove": - return re.sub(r'
Thinking... .*?
', '', resp_text, flags=re.DOTALL) - - if self.pipeline_config["ai"]["dify-service-api"]["thinking-convert"] == "plain": + + if ( + self.pipeline_config['ai']['dify-service-api']['thinking-convert'] + == 'remove' + ): + return re.sub( + r'
Thinking... .*?
', + '', + resp_text, + flags=re.DOTALL, + ) + + if ( + self.pipeline_config['ai']['dify-service-api']['thinking-convert'] + == 'plain' + ): pattern = r'
Thinking... (.*?)
' thinking_text = re.search(pattern, resp_text, flags=re.DOTALL) content_text = re.sub(pattern, '', resp_text, flags=re.DOTALL) - return f"{thinking_text.group(1)}\n{content_text}" + return f'{thinking_text.group(1)}\n{content_text}' async def _preprocess_user_message( self, query: core_entities.Query @@ -68,22 +82,24 @@ class DifyServiceAPIRunner(runner.RequestRunner): Returns: tuple[str, list[str]]: 纯文本和图片的 Dify 服务图片 ID """ - plain_text = "" + plain_text = '' image_ids = [] if isinstance(query.user_message.content, list): for ce in query.user_message.content: - if ce.type == "text": + if ce.type == 'text': plain_text += ce.text - elif ce.type == "image_base64": - image_b64, image_format = await image.extract_b64_and_format(ce.image_base64) + elif ce.type == 'image_base64': + image_b64, image_format = await image.extract_b64_and_format( + ce.image_base64 + ) file_bytes = base64.b64decode(image_b64) - file = ("img.png", file_bytes, f"image/{image_format}") + file = ('img.png', file_bytes, f'image/{image_format}') file_upload_resp = await self.dify_client.upload_file( file, - f"{query.session.launcher_type.value}_{query.session.launcher_id}", + f'{query.session.launcher_type.value}_{query.session.launcher_id}', ) - image_id = file_upload_resp["id"] + image_id = file_upload_resp['id'] image_ids.append(image_id) elif isinstance(query.user_message.content, str): plain_text = query.user_message.content @@ -94,116 +110,119 @@ class DifyServiceAPIRunner(runner.RequestRunner): self, query: core_entities.Query ) -> typing.AsyncGenerator[llm_entities.Message, None]: """调用聊天助手""" - cov_id = query.session.using_conversation.uuid or "" + cov_id = query.session.using_conversation.uuid or '' plain_text, image_ids = await self._preprocess_user_message(query) files = [ { - "type": "image", - "transfer_method": "local_file", - "upload_file_id": image_id, + 'type': 'image', + 'transfer_method': 'local_file', + 'upload_file_id': image_id, } for image_id in image_ids ] - mode = "basic" # 标记是基础编排还是工作流编排 + mode = 'basic' # 标记是基础编排还是工作流编排 basic_mode_pending_chunk = '' inputs = {} - + inputs.update(query.variables) async for chunk in self.dify_client.chat_messages( inputs=inputs, query=plain_text, - user=f"{query.session.launcher_type.value}_{query.session.launcher_id}", + user=f'{query.session.launcher_type.value}_{query.session.launcher_id}', conversation_id=cov_id, files=files, - timeout=self.pipeline_config["ai"]["dify-service-api"]["timeout"], + timeout=self.pipeline_config['ai']['dify-service-api']['timeout'], ): - self.ap.logger.debug("dify-chat-chunk: " + str(chunk)) + self.ap.logger.debug('dify-chat-chunk: ' + str(chunk)) if chunk['event'] == 'workflow_started': - mode = "workflow" + mode = 'workflow' - if mode == "workflow": + if mode == 'workflow': if chunk['event'] == 'node_finished': if chunk['data']['node_type'] == 'answer': yield llm_entities.Message( - role="assistant", - content=self._try_convert_thinking(chunk['data']['outputs']['answer']), + role='assistant', + content=self._try_convert_thinking( + chunk['data']['outputs']['answer'] + ), ) - elif mode == "basic": + elif mode == 'basic': if chunk['event'] == 'message': basic_mode_pending_chunk += chunk['answer'] elif chunk['event'] == 'message_end': yield llm_entities.Message( - role="assistant", + role='assistant', content=self._try_convert_thinking(basic_mode_pending_chunk), ) basic_mode_pending_chunk = '' - query.session.using_conversation.uuid = chunk["conversation_id"] + query.session.using_conversation.uuid = chunk['conversation_id'] async def _agent_chat_messages( self, query: core_entities.Query ) -> typing.AsyncGenerator[llm_entities.Message, None]: """调用聊天助手""" - cov_id = query.session.using_conversation.uuid or "" + cov_id = query.session.using_conversation.uuid or '' plain_text, image_ids = await self._preprocess_user_message(query) files = [ { - "type": "image", - "transfer_method": "local_file", - "upload_file_id": image_id, + 'type': 'image', + 'transfer_method': 'local_file', + 'upload_file_id': image_id, } for image_id in image_ids ] - ignored_events = ["agent_message"] + ignored_events = ['agent_message'] inputs = {} - + inputs.update(query.variables) async for chunk in self.dify_client.chat_messages( inputs=inputs, query=plain_text, - user=f"{query.session.launcher_type.value}_{query.session.launcher_id}", - response_mode="streaming", + user=f'{query.session.launcher_type.value}_{query.session.launcher_id}', + response_mode='streaming', conversation_id=cov_id, files=files, - timeout=self.pipeline_config["ai"]["dify-service-api"]["timeout"], + timeout=self.pipeline_config['ai']['dify-service-api']['timeout'], ): - self.ap.logger.debug("dify-agent-chunk: " + str(chunk)) + self.ap.logger.debug('dify-agent-chunk: ' + str(chunk)) - if chunk["event"] in ignored_events: + if chunk['event'] in ignored_events: continue - if chunk["event"] == "agent_thought": - - if chunk['tool'] != '' and chunk['observation'] != '': # 工具调用结果,跳过 + if chunk['event'] == 'agent_thought': + if ( + chunk['tool'] != '' and chunk['observation'] != '' + ): # 工具调用结果,跳过 continue if chunk['thought'].strip() != '': # 文字回复内容 msg = llm_entities.Message( - role="assistant", - content=chunk["thought"], + role='assistant', + content=chunk['thought'], ) yield msg if chunk['tool']: msg = llm_entities.Message( - role="assistant", + role='assistant', tool_calls=[ llm_entities.ToolCall( id=chunk['id'], - type="function", + type='function', function=llm_entities.FunctionCall( - name=chunk["tool"], + name=chunk['tool'], arguments=json.dumps({}), ), ) @@ -211,9 +230,7 @@ class DifyServiceAPIRunner(runner.RequestRunner): ) yield msg if chunk['event'] == 'message_file': - if chunk['type'] == 'image' and chunk['belongs_to'] == 'assistant': - base_url = self.dify_client.base_url if base_url.endswith('/v1'): @@ -222,11 +239,11 @@ class DifyServiceAPIRunner(runner.RequestRunner): image_url = base_url + chunk['url'] yield llm_entities.Message( - role="assistant", + role='assistant', content=[llm_entities.ContentElement.from_image_url(image_url)], ) - query.session.using_conversation.uuid = chunk["conversation_id"] + query.session.using_conversation.uuid = chunk['conversation_id'] async def _workflow_messages( self, query: core_entities.Query @@ -235,58 +252,57 @@ class DifyServiceAPIRunner(runner.RequestRunner): if not query.session.using_conversation.uuid: query.session.using_conversation.uuid = str(uuid.uuid4()) - - query.variables["conversation_id"] = query.session.using_conversation.uuid + + query.variables['conversation_id'] = query.session.using_conversation.uuid plain_text, image_ids = await self._preprocess_user_message(query) files = [ { - "type": "image", - "transfer_method": "local_file", - "upload_file_id": image_id, + 'type': 'image', + 'transfer_method': 'local_file', + 'upload_file_id': image_id, } for image_id in image_ids ] - ignored_events = ["text_chunk", "workflow_started"] + ignored_events = ['text_chunk', 'workflow_started'] inputs = { # these variables are legacy variables, we need to keep them for compatibility - "langbot_user_message_text": plain_text, - "langbot_session_id": query.variables["session_id"], - "langbot_conversation_id": query.variables["conversation_id"], - "langbot_msg_create_time": query.variables["msg_create_time"], + 'langbot_user_message_text': plain_text, + 'langbot_session_id': query.variables['session_id'], + 'langbot_conversation_id': query.variables['conversation_id'], + 'langbot_msg_create_time': query.variables['msg_create_time'], } - + inputs.update(query.variables) async for chunk in self.dify_client.workflow_run( inputs=inputs, - user=f"{query.session.launcher_type.value}_{query.session.launcher_id}", + user=f'{query.session.launcher_type.value}_{query.session.launcher_id}', files=files, - timeout=self.pipeline_config["ai"]["dify-service-api"]["timeout"], + timeout=self.pipeline_config['ai']['dify-service-api']['timeout'], ): - self.ap.logger.debug("dify-workflow-chunk: " + str(chunk)) - if chunk["event"] in ignored_events: + self.ap.logger.debug('dify-workflow-chunk: ' + str(chunk)) + if chunk['event'] in ignored_events: continue - if chunk["event"] == "node_started": - + if chunk['event'] == 'node_started': if ( - chunk["data"]["node_type"] == "start" - or chunk["data"]["node_type"] == "end" + chunk['data']['node_type'] == 'start' + or chunk['data']['node_type'] == 'end' ): continue msg = llm_entities.Message( - role="assistant", + role='assistant', content=None, tool_calls=[ llm_entities.ToolCall( - id=chunk["data"]["node_id"], - type="function", + id=chunk['data']['node_id'], + type='function', function=llm_entities.FunctionCall( - name=chunk["data"]["title"], + name=chunk['data']['title'], arguments=json.dumps({}), ), ) @@ -295,13 +311,13 @@ class DifyServiceAPIRunner(runner.RequestRunner): yield msg - elif chunk["event"] == "workflow_finished": + elif chunk['event'] == 'workflow_finished': if chunk['data']['error']: raise errors.DifyAPIError(chunk['data']['error']) msg = llm_entities.Message( - role="assistant", - content=chunk["data"]["outputs"]["summary"], + role='assistant', + content=chunk['data']['outputs']['summary'], ) yield msg @@ -310,16 +326,16 @@ class DifyServiceAPIRunner(runner.RequestRunner): self, query: core_entities.Query ) -> typing.AsyncGenerator[llm_entities.Message, None]: """运行请求""" - if self.pipeline_config["ai"]["dify-service-api"]["app-type"] == "chat": + if self.pipeline_config['ai']['dify-service-api']['app-type'] == 'chat': async for msg in self._chat_messages(query): yield msg - elif self.pipeline_config["ai"]["dify-service-api"]["app-type"] == "agent": + elif self.pipeline_config['ai']['dify-service-api']['app-type'] == 'agent': async for msg in self._agent_chat_messages(query): yield msg - elif self.pipeline_config["ai"]["dify-service-api"]["app-type"] == "workflow": + elif self.pipeline_config['ai']['dify-service-api']['app-type'] == 'workflow': async for msg in self._workflow_messages(query): yield msg else: raise errors.DifyAPIError( - f"不支持的 Dify 应用类型: {self.pipeline_config['ai']['dify-service-api']['app-type']}" + f'不支持的 Dify 应用类型: {self.pipeline_config["ai"]["dify-service-api"]["app-type"]}' ) diff --git a/pkg/provider/runners/localagent.py b/pkg/provider/runners/localagent.py index 68bb2b4f..d6b6f6cd 100644 --- a/pkg/provider/runners/localagent.py +++ b/pkg/provider/runners/localagent.py @@ -4,24 +4,28 @@ import json import typing from .. import runner -from ...core import app, entities as core_entities +from ...core import entities as core_entities from .. import entities as llm_entities -@runner.runner_class("local-agent") +@runner.runner_class('local-agent') class LocalAgentRunner(runner.RequestRunner): - """本地Agent请求运行器 - """ + """本地Agent请求运行器""" - async def run(self, query: core_entities.Query) -> typing.AsyncGenerator[llm_entities.Message, None]: - """运行请求 - """ + async def run( + self, query: core_entities.Query + ) -> typing.AsyncGenerator[llm_entities.Message, None]: + """运行请求""" pending_tool_calls = [] - req_messages = query.prompt.messages.copy() + query.messages.copy() + [query.user_message] + req_messages = ( + query.prompt.messages.copy() + query.messages.copy() + [query.user_message] + ) # 首次请求 - msg = await query.use_llm_model.requester.invoke_llm(query, query.use_llm_model, req_messages, query.use_funcs) + msg = await query.use_llm_model.requester.invoke_llm( + query, query.use_llm_model, req_messages, query.use_funcs + ) yield msg @@ -34,7 +38,7 @@ class LocalAgentRunner(runner.RequestRunner): for tool_call in pending_tool_calls: try: func = tool_call.function - + parameters = json.loads(func.arguments) func_ret = await self.ap.tool_mgr.execute_func_call( @@ -42,7 +46,9 @@ class LocalAgentRunner(runner.RequestRunner): ) msg = llm_entities.Message( - role="tool", content=json.dumps(func_ret, ensure_ascii=False), tool_call_id=tool_call.id + role='tool', + content=json.dumps(func_ret, ensure_ascii=False), + tool_call_id=tool_call.id, ) yield msg @@ -51,7 +57,7 @@ class LocalAgentRunner(runner.RequestRunner): except Exception as e: # 工具调用出错,添加一个报错信息到 req_messages err_msg = llm_entities.Message( - role="tool", content=f"err: {e}", tool_call_id=tool_call.id + role='tool', content=f'err: {e}', tool_call_id=tool_call.id ) yield err_msg @@ -59,7 +65,9 @@ class LocalAgentRunner(runner.RequestRunner): req_messages.append(err_msg) # 处理完所有调用,再次请求 - msg = await query.use_llm_model.requester.invoke_llm(query, query.use_llm_model, req_messages, query.use_funcs) + msg = await query.use_llm_model.requester.invoke_llm( + query, query.use_llm_model, req_messages, query.use_funcs + ) yield msg diff --git a/pkg/provider/session/sessionmgr.py b/pkg/provider/session/sessionmgr.py index eef723da..a0a582ad 100644 --- a/pkg/provider/session/sessionmgr.py +++ b/pkg/provider/session/sessionmgr.py @@ -3,13 +3,11 @@ from __future__ import annotations import asyncio from ...core import app, entities as core_entities -from ...plugin import context as plugin_context from ...provider import entities as provider_entities class SessionManager: - """会话管理器 - """ + """会话管理器""" ap: app.Application @@ -23,10 +21,12 @@ class SessionManager: pass async def get_session(self, query: core_entities.Query) -> core_entities.Session: - """获取会话 - """ + """获取会话""" for session in self.session_list: - if query.launcher_type == session.launcher_type and query.launcher_id == session.launcher_id: + if ( + query.launcher_type == session.launcher_type + and query.launcher_id == session.launcher_id + ): return session session_concurrency = self.ap.instance_config.data['concurrency']['session'] @@ -39,7 +39,12 @@ class SessionManager: self.session_list.append(session) return session - async def get_conversation(self, query: core_entities.Query, session: core_entities.Session, prompt_config: list[dict]) -> core_entities.Conversation: + async def get_conversation( + self, + query: core_entities.Query, + session: core_entities.Session, + prompt_config: list[dict], + ) -> core_entities.Conversation: """获取对话或创建对话""" if not session.conversations: @@ -52,7 +57,7 @@ class SessionManager: prompt_messages.append(provider_entities.Message(**prompt_message)) prompt = provider_entities.Prompt( - name="default", + name='default', messages=prompt_messages, ) diff --git a/pkg/provider/tools/entities.py b/pkg/provider/tools/entities.py index 746ffe92..102e03d3 100644 --- a/pkg/provider/tools/entities.py +++ b/pkg/provider/tools/entities.py @@ -1,13 +1,9 @@ from __future__ import annotations -import abc import typing -import asyncio import pydantic.v1 as pydantic -from ...core import entities as core_entities - class LLMFunction(pydantic.BaseModel): """函数""" diff --git a/pkg/provider/tools/loader.py b/pkg/provider/tools/loader.py index cae4a63f..25bb13eb 100644 --- a/pkg/provider/tools/loader.py +++ b/pkg/provider/tools/loader.py @@ -9,9 +9,10 @@ from . import entities as tools_entities preregistered_loaders: list[typing.Type[ToolLoader]] = [] + def loader_class(name: str): - """注册一个工具加载器 - """ + """注册一个工具加载器""" + def decorator(cls: typing.Type[ToolLoader]) -> typing.Type[ToolLoader]: cls.name = name preregistered_loaders.append(cls) @@ -22,7 +23,7 @@ def loader_class(name: str): class ToolLoader(abc.ABC): """工具加载器""" - + name: str = None ap: app.Application @@ -34,7 +35,7 @@ class ToolLoader(abc.ABC): pass @abc.abstractmethod - async def get_tools(self, enabled: bool=True) -> list[tools_entities.LLMFunction]: + async def get_tools(self, enabled: bool = True) -> list[tools_entities.LLMFunction]: """获取所有工具""" pass @@ -44,11 +45,13 @@ class ToolLoader(abc.ABC): pass @abc.abstractmethod - async def invoke_tool(self, query: core_entities.Query, name: str, parameters: dict) -> typing.Any: + async def invoke_tool( + self, query: core_entities.Query, name: str, parameters: dict + ) -> typing.Any: """执行工具调用""" pass @abc.abstractmethod async def shutdown(self): """关闭工具""" - pass \ No newline at end of file + pass diff --git a/pkg/provider/tools/loaders/mcp.py b/pkg/provider/tools/loaders/mcp.py index 6bda7f89..5377709f 100644 --- a/pkg/provider/tools/loaders/mcp.py +++ b/pkg/provider/tools/loaders/mcp.py @@ -30,7 +30,7 @@ class RuntimeMCPSession: self.server_name = server_name self.server_config = server_config self.ap = ap - + self.session = None self.exit_stack = AsyncExitStack() @@ -38,9 +38,9 @@ class RuntimeMCPSession: async def _init_stdio_python_server(self): server_params = StdioServerParameters( - command=self.server_config["command"], - args=self.server_config["args"], - env=self.server_config["env"], + command=self.server_config['command'], + args=self.server_config['args'], + env=self.server_config['env'], ) stdio_transport = await self.exit_stack.enter_async_context( @@ -58,12 +58,12 @@ class RuntimeMCPSession: async def _init_sse_server(self): sse_transport = await self.exit_stack.enter_async_context( sse_client( - self.server_config["url"], - headers=self.server_config.get("headers", {}), - timeout=self.server_config.get("timeout", 10), + self.server_config['url'], + headers=self.server_config.get('headers', {}), + timeout=self.server_config.get('timeout', 10), ) ) - + sseio, write = sse_transport self.session = await self.exit_stack.enter_async_context( @@ -73,18 +73,22 @@ class RuntimeMCPSession: await self.session.initialize() async def initialize(self): - self.ap.logger.debug(f"初始化 MCP 会话: {self.server_name} {self.server_config}") + self.ap.logger.debug( + f'初始化 MCP 会话: {self.server_name} {self.server_config}' + ) - if self.server_config["mode"] == "stdio": + if self.server_config['mode'] == 'stdio': await self._init_stdio_python_server() - elif self.server_config["mode"] == "sse": + elif self.server_config['mode'] == 'sse': await self._init_sse_server() else: - raise ValueError(f"无法识别 MCP 服务器类型: {self.server_name}: {self.server_config}") - + raise ValueError( + f'无法识别 MCP 服务器类型: {self.server_name}: {self.server_config}' + ) + tools = await self.session.list_tools() - self.ap.logger.debug(f"获取 MCP 工具: {tools}") + self.ap.logger.debug(f'获取 MCP 工具: {tools}') for tool in tools.tools: @@ -93,25 +97,28 @@ class RuntimeMCPSession: if result.isError: raise Exception(result.content[0].text) return result.content[0].text - + func.__name__ = tool.name - self.functions.append(tools_entities.LLMFunction( - name=tool.name, - human_desc=tool.description, - description=tool.description, - parameters=tool.inputSchema, - func=func, - )) + self.functions.append( + tools_entities.LLMFunction( + name=tool.name, + human_desc=tool.description, + description=tool.description, + parameters=tool.inputSchema, + func=func, + ) + ) async def shutdown(self): """关闭工具""" await self.session._exit_stack.aclose() -@loader.loader_class("mcp") + +@loader.loader_class('mcp') class MCPLoader(loader.ToolLoader): """MCP 工具加载器。 - + 在此加载器中管理所有与 MCP Server 的连接。 """ @@ -125,16 +132,17 @@ class MCPLoader(loader.ToolLoader): self._last_listed_functions = [] async def initialize(self): - - for server_config in self.ap.instance_config.data.get("mcp", {}).get("servers", []): - if not server_config["enable"]: + for server_config in self.ap.instance_config.data.get('mcp', {}).get( + 'servers', [] + ): + if not server_config['enable']: continue - session = RuntimeMCPSession(server_config["name"], server_config, self.ap) + session = RuntimeMCPSession(server_config['name'], server_config, self.ap) await session.initialize() # self.ap.event_loop.create_task(session.initialize()) - self.sessions[server_config["name"]] = session + self.sessions[server_config['name']] = session - async def get_tools(self, enabled: bool=True) -> list[tools_entities.LLMFunction]: + async def get_tools(self, enabled: bool = True) -> list[tools_entities.LLMFunction]: all_functions = [] for session in self.sessions.values(): @@ -147,13 +155,15 @@ class MCPLoader(loader.ToolLoader): async def has_tool(self, name: str) -> bool: return name in [f.name for f in self._last_listed_functions] - async def invoke_tool(self, query: core_entities.Query, name: str, parameters: dict) -> typing.Any: + async def invoke_tool( + self, query: core_entities.Query, name: str, parameters: dict + ) -> typing.Any: for server_name, session in self.sessions.items(): for function in session.functions: if function.name == name: return await function.func(query, **parameters) - raise ValueError(f"未找到工具: {name}") + raise ValueError(f'未找到工具: {name}') async def shutdown(self): """关闭工具""" diff --git a/pkg/provider/tools/loaders/plugin.py b/pkg/provider/tools/loaders/plugin.py index 5b964556..c53403af 100644 --- a/pkg/provider/tools/loaders/plugin.py +++ b/pkg/provider/tools/loaders/plugin.py @@ -4,19 +4,18 @@ import typing import traceback from .. import loader, entities as tools_entities -from ....core import app, entities as core_entities +from ....core import entities as core_entities from ....plugin import context as plugin_context -@loader.loader_class("plugin-tool-loader") +@loader.loader_class('plugin-tool-loader') class PluginToolLoader(loader.ToolLoader): """插件工具加载器。 - + 本加载器中不存储工具信息,仅负责从插件系统中获取工具信息。 """ - async def get_tools(self, enabled: bool=True) -> list[tools_entities.LLMFunction]: - + async def get_tools(self, enabled: bool = True) -> list[tools_entities.LLMFunction]: # 从插件系统获取工具(内容函数) all_functions: list[tools_entities.LLMFunction] = [] @@ -49,23 +48,23 @@ class PluginToolLoader(loader.ToolLoader): return function, plugin.plugin_inst return None, None - async def invoke_tool(self, query: core_entities.Query, name: str, parameters: dict) -> typing.Any: - + async def invoke_tool( + self, query: core_entities.Query, name: str, parameters: dict + ) -> typing.Any: try: - function, plugin = await self._get_function_and_plugin(name) if function is None: return None parameters = parameters.copy() - parameters = {"query": query, **parameters} + parameters = {'query': query, **parameters} return await function.func(plugin, **parameters) except Exception as e: - self.ap.logger.error(f"执行函数 {name} 时发生错误: {e}") + self.ap.logger.error(f'执行函数 {name} 时发生错误: {e}') traceback.print_exc() - return f"error occurred when executing function {name}: {e}" + return f'error occurred when executing function {name}: {e}' finally: plugin = None @@ -75,13 +74,12 @@ class PluginToolLoader(loader.ToolLoader): break if plugin is not None: - await self.ap.ctr_mgr.usage.post_function_record( plugin={ - "name": plugin.plugin_name, - "remote": plugin.plugin_repository, - "version": plugin.plugin_version, - "author": plugin.plugin_author, + 'name': plugin.plugin_name, + 'remote': plugin.plugin_repository, + 'version': plugin.plugin_version, + 'author': plugin.plugin_author, }, function_name=function.name, function_description=function.description, diff --git a/pkg/provider/tools/toolmgr.py b/pkg/provider/tools/toolmgr.py index 64befd8c..0f6fdac0 100644 --- a/pkg/provider/tools/toolmgr.py +++ b/pkg/provider/tools/toolmgr.py @@ -1,12 +1,13 @@ from __future__ import annotations import typing -import traceback from ...core import app, entities as core_entities from . import entities, loader as tools_loader -from ...plugin import context as plugin_context -from .loaders import plugin, mcp +from ...utils import importutil +from . import loaders + +importutil.import_modules_in_pkg(loaders) class ToolManager: @@ -22,13 +23,14 @@ class ToolManager: self.loaders = [] async def initialize(self): - for loader_cls in tools_loader.preregistered_loaders: loader_inst = loader_cls(self.ap) await loader_inst.initialize() self.loaders.append(loader_inst) - async def get_all_functions(self, plugin_enabled: bool=None) -> list[entities.LLMFunction]: + async def get_all_functions( + self, plugin_enabled: bool = None + ) -> list[entities.LLMFunction]: """获取所有函数""" all_functions: list[entities.LLMFunction] = [] @@ -37,17 +39,19 @@ class ToolManager: return all_functions - async def generate_tools_for_openai(self, use_funcs: list[entities.LLMFunction]) -> list: + async def generate_tools_for_openai( + self, use_funcs: list[entities.LLMFunction] + ) -> list: """生成函数列表""" tools = [] for function in use_funcs: function_schema = { - "type": "function", - "function": { - "name": function.name, - "description": function.description, - "parameters": function.parameters, + 'type': 'function', + 'function': { + 'name': function.name, + 'description': function.description, + 'parameters': function.parameters, }, } tools.append(function_schema) @@ -83,9 +87,9 @@ class ToolManager: for function in use_funcs: function_schema = { - "name": function.name, - "description": function.description, - "input_schema": function.parameters, + 'name': function.name, + 'description': function.description, + 'input_schema': function.parameters, } tools.append(function_schema) @@ -100,7 +104,7 @@ class ToolManager: if await loader.has_tool(name): return await loader.invoke_tool(query, name, parameters) else: - raise ValueError(f"未找到工具: {name}") + raise ValueError(f'未找到工具: {name}') async def shutdown(self): """关闭所有工具""" diff --git a/pkg/utils/announce.py b/pkg/utils/announce.py index 1fb6e166..47de9325 100644 --- a/pkg/utils/announce.py +++ b/pkg/utils/announce.py @@ -14,7 +14,7 @@ from ..core import app class Announcement(pydantic.BaseModel): """公告""" - + id: int time: str @@ -27,11 +27,11 @@ class Announcement(pydantic.BaseModel): def to_dict(self) -> dict: return { - "id": self.id, - "time": self.time, - "timestamp": self.timestamp, - "content": self.content, - "enabled": self.enabled + 'id': self.id, + 'time': self.time, + 'timestamp': self.timestamp, + 'content': self.content, + 'enabled': self.enabled, } @@ -43,30 +43,28 @@ class AnnouncementManager: def __init__(self, ap: app.Application): self.ap = ap - async def fetch_all( - self - ) -> list[Announcement]: + async def fetch_all(self) -> list[Announcement]: """获取所有公告""" resp = requests.get( - url="https://api.github.com/repos/RockChinQ/LangBot/contents/res/announcement.json", + url='https://api.github.com/repos/RockChinQ/LangBot/contents/res/announcement.json', proxies=self.ap.proxy_mgr.get_forward_proxies(), - timeout=5 + timeout=5, ) obj_json = resp.json() - b64_content = obj_json["content"] + b64_content = obj_json['content'] # 解码 - content = base64.b64decode(b64_content).decode("utf-8") + content = base64.b64decode(b64_content).decode('utf-8') return [Announcement(**item) for item in json.loads(content)] - async def fetch_saved( - self - ) -> list[Announcement]: - if not os.path.exists("data/labels/announcement_saved.json"): - with open("data/labels/announcement_saved.json", "w", encoding="utf-8") as f: - f.write("[]") + async def fetch_saved(self) -> list[Announcement]: + if not os.path.exists('data/labels/announcement_saved.json'): + with open( + 'data/labels/announcement_saved.json', 'w', encoding='utf-8' + ) as f: + f.write('[]') - with open("data/labels/announcement_saved.json", "r", encoding="utf-8") as f: + with open('data/labels/announcement_saved.json', 'r', encoding='utf-8') as f: content = f.read() if not content: @@ -74,19 +72,15 @@ class AnnouncementManager: return [Announcement(**item) for item in json.loads(content)] - async def write_saved( - self, - content: list[Announcement] - ): + async def write_saved(self, content: list[Announcement]): + with open('data/labels/announcement_saved.json', 'w', encoding='utf-8') as f: + f.write( + json.dumps( + [item.to_dict() for item in content], indent=4, ensure_ascii=False + ) + ) - with open("data/labels/announcement_saved.json", "w", encoding="utf-8") as f: - f.write(json.dumps([ - item.to_dict() for item in content - ], indent=4, ensure_ascii=False)) - - async def fetch_new( - self - ) -> list[Announcement]: + async def fetch_new(self) -> list[Announcement]: """获取新公告""" all = await self.fetch_all() saved = await self.fetch_saved() @@ -106,18 +100,15 @@ class AnnouncementManager: await self.write_saved(all) return to_show - async def show_announcements( - self - ) -> typing.Tuple[str, int]: + async def show_announcements(self) -> typing.Tuple[str, int]: """显示公告""" try: announcements = await self.fetch_new() - ann_text = "" + ann_text = '' for ann in announcements: - ann_text += f"[公告] {ann.time}: {ann.content}\n" + ann_text += f'[公告] {ann.time}: {ann.content}\n' if announcements: - await self.ap.ctr_mgr.main.post_announcement_showed( ids=[item.id for item in announcements] ) diff --git a/pkg/utils/constants.py b/pkg/utils/constants.py index f37f2151..7f3d0804 100644 --- a/pkg/utils/constants.py +++ b/pkg/utils/constants.py @@ -1,8 +1,8 @@ -semantic_version = "v4.0.0" +semantic_version = 'v4.0.0' required_database_version = 1 """标记本版本所需要的数据库结构版本,用于判断数据库迁移""" debug_mode = False -edition = 'community' \ No newline at end of file +edition = 'community' diff --git a/pkg/utils/funcschema.py b/pkg/utils/funcschema.py index c39b4886..52dd6efc 100644 --- a/pkg/utils/funcschema.py +++ b/pkg/utils/funcschema.py @@ -1,4 +1,3 @@ -import sys import re import inspect @@ -33,18 +32,18 @@ def get_func_schema(function: callable) -> dict: func_doc = function.__doc__ # Google Style Docstring if func_doc is None: - raise Exception("Function {} has no docstring.".format(function.__name__)) - func_doc = func_doc.strip().replace(' ','').replace('\t', '') + raise Exception('Function {} has no docstring.'.format(function.__name__)) + func_doc = func_doc.strip().replace(' ', '').replace('\t', '') # extract doc of args from docstring doc_spt = func_doc.split('\n\n') desc = doc_spt[0] - args = doc_spt[1] if len(doc_spt) > 1 else "" - returns = doc_spt[2] if len(doc_spt) > 2 else "" + args = doc_spt[1] if len(doc_spt) > 1 else '' + # returns = doc_spt[2] if len(doc_spt) > 2 else "" # extract args # delete the first line of args arg_lines = args.split('\n')[1:] - arg_doc_list = re.findall(r'(\w+)(\((\w+)\))?:\s*(.*)', args) + # arg_doc_list = re.findall(r'(\w+)(\((\w+)\))?:\s*(.*)', args) args_doc = {} for arg_line in arg_lines: doc_tuple = re.findall(r'(\w+)(\(([\w\[\]]+)\))?:\s*(.*)', arg_line) @@ -53,18 +52,16 @@ def get_func_schema(function: callable) -> dict: args_doc[doc_tuple[0][0]] = doc_tuple[0][3] # extract returns - return_doc_list = re.findall(r'(\w+):\s*(.*)', returns) + # return_doc_list = re.findall(r'(\w+):\s*(.*)', returns) params = enumerate(inspect.signature(function).parameters.values()) parameters = { - "type": "object", - "required": [], - "properties": {}, + 'type': 'object', + 'required': [], + 'properties': {}, } - for i, param in params: - # 排除 self, query if param.name in ['self', 'query']: continue @@ -72,24 +69,24 @@ def get_func_schema(function: callable) -> dict: param_type = param.annotation.__name__ type_name_mapping = { - "str": "string", - "int": "integer", - "float": "number", - "bool": "boolean", - "list": "array", - "dict": "object", + 'str': 'string', + 'int': 'integer', + 'float': 'number', + 'bool': 'boolean', + 'list': 'array', + 'dict': 'object', } if param_type in type_name_mapping: param_type = type_name_mapping[param_type] parameters['properties'][param.name] = { - "type": param_type, - "description": args_doc[param.name], + 'type': param_type, + 'description': args_doc[param.name], } # add schema for array - if param_type == "array": + if param_type == 'array': # extract type of array, the int of list[int] # use re array_type_tuple = re.findall(r'list\[(\w+)\]', str(param.annotation)) @@ -102,15 +99,15 @@ def get_func_schema(function: callable) -> dict: if array_type in type_name_mapping: array_type = type_name_mapping[array_type] - parameters['properties'][param.name]["items"] = { - "type": array_type, + parameters['properties'][param.name]['items'] = { + 'type': array_type, } if param.default is inspect.Parameter.empty: - parameters["required"].append(param.name) + parameters['required'].append(param.name) return { - "function": function, - "description": desc, - "parameters": parameters, - } \ No newline at end of file + 'function': function, + 'description': desc, + 'parameters': parameters, + } diff --git a/pkg/utils/image.py b/pkg/utils/image.py index 760c2128..9af766fb 100644 --- a/pkg/utils/image.py +++ b/pkg/utils/image.py @@ -8,23 +8,16 @@ import aiohttp import PIL.Image import httpx -import os -import aiofiles -import pathlib import asyncio -from urllib.parse import urlparse - - - async def get_gewechat_image_base64( - gewechat_url: str, - gewechat_file_url: str, - app_id: str, - xml_content: str, - token: str, - image_type: int = 2, + gewechat_url: str, + gewechat_file_url: str, + app_id: str, + xml_content: str, + token: str, + image_type: int = 2, ) -> typing.Tuple[str, str]: """从gewechat服务器获取图片并转换为base64格式 @@ -43,17 +36,14 @@ async def get_gewechat_image_base64( aiohttp.ClientTimeout: 请求超时(15秒)或连接超时(2秒) Exception: 其他错误 """ - headers = { - 'X-GEWE-TOKEN': token, - 'Content-Type': 'application/json' - } + headers = {'X-GEWE-TOKEN': token, 'Content-Type': 'application/json'} # 设置超时 timeout = aiohttp.ClientTimeout( total=15.0, # 总超时时间15秒 connect=2.0, # 连接超时2秒 sock_connect=2.0, # socket连接超时2秒 - sock_read=15.0 # socket读取超时15秒 + sock_read=15.0, # socket读取超时15秒 ) try: @@ -61,37 +51,37 @@ async def get_gewechat_image_base64( # 获取图片下载链接 try: async with session.post( - f"{gewechat_url}/v2/api/message/downloadImage", - headers=headers, - json={ - "appId": app_id, - "type": image_type, - "xml": xml_content - } + f'{gewechat_url}/v2/api/message/downloadImage', + headers=headers, + json={'appId': app_id, 'type': image_type, 'xml': xml_content}, ) as response: if response.status != 200: # print(response) - raise Exception(f"获取gewechat图片下载失败: {await response.text()}") + raise Exception( + f'获取gewechat图片下载失败: {await response.text()}' + ) resp_data = await response.json() - if resp_data.get("ret") != 200: - raise Exception(f"获取gewechat图片下载链接失败: {resp_data}") + if resp_data.get('ret') != 200: + raise Exception(f'获取gewechat图片下载链接失败: {resp_data}') file_url = resp_data['data']['fileUrl'] except asyncio.TimeoutError: - raise Exception("获取图片下载链接超时") + raise Exception('获取图片下载链接超时') except aiohttp.ClientError as e: - raise Exception(f"获取图片下载链接网络错误: {str(e)}") + raise Exception(f'获取图片下载链接网络错误: {str(e)}') # 解析原始URL并替换端口 base_url = gewechat_file_url - download_url = f"{base_url}/download/{file_url}" + download_url = f'{base_url}/download/{file_url}' # 下载图片 try: async with session.get(download_url) as img_response: if img_response.status != 200: - raise Exception(f"下载图片失败: {await img_response.text()}, URL: {download_url}") + raise Exception( + f'下载图片失败: {await img_response.text()}, URL: {download_url}' + ) image_data = await img_response.read() @@ -105,14 +95,11 @@ async def get_gewechat_image_base64( return base64_str, image_format except asyncio.TimeoutError: - raise Exception(f"下载图片超时, URL: {download_url}") + raise Exception(f'下载图片超时, URL: {download_url}') except aiohttp.ClientError as e: - raise Exception(f"下载图片网络错误: {str(e)}, URL: {download_url}") + raise Exception(f'下载图片网络错误: {str(e)}, URL: {download_url}') except Exception as e: - raise Exception(f"获取图片失败: {str(e)}") from e - - - + raise Exception(f'获取图片失败: {str(e)}') from e async def get_wecom_image_base64(pic_url: str) -> tuple[str, str]: @@ -124,22 +111,26 @@ async def get_wecom_image_base64(pic_url: str) -> tuple[str, str]: async with aiohttp.ClientSession() as session: async with session.get(pic_url) as response: if response.status != 200: - raise Exception(f"Failed to download image: {response.status}") - + raise Exception(f'Failed to download image: {response.status}') + # 读取图片数据 image_data = await response.read() - + # 获取图片格式 content_type = response.headers.get('Content-Type', '') image_format = content_type.split('/')[-1] # 例如 'image/jpeg' -> 'jpeg' - + # 转换为 base64 import base64 + image_base64 = base64.b64encode(image_data).decode('utf-8') - + return image_base64, image_format - -async def get_qq_official_image_base64(pic_url:str,content_type:str) -> tuple[str,str]: + + +async def get_qq_official_image_base64( + pic_url: str, content_type: str +) -> tuple[str, str]: """ 下载QQ官方图片, 并且转换为base64格式 @@ -149,18 +140,18 @@ async def get_qq_official_image_base64(pic_url:str,content_type:str) -> tuple[st response.raise_for_status() # 确保请求成功 image_data = response.content base64_data = base64.b64encode(image_data).decode('utf-8') - - return f"data:{content_type};base64,{base64_data}" + + return f'data:{content_type};base64,{base64_data}' def get_qq_image_downloadable_url(image_url: str) -> tuple[str, dict]: """获取QQ图片的下载链接""" parsed = urlparse(image_url) query = parse_qs(parsed.query) - return f"http://{parsed.netloc}{parsed.path}", query + return f'http://{parsed.netloc}{parsed.path}', query -async def get_qq_image_bytes(image_url: str, query: dict={}) -> tuple[bytes, str]: +async def get_qq_image_bytes(image_url: str, query: dict = {}) -> tuple[bytes, str]: """[弃用]获取QQ图片的bytes""" image_url, query_in_url = get_qq_image_downloadable_url(image_url) query = {**query, **query_in_url} @@ -177,14 +168,12 @@ async def get_qq_image_bytes(image_url: str, query: dict={}) -> tuple[bytes, str elif not content_type.startswith('image/'): pil_img = PIL.Image.open(io.BytesIO(file_bytes)) image_format = pil_img.format.lower() - else: + else: image_format = content_type.split('/')[-1] return file_bytes, image_format -async def qq_image_url_to_base64( - image_url: str -) -> typing.Tuple[str, str]: +async def qq_image_url_to_base64(image_url: str) -> typing.Tuple[str, str]: """[弃用]将QQ图片URL转为base64,并返回图片格式 Args: @@ -204,12 +193,13 @@ async def qq_image_url_to_base64( return base64_str, image_format + async def extract_b64_and_format(image_base64_data: str) -> typing.Tuple[str, str]: """提取base64编码和图片格式 - + data:image/jpeg;base64,xxx 提取出base64编码和图片格式 """ base64_str = image_base64_data.split(',')[-1] image_format = image_base64_data.split(':')[-1].split(';')[0].split('/')[-1] - return base64_str, image_format \ No newline at end of file + return base64_str, image_format diff --git a/pkg/utils/importutil.py b/pkg/utils/importutil.py new file mode 100644 index 00000000..87ca652a --- /dev/null +++ b/pkg/utils/importutil.py @@ -0,0 +1,43 @@ +import importlib +import importlib.util +import os +import typing + + +def import_modules_in_pkg(pkg: typing.Any) -> None: + """ + 导入一个包内的所有模块 + Args: + pkg: 要导入的包对象 + """ + pkg_path = os.path.dirname(pkg.__file__) + import_dir(pkg_path) + + +def import_modules_in_pkgs(pkgs: typing.List) -> None: + for pkg in pkgs: + import_modules_in_pkg(pkg) + + +def import_dot_style_dir(dot_sep_path: str): + sec = dot_sep_path.split('.') + + return import_dir(os.path.join(*sec)) + + +def import_dir(path: str): + for file in os.listdir(path): + if file.endswith('.py') and file != '__init__.py': + full_path = os.path.join(path, file) + rel_path = full_path.replace( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), '' + ) + rel_path = rel_path[1:] + rel_path = rel_path.replace('/', '.')[:-3] + importlib.import_module(rel_path) + + +if __name__ == '__main__': + from pkg.platform import types + + import_modules_in_pkg(types) diff --git a/pkg/utils/ip.py b/pkg/utils/ip.py index 1250f99e..56a12086 100644 --- a/pkg/utils/ip.py +++ b/pkg/utils/ip.py @@ -1,9 +1,12 @@ import aiohttp + async def get_myip() -> str: try: - async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session: - async with session.get("https://ip.useragentinfo.com/myip") as response: + async with aiohttp.ClientSession( + timeout=aiohttp.ClientTimeout(total=10) + ) as session: + async with session.get('https://ip.useragentinfo.com/myip') as response: return await response.text() - except Exception as e: - return '0.0.0.0' \ No newline at end of file + except Exception: + return '0.0.0.0' diff --git a/pkg/utils/logcache.py b/pkg/utils/logcache.py index d3206e9b..84c58f55 100644 --- a/pkg/utils/logcache.py +++ b/pkg/utils/logcache.py @@ -5,8 +5,9 @@ LOG_PAGE_SIZE = 20 MAX_CACHED_PAGES = 10 -class LogPage(): +class LogPage: """日志页""" + number: int """页码""" @@ -51,12 +52,12 @@ class LogCache: start_offset: int, ) -> tuple[str, int, int]: """获取指定页码和偏移量的日志""" - final_logs_str = "" + final_logs_str = '' for page in self.log_pages: if page.number == start_page_number: - final_logs_str += "\n".join(page.logs[start_offset:]) + final_logs_str += '\n'.join(page.logs[start_offset:]) elif page.number > start_page_number: - final_logs_str += "\n".join(page.logs) + final_logs_str += '\n'.join(page.logs) return final_logs_str, page.number, len(page.logs) diff --git a/pkg/utils/pkgmgr.py b/pkg/utils/pkgmgr.py index 4f3186b6..9ce8bdb8 100644 --- a/pkg/utils/pkgmgr.py +++ b/pkg/utils/pkgmgr.py @@ -6,8 +6,17 @@ def install(package): def install_upgrade(package): - pipmain(['install', '--upgrade', package, "-i", "https://pypi.tuna.tsinghua.edu.cn/simple", - "--trusted-host", "pypi.tuna.tsinghua.edu.cn"]) + pipmain( + [ + 'install', + '--upgrade', + package, + '-i', + 'https://pypi.tuna.tsinghua.edu.cn/simple', + '--trusted-host', + 'pypi.tuna.tsinghua.edu.cn', + ] + ) def run_pip(params: list): @@ -15,5 +24,15 @@ def run_pip(params: list): def install_requirements(file, extra_params: list = []): - pipmain(['install', '-r', file, "-i", "https://pypi.tuna.tsinghua.edu.cn/simple", - "--trusted-host", "pypi.tuna.tsinghua.edu.cn"] + extra_params) + pipmain( + [ + 'install', + '-r', + file, + '-i', + 'https://pypi.tuna.tsinghua.edu.cn/simple', + '--trusted-host', + 'pypi.tuna.tsinghua.edu.cn', + ] + + extra_params + ) diff --git a/pkg/utils/proxy.py b/pkg/utils/proxy.py index 838814dd..4f3f7dec 100644 --- a/pkg/utils/proxy.py +++ b/pkg/utils/proxy.py @@ -1,14 +1,12 @@ from __future__ import annotations import os -import sys from ..core import app class ProxyManager: - """代理管理器 - """ + """代理管理器""" ap: app.Application @@ -21,14 +19,24 @@ class ProxyManager: async def initialize(self): self.forward_proxies = { - "http://": os.getenv("HTTP_PROXY") or os.getenv("http_proxy"), - "https://": os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"), + 'http://': os.getenv('HTTP_PROXY') or os.getenv('http_proxy'), + 'https://': os.getenv('HTTPS_PROXY') or os.getenv('https_proxy'), } - if 'http' in self.ap.instance_config.data['proxy'] and self.ap.instance_config.data['proxy']['http']: - self.forward_proxies['http://'] = self.ap.instance_config.data['proxy']['http'] - if 'https' in self.ap.instance_config.data['proxy'] and self.ap.instance_config.data['proxy']['https']: - self.forward_proxies['https://'] = self.ap.instance_config.data['proxy']['https'] + if ( + 'http' in self.ap.instance_config.data['proxy'] + and self.ap.instance_config.data['proxy']['http'] + ): + self.forward_proxies['http://'] = self.ap.instance_config.data['proxy'][ + 'http' + ] + if ( + 'https' in self.ap.instance_config.data['proxy'] + and self.ap.instance_config.data['proxy']['https'] + ): + self.forward_proxies['https://'] = self.ap.instance_config.data['proxy'][ + 'https' + ] # 设置到环境变量 os.environ['HTTP_PROXY'] = self.forward_proxies['http://'] or '' diff --git a/pkg/utils/version.py b/pkg/utils/version.py index 9a206171..ef30c192 100644 --- a/pkg/utils/version.py +++ b/pkg/utils/version.py @@ -12,41 +12,33 @@ from . import constants class VersionManager: - """版本管理器 - """ + """版本管理器""" ap: app.Application - def __init__( - self, - ap: app.Application - ): + def __init__(self, ap: app.Application): self.ap = ap - async def initialize( - self - ): + async def initialize(self): pass - - def get_current_version( - self - ) -> str: + + def get_current_version(self) -> str: current_tag = constants.semantic_version return current_tag - + async def get_release_list(self) -> list: """获取发行列表""" rls_list_resp = requests.get( - url="https://api.github.com/repos/RockChinQ/LangBot/releases", + url='https://api.github.com/repos/RockChinQ/LangBot/releases', proxies=self.ap.proxy_mgr.get_forward_proxies(), - timeout=5 + timeout=5, ) rls_list = rls_list_resp.json() return rls_list - + async def update_all(self): """检查更新并下载源码""" start_time = time.time() @@ -58,10 +50,10 @@ class VersionManager: latest_rls = {} rls_notes = [] - latest_tag_name = "" + latest_tag_name = '' for rls in rls_list: rls_notes.append(rls['name']) # 使用发行名称作为note - if latest_tag_name == "": + if latest_tag_name == '': latest_tag_name = rls['tag_name'] if rls['tag_name'] == current_tag: @@ -69,56 +61,68 @@ class VersionManager: if latest_rls == {}: latest_rls = rls - self.ap.logger.info("更新日志: {}".format(rls_notes)) + self.ap.logger.info('更新日志: {}'.format(rls_notes)) - if latest_rls == {} and not self.is_newer(latest_tag_name, current_tag): # 没有新版本 + if latest_rls == {} and not self.is_newer( + latest_tag_name, current_tag + ): # 没有新版本 return False # 下载最新版本的zip到temp目录 - self.ap.logger.info("开始下载最新版本: {}".format(latest_rls['zipball_url'])) + self.ap.logger.info('开始下载最新版本: {}'.format(latest_rls['zipball_url'])) zip_url = latest_rls['zipball_url'] zip_resp = requests.get( - url=zip_url, - proxies=self.ap.proxy_mgr.get_forward_proxies() + url=zip_url, proxies=self.ap.proxy_mgr.get_forward_proxies() ) zip_data = zip_resp.content # 检查temp/updater目录 - if not os.path.exists("temp"): - os.mkdir("temp") - if not os.path.exists("temp/updater"): - os.mkdir("temp/updater") - with open("temp/updater/{}.zip".format(latest_rls['tag_name']), "wb") as f: + if not os.path.exists('temp'): + os.mkdir('temp') + if not os.path.exists('temp/updater'): + os.mkdir('temp/updater') + with open('temp/updater/{}.zip'.format(latest_rls['tag_name']), 'wb') as f: f.write(zip_data) - self.ap.logger.info("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name']))) + self.ap.logger.info( + '下载最新版本完成: {}'.format( + 'temp/updater/{}.zip'.format(latest_rls['tag_name']) + ) + ) # 解压zip到temp/updater// import zipfile + # 检查目标文件夹 - if os.path.exists("temp/updater/{}".format(latest_rls['tag_name'])): + if os.path.exists('temp/updater/{}'.format(latest_rls['tag_name'])): import shutil - shutil.rmtree("temp/updater/{}".format(latest_rls['tag_name'])) - os.mkdir("temp/updater/{}".format(latest_rls['tag_name'])) - with zipfile.ZipFile("temp/updater/{}.zip".format(latest_rls['tag_name']), 'r') as zip_ref: - zip_ref.extractall("temp/updater/{}".format(latest_rls['tag_name'])) + + shutil.rmtree('temp/updater/{}'.format(latest_rls['tag_name'])) + os.mkdir('temp/updater/{}'.format(latest_rls['tag_name'])) + with zipfile.ZipFile( + 'temp/updater/{}.zip'.format(latest_rls['tag_name']), 'r' + ) as zip_ref: + zip_ref.extractall('temp/updater/{}'.format(latest_rls['tag_name'])) # 覆盖源码 - source_root = "" + source_root = '' # 找到temp/updater//中的第一个子目录路径 - for root, dirs, files in os.walk("temp/updater/{}".format(latest_rls['tag_name'])): - if root != "temp/updater/{}".format(latest_rls['tag_name']): + for root, dirs, files in os.walk( + 'temp/updater/{}'.format(latest_rls['tag_name']) + ): + if root != 'temp/updater/{}'.format(latest_rls['tag_name']): source_root = root break # 覆盖源码 import shutil + for root, dirs, files in os.walk(source_root): # 覆盖所有子文件子目录 for file in files: src = os.path.join(root, file) - dst = src.replace(source_root, ".") + dst = src.replace(source_root, '.') if os.path.exists(dst): os.remove(dst) @@ -128,18 +132,18 @@ class VersionManager: # 检查目标文件是否存在 if not os.path.exists(dst): # 创建目标文件 - open(dst, "w").close() + open(dst, 'w').close() shutil.copy(src, dst) # 把current_tag写入文件 current_tag = latest_rls['tag_name'] - with open("current_tag", "w") as f: + with open('current_tag', 'w') as f: f.write(current_tag) await self.ap.ctr_mgr.main.post_update_record( - spent_seconds=int(time.time()-start_time), - infer_reason="update", + spent_seconds=int(time.time() - start_time), + infer_reason='update', old_version=old_tag, new_version=current_tag, ) @@ -155,23 +159,22 @@ class VersionManager: current_tag = self.get_current_version() # 检查是否有新版本 - latest_tag_name = "" + latest_tag_name = '' for rls in rls_list: - if latest_tag_name == "": + if latest_tag_name == '': latest_tag_name = rls['tag_name'] break return self.is_newer(latest_tag_name, current_tag) - def is_newer(self, new_tag: str, old_tag: str): """判断版本是否更新,忽略第四位版本和第一位版本""" if new_tag == old_tag: return False - new_tag = new_tag.split(".") - old_tag = old_tag.split(".") - + new_tag = new_tag.split('.') + old_tag = old_tag.split('.') + # 判断主版本是否相同 if new_tag[0] != old_tag[0]: return False @@ -180,29 +183,28 @@ class VersionManager: return True # 合成前三段,判断是否相同 - new_tag = ".".join(new_tag[:3]) - old_tag = ".".join(old_tag[:3]) + new_tag = '.'.join(new_tag[:3]) + old_tag = '.'.join(old_tag[:3]) return new_tag != old_tag - def compare_version_str(v0: str, v1: str) -> int: """比较两个版本号""" # 删除版本号前的v - if v0.startswith("v"): + if v0.startswith('v'): v0 = v0[1:] - if v1.startswith("v"): + if v1.startswith('v'): v1 = v1[1:] - v0:list = v0.split(".") - v1:list = v1.split(".") + v0: list = v0.split('.') + v1: list = v1.split('.') # 如果两个版本号节数不同,把短的后面用0补齐 if len(v0) < len(v1): - v0.extend(["0"]*(len(v1)-len(v0))) + v0.extend(['0'] * (len(v1) - len(v0))) elif len(v0) > len(v1): - v1.extend(["0"]*(len(v0)-len(v1))) + v1.extend(['0'] * (len(v0) - len(v1))) # 从高位向低位比较 for i in range(len(v0)): @@ -210,16 +212,16 @@ class VersionManager: return 1 elif int(v0[i]) < int(v1[i]): return -1 - + return 0 - async def show_version_update( - self - ) -> typing.Tuple[str, int]: + async def show_version_update(self) -> typing.Tuple[str, int]: try: - if await self.ap.ver_mgr.is_new_version_available(): - return "有新版本可用,根据文档更新:https://docs.langbot.app/deploy/update.html", logging.INFO - + return ( + '有新版本可用,根据文档更新:https://docs.langbot.app/deploy/update.html', + logging.INFO, + ) + except Exception as e: - return f"检查版本更新时出错: {e}", logging.WARNING + return f'检查版本更新时出错: {e}', logging.WARNING diff --git a/requirements.txt b/requirements.txt index e01eb373..b34c18a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,4 +37,6 @@ mcp sqlmodel # indirect -taskgroup==0.0.0a4 \ No newline at end of file +taskgroup==0.0.0a4 +ruff +pre-commit \ No newline at end of file diff --git a/res/scripts/publish_announcement.py b/res/scripts/publish_announcement.py index 812e83d9..7d2e7d40 100644 --- a/res/scripts/publish_announcement.py +++ b/res/scripts/publish_announcement.py @@ -1,32 +1,32 @@ # 输出工作路径 import os -print("工作路径: " + os.getcwd()) -announcement = input("请输入公告内容: ") - +import time import json +print('工作路径: ' + os.getcwd()) +announcement = input('请输入公告内容: ') + # 读取现有的公告文件 res/announcement.json -with open("res/announcement.json", "r", encoding="utf-8") as f: +with open('res/announcement.json', 'r', encoding='utf-8') as f: announcement_json = json.load(f) # 将公告内容写入公告文件 # 当前自然时间 -import time -now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) +now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) # 获取最后一个公告的id -last_id = announcement_json[-1]["id"] if len(announcement_json) > 0 else -1 +last_id = announcement_json[-1]['id'] if len(announcement_json) > 0 else -1 announcement = { - "id": last_id + 1, - "time": now, - "timestamp": int(time.time()), - "content": announcement + 'id': last_id + 1, + 'time': now, + 'timestamp': int(time.time()), + 'content': announcement, } announcement_json.append(announcement) # 将公告写入公告文件 -with open("res/announcement.json", "w", encoding="utf-8") as f: +with open('res/announcement.json', 'w', encoding='utf-8') as f: json.dump(announcement_json, f, indent=4, ensure_ascii=False) diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..27159c90 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,38 @@ +[lint] + +ignore = [ + "E712", # Comparison to true should be 'if cond is true:' or 'if cond:' (E712) + "F402", # Import `loader` from line 8 shadowed by loop variable + "F403", # * used, unable to detect undefined names + "F405", # may be undefined, or defined from star imports + "E741", # Ambiguous variable name: `l` + "E722", # bare-except + "E721", # type-comparison + "FURB113", # repeated-append + "FURB152", # math-constant + "UP007", # non-pep604-annotation + "UP032", # f-string + "UP045", # non-pep604-annotation-optional + "B005", # strip-with-multi-characters + "B006", # mutable-argument-default + "B007", # unused-loop-control-variable + "B026", # star-arg-unpacking-after-keyword-arg + "B903", # class-as-data-structure + "B904", # raise-without-from-inside-except + "B905", # zip-without-explicit-strict + "N806", # non-lowercase-variable-in-function + "N815", # mixed-case-variable-in-class-scope + "PT011", # pytest-raises-too-broad + "SIM102", # collapsible-if + "SIM103", # needless-bool + "SIM105", # suppressible-exception + "SIM107", # return-in-try-except-finally + "SIM108", # if-else-block-instead-of-if-exp + "SIM113", # enumerate-for-loop + "SIM117", # multiple-with-statements + "SIM210", # if-expr-with-true-false +] + +[format] +# 5. Use single quotes in `ruff format`. +quote-style = "single" \ No newline at end of file From b65ce87a39eb33251e7cb920590aee64338808f3 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Tue, 29 Apr 2025 17:44:11 +0800 Subject: [PATCH 054/121] fix: `current_stage` in `Query` --- ruff.toml => .ruff.toml | 1 + pkg/core/entities.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename ruff.toml => .ruff.toml (98%) diff --git a/ruff.toml b/.ruff.toml similarity index 98% rename from ruff.toml rename to .ruff.toml index 27159c90..992262ff 100644 --- a/ruff.toml +++ b/.ruff.toml @@ -8,6 +8,7 @@ ignore = [ "E741", # Ambiguous variable name: `l` "E722", # bare-except "E721", # type-comparison + "F821", # undefined-all "FURB113", # repeated-append "FURB152", # math-constant "UP007", # non-pep604-annotation diff --git a/pkg/core/entities.py b/pkg/core/entities.py index 5ffd0029..9eddc935 100644 --- a/pkg/core/entities.py +++ b/pkg/core/entities.py @@ -96,7 +96,7 @@ class Query(pydantic.BaseModel): """回复消息链,从resp_messages包装而得""" # ======= 内部保留 ======= - current_stage = None # pkg.pipeline.pipelinemgr.StageInstContainer + current_stage: typing.Optional['pkg.pipeline.pipelinemgr.StageInstContainer'] = None """当前所处阶段""" class Config: From d4af2d4326a9dd117d2c9b986bffd88312815d69 Mon Sep 17 00:00:00 2001 From: HYana Date: Tue, 29 Apr 2025 23:49:15 +0800 Subject: [PATCH 055/121] feat: finish update pipline but left some bugs --- web/package-lock.json | 595 ++++++++++++++++++ .../pipeline-form/PipelineFormComponent.tsx | 68 +- .../pipeline-form/PipelineFormEntity.ts | 7 + web/src/app/home/pipelines/page.tsx | 46 +- web/src/app/infra/api/api-types/index.ts | 94 ++- web/src/app/infra/http/HttpClient.ts | 4 +- 6 files changed, 799 insertions(+), 15 deletions(-) create mode 100644 web/src/app/home/pipelines/components/pipeline-form/PipelineFormEntity.ts diff --git a/web/package-lock.json b/web/package-lock.json index f8c3b1b4..73cc51e1 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -25,6 +25,9 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.4", + "husky": "^9.1.7", + "lint-staged": "^15.5.1", + "prettier": "^3.5.3", "typescript": "^5" } }, @@ -1588,6 +1591,33 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2029,6 +2059,37 @@ "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2075,6 +2136,12 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2086,6 +2153,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmmirror.com/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -2291,6 +2367,18 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -2869,6 +2957,35 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3068,6 +3185,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3103,6 +3232,18 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -3273,6 +3414,30 @@ "node": ">= 0.4" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmmirror.com/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3487,6 +3652,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -3599,6 +3776,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -3830,6 +4019,74 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lint-staged": { + "version": "15.5.1", + "resolved": "https://registry.npmmirror.com/lint-staged/-/lint-staged-15.5.1.tgz", + "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", + "dev": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/listr2": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/listr2/-/listr2-8.3.2.tgz", + "integrity": "sha512-vsBzcU4oE+v0lj4FhVLzr9dBTv4/fHIa57l+GCwovP8MoFNZJTOhGU8PXd4v2VJCbECAaijBiHntiekFMLvo0g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3856,6 +4113,68 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3876,6 +4195,12 @@ "node": ">= 0.4" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3917,6 +4242,30 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4020,6 +4369,33 @@ } } }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4135,6 +4511,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4252,6 +4643,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -4297,6 +4700,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5034,6 +5452,37 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -5044,6 +5493,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/rspack-resolver": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rspack-resolver/-/rspack-resolver-1.3.0.tgz", @@ -5348,6 +5803,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -5357,6 +5824,34 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5379,11 +5874,43 @@ "node": ">=10.0.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==" }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -5491,6 +6018,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -5500,6 +6042,18 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -5908,6 +6462,47 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index 32ca5405..467e43ac 100644 --- a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -13,10 +13,20 @@ import styles from "./pipelineFormStyle.module.css"; import { httpClient } from "@/app/infra/http/HttpClient"; import { LLMModel, Pipeline } from "@/app/infra/api/api-types"; import { UUID } from "uuidjs"; +import {PipelineFormEntity} from "@/app/home/pipelines/components/pipeline-form/PipelineFormEntity"; export default function PipelineFormComponent({ - onFinish + initValues, + onFinish, + isEditMode, + pipelineId, + disableForm, }: { + pipelineId?: string; + isEditMode: boolean; + disableForm: boolean; + // 这里的写法很不安全不规范,未来流水线需要重新整理 + initValues?: PipelineFormEntity; onFinish: () => void; }) { const [nowFormIndex, setNowFormIndex] = useState(0); @@ -40,6 +50,18 @@ export default function PipelineFormComponent({ getLLMModelList(); }, []); + useEffect(() => { + console.log("initValues change: ", initValues); + if (initValues) { + basicForm.setFieldsValue(initValues.basic); + aiForm.setFieldsValue(initValues.ai); + triggerForm.setFieldsValue(initValues.trigger); + safetyForm.setFieldsValue(initValues.safety); + outputForm.setFieldsValue(initValues.output); + } + }, [initValues]); + + function getLLMModelList() { httpClient .getProviderLLMModels() @@ -91,6 +113,14 @@ export default function PipelineFormComponent({ } function handleCommit() { + if (isEditMode) { + handleModify() + } else { + handleCreate() + } + } + + function handleCreate() { Promise.all([ basicForm.validateFields(), aiForm.validateFields(), @@ -98,13 +128,30 @@ export default function PipelineFormComponent({ safetyForm.validateFields(), outputForm.validateFields() ]) - .then(() => { - const pipeline = assembleForm(); - httpClient.createPipeline(pipeline).then(() => onFinish()); - }) - .catch((e) => { - console.error(e); - }); + .then(() => { + const pipeline = assembleForm(); + httpClient.createPipeline(pipeline).then(() => onFinish()); + }) + .catch((e) => { + console.error(e); + }); + } + + function handleModify() { + Promise.all([ + basicForm.validateFields(), + aiForm.validateFields(), + triggerForm.validateFields(), + safetyForm.validateFields(), + outputForm.validateFields() + ]) + .then(() => { + const pipeline = assembleForm(); + httpClient.updatePipeline(pipelineId || '', pipeline).then(() => onFinish()); + }) + .catch((e) => { + console.error(e); + }); } // TODO 类型混乱,需要优化 @@ -142,6 +189,7 @@ export default function PipelineFormComponent({ display: getNowFormLabel().name === "basic" ? "block" : "none" }} form={basicForm} + disabled={disableForm} > {/* Runner 配置区块 */}
运行器
@@ -331,6 +380,7 @@ export default function PipelineFormComponent({ display: getNowFormLabel().name === "trigger" ? "block" : "none" }} form={triggerForm} + disabled={disableForm} > {/* 群响应规则块 */}
群响应规则
@@ -423,6 +473,7 @@ export default function PipelineFormComponent({ display: getNowFormLabel().name === "safety" ? "block" : "none" }} form={safetyForm} + disabled={disableForm} > {/* 内容过滤块 content-filter */}
内容过滤
@@ -488,6 +539,7 @@ export default function PipelineFormComponent({ display: getNowFormLabel().name === "output" ? "block" : "none" }} form={outputForm} + disabled={disableForm} > {/* 长文本处理区块 */}
长文本处理
diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormEntity.ts b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormEntity.ts new file mode 100644 index 00000000..a9034ece --- /dev/null +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormEntity.ts @@ -0,0 +1,7 @@ +export interface PipelineFormEntity { + basic: object, + ai: object, + trigger: object, + safety: object, + output: object, +} \ No newline at end of file diff --git a/web/src/app/home/pipelines/page.tsx b/web/src/app/home/pipelines/page.tsx index fc1036f3..5c358870 100644 --- a/web/src/app/home/pipelines/page.tsx +++ b/web/src/app/home/pipelines/page.tsx @@ -6,11 +6,21 @@ import PipelineFormComponent from "./components/pipeline-form/PipelineFormCompon import { httpClient } from "@/app/infra/http/HttpClient"; import { PipelineCardVO } from "@/app/home/pipelines/components/pipeline-card/PipelineCardVO"; import PipelineCardComponent from "@/app/home/pipelines/components/pipeline-card/PipelineCardComponent"; +import {PipelineFormEntity} from "@/app/home/pipelines/components/pipeline-form/PipelineFormEntity"; export default function PluginConfigPage() { const [modalOpen, setModalOpen] = useState(false); - const [isEditForm] = useState(false); + const [isEditForm, setIsEditForm] = useState(false); const [pipelineList, setPipelineList] = useState([]); + const [selectedPipelineId, setSelectedPipelineId] = useState("") + const [selectedPipelineFormValue, setSelectedPipelineFormValue] = useState({ + basic: {}, + ai: {}, + trigger: {}, + safety: {}, + output: {}, + }) + const [disableForm, setDisableForm] = useState(false) useEffect(() => { getPipelines(); @@ -37,12 +47,29 @@ export default function PluginConfigPage() { }); } + function getSelectedPipelineForm(id?: string) { + httpClient.getPipeline(id ?? selectedPipelineId).then((value) => { + setSelectedPipelineFormValue({ + ai: value.pipeline.config.ai, + basic: { + description: value.pipeline.description, + name: value.pipeline.name, + }, + output: value.pipeline.config.output, + safety: value.pipeline.config.safety, + trigger: value.pipeline.config.trigger, + }) + setDisableForm(false) + }) + } + return (
setModalOpen(false)} onCancel={() => setModalOpen(false)} width={700} @@ -53,6 +80,10 @@ export default function PluginConfigPage() { getPipelines(); setModalOpen(false); }} + isEditMode={isEditForm} + pipelineId={selectedPipelineId} + disableForm={disableForm} + initValues={selectedPipelineFormValue} /> @@ -60,7 +91,18 @@ export default function PluginConfigPage() {
{pipelineList.map((pipeline) => { return ( - +
{ + setDisableForm(true) + setIsEditForm(true); + setModalOpen(true); + setSelectedPipelineId(pipeline.id) + getSelectedPipelineForm(pipeline.id); + }} + > + +
); })}
diff --git a/web/src/app/infra/api/api-types/index.ts b/web/src/app/infra/api/api-types/index.ts index f3113468..da421d3b 100644 --- a/web/src/app/infra/api/api-types/index.ts +++ b/web/src/app/infra/api/api-types/index.ts @@ -57,9 +57,6 @@ export interface ApiRespPipelines { pipelines: Pipeline[]; } -export interface ApiRespPipeline { - pipeline: Pipeline; -} export interface Pipeline { uuid: string; @@ -215,3 +212,94 @@ export interface MarketPluginResponse { plugins: MarketPlugin[]; total: number; } + + +interface GetPipelineConfig { + ai: { + "dashscope-app-api": { + "api-key": string; + "app-id": string; + "app-type": "agent" | "workflow"; + "references-quote"?: string; + }; + "dify-service-api": { + "api-key": string; + "app-type": "chat" | "agent" | "workflow"; + "base-url": string; + "thinking-convert": "plain" | "original" | "remove"; + timeout?: number; + }; + "local-agent": { + "max-round": number; + model: string; + prompt: Array<{ + content: string; + role: string; + }>; + }; + runner: { + runner: "local-agent" | "dify-service-api" | "dashscope-app-api"; + }; + }; + output: { + "force-delay": { + max: number; + min: number; + }; + "long-text-processing": { + "font-path": string; + strategy: "forward" | "image"; + threshold: number; + }; + misc: { + "at-sender": boolean; + "hide-exception": boolean; + "quote-origin": boolean; + "track-function-calls": boolean; + }; + }; + safety: { + "content-filter": { + "check-sensitive-words": boolean; + scope: "all" | "income-msg" | "output-msg"; + }; + "rate-limit": { + limitation: number; + strategy: "drop" | "wait"; + "window-length": number; + }; + }; + trigger: { + "access-control": { + blacklist: string[]; + mode: "blacklist" | "whitelist"; + whitelist: string[]; + }; + "group-respond-rules": { + at: boolean; + prefix: string[]; + random: number; + regexp: string[]; + }; + "ignore-rules": { + prefix: string[]; + regexp: string[]; + }; + }; +} + +interface GetPipeline { + config: GetPipelineConfig; + created_at: string; + description: string; + for_version: string; + is_default: boolean; + name: string; + stages: string[]; + updated_at: string; + uuid: string; +} + +export interface GetPipelineResponseData { + pipeline: GetPipeline; +} \ No newline at end of file diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts index 7183ba59..76ecb4ce 100644 --- a/web/src/app/infra/http/HttpClient.ts +++ b/web/src/app/infra/http/HttpClient.ts @@ -27,7 +27,7 @@ import { ApiRespAsyncTasks, ApiRespAsyncTask, ApiRespUserToken, - MarketPluginResponse + MarketPluginResponse, GetPipelineResponseData } from "../api/api-types"; import { notification } from "antd"; @@ -258,7 +258,7 @@ class HttpClient { return this.get("/api/v1/pipelines"); } - public getPipeline(uuid: string): Promise { + public getPipeline(uuid: string): Promise { return this.get(`/api/v1/pipelines/${uuid}`); } From 460e065eed2a8e30335680fabac9cc078571cd3b Mon Sep 17 00:00:00 2001 From: HYana Date: Wed, 30 Apr 2025 17:36:46 +0800 Subject: [PATCH 056/121] feat: update eslint & prettier rules --- web/.husky/pre-commit | 1 - web/.lintstagedrc.json | 4 +- web/.prettierrc.mjs | 17 +- web/eslint.config.mjs | 10 +- web/next.config.ts | 4 +- web/package-lock.json | 205 ++- web/package.json | 5 +- .../home/bots/components/bot-card/BotCard.tsx | 66 +- .../bots/components/bot-card/BotCardVO.ts | 44 +- .../home/bots/components/bot-form/BotForm.tsx | 86 +- .../bots/components/bot-form/BotFormEntity.ts | 30 +- .../bot-form/ChooseAdapterEntity.ts | 6 +- web/src/app/home/bots/page.tsx | 58 +- .../dynamic-form/DynamicFormComponent.tsx | 43 +- .../dynamic-form/DynamicFormItemComponent.tsx | 54 +- .../dynamic-form/DynamicFormItemConfig.ts | 73 +- .../dynamic-form/testDynamicConfigList.ts | 76 +- .../EmptyAndCreateComponent.tsx | 54 +- .../components/home-sidebar/HomeSidebar.tsx | 98 +- .../home-sidebar/HomeSidebarChild.tsx | 4 +- .../home-sidebar/sidbarConfigList.ts | 52 +- .../components/home-titlebar/HomeTitleBar.tsx | 23 +- web/src/app/home/layout.tsx | 28 +- web/src/app/home/mock-api/index.ts | 1447 +++++++++-------- web/src/app/home/models/ICreateLLMField.ts | 14 +- .../models/component/llm-card/LLMCard.tsx | 54 +- .../models/component/llm-card/LLMCardVO.ts | 31 +- .../component/llm-form/ChooseAdapterEntity.ts | 6 +- .../models/component/llm-form/LLMForm.tsx | 88 +- web/src/app/home/models/page.tsx | 221 +-- web/src/app/home/page.tsx | 5 +- .../pipeline-card/PipelineCardComponent.tsx | 52 +- .../pipeline-card/PipelineCardVO.ts | 36 +- .../pipeline-form/PipelineChildFormEntity.ts | 2 +- .../pipeline-form/PipelineFormComponent.tsx | 311 ++-- .../pipeline-form/PipelineFormEntity.ts | 12 +- web/src/app/home/pipelines/page.tsx | 67 +- web/src/app/home/plugins/page.tsx | 64 +- .../plugins/plugin-installed/PluginCardVO.ts | 41 +- .../PluginInstalledComponent.tsx | 30 +- .../plugin-card/PluginCardComponent.tsx | 22 +- .../plugin-market/PluginMarketComponent.tsx | 205 +-- .../PluginMarketCardComponent.tsx | 20 +- .../plugin-market-card/PluginMarketCardVO.ts | 46 +- web/src/app/infra/api/api-types/index.ts | 68 +- .../CreateCardComponent.tsx | 42 +- web/src/app/infra/basic-types/I18N.ts | 6 +- web/src/app/infra/http/HttpClient.ts | 126 +- web/src/app/layout.tsx | 13 +- web/src/app/login/layout.tsx | 36 +- web/src/app/login/page.tsx | 46 +- web/src/app/not-found.tsx | 185 ++- web/src/app/page.tsx | 6 +- 53 files changed, 2241 insertions(+), 2102 deletions(-) diff --git a/web/.husky/pre-commit b/web/.husky/pre-commit index 832da580..aa89fa4b 100644 --- a/web/.husky/pre-commit +++ b/web/.husky/pre-commit @@ -1,3 +1,2 @@ cd web pnpm lint-staged -pnpm test \ No newline at end of file diff --git a/web/.lintstagedrc.json b/web/.lintstagedrc.json index f7a24c1f..253287a0 100644 --- a/web/.lintstagedrc.json +++ b/web/.lintstagedrc.json @@ -1,3 +1,3 @@ { - "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix", "eslint"] -} \ No newline at end of file + "*.{js,jsx,ts,tsx}": ["eslint --fix", "eslint"] +} diff --git a/web/.prettierrc.mjs b/web/.prettierrc.mjs index 0ce90351..01ed3d48 100644 --- a/web/.prettierrc.mjs +++ b/web/.prettierrc.mjs @@ -3,7 +3,20 @@ * @type {import("prettier").Config} */ const config = { - trailingComma: "none", + // 单行长度 + printWidth: 80, + // 缩进 + tabWidth: 2, + // 使用空格代替tab缩进 + useTabs: false, + // 句末使用分号 + semi: true, + // 使用单引号 + singleQuote: true, + // 大括号前后空格 + bracketSpacing: true, + attributeVerticalAlignment: 'auto', + trailingComma: 'all', }; -export default config; \ No newline at end of file +export default config; diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index c85fb67c..18b74c95 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -1,6 +1,7 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { FlatCompat } from '@eslint/eslintrc'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -10,7 +11,8 @@ const compat = new FlatCompat({ }); const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), + ...compat.extends('next/core-web-vitals', 'next/typescript'), + eslintPluginPrettierRecommended, ]; export default eslintConfig; diff --git a/web/next.config.ts b/web/next.config.ts index 9d1f4a25..1abe320c 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -1,8 +1,8 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ - output: 'export' + output: 'export', }; export default nextConfig; diff --git a/web/package-lock.json b/web/package-lock.json index 73cc51e1..3ee13d82 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -25,10 +25,13 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.4", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-prettier": "^5.2.6", "husky": "^9.1.7", "lint-staged": "^15.5.1", "prettier": "^3.5.3", - "typescript": "^5" + "typescript": "^5.8.3", + "typescript-eslint": "^8.31.1" } }, "node_modules/@ant-design/colors": { @@ -906,6 +909,18 @@ "node": ">=12.4.0" } }, + "node_modules/@pkgr/core": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz", + "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@rc-component/async-validator": { "version": "5.0.4", "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz", @@ -1133,16 +1148,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz", - "integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz", + "integrity": "sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/type-utils": "8.28.0", - "@typescript-eslint/utils": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.31.1", + "@typescript-eslint/type-utils": "8.31.1", + "@typescript-eslint/utils": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1162,15 +1177,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz", - "integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.1.tgz", + "integrity": "sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/scope-manager": "8.31.1", + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/typescript-estree": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4" }, "engines": { @@ -1186,13 +1201,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz", - "integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz", + "integrity": "sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0" + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1203,13 +1218,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz", - "integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz", + "integrity": "sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.28.0", - "@typescript-eslint/utils": "8.28.0", + "@typescript-eslint/typescript-estree": "8.31.1", + "@typescript-eslint/utils": "8.31.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -1226,9 +1241,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz", - "integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.1.tgz", + "integrity": "sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1239,13 +1254,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz", - "integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz", + "integrity": "sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/visitor-keys": "8.28.0", + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/visitor-keys": "8.31.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1317,15 +1332,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz", - "integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.1.tgz", + "integrity": "sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.28.0", - "@typescript-eslint/types": "8.28.0", - "@typescript-eslint/typescript-estree": "8.28.0" + "@typescript-eslint/scope-manager": "8.31.1", + "@typescript-eslint/types": "8.31.1", + "@typescript-eslint/typescript-estree": "8.31.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1340,12 +1355,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz", - "integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==", + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz", + "integrity": "sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.28.0", + "@typescript-eslint/types": "8.31.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2640,6 +2655,18 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", + "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -2800,6 +2827,36 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", + "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.4", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", @@ -2992,6 +3049,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -4715,6 +4778,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6117,6 +6192,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz", + "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -6300,9 +6391,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6312,6 +6403,28 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.31.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.1.tgz", + "integrity": "sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.31.1", + "@typescript-eslint/parser": "8.31.1", + "@typescript-eslint/utils": "8.31.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/web/package.json b/web/package.json index 2fc93f2e..17b7ffda 100644 --- a/web/package.json +++ b/web/package.json @@ -28,9 +28,12 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.2.4", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-prettier": "^5.2.6", "husky": "^9.1.7", "lint-staged": "^15.5.1", "prettier": "^3.5.3", - "typescript": "^5" + "typescript": "^5.8.3", + "typescript-eslint": "^8.31.1" } } diff --git a/web/src/app/home/bots/components/bot-card/BotCard.tsx b/web/src/app/home/bots/components/bot-card/BotCard.tsx index 92d2449a..f57a2761 100644 --- a/web/src/app/home/bots/components/bot-card/BotCard.tsx +++ b/web/src/app/home/bots/components/bot-card/BotCard.tsx @@ -1,39 +1,33 @@ -import {BotCardVO} from "@/app/home/bots/components/bot-card/BotCardVO"; -import styles from "./botCard.module.css"; +import { BotCardVO } from '@/app/home/bots/components/bot-card/BotCardVO'; +import styles from './botCard.module.css'; -export default function BotCard({ - botCardVO -}: { - botCardVO: BotCardVO; -}) { - return ( -
- {/* icon和基本信息 */} -
- {/* icon */} -
- ICO -
- {/* bot基本信息 */} -
-
- {botCardVO.name} -
-
- 平台:{botCardVO.adapter} -
-
- 绑定流水线:{botCardVO.pipelineName} -
-
-
- {/* 描述和创建时间 */} -
- 描述:{botCardVO.description} -
- {/*
+export default function BotCard({ botCardVO }: { botCardVO: BotCardVO }) { + return ( +
+ {/* icon和基本信息 */} +
+ {/* icon */} +
ICO
+ {/* bot基本信息 */} +
+
+ {botCardVO.name} +
+
+ 平台:{botCardVO.adapter} +
+
+ 绑定流水线:{botCardVO.pipelineName} +
+
+
+ {/* 描述和创建时间 */} +
+ 描述:{botCardVO.description} +
+ {/*
更新时间:{botCardVO.updateTime}
*/} -
- ) -} \ No newline at end of file +
+ ); +} diff --git a/web/src/app/home/bots/components/bot-card/BotCardVO.ts b/web/src/app/home/bots/components/bot-card/BotCardVO.ts index 6f32b183..a015a6c7 100644 --- a/web/src/app/home/bots/components/bot-card/BotCardVO.ts +++ b/web/src/app/home/bots/components/bot-card/BotCardVO.ts @@ -1,28 +1,26 @@ export interface IBotCardVO { - id: string; - name: string; - adapter: string; - description: string; - updateTime: string; - pipelineName: string; + id: string; + name: string; + adapter: string; + description: string; + updateTime: string; + pipelineName: string; } export class BotCardVO implements IBotCardVO { - id: string; - adapter: string; - description: string; - name: string; - updateTime: string; - pipelineName: string; + id: string; + adapter: string; + description: string; + name: string; + updateTime: string; + pipelineName: string; - - constructor(props: IBotCardVO) { - this.id = props.id; - this.name = props.name; - this.adapter = props.adapter; - this.description = props.description; - this.updateTime = props.updateTime; - this.pipelineName = props.pipelineName; - } - -} \ No newline at end of file + constructor(props: IBotCardVO) { + this.id = props.id; + this.name = props.name; + this.adapter = props.adapter; + this.description = props.description; + this.updateTime = props.updateTime; + this.pipelineName = props.pipelineName; + } +} diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index 5c3e804d..156ee581 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -1,24 +1,24 @@ import { BotFormEntity, - IBotFormEntity -} from "@/app/home/bots/components/bot-form/BotFormEntity"; -import { Button, Form, Input, notification, Select, Space } from "antd"; -import { useEffect, useState } from "react"; -import { IChooseAdapterEntity } from "@/app/home/bots/components/bot-form/ChooseAdapterEntity"; + IBotFormEntity, +} from '@/app/home/bots/components/bot-form/BotFormEntity'; +import { Button, Form, Input, notification, Select, Space } from 'antd'; +import { useEffect, useState } from 'react'; +import { IChooseAdapterEntity } from '@/app/home/bots/components/bot-form/ChooseAdapterEntity'; import { DynamicFormItemConfig, IDynamicFormItemConfig, - parseDynamicFormItemType -} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; -import { UUID } from "uuidjs"; -import DynamicFormComponent from "@/app/home/components/dynamic-form/DynamicFormComponent"; -import { httpClient } from "@/app/infra/http/HttpClient"; -import { Bot } from "@/app/infra/api/api-types"; + parseDynamicFormItemType, +} from '@/app/home/components/dynamic-form/DynamicFormItemConfig'; +import { UUID } from 'uuidjs'; +import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent'; +import { httpClient } from '@/app/infra/http/HttpClient'; +import { Bot } from '@/app/infra/api/api-types'; export default function BotForm({ initBotId, onFormSubmit, - onFormCancel + onFormCancel, }: { initBotId?: string; onFormSubmit: (value: IBotFormEntity) => void; @@ -55,9 +55,9 @@ export default function BotForm({ rawAdapterList.adapters.map((item) => { return { label: item.label.zh_CN, - value: item.name + value: item.name, }; - }) + }), ); // 初始化适配器表单map rawAdapterList.adapters.forEach((rawAdapter) => { @@ -71,9 +71,9 @@ export default function BotForm({ label: item.label, name: item.name, required: item.required, - type: parseDynamicFormItemType(item.type) - }) - ) + type: parseDynamicFormItemType(item.type), + }), + ), ); }); // 拉取初始化表单信息 @@ -99,12 +99,12 @@ export default function BotForm({ adapter: bot.adapter, description: bot.description, name: bot.name, - adapter_config: bot.adapter_config + adapter_config: bot.adapter_config, }); } function handleAdapterSelect(adapterName: string) { - console.log("Select adapter: ", adapterName); + console.log('Select adapter: ', adapterName); if (adapterName) { const dynamicFormConfigList = adapterNameToDynamicConfigMap.get(adapterName); @@ -129,33 +129,33 @@ export default function BotForm({ // 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 function onDynamicFormSubmit(value: object) { setIsLoading(true); - console.log("set loading", true); + console.log('set loading', true); if (initBotId) { // 编辑提交 - console.log("submit edit", form.getFieldsValue(), value); + console.log('submit edit', form.getFieldsValue(), value); const updateBot: Bot = { uuid: initBotId, name: form.getFieldsValue().name, description: form.getFieldsValue().description, adapter: form.getFieldsValue().adapter, - adapter_config: value + adapter_config: value, }; httpClient .updateBot(initBotId, updateBot) .then((res) => { // TODO success toast - console.log("update bot success", res); + console.log('update bot success', res); onFormSubmit(form.getFieldsValue()); notification.success({ - message: "更新成功", - description: "机器人更新成功" + message: '更新成功', + description: '机器人更新成功', }); }) .catch(() => { // TODO error toast notification.error({ - message: "更新失败", - description: "机器人更新失败" + message: '更新失败', + description: '机器人更新失败', }); }) .finally(() => { @@ -165,20 +165,20 @@ export default function BotForm({ }); } else { // 创建提交 - console.log("submit create", form.getFieldsValue(), value); + console.log('submit create', form.getFieldsValue(), value); const newBot: Bot = { name: form.getFieldsValue().name, description: form.getFieldsValue().description, adapter: form.getFieldsValue().adapter, - adapter_config: value + adapter_config: value, }; httpClient .createBot(newBot) .then((res) => { // TODO success toast notification.success({ - message: "创建成功", - description: "机器人创建成功" + message: '创建成功', + description: '机器人创建成功', }); console.log(res); onFormSubmit(form.getFieldsValue()); @@ -186,8 +186,8 @@ export default function BotForm({ .catch(() => { // TODO error toast notification.error({ - message: "创建失败", - description: "机器人创建失败" + message: '创建失败', + description: '机器人创建失败', }); }) .finally(() => { @@ -197,7 +197,7 @@ export default function BotForm({ }); } setShowDynamicForm(false); - console.log("set loading", false); + console.log('set loading', false); // TODO 刷新bot列表 // TODO 关闭当前弹窗 Already closed @setShowDynamicForm(false)? } @@ -217,9 +217,9 @@ export default function BotForm({ disabled={isLoading} > - label={"机器人名称"} - name={"name"} - rules={[{ required: true, message: "该项为必填项哦~" }]} + label={'机器人名称'} + name={'name'} + rules={[{ required: true, message: '该项为必填项哦~' }]} > - label={"描述"} - name={"description"} - rules={[{ required: true, message: "该项为必填项哦~" }]} + label={'描述'} + name={'description'} + rules={[{ required: true, message: '该项为必填项哦~' }]} > - label={"平台/适配器选择"} - name={"adapter"} - rules={[{ required: true, message: "该项为必填项哦~" }]} + label={'平台/适配器选择'} + name={'adapter'} + rules={[{ required: true, message: '该项为必填项哦~' }]} > - } + {config.type === DynamicFormItemType.STRING && } - { - config.type === DynamicFormItemType.BOOLEAN && - - } + {config.type === DynamicFormItemType.BOOLEAN && } - { - config.type === DynamicFormItemType.STRING_ARRAY && - + )} + + ); +} diff --git a/web/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts b/web/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts index 8937b0c5..d18dfa47 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts +++ b/web/src/app/home/components/dynamic-form/DynamicFormItemConfig.ts @@ -1,51 +1,54 @@ export interface IDynamicFormItemConfig { - id: string; - default: string | number | boolean | Array; - label: IDynamicFormItemLabel; - name: string; - required: boolean; - type: DynamicFormItemType - description?: IDynamicFormItemLabel; + id: string; + default: string | number | boolean | Array; + label: IDynamicFormItemLabel; + name: string; + required: boolean; + type: DynamicFormItemType; + description?: IDynamicFormItemLabel; } export class DynamicFormItemConfig implements IDynamicFormItemConfig { - id: string; - name: string; - default: string | number | boolean | Array; - label: IDynamicFormItemLabel; - required: boolean; - type: DynamicFormItemType; - description?: IDynamicFormItemLabel; - - constructor(params: IDynamicFormItemConfig) { - this.id = params.id; - this.name = params.name; - this.default = params.default; - this.label = params.label; - this.required = params.required; - this.type = params.type; - this.description = params.description; - } + id: string; + name: string; + default: string | number | boolean | Array; + label: IDynamicFormItemLabel; + required: boolean; + type: DynamicFormItemType; + description?: IDynamicFormItemLabel; + constructor(params: IDynamicFormItemConfig) { + this.id = params.id; + this.name = params.name; + this.default = params.default; + this.label = params.label; + this.required = params.required; + this.type = params.type; + this.description = params.description; + } } export interface IDynamicFormItemLabel { - en_US: string, - zh_CN: string, + en_US: string; + zh_CN: string; } export enum DynamicFormItemType { - INT = "integer", - STRING = "string", - BOOLEAN = "boolean", - STRING_ARRAY = "array[string]", - UNKNOWN = "unknown", + INT = 'integer', + STRING = 'string', + BOOLEAN = 'boolean', + STRING_ARRAY = 'array[string]', + UNKNOWN = 'unknown', } -export function isDynamicFormItemType(value: string): value is DynamicFormItemType { - return Object.values(DynamicFormItemType).includes(value as DynamicFormItemType); +export function isDynamicFormItemType( + value: string, +): value is DynamicFormItemType { + return Object.values(DynamicFormItemType).includes( + value as DynamicFormItemType, + ); } export function parseDynamicFormItemType(value: string): DynamicFormItemType { - return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN; -} \ No newline at end of file + return isDynamicFormItemType(value) ? value : DynamicFormItemType.UNKNOWN; +} diff --git a/web/src/app/home/components/dynamic-form/testDynamicConfigList.ts b/web/src/app/home/components/dynamic-form/testDynamicConfigList.ts index e7ff7ecc..244d7645 100644 --- a/web/src/app/home/components/dynamic-form/testDynamicConfigList.ts +++ b/web/src/app/home/components/dynamic-form/testDynamicConfigList.ts @@ -1,41 +1,41 @@ import { - DynamicFormItemConfig, - DynamicFormItemType, - IDynamicFormItemConfig -} from "@/app/home/components/dynamic-form/DynamicFormItemConfig"; + DynamicFormItemConfig, + DynamicFormItemType, + IDynamicFormItemConfig, +} from '@/app/home/components/dynamic-form/DynamicFormItemConfig'; export const testDynamicConfigList: IDynamicFormItemConfig[] = [ - new DynamicFormItemConfig({ - default: "", - id: "111", - label: { - zh_CN: "测试字段string", - en_US: "eng test" - }, - name: "string_test", - required: false, - type: DynamicFormItemType.STRING - }), - new DynamicFormItemConfig({ - default: "", - id: "222", - label: { - zh_CN: "测试字段int", - en_US: "int eng test" - }, - name: "int_test", - required: true, - type: DynamicFormItemType.INT - }), - new DynamicFormItemConfig({ - default: "", - id: "333", - label: { - zh_CN: "测试字段boolean", - en_US: "boolean eng test" - }, - name: "boolean_test", - required: false, - type: DynamicFormItemType.BOOLEAN - }), -] \ No newline at end of file + new DynamicFormItemConfig({ + default: '', + id: '111', + label: { + zh_CN: '测试字段string', + en_US: 'eng test', + }, + name: 'string_test', + required: false, + type: DynamicFormItemType.STRING, + }), + new DynamicFormItemConfig({ + default: '', + id: '222', + label: { + zh_CN: '测试字段int', + en_US: 'int eng test', + }, + name: 'int_test', + required: true, + type: DynamicFormItemType.INT, + }), + new DynamicFormItemConfig({ + default: '', + id: '333', + label: { + zh_CN: '测试字段boolean', + en_US: 'boolean eng test', + }, + name: 'boolean_test', + required: false, + type: DynamicFormItemType.BOOLEAN, + }), +]; diff --git a/web/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx b/web/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx index 19ab7fef..b55b4cee 100644 --- a/web/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx +++ b/web/src/app/home/components/empty-and-create-component/EmptyAndCreateComponent.tsx @@ -1,35 +1,27 @@ -import styles from "./emptyAndCreate.module.css"; +import styles from './emptyAndCreate.module.css'; -export default function EmptyAndCreateComponent({ - title, - subTitle, - buttonText, - onButtonClick, +export default function EmptyAndCreateComponent({ + title, + subTitle, + buttonText, + onButtonClick, }: { - title: string, - subTitle: string, - buttonText: string, - onButtonClick: () => void, + title: string; + subTitle: string; + buttonText: string; + onButtonClick: () => void; }) { - - return ( -
-
-
-
- {title} -
-
- {subTitle} -
-
-
- {buttonText} -
-
+ return ( +
+
+
+
{title}
+
{subTitle}
- ) -} \ No newline at end of file +
+ {buttonText} +
+
+
+ ); +} diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index 7a950e91..302abc27 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -1,17 +1,17 @@ -"use client"; +'use client'; -import styles from "./HomeSidebar.module.css"; -import { useEffect, useState } from "react"; +import styles from './HomeSidebar.module.css'; +import { useEffect, useState } from 'react'; import { SidebarChild, - SidebarChildVO -} from "@/app/home/components/home-sidebar/HomeSidebarChild"; -import { useRouter, usePathname } from "next/navigation"; -import { sidebarConfigList } from "@/app/home/components/home-sidebar/sidbarConfigList"; + SidebarChildVO, +} from '@/app/home/components/home-sidebar/HomeSidebarChild'; +import { useRouter, usePathname } from 'next/navigation'; +import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList'; // TODO 侧边导航栏要加动画 export default function HomeSidebar({ - onSelectedChangeAction + onSelectedChangeAction, }: { onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void; }) { @@ -24,13 +24,13 @@ export default function HomeSidebar({ }, [pathname]); const [selectedChild, setSelectedChild] = useState( - sidebarConfigList[0] + sidebarConfigList[0], ); useEffect(() => { - console.log("HomeSidebar挂载完成"); + console.log('HomeSidebar挂载完成'); initSelect(); - return () => console.log("HomeSidebar卸载"); + return () => console.log('HomeSidebar卸载'); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -52,14 +52,14 @@ export default function HomeSidebar({ function handleRouteChange(pathname: string) { // TODO 这段逻辑并不好,未来router封装好后改掉 // 判断在home下,并且路由更改的是自己的路由子组件则更新UI - const routeList = pathname.split("/"); + const routeList = pathname.split('/'); if ( - routeList[1] === "home" && + routeList[1] === 'home' && sidebarConfigList.find((childConfig) => childConfig.route === pathname) ) { - console.log("find success"); + console.log('find success'); const routeSelectChild = sidebarConfigList.find( - (childConfig) => childConfig.route === pathname + (childConfig) => childConfig.route === pathname, ); if (routeSelectChild) { setSelectedChild(routeSelectChild); @@ -67,42 +67,34 @@ export default function HomeSidebar({ } } - - return ( -
- {/* LangBot、ICON区域 */} -
- {/* icon */} -
- L -
-
- Langbot -
+ return ( +
+ {/* LangBot、ICON区域 */} +
+ {/* icon */} +
L
+
Langbot
+
+ {/* 菜单列表,后期可升级成配置驱动 */} +
+ {sidebarConfigList.map((config) => { + return ( +
{ + console.log('click:', config.id); + handleChildClick(config); + }} + > +
- {/* 菜单列表,后期可升级成配置驱动 */} -
- { - sidebarConfigList.map(config => { - return ( -
{ - console.log('click:', config.id) - handleChildClick(config) - }} - > - -
- ) - }) - } - -
-
- ); -} \ No newline at end of file + ); + })} +
+
+ ); +} diff --git a/web/src/app/home/components/home-sidebar/HomeSidebarChild.tsx b/web/src/app/home/components/home-sidebar/HomeSidebarChild.tsx index e1089eba..2451e82b 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebarChild.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebarChild.tsx @@ -1,4 +1,4 @@ -import styles from "./HomeSidebar.module.css"; +import styles from './HomeSidebar.module.css'; export interface ISidebarChildVO { id: string; @@ -24,7 +24,7 @@ export class SidebarChildVO { export function SidebarChild({ icon, name, - isSelected + isSelected, }: { icon: string; name: string; diff --git a/web/src/app/home/components/home-sidebar/sidbarConfigList.ts b/web/src/app/home/components/home-sidebar/sidbarConfigList.ts index 4c83ea79..518d4f14 100644 --- a/web/src/app/home/components/home-sidebar/sidbarConfigList.ts +++ b/web/src/app/home/components/home-sidebar/sidbarConfigList.ts @@ -1,28 +1,28 @@ -import {SidebarChildVO} from "@/app/home/components/home-sidebar/HomeSidebarChild"; +import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild'; export const sidebarConfigList = [ - new SidebarChildVO({ - id: "models", - name: "模型配置", - icon: "", - route: "/home/models", - }), - new SidebarChildVO({ - id: "bots", - name: "机器人", - icon: "", - route: "/home/bots", - }), - new SidebarChildVO({ - id: "pipelines", - name: "流水线", - icon: "", - route: "/home/pipelines", - }), - new SidebarChildVO({ - id: "plugins", - name: "插件管理", - icon: "", - route: "/home/plugins", - }), -] + new SidebarChildVO({ + id: 'models', + name: '模型配置', + icon: '', + route: '/home/models', + }), + new SidebarChildVO({ + id: 'bots', + name: '机器人', + icon: '', + route: '/home/bots', + }), + new SidebarChildVO({ + id: 'pipelines', + name: '流水线', + icon: '', + route: '/home/pipelines', + }), + new SidebarChildVO({ + id: 'plugins', + name: '插件管理', + icon: '', + route: '/home/plugins', + }), +]; diff --git a/web/src/app/home/components/home-titlebar/HomeTitleBar.tsx b/web/src/app/home/components/home-titlebar/HomeTitleBar.tsx index 7011055d..a00c961c 100644 --- a/web/src/app/home/components/home-titlebar/HomeTitleBar.tsx +++ b/web/src/app/home/components/home-titlebar/HomeTitleBar.tsx @@ -1,16 +1,9 @@ -import styles from "./HomeTittleBar.module.css" +import styles from './HomeTittleBar.module.css'; - -export default function HomeTitleBar({ - title, -}: { - title: string -}) { - return ( -
-
{title}
-
- ); -} \ No newline at end of file +export default function HomeTitleBar({ title }: { title: string }) { + return ( +
+
{title}
+
+ ); +} diff --git a/web/src/app/home/layout.tsx b/web/src/app/home/layout.tsx index c765aef0..b3828614 100644 --- a/web/src/app/home/layout.tsx +++ b/web/src/app/home/layout.tsx @@ -1,35 +1,31 @@ -"use client" +'use client'; import '@ant-design/v5-patch-for-react-19'; -import styles from "./layout.module.css" -import HomeSidebar from "@/app/home/components/home-sidebar/HomeSidebar"; -import HomeTitleBar from "@/app/home/components/home-titlebar/HomeTitleBar"; -import React, { useState } from "react"; -import { SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild"; -import { useRouter } from 'next/navigation'; +import styles from './layout.module.css'; +import HomeSidebar from '@/app/home/components/home-sidebar/HomeSidebar'; +import HomeTitleBar from '@/app/home/components/home-titlebar/HomeTitleBar'; +import React, { useState } from 'react'; +import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild'; import { Layout } from 'antd'; const { Sider, Content } = Layout; export default function HomeLayout({ - children + children, }: Readonly<{ children: React.ReactNode; }>) { - const router = useRouter(); - const [title, setTitle] = useState("") + const [title, setTitle] = useState(''); const onSelectedChangeAction = (child: SidebarChildVO) => { - setTitle(child.name) - } + setTitle(child.name); + }; return ( {/* homeLayoutContainer 是整个容器的入口,使用 flex 的左右布局 */} - + {/* HomeSidebar 为侧边栏 */} @@ -44,5 +40,5 @@ export default function HomeLayout({ - ) + ); } diff --git a/web/src/app/home/mock-api/index.ts b/web/src/app/home/mock-api/index.ts index ec6db0ef..8791ecfd 100644 --- a/web/src/app/home/mock-api/index.ts +++ b/web/src/app/home/mock-api/index.ts @@ -1,733 +1,736 @@ -import {GetMetaDataResponse} from "@/app/infra/api/api-types/pipelines/GetMetaDataResponse"; -import {ApiResponse} from "@/app/infra/api/api-types"; +import { GetMetaDataResponse } from '@/app/infra/api/api-types/pipelines/GetMetaDataResponse'; +import { ApiResponse } from '@/app/infra/api/api-types'; -export async function fetchPipelineMetaData(): Promise> { - return { - "code": 0, - "data": { - "configs": [ +export async function fetchPipelineMetaData(): Promise< + ApiResponse +> { + return { + code: 0, + data: { + configs: [ + { + label: { + en_US: 'Trigger', + zh_CN: '触发条件', + }, + name: 'trigger', + stages: [ + { + config: [ { - "label": { - "en_US": "Trigger", - "zh_CN": "触发条件" - }, - "name": "trigger", - "stages": [ - { - "config": [ - { - "default": false, - "description": { - "en_US": "Whether to trigger when the message mentions the bot", - "zh_CN": "是否在消息@机器人时触发" - }, - "label": { - "en_US": "At", - "zh_CN": "@" - }, - "name": "at", - "required": true, - "type": "boolean" - }, - { - "default": [], - "description": { - "en_US": "The prefix of the message", - "zh_CN": "消息前缀" - }, - "items": { - "type": "string" - }, - "label": { - "en_US": "Prefix", - "zh_CN": "前缀" - }, - "name": "prefix", - "required": true, - "type": "array" - }, - { - "default": [], - "description": { - "en_US": "The regexp of the message", - "zh_CN": "消息正则表达式" - }, - "items": { - "type": "string" - }, - "label": { - "en_US": "Regexp", - "zh_CN": "正则表达式" - }, - "name": "regexp", - "required": true, - "type": "array" - }, - { - "default": 0, - "description": { - "en_US": "The probability of the random response, range from 0.0 to 1.0", - "zh_CN": "随机响应概率,范围为 0.0-1.0" - }, - "label": { - "en_US": "Random", - "zh_CN": "随机" - }, - "name": "random", - "required": false, - "type": "float" - } - ], - "description": { - "en_US": "The group respond rule of the pipeline", - "zh_CN": "群响应规则" - }, - "label": { - "en_US": "Group Respond Rule", - "zh_CN": "群响应规则" - }, - "name": "group-respond-rules" - }, - { - "config": [ - { - "default": "blacklist", - "description": { - "en_US": "The mode of the access control", - "zh_CN": "访问控制模式" - }, - "label": { - "en_US": "Mode", - "zh_CN": "模式" - }, - "name": "mode", - "options": [ - { - "label": { - "en_US": "Blacklist", - "zh_CN": "黑名单" - }, - "name": "blacklist" - }, - { - "label": { - "en_US": "Whitelist", - "zh_CN": "白名单" - }, - "name": "whitelist" - } - ], - "required": true, - "type": "select" - }, - { - "default": [], - "items": { - "type": "string" - }, - "label": { - "en_US": "Blacklist", - "zh_CN": "黑名单" - }, - "name": "blacklist", - "required": true, - "type": "array" - }, - { - "default": [], - "items": { - "type": "string" - }, - "label": { - "en_US": "Whitelist", - "zh_CN": "白名单" - }, - "name": "whitelist", - "required": true, - "type": "array" - } - ], - "label": { - "en_US": "Access Control", - "zh_CN": "访问控制" - }, - "name": "access-control" - }, - { - "config": [ - { - "default": [], - "description": { - "en_US": "The prefix of the message", - "zh_CN": "消息前缀" - }, - "items": { - "type": "string" - }, - "label": { - "en_US": "Prefix", - "zh_CN": "前缀" - }, - "name": "prefix", - "required": true, - "type": "array" - }, - { - "default": [], - "description": { - "en_US": "The regexp of the message", - "zh_CN": "消息正则表达式" - }, - "items": { - "type": "string" - }, - "label": { - "en_US": "Regexp", - "zh_CN": "正则表达式" - }, - "name": "regexp", - "required": true, - "type": "array" - } - ], - "label": { - "en_US": "Ignore Rules", - "zh_CN": "消息忽略规则" - }, - "name": "ignore-rules" - } - ] + default: false, + description: { + en_US: + 'Whether to trigger when the message mentions the bot', + zh_CN: '是否在消息@机器人时触发', + }, + label: { + en_US: 'At', + zh_CN: '@', + }, + name: 'at', + required: true, + type: 'boolean', }, { - "label": { - "en_US": "Safety Control", - "zh_CN": "安全控制" - }, - "name": "safety", - "stages": [ - { - "config": [ - { - "default": "all", - "label": { - "en_US": "Scope", - "zh_CN": "检查范围" - }, - "name": "scope", - "options": [ - { - "label": { - "en_US": "All", - "zh_CN": "全部" - }, - "name": "all" - }, - { - "label": { - "en_US": "Income Message", - "zh_CN": "传入消息(用户消息)" - }, - "name": "income-msg" - }, - { - "label": { - "en_US": "Output Message", - "zh_CN": "传出消息(机器人消息)" - }, - "name": "output-msg" - } - ], - "required": true, - "type": "select" - }, - { - "default": false, - "label": { - "en_US": "Check Sensitive Words", - "zh_CN": "检查敏感词" - }, - "name": "check-sensitive-words", - "required": true, - "type": "boolean" - } - ], - "label": { - "en_US": "Content Filter", - "zh_CN": "内容过滤" - }, - "name": "content-filter" - }, - { - "config": [ - { - "default": 60, - "label": { - "en_US": "Window Length", - "zh_CN": "窗口长度(秒)" - }, - "name": "window-length", - "required": true, - "type": "integer" - }, - { - "default": 60, - "label": { - "en_US": "Limitation", - "zh_CN": "限制次数" - }, - "name": "limitation", - "required": true, - "type": "integer" - }, - { - "default": "drop", - "label": { - "en_US": "Strategy", - "zh_CN": "策略" - }, - "name": "strategy", - "options": [ - { - "label": { - "en_US": "Drop", - "zh_CN": "丢弃" - }, - "name": "drop" - }, - { - "label": { - "en_US": "Wait", - "zh_CN": "等待" - }, - "name": "wait" - } - ], - "required": true, - "type": "select" - } - ], - "label": { - "en_US": "Rate Limit", - "zh_CN": "速率限制" - }, - "name": "rate-limit" - } - ] + default: [], + description: { + en_US: 'The prefix of the message', + zh_CN: '消息前缀', + }, + items: { + type: 'string', + }, + label: { + en_US: 'Prefix', + zh_CN: '前缀', + }, + name: 'prefix', + required: true, + type: 'array', }, { - "label": { - "en_US": "AI Feature", - "zh_CN": "AI 能力" - }, - "name": "ai", - "stages": [ - { - "config": [ - { - "default": "local-agent", - "label": { - "en_US": "Runner", - "zh_CN": "运行器" - }, - "name": "runner", - "options": [ - { - "label": { - "en_US": "Embedded Agent", - "zh_CN": "内置 Agent" - }, - "name": "local-agent" - }, - { - "label": { - "en_US": "Dify Service API", - "zh_CN": "Dify 服务 API" - }, - "name": "dify-service-api" - }, - { - "label": { - "en_US": "Aliyun Dashscope App API", - "zh_CN": "阿里云百炼平台 API" - }, - "name": "dashscope-app-api" - } - ], - "required": true, - "type": "select" - } - ], - "label": { - "en_US": "Runner", - "zh_CN": "运行方式" - }, - "name": "runner" - }, - { - "config": [ - { - "label": { - "en_US": "Model", - "zh_CN": "模型" - }, - "name": "model", - "required": true, - "scope": "/provider/models/llm", - "type": "select" - }, - { - "default": 10, - "label": { - "en_US": "Max Round", - "zh_CN": "最大回合数" - }, - "name": "max-round", - "required": true, - "type": "integer" - }, - { - "items": { - "properties": { - "content": { - "type": "string" - }, - "role": { - "default": "user", - "type": "string" - } - }, - "type": "object" - }, - "label": { - "en_US": "Prompt", - "zh_CN": "提示词" - }, - "name": "prompt", - "required": true, - "type": "array" - } - ], - "description": { - "en_US": "Configure the embedded agent of the pipeline", - "zh_CN": "配置内置 Agent" - }, - "label": { - "en_US": "Embedded Agent", - "zh_CN": "内置 Agent" - }, - "name": "local-agent" - }, - { - "config": [ - { - "label": { - "en_US": "Base URL", - "zh_CN": "基础 URL" - }, - "name": "base-url", - "required": true, - "type": "string" - }, - { - "default": "chat", - "label": { - "en_US": "App Type", - "zh_CN": "应用类型" - }, - "name": "app-type", - "options": [ - { - "label": { - "en_US": "Chat", - "zh_CN": "聊天(包括Chatflow)" - }, - "name": "chat" - }, - { - "label": { - "en_US": "Agent", - "zh_CN": "Agent" - }, - "name": "agent" - }, - { - "label": { - "en_US": "Workflow", - "zh_CN": "工作流" - }, - "name": "workflow" - } - ], - "required": true, - "type": "select" - }, - { - "label": { - "en_US": "API Key", - "zh_CN": "API 密钥" - }, - "name": "api-key", - "required": true, - "type": "string" - }, - { - "default": "plain", - "label": { - "en_US": "CoT Convert", - "zh_CN": "思维链转换策略" - }, - "name": "thinking-convert", - "options": [ - { - "label": { - "en_US": "Convert to ...", - "zh_CN": "转换成 ..." - }, - "name": "plain" - }, - { - "label": { - "en_US": "Original", - "zh_CN": "原始" - }, - "name": "original" - }, - { - "label": { - "en_US": "Remove", - "zh_CN": "移除" - }, - "name": "remove" - } - ], - "required": true, - "type": "select" - } - ], - "description": { - "en_US": "Configure the Dify service API of the pipeline", - "zh_CN": "配置 Dify 服务 API" - }, - "label": { - "en_US": "Dify Service API", - "zh_CN": "Dify 服务 API" - }, - "name": "dify-service-api" - }, - { - "config": [ - { - "default": "agent", - "label": { - "en_US": "App Type", - "zh_CN": "应用类型" - }, - "name": "app-type", - "options": [ - { - "label": { - "en_US": "Agent", - "zh_CN": "Agent" - }, - "name": "agent" - }, - { - "label": { - "en_US": "Workflow", - "zh_CN": "工作流" - }, - "name": "workflow" - } - ], - "required": true, - "type": "select" - }, - { - "label": { - "en_US": "API Key", - "zh_CN": "API 密钥" - }, - "name": "api-key", - "required": true, - "type": "string" - }, - { - "label": { - "en_US": "App ID", - "zh_CN": "应用 ID" - }, - "name": "app-id", - "required": true, - "type": "string" - }, - { - "default": "参考资料来自:", - "label": { - "en_US": "References Quote", - "zh_CN": "引用文本" - }, - "name": "references_quote", - "required": false, - "type": "string" - } - ], - "description": { - "en_US": "Configure the Aliyun Dashscope App API of the pipeline", - "zh_CN": "配置阿里云百炼平台 API" - }, - "label": { - "en_US": "Aliyun Dashscope App API", - "zh_CN": "阿里云百炼平台 API" - }, - "name": "dashscope-app-api" - } - ] + default: [], + description: { + en_US: 'The regexp of the message', + zh_CN: '消息正则表达式', + }, + items: { + type: 'string', + }, + label: { + en_US: 'Regexp', + zh_CN: '正则表达式', + }, + name: 'regexp', + required: true, + type: 'array', }, { - "label": { - "en_US": "Output Processing", - "zh_CN": "输出处理" + default: 0, + description: { + en_US: + 'The probability of the random response, range from 0.0 to 1.0', + zh_CN: '随机响应概率,范围为 0.0-1.0', + }, + label: { + en_US: 'Random', + zh_CN: '随机', + }, + name: 'random', + required: false, + type: 'float', + }, + ], + description: { + en_US: 'The group respond rule of the pipeline', + zh_CN: '群响应规则', + }, + label: { + en_US: 'Group Respond Rule', + zh_CN: '群响应规则', + }, + name: 'group-respond-rules', + }, + { + config: [ + { + default: 'blacklist', + description: { + en_US: 'The mode of the access control', + zh_CN: '访问控制模式', + }, + label: { + en_US: 'Mode', + zh_CN: '模式', + }, + name: 'mode', + options: [ + { + label: { + en_US: 'Blacklist', + zh_CN: '黑名单', + }, + name: 'blacklist', }, - "name": "output", - "stages": [ - { - "config": [ - { - "default": 1000, - "label": { - "en_US": "Threshold", - "zh_CN": "阈值" - }, - "name": "threshold", - "required": true, - "type": "integer" - }, - { - "default": "forward", - "label": { - "en_US": "Strategy", - "zh_CN": "策略" - }, - "name": "strategy", - "options": [ - { - "label": { - "en_US": "Forward Message Component", - "zh_CN": "转发消息组件" - }, - "name": "forward" - }, - { - "label": { - "en_US": "Convert to Image", - "zh_CN": "转换为图片" - }, - "name": "image" - } - ], - "required": true, - "type": "select" - }, - { - "default": "", - "label": { - "en_US": "Font Path", - "zh_CN": "字体路径" - }, - "name": "font-path", - "required": true, - "type": "string" - } - ], - "label": { - "en_US": "Long Text Processing", - "zh_CN": "长文本处理" - }, - "name": "long-text-processing" - }, - { - "config": [ - { - "default": 0, - "label": { - "en_US": "Min", - "zh_CN": "最小秒数" - }, - "name": "min", - "required": true, - "type": "integer" - }, - { - "default": 0, - "label": { - "en_US": "Max", - "zh_CN": "最大秒数" - }, - "name": "max", - "required": true, - "type": "integer" - } - ], - "label": { - "en_US": "Force Delay", - "zh_CN": "强制延迟" - }, - "name": "force-delay" - }, - { - "config": [ - { - "default": true, - "label": { - "en_US": "Hide Exception", - "zh_CN": "不输出异常信息给用户" - }, - "name": "hide-exception", - "required": true, - "type": "boolean" - }, - { - "default": true, - "label": { - "en_US": "At Sender", - "zh_CN": "在回复中@发送者" - }, - "name": "at-sender", - "required": true, - "type": "boolean" - }, - { - "default": false, - "label": { - "en_US": "Quote Origin", - "zh_CN": "引用原文" - }, - "name": "quote-origin", - "required": true, - "type": "boolean" - }, - { - "default": true, - "label": { - "en_US": "Track Function Calls", - "zh_CN": "跟踪函数调用" - }, - "name": "track-function-calls", - "required": true, - "type": "boolean" - } - ], - "label": { - "en_US": "Misc", - "zh_CN": "杂项" - }, - "name": "misc" - } - ] - } - ] + { + label: { + en_US: 'Whitelist', + zh_CN: '白名单', + }, + name: 'whitelist', + }, + ], + required: true, + type: 'select', + }, + { + default: [], + items: { + type: 'string', + }, + label: { + en_US: 'Blacklist', + zh_CN: '黑名单', + }, + name: 'blacklist', + required: true, + type: 'array', + }, + { + default: [], + items: { + type: 'string', + }, + label: { + en_US: 'Whitelist', + zh_CN: '白名单', + }, + name: 'whitelist', + required: true, + type: 'array', + }, + ], + label: { + en_US: 'Access Control', + zh_CN: '访问控制', + }, + name: 'access-control', + }, + { + config: [ + { + default: [], + description: { + en_US: 'The prefix of the message', + zh_CN: '消息前缀', + }, + items: { + type: 'string', + }, + label: { + en_US: 'Prefix', + zh_CN: '前缀', + }, + name: 'prefix', + required: true, + type: 'array', + }, + { + default: [], + description: { + en_US: 'The regexp of the message', + zh_CN: '消息正则表达式', + }, + items: { + type: 'string', + }, + label: { + en_US: 'Regexp', + zh_CN: '正则表达式', + }, + name: 'regexp', + required: true, + type: 'array', + }, + ], + label: { + en_US: 'Ignore Rules', + zh_CN: '消息忽略规则', + }, + name: 'ignore-rules', + }, + ], }, - "msg": "ok" - } + { + label: { + en_US: 'Safety Control', + zh_CN: '安全控制', + }, + name: 'safety', + stages: [ + { + config: [ + { + default: 'all', + label: { + en_US: 'Scope', + zh_CN: '检查范围', + }, + name: 'scope', + options: [ + { + label: { + en_US: 'All', + zh_CN: '全部', + }, + name: 'all', + }, + { + label: { + en_US: 'Income Message', + zh_CN: '传入消息(用户消息)', + }, + name: 'income-msg', + }, + { + label: { + en_US: 'Output Message', + zh_CN: '传出消息(机器人消息)', + }, + name: 'output-msg', + }, + ], + required: true, + type: 'select', + }, + { + default: false, + label: { + en_US: 'Check Sensitive Words', + zh_CN: '检查敏感词', + }, + name: 'check-sensitive-words', + required: true, + type: 'boolean', + }, + ], + label: { + en_US: 'Content Filter', + zh_CN: '内容过滤', + }, + name: 'content-filter', + }, + { + config: [ + { + default: 60, + label: { + en_US: 'Window Length', + zh_CN: '窗口长度(秒)', + }, + name: 'window-length', + required: true, + type: 'integer', + }, + { + default: 60, + label: { + en_US: 'Limitation', + zh_CN: '限制次数', + }, + name: 'limitation', + required: true, + type: 'integer', + }, + { + default: 'drop', + label: { + en_US: 'Strategy', + zh_CN: '策略', + }, + name: 'strategy', + options: [ + { + label: { + en_US: 'Drop', + zh_CN: '丢弃', + }, + name: 'drop', + }, + { + label: { + en_US: 'Wait', + zh_CN: '等待', + }, + name: 'wait', + }, + ], + required: true, + type: 'select', + }, + ], + label: { + en_US: 'Rate Limit', + zh_CN: '速率限制', + }, + name: 'rate-limit', + }, + ], + }, + { + label: { + en_US: 'AI Feature', + zh_CN: 'AI 能力', + }, + name: 'ai', + stages: [ + { + config: [ + { + default: 'local-agent', + label: { + en_US: 'Runner', + zh_CN: '运行器', + }, + name: 'runner', + options: [ + { + label: { + en_US: 'Embedded Agent', + zh_CN: '内置 Agent', + }, + name: 'local-agent', + }, + { + label: { + en_US: 'Dify Service API', + zh_CN: 'Dify 服务 API', + }, + name: 'dify-service-api', + }, + { + label: { + en_US: 'Aliyun Dashscope App API', + zh_CN: '阿里云百炼平台 API', + }, + name: 'dashscope-app-api', + }, + ], + required: true, + type: 'select', + }, + ], + label: { + en_US: 'Runner', + zh_CN: '运行方式', + }, + name: 'runner', + }, + { + config: [ + { + label: { + en_US: 'Model', + zh_CN: '模型', + }, + name: 'model', + required: true, + scope: '/provider/models/llm', + type: 'select', + }, + { + default: 10, + label: { + en_US: 'Max Round', + zh_CN: '最大回合数', + }, + name: 'max-round', + required: true, + type: 'integer', + }, + { + items: { + properties: { + content: { + type: 'string', + }, + role: { + default: 'user', + type: 'string', + }, + }, + type: 'object', + }, + label: { + en_US: 'Prompt', + zh_CN: '提示词', + }, + name: 'prompt', + required: true, + type: 'array', + }, + ], + description: { + en_US: 'Configure the embedded agent of the pipeline', + zh_CN: '配置内置 Agent', + }, + label: { + en_US: 'Embedded Agent', + zh_CN: '内置 Agent', + }, + name: 'local-agent', + }, + { + config: [ + { + label: { + en_US: 'Base URL', + zh_CN: '基础 URL', + }, + name: 'base-url', + required: true, + type: 'string', + }, + { + default: 'chat', + label: { + en_US: 'App Type', + zh_CN: '应用类型', + }, + name: 'app-type', + options: [ + { + label: { + en_US: 'Chat', + zh_CN: '聊天(包括Chatflow)', + }, + name: 'chat', + }, + { + label: { + en_US: 'Agent', + zh_CN: 'Agent', + }, + name: 'agent', + }, + { + label: { + en_US: 'Workflow', + zh_CN: '工作流', + }, + name: 'workflow', + }, + ], + required: true, + type: 'select', + }, + { + label: { + en_US: 'API Key', + zh_CN: 'API 密钥', + }, + name: 'api-key', + required: true, + type: 'string', + }, + { + default: 'plain', + label: { + en_US: 'CoT Convert', + zh_CN: '思维链转换策略', + }, + name: 'thinking-convert', + options: [ + { + label: { + en_US: 'Convert to ...', + zh_CN: '转换成 ...', + }, + name: 'plain', + }, + { + label: { + en_US: 'Original', + zh_CN: '原始', + }, + name: 'original', + }, + { + label: { + en_US: 'Remove', + zh_CN: '移除', + }, + name: 'remove', + }, + ], + required: true, + type: 'select', + }, + ], + description: { + en_US: 'Configure the Dify service API of the pipeline', + zh_CN: '配置 Dify 服务 API', + }, + label: { + en_US: 'Dify Service API', + zh_CN: 'Dify 服务 API', + }, + name: 'dify-service-api', + }, + { + config: [ + { + default: 'agent', + label: { + en_US: 'App Type', + zh_CN: '应用类型', + }, + name: 'app-type', + options: [ + { + label: { + en_US: 'Agent', + zh_CN: 'Agent', + }, + name: 'agent', + }, + { + label: { + en_US: 'Workflow', + zh_CN: '工作流', + }, + name: 'workflow', + }, + ], + required: true, + type: 'select', + }, + { + label: { + en_US: 'API Key', + zh_CN: 'API 密钥', + }, + name: 'api-key', + required: true, + type: 'string', + }, + { + label: { + en_US: 'App ID', + zh_CN: '应用 ID', + }, + name: 'app-id', + required: true, + type: 'string', + }, + { + default: '参考资料来自:', + label: { + en_US: 'References Quote', + zh_CN: '引用文本', + }, + name: 'references_quote', + required: false, + type: 'string', + }, + ], + description: { + en_US: 'Configure the Aliyun Dashscope App API of the pipeline', + zh_CN: '配置阿里云百炼平台 API', + }, + label: { + en_US: 'Aliyun Dashscope App API', + zh_CN: '阿里云百炼平台 API', + }, + name: 'dashscope-app-api', + }, + ], + }, + { + label: { + en_US: 'Output Processing', + zh_CN: '输出处理', + }, + name: 'output', + stages: [ + { + config: [ + { + default: 1000, + label: { + en_US: 'Threshold', + zh_CN: '阈值', + }, + name: 'threshold', + required: true, + type: 'integer', + }, + { + default: 'forward', + label: { + en_US: 'Strategy', + zh_CN: '策略', + }, + name: 'strategy', + options: [ + { + label: { + en_US: 'Forward Message Component', + zh_CN: '转发消息组件', + }, + name: 'forward', + }, + { + label: { + en_US: 'Convert to Image', + zh_CN: '转换为图片', + }, + name: 'image', + }, + ], + required: true, + type: 'select', + }, + { + default: '', + label: { + en_US: 'Font Path', + zh_CN: '字体路径', + }, + name: 'font-path', + required: true, + type: 'string', + }, + ], + label: { + en_US: 'Long Text Processing', + zh_CN: '长文本处理', + }, + name: 'long-text-processing', + }, + { + config: [ + { + default: 0, + label: { + en_US: 'Min', + zh_CN: '最小秒数', + }, + name: 'min', + required: true, + type: 'integer', + }, + { + default: 0, + label: { + en_US: 'Max', + zh_CN: '最大秒数', + }, + name: 'max', + required: true, + type: 'integer', + }, + ], + label: { + en_US: 'Force Delay', + zh_CN: '强制延迟', + }, + name: 'force-delay', + }, + { + config: [ + { + default: true, + label: { + en_US: 'Hide Exception', + zh_CN: '不输出异常信息给用户', + }, + name: 'hide-exception', + required: true, + type: 'boolean', + }, + { + default: true, + label: { + en_US: 'At Sender', + zh_CN: '在回复中@发送者', + }, + name: 'at-sender', + required: true, + type: 'boolean', + }, + { + default: false, + label: { + en_US: 'Quote Origin', + zh_CN: '引用原文', + }, + name: 'quote-origin', + required: true, + type: 'boolean', + }, + { + default: true, + label: { + en_US: 'Track Function Calls', + zh_CN: '跟踪函数调用', + }, + name: 'track-function-calls', + required: true, + type: 'boolean', + }, + ], + label: { + en_US: 'Misc', + zh_CN: '杂项', + }, + name: 'misc', + }, + ], + }, + ], + }, + msg: 'ok', + }; } - diff --git a/web/src/app/home/models/ICreateLLMField.ts b/web/src/app/home/models/ICreateLLMField.ts index 2c484f97..4ded490b 100644 --- a/web/src/app/home/models/ICreateLLMField.ts +++ b/web/src/app/home/models/ICreateLLMField.ts @@ -1,8 +1,8 @@ export interface ICreateLLMField { - name: string; - model_provider: string; - url: string; - api_key: string; - abilities: string[]; - extra_args: string[]; -} \ No newline at end of file + name: string; + model_provider: string; + url: string; + api_key: string; + abilities: string[]; + extra_args: string[]; +} diff --git a/web/src/app/home/models/component/llm-card/LLMCard.tsx b/web/src/app/home/models/component/llm-card/LLMCard.tsx index a7672445..96040fa9 100644 --- a/web/src/app/home/models/component/llm-card/LLMCard.tsx +++ b/web/src/app/home/models/component/llm-card/LLMCard.tsx @@ -1,33 +1,25 @@ -import styles from "../../LLMConfig.module.css" -import {LLMCardVO} from "@/app/home/models/component/llm-card/LLMCardVO"; +import styles from '../../LLMConfig.module.css'; +import { LLMCardVO } from '@/app/home/models/component/llm-card/LLMCardVO'; -export default function LLMCard({ - cardVO -}: { - cardVO: LLMCardVO -}) { - return ( -
- {/* icon和基本信息 */} -
- {/* icon */} -
- ICO -
- {/* bot基本信息 */} -
-
- {cardVO.name} -
-
- 厂商:{cardVO.company} -
-
-
- {/* URL和创建时间 */} -
- URL:{cardVO.URL} -
+export default function LLMCard({ cardVO }: { cardVO: LLMCardVO }) { + return ( +
+ {/* icon和基本信息 */} +
+ {/* icon */} +
ICO
+ {/* bot基本信息 */} +
+
+ {cardVO.name} +
+
+ 厂商:{cardVO.company} +
- ); -} \ No newline at end of file +
+ {/* URL和创建时间 */} +
URL:{cardVO.URL}
+
+ ); +} diff --git a/web/src/app/home/models/component/llm-card/LLMCardVO.ts b/web/src/app/home/models/component/llm-card/LLMCardVO.ts index 6d62cb3c..7f731fcd 100644 --- a/web/src/app/home/models/component/llm-card/LLMCardVO.ts +++ b/web/src/app/home/models/component/llm-card/LLMCardVO.ts @@ -1,21 +1,20 @@ export interface ILLMCardVO { - id: string; - name: string; - company: string; - URL: string; + id: string; + name: string; + company: string; + URL: string; } export class LLMCardVO implements ILLMCardVO { - id: string; - name: string; - company: string; - URL: string; + id: string; + name: string; + company: string; + URL: string; - constructor(props: ILLMCardVO) { - this.id = props.id; - this.name = props.name; - this.company = props.company; - this.URL = props.URL; - } - -} \ No newline at end of file + constructor(props: ILLMCardVO) { + this.id = props.id; + this.name = props.name; + this.company = props.company; + this.URL = props.URL; + } +} diff --git a/web/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts b/web/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts index dd97d284..5728c1ce 100644 --- a/web/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts +++ b/web/src/app/home/models/component/llm-form/ChooseAdapterEntity.ts @@ -1,4 +1,4 @@ export interface IChooseRequesterEntity { - label: string - value: string -} \ No newline at end of file + label: string; + value: string; +} diff --git a/web/src/app/home/models/component/llm-form/LLMForm.tsx b/web/src/app/home/models/component/llm-form/LLMForm.tsx index 4832cc6c..2768443a 100644 --- a/web/src/app/home/models/component/llm-form/LLMForm.tsx +++ b/web/src/app/home/models/component/llm-form/LLMForm.tsx @@ -1,18 +1,18 @@ -import styles from "@/app/home/models/LLMConfig.module.css"; -import { Button, Form, Input, Select, SelectProps, Space, Modal } from "antd"; -import { ICreateLLMField } from "@/app/home/models/ICreateLLMField"; -import { useEffect, useState } from "react"; -import { IChooseRequesterEntity } from "@/app/home/models/component/llm-form/ChooseAdapterEntity"; -import { httpClient } from "@/app/infra/http/HttpClient"; -import { LLMModel } from "@/app/infra/api/api-types"; -import { UUID } from "uuidjs"; +import styles from '@/app/home/models/LLMConfig.module.css'; +import { Button, Form, Input, Select, SelectProps, Space, Modal } from 'antd'; +import { ICreateLLMField } from '@/app/home/models/ICreateLLMField'; +import { useEffect, useState } from 'react'; +import { IChooseRequesterEntity } from '@/app/home/models/component/llm-form/ChooseAdapterEntity'; +import { httpClient } from '@/app/infra/http/HttpClient'; +import { LLMModel } from '@/app/infra/api/api-types'; +import { UUID } from 'uuidjs'; export default function LLMForm({ editMode, initLLMId, onFormSubmit, onFormCancel, - onLLMDeleted + onLLMDeleted, }: { editMode: boolean; initLLMId?: string; @@ -21,18 +21,18 @@ export default function LLMForm({ onLLMDeleted: () => void; }) { const [form] = Form.useForm(); - const extraOptions: SelectProps["options"] = []; + const extraOptions: SelectProps['options'] = []; const [initValue] = useState(); const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); - const abilityOptions: SelectProps["options"] = [ + const abilityOptions: SelectProps['options'] = [ { - label: "函数调用", - value: "func_call" + label: '函数调用', + value: 'func_call', }, { - label: "图像识别", - value: "vision" - } + label: '图像识别', + value: 'vision', + }, ]; const [requesterNameList, setRequesterNameList] = useState< IChooseRequesterEntity[] @@ -56,9 +56,9 @@ export default function LLMForm({ requesterNameList.requesters.map((item) => { return { label: item.label.zh_CN, - value: item.name + value: item.name, }; - }) + }), ); } @@ -76,7 +76,7 @@ export default function LLMForm({ url: llmModel.model.requester_config?.base_url, api_key: llmModel.model.api_keys[0], abilities: llmModel.model.abilities, - extra_args: fakeExtraArgs + extra_args: fakeExtraArgs, }; } @@ -110,19 +110,19 @@ export default function LLMForm({ // } function onCreateLLM(value: ICreateLLMField) { - console.log("create llm", value); + console.log('create llm', value); const requestParam: LLMModel = { uuid: UUID.generate(), name: value.name, - description: "", + description: '', requester: value.model_provider, requester_config: { base_url: value.url, - timeout: 120 + timeout: 120, }, extra_args: value.extra_args, api_keys: [value.api_key], - abilities: value.abilities + abilities: value.abilities, // created_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', // updated_at: 'Sun Apr 27 2025 21:56:35 GMT+0800', }; @@ -145,15 +145,15 @@ export default function LLMForm({
setShowDeleteConfirmModal(false)} footer={