From 40aec98cf00482c0fcd737a0015ee03cdc0c8b19 Mon Sep 17 00:00:00 2001 From: suifeng <369202865@qq.com> Date: Sun, 24 Aug 2025 21:57:28 +0800 Subject: [PATCH 1/2] =?UTF-8?q?[dev]=20=E4=BB=8E=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E4=B8=AD=E7=A7=BB=E9=99=A4=E6=8E=89sf-chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prompto-lab-app/SF-Chain/pom.xml | 180 + .../SF-Chain/src/main/frontend/.editorconfig | 9 + .../SF-Chain/src/main/frontend/.gitattributes | 1 + .../SF-Chain/src/main/frontend/.gitignore | 30 + .../src/main/frontend/.prettierrc.json | 6 + .../main/frontend/.vite/deps/_metadata.json | 31 + .../frontend/.vite/deps/chunk-IDACCYAP.js | 12619 ++++++++++++++++ .../frontend/.vite/deps/chunk-IDACCYAP.js.map | 7 + .../src/main/frontend/.vite/deps/package.json | 3 + .../src/main/frontend/.vite/deps/pinia.js | 5891 ++++++++ .../src/main/frontend/.vite/deps/pinia.js.map | 7 + .../main/frontend/.vite/deps/vue-router.js | 2936 ++++ .../frontend/.vite/deps/vue-router.js.map | 7 + .../src/main/frontend/.vite/deps/vue.js | 343 + .../src/main/frontend/.vite/deps/vue.js.map | 7 + .../SF-Chain/src/main/frontend/Dockerfile | 24 + .../SF-Chain/src/main/frontend/README.md | 39 + .../SF-Chain/src/main/frontend/env.d.ts | 1 + .../src/main/frontend/eslint.config.ts | 22 + .../SF-Chain/src/main/frontend/index.html | 15 + .../SF-Chain/src/main/frontend/nginx-vue.conf | 20 + .../src/main/frontend/package-lock.json | 5535 +++++++ .../SF-Chain/src/main/frontend/package.json | 43 + .../src/main/frontend/public/favicon.ico | Bin 0 -> 4286 bytes .../main/frontend/public}/icons/anthropic.svg | 0 .../main/frontend/public}/icons/deepseek.svg | 0 .../main/frontend/public}/icons/default.svg | 0 .../main/frontend/public}/icons/doubao.svg | 0 .../main/frontend/public}/icons/google.svg | 0 .../main/frontend/public}/icons/openai.svg | 0 .../main/frontend/public}/icons/qianwen.svg | 0 .../SF-Chain/src/main/frontend/src/App.vue | 544 + .../src/main/frontend/src/assets/base.css | 86 + .../src/main/frontend/src/assets/logo.svg | 1 + .../src/main/frontend/src/assets/main.css | 82 + .../src/components/AICallLogViewer.vue | 855 ++ .../frontend/src/components/AiNodeConfig.vue | 1644 ++ .../frontend/src/components/ApiInfoConfig.vue | 1545 ++ .../frontend/src/components/HeaderBar.vue | 564 + .../frontend/src/components/JsonViewer.vue | 559 + .../src/components/LogDetailModal.vue | 1373 ++ .../src/components/SystemManagement.vue | 286 + .../main/frontend/src/components/Toast.vue | 546 + .../SF-Chain/src/main/frontend/src/main.ts | 22 + .../src/main/frontend/src/router/index.ts | 15 + .../frontend/src/services/aiCallLogApi.ts | 53 + .../main/frontend/src/services/aiModelApi.ts | 49 + .../frontend/src/services/aiOperationApi.ts | 55 + .../main/frontend/src/services/apiConfig.ts | 65 + .../main/frontend/src/services/apiUtils.ts | 263 + .../main/frontend/src/services/systemApi.ts | 37 + .../src/main/frontend/src/types/system.ts | 127 + .../main/frontend/src/utils/aiProviders.ts | 215 + .../src/main/frontend/src/utils/toast.ts | 189 + .../src/main/frontend/tsconfig.app.json | 12 + .../SF-Chain/src/main/frontend/tsconfig.json | 11 + .../src/main/frontend/tsconfig.node.json | 19 + .../SF-Chain/src/main/frontend/vite.config.ts | 18 + .../main/resources/META-INF/spring.factories | 3 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../src/main/resources/migration/v1_mysql.sql | 63 + .../resources/migration/v1_postgresql.sql} | 30 +- .../resources/static/assets/index-Cp61OaZJ.js | 75 + .../static/assets/index-DtzThp8m.css | 1 + .../src/main/resources/static/favicon.ico | Bin 0 -> 4286 bytes .../main/resources/static/icons/anthropic.svg | 1 + .../main/resources/static/icons/deepseek.svg | 1 + .../main/resources/static/icons/default.svg | 1 + .../main/resources/static/icons/doubao.svg | 1 + .../main/resources/static/icons/google.svg | 1 + .../main/resources/static/icons/openai.svg | 1 + .../main/resources/static/icons/qianwen.svg | 1 + .../src/main/resources/static/index.html | 16 + prompto-lab-app/pom.xml | 7 + .../timemachinelab/core/model/PromptAI.java | 2 +- .../application/ConversationService.java | 2 +- .../ai/ConversationOperation.java | 4 +- .../ai/QuestionGenerationOperation.java | 4 +- .../sfchain/annotation/AIOp.java | 71 - .../config/OpenAIAutoConfiguration.java | 74 - .../sfchain/config/OpenAIModelsConfig.java | 133 - .../constants/AIOperationConstant.java | 28 - .../controller/AICallLogController.java | 72 - .../sfchain/controller/AIModelController.java | 244 - .../controller/AIOperationController.java | 281 - .../controller/AISystemController.java | 131 - .../timemachinelab/sfchain/core/AIModel.java | 40 - .../sfchain/core/AIOperationRegistry.java | 152 - .../sfchain/core/AIPromptBuilder.java | 155 - .../sfchain/core/AIService.java | 457 - .../sfchain/core/BaseAIOperation.java | 540 - .../sfchain/core/ModelRegistry.java | 71 - .../sfchain/core/logging/AICallLog.java | 71 - .../sfchain/core/logging/AICallLogAspect.java | 76 - .../core/logging/AICallLogManager.java | 238 - .../core/logging/AICallLogSummary.java | 81 - .../core/openai/OpenAICompatibleModel.java | 155 - .../sfchain/core/openai/OpenAIHttpClient.java | 143 - .../core/openai/OpenAIModelConfig.java | 120 - .../core/openai/OpenAIModelFactory.java | 79 - .../sfchain/core/openai/OpenAIRequest.java | 102 - .../sfchain/core/openai/OpenAIResponse.java | 108 - .../operations/JSONRepairOperation.java | 140 - .../operations/ModelValidationOperation.java | 86 - .../DynamicOperationConfigService.java | 82 - .../sfchain/persistence/ModelConfigData.java | 125 - .../persistence/OperationConfigData.java | 143 - .../persistence/PersistenceManager.java | 669 - .../persistence/PersistenceService.java | 127 - .../PostgreSQLPersistenceService.java | 319 - .../context/ChatContextService.java | 87 - .../persistence/context/ChatMessage.java | 62 - .../context/MapBasedChatContextService.java | 177 - .../persistence/entity/ModelConfigEntity.java | 73 - .../entity/OperationConfigEntity.java | 82 - .../repository/ModelConfigRepository.java | 68 - .../repository/OperationConfigRepository.java | 75 - .../src/main/resources/application.yml | 11 + .../testnode/ainode/AIChatOperation.java | 5 +- 119 files changed, 37219 insertions(+), 5960 deletions(-) create mode 100644 prompto-lab-app/SF-Chain/pom.xml create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.editorconfig create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.gitattributes create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.gitignore create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.prettierrc.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/_metadata.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/chunk-IDACCYAP.js create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/chunk-IDACCYAP.js.map create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/package.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/pinia.js create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/pinia.js.map create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/vue-router.js create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/vue-router.js.map create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/vue.js create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/vue.js.map create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/Dockerfile create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/README.md create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/env.d.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/eslint.config.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/index.html create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/nginx-vue.conf create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/package-lock.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/package.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/public/favicon.ico rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/anthropic.svg (100%) rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/deepseek.svg (100%) rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/default.svg (100%) rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/doubao.svg (100%) rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/google.svg (100%) rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/openai.svg (100%) rename {prompto-lab-ui/src/assets => prompto-lab-app/SF-Chain/src/main/frontend/public}/icons/qianwen.svg (100%) create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/App.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/assets/base.css create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/assets/logo.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/assets/main.css create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/AICallLogViewer.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/AiNodeConfig.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/ApiInfoConfig.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/HeaderBar.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/JsonViewer.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/LogDetailModal.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/SystemManagement.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/components/Toast.vue create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/main.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/router/index.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiCallLogApi.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiModelApi.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiOperationApi.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiConfig.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiUtils.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/services/systemApi.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/types/system.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/utils/aiProviders.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/src/utils/toast.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.app.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.node.json create mode 100644 prompto-lab-app/SF-Chain/src/main/frontend/vite.config.ts create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring.factories create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/migration/v1_mysql.sql rename prompto-lab-app/{src/main/resources/db/migration/V1__Create_AI_Tables.sql => SF-Chain/src/main/resources/migration/v1_postgresql.sql} (61%) create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-Cp61OaZJ.js create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-DtzThp8m.css create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/favicon.ico create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/anthropic.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/deepseek.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/default.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/doubao.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/google.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/openai.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/icons/qianwen.svg create mode 100644 prompto-lab-app/SF-Chain/src/main/resources/static/index.html delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/annotation/AIOp.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIAutoConfiguration.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIModelsConfig.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/constants/AIOperationConstant.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AICallLogController.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIModel.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIOperationRegistry.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIPromptBuilder.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIService.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/BaseAIOperation.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/ModelRegistry.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLog.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogAspect.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogManager.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogSummary.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAICompatibleModel.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIHttpClient.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelConfig.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelFactory.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIRequest.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIResponse.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/operations/JSONRepairOperation.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/operations/ModelValidationOperation.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/DynamicOperationConfigService.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/ModelConfigData.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/OperationConfigData.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceManager.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceService.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PostgreSQLPersistenceService.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatContextService.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatMessage.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/MapBasedChatContextService.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/ModelConfigEntity.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/OperationConfigEntity.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/ModelConfigRepository.java delete mode 100644 prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/OperationConfigRepository.java diff --git a/prompto-lab-app/SF-Chain/pom.xml b/prompto-lab-app/SF-Chain/pom.xml new file mode 100644 index 0000000..ff5f646 --- /dev/null +++ b/prompto-lab-app/SF-Chain/pom.xml @@ -0,0 +1,180 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.14 + + + + com.suifeng + sf-chain + 1.0.0 + jar + SF-Chain Framework + SF-Chain AI Framework - A reusable AI operation framework + + + 17 + 17 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-autoconfigure + + + + + org.projectlombok + lombok + true + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework + spring-aspects + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.junit.jupiter + junit-jupiter + test + + + + + com.vladmihalcea + hibernate-types-52 + 2.21.1 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.40 + + + + + com.mysql + mysql-connector-j + runtime + + + + + org.postgresql + postgresql + runtime + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + org.projectlombok + lombok + ${lombok.version} + + + org.springframework.boot + spring-boot-configuration-processor + 2.7.14 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + + copy-frontend-resources + process-resources + + copy-resources + + + ${project.build.outputDirectory}/static + + + src/main/frontend/dist + false + + + + + + + + + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/.editorconfig b/prompto-lab-app/SF-Chain/src/main/frontend/.editorconfig new file mode 100644 index 0000000..5a5809d --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/.editorconfig @@ -0,0 +1,9 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +end_of_line = lf +max_line_length = 100 diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/.gitattributes b/prompto-lab-app/SF-Chain/src/main/frontend/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/.gitignore b/prompto-lab-app/SF-Chain/src/main/frontend/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/.prettierrc.json b/prompto-lab-app/SF-Chain/src/main/frontend/.prettierrc.json new file mode 100644 index 0000000..29a2402 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/_metadata.json b/prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/_metadata.json new file mode 100644 index 0000000..81b3959 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/_metadata.json @@ -0,0 +1,31 @@ +{ + "hash": "09d8b648", + "configHash": "bfd8a383", + "lockfileHash": "ca82ab84", + "browserHash": "0deaf2f1", + "optimized": { + "vue": { + "src": "../../node_modules/vue/dist/vue.runtime.esm-bundler.js", + "file": "vue.js", + "fileHash": "12257286", + "needsInterop": false + }, + "pinia": { + "src": "../../node_modules/pinia/dist/pinia.mjs", + "file": "pinia.js", + "fileHash": "93a896c9", + "needsInterop": false + }, + "vue-router": { + "src": "../../node_modules/vue-router/dist/vue-router.mjs", + "file": "vue-router.js", + "fileHash": "a0b9ef41", + "needsInterop": false + } + }, + "chunks": { + "chunk-IDACCYAP": { + "file": "chunk-IDACCYAP.js" + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/chunk-IDACCYAP.js b/prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/chunk-IDACCYAP.js new file mode 100644 index 0000000..e143287 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/.vite/deps/chunk-IDACCYAP.js @@ -0,0 +1,12619 @@ +// node_modules/@vue/shared/dist/shared.esm-bundler.js +function makeMap(str) { + const map2 = /* @__PURE__ */ Object.create(null); + for (const key of str.split(",")) map2[key] = 1; + return (val) => val in map2; +} +var EMPTY_OBJ = true ? Object.freeze({}) : {}; +var EMPTY_ARR = true ? Object.freeze([]) : []; +var NOOP = () => { +}; +var NO = () => false; +var isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter +(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97); +var isModelListener = (key) => key.startsWith("onUpdate:"); +var extend = Object.assign; +var remove = (arr, el) => { + const i = arr.indexOf(el); + if (i > -1) { + arr.splice(i, 1); + } +}; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var hasOwn = (val, key) => hasOwnProperty.call(val, key); +var isArray = Array.isArray; +var isMap = (val) => toTypeString(val) === "[object Map]"; +var isSet = (val) => toTypeString(val) === "[object Set]"; +var isDate = (val) => toTypeString(val) === "[object Date]"; +var isRegExp = (val) => toTypeString(val) === "[object RegExp]"; +var isFunction = (val) => typeof val === "function"; +var isString = (val) => typeof val === "string"; +var isSymbol = (val) => typeof val === "symbol"; +var isObject = (val) => val !== null && typeof val === "object"; +var isPromise = (val) => { + return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch); +}; +var objectToString = Object.prototype.toString; +var toTypeString = (value) => objectToString.call(value); +var toRawType = (value) => { + return toTypeString(value).slice(8, -1); +}; +var isPlainObject = (val) => toTypeString(val) === "[object Object]"; +var isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key; +var isReservedProp = makeMap( + // the leading comma is intentional so empty string "" is also included + ",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted" +); +var isBuiltInDirective = makeMap( + "bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo" +); +var cacheStringFunction = (fn) => { + const cache = /* @__PURE__ */ Object.create(null); + return (str) => { + const hit = cache[str]; + return hit || (cache[str] = fn(str)); + }; +}; +var camelizeRE = /-(\w)/g; +var camelize = cacheStringFunction( + (str) => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); + } +); +var hyphenateRE = /\B([A-Z])/g; +var hyphenate = cacheStringFunction( + (str) => str.replace(hyphenateRE, "-$1").toLowerCase() +); +var capitalize = cacheStringFunction((str) => { + return str.charAt(0).toUpperCase() + str.slice(1); +}); +var toHandlerKey = cacheStringFunction( + (str) => { + const s = str ? `on${capitalize(str)}` : ``; + return s; + } +); +var hasChanged = (value, oldValue) => !Object.is(value, oldValue); +var invokeArrayFns = (fns, ...arg) => { + for (let i = 0; i < fns.length; i++) { + fns[i](...arg); + } +}; +var def = (obj, key, value, writable = false) => { + Object.defineProperty(obj, key, { + configurable: true, + enumerable: false, + writable, + value + }); +}; +var looseToNumber = (val) => { + const n = parseFloat(val); + return isNaN(n) ? val : n; +}; +var toNumber = (val) => { + const n = isString(val) ? Number(val) : NaN; + return isNaN(n) ? val : n; +}; +var _globalThis; +var getGlobalThis = () => { + return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}); +}; +var GLOBALS_ALLOWED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol"; +var isGloballyAllowed = makeMap(GLOBALS_ALLOWED); +function normalizeStyle(value) { + if (isArray(value)) { + const res = {}; + for (let i = 0; i < value.length; i++) { + const item = value[i]; + const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item); + if (normalized) { + for (const key in normalized) { + res[key] = normalized[key]; + } + } + } + return res; + } else if (isString(value) || isObject(value)) { + return value; + } +} +var listDelimiterRE = /;(?![^(]*\))/g; +var propertyDelimiterRE = /:([^]+)/; +var styleCommentRE = /\/\*[^]*?\*\//g; +function parseStringStyle(cssText) { + const ret = {}; + cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => { + if (item) { + const tmp = item.split(propertyDelimiterRE); + tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim()); + } + }); + return ret; +} +function stringifyStyle(styles) { + if (!styles) return ""; + if (isString(styles)) return styles; + let ret = ""; + for (const key in styles) { + const value = styles[key]; + if (isString(value) || typeof value === "number") { + const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key); + ret += `${normalizedKey}:${value};`; + } + } + return ret; +} +function normalizeClass(value) { + let res = ""; + if (isString(value)) { + res = value; + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + const normalized = normalizeClass(value[i]); + if (normalized) { + res += normalized + " "; + } + } + } else if (isObject(value)) { + for (const name in value) { + if (value[name]) { + res += name + " "; + } + } + } + return res.trim(); +} +function normalizeProps(props) { + if (!props) return null; + let { class: klass, style } = props; + if (klass && !isString(klass)) { + props.class = normalizeClass(klass); + } + if (style) { + props.style = normalizeStyle(style); + } + return props; +} +var HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot"; +var SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view"; +var MATH_TAGS = "annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics"; +var VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr"; +var isHTMLTag = makeMap(HTML_TAGS); +var isSVGTag = makeMap(SVG_TAGS); +var isMathMLTag = makeMap(MATH_TAGS); +var isVoidTag = makeMap(VOID_TAGS); +var specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`; +var isSpecialBooleanAttr = makeMap(specialBooleanAttrs); +var isBooleanAttr = makeMap( + specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected` +); +function includeBooleanAttr(value) { + return !!value || value === ""; +} +var isKnownHtmlAttr = makeMap( + `accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap` +); +var isKnownSvgAttr = makeMap( + `xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan` +); +var isKnownMathMLAttr = makeMap( + `accent,accentunder,actiontype,align,alignmentscope,altimg,altimg-height,altimg-valign,altimg-width,alttext,bevelled,close,columnsalign,columnlines,columnspan,denomalign,depth,dir,display,displaystyle,encoding,equalcolumns,equalrows,fence,fontstyle,fontweight,form,frame,framespacing,groupalign,height,href,id,indentalign,indentalignfirst,indentalignlast,indentshift,indentshiftfirst,indentshiftlast,indextype,justify,largetop,largeop,lquote,lspace,mathbackground,mathcolor,mathsize,mathvariant,maxsize,minlabelspacing,mode,other,overflow,position,rowalign,rowlines,rowspan,rquote,rspace,scriptlevel,scriptminsize,scriptsizemultiplier,selection,separator,separators,shift,side,src,stackalign,stretchy,subscriptshift,superscriptshift,symmetric,voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns` +); +function isRenderableAttrValue(value) { + if (value == null) { + return false; + } + const type = typeof value; + return type === "string" || type === "number" || type === "boolean"; +} +var cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g; +function getEscapedCssVarName(key, doubleEscape) { + return key.replace( + cssVarNameEscapeSymbolsRE, + (s) => doubleEscape ? s === '"' ? '\\\\\\"' : `\\\\${s}` : `\\${s}` + ); +} +function looseCompareArrays(a, b) { + if (a.length !== b.length) return false; + let equal = true; + for (let i = 0; equal && i < a.length; i++) { + equal = looseEqual(a[i], b[i]); + } + return equal; +} +function looseEqual(a, b) { + if (a === b) return true; + let aValidType = isDate(a); + let bValidType = isDate(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? a.getTime() === b.getTime() : false; + } + aValidType = isSymbol(a); + bValidType = isSymbol(b); + if (aValidType || bValidType) { + return a === b; + } + aValidType = isArray(a); + bValidType = isArray(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? looseCompareArrays(a, b) : false; + } + aValidType = isObject(a); + bValidType = isObject(b); + if (aValidType || bValidType) { + if (!aValidType || !bValidType) { + return false; + } + const aKeysCount = Object.keys(a).length; + const bKeysCount = Object.keys(b).length; + if (aKeysCount !== bKeysCount) { + return false; + } + for (const key in a) { + const aHasKey = a.hasOwnProperty(key); + const bHasKey = b.hasOwnProperty(key); + if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) { + return false; + } + } + } + return String(a) === String(b); +} +function looseIndexOf(arr, val) { + return arr.findIndex((item) => looseEqual(item, val)); +} +var isRef = (val) => { + return !!(val && val["__v_isRef"] === true); +}; +var toDisplayString = (val) => { + return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val); +}; +var replacer = (_key, val) => { + if (isRef(val)) { + return replacer(_key, val.value); + } else if (isMap(val)) { + return { + [`Map(${val.size})`]: [...val.entries()].reduce( + (entries, [key, val2], i) => { + entries[stringifySymbol(key, i) + " =>"] = val2; + return entries; + }, + {} + ) + }; + } else if (isSet(val)) { + return { + [`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v)) + }; + } else if (isSymbol(val)) { + return stringifySymbol(val); + } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) { + return String(val); + } + return val; +}; +var stringifySymbol = (v, i = "") => { + var _a; + return ( + // Symbol.description in es2019+ so we need to cast here to pass + // the lib: es2016 check + isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v + ); +}; + +// node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js +function warn(msg, ...args) { + console.warn(`[Vue warn] ${msg}`, ...args); +} +var activeEffectScope; +var EffectScope = class { + constructor(detached = false) { + this.detached = detached; + this._active = true; + this._on = 0; + this.effects = []; + this.cleanups = []; + this._isPaused = false; + this.parent = activeEffectScope; + if (!detached && activeEffectScope) { + this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push( + this + ) - 1; + } + } + get active() { + return this._active; + } + pause() { + if (this._active) { + this._isPaused = true; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].pause(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].pause(); + } + } + } + /** + * Resumes the effect scope, including all child scopes and effects. + */ + resume() { + if (this._active) { + if (this._isPaused) { + this._isPaused = false; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].resume(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].resume(); + } + } + } + } + run(fn) { + if (this._active) { + const currentEffectScope = activeEffectScope; + try { + activeEffectScope = this; + return fn(); + } finally { + activeEffectScope = currentEffectScope; + } + } else if (true) { + warn(`cannot run an inactive effect scope.`); + } + } + /** + * This should only be called on non-detached scopes + * @internal + */ + on() { + if (++this._on === 1) { + this.prevScope = activeEffectScope; + activeEffectScope = this; + } + } + /** + * This should only be called on non-detached scopes + * @internal + */ + off() { + if (this._on > 0 && --this._on === 0) { + activeEffectScope = this.prevScope; + this.prevScope = void 0; + } + } + stop(fromParent) { + if (this._active) { + this._active = false; + let i, l; + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].stop(); + } + this.effects.length = 0; + for (i = 0, l = this.cleanups.length; i < l; i++) { + this.cleanups[i](); + } + this.cleanups.length = 0; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].stop(true); + } + this.scopes.length = 0; + } + if (!this.detached && this.parent && !fromParent) { + const last = this.parent.scopes.pop(); + if (last && last !== this) { + this.parent.scopes[this.index] = last; + last.index = this.index; + } + } + this.parent = void 0; + } + } +}; +function effectScope(detached) { + return new EffectScope(detached); +} +function getCurrentScope() { + return activeEffectScope; +} +function onScopeDispose(fn, failSilently = false) { + if (activeEffectScope) { + activeEffectScope.cleanups.push(fn); + } else if (!failSilently) { + warn( + `onScopeDispose() is called when there is no active effect scope to be associated with.` + ); + } +} +var activeSub; +var pausedQueueEffects = /* @__PURE__ */ new WeakSet(); +var ReactiveEffect = class { + constructor(fn) { + this.fn = fn; + this.deps = void 0; + this.depsTail = void 0; + this.flags = 1 | 4; + this.next = void 0; + this.cleanup = void 0; + this.scheduler = void 0; + if (activeEffectScope && activeEffectScope.active) { + activeEffectScope.effects.push(this); + } + } + pause() { + this.flags |= 64; + } + resume() { + if (this.flags & 64) { + this.flags &= -65; + if (pausedQueueEffects.has(this)) { + pausedQueueEffects.delete(this); + this.trigger(); + } + } + } + /** + * @internal + */ + notify() { + if (this.flags & 2 && !(this.flags & 32)) { + return; + } + if (!(this.flags & 8)) { + batch(this); + } + } + run() { + if (!(this.flags & 1)) { + return this.fn(); + } + this.flags |= 2; + cleanupEffect(this); + prepareDeps(this); + const prevEffect = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = this; + shouldTrack = true; + try { + return this.fn(); + } finally { + if (activeSub !== this) { + warn( + "Active effect was not restored correctly - this is likely a Vue internal bug." + ); + } + cleanupDeps(this); + activeSub = prevEffect; + shouldTrack = prevShouldTrack; + this.flags &= -3; + } + } + stop() { + if (this.flags & 1) { + for (let link = this.deps; link; link = link.nextDep) { + removeSub(link); + } + this.deps = this.depsTail = void 0; + cleanupEffect(this); + this.onStop && this.onStop(); + this.flags &= -2; + } + } + trigger() { + if (this.flags & 64) { + pausedQueueEffects.add(this); + } else if (this.scheduler) { + this.scheduler(); + } else { + this.runIfDirty(); + } + } + /** + * @internal + */ + runIfDirty() { + if (isDirty(this)) { + this.run(); + } + } + get dirty() { + return isDirty(this); + } +}; +var batchDepth = 0; +var batchedSub; +var batchedComputed; +function batch(sub, isComputed = false) { + sub.flags |= 8; + if (isComputed) { + sub.next = batchedComputed; + batchedComputed = sub; + return; + } + sub.next = batchedSub; + batchedSub = sub; +} +function startBatch() { + batchDepth++; +} +function endBatch() { + if (--batchDepth > 0) { + return; + } + if (batchedComputed) { + let e = batchedComputed; + batchedComputed = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= -9; + e = next; + } + } + let error; + while (batchedSub) { + let e = batchedSub; + batchedSub = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= -9; + if (e.flags & 1) { + try { + ; + e.trigger(); + } catch (err) { + if (!error) error = err; + } + } + e = next; + } + } + if (error) throw error; +} +function prepareDeps(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + link.version = -1; + link.prevActiveLink = link.dep.activeLink; + link.dep.activeLink = link; + } +} +function cleanupDeps(sub) { + let head; + let tail = sub.depsTail; + let link = tail; + while (link) { + const prev = link.prevDep; + if (link.version === -1) { + if (link === tail) tail = prev; + removeSub(link); + removeDep(link); + } else { + head = link; + } + link.dep.activeLink = link.prevActiveLink; + link.prevActiveLink = void 0; + link = prev; + } + sub.deps = head; + sub.depsTail = tail; +} +function isDirty(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + if (link.dep.version !== link.version || link.dep.computed && (refreshComputed(link.dep.computed) || link.dep.version !== link.version)) { + return true; + } + } + if (sub._dirty) { + return true; + } + return false; +} +function refreshComputed(computed3) { + if (computed3.flags & 4 && !(computed3.flags & 16)) { + return; + } + computed3.flags &= -17; + if (computed3.globalVersion === globalVersion) { + return; + } + computed3.globalVersion = globalVersion; + if (!computed3.isSSR && computed3.flags & 128 && (!computed3.deps && !computed3._dirty || !isDirty(computed3))) { + return; + } + computed3.flags |= 2; + const dep = computed3.dep; + const prevSub = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = computed3; + shouldTrack = true; + try { + prepareDeps(computed3); + const value = computed3.fn(computed3._value); + if (dep.version === 0 || hasChanged(value, computed3._value)) { + computed3.flags |= 128; + computed3._value = value; + dep.version++; + } + } catch (err) { + dep.version++; + throw err; + } finally { + activeSub = prevSub; + shouldTrack = prevShouldTrack; + cleanupDeps(computed3); + computed3.flags &= -3; + } +} +function removeSub(link, soft = false) { + const { dep, prevSub, nextSub } = link; + if (prevSub) { + prevSub.nextSub = nextSub; + link.prevSub = void 0; + } + if (nextSub) { + nextSub.prevSub = prevSub; + link.nextSub = void 0; + } + if (dep.subsHead === link) { + dep.subsHead = nextSub; + } + if (dep.subs === link) { + dep.subs = prevSub; + if (!prevSub && dep.computed) { + dep.computed.flags &= -5; + for (let l = dep.computed.deps; l; l = l.nextDep) { + removeSub(l, true); + } + } + } + if (!soft && !--dep.sc && dep.map) { + dep.map.delete(dep.key); + } +} +function removeDep(link) { + const { prevDep, nextDep } = link; + if (prevDep) { + prevDep.nextDep = nextDep; + link.prevDep = void 0; + } + if (nextDep) { + nextDep.prevDep = prevDep; + link.nextDep = void 0; + } +} +function effect(fn, options) { + if (fn.effect instanceof ReactiveEffect) { + fn = fn.effect.fn; + } + const e = new ReactiveEffect(fn); + if (options) { + extend(e, options); + } + try { + e.run(); + } catch (err) { + e.stop(); + throw err; + } + const runner = e.run.bind(e); + runner.effect = e; + return runner; +} +function stop(runner) { + runner.effect.stop(); +} +var shouldTrack = true; +var trackStack = []; +function pauseTracking() { + trackStack.push(shouldTrack); + shouldTrack = false; +} +function resetTracking() { + const last = trackStack.pop(); + shouldTrack = last === void 0 ? true : last; +} +function cleanupEffect(e) { + const { cleanup } = e; + e.cleanup = void 0; + if (cleanup) { + const prevSub = activeSub; + activeSub = void 0; + try { + cleanup(); + } finally { + activeSub = prevSub; + } + } +} +var globalVersion = 0; +var Link = class { + constructor(sub, dep) { + this.sub = sub; + this.dep = dep; + this.version = dep.version; + this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0; + } +}; +var Dep = class { + // TODO isolatedDeclarations "__v_skip" + constructor(computed3) { + this.computed = computed3; + this.version = 0; + this.activeLink = void 0; + this.subs = void 0; + this.map = void 0; + this.key = void 0; + this.sc = 0; + this.__v_skip = true; + if (true) { + this.subsHead = void 0; + } + } + track(debugInfo) { + if (!activeSub || !shouldTrack || activeSub === this.computed) { + return; + } + let link = this.activeLink; + if (link === void 0 || link.sub !== activeSub) { + link = this.activeLink = new Link(activeSub, this); + if (!activeSub.deps) { + activeSub.deps = activeSub.depsTail = link; + } else { + link.prevDep = activeSub.depsTail; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + } + addSub(link); + } else if (link.version === -1) { + link.version = this.version; + if (link.nextDep) { + const next = link.nextDep; + next.prevDep = link.prevDep; + if (link.prevDep) { + link.prevDep.nextDep = next; + } + link.prevDep = activeSub.depsTail; + link.nextDep = void 0; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + if (activeSub.deps === link) { + activeSub.deps = next; + } + } + } + if (activeSub.onTrack) { + activeSub.onTrack( + extend( + { + effect: activeSub + }, + debugInfo + ) + ); + } + return link; + } + trigger(debugInfo) { + this.version++; + globalVersion++; + this.notify(debugInfo); + } + notify(debugInfo) { + startBatch(); + try { + if (true) { + for (let head = this.subsHead; head; head = head.nextSub) { + if (head.sub.onTrigger && !(head.sub.flags & 8)) { + head.sub.onTrigger( + extend( + { + effect: head.sub + }, + debugInfo + ) + ); + } + } + } + for (let link = this.subs; link; link = link.prevSub) { + if (link.sub.notify()) { + ; + link.sub.dep.notify(); + } + } + } finally { + endBatch(); + } + } +}; +function addSub(link) { + link.dep.sc++; + if (link.sub.flags & 4) { + const computed3 = link.dep.computed; + if (computed3 && !link.dep.subs) { + computed3.flags |= 4 | 16; + for (let l = computed3.deps; l; l = l.nextDep) { + addSub(l); + } + } + const currentTail = link.dep.subs; + if (currentTail !== link) { + link.prevSub = currentTail; + if (currentTail) currentTail.nextSub = link; + } + if (link.dep.subsHead === void 0) { + link.dep.subsHead = link; + } + link.dep.subs = link; + } +} +var targetMap = /* @__PURE__ */ new WeakMap(); +var ITERATE_KEY = Symbol( + true ? "Object iterate" : "" +); +var MAP_KEY_ITERATE_KEY = Symbol( + true ? "Map keys iterate" : "" +); +var ARRAY_ITERATE_KEY = Symbol( + true ? "Array iterate" : "" +); +function track(target, type, key) { + if (shouldTrack && activeSub) { + let depsMap = targetMap.get(target); + if (!depsMap) { + targetMap.set(target, depsMap = /* @__PURE__ */ new Map()); + } + let dep = depsMap.get(key); + if (!dep) { + depsMap.set(key, dep = new Dep()); + dep.map = depsMap; + dep.key = key; + } + if (true) { + dep.track({ + target, + type, + key + }); + } else { + dep.track(); + } + } +} +function trigger(target, type, key, newValue, oldValue, oldTarget) { + const depsMap = targetMap.get(target); + if (!depsMap) { + globalVersion++; + return; + } + const run = (dep) => { + if (dep) { + if (true) { + dep.trigger({ + target, + type, + key, + newValue, + oldValue, + oldTarget + }); + } else { + dep.trigger(); + } + } + }; + startBatch(); + if (type === "clear") { + depsMap.forEach(run); + } else { + const targetIsArray = isArray(target); + const isArrayIndex = targetIsArray && isIntegerKey(key); + if (targetIsArray && key === "length") { + const newLength = Number(newValue); + depsMap.forEach((dep, key2) => { + if (key2 === "length" || key2 === ARRAY_ITERATE_KEY || !isSymbol(key2) && key2 >= newLength) { + run(dep); + } + }); + } else { + if (key !== void 0 || depsMap.has(void 0)) { + run(depsMap.get(key)); + } + if (isArrayIndex) { + run(depsMap.get(ARRAY_ITERATE_KEY)); + } + switch (type) { + case "add": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } else if (isArrayIndex) { + run(depsMap.get("length")); + } + break; + case "delete": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } + break; + case "set": + if (isMap(target)) { + run(depsMap.get(ITERATE_KEY)); + } + break; + } + } + } + endBatch(); +} +function getDepFromReactive(object, key) { + const depMap = targetMap.get(object); + return depMap && depMap.get(key); +} +function reactiveReadArray(array) { + const raw = toRaw(array); + if (raw === array) return raw; + track(raw, "iterate", ARRAY_ITERATE_KEY); + return isShallow(array) ? raw : raw.map(toReactive); +} +function shallowReadArray(arr) { + track(arr = toRaw(arr), "iterate", ARRAY_ITERATE_KEY); + return arr; +} +var arrayInstrumentations = { + __proto__: null, + [Symbol.iterator]() { + return iterator(this, Symbol.iterator, toReactive); + }, + concat(...args) { + return reactiveReadArray(this).concat( + ...args.map((x) => isArray(x) ? reactiveReadArray(x) : x) + ); + }, + entries() { + return iterator(this, "entries", (value) => { + value[1] = toReactive(value[1]); + return value; + }); + }, + every(fn, thisArg) { + return apply(this, "every", fn, thisArg, void 0, arguments); + }, + filter(fn, thisArg) { + return apply(this, "filter", fn, thisArg, (v) => v.map(toReactive), arguments); + }, + find(fn, thisArg) { + return apply(this, "find", fn, thisArg, toReactive, arguments); + }, + findIndex(fn, thisArg) { + return apply(this, "findIndex", fn, thisArg, void 0, arguments); + }, + findLast(fn, thisArg) { + return apply(this, "findLast", fn, thisArg, toReactive, arguments); + }, + findLastIndex(fn, thisArg) { + return apply(this, "findLastIndex", fn, thisArg, void 0, arguments); + }, + // flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement + forEach(fn, thisArg) { + return apply(this, "forEach", fn, thisArg, void 0, arguments); + }, + includes(...args) { + return searchProxy(this, "includes", args); + }, + indexOf(...args) { + return searchProxy(this, "indexOf", args); + }, + join(separator) { + return reactiveReadArray(this).join(separator); + }, + // keys() iterator only reads `length`, no optimisation required + lastIndexOf(...args) { + return searchProxy(this, "lastIndexOf", args); + }, + map(fn, thisArg) { + return apply(this, "map", fn, thisArg, void 0, arguments); + }, + pop() { + return noTracking(this, "pop"); + }, + push(...args) { + return noTracking(this, "push", args); + }, + reduce(fn, ...args) { + return reduce(this, "reduce", fn, args); + }, + reduceRight(fn, ...args) { + return reduce(this, "reduceRight", fn, args); + }, + shift() { + return noTracking(this, "shift"); + }, + // slice could use ARRAY_ITERATE but also seems to beg for range tracking + some(fn, thisArg) { + return apply(this, "some", fn, thisArg, void 0, arguments); + }, + splice(...args) { + return noTracking(this, "splice", args); + }, + toReversed() { + return reactiveReadArray(this).toReversed(); + }, + toSorted(comparer) { + return reactiveReadArray(this).toSorted(comparer); + }, + toSpliced(...args) { + return reactiveReadArray(this).toSpliced(...args); + }, + unshift(...args) { + return noTracking(this, "unshift", args); + }, + values() { + return iterator(this, "values", toReactive); + } +}; +function iterator(self2, method, wrapValue) { + const arr = shallowReadArray(self2); + const iter = arr[method](); + if (arr !== self2 && !isShallow(self2)) { + iter._next = iter.next; + iter.next = () => { + const result = iter._next(); + if (result.value) { + result.value = wrapValue(result.value); + } + return result; + }; + } + return iter; +} +var arrayProto = Array.prototype; +function apply(self2, method, fn, thisArg, wrappedRetFn, args) { + const arr = shallowReadArray(self2); + const needsWrap = arr !== self2 && !isShallow(self2); + const methodFn = arr[method]; + if (methodFn !== arrayProto[method]) { + const result2 = methodFn.apply(self2, args); + return needsWrap ? toReactive(result2) : result2; + } + let wrappedFn = fn; + if (arr !== self2) { + if (needsWrap) { + wrappedFn = function(item, index) { + return fn.call(this, toReactive(item), index, self2); + }; + } else if (fn.length > 2) { + wrappedFn = function(item, index) { + return fn.call(this, item, index, self2); + }; + } + } + const result = methodFn.call(arr, wrappedFn, thisArg); + return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result; +} +function reduce(self2, method, fn, args) { + const arr = shallowReadArray(self2); + let wrappedFn = fn; + if (arr !== self2) { + if (!isShallow(self2)) { + wrappedFn = function(acc, item, index) { + return fn.call(this, acc, toReactive(item), index, self2); + }; + } else if (fn.length > 3) { + wrappedFn = function(acc, item, index) { + return fn.call(this, acc, item, index, self2); + }; + } + } + return arr[method](wrappedFn, ...args); +} +function searchProxy(self2, method, args) { + const arr = toRaw(self2); + track(arr, "iterate", ARRAY_ITERATE_KEY); + const res = arr[method](...args); + if ((res === -1 || res === false) && isProxy(args[0])) { + args[0] = toRaw(args[0]); + return arr[method](...args); + } + return res; +} +function noTracking(self2, method, args = []) { + pauseTracking(); + startBatch(); + const res = toRaw(self2)[method].apply(self2, args); + endBatch(); + resetTracking(); + return res; +} +var isNonTrackableKeys = makeMap(`__proto__,__v_isRef,__isVue`); +var builtInSymbols = new Set( + Object.getOwnPropertyNames(Symbol).filter((key) => key !== "arguments" && key !== "caller").map((key) => Symbol[key]).filter(isSymbol) +); +function hasOwnProperty2(key) { + if (!isSymbol(key)) key = String(key); + const obj = toRaw(this); + track(obj, "has", key); + return obj.hasOwnProperty(key); +} +var BaseReactiveHandler = class { + constructor(_isReadonly = false, _isShallow = false) { + this._isReadonly = _isReadonly; + this._isShallow = _isShallow; + } + get(target, key, receiver) { + if (key === "__v_skip") return target["__v_skip"]; + const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow; + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_isShallow") { + return isShallow2; + } else if (key === "__v_raw") { + if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype + // this means the receiver is a user proxy of the reactive proxy + Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) { + return target; + } + return; + } + const targetIsArray = isArray(target); + if (!isReadonly2) { + let fn; + if (targetIsArray && (fn = arrayInstrumentations[key])) { + return fn; + } + if (key === "hasOwnProperty") { + return hasOwnProperty2; + } + } + const res = Reflect.get( + target, + key, + // if this is a proxy wrapping a ref, return methods using the raw ref + // as receiver so that we don't have to call `toRaw` on the ref in all + // its class methods + isRef2(target) ? target : receiver + ); + if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { + return res; + } + if (!isReadonly2) { + track(target, "get", key); + } + if (isShallow2) { + return res; + } + if (isRef2(res)) { + return targetIsArray && isIntegerKey(key) ? res : res.value; + } + if (isObject(res)) { + return isReadonly2 ? readonly(res) : reactive(res); + } + return res; + } +}; +var MutableReactiveHandler = class extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(false, isShallow2); + } + set(target, key, value, receiver) { + let oldValue = target[key]; + if (!this._isShallow) { + const isOldValueReadonly = isReadonly(oldValue); + if (!isShallow(value) && !isReadonly(value)) { + oldValue = toRaw(oldValue); + value = toRaw(value); + } + if (!isArray(target) && isRef2(oldValue) && !isRef2(value)) { + if (isOldValueReadonly) { + return false; + } else { + oldValue.value = value; + return true; + } + } + } + const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key); + const result = Reflect.set( + target, + key, + value, + isRef2(target) ? target : receiver + ); + if (target === toRaw(receiver)) { + if (!hadKey) { + trigger(target, "add", key, value); + } else if (hasChanged(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + } + return result; + } + deleteProperty(target, key) { + const hadKey = hasOwn(target, key); + const oldValue = target[key]; + const result = Reflect.deleteProperty(target, key); + if (result && hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + } + has(target, key) { + const result = Reflect.has(target, key); + if (!isSymbol(key) || !builtInSymbols.has(key)) { + track(target, "has", key); + } + return result; + } + ownKeys(target) { + track( + target, + "iterate", + isArray(target) ? "length" : ITERATE_KEY + ); + return Reflect.ownKeys(target); + } +}; +var ReadonlyReactiveHandler = class extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(true, isShallow2); + } + set(target, key) { + if (true) { + warn( + `Set operation on key "${String(key)}" failed: target is readonly.`, + target + ); + } + return true; + } + deleteProperty(target, key) { + if (true) { + warn( + `Delete operation on key "${String(key)}" failed: target is readonly.`, + target + ); + } + return true; + } +}; +var mutableHandlers = new MutableReactiveHandler(); +var readonlyHandlers = new ReadonlyReactiveHandler(); +var shallowReactiveHandlers = new MutableReactiveHandler(true); +var shallowReadonlyHandlers = new ReadonlyReactiveHandler(true); +var toShallow = (value) => value; +var getProto = (v) => Reflect.getPrototypeOf(v); +function createIterableMethod(method, isReadonly2, isShallow2) { + return function(...args) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const targetIsMap = isMap(rawTarget); + const isPair = method === "entries" || method === Symbol.iterator && targetIsMap; + const isKeyOnly = method === "keys" && targetIsMap; + const innerIterator = target[method](...args); + const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive; + !isReadonly2 && track( + rawTarget, + "iterate", + isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY + ); + return { + // iterator protocol + next() { + const { value, done } = innerIterator.next(); + return done ? { value, done } : { + value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), + done + }; + }, + // iterable protocol + [Symbol.iterator]() { + return this; + } + }; + }; +} +function createReadonlyMethod(type) { + return function(...args) { + if (true) { + const key = args[0] ? `on key "${args[0]}" ` : ``; + warn( + `${capitalize(type)} operation ${key}failed: target is readonly.`, + toRaw(this) + ); + } + return type === "delete" ? false : type === "clear" ? void 0 : this; + }; +} +function createInstrumentations(readonly2, shallow) { + const instrumentations = { + get(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly2) { + if (hasChanged(key, rawKey)) { + track(rawTarget, "get", key); + } + track(rawTarget, "get", rawKey); + } + const { has } = getProto(rawTarget); + const wrap = shallow ? toShallow : readonly2 ? toReadonly : toReactive; + if (has.call(rawTarget, key)) { + return wrap(target.get(key)); + } else if (has.call(rawTarget, rawKey)) { + return wrap(target.get(rawKey)); + } else if (target !== rawTarget) { + target.get(key); + } + }, + get size() { + const target = this["__v_raw"]; + !readonly2 && track(toRaw(target), "iterate", ITERATE_KEY); + return Reflect.get(target, "size", target); + }, + has(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly2) { + if (hasChanged(key, rawKey)) { + track(rawTarget, "has", key); + } + track(rawTarget, "has", rawKey); + } + return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey); + }, + forEach(callback, thisArg) { + const observed = this; + const target = observed["__v_raw"]; + const rawTarget = toRaw(target); + const wrap = shallow ? toShallow : readonly2 ? toReadonly : toReactive; + !readonly2 && track(rawTarget, "iterate", ITERATE_KEY); + return target.forEach((value, key) => { + return callback.call(thisArg, wrap(value), wrap(key), observed); + }); + } + }; + extend( + instrumentations, + readonly2 ? { + add: createReadonlyMethod("add"), + set: createReadonlyMethod("set"), + delete: createReadonlyMethod("delete"), + clear: createReadonlyMethod("clear") + } : { + add(value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const proto = getProto(target); + const hadKey = proto.has.call(target, value); + if (!hadKey) { + target.add(value); + trigger(target, "add", value, value); + } + return this; + }, + set(key, value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const { has, get } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else if (true) { + checkIdentityKeys(target, has, key); + } + const oldValue = get.call(target, key); + target.set(key, value); + if (!hadKey) { + trigger(target, "add", key, value); + } else if (hasChanged(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + return this; + }, + delete(key) { + const target = toRaw(this); + const { has, get } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else if (true) { + checkIdentityKeys(target, has, key); + } + const oldValue = get ? get.call(target, key) : void 0; + const result = target.delete(key); + if (hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + }, + clear() { + const target = toRaw(this); + const hadItems = target.size !== 0; + const oldTarget = true ? isMap(target) ? new Map(target) : new Set(target) : void 0; + const result = target.clear(); + if (hadItems) { + trigger( + target, + "clear", + void 0, + void 0, + oldTarget + ); + } + return result; + } + } + ); + const iteratorMethods = [ + "keys", + "values", + "entries", + Symbol.iterator + ]; + iteratorMethods.forEach((method) => { + instrumentations[method] = createIterableMethod(method, readonly2, shallow); + }); + return instrumentations; +} +function createInstrumentationGetter(isReadonly2, shallow) { + const instrumentations = createInstrumentations(isReadonly2, shallow); + return (target, key, receiver) => { + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_raw") { + return target; + } + return Reflect.get( + hasOwn(instrumentations, key) && key in target ? instrumentations : target, + key, + receiver + ); + }; +} +var mutableCollectionHandlers = { + get: createInstrumentationGetter(false, false) +}; +var shallowCollectionHandlers = { + get: createInstrumentationGetter(false, true) +}; +var readonlyCollectionHandlers = { + get: createInstrumentationGetter(true, false) +}; +var shallowReadonlyCollectionHandlers = { + get: createInstrumentationGetter(true, true) +}; +function checkIdentityKeys(target, has, key) { + const rawKey = toRaw(key); + if (rawKey !== key && has.call(target, rawKey)) { + const type = toRawType(target); + warn( + `Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.` + ); + } +} +var reactiveMap = /* @__PURE__ */ new WeakMap(); +var shallowReactiveMap = /* @__PURE__ */ new WeakMap(); +var readonlyMap = /* @__PURE__ */ new WeakMap(); +var shallowReadonlyMap = /* @__PURE__ */ new WeakMap(); +function targetTypeMap(rawType) { + switch (rawType) { + case "Object": + case "Array": + return 1; + case "Map": + case "Set": + case "WeakMap": + case "WeakSet": + return 2; + default: + return 0; + } +} +function getTargetType(value) { + return value["__v_skip"] || !Object.isExtensible(value) ? 0 : targetTypeMap(toRawType(value)); +} +function reactive(target) { + if (isReadonly(target)) { + return target; + } + return createReactiveObject( + target, + false, + mutableHandlers, + mutableCollectionHandlers, + reactiveMap + ); +} +function shallowReactive(target) { + return createReactiveObject( + target, + false, + shallowReactiveHandlers, + shallowCollectionHandlers, + shallowReactiveMap + ); +} +function readonly(target) { + return createReactiveObject( + target, + true, + readonlyHandlers, + readonlyCollectionHandlers, + readonlyMap + ); +} +function shallowReadonly(target) { + return createReactiveObject( + target, + true, + shallowReadonlyHandlers, + shallowReadonlyCollectionHandlers, + shallowReadonlyMap + ); +} +function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) { + if (!isObject(target)) { + if (true) { + warn( + `value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String( + target + )}` + ); + } + return target; + } + if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) { + return target; + } + const targetType = getTargetType(target); + if (targetType === 0) { + return target; + } + const existingProxy = proxyMap.get(target); + if (existingProxy) { + return existingProxy; + } + const proxy = new Proxy( + target, + targetType === 2 ? collectionHandlers : baseHandlers + ); + proxyMap.set(target, proxy); + return proxy; +} +function isReactive(value) { + if (isReadonly(value)) { + return isReactive(value["__v_raw"]); + } + return !!(value && value["__v_isReactive"]); +} +function isReadonly(value) { + return !!(value && value["__v_isReadonly"]); +} +function isShallow(value) { + return !!(value && value["__v_isShallow"]); +} +function isProxy(value) { + return value ? !!value["__v_raw"] : false; +} +function toRaw(observed) { + const raw = observed && observed["__v_raw"]; + return raw ? toRaw(raw) : observed; +} +function markRaw(value) { + if (!hasOwn(value, "__v_skip") && Object.isExtensible(value)) { + def(value, "__v_skip", true); + } + return value; +} +var toReactive = (value) => isObject(value) ? reactive(value) : value; +var toReadonly = (value) => isObject(value) ? readonly(value) : value; +function isRef2(r) { + return r ? r["__v_isRef"] === true : false; +} +function ref(value) { + return createRef(value, false); +} +function shallowRef(value) { + return createRef(value, true); +} +function createRef(rawValue, shallow) { + if (isRef2(rawValue)) { + return rawValue; + } + return new RefImpl(rawValue, shallow); +} +var RefImpl = class { + constructor(value, isShallow2) { + this.dep = new Dep(); + this["__v_isRef"] = true; + this["__v_isShallow"] = false; + this._rawValue = isShallow2 ? value : toRaw(value); + this._value = isShallow2 ? value : toReactive(value); + this["__v_isShallow"] = isShallow2; + } + get value() { + if (true) { + this.dep.track({ + target: this, + type: "get", + key: "value" + }); + } else { + this.dep.track(); + } + return this._value; + } + set value(newValue) { + const oldValue = this._rawValue; + const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue); + newValue = useDirectValue ? newValue : toRaw(newValue); + if (hasChanged(newValue, oldValue)) { + this._rawValue = newValue; + this._value = useDirectValue ? newValue : toReactive(newValue); + if (true) { + this.dep.trigger({ + target: this, + type: "set", + key: "value", + newValue, + oldValue + }); + } else { + this.dep.trigger(); + } + } + } +}; +function triggerRef(ref2) { + if (ref2.dep) { + if (true) { + ref2.dep.trigger({ + target: ref2, + type: "set", + key: "value", + newValue: ref2._value + }); + } else { + ref2.dep.trigger(); + } + } +} +function unref(ref2) { + return isRef2(ref2) ? ref2.value : ref2; +} +function toValue(source) { + return isFunction(source) ? source() : unref(source); +} +var shallowUnwrapHandlers = { + get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)), + set: (target, key, value, receiver) => { + const oldValue = target[key]; + if (isRef2(oldValue) && !isRef2(value)) { + oldValue.value = value; + return true; + } else { + return Reflect.set(target, key, value, receiver); + } + } +}; +function proxyRefs(objectWithRefs) { + return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers); +} +var CustomRefImpl = class { + constructor(factory) { + this["__v_isRef"] = true; + this._value = void 0; + const dep = this.dep = new Dep(); + const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep)); + this._get = get; + this._set = set; + } + get value() { + return this._value = this._get(); + } + set value(newVal) { + this._set(newVal); + } +}; +function customRef(factory) { + return new CustomRefImpl(factory); +} +function toRefs(object) { + if (!isProxy(object)) { + warn(`toRefs() expects a reactive object but received a plain one.`); + } + const ret = isArray(object) ? new Array(object.length) : {}; + for (const key in object) { + ret[key] = propertyToRef(object, key); + } + return ret; +} +var ObjectRefImpl = class { + constructor(_object, _key, _defaultValue) { + this._object = _object; + this._key = _key; + this._defaultValue = _defaultValue; + this["__v_isRef"] = true; + this._value = void 0; + } + get value() { + const val = this._object[this._key]; + return this._value = val === void 0 ? this._defaultValue : val; + } + set value(newVal) { + this._object[this._key] = newVal; + } + get dep() { + return getDepFromReactive(toRaw(this._object), this._key); + } +}; +var GetterRefImpl = class { + constructor(_getter) { + this._getter = _getter; + this["__v_isRef"] = true; + this["__v_isReadonly"] = true; + this._value = void 0; + } + get value() { + return this._value = this._getter(); + } +}; +function toRef(source, key, defaultValue) { + if (isRef2(source)) { + return source; + } else if (isFunction(source)) { + return new GetterRefImpl(source); + } else if (isObject(source) && arguments.length > 1) { + return propertyToRef(source, key, defaultValue); + } else { + return ref(source); + } +} +function propertyToRef(source, key, defaultValue) { + const val = source[key]; + return isRef2(val) ? val : new ObjectRefImpl(source, key, defaultValue); +} +var ComputedRefImpl = class { + constructor(fn, setter, isSSR) { + this.fn = fn; + this.setter = setter; + this._value = void 0; + this.dep = new Dep(this); + this.__v_isRef = true; + this.deps = void 0; + this.depsTail = void 0; + this.flags = 16; + this.globalVersion = globalVersion - 1; + this.next = void 0; + this.effect = this; + this["__v_isReadonly"] = !setter; + this.isSSR = isSSR; + } + /** + * @internal + */ + notify() { + this.flags |= 16; + if (!(this.flags & 8) && // avoid infinite self recursion + activeSub !== this) { + batch(this, true); + return true; + } else if (true) ; + } + get value() { + const link = true ? this.dep.track({ + target: this, + type: "get", + key: "value" + }) : this.dep.track(); + refreshComputed(this); + if (link) { + link.version = this.dep.version; + } + return this._value; + } + set value(newValue) { + if (this.setter) { + this.setter(newValue); + } else if (true) { + warn("Write operation failed: computed value is readonly"); + } + } +}; +function computed(getterOrOptions, debugOptions, isSSR = false) { + let getter; + let setter; + if (isFunction(getterOrOptions)) { + getter = getterOrOptions; + } else { + getter = getterOrOptions.get; + setter = getterOrOptions.set; + } + const cRef = new ComputedRefImpl(getter, setter, isSSR); + if (debugOptions && !isSSR) { + cRef.onTrack = debugOptions.onTrack; + cRef.onTrigger = debugOptions.onTrigger; + } + return cRef; +} +var TrackOpTypes = { + "GET": "get", + "HAS": "has", + "ITERATE": "iterate" +}; +var TriggerOpTypes = { + "SET": "set", + "ADD": "add", + "DELETE": "delete", + "CLEAR": "clear" +}; +var INITIAL_WATCHER_VALUE = {}; +var cleanupMap = /* @__PURE__ */ new WeakMap(); +var activeWatcher = void 0; +function getCurrentWatcher() { + return activeWatcher; +} +function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher) { + if (owner) { + let cleanups = cleanupMap.get(owner); + if (!cleanups) cleanupMap.set(owner, cleanups = []); + cleanups.push(cleanupFn); + } else if (!failSilently) { + warn( + `onWatcherCleanup() was called when there was no active watcher to associate with.` + ); + } +} +function watch(source, cb, options = EMPTY_OBJ) { + const { immediate, deep, once, scheduler, augmentJob, call } = options; + const warnInvalidSource = (s) => { + (options.onWarn || warn)( + `Invalid watch source: `, + s, + `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.` + ); + }; + const reactiveGetter = (source2) => { + if (deep) return source2; + if (isShallow(source2) || deep === false || deep === 0) + return traverse(source2, 1); + return traverse(source2); + }; + let effect2; + let getter; + let cleanup; + let boundCleanup; + let forceTrigger = false; + let isMultiSource = false; + if (isRef2(source)) { + getter = () => source.value; + forceTrigger = isShallow(source); + } else if (isReactive(source)) { + getter = () => reactiveGetter(source); + forceTrigger = true; + } else if (isArray(source)) { + isMultiSource = true; + forceTrigger = source.some((s) => isReactive(s) || isShallow(s)); + getter = () => source.map((s) => { + if (isRef2(s)) { + return s.value; + } else if (isReactive(s)) { + return reactiveGetter(s); + } else if (isFunction(s)) { + return call ? call(s, 2) : s(); + } else { + warnInvalidSource(s); + } + }); + } else if (isFunction(source)) { + if (cb) { + getter = call ? () => call(source, 2) : source; + } else { + getter = () => { + if (cleanup) { + pauseTracking(); + try { + cleanup(); + } finally { + resetTracking(); + } + } + const currentEffect = activeWatcher; + activeWatcher = effect2; + try { + return call ? call(source, 3, [boundCleanup]) : source(boundCleanup); + } finally { + activeWatcher = currentEffect; + } + }; + } + } else { + getter = NOOP; + warnInvalidSource(source); + } + if (cb && deep) { + const baseGetter = getter; + const depth = deep === true ? Infinity : deep; + getter = () => traverse(baseGetter(), depth); + } + const scope = getCurrentScope(); + const watchHandle = () => { + effect2.stop(); + if (scope && scope.active) { + remove(scope.effects, effect2); + } + }; + if (once && cb) { + const _cb = cb; + cb = (...args) => { + _cb(...args); + watchHandle(); + }; + } + let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE; + const job = (immediateFirstRun) => { + if (!(effect2.flags & 1) || !effect2.dirty && !immediateFirstRun) { + return; + } + if (cb) { + const newValue = effect2.run(); + if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue))) { + if (cleanup) { + cleanup(); + } + const currentWatcher = activeWatcher; + activeWatcher = effect2; + try { + const args = [ + newValue, + // pass undefined as the old value when it's changed for the first time + oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, + boundCleanup + ]; + oldValue = newValue; + call ? call(cb, 3, args) : ( + // @ts-expect-error + cb(...args) + ); + } finally { + activeWatcher = currentWatcher; + } + } + } else { + effect2.run(); + } + }; + if (augmentJob) { + augmentJob(job); + } + effect2 = new ReactiveEffect(getter); + effect2.scheduler = scheduler ? () => scheduler(job, false) : job; + boundCleanup = (fn) => onWatcherCleanup(fn, false, effect2); + cleanup = effect2.onStop = () => { + const cleanups = cleanupMap.get(effect2); + if (cleanups) { + if (call) { + call(cleanups, 4); + } else { + for (const cleanup2 of cleanups) cleanup2(); + } + cleanupMap.delete(effect2); + } + }; + if (true) { + effect2.onTrack = options.onTrack; + effect2.onTrigger = options.onTrigger; + } + if (cb) { + if (immediate) { + job(true); + } else { + oldValue = effect2.run(); + } + } else if (scheduler) { + scheduler(job.bind(null, true), true); + } else { + effect2.run(); + } + watchHandle.pause = effect2.pause.bind(effect2); + watchHandle.resume = effect2.resume.bind(effect2); + watchHandle.stop = watchHandle; + return watchHandle; +} +function traverse(value, depth = Infinity, seen) { + if (depth <= 0 || !isObject(value) || value["__v_skip"]) { + return value; + } + seen = seen || /* @__PURE__ */ new Set(); + if (seen.has(value)) { + return value; + } + seen.add(value); + depth--; + if (isRef2(value)) { + traverse(value.value, depth, seen); + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + traverse(value[i], depth, seen); + } + } else if (isSet(value) || isMap(value)) { + value.forEach((v) => { + traverse(v, depth, seen); + }); + } else if (isPlainObject(value)) { + for (const key in value) { + traverse(value[key], depth, seen); + } + for (const key of Object.getOwnPropertySymbols(value)) { + if (Object.prototype.propertyIsEnumerable.call(value, key)) { + traverse(value[key], depth, seen); + } + } + } + return value; +} + +// node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js +var stack = []; +function pushWarningContext(vnode) { + stack.push(vnode); +} +function popWarningContext() { + stack.pop(); +} +var isWarning = false; +function warn$1(msg, ...args) { + if (isWarning) return; + isWarning = true; + pauseTracking(); + const instance = stack.length ? stack[stack.length - 1].component : null; + const appWarnHandler = instance && instance.appContext.config.warnHandler; + const trace = getComponentTrace(); + if (appWarnHandler) { + callWithErrorHandling( + appWarnHandler, + instance, + 11, + [ + // eslint-disable-next-line no-restricted-syntax + msg + args.map((a) => { + var _a, _b; + return (_b = (_a = a.toString) == null ? void 0 : _a.call(a)) != null ? _b : JSON.stringify(a); + }).join(""), + instance && instance.proxy, + trace.map( + ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>` + ).join("\n"), + trace + ] + ); + } else { + const warnArgs = [`[Vue warn]: ${msg}`, ...args]; + if (trace.length && // avoid spamming console during tests + true) { + warnArgs.push(` +`, ...formatTrace(trace)); + } + console.warn(...warnArgs); + } + resetTracking(); + isWarning = false; +} +function getComponentTrace() { + let currentVNode = stack[stack.length - 1]; + if (!currentVNode) { + return []; + } + const normalizedStack = []; + while (currentVNode) { + const last = normalizedStack[0]; + if (last && last.vnode === currentVNode) { + last.recurseCount++; + } else { + normalizedStack.push({ + vnode: currentVNode, + recurseCount: 0 + }); + } + const parentInstance = currentVNode.component && currentVNode.component.parent; + currentVNode = parentInstance && parentInstance.vnode; + } + return normalizedStack; +} +function formatTrace(trace) { + const logs = []; + trace.forEach((entry, i) => { + logs.push(...i === 0 ? [] : [` +`], ...formatTraceEntry(entry)); + }); + return logs; +} +function formatTraceEntry({ vnode, recurseCount }) { + const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``; + const isRoot = vnode.component ? vnode.component.parent == null : false; + const open = ` at <${formatComponentName( + vnode.component, + vnode.type, + isRoot + )}`; + const close = `>` + postfix; + return vnode.props ? [open, ...formatProps(vnode.props), close] : [open + close]; +} +function formatProps(props) { + const res = []; + const keys = Object.keys(props); + keys.slice(0, 3).forEach((key) => { + res.push(...formatProp(key, props[key])); + }); + if (keys.length > 3) { + res.push(` ...`); + } + return res; +} +function formatProp(key, value, raw) { + if (isString(value)) { + value = JSON.stringify(value); + return raw ? value : [`${key}=${value}`]; + } else if (typeof value === "number" || typeof value === "boolean" || value == null) { + return raw ? value : [`${key}=${value}`]; + } else if (isRef2(value)) { + value = formatProp(key, toRaw(value.value), true); + return raw ? value : [`${key}=Ref<`, value, `>`]; + } else if (isFunction(value)) { + return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]; + } else { + value = toRaw(value); + return raw ? value : [`${key}=`, value]; + } +} +function assertNumber(val, type) { + if (false) return; + if (val === void 0) { + return; + } else if (typeof val !== "number") { + warn$1(`${type} is not a valid number - got ${JSON.stringify(val)}.`); + } else if (isNaN(val)) { + warn$1(`${type} is NaN - the duration expression might be incorrect.`); + } +} +var ErrorCodes = { + "SETUP_FUNCTION": 0, + "0": "SETUP_FUNCTION", + "RENDER_FUNCTION": 1, + "1": "RENDER_FUNCTION", + "NATIVE_EVENT_HANDLER": 5, + "5": "NATIVE_EVENT_HANDLER", + "COMPONENT_EVENT_HANDLER": 6, + "6": "COMPONENT_EVENT_HANDLER", + "VNODE_HOOK": 7, + "7": "VNODE_HOOK", + "DIRECTIVE_HOOK": 8, + "8": "DIRECTIVE_HOOK", + "TRANSITION_HOOK": 9, + "9": "TRANSITION_HOOK", + "APP_ERROR_HANDLER": 10, + "10": "APP_ERROR_HANDLER", + "APP_WARN_HANDLER": 11, + "11": "APP_WARN_HANDLER", + "FUNCTION_REF": 12, + "12": "FUNCTION_REF", + "ASYNC_COMPONENT_LOADER": 13, + "13": "ASYNC_COMPONENT_LOADER", + "SCHEDULER": 14, + "14": "SCHEDULER", + "COMPONENT_UPDATE": 15, + "15": "COMPONENT_UPDATE", + "APP_UNMOUNT_CLEANUP": 16, + "16": "APP_UNMOUNT_CLEANUP" +}; +var ErrorTypeStrings$1 = { + ["sp"]: "serverPrefetch hook", + ["bc"]: "beforeCreate hook", + ["c"]: "created hook", + ["bm"]: "beforeMount hook", + ["m"]: "mounted hook", + ["bu"]: "beforeUpdate hook", + ["u"]: "updated", + ["bum"]: "beforeUnmount hook", + ["um"]: "unmounted hook", + ["a"]: "activated hook", + ["da"]: "deactivated hook", + ["ec"]: "errorCaptured hook", + ["rtc"]: "renderTracked hook", + ["rtg"]: "renderTriggered hook", + [0]: "setup function", + [1]: "render function", + [2]: "watcher getter", + [3]: "watcher callback", + [4]: "watcher cleanup function", + [5]: "native event handler", + [6]: "component event handler", + [7]: "vnode hook", + [8]: "directive hook", + [9]: "transition hook", + [10]: "app errorHandler", + [11]: "app warnHandler", + [12]: "ref function", + [13]: "async component loader", + [14]: "scheduler flush", + [15]: "component update", + [16]: "app unmount cleanup function" +}; +function callWithErrorHandling(fn, instance, type, args) { + try { + return args ? fn(...args) : fn(); + } catch (err) { + handleError(err, instance, type); + } +} +function callWithAsyncErrorHandling(fn, instance, type, args) { + if (isFunction(fn)) { + const res = callWithErrorHandling(fn, instance, type, args); + if (res && isPromise(res)) { + res.catch((err) => { + handleError(err, instance, type); + }); + } + return res; + } + if (isArray(fn)) { + const values = []; + for (let i = 0; i < fn.length; i++) { + values.push(callWithAsyncErrorHandling(fn[i], instance, type, args)); + } + return values; + } else if (true) { + warn$1( + `Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}` + ); + } +} +function handleError(err, instance, type, throwInDev = true) { + const contextVNode = instance ? instance.vnode : null; + const { errorHandler, throwUnhandledErrorInProduction } = instance && instance.appContext.config || EMPTY_OBJ; + if (instance) { + let cur = instance.parent; + const exposedInstance = instance.proxy; + const errorInfo = true ? ErrorTypeStrings$1[type] : `https://vuejs.org/error-reference/#runtime-${type}`; + while (cur) { + const errorCapturedHooks = cur.ec; + if (errorCapturedHooks) { + for (let i = 0; i < errorCapturedHooks.length; i++) { + if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) { + return; + } + } + } + cur = cur.parent; + } + if (errorHandler) { + pauseTracking(); + callWithErrorHandling(errorHandler, null, 10, [ + err, + exposedInstance, + errorInfo + ]); + resetTracking(); + return; + } + } + logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction); +} +function logError(err, type, contextVNode, throwInDev = true, throwInProd = false) { + if (true) { + const info = ErrorTypeStrings$1[type]; + if (contextVNode) { + pushWarningContext(contextVNode); + } + warn$1(`Unhandled error${info ? ` during execution of ${info}` : ``}`); + if (contextVNode) { + popWarningContext(); + } + if (throwInDev) { + throw err; + } else { + console.error(err); + } + } else if (throwInProd) { + throw err; + } else { + console.error(err); + } +} +var queue = []; +var flushIndex = -1; +var pendingPostFlushCbs = []; +var activePostFlushCbs = null; +var postFlushIndex = 0; +var resolvedPromise = Promise.resolve(); +var currentFlushPromise = null; +var RECURSION_LIMIT = 100; +function nextTick(fn) { + const p2 = currentFlushPromise || resolvedPromise; + return fn ? p2.then(this ? fn.bind(this) : fn) : p2; +} +function findInsertionIndex(id) { + let start = flushIndex + 1; + let end = queue.length; + while (start < end) { + const middle = start + end >>> 1; + const middleJob = queue[middle]; + const middleJobId = getId(middleJob); + if (middleJobId < id || middleJobId === id && middleJob.flags & 2) { + start = middle + 1; + } else { + end = middle; + } + } + return start; +} +function queueJob(job) { + if (!(job.flags & 1)) { + const jobId = getId(job); + const lastJob = queue[queue.length - 1]; + if (!lastJob || // fast path when the job id is larger than the tail + !(job.flags & 2) && jobId >= getId(lastJob)) { + queue.push(job); + } else { + queue.splice(findInsertionIndex(jobId), 0, job); + } + job.flags |= 1; + queueFlush(); + } +} +function queueFlush() { + if (!currentFlushPromise) { + currentFlushPromise = resolvedPromise.then(flushJobs); + } +} +function queuePostFlushCb(cb) { + if (!isArray(cb)) { + if (activePostFlushCbs && cb.id === -1) { + activePostFlushCbs.splice(postFlushIndex + 1, 0, cb); + } else if (!(cb.flags & 1)) { + pendingPostFlushCbs.push(cb); + cb.flags |= 1; + } + } else { + pendingPostFlushCbs.push(...cb); + } + queueFlush(); +} +function flushPreFlushCbs(instance, seen, i = flushIndex + 1) { + if (true) { + seen = seen || /* @__PURE__ */ new Map(); + } + for (; i < queue.length; i++) { + const cb = queue[i]; + if (cb && cb.flags & 2) { + if (instance && cb.id !== instance.uid) { + continue; + } + if (checkRecursiveUpdates(seen, cb)) { + continue; + } + queue.splice(i, 1); + i--; + if (cb.flags & 4) { + cb.flags &= -2; + } + cb(); + if (!(cb.flags & 4)) { + cb.flags &= -2; + } + } + } +} +function flushPostFlushCbs(seen) { + if (pendingPostFlushCbs.length) { + const deduped = [...new Set(pendingPostFlushCbs)].sort( + (a, b) => getId(a) - getId(b) + ); + pendingPostFlushCbs.length = 0; + if (activePostFlushCbs) { + activePostFlushCbs.push(...deduped); + return; + } + activePostFlushCbs = deduped; + if (true) { + seen = seen || /* @__PURE__ */ new Map(); + } + for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) { + const cb = activePostFlushCbs[postFlushIndex]; + if (checkRecursiveUpdates(seen, cb)) { + continue; + } + if (cb.flags & 4) { + cb.flags &= -2; + } + if (!(cb.flags & 8)) cb(); + cb.flags &= -2; + } + activePostFlushCbs = null; + postFlushIndex = 0; + } +} +var getId = (job) => job.id == null ? job.flags & 2 ? -1 : Infinity : job.id; +function flushJobs(seen) { + if (true) { + seen = seen || /* @__PURE__ */ new Map(); + } + const check = true ? (job) => checkRecursiveUpdates(seen, job) : NOOP; + try { + for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job && !(job.flags & 8)) { + if (check(job)) { + continue; + } + if (job.flags & 4) { + job.flags &= ~1; + } + callWithErrorHandling( + job, + job.i, + job.i ? 15 : 14 + ); + if (!(job.flags & 4)) { + job.flags &= ~1; + } + } + } + } finally { + for (; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job) { + job.flags &= -2; + } + } + flushIndex = -1; + queue.length = 0; + flushPostFlushCbs(seen); + currentFlushPromise = null; + if (queue.length || pendingPostFlushCbs.length) { + flushJobs(seen); + } + } +} +function checkRecursiveUpdates(seen, fn) { + const count = seen.get(fn) || 0; + if (count > RECURSION_LIMIT) { + const instance = fn.i; + const componentName = instance && getComponentName(instance.type); + handleError( + `Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`, + null, + 10 + ); + return true; + } + seen.set(fn, count + 1); + return false; +} +var isHmrUpdating = false; +var hmrDirtyComponents = /* @__PURE__ */ new Map(); +if (true) { + getGlobalThis().__VUE_HMR_RUNTIME__ = { + createRecord: tryWrap(createRecord), + rerender: tryWrap(rerender), + reload: tryWrap(reload) + }; +} +var map = /* @__PURE__ */ new Map(); +function registerHMR(instance) { + const id = instance.type.__hmrId; + let record = map.get(id); + if (!record) { + createRecord(id, instance.type); + record = map.get(id); + } + record.instances.add(instance); +} +function unregisterHMR(instance) { + map.get(instance.type.__hmrId).instances.delete(instance); +} +function createRecord(id, initialDef) { + if (map.has(id)) { + return false; + } + map.set(id, { + initialDef: normalizeClassComponent(initialDef), + instances: /* @__PURE__ */ new Set() + }); + return true; +} +function normalizeClassComponent(component) { + return isClassComponent(component) ? component.__vccOpts : component; +} +function rerender(id, newRender) { + const record = map.get(id); + if (!record) { + return; + } + record.initialDef.render = newRender; + [...record.instances].forEach((instance) => { + if (newRender) { + instance.render = newRender; + normalizeClassComponent(instance.type).render = newRender; + } + instance.renderCache = []; + isHmrUpdating = true; + instance.update(); + isHmrUpdating = false; + }); +} +function reload(id, newComp) { + const record = map.get(id); + if (!record) return; + newComp = normalizeClassComponent(newComp); + updateComponentDef(record.initialDef, newComp); + const instances = [...record.instances]; + for (let i = 0; i < instances.length; i++) { + const instance = instances[i]; + const oldComp = normalizeClassComponent(instance.type); + let dirtyInstances = hmrDirtyComponents.get(oldComp); + if (!dirtyInstances) { + if (oldComp !== record.initialDef) { + updateComponentDef(oldComp, newComp); + } + hmrDirtyComponents.set(oldComp, dirtyInstances = /* @__PURE__ */ new Set()); + } + dirtyInstances.add(instance); + instance.appContext.propsCache.delete(instance.type); + instance.appContext.emitsCache.delete(instance.type); + instance.appContext.optionsCache.delete(instance.type); + if (instance.ceReload) { + dirtyInstances.add(instance); + instance.ceReload(newComp.styles); + dirtyInstances.delete(instance); + } else if (instance.parent) { + queueJob(() => { + isHmrUpdating = true; + instance.parent.update(); + isHmrUpdating = false; + dirtyInstances.delete(instance); + }); + } else if (instance.appContext.reload) { + instance.appContext.reload(); + } else if (typeof window !== "undefined") { + window.location.reload(); + } else { + console.warn( + "[HMR] Root or manually mounted instance modified. Full reload required." + ); + } + if (instance.root.ce && instance !== instance.root) { + instance.root.ce._removeChildStyle(oldComp); + } + } + queuePostFlushCb(() => { + hmrDirtyComponents.clear(); + }); +} +function updateComponentDef(oldComp, newComp) { + extend(oldComp, newComp); + for (const key in oldComp) { + if (key !== "__file" && !(key in newComp)) { + delete oldComp[key]; + } + } +} +function tryWrap(fn) { + return (id, arg) => { + try { + return fn(id, arg); + } catch (e) { + console.error(e); + console.warn( + `[HMR] Something went wrong during Vue component hot-reload. Full reload required.` + ); + } + }; +} +var devtools$1; +var buffer = []; +var devtoolsNotInstalled = false; +function emit$1(event, ...args) { + if (devtools$1) { + devtools$1.emit(event, ...args); + } else if (!devtoolsNotInstalled) { + buffer.push({ event, args }); + } +} +function setDevtoolsHook$1(hook, target) { + var _a, _b; + devtools$1 = hook; + if (devtools$1) { + devtools$1.enabled = true; + buffer.forEach(({ event, args }) => devtools$1.emit(event, ...args)); + buffer = []; + } else if ( + // handle late devtools injection - only do this if we are in an actual + // browser environment to avoid the timer handle stalling test runner exit + // (#4815) + typeof window !== "undefined" && // some envs mock window but not fully + window.HTMLElement && // also exclude jsdom + // eslint-disable-next-line no-restricted-syntax + !((_b = (_a = window.navigator) == null ? void 0 : _a.userAgent) == null ? void 0 : _b.includes("jsdom")) + ) { + const replay = target.__VUE_DEVTOOLS_HOOK_REPLAY__ = target.__VUE_DEVTOOLS_HOOK_REPLAY__ || []; + replay.push((newHook) => { + setDevtoolsHook$1(newHook, target); + }); + setTimeout(() => { + if (!devtools$1) { + target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null; + devtoolsNotInstalled = true; + buffer = []; + } + }, 3e3); + } else { + devtoolsNotInstalled = true; + buffer = []; + } +} +function devtoolsInitApp(app, version2) { + emit$1("app:init", app, version2, { + Fragment, + Text, + Comment, + Static + }); +} +function devtoolsUnmountApp(app) { + emit$1("app:unmount", app); +} +var devtoolsComponentAdded = createDevtoolsComponentHook( + "component:added" + /* COMPONENT_ADDED */ +); +var devtoolsComponentUpdated = createDevtoolsComponentHook( + "component:updated" + /* COMPONENT_UPDATED */ +); +var _devtoolsComponentRemoved = createDevtoolsComponentHook( + "component:removed" + /* COMPONENT_REMOVED */ +); +var devtoolsComponentRemoved = (component) => { + if (devtools$1 && typeof devtools$1.cleanupBuffer === "function" && // remove the component if it wasn't buffered + !devtools$1.cleanupBuffer(component)) { + _devtoolsComponentRemoved(component); + } +}; +function createDevtoolsComponentHook(hook) { + return (component) => { + emit$1( + hook, + component.appContext.app, + component.uid, + component.parent ? component.parent.uid : void 0, + component + ); + }; +} +var devtoolsPerfStart = createDevtoolsPerformanceHook( + "perf:start" + /* PERFORMANCE_START */ +); +var devtoolsPerfEnd = createDevtoolsPerformanceHook( + "perf:end" + /* PERFORMANCE_END */ +); +function createDevtoolsPerformanceHook(hook) { + return (component, type, time) => { + emit$1(hook, component.appContext.app, component.uid, component, type, time); + }; +} +function devtoolsComponentEmit(component, event, params) { + emit$1( + "component:emit", + component.appContext.app, + component, + event, + params + ); +} +var currentRenderingInstance = null; +var currentScopeId = null; +function setCurrentRenderingInstance(instance) { + const prev = currentRenderingInstance; + currentRenderingInstance = instance; + currentScopeId = instance && instance.type.__scopeId || null; + return prev; +} +function pushScopeId(id) { + currentScopeId = id; +} +function popScopeId() { + currentScopeId = null; +} +var withScopeId = (_id) => withCtx; +function withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) { + if (!ctx) return fn; + if (fn._n) { + return fn; + } + const renderFnWithContext = (...args) => { + if (renderFnWithContext._d) { + setBlockTracking(-1); + } + const prevInstance = setCurrentRenderingInstance(ctx); + let res; + try { + res = fn(...args); + } finally { + setCurrentRenderingInstance(prevInstance); + if (renderFnWithContext._d) { + setBlockTracking(1); + } + } + if (true) { + devtoolsComponentUpdated(ctx); + } + return res; + }; + renderFnWithContext._n = true; + renderFnWithContext._c = true; + renderFnWithContext._d = true; + return renderFnWithContext; +} +function validateDirectiveName(name) { + if (isBuiltInDirective(name)) { + warn$1("Do not use built-in directive ids as custom directive id: " + name); + } +} +function withDirectives(vnode, directives) { + if (currentRenderingInstance === null) { + warn$1(`withDirectives can only be used inside render functions.`); + return vnode; + } + const instance = getComponentPublicInstance(currentRenderingInstance); + const bindings = vnode.dirs || (vnode.dirs = []); + for (let i = 0; i < directives.length; i++) { + let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]; + if (dir) { + if (isFunction(dir)) { + dir = { + mounted: dir, + updated: dir + }; + } + if (dir.deep) { + traverse(value); + } + bindings.push({ + dir, + instance, + value, + oldValue: void 0, + arg, + modifiers + }); + } + } + return vnode; +} +function invokeDirectiveHook(vnode, prevVNode, instance, name) { + const bindings = vnode.dirs; + const oldBindings = prevVNode && prevVNode.dirs; + for (let i = 0; i < bindings.length; i++) { + const binding = bindings[i]; + if (oldBindings) { + binding.oldValue = oldBindings[i].value; + } + let hook = binding.dir[name]; + if (hook) { + pauseTracking(); + callWithAsyncErrorHandling(hook, instance, 8, [ + vnode.el, + binding, + vnode, + prevVNode + ]); + resetTracking(); + } + } +} +var TeleportEndKey = Symbol("_vte"); +var isTeleport = (type) => type.__isTeleport; +var isTeleportDisabled = (props) => props && (props.disabled || props.disabled === ""); +var isTeleportDeferred = (props) => props && (props.defer || props.defer === ""); +var isTargetSVG = (target) => typeof SVGElement !== "undefined" && target instanceof SVGElement; +var isTargetMathML = (target) => typeof MathMLElement === "function" && target instanceof MathMLElement; +var resolveTarget = (props, select) => { + const targetSelector = props && props.to; + if (isString(targetSelector)) { + if (!select) { + warn$1( + `Current renderer does not support string target for Teleports. (missing querySelector renderer option)` + ); + return null; + } else { + const target = select(targetSelector); + if (!target && !isTeleportDisabled(props)) { + warn$1( + `Failed to locate Teleport target with selector "${targetSelector}". Note the target element must exist before the component is mounted - i.e. the target cannot be rendered by the component itself, and ideally should be outside of the entire Vue component tree.` + ); + } + return target; + } + } else { + if (!targetSelector && !isTeleportDisabled(props)) { + warn$1(`Invalid Teleport target: ${targetSelector}`); + } + return targetSelector; + } +}; +var TeleportImpl = { + name: "Teleport", + __isTeleport: true, + process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals) { + const { + mc: mountChildren, + pc: patchChildren, + pbc: patchBlockChildren, + o: { insert, querySelector, createText, createComment } + } = internals; + const disabled = isTeleportDisabled(n2.props); + let { shapeFlag, children, dynamicChildren } = n2; + if (isHmrUpdating) { + optimized = false; + dynamicChildren = null; + } + if (n1 == null) { + const placeholder = n2.el = true ? createComment("teleport start") : createText(""); + const mainAnchor = n2.anchor = true ? createComment("teleport end") : createText(""); + insert(placeholder, container, anchor); + insert(mainAnchor, container, anchor); + const mount = (container2, anchor2) => { + if (shapeFlag & 16) { + if (parentComponent && parentComponent.isCE) { + parentComponent.ce._teleportTarget = container2; + } + mountChildren( + children, + container2, + anchor2, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized + ); + } + }; + const mountToTarget = () => { + const target = n2.target = resolveTarget(n2.props, querySelector); + const targetAnchor = prepareAnchor(target, n2, createText, insert); + if (target) { + if (namespace !== "svg" && isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace !== "mathml" && isTargetMathML(target)) { + namespace = "mathml"; + } + if (!disabled) { + mount(target, targetAnchor); + updateCssVars(n2, false); + } + } else if (!disabled) { + warn$1( + "Invalid Teleport target on mount:", + target, + `(${typeof target})` + ); + } + }; + if (disabled) { + mount(container, mainAnchor); + updateCssVars(n2, true); + } + if (isTeleportDeferred(n2.props)) { + n2.el.__isMounted = false; + queuePostRenderEffect(() => { + mountToTarget(); + delete n2.el.__isMounted; + }, parentSuspense); + } else { + mountToTarget(); + } + } else { + if (isTeleportDeferred(n2.props) && n1.el.__isMounted === false) { + queuePostRenderEffect(() => { + TeleportImpl.process( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + internals + ); + }, parentSuspense); + return; + } + n2.el = n1.el; + n2.targetStart = n1.targetStart; + const mainAnchor = n2.anchor = n1.anchor; + const target = n2.target = n1.target; + const targetAnchor = n2.targetAnchor = n1.targetAnchor; + const wasDisabled = isTeleportDisabled(n1.props); + const currentContainer = wasDisabled ? container : target; + const currentAnchor = wasDisabled ? mainAnchor : targetAnchor; + if (namespace === "svg" || isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace === "mathml" || isTargetMathML(target)) { + namespace = "mathml"; + } + if (dynamicChildren) { + patchBlockChildren( + n1.dynamicChildren, + dynamicChildren, + currentContainer, + parentComponent, + parentSuspense, + namespace, + slotScopeIds + ); + traverseStaticChildren(n1, n2, false); + } else if (!optimized) { + patchChildren( + n1, + n2, + currentContainer, + currentAnchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + false + ); + } + if (disabled) { + if (!wasDisabled) { + moveTeleport( + n2, + container, + mainAnchor, + internals, + 1 + ); + } else { + if (n2.props && n1.props && n2.props.to !== n1.props.to) { + n2.props.to = n1.props.to; + } + } + } else { + if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) { + const nextTarget = n2.target = resolveTarget( + n2.props, + querySelector + ); + if (nextTarget) { + moveTeleport( + n2, + nextTarget, + null, + internals, + 0 + ); + } else if (true) { + warn$1( + "Invalid Teleport target on update:", + target, + `(${typeof target})` + ); + } + } else if (wasDisabled) { + moveTeleport( + n2, + target, + targetAnchor, + internals, + 1 + ); + } + } + updateCssVars(n2, disabled); + } + }, + remove(vnode, parentComponent, parentSuspense, { um: unmount, o: { remove: hostRemove } }, doRemove) { + const { + shapeFlag, + children, + anchor, + targetStart, + targetAnchor, + target, + props + } = vnode; + if (target) { + hostRemove(targetStart); + hostRemove(targetAnchor); + } + doRemove && hostRemove(anchor); + if (shapeFlag & 16) { + const shouldRemove = doRemove || !isTeleportDisabled(props); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + unmount( + child, + parentComponent, + parentSuspense, + shouldRemove, + !!child.dynamicChildren + ); + } + } + }, + move: moveTeleport, + hydrate: hydrateTeleport +}; +function moveTeleport(vnode, container, parentAnchor, { o: { insert }, m: move }, moveType = 2) { + if (moveType === 0) { + insert(vnode.targetAnchor, container, parentAnchor); + } + const { el, anchor, shapeFlag, children, props } = vnode; + const isReorder = moveType === 2; + if (isReorder) { + insert(el, container, parentAnchor); + } + if (!isReorder || isTeleportDisabled(props)) { + if (shapeFlag & 16) { + for (let i = 0; i < children.length; i++) { + move( + children[i], + container, + parentAnchor, + 2 + ); + } + } + } + if (isReorder) { + insert(anchor, container, parentAnchor); + } +} +function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, { + o: { nextSibling, parentNode, querySelector, insert, createText } +}, hydrateChildren) { + const target = vnode.target = resolveTarget( + vnode.props, + querySelector + ); + if (target) { + const disabled = isTeleportDisabled(vnode.props); + const targetNode = target._lpa || target.firstChild; + if (vnode.shapeFlag & 16) { + if (disabled) { + vnode.anchor = hydrateChildren( + nextSibling(node), + vnode, + parentNode(node), + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + vnode.targetStart = targetNode; + vnode.targetAnchor = targetNode && nextSibling(targetNode); + } else { + vnode.anchor = nextSibling(node); + let targetAnchor = targetNode; + while (targetAnchor) { + if (targetAnchor && targetAnchor.nodeType === 8) { + if (targetAnchor.data === "teleport start anchor") { + vnode.targetStart = targetAnchor; + } else if (targetAnchor.data === "teleport anchor") { + vnode.targetAnchor = targetAnchor; + target._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor); + break; + } + } + targetAnchor = nextSibling(targetAnchor); + } + if (!vnode.targetAnchor) { + prepareAnchor(target, vnode, createText, insert); + } + hydrateChildren( + targetNode && nextSibling(targetNode), + vnode, + target, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + } + updateCssVars(vnode, disabled); + } + return vnode.anchor && nextSibling(vnode.anchor); +} +var Teleport = TeleportImpl; +function updateCssVars(vnode, isDisabled) { + const ctx = vnode.ctx; + if (ctx && ctx.ut) { + let node, anchor; + if (isDisabled) { + node = vnode.el; + anchor = vnode.anchor; + } else { + node = vnode.targetStart; + anchor = vnode.targetAnchor; + } + while (node && node !== anchor) { + if (node.nodeType === 1) node.setAttribute("data-v-owner", ctx.uid); + node = node.nextSibling; + } + ctx.ut(); + } +} +function prepareAnchor(target, vnode, createText, insert) { + const targetStart = vnode.targetStart = createText(""); + const targetAnchor = vnode.targetAnchor = createText(""); + targetStart[TeleportEndKey] = targetAnchor; + if (target) { + insert(targetStart, target); + insert(targetAnchor, target); + } + return targetAnchor; +} +var leaveCbKey = Symbol("_leaveCb"); +var enterCbKey = Symbol("_enterCb"); +function useTransitionState() { + const state = { + isMounted: false, + isLeaving: false, + isUnmounting: false, + leavingVNodes: /* @__PURE__ */ new Map() + }; + onMounted(() => { + state.isMounted = true; + }); + onBeforeUnmount(() => { + state.isUnmounting = true; + }); + return state; +} +var TransitionHookValidator = [Function, Array]; +var BaseTransitionPropsValidators = { + mode: String, + appear: Boolean, + persisted: Boolean, + // enter + onBeforeEnter: TransitionHookValidator, + onEnter: TransitionHookValidator, + onAfterEnter: TransitionHookValidator, + onEnterCancelled: TransitionHookValidator, + // leave + onBeforeLeave: TransitionHookValidator, + onLeave: TransitionHookValidator, + onAfterLeave: TransitionHookValidator, + onLeaveCancelled: TransitionHookValidator, + // appear + onBeforeAppear: TransitionHookValidator, + onAppear: TransitionHookValidator, + onAfterAppear: TransitionHookValidator, + onAppearCancelled: TransitionHookValidator +}; +var recursiveGetSubtree = (instance) => { + const subTree = instance.subTree; + return subTree.component ? recursiveGetSubtree(subTree.component) : subTree; +}; +var BaseTransitionImpl = { + name: `BaseTransition`, + props: BaseTransitionPropsValidators, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const state = useTransitionState(); + return () => { + const children = slots.default && getTransitionRawChildren(slots.default(), true); + if (!children || !children.length) { + return; + } + const child = findNonCommentChild(children); + const rawProps = toRaw(props); + const { mode } = rawProps; + if (mode && mode !== "in-out" && mode !== "out-in" && mode !== "default") { + warn$1(`invalid mode: ${mode}`); + } + if (state.isLeaving) { + return emptyPlaceholder(child); + } + const innerChild = getInnerChild$1(child); + if (!innerChild) { + return emptyPlaceholder(child); + } + let enterHooks = resolveTransitionHooks( + innerChild, + rawProps, + state, + instance, + // #11061, ensure enterHooks is fresh after clone + (hooks) => enterHooks = hooks + ); + if (innerChild.type !== Comment) { + setTransitionHooks(innerChild, enterHooks); + } + let oldInnerChild = instance.subTree && getInnerChild$1(instance.subTree); + if (oldInnerChild && oldInnerChild.type !== Comment && !isSameVNodeType(innerChild, oldInnerChild) && recursiveGetSubtree(instance).type !== Comment) { + let leavingHooks = resolveTransitionHooks( + oldInnerChild, + rawProps, + state, + instance + ); + setTransitionHooks(oldInnerChild, leavingHooks); + if (mode === "out-in" && innerChild.type !== Comment) { + state.isLeaving = true; + leavingHooks.afterLeave = () => { + state.isLeaving = false; + if (!(instance.job.flags & 8)) { + instance.update(); + } + delete leavingHooks.afterLeave; + oldInnerChild = void 0; + }; + return emptyPlaceholder(child); + } else if (mode === "in-out" && innerChild.type !== Comment) { + leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => { + const leavingVNodesCache = getLeavingNodesForType( + state, + oldInnerChild + ); + leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild; + el[leaveCbKey] = () => { + earlyRemove(); + el[leaveCbKey] = void 0; + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + enterHooks.delayedLeave = () => { + delayedLeave(); + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + }; + } else { + oldInnerChild = void 0; + } + } else if (oldInnerChild) { + oldInnerChild = void 0; + } + return child; + }; + } +}; +function findNonCommentChild(children) { + let child = children[0]; + if (children.length > 1) { + let hasFound = false; + for (const c of children) { + if (c.type !== Comment) { + if (hasFound) { + warn$1( + " can only be used on a single element or component. Use for lists." + ); + break; + } + child = c; + hasFound = true; + if (false) break; + } + } + } + return child; +} +var BaseTransition = BaseTransitionImpl; +function getLeavingNodesForType(state, vnode) { + const { leavingVNodes } = state; + let leavingVNodesCache = leavingVNodes.get(vnode.type); + if (!leavingVNodesCache) { + leavingVNodesCache = /* @__PURE__ */ Object.create(null); + leavingVNodes.set(vnode.type, leavingVNodesCache); + } + return leavingVNodesCache; +} +function resolveTransitionHooks(vnode, props, state, instance, postClone) { + const { + appear, + mode, + persisted = false, + onBeforeEnter, + onEnter, + onAfterEnter, + onEnterCancelled, + onBeforeLeave, + onLeave, + onAfterLeave, + onLeaveCancelled, + onBeforeAppear, + onAppear, + onAfterAppear, + onAppearCancelled + } = props; + const key = String(vnode.key); + const leavingVNodesCache = getLeavingNodesForType(state, vnode); + const callHook3 = (hook, args) => { + hook && callWithAsyncErrorHandling( + hook, + instance, + 9, + args + ); + }; + const callAsyncHook = (hook, args) => { + const done = args[1]; + callHook3(hook, args); + if (isArray(hook)) { + if (hook.every((hook2) => hook2.length <= 1)) done(); + } else if (hook.length <= 1) { + done(); + } + }; + const hooks = { + mode, + persisted, + beforeEnter(el) { + let hook = onBeforeEnter; + if (!state.isMounted) { + if (appear) { + hook = onBeforeAppear || onBeforeEnter; + } else { + return; + } + } + if (el[leaveCbKey]) { + el[leaveCbKey]( + true + /* cancelled */ + ); + } + const leavingVNode = leavingVNodesCache[key]; + if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el[leaveCbKey]) { + leavingVNode.el[leaveCbKey](); + } + callHook3(hook, [el]); + }, + enter(el) { + let hook = onEnter; + let afterHook = onAfterEnter; + let cancelHook = onEnterCancelled; + if (!state.isMounted) { + if (appear) { + hook = onAppear || onEnter; + afterHook = onAfterAppear || onAfterEnter; + cancelHook = onAppearCancelled || onEnterCancelled; + } else { + return; + } + } + let called = false; + const done = el[enterCbKey] = (cancelled) => { + if (called) return; + called = true; + if (cancelled) { + callHook3(cancelHook, [el]); + } else { + callHook3(afterHook, [el]); + } + if (hooks.delayedLeave) { + hooks.delayedLeave(); + } + el[enterCbKey] = void 0; + }; + if (hook) { + callAsyncHook(hook, [el, done]); + } else { + done(); + } + }, + leave(el, remove2) { + const key2 = String(vnode.key); + if (el[enterCbKey]) { + el[enterCbKey]( + true + /* cancelled */ + ); + } + if (state.isUnmounting) { + return remove2(); + } + callHook3(onBeforeLeave, [el]); + let called = false; + const done = el[leaveCbKey] = (cancelled) => { + if (called) return; + called = true; + remove2(); + if (cancelled) { + callHook3(onLeaveCancelled, [el]); + } else { + callHook3(onAfterLeave, [el]); + } + el[leaveCbKey] = void 0; + if (leavingVNodesCache[key2] === vnode) { + delete leavingVNodesCache[key2]; + } + }; + leavingVNodesCache[key2] = vnode; + if (onLeave) { + callAsyncHook(onLeave, [el, done]); + } else { + done(); + } + }, + clone(vnode2) { + const hooks2 = resolveTransitionHooks( + vnode2, + props, + state, + instance, + postClone + ); + if (postClone) postClone(hooks2); + return hooks2; + } + }; + return hooks; +} +function emptyPlaceholder(vnode) { + if (isKeepAlive(vnode)) { + vnode = cloneVNode(vnode); + vnode.children = null; + return vnode; + } +} +function getInnerChild$1(vnode) { + if (!isKeepAlive(vnode)) { + if (isTeleport(vnode.type) && vnode.children) { + return findNonCommentChild(vnode.children); + } + return vnode; + } + if (vnode.component) { + return vnode.component.subTree; + } + const { shapeFlag, children } = vnode; + if (children) { + if (shapeFlag & 16) { + return children[0]; + } + if (shapeFlag & 32 && isFunction(children.default)) { + return children.default(); + } + } +} +function setTransitionHooks(vnode, hooks) { + if (vnode.shapeFlag & 6 && vnode.component) { + vnode.transition = hooks; + setTransitionHooks(vnode.component.subTree, hooks); + } else if (vnode.shapeFlag & 128) { + vnode.ssContent.transition = hooks.clone(vnode.ssContent); + vnode.ssFallback.transition = hooks.clone(vnode.ssFallback); + } else { + vnode.transition = hooks; + } +} +function getTransitionRawChildren(children, keepComment = false, parentKey) { + let ret = []; + let keyedFragmentCount = 0; + for (let i = 0; i < children.length; i++) { + let child = children[i]; + const key = parentKey == null ? child.key : String(parentKey) + String(child.key != null ? child.key : i); + if (child.type === Fragment) { + if (child.patchFlag & 128) keyedFragmentCount++; + ret = ret.concat( + getTransitionRawChildren(child.children, keepComment, key) + ); + } else if (keepComment || child.type !== Comment) { + ret.push(key != null ? cloneVNode(child, { key }) : child); + } + } + if (keyedFragmentCount > 1) { + for (let i = 0; i < ret.length; i++) { + ret[i].patchFlag = -2; + } + } + return ret; +} +function defineComponent(options, extraOptions) { + return isFunction(options) ? ( + // #8236: extend call and options.name access are considered side-effects + // by Rollup, so we have to wrap it in a pure-annotated IIFE. + (() => extend({ name: options.name }, extraOptions, { setup: options }))() + ) : options; +} +function useId() { + const i = getCurrentInstance(); + if (i) { + return (i.appContext.config.idPrefix || "v") + "-" + i.ids[0] + i.ids[1]++; + } else if (true) { + warn$1( + `useId() is called when there is no active component instance to be associated with.` + ); + } + return ""; +} +function markAsyncBoundary(instance) { + instance.ids = [instance.ids[0] + instance.ids[2]++ + "-", 0, 0]; +} +var knownTemplateRefs = /* @__PURE__ */ new WeakSet(); +function useTemplateRef(key) { + const i = getCurrentInstance(); + const r = shallowRef(null); + if (i) { + const refs = i.refs === EMPTY_OBJ ? i.refs = {} : i.refs; + let desc; + if ((desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable) { + warn$1(`useTemplateRef('${key}') already exists.`); + } else { + Object.defineProperty(refs, key, { + enumerable: true, + get: () => r.value, + set: (val) => r.value = val + }); + } + } else if (true) { + warn$1( + `useTemplateRef() is called when there is no active component instance to be associated with.` + ); + } + const ret = true ? readonly(r) : r; + if (true) { + knownTemplateRefs.add(ret); + } + return ret; +} +function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) { + if (isArray(rawRef)) { + rawRef.forEach( + (r, i) => setRef( + r, + oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), + parentSuspense, + vnode, + isUnmount + ) + ); + return; + } + if (isAsyncWrapper(vnode) && !isUnmount) { + if (vnode.shapeFlag & 512 && vnode.type.__asyncResolved && vnode.component.subTree.component) { + setRef(rawRef, oldRawRef, parentSuspense, vnode.component.subTree); + } + return; + } + const refValue = vnode.shapeFlag & 4 ? getComponentPublicInstance(vnode.component) : vnode.el; + const value = isUnmount ? null : refValue; + const { i: owner, r: ref2 } = rawRef; + if (!owner) { + warn$1( + `Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function.` + ); + return; + } + const oldRef = oldRawRef && oldRawRef.r; + const refs = owner.refs === EMPTY_OBJ ? owner.refs = {} : owner.refs; + const setupState = owner.setupState; + const rawSetupState = toRaw(setupState); + const canSetSetupRef = setupState === EMPTY_OBJ ? () => false : (key) => { + if (true) { + if (hasOwn(rawSetupState, key) && !isRef2(rawSetupState[key])) { + warn$1( + `Template ref "${key}" used on a non-ref value. It will not work in the production build.` + ); + } + if (knownTemplateRefs.has(rawSetupState[key])) { + return false; + } + } + return hasOwn(rawSetupState, key); + }; + if (oldRef != null && oldRef !== ref2) { + if (isString(oldRef)) { + refs[oldRef] = null; + if (canSetSetupRef(oldRef)) { + setupState[oldRef] = null; + } + } else if (isRef2(oldRef)) { + oldRef.value = null; + } + } + if (isFunction(ref2)) { + callWithErrorHandling(ref2, owner, 12, [value, refs]); + } else { + const _isString = isString(ref2); + const _isRef = isRef2(ref2); + if (_isString || _isRef) { + const doSet = () => { + if (rawRef.f) { + const existing = _isString ? canSetSetupRef(ref2) ? setupState[ref2] : refs[ref2] : ref2.value; + if (isUnmount) { + isArray(existing) && remove(existing, refValue); + } else { + if (!isArray(existing)) { + if (_isString) { + refs[ref2] = [refValue]; + if (canSetSetupRef(ref2)) { + setupState[ref2] = refs[ref2]; + } + } else { + ref2.value = [refValue]; + if (rawRef.k) refs[rawRef.k] = ref2.value; + } + } else if (!existing.includes(refValue)) { + existing.push(refValue); + } + } + } else if (_isString) { + refs[ref2] = value; + if (canSetSetupRef(ref2)) { + setupState[ref2] = value; + } + } else if (_isRef) { + ref2.value = value; + if (rawRef.k) refs[rawRef.k] = value; + } else if (true) { + warn$1("Invalid template ref type:", ref2, `(${typeof ref2})`); + } + }; + if (value) { + doSet.id = -1; + queuePostRenderEffect(doSet, parentSuspense); + } else { + doSet(); + } + } else if (true) { + warn$1("Invalid template ref type:", ref2, `(${typeof ref2})`); + } + } +} +var hasLoggedMismatchError = false; +var logMismatchError = () => { + if (hasLoggedMismatchError) { + return; + } + console.error("Hydration completed but contains mismatches."); + hasLoggedMismatchError = true; +}; +var isSVGContainer = (container) => container.namespaceURI.includes("svg") && container.tagName !== "foreignObject"; +var isMathMLContainer = (container) => container.namespaceURI.includes("MathML"); +var getContainerType = (container) => { + if (container.nodeType !== 1) return void 0; + if (isSVGContainer(container)) return "svg"; + if (isMathMLContainer(container)) return "mathml"; + return void 0; +}; +var isComment = (node) => node.nodeType === 8; +function createHydrationFunctions(rendererInternals) { + const { + mt: mountComponent, + p: patch, + o: { + patchProp: patchProp2, + createText, + nextSibling, + parentNode, + remove: remove2, + insert, + createComment + } + } = rendererInternals; + const hydrate2 = (vnode, container) => { + if (!container.hasChildNodes()) { + warn$1( + `Attempting to hydrate existing markup but container is empty. Performing full mount instead.` + ); + patch(null, vnode, container); + flushPostFlushCbs(); + container._vnode = vnode; + return; + } + hydrateNode(container.firstChild, vnode, null, null, null); + flushPostFlushCbs(); + container._vnode = vnode; + }; + const hydrateNode = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized = false) => { + optimized = optimized || !!vnode.dynamicChildren; + const isFragmentStart = isComment(node) && node.data === "["; + const onMismatch = () => handleMismatch( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + isFragmentStart + ); + const { type, ref: ref2, shapeFlag, patchFlag } = vnode; + let domType = node.nodeType; + vnode.el = node; + if (true) { + def(node, "__vnode", vnode, true); + def(node, "__vueParentComponent", parentComponent, true); + } + if (patchFlag === -2) { + optimized = false; + vnode.dynamicChildren = null; + } + let nextNode = null; + switch (type) { + case Text: + if (domType !== 3) { + if (vnode.children === "") { + insert(vnode.el = createText(""), parentNode(node), node); + nextNode = node; + } else { + nextNode = onMismatch(); + } + } else { + if (node.data !== vnode.children) { + warn$1( + `Hydration text mismatch in`, + node.parentNode, + ` + - rendered on server: ${JSON.stringify( + node.data + )} + - expected on client: ${JSON.stringify(vnode.children)}` + ); + logMismatchError(); + node.data = vnode.children; + } + nextNode = nextSibling(node); + } + break; + case Comment: + if (isTemplateNode(node)) { + nextNode = nextSibling(node); + replaceNode( + vnode.el = node.content.firstChild, + node, + parentComponent + ); + } else if (domType !== 8 || isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = nextSibling(node); + } + break; + case Static: + if (isFragmentStart) { + node = nextSibling(node); + domType = node.nodeType; + } + if (domType === 1 || domType === 3) { + nextNode = node; + const needToAdoptContent = !vnode.children.length; + for (let i = 0; i < vnode.staticCount; i++) { + if (needToAdoptContent) + vnode.children += nextNode.nodeType === 1 ? nextNode.outerHTML : nextNode.data; + if (i === vnode.staticCount - 1) { + vnode.anchor = nextNode; + } + nextNode = nextSibling(nextNode); + } + return isFragmentStart ? nextSibling(nextNode) : nextNode; + } else { + onMismatch(); + } + break; + case Fragment: + if (!isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = hydrateFragment( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + break; + default: + if (shapeFlag & 1) { + if ((domType !== 1 || vnode.type.toLowerCase() !== node.tagName.toLowerCase()) && !isTemplateNode(node)) { + nextNode = onMismatch(); + } else { + nextNode = hydrateElement( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + } else if (shapeFlag & 6) { + vnode.slotScopeIds = slotScopeIds; + const container = parentNode(node); + if (isFragmentStart) { + nextNode = locateClosingAnchor(node); + } else if (isComment(node) && node.data === "teleport start") { + nextNode = locateClosingAnchor(node, node.data, "teleport end"); + } else { + nextNode = nextSibling(node); + } + mountComponent( + vnode, + container, + null, + parentComponent, + parentSuspense, + getContainerType(container), + optimized + ); + if (isAsyncWrapper(vnode) && !vnode.type.__asyncResolved) { + let subTree; + if (isFragmentStart) { + subTree = createVNode(Fragment); + subTree.anchor = nextNode ? nextNode.previousSibling : container.lastChild; + } else { + subTree = node.nodeType === 3 ? createTextVNode("") : createVNode("div"); + } + subTree.el = node; + vnode.component.subTree = subTree; + } + } else if (shapeFlag & 64) { + if (domType !== 8) { + nextNode = onMismatch(); + } else { + nextNode = vnode.type.hydrate( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized, + rendererInternals, + hydrateChildren + ); + } + } else if (shapeFlag & 128) { + nextNode = vnode.type.hydrate( + node, + vnode, + parentComponent, + parentSuspense, + getContainerType(parentNode(node)), + slotScopeIds, + optimized, + rendererInternals, + hydrateNode + ); + } else if (true) { + warn$1("Invalid HostVNode type:", type, `(${typeof type})`); + } + } + if (ref2 != null) { + setRef(ref2, null, parentSuspense, vnode); + } + return nextNode; + }; + const hydrateElement = (el, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!vnode.dynamicChildren; + const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode; + const forcePatch = type === "input" || type === "option"; + if (true) { + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "created"); + } + let needCallTransitionHooks = false; + if (isTemplateNode(el)) { + needCallTransitionHooks = needTransition( + null, + // no need check parentSuspense in hydration + transition + ) && parentComponent && parentComponent.vnode.props && parentComponent.vnode.props.appear; + const content = el.content.firstChild; + if (needCallTransitionHooks) { + const cls = content.getAttribute("class"); + if (cls) content.$cls = cls; + transition.beforeEnter(content); + } + replaceNode(content, el, parentComponent); + vnode.el = el = content; + } + if (shapeFlag & 16 && // skip if element has innerHTML / textContent + !(props && (props.innerHTML || props.textContent))) { + let next = hydrateChildren( + el.firstChild, + vnode, + el, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + let hasWarned2 = false; + while (next) { + if (!isMismatchAllowed( + el, + 1 + /* CHILDREN */ + )) { + if (!hasWarned2) { + warn$1( + `Hydration children mismatch on`, + el, + ` +Server rendered element contains more child nodes than client vdom.` + ); + hasWarned2 = true; + } + logMismatchError(); + } + const cur = next; + next = next.nextSibling; + remove2(cur); + } + } else if (shapeFlag & 8) { + let clientText = vnode.children; + if (clientText[0] === "\n" && (el.tagName === "PRE" || el.tagName === "TEXTAREA")) { + clientText = clientText.slice(1); + } + if (el.textContent !== clientText) { + if (!isMismatchAllowed( + el, + 0 + /* TEXT */ + )) { + warn$1( + `Hydration text content mismatch on`, + el, + ` + - rendered on server: ${el.textContent} + - expected on client: ${vnode.children}` + ); + logMismatchError(); + } + el.textContent = vnode.children; + } + } + if (props) { + if (true) { + const isCustomElement = el.tagName.includes("-"); + for (const key in props) { + if (// #11189 skip if this node has directives that have created hooks + // as it could have mutated the DOM in any possible way + !(dirs && dirs.some((d) => d.dir.created)) && propHasMismatch(el, key, props[key], vnode, parentComponent)) { + logMismatchError(); + } + if (forcePatch && (key.endsWith("value") || key === "indeterminate") || isOn(key) && !isReservedProp(key) || // force hydrate v-bind with .prop modifiers + key[0] === "." || isCustomElement) { + patchProp2(el, key, null, props[key], void 0, parentComponent); + } + } + } else if (props.onClick) { + patchProp2( + el, + "onClick", + null, + props.onClick, + void 0, + parentComponent + ); + } else if (patchFlag & 4 && isReactive(props.style)) { + for (const key in props.style) props.style[key]; + } + } + let vnodeHooks; + if (vnodeHooks = props && props.onVnodeBeforeMount) { + invokeVNodeHook(vnodeHooks, parentComponent, vnode); + } + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "beforeMount"); + } + if ((vnodeHooks = props && props.onVnodeMounted) || dirs || needCallTransitionHooks) { + queueEffectWithSuspense(() => { + vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode); + needCallTransitionHooks && transition.enter(el); + dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted"); + }, parentSuspense); + } + } + return el.nextSibling; + }; + const hydrateChildren = (node, parentVNode, container, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!parentVNode.dynamicChildren; + const children = parentVNode.children; + const l = children.length; + let hasWarned2 = false; + for (let i = 0; i < l; i++) { + const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]); + const isText = vnode.type === Text; + if (node) { + if (isText && !optimized) { + if (i + 1 < l && normalizeVNode(children[i + 1]).type === Text) { + insert( + createText( + node.data.slice(vnode.children.length) + ), + container, + nextSibling(node) + ); + node.data = vnode.children; + } + } + node = hydrateNode( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } else if (isText && !vnode.children) { + insert(vnode.el = createText(""), container); + } else { + if (!isMismatchAllowed( + container, + 1 + /* CHILDREN */ + )) { + if (!hasWarned2) { + warn$1( + `Hydration children mismatch on`, + container, + ` +Server rendered element contains fewer child nodes than client vdom.` + ); + hasWarned2 = true; + } + logMismatchError(); + } + patch( + null, + vnode, + container, + null, + parentComponent, + parentSuspense, + getContainerType(container), + slotScopeIds + ); + } + } + return node; + }; + const hydrateFragment = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + const { slotScopeIds: fragmentSlotScopeIds } = vnode; + if (fragmentSlotScopeIds) { + slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds; + } + const container = parentNode(node); + const next = hydrateChildren( + nextSibling(node), + vnode, + container, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + if (next && isComment(next) && next.data === "]") { + return nextSibling(vnode.anchor = next); + } else { + logMismatchError(); + insert(vnode.anchor = createComment(`]`), container, next); + return next; + } + }; + const handleMismatch = (node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragment) => { + if (!isMismatchAllowed( + node.parentElement, + 1 + /* CHILDREN */ + )) { + warn$1( + `Hydration node mismatch: +- rendered on server:`, + node, + node.nodeType === 3 ? `(text)` : isComment(node) && node.data === "[" ? `(start of fragment)` : ``, + ` +- expected on client:`, + vnode.type + ); + logMismatchError(); + } + vnode.el = null; + if (isFragment) { + const end = locateClosingAnchor(node); + while (true) { + const next2 = nextSibling(node); + if (next2 && next2 !== end) { + remove2(next2); + } else { + break; + } + } + } + const next = nextSibling(node); + const container = parentNode(node); + remove2(node); + patch( + null, + vnode, + container, + next, + parentComponent, + parentSuspense, + getContainerType(container), + slotScopeIds + ); + if (parentComponent) { + parentComponent.vnode.el = vnode.el; + updateHOCHostEl(parentComponent, vnode.el); + } + return next; + }; + const locateClosingAnchor = (node, open = "[", close = "]") => { + let match = 0; + while (node) { + node = nextSibling(node); + if (node && isComment(node)) { + if (node.data === open) match++; + if (node.data === close) { + if (match === 0) { + return nextSibling(node); + } else { + match--; + } + } + } + } + return node; + }; + const replaceNode = (newNode, oldNode, parentComponent) => { + const parentNode2 = oldNode.parentNode; + if (parentNode2) { + parentNode2.replaceChild(newNode, oldNode); + } + let parent = parentComponent; + while (parent) { + if (parent.vnode.el === oldNode) { + parent.vnode.el = parent.subTree.el = newNode; + } + parent = parent.parent; + } + }; + const isTemplateNode = (node) => { + return node.nodeType === 1 && node.tagName === "TEMPLATE"; + }; + return [hydrate2, hydrateNode]; +} +function propHasMismatch(el, key, clientValue, vnode, instance) { + let mismatchType; + let mismatchKey; + let actual; + let expected; + if (key === "class") { + if (el.$cls) { + actual = el.$cls; + delete el.$cls; + } else { + actual = el.getAttribute("class"); + } + expected = normalizeClass(clientValue); + if (!isSetEqual(toClassSet(actual || ""), toClassSet(expected))) { + mismatchType = 2; + mismatchKey = `class`; + } + } else if (key === "style") { + actual = el.getAttribute("style") || ""; + expected = isString(clientValue) ? clientValue : stringifyStyle(normalizeStyle(clientValue)); + const actualMap = toStyleMap(actual); + const expectedMap = toStyleMap(expected); + if (vnode.dirs) { + for (const { dir, value } of vnode.dirs) { + if (dir.name === "show" && !value) { + expectedMap.set("display", "none"); + } + } + } + if (instance) { + resolveCssVars(instance, vnode, expectedMap); + } + if (!isMapEqual(actualMap, expectedMap)) { + mismatchType = 3; + mismatchKey = "style"; + } + } else if (el instanceof SVGElement && isKnownSvgAttr(key) || el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key))) { + if (isBooleanAttr(key)) { + actual = el.hasAttribute(key); + expected = includeBooleanAttr(clientValue); + } else if (clientValue == null) { + actual = el.hasAttribute(key); + expected = false; + } else { + if (el.hasAttribute(key)) { + actual = el.getAttribute(key); + } else if (key === "value" && el.tagName === "TEXTAREA") { + actual = el.value; + } else { + actual = false; + } + expected = isRenderableAttrValue(clientValue) ? String(clientValue) : false; + } + if (actual !== expected) { + mismatchType = 4; + mismatchKey = key; + } + } + if (mismatchType != null && !isMismatchAllowed(el, mismatchType)) { + const format = (v) => v === false ? `(not rendered)` : `${mismatchKey}="${v}"`; + const preSegment = `Hydration ${MismatchTypeString[mismatchType]} mismatch on`; + const postSegment = ` + - rendered on server: ${format(actual)} + - expected on client: ${format(expected)} + Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead. + You should fix the source of the mismatch.`; + { + warn$1(preSegment, el, postSegment); + } + return true; + } + return false; +} +function toClassSet(str) { + return new Set(str.trim().split(/\s+/)); +} +function isSetEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const s of a) { + if (!b.has(s)) { + return false; + } + } + return true; +} +function toStyleMap(str) { + const styleMap = /* @__PURE__ */ new Map(); + for (const item of str.split(";")) { + let [key, value] = item.split(":"); + key = key.trim(); + value = value && value.trim(); + if (key && value) { + styleMap.set(key, value); + } + } + return styleMap; +} +function isMapEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const [key, value] of a) { + if (value !== b.get(key)) { + return false; + } + } + return true; +} +function resolveCssVars(instance, vnode, expectedMap) { + const root = instance.subTree; + if (instance.getCssVars && (vnode === root || root && root.type === Fragment && root.children.includes(vnode))) { + const cssVars = instance.getCssVars(); + for (const key in cssVars) { + expectedMap.set( + `--${getEscapedCssVarName(key, false)}`, + String(cssVars[key]) + ); + } + } + if (vnode === root && instance.parent) { + resolveCssVars(instance.parent, instance.vnode, expectedMap); + } +} +var allowMismatchAttr = "data-allow-mismatch"; +var MismatchTypeString = { + [ + 0 + /* TEXT */ + ]: "text", + [ + 1 + /* CHILDREN */ + ]: "children", + [ + 2 + /* CLASS */ + ]: "class", + [ + 3 + /* STYLE */ + ]: "style", + [ + 4 + /* ATTRIBUTE */ + ]: "attribute" +}; +function isMismatchAllowed(el, allowedType) { + if (allowedType === 0 || allowedType === 1) { + while (el && !el.hasAttribute(allowMismatchAttr)) { + el = el.parentElement; + } + } + const allowedAttr = el && el.getAttribute(allowMismatchAttr); + if (allowedAttr == null) { + return false; + } else if (allowedAttr === "") { + return true; + } else { + const list = allowedAttr.split(","); + if (allowedType === 0 && list.includes("children")) { + return true; + } + return list.includes(MismatchTypeString[allowedType]); + } +} +var requestIdleCallback = getGlobalThis().requestIdleCallback || ((cb) => setTimeout(cb, 1)); +var cancelIdleCallback = getGlobalThis().cancelIdleCallback || ((id) => clearTimeout(id)); +var hydrateOnIdle = (timeout = 1e4) => (hydrate2) => { + const id = requestIdleCallback(hydrate2, { timeout }); + return () => cancelIdleCallback(id); +}; +function elementIsVisibleInViewport(el) { + const { top, left, bottom, right } = el.getBoundingClientRect(); + const { innerHeight, innerWidth } = window; + return (top > 0 && top < innerHeight || bottom > 0 && bottom < innerHeight) && (left > 0 && left < innerWidth || right > 0 && right < innerWidth); +} +var hydrateOnVisible = (opts) => (hydrate2, forEach) => { + const ob = new IntersectionObserver((entries) => { + for (const e of entries) { + if (!e.isIntersecting) continue; + ob.disconnect(); + hydrate2(); + break; + } + }, opts); + forEach((el) => { + if (!(el instanceof Element)) return; + if (elementIsVisibleInViewport(el)) { + hydrate2(); + ob.disconnect(); + return false; + } + ob.observe(el); + }); + return () => ob.disconnect(); +}; +var hydrateOnMediaQuery = (query) => (hydrate2) => { + if (query) { + const mql = matchMedia(query); + if (mql.matches) { + hydrate2(); + } else { + mql.addEventListener("change", hydrate2, { once: true }); + return () => mql.removeEventListener("change", hydrate2); + } + } +}; +var hydrateOnInteraction = (interactions = []) => (hydrate2, forEach) => { + if (isString(interactions)) interactions = [interactions]; + let hasHydrated = false; + const doHydrate = (e) => { + if (!hasHydrated) { + hasHydrated = true; + teardown(); + hydrate2(); + e.target.dispatchEvent(new e.constructor(e.type, e)); + } + }; + const teardown = () => { + forEach((el) => { + for (const i of interactions) { + el.removeEventListener(i, doHydrate); + } + }); + }; + forEach((el) => { + for (const i of interactions) { + el.addEventListener(i, doHydrate, { once: true }); + } + }); + return teardown; +}; +function forEachElement(node, cb) { + if (isComment(node) && node.data === "[") { + let depth = 1; + let next = node.nextSibling; + while (next) { + if (next.nodeType === 1) { + const result = cb(next); + if (result === false) { + break; + } + } else if (isComment(next)) { + if (next.data === "]") { + if (--depth === 0) break; + } else if (next.data === "[") { + depth++; + } + } + next = next.nextSibling; + } + } else { + cb(node); + } +} +var isAsyncWrapper = (i) => !!i.type.__asyncLoader; +function defineAsyncComponent(source) { + if (isFunction(source)) { + source = { loader: source }; + } + const { + loader, + loadingComponent, + errorComponent, + delay = 200, + hydrate: hydrateStrategy, + timeout, + // undefined = never times out + suspensible = true, + onError: userOnError + } = source; + let pendingRequest = null; + let resolvedComp; + let retries = 0; + const retry = () => { + retries++; + pendingRequest = null; + return load(); + }; + const load = () => { + let thisRequest; + return pendingRequest || (thisRequest = pendingRequest = loader().catch((err) => { + err = err instanceof Error ? err : new Error(String(err)); + if (userOnError) { + return new Promise((resolve2, reject) => { + const userRetry = () => resolve2(retry()); + const userFail = () => reject(err); + userOnError(err, userRetry, userFail, retries + 1); + }); + } else { + throw err; + } + }).then((comp) => { + if (thisRequest !== pendingRequest && pendingRequest) { + return pendingRequest; + } + if (!comp) { + warn$1( + `Async component loader resolved to undefined. If you are using retry(), make sure to return its return value.` + ); + } + if (comp && (comp.__esModule || comp[Symbol.toStringTag] === "Module")) { + comp = comp.default; + } + if (comp && !isObject(comp) && !isFunction(comp)) { + throw new Error(`Invalid async component load result: ${comp}`); + } + resolvedComp = comp; + return comp; + })); + }; + return defineComponent({ + name: "AsyncComponentWrapper", + __asyncLoader: load, + __asyncHydrate(el, instance, hydrate2) { + let patched = false; + const doHydrate = hydrateStrategy ? () => { + const performHydrate = () => { + if (patched) { + warn$1( + `Skipping lazy hydration for component '${getComponentName(resolvedComp)}': it was updated before lazy hydration performed.` + ); + return; + } + hydrate2(); + }; + const teardown = hydrateStrategy( + performHydrate, + (cb) => forEachElement(el, cb) + ); + if (teardown) { + (instance.bum || (instance.bum = [])).push(teardown); + } + (instance.u || (instance.u = [])).push(() => patched = true); + } : hydrate2; + if (resolvedComp) { + doHydrate(); + } else { + load().then(() => !instance.isUnmounted && doHydrate()); + } + }, + get __asyncResolved() { + return resolvedComp; + }, + setup() { + const instance = currentInstance; + markAsyncBoundary(instance); + if (resolvedComp) { + return () => createInnerComp(resolvedComp, instance); + } + const onError = (err) => { + pendingRequest = null; + handleError( + err, + instance, + 13, + !errorComponent + ); + }; + if (suspensible && instance.suspense || isInSSRComponentSetup) { + return load().then((comp) => { + return () => createInnerComp(comp, instance); + }).catch((err) => { + onError(err); + return () => errorComponent ? createVNode(errorComponent, { + error: err + }) : null; + }); + } + const loaded = ref(false); + const error = ref(); + const delayed = ref(!!delay); + if (delay) { + setTimeout(() => { + delayed.value = false; + }, delay); + } + if (timeout != null) { + setTimeout(() => { + if (!loaded.value && !error.value) { + const err = new Error( + `Async component timed out after ${timeout}ms.` + ); + onError(err); + error.value = err; + } + }, timeout); + } + load().then(() => { + loaded.value = true; + if (instance.parent && isKeepAlive(instance.parent.vnode)) { + instance.parent.update(); + } + }).catch((err) => { + onError(err); + error.value = err; + }); + return () => { + if (loaded.value && resolvedComp) { + return createInnerComp(resolvedComp, instance); + } else if (error.value && errorComponent) { + return createVNode(errorComponent, { + error: error.value + }); + } else if (loadingComponent && !delayed.value) { + return createVNode(loadingComponent); + } + }; + } + }); +} +function createInnerComp(comp, parent) { + const { ref: ref2, props, children, ce } = parent.vnode; + const vnode = createVNode(comp, props, children); + vnode.ref = ref2; + vnode.ce = ce; + delete parent.vnode.ce; + return vnode; +} +var isKeepAlive = (vnode) => vnode.type.__isKeepAlive; +var KeepAliveImpl = { + name: `KeepAlive`, + // Marker for special handling inside the renderer. We are not using a === + // check directly on KeepAlive in the renderer, because importing it directly + // would prevent it from being tree-shaken. + __isKeepAlive: true, + props: { + include: [String, RegExp, Array], + exclude: [String, RegExp, Array], + max: [String, Number] + }, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const sharedContext = instance.ctx; + if (!sharedContext.renderer) { + return () => { + const children = slots.default && slots.default(); + return children && children.length === 1 ? children[0] : children; + }; + } + const cache = /* @__PURE__ */ new Map(); + const keys = /* @__PURE__ */ new Set(); + let current = null; + if (true) { + instance.__v_cache = cache; + } + const parentSuspense = instance.suspense; + const { + renderer: { + p: patch, + m: move, + um: _unmount, + o: { createElement } + } + } = sharedContext; + const storageContainer = createElement("div"); + sharedContext.activate = (vnode, container, anchor, namespace, optimized) => { + const instance2 = vnode.component; + move(vnode, container, anchor, 0, parentSuspense); + patch( + instance2.vnode, + vnode, + container, + anchor, + instance2, + parentSuspense, + namespace, + vnode.slotScopeIds, + optimized + ); + queuePostRenderEffect(() => { + instance2.isDeactivated = false; + if (instance2.a) { + invokeArrayFns(instance2.a); + } + const vnodeHook = vnode.props && vnode.props.onVnodeMounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + }, parentSuspense); + if (true) { + devtoolsComponentAdded(instance2); + } + }; + sharedContext.deactivate = (vnode) => { + const instance2 = vnode.component; + invalidateMount(instance2.m); + invalidateMount(instance2.a); + move(vnode, storageContainer, null, 1, parentSuspense); + queuePostRenderEffect(() => { + if (instance2.da) { + invokeArrayFns(instance2.da); + } + const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + instance2.isDeactivated = true; + }, parentSuspense); + if (true) { + devtoolsComponentAdded(instance2); + } + if (true) { + instance2.__keepAliveStorageContainer = storageContainer; + } + }; + function unmount(vnode) { + resetShapeFlag(vnode); + _unmount(vnode, instance, parentSuspense, true); + } + function pruneCache(filter) { + cache.forEach((vnode, key) => { + const name = getComponentName(vnode.type); + if (name && !filter(name)) { + pruneCacheEntry(key); + } + }); + } + function pruneCacheEntry(key) { + const cached = cache.get(key); + if (cached && (!current || !isSameVNodeType(cached, current))) { + unmount(cached); + } else if (current) { + resetShapeFlag(current); + } + cache.delete(key); + keys.delete(key); + } + watch2( + () => [props.include, props.exclude], + ([include, exclude]) => { + include && pruneCache((name) => matches(include, name)); + exclude && pruneCache((name) => !matches(exclude, name)); + }, + // prune post-render after `current` has been updated + { flush: "post", deep: true } + ); + let pendingCacheKey = null; + const cacheSubtree = () => { + if (pendingCacheKey != null) { + if (isSuspense(instance.subTree.type)) { + queuePostRenderEffect(() => { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + }, instance.subTree.suspense); + } else { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + } + } + }; + onMounted(cacheSubtree); + onUpdated(cacheSubtree); + onBeforeUnmount(() => { + cache.forEach((cached) => { + const { subTree, suspense } = instance; + const vnode = getInnerChild(subTree); + if (cached.type === vnode.type && cached.key === vnode.key) { + resetShapeFlag(vnode); + const da = vnode.component.da; + da && queuePostRenderEffect(da, suspense); + return; + } + unmount(cached); + }); + }); + return () => { + pendingCacheKey = null; + if (!slots.default) { + return current = null; + } + const children = slots.default(); + const rawVNode = children[0]; + if (children.length > 1) { + if (true) { + warn$1(`KeepAlive should contain exactly one component child.`); + } + current = null; + return children; + } else if (!isVNode(rawVNode) || !(rawVNode.shapeFlag & 4) && !(rawVNode.shapeFlag & 128)) { + current = null; + return rawVNode; + } + let vnode = getInnerChild(rawVNode); + if (vnode.type === Comment) { + current = null; + return vnode; + } + const comp = vnode.type; + const name = getComponentName( + isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp + ); + const { include, exclude, max } = props; + if (include && (!name || !matches(include, name)) || exclude && name && matches(exclude, name)) { + vnode.shapeFlag &= -257; + current = vnode; + return rawVNode; + } + const key = vnode.key == null ? comp : vnode.key; + const cachedVNode = cache.get(key); + if (vnode.el) { + vnode = cloneVNode(vnode); + if (rawVNode.shapeFlag & 128) { + rawVNode.ssContent = vnode; + } + } + pendingCacheKey = key; + if (cachedVNode) { + vnode.el = cachedVNode.el; + vnode.component = cachedVNode.component; + if (vnode.transition) { + setTransitionHooks(vnode, vnode.transition); + } + vnode.shapeFlag |= 512; + keys.delete(key); + keys.add(key); + } else { + keys.add(key); + if (max && keys.size > parseInt(max, 10)) { + pruneCacheEntry(keys.values().next().value); + } + } + vnode.shapeFlag |= 256; + current = vnode; + return isSuspense(rawVNode.type) ? rawVNode : vnode; + }; + } +}; +var KeepAlive = KeepAliveImpl; +function matches(pattern, name) { + if (isArray(pattern)) { + return pattern.some((p2) => matches(p2, name)); + } else if (isString(pattern)) { + return pattern.split(",").includes(name); + } else if (isRegExp(pattern)) { + pattern.lastIndex = 0; + return pattern.test(name); + } + return false; +} +function onActivated(hook, target) { + registerKeepAliveHook(hook, "a", target); +} +function onDeactivated(hook, target) { + registerKeepAliveHook(hook, "da", target); +} +function registerKeepAliveHook(hook, type, target = currentInstance) { + const wrappedHook = hook.__wdc || (hook.__wdc = () => { + let current = target; + while (current) { + if (current.isDeactivated) { + return; + } + current = current.parent; + } + return hook(); + }); + injectHook(type, wrappedHook, target); + if (target) { + let current = target.parent; + while (current && current.parent) { + if (isKeepAlive(current.parent.vnode)) { + injectToKeepAliveRoot(wrappedHook, type, target, current); + } + current = current.parent; + } + } +} +function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { + const injected = injectHook( + type, + hook, + keepAliveRoot, + true + /* prepend */ + ); + onUnmounted(() => { + remove(keepAliveRoot[type], injected); + }, target); +} +function resetShapeFlag(vnode) { + vnode.shapeFlag &= -257; + vnode.shapeFlag &= -513; +} +function getInnerChild(vnode) { + return vnode.shapeFlag & 128 ? vnode.ssContent : vnode; +} +function injectHook(type, hook, target = currentInstance, prepend = false) { + if (target) { + const hooks = target[type] || (target[type] = []); + const wrappedHook = hook.__weh || (hook.__weh = (...args) => { + pauseTracking(); + const reset = setCurrentInstance(target); + const res = callWithAsyncErrorHandling(hook, target, type, args); + reset(); + resetTracking(); + return res; + }); + if (prepend) { + hooks.unshift(wrappedHook); + } else { + hooks.push(wrappedHook); + } + return wrappedHook; + } else if (true) { + const apiName = toHandlerKey(ErrorTypeStrings$1[type].replace(/ hook$/, "")); + warn$1( + `${apiName} is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.` + ); + } +} +var createHook = (lifecycle) => (hook, target = currentInstance) => { + if (!isInSSRComponentSetup || lifecycle === "sp") { + injectHook(lifecycle, (...args) => hook(...args), target); + } +}; +var onBeforeMount = createHook("bm"); +var onMounted = createHook("m"); +var onBeforeUpdate = createHook( + "bu" +); +var onUpdated = createHook("u"); +var onBeforeUnmount = createHook( + "bum" +); +var onUnmounted = createHook("um"); +var onServerPrefetch = createHook( + "sp" +); +var onRenderTriggered = createHook("rtg"); +var onRenderTracked = createHook("rtc"); +function onErrorCaptured(hook, target = currentInstance) { + injectHook("ec", hook, target); +} +var COMPONENTS = "components"; +var DIRECTIVES = "directives"; +function resolveComponent(name, maybeSelfReference) { + return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name; +} +var NULL_DYNAMIC_COMPONENT = Symbol.for("v-ndc"); +function resolveDynamicComponent(component) { + if (isString(component)) { + return resolveAsset(COMPONENTS, component, false) || component; + } else { + return component || NULL_DYNAMIC_COMPONENT; + } +} +function resolveDirective(name) { + return resolveAsset(DIRECTIVES, name); +} +function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) { + const instance = currentRenderingInstance || currentInstance; + if (instance) { + const Component = instance.type; + if (type === COMPONENTS) { + const selfName = getComponentName( + Component, + false + ); + if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) { + return Component; + } + } + const res = ( + // local registration + // check instance[type] first which is resolved for options API + resolve(instance[type] || Component[type], name) || // global registration + resolve(instance.appContext[type], name) + ); + if (!res && maybeSelfReference) { + return Component; + } + if (warnMissing && !res) { + const extra = type === COMPONENTS ? ` +If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``; + warn$1(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`); + } + return res; + } else if (true) { + warn$1( + `resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().` + ); + } +} +function resolve(registry, name) { + return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]); +} +function renderList(source, renderItem, cache, index) { + let ret; + const cached = cache && cache[index]; + const sourceIsArray = isArray(source); + if (sourceIsArray || isString(source)) { + const sourceIsReactiveArray = sourceIsArray && isReactive(source); + let needsWrap = false; + let isReadonlySource = false; + if (sourceIsReactiveArray) { + needsWrap = !isShallow(source); + isReadonlySource = isReadonly(source); + source = shallowReadArray(source); + } + ret = new Array(source.length); + for (let i = 0, l = source.length; i < l; i++) { + ret[i] = renderItem( + needsWrap ? isReadonlySource ? toReadonly(toReactive(source[i])) : toReactive(source[i]) : source[i], + i, + void 0, + cached && cached[i] + ); + } + } else if (typeof source === "number") { + if (!Number.isInteger(source)) { + warn$1(`The v-for range expect an integer value but got ${source}.`); + } + ret = new Array(source); + for (let i = 0; i < source; i++) { + ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]); + } + } else if (isObject(source)) { + if (source[Symbol.iterator]) { + ret = Array.from( + source, + (item, i) => renderItem(item, i, void 0, cached && cached[i]) + ); + } else { + const keys = Object.keys(source); + ret = new Array(keys.length); + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + ret[i] = renderItem(source[key], key, i, cached && cached[i]); + } + } + } else { + ret = []; + } + if (cache) { + cache[index] = ret; + } + return ret; +} +function createSlots(slots, dynamicSlots) { + for (let i = 0; i < dynamicSlots.length; i++) { + const slot = dynamicSlots[i]; + if (isArray(slot)) { + for (let j = 0; j < slot.length; j++) { + slots[slot[j].name] = slot[j].fn; + } + } else if (slot) { + slots[slot.name] = slot.key ? (...args) => { + const res = slot.fn(...args); + if (res) res.key = slot.key; + return res; + } : slot.fn; + } + } + return slots; +} +function renderSlot(slots, name, props = {}, fallback, noSlotted) { + if (currentRenderingInstance.ce || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.ce) { + if (name !== "default") props.name = name; + return openBlock(), createBlock( + Fragment, + null, + [createVNode("slot", props, fallback && fallback())], + 64 + ); + } + let slot = slots[name]; + if (slot && slot.length > 1) { + warn$1( + `SSR-optimized slot function detected in a non-SSR-optimized render function. You need to mark this component with $dynamic-slots in the parent template.` + ); + slot = () => []; + } + if (slot && slot._c) { + slot._d = false; + } + openBlock(); + const validSlotContent = slot && ensureValidVNode(slot(props)); + const slotKey = props.key || // slot content array of a dynamic conditional slot may have a branch + // key attached in the `createSlots` helper, respect that + validSlotContent && validSlotContent.key; + const rendered = createBlock( + Fragment, + { + key: (slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) + // #7256 force differentiate fallback content from actual content + (!validSlotContent && fallback ? "_fb" : "") + }, + validSlotContent || (fallback ? fallback() : []), + validSlotContent && slots._ === 1 ? 64 : -2 + ); + if (!noSlotted && rendered.scopeId) { + rendered.slotScopeIds = [rendered.scopeId + "-s"]; + } + if (slot && slot._c) { + slot._d = true; + } + return rendered; +} +function ensureValidVNode(vnodes) { + return vnodes.some((child) => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children)) + return false; + return true; + }) ? vnodes : null; +} +function toHandlers(obj, preserveCaseIfNecessary) { + const ret = {}; + if (!isObject(obj)) { + warn$1(`v-on with no argument expects an object value.`); + return ret; + } + for (const key in obj) { + ret[preserveCaseIfNecessary && /[A-Z]/.test(key) ? `on:${key}` : toHandlerKey(key)] = obj[key]; + } + return ret; +} +var getPublicInstance = (i) => { + if (!i) return null; + if (isStatefulComponent(i)) return getComponentPublicInstance(i); + return getPublicInstance(i.parent); +}; +var publicPropertiesMap = ( + // Move PURE marker to new line to workaround compiler discarding it + // due to type annotation + extend(/* @__PURE__ */ Object.create(null), { + $: (i) => i, + $el: (i) => i.vnode.el, + $data: (i) => i.data, + $props: (i) => true ? shallowReadonly(i.props) : i.props, + $attrs: (i) => true ? shallowReadonly(i.attrs) : i.attrs, + $slots: (i) => true ? shallowReadonly(i.slots) : i.slots, + $refs: (i) => true ? shallowReadonly(i.refs) : i.refs, + $parent: (i) => getPublicInstance(i.parent), + $root: (i) => getPublicInstance(i.root), + $host: (i) => i.ce, + $emit: (i) => i.emit, + $options: (i) => __VUE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type, + $forceUpdate: (i) => i.f || (i.f = () => { + queueJob(i.update); + }), + $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)), + $watch: (i) => __VUE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP + }) +); +var isReservedPrefix = (key) => key === "_" || key === "$"; +var hasSetupBinding = (state, key) => state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key); +var PublicInstanceProxyHandlers = { + get({ _: instance }, key) { + if (key === "__v_skip") { + return true; + } + const { ctx, setupState, data, props, accessCache, type, appContext } = instance; + if (key === "__isVue") { + return true; + } + let normalizedProps; + if (key[0] !== "$") { + const n = accessCache[key]; + if (n !== void 0) { + switch (n) { + case 1: + return setupState[key]; + case 2: + return data[key]; + case 4: + return ctx[key]; + case 3: + return props[key]; + } + } else if (hasSetupBinding(setupState, key)) { + accessCache[key] = 1; + return setupState[key]; + } else if (data !== EMPTY_OBJ && hasOwn(data, key)) { + accessCache[key] = 2; + return data[key]; + } else if ( + // only cache other properties when instance has declared (thus stable) + // props + (normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key) + ) { + accessCache[key] = 3; + return props[key]; + } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { + accessCache[key] = 4; + return ctx[key]; + } else if (!__VUE_OPTIONS_API__ || shouldCacheAccess) { + accessCache[key] = 0; + } + } + const publicGetter = publicPropertiesMap[key]; + let cssModule, globalProperties; + if (publicGetter) { + if (key === "$attrs") { + track(instance.attrs, "get", ""); + markAttrsAccessed(); + } else if (key === "$slots") { + track(instance, "get", key); + } + return publicGetter(instance); + } else if ( + // css module (injected by vue-loader) + (cssModule = type.__cssModules) && (cssModule = cssModule[key]) + ) { + return cssModule; + } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { + accessCache[key] = 4; + return ctx[key]; + } else if ( + // global properties + globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key) + ) { + { + return globalProperties[key]; + } + } else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading + // to infinite warning loop + key.indexOf("__v") !== 0)) { + if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) { + warn$1( + `Property ${JSON.stringify( + key + )} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.` + ); + } else if (instance === currentRenderingInstance) { + warn$1( + `Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.` + ); + } + } + }, + set({ _: instance }, key, value) { + const { data, setupState, ctx } = instance; + if (hasSetupBinding(setupState, key)) { + setupState[key] = value; + return true; + } else if (setupState.__isScriptSetup && hasOwn(setupState, key)) { + warn$1(`Cannot mutate + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/nginx-vue.conf b/prompto-lab-app/SF-Chain/src/main/frontend/nginx-vue.conf new file mode 100644 index 0000000..bedeb78 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/nginx-vue.conf @@ -0,0 +1,20 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # 处理Vue Router + location / { + try_files $uri $uri/ /index.html; + } + + # 代理API到后端 + location /api/ { + proxy_pass http://backend:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/package-lock.json b/prompto-lab-app/SF-Chain/src/main/frontend/package-lock.json new file mode 100644 index 0000000..ada2dcc --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/package-lock.json @@ -0,0 +1,5535 @@ +{ + "name": "poet-ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "poet-ui", + "version": "0.0.0", + "dependencies": { + "lodash-es": "^4.17.21", + "marked": "^16.1.0", + "pinia": "^3.0.3", + "turndown": "^7.2.0", + "vue": "^3.5.17", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/lodash-es": "^4.17.12", + "@types/node": "^22.15.32", + "@types/turndown": "^5.0.5", + "@vitejs/plugin-vue": "^5.0.0", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.5.1", + "@vue/tsconfig": "^0.7.0", + "esbuild": "^0.25.6", + "eslint": "^9.29.0", + "eslint-plugin-vue": "~10.2.0", + "jiti": "^2.4.2", + "npm-run-all2": "^8.0.4", + "prettier": "3.5.3", + "typescript": "~5.8.0", + "vite": "^5.4.10", + "vite-plugin-vue-devtools": "^7.7.7", + "vue-tsc": "^2.2.10" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "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/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/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/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "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/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "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-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "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, + "license": "MIT", + "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/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "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, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "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, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", + "license": "BSD-2-Clause" + }, + "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/@pkgr/core": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", + "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "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/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.2.tgz", + "integrity": "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.16.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.4.tgz", + "integrity": "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/turndown": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", + "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "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.37.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.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/project-service": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.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": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.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/tsconfig-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "dev": true, + "license": "MIT", + "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/type-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.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/types": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "dev": true, + "license": "MIT", + "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.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.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.1.0" + }, + "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/utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.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.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz", + "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz", + "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "@vue/babel-helper-vue-transform-on": "1.4.0", + "@vue/babel-plugin-resolve-type": "1.4.0", + "@vue/shared": "^3.5.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz", + "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/parser": "^7.26.9", + "@vue/compiler-sfc": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz", + "integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/shared": "3.5.17", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz", + "integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz", + "integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@vue/compiler-core": "3.5.17", + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.17", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz", + "integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-core": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.7.tgz", + "integrity": "sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7", + "@vue/devtools-shared": "^7.7.7", + "mitt": "^3.0.1", + "nanoid": "^5.1.0", + "pathe": "^2.0.3", + "vite-hot-client": "^2.0.4" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/eslint-config-prettier": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", + "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2" + }, + "peerDependencies": { + "eslint": ">= 8.21.0", + "prettier": ">= 3.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz", + "integrity": "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.35.1", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.35.1", + "vue-eslint-parser": "^10.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz", + "integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz", + "integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/shared": "3.5.17" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz", + "integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.17", + "@vue/runtime-core": "3.5.17", + "@vue/shared": "3.5.17", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz", + "integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.17", + "@vue/shared": "3.5.17" + }, + "peerDependencies": { + "vue": "3.5.17" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz", + "integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", + "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "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/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/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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, + "license": "Python-2.0" + }, + "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, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz", + "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "dev": true, + "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" + } + ], + "license": "CC-BY-4.0" + }, + "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, + "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/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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "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, + "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/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "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/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.186", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.186.tgz", + "integrity": "sha512-lur7L4BFklgepaJxj4DqPk7vKbTEl0pajNlg2QjE5shefmlmBLm2HvQ7PMf1R/GvlevT/581cop33/quQcfX3A==", + "dev": true, + "license": "ISC" + }, + "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/error-stack-parser-es": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@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.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.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-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz", + "integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "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-vue": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.2.0.tgz", + "integrity": "sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "vue-eslint-parser": "^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "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": "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/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-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, + "license": "BSD-3-Clause", + "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, + "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/execa": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", + "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "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", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "Apache-2.0" + }, + "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, + "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.8" + }, + "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/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "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, + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "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, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "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-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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "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/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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, + "license": "MIT" + }, + "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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "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-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/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": "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/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "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/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "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/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-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/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/marked": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.1.0.tgz", + "integrity": "sha512-Me7BNa1aqrxVinDnFfvCgHh2yHvLbFvILBs899MhuBpbE5VPzpSqv7alaESfkqkgc9JNvUGH4gqwZeOzLnY8Jg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "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" + } + ], + "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/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "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", + "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/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "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-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/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "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/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "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, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "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", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", + "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.4" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "license": "MIT", + "dependencies": { + "@mixmark-io/domino": "^2.2.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/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", + "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.37.0", + "@typescript-eslint/parser": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.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/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "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/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": 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-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.7.tgz", + "integrity": "sha512-d0fIh3wRcgSlr4Vz7bAk4va1MkdqhQgj9ANE/rBhsAjOnRfTLs2ocjFMvSUOsv6SRRXU9G+VM7yMgqDb6yI4iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.7", + "@vue/devtools-kit": "^7.7.7", + "@vue/devtools-shared": "^7.7.7", + "execa": "^9.5.2", + "sirv": "^3.0.1", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", + "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.2.tgz", + "integrity": "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/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==", + "dev": 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/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz", + "integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.17", + "@vue/compiler-sfc": "3.5.17", + "@vue/runtime-dom": "3.5.17", + "@vue/server-renderer": "3.5.17", + "@vue/shared": "3.5.17" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-router/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-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "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/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/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "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/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/package.json b/prompto-lab-app/SF-Chain/src/main/frontend/package.json new file mode 100644 index 0000000..25d5393 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/package.json @@ -0,0 +1,43 @@ +{ + "name": "poet-ui", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "lint": "eslint . --fix", + "format": "prettier --write src/" + }, + "dependencies": { + "lodash-es": "^4.17.21", + "marked": "^16.1.0", + "pinia": "^3.0.3", + "turndown": "^7.2.0", + "vue": "^3.5.17", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@tsconfig/node22": "^22.0.2", + "@types/lodash-es": "^4.17.12", + "@types/node": "^22.15.32", + "@types/turndown": "^5.0.5", + "@vitejs/plugin-vue": "^5.0.0", + "@vue/eslint-config-prettier": "^10.2.0", + "@vue/eslint-config-typescript": "^14.5.1", + "@vue/tsconfig": "^0.7.0", + "esbuild": "^0.25.6", + "eslint": "^9.29.0", + "eslint-plugin-vue": "~10.2.0", + "jiti": "^2.4.2", + "npm-run-all2": "^8.0.4", + "prettier": "3.5.3", + "typescript": "~5.8.0", + "vite": "^5.4.10", + "vite-plugin-vue-devtools": "^7.7.7", + "vue-tsc": "^2.2.10" + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/public/favicon.ico b/prompto-lab-app/SF-Chain/src/main/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/prompto-lab-ui/src/assets/icons/anthropic.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/anthropic.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/anthropic.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/anthropic.svg diff --git a/prompto-lab-ui/src/assets/icons/deepseek.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/deepseek.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/deepseek.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/deepseek.svg diff --git a/prompto-lab-ui/src/assets/icons/default.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/default.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/default.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/default.svg diff --git a/prompto-lab-ui/src/assets/icons/doubao.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/doubao.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/doubao.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/doubao.svg diff --git a/prompto-lab-ui/src/assets/icons/google.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/google.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/google.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/google.svg diff --git a/prompto-lab-ui/src/assets/icons/openai.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/openai.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/openai.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/openai.svg diff --git a/prompto-lab-ui/src/assets/icons/qianwen.svg b/prompto-lab-app/SF-Chain/src/main/frontend/public/icons/qianwen.svg similarity index 100% rename from prompto-lab-ui/src/assets/icons/qianwen.svg rename to prompto-lab-app/SF-Chain/src/main/frontend/public/icons/qianwen.svg diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/App.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/App.vue new file mode 100644 index 0000000..15663fe --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/App.vue @@ -0,0 +1,544 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/base.css b/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/logo.svg b/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/main.css b/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/main.css new file mode 100644 index 0000000..003af53 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/assets/main.css @@ -0,0 +1,82 @@ +@import './base.css'; + +/* 重置默认样式 */ +* { + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #fafafa; +} + +#app { + min-height: 100vh; + width: 100%; +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 通用工具类 */ +.container { + max-width: 1400px; + margin: 0 auto; + padding: 0 20px; +} + +.text-center { + text-align: center; +} + +.mt-4 { + margin-top: 1rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.p-4 { + padding: 1rem; +} + +/* 移除了导致布局问题的样式 */ +a { + text-decoration: none; + color: #667eea; + transition: 0.3s; +} + +a:hover { + color: #764ba2; +} + +/* 确保全屏布局 */ +@media (min-width: 1024px) { + #app { + width: 100%; + max-width: none; + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/AICallLogViewer.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/AICallLogViewer.vue new file mode 100644 index 0000000..ab7492a --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/AICallLogViewer.vue @@ -0,0 +1,855 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/AiNodeConfig.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/AiNodeConfig.vue new file mode 100644 index 0000000..d25a6bb --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/AiNodeConfig.vue @@ -0,0 +1,1644 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/ApiInfoConfig.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/ApiInfoConfig.vue new file mode 100644 index 0000000..63c55de --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/ApiInfoConfig.vue @@ -0,0 +1,1545 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/HeaderBar.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/HeaderBar.vue new file mode 100644 index 0000000..1c965d8 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/HeaderBar.vue @@ -0,0 +1,564 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/JsonViewer.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/JsonViewer.vue new file mode 100644 index 0000000..816cceb --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/JsonViewer.vue @@ -0,0 +1,559 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/LogDetailModal.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/LogDetailModal.vue new file mode 100644 index 0000000..89e368c --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/LogDetailModal.vue @@ -0,0 +1,1373 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/SystemManagement.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/SystemManagement.vue new file mode 100644 index 0000000..5d16d94 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/SystemManagement.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/components/Toast.vue b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/Toast.vue new file mode 100644 index 0000000..481e87a --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/components/Toast.vue @@ -0,0 +1,546 @@ + + + + + diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/main.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/main.ts new file mode 100644 index 0000000..9c83fc5 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/main.ts @@ -0,0 +1,22 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' +import { apiClient } from './services/apiUtils' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +// 初始化API配置 +apiClient.initialize().then(() => { + app.mount('#app') +}).catch(error => { + console.error('Failed to initialize API config:', error) + // 即使配置失败也要启动应用,使用默认配置 + app.mount('#app') +}) diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/router/index.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/router/index.ts new file mode 100644 index 0000000..fec90c5 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/router/index.ts @@ -0,0 +1,15 @@ +import { createRouter, createWebHistory } from 'vue-router' +import App from '../App.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: App + } + ] +}) + +export default router diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiCallLogApi.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiCallLogApi.ts new file mode 100644 index 0000000..6d4751c --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiCallLogApi.ts @@ -0,0 +1,53 @@ +import { API_CONFIG } from './apiConfig' +import { apiJsonRequest } from './apiUtils' +import type { AICallLogSummary, AICallLog, LogStatistics, ApiResponse } from '@/types/system' + +export const aiCallLogApi = { + // 获取所有日志摘要(轻量级) + async getAllLogSummaries(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/ai-logs`, { + method: 'GET', + requireAuth: true + }) + }, + + // 根据调用ID获取完整日志详情 + async getFullLog(callId: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/ai-logs/${encodeURIComponent(callId)}`, { + method: 'GET', + requireAuth: true + }) + }, + + // 根据操作类型获取日志摘要 + async getLogSummariesByOperation(operationType: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/ai-logs/operation/${encodeURIComponent(operationType)}`, { + method: 'GET', + requireAuth: true + }) + }, + + // 根据模型名称获取日志摘要 + async getLogSummariesByModel(modelName: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/ai-logs/model/${encodeURIComponent(modelName)}`, { + method: 'GET', + requireAuth: true + }) + }, + + // 获取统计信息 + async getStatistics(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/ai-logs/statistics`, { + method: 'GET', + requireAuth: true + }) + }, + + // 清空所有日志 + async clearLogs(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/ai-logs`, { + method: 'DELETE', + requireAuth: true + }) + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiModelApi.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiModelApi.ts new file mode 100644 index 0000000..54b6ce4 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiModelApi.ts @@ -0,0 +1,49 @@ +import { API_CONFIG } from './apiConfig' +import { apiJsonRequest } from './apiUtils' +import type { ModelConfigData, ApiResponse, ModelsResponse, TestConnectionResponse } from '@/types/system' + +export const aiModelApi = { + // 获取所有模型配置 + async getAllModels(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/models/list`, { + method: 'GET', + requireAuth: true + }) + }, + + // 获取单个模型配置 - 改为POST请求 + async getModel(modelName: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/models/get`, { + method: 'POST', + body: JSON.stringify({ modelName }), + requireAuth: true + }) + }, + + // 保存模型配置(创建或更新) + async saveModel(modelName: string, config: ModelConfigData): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/models/save`, { + method: 'POST', + body: JSON.stringify({ ...config, modelName }), + requireAuth: true + }) + }, + + // 删除模型配置 - 改为POST请求 + async deleteModel(modelName: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/models/delete`, { + method: 'POST', + body: JSON.stringify({ modelName }), + requireAuth: true + }) + }, + + // 测试模型连接 - 改为POST请求 + async testModel(modelName: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/models/test`, { + method: 'POST', + body: JSON.stringify({ modelName }), + requireAuth: true + }) + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiOperationApi.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiOperationApi.ts new file mode 100644 index 0000000..cbd0b83 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/aiOperationApi.ts @@ -0,0 +1,55 @@ +import { API_CONFIG } from './apiConfig' +import { apiJsonRequest } from './apiUtils' +import type { OperationConfigData, OperationsResponse, OperationDetailResponse, ApiResponse } from '@/types/system' + +export const aiOperationApi = { + // 获取所有操作配置 + async getAllOperations(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/operations`, { + method: 'GET', + requireAuth: true + }) + }, + + // 获取单个操作配置 - 改为POST请求 + async getOperation(operationType: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/operations/get`, { + method: 'POST', + body: JSON.stringify({ operationType }), + requireAuth: true + }) + }, + + // 保存操作配置 - 修正数据结构 + async saveOperationConfig(operationType: string, config: OperationConfigData): Promise { + // 确保operationType设置在config对象中 + const configWithType = { + ...config, + operationType: operationType + } + + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/operations/save`, { + method: 'POST', + body: JSON.stringify(configWithType), + requireAuth: true + }) + }, + + // 设置单个操作模型映射 - 改为请求体参数 + async setOperationMapping(operationType: string, modelName: string): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/operations/mapping`, { + method: 'POST', + body: JSON.stringify({ operationType, modelName }), + requireAuth: true + }) + }, + + // 批量设置操作模型映射 + async setOperationMappings(mappings: Record): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/operations/mappings`, { + method: 'POST', + body: JSON.stringify({ mappings }), + requireAuth: true + }) + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiConfig.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiConfig.ts new file mode 100644 index 0000000..5371e7c --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiConfig.ts @@ -0,0 +1,65 @@ +// API配置 +interface ApiConfig { + baseUrl: string; + endpoints: { + AI_MODELS: string; + AI_OPERATIONS: string; + AI_CALL_LOGS: string; + AI_SYSTEM: string; + }; +} + +let apiConfig: ApiConfig | null = null; + +// 动态获取API配置 +export async function getApiConfig(): Promise { + if (apiConfig) { + return apiConfig; + } + + try { + // 在生产环境中,从当前域名获取配置 + const configUrl = import.meta.env.DEV + ? 'http://localhost:5001/api/sf-chain/config/api-info' + : '/sf-chain/config/api-info'; + + const response = await fetch(configUrl); + const config = await response.json(); + + apiConfig = { + baseUrl: config.baseUrl || '', + endpoints: config.endpoints || { + AI_MODELS: '/sf-chain/models', + AI_OPERATIONS: '/sf-chain/operations', + AI_CALL_LOGS: '/sf-chain/call-logs', + AI_SYSTEM: '/sf-chain/system' + } + }; + + return apiConfig; + } catch (error) { + console.warn('Failed to fetch API config, using defaults:', error); + // 降级到默认配置 + apiConfig = { + baseUrl: import.meta.env.DEV ? 'http://localhost:5001/api' : '', + endpoints: { + AI_MODELS: '/sf-chain/models', + AI_OPERATIONS: '/sf-chain/operations', + AI_CALL_LOGS: '/sf-chain/call-logs', + AI_SYSTEM: '/sf-chain/system' + } + }; + return apiConfig; + } +} + +// 兼容性导出 +export const API_CONFIG = { + BASE_URL: '', // 将在运行时动态设置 + ENDPOINTS: { + AI_MODELS: '/sf-chain/models', + AI_OPERATIONS: '/sf-chain/operations', + AI_CALL_LOGS: '/sf-chain/call-logs', + AI_SYSTEM: '/sf-chain/system' + } +}; diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiUtils.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiUtils.ts new file mode 100644 index 0000000..8084462 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/apiUtils.ts @@ -0,0 +1,263 @@ +import { toast } from '@/utils/toast' +import { getApiConfig } from './apiConfig'; + +// API请求工具函数 +export interface RequestOptions { + method?: string + headers?: Record + body?: string + signal?: AbortSignal + requireAuth?: boolean + isAIRequest?: boolean +} + +// 获取存储的token +export function getAuthToken(): string | null { + return localStorage.getItem('token') +} + +// 设置token +export function setAuthToken(token: string): void { + localStorage.setItem('token', token) +} + +// 清除认证信息 +function clearAuthData() { + localStorage.removeItem('token') + localStorage.removeItem('userInfo') +} + +// 处理认证失败 +function handleAuthFailure() { + clearAuthData() + toast.error({ + title: '认证失败', + message: '请检查您的token是否正确', + duration: 4000 + }) +} + +// 处理需要登录 +function handleRequireLogin() { + toast.error({ + title: '需要认证', + message: '请在页面顶部输入有效的token', + duration: 4000 + }) +} + +// 统一的请求函数 +export async function apiRequest(url: string, options: RequestOptions = {}): Promise { + const { + method = 'GET', + headers = {}, + body, + signal, + requireAuth = true, + isAIRequest = false + } = options + + const requestHeaders: Record = { + 'Content-Type': 'application/json', + ...headers + } + + // 判断是否是AI请求 + const isAI = isAIRequest || url.includes('/sf-chain/') + + if (isAI || requireAuth) { + // AI接口和普通接口都统一使用Authorization token + const token = getAuthToken() + if (token) { + requestHeaders['Authorization'] = `${token}` + } else { + handleRequireLogin() + throw new Error('未提供认证token') + } + + if (isAI) { + console.log('🔍 AI请求调试信息:') + console.log('URL:', url) + console.log('Authorization Token:', token) + console.log('请求头:', requestHeaders) + } + } + + try { + console.log('📤 发送请求:', { url, method, headers: requestHeaders }) + const response = await fetch(url, { + method, + headers: requestHeaders, + body, + signal + }) + + if (response.status === 401) { + if (isAI) { + toast.error({ + title: 'AI接口认证失败', + message: '缺少Authorization请求头', + duration: 4000 + }) + } else { + handleAuthFailure() + } + throw new Error('认证失败') + } + + if (response.status === 403) { + if (isAI) { + toast.error({ + title: 'AI接口权限不足', + message: 'Authorization验证失败', + duration: 4000 + }) + } else { + toast.error({ + title: '权限不足', + message: '您没有权限执行此操作', + duration: 4000 + }) + } + throw new Error('权限不足') + } + + if (response.status === 404) { + toast.error({ + title: '资源不存在', + message: '请求的资源不存在或已被删除', + duration: 3000 + }) + throw new Error('资源不存在') + } + + if (response.status >= 500) { + toast.error({ + title: '服务器错误', + message: '服务器暂时无法处理您的请求,请稍后重试', + duration: 5000, + closable: true + }) + throw new Error('服务器错误') + } + + return response + } catch (error: unknown) { + const err = error as Error + if (err.name === 'TypeError' && err.message.includes('fetch')) { + toast.error({ + title: '网络连接失败', + message: '无法连接到服务器,请检查网络连接', + duration: 5000, + closable: true + }) + } else if (err.name === 'AbortError') { + console.log('请求已取消') + } else if (!err.message.includes('认证失败') && + !err.message.includes('权限不足') && + !err.message.includes('资源不存在') && + !err.message.includes('未提供认证token')) { + toast.error({ + title: '请求失败', + message: err.message || '请求处理失败,请稍后重试', + duration: 4000 + }) + } + throw error + } +} + +// 便捷的JSON请求函数 +export async function apiJsonRequest( + url: string, + options: RequestOptions = {} +): Promise { + const response = await apiRequest(url, options) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + try { + return await response.json() + } catch { + toast.error({ + title: '数据解析失败', + message: '服务器返回的数据格式有误', + duration: 3000 + }) + throw new Error('数据解析失败') + } +} + +// AI接口专用请求函数 +export async function aiApiRequest( + url: string, + options: RequestOptions = {} +): Promise { + return apiJsonRequest(url, { ...options, isAIRequest: true }) +} + +export class ApiClient { + private static instance: ApiClient; + private baseUrl: string = ''; + + private constructor() {} + + public static getInstance(): ApiClient { + if (!ApiClient.instance) { + ApiClient.instance = new ApiClient(); + } + return ApiClient.instance; + } + + public async initialize(): Promise { + const config = await getApiConfig(); + this.baseUrl = config.baseUrl; + } + + public async request(endpoint: string, options: RequestInit = {}): Promise { + if (!this.baseUrl) { + await this.initialize(); + } + + const url = `${this.baseUrl}${endpoint}`; + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + ...options, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); + } + + public async get(endpoint: string): Promise { + return this.request(endpoint, { method: 'GET' }); + } + + public async post(endpoint: string, data?: any): Promise { + return this.request(endpoint, { + method: 'POST', + body: data ? JSON.stringify(data) : undefined, + }); + } + + public async put(endpoint: string, data?: any): Promise { + return this.request(endpoint, { + method: 'PUT', + body: data ? JSON.stringify(data) : undefined, + }); + } + + public async delete(endpoint: string): Promise { + return this.request(endpoint, { method: 'DELETE' }); + } +} + +export const apiClient = ApiClient.getInstance(); diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/services/systemApi.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/systemApi.ts new file mode 100644 index 0000000..c28f5f1 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/services/systemApi.ts @@ -0,0 +1,37 @@ +import { API_CONFIG } from './apiConfig' +import { apiJsonRequest } from './apiUtils' +import type { SystemOverview, ApiResponse } from '@/types/system' + +export const systemApi = { + // 获取系统概览信息 + async getSystemOverview(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/system/overview`, { + method: 'GET', + requireAuth: true + }) + }, + + // 创建系统配置备份 + async createBackup(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/system/backup`, { + method: 'POST', + requireAuth: true + }) + }, + + // 刷新系统配置 + async refreshSystem(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/system/refresh`, { + method: 'POST', + requireAuth: true + }) + }, + + // 重置系统配置 + async resetSystem(): Promise { + return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf-chain/system/reset`, { + method: 'POST', + requireAuth: true + }) + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/types/system.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/types/system.ts new file mode 100644 index 0000000..ee0d08c --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/types/system.ts @@ -0,0 +1,127 @@ +export interface SystemOverview { + totalModels: number + enabledModels: number + totalOperations: number + enabledOperations: number // 添加这个字段 + configuredOperations: number + lastUpdate: number +} + +export interface ApiResponse { + success: boolean + message: string + data?: T + timestamp?: number + code?: number +} + +export interface ModelConfigData { + modelName: string + baseUrl: string + apiKey: string + defaultMaxTokens?: number + defaultTemperature?: number + supportStream?: boolean + supportJsonOutput?: boolean + supportThinking?: boolean + additionalHeaders?: Record + description?: string + provider?: string + enabled?: boolean +} + +export interface ApiResponse { + success: boolean + message: string + data?: T + modelName?: string + validated?: boolean + error?: string +} + +export interface ModelsResponse { + models: Record + groupedByProvider: Record + total: number +} + +export interface TestConnectionResponse { + success: boolean + message: string + modelName: string +} + +export interface ModelListResponse { + models: Record + groupedByProvider: Record + total: number +} + +export interface OperationConfigData { + operationType: string + description?: string + enabled?: boolean + maxTokens?: number + temperature?: number + timeout?: number + retryCount?: number + jsonOutput?: boolean + streamOutput?: boolean + thinkingMode?: boolean + promptPrefix?: string + promptSuffix?: string + systemPrompt?: string + outputFormat?: string + customParams?: Record + modelName?: string +} + +export interface OperationsResponse { + mappings: Record + configs: Record + totalOperations: number + configuredOperations: number +} + +export interface OperationDetailResponse { + operation: OperationConfigData + associatedModel?: ModelConfigData +} + +// AI调用日志相关类型定义 +// 更新 AICallLogSummary 接口 +export interface AICallLogSummary { + callId: string + operationType: string + modelName: string + callTime: string // 后端返回LocalDateTime转换为字符串 + duration: number + status: string // 后端返回CallStatus枚举转换为字符串 + errorMessage: string | null + frequency: number + lastAccessTime: string + requestParams?: { + maxTokens: number + temperature: number + jsonOutput: boolean + thinking: boolean + } +} + +// 新增 AICallLog 接口 +export interface AICallLog extends AICallLogSummary { + input?: Record + prompt?: string + rawResponse?: string + output?: Record +} + +export interface LogStatistics { + totalCalls: number + successfulCalls: number + failedCalls: number + totalTokensUsed: number + averageResponseTime: number + callsByOperation: Record + callsByModel: Record +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/utils/aiProviders.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/utils/aiProviders.ts new file mode 100644 index 0000000..a275b5c --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/utils/aiProviders.ts @@ -0,0 +1,215 @@ +// AI提供商相关工具函数 +export interface AIProvider { + key: string + name: string + icon: string + // 新增:用于表单显示的配置 + displayOrder: number + category?: string + description?: string + // 新增:默认配置建议 + defaultConfig?: { + baseUrl?: string + maxTokens?: number + temperature?: number + supportStream?: boolean + supportJsonOutput?: boolean + supportThinking?: boolean + } +} + +// AI提供商配置 - 增强版 +const AI_PROVIDERS: Record = { + openai: { + key: 'openai', + name: 'OpenAI', + icon: '/icons/openai.svg', + displayOrder: 1, + category: 'commercial', + description: 'OpenAI GPT系列模型', + defaultConfig: { + baseUrl: 'https://api.openai.com/v1', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: true, + supportThinking: false + } + }, + anthropic: { + key: 'anthropic', + name: 'Anthropic', + icon: '/icons/anthropic.svg', + displayOrder: 2, + category: 'commercial', + description: 'Anthropic Claude系列模型', + defaultConfig: { + baseUrl: 'https://api.anthropic.com/v1', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: true, + supportThinking: true + } + }, + google: { + key: 'google', + name: 'Google', + icon: '/icons/google.svg', + displayOrder: 3, + category: 'commercial', + description: 'Google Gemini系列模型', + defaultConfig: { + baseUrl: 'https://generativelanguage.googleapis.com/v1', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: true, + supportThinking: false + } + }, + deepseek: { + key: 'deepseek', + name: 'DeepSeek', + icon: '/icons/deepseek.svg', + displayOrder: 4, + category: 'domestic', + description: 'DeepSeek系列模型', + defaultConfig: { + baseUrl: 'https://api.deepseek.com/v1', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: true, + supportThinking: true + } + }, + doubao: { + key: 'doubao', + name: '豆包', + icon: '/icons/doubao.svg', + displayOrder: 5, + category: 'domestic', + description: '字节跳动豆包系列模型', + defaultConfig: { + baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: true, + supportThinking: false + } + }, + qianwen: { + key: 'qianwen', + name: '千问', + icon: '/icons/qianwen.svg', + displayOrder: 6, + category: 'domestic', + description: '阿里云千问系列模型', + defaultConfig: { + baseUrl: 'https://dashscope.aliyuncs.com/api/v1', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: true, + supportThinking: false + } + }, + other: { + key: 'other', + name: '其他', + icon: '/icons/default.svg', + displayOrder: 999, + category: 'custom', + description: '自定义或其他提供商', + defaultConfig: { + baseUrl: '', + maxTokens: 4096, + temperature: 0.7, + supportStream: true, + supportJsonOutput: false, + supportThinking: false + } + } +} + +/** + * 获取提供商名称 + */ +export const getProviderName = (provider: string): string => { + return AI_PROVIDERS[provider]?.name || AI_PROVIDERS.other.name +} + +/** + * 获取提供商图标路径 + */ +export const getProviderIcon = (provider: string): string => { + return AI_PROVIDERS[provider]?.icon || AI_PROVIDERS.other.icon +} + +/** + * 根据模型名称推断提供商 + */ +export const getProviderFromModel = (modelName: string): string => { + const model = modelName.toLowerCase() + if (model.includes('gpt') || model.includes('openai')) return 'openai' + if (model.includes('claude') || model.includes('anthropic')) return 'anthropic' + if (model.includes('gemini') || model.includes('google')) return 'google' + if (model.includes('deepseek')) return 'deepseek' + if (model.includes('doubao') || model.includes('豆包')) return 'doubao' + if (model.includes('qwen') || model.includes('千问') || model.includes('qianwen')) return 'qianwen' + return 'other' +} + +/** + * 获取所有提供商列表(按显示顺序排序) + */ +export const getAllProviders = (): AIProvider[] => { + return Object.values(AI_PROVIDERS).sort((a, b) => a.displayOrder - b.displayOrder) +} + +/** + * 按分类获取提供商 + */ +export const getProvidersByCategory = (): Record => { + const categories: Record = {} + const providers = getAllProviders() + + providers.forEach(provider => { + const category = provider.category || 'other' + if (!categories[category]) { + categories[category] = [] + } + categories[category].push(provider) + }) + + return categories +} + +/** + * 获取提供商的默认配置 + */ +export const getProviderDefaultConfig = (provider: string) => { + return AI_PROVIDERS[provider]?.defaultConfig || AI_PROVIDERS.other.defaultConfig +} + +/** + * 获取分类显示名称 + */ +export const getCategoryName = (category: string): string => { + const categoryNames: Record = { + commercial: '商业模型', + domestic: '国产模型', + custom: '自定义', + other: '其他' + } + return categoryNames[category] || category +} + +/** + * 提供商顺序(用于排序显示) + */ +export const getProviderOrder = (): string[] => { + return getAllProviders().map(provider => provider.key) +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/src/utils/toast.ts b/prompto-lab-app/SF-Chain/src/main/frontend/src/utils/toast.ts new file mode 100644 index 0000000..3d764f2 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/src/utils/toast.ts @@ -0,0 +1,189 @@ +import { createApp, type App } from 'vue' +import Toast from '@/components/Toast.vue' + +interface ToastOptions { + type?: 'success' | 'error' | 'warning' | 'info' + title?: string + message: string + duration?: number + closable?: boolean + showProgress?: boolean + position?: 'top' | 'center' | 'bottom' + offset?: number +} + +// 预设的停留时间配置 +const DURATION_PRESETS = { + success: 3000, + error: 6000, + warning: 4000, + info: 3000, + short: 2000, + medium: 4000, + long: 6000, + persistent: 0 // 不自动关闭 +} + +class ToastManager { + private toasts: { app: App; container: HTMLElement; id: string }[] = [] + private toastCounter = 0 + + private show(options: ToastOptions) { + const id = `toast-${++this.toastCounter}` + const container = document.createElement('div') + + // 计算位置偏移 + const offset = this.calculateOffset(options.position || 'top') + + const app = createApp(Toast, { + ...options, + id, + offset, + onClose: () => { + this.remove(app) + } + }) + + app.mount(container) + this.toasts.push({ app, container, id }) + + return app + } + + private calculateOffset(position: string): number { + const samePositionToasts = this.toasts.filter(toast => { + // 这里需要从toast实例中获取position,简化处理 + return true + }) + return samePositionToasts.length * 80 // 每个toast间隔80px + } + + private remove(targetApp: App) { + const index = this.toasts.findIndex(({ app }) => app === targetApp) + if (index > -1) { + const { app, container } = this.toasts[index] + this.toasts.splice(index, 1) + app.unmount() + if (container.parentNode) { + container.parentNode.removeChild(container) + } + // 重新计算其他toast的位置 + this.updatePositions() + } + } + + private updatePositions() { + // 更新剩余toast的位置 + this.toasts.forEach((toast, index) => { + // 这里可以通过expose的方法更新位置 + }) + } + + // 支持预设时长 + success(messageOrOptions: string | ToastOptions, title?: string, options?: Partial) { + if (typeof messageOrOptions === 'string') { + return this.show({ + type: 'success', + title: title || '成功', + message: messageOrOptions, + duration: DURATION_PRESETS.success, + ...options + }) + } else { + return this.show({ + type: 'success', + title: messageOrOptions.title || '成功', + duration: DURATION_PRESETS.success, + ...messageOrOptions + }) + } + } + + error(messageOrOptions: string | ToastOptions, title?: string, options?: Partial) { + if (typeof messageOrOptions === 'string') { + return this.show({ + type: 'error', + title: title || '错误', + message: messageOrOptions, + duration: DURATION_PRESETS.error, + ...options + }) + } else { + return this.show({ + type: 'error', + title: messageOrOptions.title || '错误', + duration: DURATION_PRESETS.error, + ...messageOrOptions + }) + } + } + + warning(messageOrOptions: string | ToastOptions, title?: string, options?: Partial) { + if (typeof messageOrOptions === 'string') { + return this.show({ + type: 'warning', + title: title || '警告', + message: messageOrOptions, + duration: DURATION_PRESETS.warning, + ...options + }) + } else { + return this.show({ + type: 'warning', + title: messageOrOptions.title || '警告', + duration: DURATION_PRESETS.warning, + ...messageOrOptions + }) + } + } + + info(messageOrOptions: string | ToastOptions, title?: string, options?: Partial) { + if (typeof messageOrOptions === 'string') { + return this.show({ + type: 'info', + title: title || '提示', + message: messageOrOptions, + duration: DURATION_PRESETS.info, + ...options + }) + } else { + return this.show({ + type: 'info', + title: messageOrOptions.title || '提示', + duration: DURATION_PRESETS.info, + ...messageOrOptions + }) + } + } + + // 新增便捷方法 + quick(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') { + return this.show({ + type, + message, + duration: DURATION_PRESETS.short, + showProgress: false, + closable: false + }) + } + + persistent(message: string, title?: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') { + return this.show({ + type, + title, + message, + duration: 0, + closable: true + }) + } + + clear() { + this.toasts.forEach(({ app }) => { + this.remove(app) + }) + } +} + +export const toast = new ToastManager() +export type { ToastOptions } +export { DURATION_PRESETS } diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.app.json b/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.app.json new file mode 100644 index 0000000..913b8f2 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.json b/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.node.json b/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.node.json new file mode 100644 index 0000000..a83dfc9 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node22/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/frontend/vite.config.ts b/prompto-lab-app/SF-Chain/src/main/frontend/vite.config.ts new file mode 100644 index 0000000..4217010 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/frontend/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +}) diff --git a/prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring.factories b/prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..5ea5318 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configuration +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.suifeng.sfchain.config.SfChainAutoConfiguration \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..3255854 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.suifeng.sfchain.config.SfChainAutoConfiguration \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/migration/v1_mysql.sql b/prompto-lab-app/SF-Chain/src/main/resources/migration/v1_mysql.sql new file mode 100644 index 0000000..9832f42 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/migration/v1_mysql.sql @@ -0,0 +1,63 @@ +-- 创建AI相关数据表 (MySQL版本) +-- 作者: suifeng +-- 日期: 2025/1/27 + +-- 创建模型配置表 +CREATE TABLE IF NOT EXISTS sfchain_model_configs ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + model_name VARCHAR(100) NOT NULL UNIQUE, + provider VARCHAR(50) NOT NULL, + api_key VARCHAR(500), + base_url VARCHAR(500), + enabled BOOLEAN NOT NULL DEFAULT true, + description VARCHAR(1000), + custom_params JSON, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 创建操作配置表(直接包含模型名称字段) +CREATE TABLE IF NOT EXISTS sfchain_operation_configs ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + operation_type VARCHAR(100) NOT NULL UNIQUE, + description VARCHAR(1000), + enabled BOOLEAN NOT NULL DEFAULT true, + max_tokens INTEGER, + temperature DOUBLE, + json_output BOOLEAN NOT NULL DEFAULT false, + thinking_mode BOOLEAN NOT NULL DEFAULT false, + custom_params JSON, + model_name VARCHAR(100), -- 直接在操作配置表中存储关联的模型名称 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 创建索引 +CREATE INDEX idx_sfchain_model_configs_model_name ON sfchain_model_configs(model_name); +CREATE INDEX idx_sfchain_model_configs_provider ON sfchain_model_configs(provider); +CREATE INDEX idx_sfchain_model_configs_enabled ON sfchain_model_configs(enabled); +CREATE INDEX idx_sfchain_operation_configs_operation_type ON sfchain_operation_configs(operation_type); +CREATE INDEX idx_sfchain_operation_configs_enabled ON sfchain_operation_configs(enabled); +CREATE INDEX idx_sfchain_operation_configs_model ON sfchain_operation_configs(model_name); + +-- 添加外键约束 +ALTER TABLE sfchain_operation_configs + ADD CONSTRAINT fk_operation_model + FOREIGN KEY (model_name) REFERENCES sfchain_model_configs(model_name) + ON DELETE SET NULL; + +-- 仅在表为空时插入示例数据 +INSERT INTO sfchain_model_configs (model_name, provider, enabled, description) +SELECT * FROM ( + SELECT 'deepseek-chat' as model_name, 'deepseek' as provider, true as enabled, 'DeepSeek Chat模型' as description + UNION ALL + SELECT 'gpt-4o', 'openai', true, 'OpenAI GPT-4o模型' + UNION ALL + SELECT 'siliconflow-qwen', 'siliconflow', true, 'SiliconFlow Qwen模型' +) AS tmp +WHERE NOT EXISTS ( + SELECT 1 FROM sfchain_model_configs LIMIT 1 +); + +-- 提交事务 +COMMIT; \ No newline at end of file diff --git a/prompto-lab-app/src/main/resources/db/migration/V1__Create_AI_Tables.sql b/prompto-lab-app/SF-Chain/src/main/resources/migration/v1_postgresql.sql similarity index 61% rename from prompto-lab-app/src/main/resources/db/migration/V1__Create_AI_Tables.sql rename to prompto-lab-app/SF-Chain/src/main/resources/migration/v1_postgresql.sql index bc8fb9b..4fa4f01 100644 --- a/prompto-lab-app/src/main/resources/db/migration/V1__Create_AI_Tables.sql +++ b/prompto-lab-app/SF-Chain/src/main/resources/migration/v1_postgresql.sql @@ -3,7 +3,7 @@ -- 日期: 2025/1/27 -- 创建模型配置表 -CREATE TABLE IF NOT EXISTS ai_model_configs ( +CREATE TABLE IF NOT EXISTS sfchain_model_configs ( id BIGSERIAL PRIMARY KEY, model_name VARCHAR(100) NOT NULL UNIQUE, provider VARCHAR(50) NOT NULL, @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS ai_model_configs ( ); -- 创建操作配置表(直接包含模型名称字段) -CREATE TABLE IF NOT EXISTS ai_operation_configs ( +CREATE TABLE IF NOT EXISTS sfchain_operation_configs ( id BIGSERIAL PRIMARY KEY, operation_type VARCHAR(100) NOT NULL UNIQUE, description VARCHAR(1000), @@ -33,17 +33,17 @@ CREATE TABLE IF NOT EXISTS ai_operation_configs ( ); -- 创建索引 -CREATE INDEX IF NOT EXISTS idx_model_configs_model_name ON ai_model_configs(model_name); -CREATE INDEX IF NOT EXISTS idx_model_configs_provider ON ai_model_configs(provider); -CREATE INDEX IF NOT EXISTS idx_model_configs_enabled ON ai_model_configs(enabled); -CREATE INDEX IF NOT EXISTS idx_operation_configs_operation_type ON ai_operation_configs(operation_type); -CREATE INDEX IF NOT EXISTS idx_operation_configs_enabled ON ai_operation_configs(enabled); -CREATE INDEX IF NOT EXISTS idx_operation_configs_model ON ai_operation_configs(model_name); +CREATE INDEX IF NOT EXISTS idx_sfchain_model_configs_model_name ON sfchain_model_configs(model_name); +CREATE INDEX IF NOT EXISTS idx_sfchain_model_configs_provider ON sfchain_model_configs(provider); +CREATE INDEX IF NOT EXISTS idx_sfchain_model_configs_enabled ON sfchain_model_configs(enabled); +CREATE INDEX IF NOT EXISTS idx_sfchain_operation_configs_operation_type ON sfchain_operation_configs(operation_type); +CREATE INDEX IF NOT EXISTS idx_sfchain_operation_configs_enabled ON sfchain_operation_configs(enabled); +CREATE INDEX IF NOT EXISTS idx_sfchain_operation_configs_model ON sfchain_operation_configs(model_name); -- 添加外键约束 -ALTER TABLE ai_operation_configs +ALTER TABLE sfchain_operation_configs ADD CONSTRAINT fk_operation_model - FOREIGN KEY (model_name) REFERENCES ai_model_configs(model_name) + FOREIGN KEY (model_name) REFERENCES sfchain_model_configs(model_name) ON DELETE SET NULL; -- 创建更新时间戳的触发器函数 @@ -56,16 +56,16 @@ END; $$ language 'plpgsql'; -- 为每个表创建更新时间戳触发器 -CREATE TRIGGER update_ai_model_configs_updated_at - BEFORE UPDATE ON ai_model_configs +CREATE TRIGGER update_sfchain_model_configs_updated_at + BEFORE UPDATE ON sfchain_model_configs FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -CREATE TRIGGER update_ai_operation_configs_updated_at - BEFORE UPDATE ON ai_operation_configs +CREATE TRIGGER update_sfchain_operation_configs_updated_at + BEFORE UPDATE ON sfchain_operation_configs FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 插入一些示例数据 -INSERT INTO ai_model_configs (model_name, provider, enabled, description) VALUES +INSERT INTO sfchain_model_configs (model_name, provider, enabled, description) VALUES ('deepseek-chat', 'deepseek', true, 'DeepSeek Chat模型'), ('gpt-4o', 'openai', true, 'OpenAI GPT-4o模型'), ('siliconflow-qwen', 'siliconflow', true, 'SiliconFlow Qwen模型') diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-Cp61OaZJ.js b/prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-Cp61OaZJ.js new file mode 100644 index 0000000..0072f37 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-Cp61OaZJ.js @@ -0,0 +1,75 @@ +var Sl=Object.defineProperty;var Tl=(e,t,s)=>t in e?Sl(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var gs=(e,t,s)=>Tl(e,typeof t!="symbol"?t+"":t,s);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const r of o)if(r.type==="childList")for(const l of r.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&n(l)}).observe(document,{childList:!0,subtree:!0});function s(o){const r={};return o.integrity&&(r.integrity=o.integrity),o.referrerPolicy&&(r.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?r.credentials="include":o.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(o){if(o.ep)return;o.ep=!0;const r=s(o);fetch(o.href,r)}})();/** +* @vue/shared v3.5.17 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function vo(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const we={},ns=[],ot=()=>{},El=()=>!1,bn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),go=e=>e.startsWith("onUpdate:"),Re=Object.assign,mo=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},Al=Object.prototype.hasOwnProperty,he=(e,t)=>Al.call(e,t),ee=Array.isArray,os=e=>zs(e)==="[object Map]",ps=e=>zs(e)==="[object Set]",Uo=e=>zs(e)==="[object Date]",ie=e=>typeof e=="function",Te=e=>typeof e=="string",gt=e=>typeof e=="symbol",ke=e=>e!==null&&typeof e=="object",Kr=e=>(ke(e)||ie(e))&&ie(e.then)&&ie(e.catch),zr=Object.prototype.toString,zs=e=>zr.call(e),Ml=e=>zs(e).slice(8,-1),Gr=e=>zs(e)==="[object Object]",yo=e=>Te(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,xs=vo(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),_n=e=>{const t=Object.create(null);return s=>t[s]||(t[s]=e(s))},Ol=/-(\w)/g,nt=_n(e=>e.replace(Ol,(t,s)=>s?s.toUpperCase():"")),Pl=/\B([A-Z])/g,Ht=_n(e=>e.replace(Pl,"-$1").toLowerCase()),wn=_n(e=>e.charAt(0).toUpperCase()+e.slice(1)),Rn=_n(e=>e?`on${wn(e)}`:""),Nt=(e,t)=>!Object.is(e,t),sn=(e,...t)=>{for(let s=0;s{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},cn=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Rl=e=>{const t=Te(e)?Number(e):NaN;return isNaN(t)?e:t};let Ho;const kn=()=>Ho||(Ho=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Ns(e){if(ee(e)){const t={};for(let s=0;s{if(s){const n=s.split(Il);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function be(e){let t="";if(Te(e))t=e;else if(ee(e))for(let s=0;sYt(s,t))}const Wr=e=>!!(e&&e.__v_isRef===!0),U=e=>Te(e)?e:e==null?"":ee(e)||ke(e)&&(e.toString===zr||!ie(e.toString))?Wr(e)?U(e.value):JSON.stringify(e,Qr,2):String(e),Qr=(e,t)=>Wr(t)?Qr(e,t.value):os(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,o],r)=>(s[Ln(n,r)+" =>"]=o,s),{})}:ps(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>Ln(s))}:gt(t)?Ln(t):ke(t)&&!ee(t)&&!Gr(t)?String(t):t,Ln=(e,t="")=>{var s;return gt(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/** +* @vue/reactivity v3.5.17 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Je;class Yr{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=Je,!t&&Je&&(this.index=(Je.scopes||(Je.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t0&&--this._on===0&&(Je=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let s,n;for(s=0,n=this.effects.length;s0)return;if($s){let t=$s;for($s=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;Cs;){let t=Cs;for(Cs=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function ti(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function si(e){let t,s=e.depsTail,n=s;for(;n;){const o=n.prevDep;n.version===-1?(n===s&&(s=o),ko(n),Fl(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=o}e.deps=t,e.depsTail=s}function Qn(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(ni(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function ni(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===js)||(e.globalVersion=js,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Qn(e))))return;e.flags|=2;const t=e.dep,s=Ce,n=rt;Ce=e,rt=!0;try{ti(e);const o=e.fn(e._value);(t.version===0||Nt(o,e._value))&&(e.flags|=128,e._value=o,t.version++)}catch(o){throw t.version++,o}finally{Ce=s,rt=n,si(e),e.flags&=-3}}function ko(e,t=!1){const{dep:s,prevSub:n,nextSub:o}=e;if(n&&(n.nextSub=o,e.prevSub=void 0),o&&(o.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let r=s.computed.deps;r;r=r.nextDep)ko(r,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function Fl(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let rt=!0;const oi=[];function St(){oi.push(rt),rt=!1}function Tt(){const e=oi.pop();rt=e===void 0?!0:e}function Do(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=Ce;Ce=void 0;try{t()}finally{Ce=s}}}let js=0;class ql{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class xo{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!Ce||!rt||Ce===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==Ce)s=this.activeLink=new ql(Ce,this),Ce.deps?(s.prevDep=Ce.depsTail,Ce.depsTail.nextDep=s,Ce.depsTail=s):Ce.deps=Ce.depsTail=s,ri(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=Ce.depsTail,s.nextDep=void 0,Ce.depsTail.nextDep=s,Ce.depsTail=s,Ce.deps===s&&(Ce.deps=n)}return s}trigger(t){this.version++,js++,this.notify(t)}notify(t){_o();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{wo()}}}function ri(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let n=t.deps;n;n=n.nextDep)ri(n)}const s=e.dep.subs;s!==e&&(e.prevSub=s,s&&(s.nextSub=e)),e.dep.subs=e}}const Yn=new WeakMap,Wt=Symbol(""),Zn=Symbol(""),Bs=Symbol("");function je(e,t,s){if(rt&&Ce){let n=Yn.get(e);n||Yn.set(e,n=new Map);let o=n.get(s);o||(n.set(s,o=new xo),o.map=n,o.key=s),o.track()}}function kt(e,t,s,n,o,r){const l=Yn.get(e);if(!l){js++;return}const a=c=>{c&&c.trigger()};if(_o(),t==="clear")l.forEach(a);else{const c=ee(e),d=c&&yo(s);if(c&&s==="length"){const u=Number(n);l.forEach((f,v)=>{(v==="length"||v===Bs||!gt(v)&&v>=u)&&a(f)})}else switch((s!==void 0||l.has(void 0))&&a(l.get(s)),d&&a(l.get(Bs)),t){case"add":c?d&&a(l.get("length")):(a(l.get(Wt)),os(e)&&a(l.get(Zn)));break;case"delete":c||(a(l.get(Wt)),os(e)&&a(l.get(Zn)));break;case"set":os(e)&&a(l.get(Wt));break}}wo()}function Xt(e){const t=fe(e);return t===e?t:(je(t,"iterate",Bs),tt(e)?t:t.map(Ie))}function xn(e){return je(e=fe(e),"iterate",Bs),e}const Kl={__proto__:null,[Symbol.iterator](){return Nn(this,Symbol.iterator,Ie)},concat(...e){return Xt(this).concat(...e.map(t=>ee(t)?Xt(t):t))},entries(){return Nn(this,"entries",e=>(e[1]=Ie(e[1]),e))},every(e,t){return mt(this,"every",e,t,void 0,arguments)},filter(e,t){return mt(this,"filter",e,t,s=>s.map(Ie),arguments)},find(e,t){return mt(this,"find",e,t,Ie,arguments)},findIndex(e,t){return mt(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return mt(this,"findLast",e,t,Ie,arguments)},findLastIndex(e,t){return mt(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return mt(this,"forEach",e,t,void 0,arguments)},includes(...e){return jn(this,"includes",e)},indexOf(...e){return jn(this,"indexOf",e)},join(e){return Xt(this).join(e)},lastIndexOf(...e){return jn(this,"lastIndexOf",e)},map(e,t){return mt(this,"map",e,t,void 0,arguments)},pop(){return ms(this,"pop")},push(...e){return ms(this,"push",e)},reduce(e,...t){return Vo(this,"reduce",e,t)},reduceRight(e,...t){return Vo(this,"reduceRight",e,t)},shift(){return ms(this,"shift")},some(e,t){return mt(this,"some",e,t,void 0,arguments)},splice(...e){return ms(this,"splice",e)},toReversed(){return Xt(this).toReversed()},toSorted(e){return Xt(this).toSorted(e)},toSpliced(...e){return Xt(this).toSpliced(...e)},unshift(...e){return ms(this,"unshift",e)},values(){return Nn(this,"values",Ie)}};function Nn(e,t,s){const n=xn(e),o=n[t]();return n!==e&&!tt(e)&&(o._next=o.next,o.next=()=>{const r=o._next();return r.value&&(r.value=s(r.value)),r}),o}const zl=Array.prototype;function mt(e,t,s,n,o,r){const l=xn(e),a=l!==e&&!tt(e),c=l[t];if(c!==zl[t]){const f=c.apply(e,r);return a?Ie(f):f}let d=s;l!==e&&(a?d=function(f,v){return s.call(this,Ie(f),v,e)}:s.length>2&&(d=function(f,v){return s.call(this,f,v,e)}));const u=c.call(l,d,n);return a&&o?o(u):u}function Vo(e,t,s,n){const o=xn(e);let r=s;return o!==e&&(tt(e)?s.length>3&&(r=function(l,a,c){return s.call(this,l,a,c,e)}):r=function(l,a,c){return s.call(this,l,Ie(a),c,e)}),o[t](r,...n)}function jn(e,t,s){const n=fe(e);je(n,"iterate",Bs);const o=n[t](...s);return(o===-1||o===!1)&&So(s[0])?(s[0]=fe(s[0]),n[t](...s)):o}function ms(e,t,s=[]){St(),_o();const n=fe(e)[t].apply(e,s);return wo(),Tt(),n}const Gl=vo("__proto__,__v_isRef,__isVue"),ii=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(gt));function Jl(e){gt(e)||(e=String(e));const t=fe(this);return je(t,"has",e),t.hasOwnProperty(e)}class li{constructor(t=!1,s=!1){this._isReadonly=t,this._isShallow=s}get(t,s,n){if(s==="__v_skip")return t.__v_skip;const o=this._isReadonly,r=this._isShallow;if(s==="__v_isReactive")return!o;if(s==="__v_isReadonly")return o;if(s==="__v_isShallow")return r;if(s==="__v_raw")return n===(o?r?oa:di:r?ui:ci).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(n)?t:void 0;const l=ee(t);if(!o){let c;if(l&&(c=Kl[s]))return c;if(s==="hasOwnProperty")return Jl}const a=Reflect.get(t,s,Ue(t)?t:n);return(gt(s)?ii.has(s):Gl(s))||(o||je(t,"get",s),r)?a:Ue(a)?l&&yo(s)?a:a.value:ke(a)?o?pi(a):hs(a):a}}class ai extends li{constructor(t=!1){super(!1,t)}set(t,s,n,o){let r=t[s];if(!this._isShallow){const c=jt(r);if(!tt(n)&&!jt(n)&&(r=fe(r),n=fe(n)),!ee(t)&&Ue(r)&&!Ue(n))return c?!1:(r.value=n,!0)}const l=ee(t)&&yo(s)?Number(s)e,Ws=e=>Reflect.getPrototypeOf(e);function Xl(e,t,s){return function(...n){const o=this.__v_raw,r=fe(o),l=os(r),a=e==="entries"||e===Symbol.iterator&&l,c=e==="keys"&&l,d=o[e](...n),u=s?Xn:t?un:Ie;return!t&&je(r,"iterate",c?Zn:Wt),{next(){const{value:f,done:v}=d.next();return v?{value:f,done:v}:{value:a?[u(f[0]),u(f[1])]:u(f),done:v}},[Symbol.iterator](){return this}}}}function Qs(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function ea(e,t){const s={get(o){const r=this.__v_raw,l=fe(r),a=fe(o);e||(Nt(o,a)&&je(l,"get",o),je(l,"get",a));const{has:c}=Ws(l),d=t?Xn:e?un:Ie;if(c.call(l,o))return d(r.get(o));if(c.call(l,a))return d(r.get(a));r!==l&&r.get(o)},get size(){const o=this.__v_raw;return!e&&je(fe(o),"iterate",Wt),Reflect.get(o,"size",o)},has(o){const r=this.__v_raw,l=fe(r),a=fe(o);return e||(Nt(o,a)&&je(l,"has",o),je(l,"has",a)),o===a?r.has(o):r.has(o)||r.has(a)},forEach(o,r){const l=this,a=l.__v_raw,c=fe(a),d=t?Xn:e?un:Ie;return!e&&je(c,"iterate",Wt),a.forEach((u,f)=>o.call(r,d(u),d(f),l))}};return Re(s,e?{add:Qs("add"),set:Qs("set"),delete:Qs("delete"),clear:Qs("clear")}:{add(o){!t&&!tt(o)&&!jt(o)&&(o=fe(o));const r=fe(this);return Ws(r).has.call(r,o)||(r.add(o),kt(r,"add",o,o)),this},set(o,r){!t&&!tt(r)&&!jt(r)&&(r=fe(r));const l=fe(this),{has:a,get:c}=Ws(l);let d=a.call(l,o);d||(o=fe(o),d=a.call(l,o));const u=c.call(l,o);return l.set(o,r),d?Nt(r,u)&&kt(l,"set",o,r):kt(l,"add",o,r),this},delete(o){const r=fe(this),{has:l,get:a}=Ws(r);let c=l.call(r,o);c||(o=fe(o),c=l.call(r,o)),a&&a.call(r,o);const d=r.delete(o);return c&&kt(r,"delete",o,void 0),d},clear(){const o=fe(this),r=o.size!==0,l=o.clear();return r&&kt(o,"clear",void 0,void 0),l}}),["keys","values","entries",Symbol.iterator].forEach(o=>{s[o]=Xl(o,e,t)}),s}function Co(e,t){const s=ea(e,t);return(n,o,r)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?n:Reflect.get(he(s,o)&&o in n?s:n,o,r)}const ta={get:Co(!1,!1)},sa={get:Co(!1,!0)},na={get:Co(!0,!1)};const ci=new WeakMap,ui=new WeakMap,di=new WeakMap,oa=new WeakMap;function ra(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function ia(e){return e.__v_skip||!Object.isExtensible(e)?0:ra(Ml(e))}function hs(e){return jt(e)?e:$o(e,!1,Ql,ta,ci)}function fi(e){return $o(e,!1,Zl,sa,ui)}function pi(e){return $o(e,!0,Yl,na,di)}function $o(e,t,s,n,o){if(!ke(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const r=ia(e);if(r===0)return e;const l=o.get(e);if(l)return l;const a=new Proxy(e,r===2?n:s);return o.set(e,a),a}function rs(e){return jt(e)?rs(e.__v_raw):!!(e&&e.__v_isReactive)}function jt(e){return!!(e&&e.__v_isReadonly)}function tt(e){return!!(e&&e.__v_isShallow)}function So(e){return e?!!e.__v_raw:!1}function fe(e){const t=e&&e.__v_raw;return t?fe(t):e}function hi(e){return!he(e,"__v_skip")&&Object.isExtensible(e)&&Wn(e,"__v_skip",!0),e}const Ie=e=>ke(e)?hs(e):e,un=e=>ke(e)?pi(e):e;function Ue(e){return e?e.__v_isRef===!0:!1}function Z(e){return vi(e,!1)}function la(e){return vi(e,!0)}function vi(e,t){return Ue(e)?e:new aa(e,t)}class aa{constructor(t,s){this.dep=new xo,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=s?t:fe(t),this._value=s?t:Ie(t),this.__v_isShallow=s}get value(){return this.dep.track(),this._value}set value(t){const s=this._rawValue,n=this.__v_isShallow||tt(t)||jt(t);t=n?t:fe(t),Nt(t,s)&&(this._rawValue=t,this._value=n?t:Ie(t),this.dep.trigger())}}function Ee(e){return Ue(e)?e.value:e}const ca={get:(e,t,s)=>t==="__v_raw"?e:Ee(Reflect.get(e,t,s)),set:(e,t,s,n)=>{const o=e[t];return Ue(o)&&!Ue(s)?(o.value=s,!0):Reflect.set(e,t,s,n)}};function gi(e){return rs(e)?e:new Proxy(e,ca)}class ua{constructor(t,s,n){this.fn=t,this.setter=s,this._value=void 0,this.dep=new xo(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=js-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!s,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&Ce!==this)return ei(this,!0),!0}get value(){const t=this.dep.track();return ni(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function da(e,t,s=!1){let n,o;return ie(e)?n=e:(n=e.get,o=e.set),new ua(n,o,s)}const Ys={},dn=new WeakMap;let zt;function fa(e,t=!1,s=zt){if(s){let n=dn.get(s);n||dn.set(s,n=[]),n.push(e)}}function pa(e,t,s=we){const{immediate:n,deep:o,once:r,scheduler:l,augmentJob:a,call:c}=s,d=y=>o?y:tt(y)||o===!1||o===0?xt(y,1):xt(y);let u,f,v,g,x=!1,L=!1;if(Ue(e)?(f=()=>e.value,x=tt(e)):rs(e)?(f=()=>d(e),x=!0):ee(e)?(L=!0,x=e.some(y=>rs(y)||tt(y)),f=()=>e.map(y=>{if(Ue(y))return y.value;if(rs(y))return d(y);if(ie(y))return c?c(y,2):y()})):ie(e)?t?f=c?()=>c(e,2):e:f=()=>{if(v){St();try{v()}finally{Tt()}}const y=zt;zt=u;try{return c?c(e,3,[g]):e(g)}finally{zt=y}}:f=ot,t&&o){const y=f,P=o===!0?1/0:o;f=()=>xt(y(),P)}const J=Vl(),j=()=>{u.stop(),J&&J.active&&mo(J.effects,u)};if(r&&t){const y=t;t=(...P)=>{y(...P),j()}}let V=L?new Array(e.length).fill(Ys):Ys;const T=y=>{if(!(!(u.flags&1)||!u.dirty&&!y))if(t){const P=u.run();if(o||x||(L?P.some((I,C)=>Nt(I,V[C])):Nt(P,V))){v&&v();const I=zt;zt=u;try{const C=[P,V===Ys?void 0:L&&V[0]===Ys?[]:V,g];V=P,c?c(t,3,C):t(...C)}finally{zt=I}}}else u.run()};return a&&a(T),u=new Zr(f),u.scheduler=l?()=>l(T,!1):T,g=y=>fa(y,!1,u),v=u.onStop=()=>{const y=dn.get(u);if(y){if(c)c(y,4);else for(const P of y)P();dn.delete(u)}},t?n?T(!0):V=u.run():l?l(T.bind(null,!0),!0):u.run(),j.pause=u.pause.bind(u),j.resume=u.resume.bind(u),j.stop=j,j}function xt(e,t=1/0,s){if(t<=0||!ke(e)||e.__v_skip||(s=s||new Set,s.has(e)))return e;if(s.add(e),t--,Ue(e))xt(e.value,t,s);else if(ee(e))for(let n=0;n{xt(n,t,s)});else if(Gr(e)){for(const n in e)xt(e[n],t,s);for(const n of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,n)&&xt(e[n],t,s)}return e}/** +* @vue/runtime-core v3.5.17 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Gs(e,t,s,n){try{return n?e(...n):e()}catch(o){Cn(o,t,s)}}function it(e,t,s,n){if(ie(e)){const o=Gs(e,t,s,n);return o&&Kr(o)&&o.catch(r=>{Cn(r,t,s)}),o}if(ee(e)){const o=[];for(let r=0;r>>1,o=Fe[n],r=Us(o);r=Us(s)?Fe.push(e):Fe.splice(va(t),0,e),e.flags|=1,yi()}}function yi(){fn||(fn=mi.then(wi))}function bi(e){ee(e)?is.push(...e):Pt&&e.id===-1?Pt.splice(ts+1,0,e):e.flags&1||(is.push(e),e.flags|=1),yi()}function Fo(e,t,s=pt+1){for(;sUs(s)-Us(n));if(is.length=0,Pt){Pt.push(...t);return}for(Pt=t,ts=0;tse.id==null?e.flags&2?-1:1/0:e.id;function wi(e){try{for(pt=0;pt{n._d&&nr(-1);const r=pn(t);let l;try{l=e(...o)}finally{pn(r),n._d&&nr(1)}return l};return n._n=!0,n._c=!0,n._d=!0,n}function $e(e,t){if(Qe===null)return e;const s=On(Qe),n=e.dirs||(e.dirs=[]);for(let o=0;oe.__isTeleport,Ss=e=>e&&(e.disabled||e.disabled===""),qo=e=>e&&(e.defer||e.defer===""),Ko=e=>typeof SVGElement<"u"&&e instanceof SVGElement,zo=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,eo=(e,t)=>{const s=e&&e.to;return Te(s)?t?t(s):null:s},Si={name:"Teleport",__isTeleport:!0,process(e,t,s,n,o,r,l,a,c,d){const{mc:u,pc:f,pbc:v,o:{insert:g,querySelector:x,createText:L,createComment:J}}=d,j=Ss(t.props);let{shapeFlag:V,children:T,dynamicChildren:y}=t;if(e==null){const P=t.el=L(""),I=t.anchor=L("");g(P,s,n),g(I,s,n);const C=(O,W)=>{V&16&&(o&&o.isCE&&(o.ce._teleportTarget=O),u(T,O,W,o,r,l,a,c))},$=()=>{const O=t.target=eo(t.props,x),W=Ti(O,t,L,g);O&&(l!=="svg"&&Ko(O)?l="svg":l!=="mathml"&&zo(O)&&(l="mathml"),j||(C(O,W),nn(t,!1)))};j&&(C(s,I),nn(t,!0)),qo(t.props)?(t.el.__isMounted=!1,Ve(()=>{$(),delete t.el.__isMounted},r)):$()}else{if(qo(t.props)&&e.el.__isMounted===!1){Ve(()=>{Si.process(e,t,s,n,o,r,l,a,c,d)},r);return}t.el=e.el,t.targetStart=e.targetStart;const P=t.anchor=e.anchor,I=t.target=e.target,C=t.targetAnchor=e.targetAnchor,$=Ss(e.props),O=$?s:I,W=$?P:C;if(l==="svg"||Ko(I)?l="svg":(l==="mathml"||zo(I))&&(l="mathml"),y?(v(e.dynamicChildren,y,O,o,r,l,a),Mo(e,t,!0)):c||f(e,t,O,W,o,r,l,a,!1),j)$?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):Zs(t,s,P,d,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const te=t.target=eo(t.props,x);te&&Zs(t,te,null,d,0)}else $&&Zs(t,I,C,d,1);nn(t,j)}},remove(e,t,s,{um:n,o:{remove:o}},r){const{shapeFlag:l,children:a,anchor:c,targetStart:d,targetAnchor:u,target:f,props:v}=e;if(f&&(o(d),o(u)),r&&o(c),l&16){const g=r||!Ss(v);for(let x=0;x{e.isMounted=!0}),Ni(()=>{e.isUnmounting=!0}),e}const et=[Function,Array],Ei={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:et,onEnter:et,onAfterEnter:et,onEnterCancelled:et,onBeforeLeave:et,onLeave:et,onAfterLeave:et,onLeaveCancelled:et,onBeforeAppear:et,onAppear:et,onAfterAppear:et,onAppearCancelled:et},Ai=e=>{const t=e.subTree;return t.component?Ai(t.component):t},ba={name:"BaseTransition",props:Ei,setup(e,{slots:t}){const s=sl(),n=ya();return()=>{const o=t.default&&Pi(t.default(),!0);if(!o||!o.length)return;const r=Mi(o),l=fe(e),{mode:a}=l;if(n.isLeaving)return Bn(r);const c=Go(r);if(!c)return Bn(r);let d=to(c,l,n,s,f=>d=f);c.type!==Ke&&Hs(c,d);let u=s.subTree&&Go(s.subTree);if(u&&u.type!==Ke&&!Gt(c,u)&&Ai(s).type!==Ke){let f=to(u,l,n,s);if(Hs(u,f),a==="out-in"&&c.type!==Ke)return n.isLeaving=!0,f.afterLeave=()=>{n.isLeaving=!1,s.job.flags&8||s.update(),delete f.afterLeave,u=void 0},Bn(r);a==="in-out"&&c.type!==Ke?f.delayLeave=(v,g,x)=>{const L=Oi(n,u);L[String(u.key)]=u,v[Rt]=()=>{g(),v[Rt]=void 0,delete d.delayedLeave,u=void 0},d.delayedLeave=()=>{x(),delete d.delayedLeave,u=void 0}}:u=void 0}else u&&(u=void 0);return r}}};function Mi(e){let t=e[0];if(e.length>1){for(const s of e)if(s.type!==Ke){t=s;break}}return t}const _a=ba;function Oi(e,t){const{leavingVNodes:s}=e;let n=s.get(t.type);return n||(n=Object.create(null),s.set(t.type,n)),n}function to(e,t,s,n,o){const{appear:r,mode:l,persisted:a=!1,onBeforeEnter:c,onEnter:d,onAfterEnter:u,onEnterCancelled:f,onBeforeLeave:v,onLeave:g,onAfterLeave:x,onLeaveCancelled:L,onBeforeAppear:J,onAppear:j,onAfterAppear:V,onAppearCancelled:T}=t,y=String(e.key),P=Oi(s,e),I=(O,W)=>{O&&it(O,n,9,W)},C=(O,W)=>{const te=W[1];I(O,W),ee(O)?O.every(k=>k.length<=1)&&te():O.length<=1&&te()},$={mode:l,persisted:a,beforeEnter(O){let W=c;if(!s.isMounted)if(r)W=J||c;else return;O[Rt]&&O[Rt](!0);const te=P[y];te&&Gt(e,te)&&te.el[Rt]&&te.el[Rt](),I(W,[O])},enter(O){let W=d,te=u,k=f;if(!s.isMounted)if(r)W=j||d,te=V||u,k=T||f;else return;let _=!1;const B=O[Xs]=ne=>{_||(_=!0,ne?I(k,[O]):I(te,[O]),$.delayedLeave&&$.delayedLeave(),O[Xs]=void 0)};W?C(W,[O,B]):B()},leave(O,W){const te=String(e.key);if(O[Xs]&&O[Xs](!0),s.isUnmounting)return W();I(v,[O]);let k=!1;const _=O[Rt]=B=>{k||(k=!0,W(),B?I(L,[O]):I(x,[O]),O[Rt]=void 0,P[te]===e&&delete P[te])};P[te]=e,g?C(g,[O,_]):_()},clone(O){const W=to(O,t,s,n,o);return o&&o(W),W}};return $}function Bn(e){if(Sn(e))return e=Bt(e),e.children=null,e}function Go(e){if(!Sn(e))return $i(e.type)&&e.children?Mi(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:s}=e;if(s){if(t&16)return s[0];if(t&32&&ie(s.default))return s.default()}}function Hs(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Hs(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Pi(e,t=!1,s){let n=[],o=0;for(let r=0;r1)for(let r=0;rTs(x,t&&(ee(t)?t[L]:t),s,n,o));return}if(Es(n)&&!o){n.shapeFlag&512&&n.type.__asyncResolved&&n.component.subTree.component&&Ts(e,t,s,n.component.subTree);return}const r=n.shapeFlag&4?On(n.component):n.el,l=o?null:r,{i:a,r:c}=e,d=t&&t.r,u=a.refs===we?a.refs={}:a.refs,f=a.setupState,v=fe(f),g=f===we?()=>!1:x=>he(v,x);if(d!=null&&d!==c&&(Te(d)?(u[d]=null,g(d)&&(f[d]=null)):Ue(d)&&(d.value=null)),ie(c))Gs(c,a,12,[l,u]);else{const x=Te(c),L=Ue(c);if(x||L){const J=()=>{if(e.f){const j=x?g(c)?f[c]:u[c]:c.value;o?ee(j)&&mo(j,r):ee(j)?j.includes(r)||j.push(r):x?(u[c]=[r],g(c)&&(f[c]=u[c])):(c.value=[r],e.k&&(u[e.k]=c.value))}else x?(u[c]=l,g(c)&&(f[c]=l)):L&&(c.value=l,e.k&&(u[e.k]=l))};l?(J.id=-1,Ve(J,s)):J()}}}kn().requestIdleCallback;kn().cancelIdleCallback;const Es=e=>!!e.type.__asyncLoader,Sn=e=>e.type.__isKeepAlive;function wa(e,t){Li(e,"a",t)}function ka(e,t){Li(e,"da",t)}function Li(e,t,s=Ne){const n=e.__wdc||(e.__wdc=()=>{let o=s;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(Tn(t,n,s),s){let o=s.parent;for(;o&&o.parent;)Sn(o.parent.vnode)&&xa(n,t,s,o),o=o.parent}}function xa(e,t,s,n){const o=Tn(t,e,n,!0);En(()=>{mo(n[t],o)},s)}function Tn(e,t,s=Ne,n=!1){if(s){const o=s[e]||(s[e]=[]),r=t.__weh||(t.__weh=(...l)=>{St();const a=Js(s),c=it(t,s,e,l);return a(),Tt(),c});return n?o.unshift(r):o.push(r),r}}const Et=e=>(t,s=Ne)=>{(!Vs||e==="sp")&&Tn(e,(...n)=>t(...n),s)},Ca=Et("bm"),Dt=Et("m"),Ii=Et("bu"),$a=Et("u"),Ni=Et("bum"),En=Et("um"),Sa=Et("sp"),Ta=Et("rtg"),Ea=Et("rtc");function Aa(e,t=Ne){Tn("ec",e,t)}const Ma="components",ji=Symbol.for("v-ndc");function Oa(e){return Te(e)?Pa(Ma,e,!1)||e:e||ji}function Pa(e,t,s=!0,n=!1){const o=Qe||Ne;if(o){const r=o.type;{const a=yc(r,!1);if(a&&(a===t||a===nt(t)||a===wn(nt(t))))return r}const l=Jo(o[e]||r[e],t)||Jo(o.appContext[e],t);return!l&&n?r:l}}function Jo(e,t){return e&&(e[t]||e[nt(t)]||e[wn(nt(t))])}function Le(e,t,s,n){let o;const r=s,l=ee(e);if(l||Te(e)){const a=l&&rs(e);let c=!1,d=!1;a&&(c=!tt(e),d=jt(e),e=xn(e)),o=new Array(e.length);for(let u=0,f=e.length;ut(a,c,void 0,r));else{const a=Object.keys(e);o=new Array(a.length);for(let c=0,d=a.length;ce?nl(e)?On(e):so(e.parent):null,As=Re(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>so(e.parent),$root:e=>so(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Ui(e),$forceUpdate:e=>e.f||(e.f=()=>{To(e.update)}),$nextTick:e=>e.n||(e.n=$n.bind(e.proxy)),$watch:e=>Xa.bind(e)}),Un=(e,t)=>e!==we&&!e.__isScriptSetup&&he(e,t),Ra={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:s,setupState:n,data:o,props:r,accessCache:l,type:a,appContext:c}=e;let d;if(t[0]!=="$"){const g=l[t];if(g!==void 0)switch(g){case 1:return n[t];case 2:return o[t];case 4:return s[t];case 3:return r[t]}else{if(Un(n,t))return l[t]=1,n[t];if(o!==we&&he(o,t))return l[t]=2,o[t];if((d=e.propsOptions[0])&&he(d,t))return l[t]=3,r[t];if(s!==we&&he(s,t))return l[t]=4,s[t];no&&(l[t]=0)}}const u=As[t];let f,v;if(u)return t==="$attrs"&&je(e.attrs,"get",""),u(e);if((f=a.__cssModules)&&(f=f[t]))return f;if(s!==we&&he(s,t))return l[t]=4,s[t];if(v=c.config.globalProperties,he(v,t))return v[t]},set({_:e},t,s){const{data:n,setupState:o,ctx:r}=e;return Un(o,t)?(o[t]=s,!0):n!==we&&he(n,t)?(n[t]=s,!0):he(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(r[t]=s,!0)},has({_:{data:e,setupState:t,accessCache:s,ctx:n,appContext:o,propsOptions:r}},l){let a;return!!s[l]||e!==we&&he(e,l)||Un(t,l)||(a=r[0])&&he(a,l)||he(n,l)||he(As,l)||he(o.config.globalProperties,l)},defineProperty(e,t,s){return s.get!=null?e._.accessCache[t]=0:he(s,"value")&&this.set(e,t,s.value,null),Reflect.defineProperty(e,t,s)}};function Wo(e){return ee(e)?e.reduce((t,s)=>(t[s]=null,t),{}):e}let no=!0;function La(e){const t=Ui(e),s=e.proxy,n=e.ctx;no=!1,t.beforeCreate&&Qo(t.beforeCreate,e,"bc");const{data:o,computed:r,methods:l,watch:a,provide:c,inject:d,created:u,beforeMount:f,mounted:v,beforeUpdate:g,updated:x,activated:L,deactivated:J,beforeDestroy:j,beforeUnmount:V,destroyed:T,unmounted:y,render:P,renderTracked:I,renderTriggered:C,errorCaptured:$,serverPrefetch:O,expose:W,inheritAttrs:te,components:k,directives:_,filters:B}=t;if(d&&Ia(d,n,null),l)for(const ae in l){const ce=l[ae];ie(ce)&&(n[ae]=ce.bind(s))}if(o){const ae=o.call(s,s);ke(ae)&&(e.data=hs(ae))}if(no=!0,r)for(const ae in r){const ce=r[ae],Ae=ie(ce)?ce.bind(s,s):ie(ce.get)?ce.get.bind(s,s):ot,G=!ie(ce)&&ie(ce.set)?ce.set.bind(s):ot,b=ve({get:Ae,set:G});Object.defineProperty(n,ae,{enumerable:!0,configurable:!0,get:()=>b.value,set:ue=>b.value=ue})}if(a)for(const ae in a)Bi(a[ae],n,s,ae);if(c){const ae=ie(c)?c.call(s):c;Reflect.ownKeys(ae).forEach(ce=>{on(ce,ae[ce])})}u&&Qo(u,e,"c");function oe(ae,ce){ee(ce)?ce.forEach(Ae=>ae(Ae.bind(s))):ce&&ae(ce.bind(s))}if(oe(Ca,f),oe(Dt,v),oe(Ii,g),oe($a,x),oe(wa,L),oe(ka,J),oe(Aa,$),oe(Ea,I),oe(Ta,C),oe(Ni,V),oe(En,y),oe(Sa,O),ee(W))if(W.length){const ae=e.exposed||(e.exposed={});W.forEach(ce=>{Object.defineProperty(ae,ce,{get:()=>s[ce],set:Ae=>s[ce]=Ae})})}else e.exposed||(e.exposed={});P&&e.render===ot&&(e.render=P),te!=null&&(e.inheritAttrs=te),k&&(e.components=k),_&&(e.directives=_),O&&Ri(e)}function Ia(e,t,s=ot){ee(e)&&(e=oo(e));for(const n in e){const o=e[n];let r;ke(o)?"default"in o?r=$t(o.from||n,o.default,!0):r=$t(o.from||n):r=$t(o),Ue(r)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>r.value,set:l=>r.value=l}):t[n]=r}}function Qo(e,t,s){it(ee(e)?e.map(n=>n.bind(t.proxy)):e.bind(t.proxy),t,s)}function Bi(e,t,s,n){let o=n.includes(".")?Yi(s,n):()=>s[n];if(Te(e)){const r=t[e];ie(r)&&as(o,r)}else if(ie(e))as(o,e.bind(s));else if(ke(e))if(ee(e))e.forEach(r=>Bi(r,t,s,n));else{const r=ie(e.handler)?e.handler.bind(s):t[e.handler];ie(r)&&as(o,r,e)}}function Ui(e){const t=e.type,{mixins:s,extends:n}=t,{mixins:o,optionsCache:r,config:{optionMergeStrategies:l}}=e.appContext,a=r.get(t);let c;return a?c=a:!o.length&&!s&&!n?c=t:(c={},o.length&&o.forEach(d=>hn(c,d,l,!0)),hn(c,t,l)),ke(t)&&r.set(t,c),c}function hn(e,t,s,n=!1){const{mixins:o,extends:r}=t;r&&hn(e,r,s,!0),o&&o.forEach(l=>hn(e,l,s,!0));for(const l in t)if(!(n&&l==="expose")){const a=Na[l]||s&&s[l];e[l]=a?a(e[l],t[l]):t[l]}return e}const Na={data:Yo,props:Zo,emits:Zo,methods:ws,computed:ws,beforeCreate:De,created:De,beforeMount:De,mounted:De,beforeUpdate:De,updated:De,beforeDestroy:De,beforeUnmount:De,destroyed:De,unmounted:De,activated:De,deactivated:De,errorCaptured:De,serverPrefetch:De,components:ws,directives:ws,watch:Ba,provide:Yo,inject:ja};function Yo(e,t){return t?e?function(){return Re(ie(e)?e.call(this,this):e,ie(t)?t.call(this,this):t)}:t:e}function ja(e,t){return ws(oo(e),oo(t))}function oo(e){if(ee(e)){const t={};for(let s=0;s1)return s&&ie(t)?t.call(n&&n.proxy):t}}const Di={},Vi=()=>Object.create(Di),Fi=e=>Object.getPrototypeOf(e)===Di;function Da(e,t,s,n=!1){const o={},r=Vi();e.propsDefaults=Object.create(null),qi(e,t,o,r);for(const l in e.propsOptions[0])l in o||(o[l]=void 0);s?e.props=n?o:fi(o):e.type.props?e.props=o:e.props=r,e.attrs=r}function Va(e,t,s,n){const{props:o,attrs:r,vnode:{patchFlag:l}}=e,a=fe(o),[c]=e.propsOptions;let d=!1;if((n||l>0)&&!(l&16)){if(l&8){const u=e.vnode.dynamicProps;for(let f=0;f{c=!0;const[v,g]=Ki(f,t,!0);Re(l,v),g&&a.push(...g)};!s&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!r&&!c)return ke(e)&&n.set(e,ns),ns;if(ee(r))for(let u=0;ue[0]==="_"||e==="$stable",Ao=e=>ee(e)?e.map(ht):[ht(e)],qa=(e,t,s)=>{if(t._n)return t;const n=xi((...o)=>Ao(t(...o)),s);return n._c=!1,n},zi=(e,t,s)=>{const n=e._ctx;for(const o in e){if(Eo(o))continue;const r=e[o];if(ie(r))t[o]=qa(o,r,n);else if(r!=null){const l=Ao(r);t[o]=()=>l}}},Gi=(e,t)=>{const s=Ao(t);e.slots.default=()=>s},Ji=(e,t,s)=>{for(const n in t)(s||!Eo(n))&&(e[n]=t[n])},Ka=(e,t,s)=>{const n=e.slots=Vi();if(e.vnode.shapeFlag&32){const o=t.__;o&&Wn(n,"__",o,!0);const r=t._;r?(Ji(n,t,s),s&&Wn(n,"_",r,!0)):zi(t,n)}else t&&Gi(e,t)},za=(e,t,s)=>{const{vnode:n,slots:o}=e;let r=!0,l=we;if(n.shapeFlag&32){const a=t._;a?s&&a===1?r=!1:Ji(o,t,s):(r=!t.$stable,zi(t,o)),l=t}else t&&(Gi(e,t),l={default:1});if(r)for(const a in o)!Eo(a)&&l[a]==null&&delete o[a]},Ve=ic;function Ga(e){return Ja(e)}function Ja(e,t){const s=kn();s.__VUE__=!0;const{insert:n,remove:o,patchProp:r,createElement:l,createText:a,createComment:c,setText:d,setElementText:u,parentNode:f,nextSibling:v,setScopeId:g=ot,insertStaticContent:x}=e,L=(p,h,m,A=null,R=null,M=null,F=void 0,D=null,H=!!h.dynamicChildren)=>{if(p===h)return;p&&!Gt(p,h)&&(A=S(p),ue(p,R,M,!0),p=null),h.patchFlag===-2&&(H=!1,h.dynamicChildren=null);const{type:N,ref:se,shapeFlag:K}=h;switch(N){case Mn:J(p,h,m,A);break;case Ke:j(p,h,m,A);break;case Ms:p==null&&V(h,m,A,F);break;case ye:k(p,h,m,A,R,M,F,D,H);break;default:K&1?P(p,h,m,A,R,M,F,D,H):K&6?_(p,h,m,A,R,M,F,D,H):(K&64||K&128)&&N.process(p,h,m,A,R,M,F,D,H,Q)}se!=null&&R?Ts(se,p&&p.ref,M,h||p,!h):se==null&&p&&p.ref!=null&&Ts(p.ref,null,M,p,!0)},J=(p,h,m,A)=>{if(p==null)n(h.el=a(h.children),m,A);else{const R=h.el=p.el;h.children!==p.children&&d(R,h.children)}},j=(p,h,m,A)=>{p==null?n(h.el=c(h.children||""),m,A):h.el=p.el},V=(p,h,m,A)=>{[p.el,p.anchor]=x(p.children,h,m,A,p.el,p.anchor)},T=({el:p,anchor:h},m,A)=>{let R;for(;p&&p!==h;)R=v(p),n(p,m,A),p=R;n(h,m,A)},y=({el:p,anchor:h})=>{let m;for(;p&&p!==h;)m=v(p),o(p),p=m;o(h)},P=(p,h,m,A,R,M,F,D,H)=>{h.type==="svg"?F="svg":h.type==="math"&&(F="mathml"),p==null?I(h,m,A,R,M,F,D,H):O(p,h,R,M,F,D,H)},I=(p,h,m,A,R,M,F,D)=>{let H,N;const{props:se,shapeFlag:K,transition:X,dirs:re}=p;if(H=p.el=l(p.type,M,se&&se.is,se),K&8?u(H,p.children):K&16&&$(p.children,H,null,A,R,Hn(p,M),F,D),re&&Vt(p,null,A,"created"),C(H,p,p.scopeId,F,A),se){for(const xe in se)xe!=="value"&&!xs(xe)&&r(H,xe,null,se[xe],M,A);"value"in se&&r(H,"value",null,se.value,M),(N=se.onVnodeBeforeMount)&&ft(N,A,p)}re&&Vt(p,null,A,"beforeMount");const de=Wa(R,X);de&&X.beforeEnter(H),n(H,h,m),((N=se&&se.onVnodeMounted)||de||re)&&Ve(()=>{N&&ft(N,A,p),de&&X.enter(H),re&&Vt(p,null,A,"mounted")},R)},C=(p,h,m,A,R)=>{if(m&&g(p,m),A)for(let M=0;M{for(let N=H;N{const D=h.el=p.el;let{patchFlag:H,dynamicChildren:N,dirs:se}=h;H|=p.patchFlag&16;const K=p.props||we,X=h.props||we;let re;if(m&&Ft(m,!1),(re=X.onVnodeBeforeUpdate)&&ft(re,m,h,p),se&&Vt(h,p,m,"beforeUpdate"),m&&Ft(m,!0),(K.innerHTML&&X.innerHTML==null||K.textContent&&X.textContent==null)&&u(D,""),N?W(p.dynamicChildren,N,D,m,A,Hn(h,R),M):F||ce(p,h,D,null,m,A,Hn(h,R),M,!1),H>0){if(H&16)te(D,K,X,m,R);else if(H&2&&K.class!==X.class&&r(D,"class",null,X.class,R),H&4&&r(D,"style",K.style,X.style,R),H&8){const de=h.dynamicProps;for(let xe=0;xe{re&&ft(re,m,h,p),se&&Vt(h,p,m,"updated")},A)},W=(p,h,m,A,R,M,F)=>{for(let D=0;D{if(h!==m){if(h!==we)for(const M in h)!xs(M)&&!(M in m)&&r(p,M,h[M],null,R,A);for(const M in m){if(xs(M))continue;const F=m[M],D=h[M];F!==D&&M!=="value"&&r(p,M,D,F,R,A)}"value"in m&&r(p,"value",h.value,m.value,R)}},k=(p,h,m,A,R,M,F,D,H)=>{const N=h.el=p?p.el:a(""),se=h.anchor=p?p.anchor:a("");let{patchFlag:K,dynamicChildren:X,slotScopeIds:re}=h;re&&(D=D?D.concat(re):re),p==null?(n(N,m,A),n(se,m,A),$(h.children||[],m,se,R,M,F,D,H)):K>0&&K&64&&X&&p.dynamicChildren?(W(p.dynamicChildren,X,m,R,M,F,D),(h.key!=null||R&&h===R.subTree)&&Mo(p,h,!0)):ce(p,h,m,se,R,M,F,D,H)},_=(p,h,m,A,R,M,F,D,H)=>{h.slotScopeIds=D,p==null?h.shapeFlag&512?R.ctx.activate(h,m,A,F,H):B(h,m,A,R,M,F,H):ne(p,h,H)},B=(p,h,m,A,R,M,F)=>{const D=p.component=pc(p,A,R);if(Sn(p)&&(D.ctx.renderer=Q),hc(D,!1,F),D.asyncDep){if(R&&R.registerDep(D,oe,F),!p.el){const H=D.subTree=Be(Ke);j(null,H,h,m)}}else oe(D,p,h,m,R,M,F)},ne=(p,h,m)=>{const A=h.component=p.component;if(oc(p,h,m))if(A.asyncDep&&!A.asyncResolved){ae(A,h,m);return}else A.next=h,A.update();else h.el=p.el,A.vnode=h},oe=(p,h,m,A,R,M,F)=>{const D=()=>{if(p.isMounted){let{next:K,bu:X,u:re,parent:de,vnode:xe}=p;{const ut=Wi(p);if(ut){K&&(K.el=xe.el,ae(p,K,F)),ut.asyncDep.then(()=>{p.isUnmounted||D()});return}}let ge=K,ze;Ft(p,!1),K?(K.el=xe.el,ae(p,K,F)):K=xe,X&&sn(X),(ze=K.props&&K.props.onVnodeBeforeUpdate)&&ft(ze,de,K,xe),Ft(p,!0);const Ge=tr(p),ct=p.subTree;p.subTree=Ge,L(ct,Ge,f(ct.el),S(ct),p,R,M),K.el=Ge.el,ge===null&&rc(p,Ge.el),re&&Ve(re,R),(ze=K.props&&K.props.onVnodeUpdated)&&Ve(()=>ft(ze,de,K,xe),R)}else{let K;const{el:X,props:re}=h,{bm:de,m:xe,parent:ge,root:ze,type:Ge}=p,ct=Es(h);Ft(p,!1),de&&sn(de),!ct&&(K=re&&re.onVnodeBeforeMount)&&ft(K,ge,h),Ft(p,!0);{ze.ce&&ze.ce._def.shadowRoot!==!1&&ze.ce._injectChildStyle(Ge);const ut=p.subTree=tr(p);L(null,ut,m,A,p,R,M),h.el=ut.el}if(xe&&Ve(xe,R),!ct&&(K=re&&re.onVnodeMounted)){const ut=h;Ve(()=>ft(K,ge,ut),R)}(h.shapeFlag&256||ge&&Es(ge.vnode)&&ge.vnode.shapeFlag&256)&&p.a&&Ve(p.a,R),p.isMounted=!0,h=m=A=null}};p.scope.on();const H=p.effect=new Zr(D);p.scope.off();const N=p.update=H.run.bind(H),se=p.job=H.runIfDirty.bind(H);se.i=p,se.id=p.uid,H.scheduler=()=>To(se),Ft(p,!0),N()},ae=(p,h,m)=>{h.component=p;const A=p.vnode.props;p.vnode=h,p.next=null,Va(p,h.props,A,m),za(p,h.children,m),St(),Fo(p),Tt()},ce=(p,h,m,A,R,M,F,D,H=!1)=>{const N=p&&p.children,se=p?p.shapeFlag:0,K=h.children,{patchFlag:X,shapeFlag:re}=h;if(X>0){if(X&128){G(N,K,m,A,R,M,F,D,H);return}else if(X&256){Ae(N,K,m,A,R,M,F,D,H);return}}re&8?(se&16&&He(N,R,M),K!==N&&u(m,K)):se&16?re&16?G(N,K,m,A,R,M,F,D,H):He(N,R,M,!0):(se&8&&u(m,""),re&16&&$(K,m,A,R,M,F,D,H))},Ae=(p,h,m,A,R,M,F,D,H)=>{p=p||ns,h=h||ns;const N=p.length,se=h.length,K=Math.min(N,se);let X;for(X=0;Xse?He(p,R,M,!0,!1,K):$(h,m,A,R,M,F,D,H,K)},G=(p,h,m,A,R,M,F,D,H)=>{let N=0;const se=h.length;let K=p.length-1,X=se-1;for(;N<=K&&N<=X;){const re=p[N],de=h[N]=H?Lt(h[N]):ht(h[N]);if(Gt(re,de))L(re,de,m,null,R,M,F,D,H);else break;N++}for(;N<=K&&N<=X;){const re=p[K],de=h[X]=H?Lt(h[X]):ht(h[X]);if(Gt(re,de))L(re,de,m,null,R,M,F,D,H);else break;K--,X--}if(N>K){if(N<=X){const re=X+1,de=reX)for(;N<=K;)ue(p[N],R,M,!0),N++;else{const re=N,de=N,xe=new Map;for(N=de;N<=X;N++){const Ye=h[N]=H?Lt(h[N]):ht(h[N]);Ye.key!=null&&xe.set(Ye.key,N)}let ge,ze=0;const Ge=X-de+1;let ct=!1,ut=0;const vs=new Array(Ge);for(N=0;N=Ge){ue(Ye,R,M,!0);continue}let dt;if(Ye.key!=null)dt=xe.get(Ye.key);else for(ge=de;ge<=X;ge++)if(vs[ge-de]===0&&Gt(Ye,h[ge])){dt=ge;break}dt===void 0?ue(Ye,R,M,!0):(vs[dt-de]=N+1,dt>=ut?ut=dt:ct=!0,L(Ye,h[dt],m,null,R,M,F,D,H),ze++)}const jo=ct?Qa(vs):ns;for(ge=jo.length-1,N=Ge-1;N>=0;N--){const Ye=de+N,dt=h[Ye],Bo=Ye+1{const{el:M,type:F,transition:D,children:H,shapeFlag:N}=p;if(N&6){b(p.component.subTree,h,m,A);return}if(N&128){p.suspense.move(h,m,A);return}if(N&64){F.move(p,h,m,Q);return}if(F===ye){n(M,h,m);for(let K=0;KD.enter(M),R);else{const{leave:K,delayLeave:X,afterLeave:re}=D,de=()=>{p.ctx.isUnmounted?o(M):n(M,h,m)},xe=()=>{K(M,()=>{de(),re&&re()})};X?X(M,de,xe):xe()}else n(M,h,m)},ue=(p,h,m,A=!1,R=!1)=>{const{type:M,props:F,ref:D,children:H,dynamicChildren:N,shapeFlag:se,patchFlag:K,dirs:X,cacheIndex:re}=p;if(K===-2&&(R=!1),D!=null&&(St(),Ts(D,null,m,p,!0),Tt()),re!=null&&(h.renderCache[re]=void 0),se&256){h.ctx.deactivate(p);return}const de=se&1&&X,xe=!Es(p);let ge;if(xe&&(ge=F&&F.onVnodeBeforeUnmount)&&ft(ge,h,p),se&6)Se(p.component,m,A);else{if(se&128){p.suspense.unmount(m,A);return}de&&Vt(p,null,h,"beforeUnmount"),se&64?p.type.remove(p,h,m,Q,A):N&&!N.hasOnce&&(M!==ye||K>0&&K&64)?He(N,h,m,!1,!0):(M===ye&&K&384||!R&&se&16)&&He(H,h,m),A&&Y(p)}(xe&&(ge=F&&F.onVnodeUnmounted)||de)&&Ve(()=>{ge&&ft(ge,h,p),de&&Vt(p,null,h,"unmounted")},m)},Y=p=>{const{type:h,el:m,anchor:A,transition:R}=p;if(h===ye){me(m,A);return}if(h===Ms){y(p);return}const M=()=>{o(m),R&&!R.persisted&&R.afterLeave&&R.afterLeave()};if(p.shapeFlag&1&&R&&!R.persisted){const{leave:F,delayLeave:D}=R,H=()=>F(m,M);D?D(p.el,M,H):H()}else M()},me=(p,h)=>{let m;for(;p!==h;)m=v(p),o(p),p=m;o(h)},Se=(p,h,m)=>{const{bum:A,scope:R,job:M,subTree:F,um:D,m:H,a:N,parent:se,slots:{__:K}}=p;er(H),er(N),A&&sn(A),se&&ee(K)&&K.forEach(X=>{se.renderCache[X]=void 0}),R.stop(),M&&(M.flags|=8,ue(F,p,h,m)),D&&Ve(D,h),Ve(()=>{p.isUnmounted=!0},h),h&&h.pendingBranch&&!h.isUnmounted&&p.asyncDep&&!p.asyncResolved&&p.suspenseId===h.pendingId&&(h.deps--,h.deps===0&&h.resolve())},He=(p,h,m,A=!1,R=!1,M=0)=>{for(let F=M;F{if(p.shapeFlag&6)return S(p.component.subTree);if(p.shapeFlag&128)return p.suspense.next();const h=v(p.anchor||p.el),m=h&&h[Ci];return m?v(m):h};let z=!1;const q=(p,h,m)=>{p==null?h._vnode&&ue(h._vnode,null,null,!0):L(h._vnode||null,p,h,null,null,null,m),h._vnode=p,z||(z=!0,Fo(),_i(),z=!1)},Q={p:L,um:ue,m:b,r:Y,mt:B,mc:$,pc:ce,pbc:W,n:S,o:e};return{render:q,hydrate:void 0,createApp:Ha(q)}}function Hn({type:e,props:t},s){return s==="svg"&&e==="foreignObject"||s==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:s}function Ft({effect:e,job:t},s){s?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Wa(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Mo(e,t,s=!1){const n=e.children,o=t.children;if(ee(n)&&ee(o))for(let r=0;r>1,e[s[a]]0&&(t[n]=s[r-1]),s[r]=n)}}for(r=s.length,l=s[r-1];r-- >0;)s[r]=l,l=t[l];return s}function Wi(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Wi(t)}function er(e){if(e)for(let t=0;t$t(Ya);function as(e,t,s){return Qi(e,t,s)}function Qi(e,t,s=we){const{immediate:n,deep:o,flush:r,once:l}=s,a=Re({},s),c=t&&n||!t&&r!=="post";let d;if(Vs){if(r==="sync"){const g=Za();d=g.__watcherHandles||(g.__watcherHandles=[])}else if(!c){const g=()=>{};return g.stop=ot,g.resume=ot,g.pause=ot,g}}const u=Ne;a.call=(g,x,L)=>it(g,u,x,L);let f=!1;r==="post"?a.scheduler=g=>{Ve(g,u&&u.suspense)}:r!=="sync"&&(f=!0,a.scheduler=(g,x)=>{x?g():To(g)}),a.augmentJob=g=>{t&&(g.flags|=4),f&&(g.flags|=2,u&&(g.id=u.uid,g.i=u))};const v=pa(e,t,a);return Vs&&(d?d.push(v):c&&v()),v}function Xa(e,t,s){const n=this.proxy,o=Te(e)?e.includes(".")?Yi(n,e):()=>n[e]:e.bind(n,n);let r;ie(t)?r=t:(r=t.handler,s=t);const l=Js(this),a=Qi(o,r.bind(n),s);return l(),a}function Yi(e,t){const s=t.split(".");return()=>{let n=e;for(let o=0;ot==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${nt(t)}Modifiers`]||e[`${Ht(t)}Modifiers`];function tc(e,t,...s){if(e.isUnmounted)return;const n=e.vnode.props||we;let o=s;const r=t.startsWith("update:"),l=r&&ec(n,t.slice(7));l&&(l.trim&&(o=s.map(u=>Te(u)?u.trim():u)),l.number&&(o=s.map(cn)));let a,c=n[a=Rn(t)]||n[a=Rn(nt(t))];!c&&r&&(c=n[a=Rn(Ht(t))]),c&&it(c,e,6,o);const d=n[a+"Once"];if(d){if(!e.emitted)e.emitted={};else if(e.emitted[a])return;e.emitted[a]=!0,it(d,e,6,o)}}function Zi(e,t,s=!1){const n=t.emitsCache,o=n.get(e);if(o!==void 0)return o;const r=e.emits;let l={},a=!1;if(!ie(e)){const c=d=>{const u=Zi(d,t,!0);u&&(a=!0,Re(l,u))};!s&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!r&&!a?(ke(e)&&n.set(e,null),null):(ee(r)?r.forEach(c=>l[c]=null):Re(l,r),ke(e)&&n.set(e,l),l)}function An(e,t){return!e||!bn(t)?!1:(t=t.slice(2).replace(/Once$/,""),he(e,t[0].toLowerCase()+t.slice(1))||he(e,Ht(t))||he(e,t))}function tr(e){const{type:t,vnode:s,proxy:n,withProxy:o,propsOptions:[r],slots:l,attrs:a,emit:c,render:d,renderCache:u,props:f,data:v,setupState:g,ctx:x,inheritAttrs:L}=e,J=pn(e);let j,V;try{if(s.shapeFlag&4){const y=o||n,P=y;j=ht(d.call(P,y,u,f,g,v,x)),V=a}else{const y=t;j=ht(y.length>1?y(f,{attrs:a,slots:l,emit:c}):y(f,null)),V=t.props?a:sc(a)}}catch(y){Os.length=0,Cn(y,e,1),j=Be(Ke)}let T=j;if(V&&L!==!1){const y=Object.keys(V),{shapeFlag:P}=T;y.length&&P&7&&(r&&y.some(go)&&(V=nc(V,r)),T=Bt(T,V,!1,!0))}return s.dirs&&(T=Bt(T,null,!1,!0),T.dirs=T.dirs?T.dirs.concat(s.dirs):s.dirs),s.transition&&Hs(T,s.transition),j=T,pn(J),j}const sc=e=>{let t;for(const s in e)(s==="class"||s==="style"||bn(s))&&((t||(t={}))[s]=e[s]);return t},nc=(e,t)=>{const s={};for(const n in e)(!go(n)||!(n.slice(9)in t))&&(s[n]=e[n]);return s};function oc(e,t,s){const{props:n,children:o,component:r}=e,{props:l,children:a,patchFlag:c}=t,d=r.emitsOptions;if(t.dirs||t.transition)return!0;if(s&&c>=0){if(c&1024)return!0;if(c&16)return n?sr(n,l,d):!!l;if(c&8){const u=t.dynamicProps;for(let f=0;fe.__isSuspense;function ic(e,t){t&&t.pendingBranch?ee(e)?t.effects.push(...e):t.effects.push(e):bi(e)}const ye=Symbol.for("v-fgt"),Mn=Symbol.for("v-txt"),Ke=Symbol.for("v-cmt"),Ms=Symbol.for("v-stc"),Os=[];let Xe=null;function w(e=!1){Os.push(Xe=e?null:[])}function lc(){Os.pop(),Xe=Os[Os.length-1]||null}let Ds=1;function nr(e,t=!1){Ds+=e,e<0&&Xe&&t&&(Xe.hasOnce=!0)}function el(e){return e.dynamicChildren=Ds>0?Xe||ns:null,lc(),Ds>0&&Xe&&Xe.push(e),e}function E(e,t,s,n,o,r){return el(i(e,t,s,n,o,r,!0))}function Ze(e,t,s,n,o){return el(Be(e,t,s,n,o,!0))}function vn(e){return e?e.__v_isVNode===!0:!1}function Gt(e,t){return e.type===t.type&&e.key===t.key}const tl=({key:e})=>e??null,rn=({ref:e,ref_key:t,ref_for:s})=>(typeof e=="number"&&(e=""+e),e!=null?Te(e)||Ue(e)||ie(e)?{i:Qe,r:e,k:t,f:!!s}:e:null);function i(e,t=null,s=null,n=0,o=null,r=e===ye?0:1,l=!1,a=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&tl(t),ref:t&&rn(t),scopeId:ki,slotScopeIds:null,children:s,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:r,patchFlag:n,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Qe};return a?(Po(c,s),r&128&&e.normalize(c)):s&&(c.shapeFlag|=Te(s)?8:16),Ds>0&&!l&&Xe&&(c.patchFlag>0||r&6)&&c.patchFlag!==32&&Xe.push(c),c}const Be=ac;function ac(e,t=null,s=null,n=0,o=null,r=!1){if((!e||e===ji)&&(e=Ke),vn(e)){const a=Bt(e,t,!0);return s&&Po(a,s),Ds>0&&!r&&Xe&&(a.shapeFlag&6?Xe[Xe.indexOf(e)]=a:Xe.push(a)),a.patchFlag=-2,a}if(bc(e)&&(e=e.__vccOpts),t){t=cc(t);let{class:a,style:c}=t;a&&!Te(a)&&(t.class=be(a)),ke(c)&&(So(c)&&!ee(c)&&(c=Re({},c)),t.style=Ns(c))}const l=Te(e)?1:Xi(e)?128:$i(e)?64:ke(e)?4:ie(e)?2:0;return i(e,t,s,n,o,l,r,!0)}function cc(e){return e?So(e)||Fi(e)?Re({},e):e:null}function Bt(e,t,s=!1,n=!1){const{props:o,ref:r,patchFlag:l,children:a,transition:c}=e,d=t?uc(o||{},t):o,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:d,key:d&&tl(d),ref:t&&t.ref?s&&r?ee(r)?r.concat(rn(t)):[r,rn(t)]:rn(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:a,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==ye?l===-1?16:l|16:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Bt(e.ssContent),ssFallback:e.ssFallback&&Bt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&n&&Hs(u,c.clone(u)),u}function Pe(e=" ",t=0){return Be(Mn,null,e,t)}function Oo(e,t){const s=Be(Ms,null,e);return s.staticCount=t,s}function le(e="",t=!1){return t?(w(),Ze(Ke,null,e)):Be(Ke,null,e)}function ht(e){return e==null||typeof e=="boolean"?Be(Ke):ee(e)?Be(ye,null,e.slice()):vn(e)?Lt(e):Be(Mn,null,String(e))}function Lt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Bt(e)}function Po(e,t){let s=0;const{shapeFlag:n}=e;if(t==null)t=null;else if(ee(t))s=16;else if(typeof t=="object")if(n&65){const o=t.default;o&&(o._c&&(o._d=!1),Po(e,o()),o._c&&(o._d=!0));return}else{s=32;const o=t._;!o&&!Fi(t)?t._ctx=Qe:o===3&&Qe&&(Qe.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else ie(t)?(t={default:t,_ctx:Qe},s=32):(t=String(t),n&64?(s=16,t=[Pe(t)]):s=8);e.children=t,e.shapeFlag|=s}function uc(...e){const t={};for(let s=0;sNe||Qe;let gn,io;{const e=kn(),t=(s,n)=>{let o;return(o=e[s])||(o=e[s]=[]),o.push(n),r=>{o.length>1?o.forEach(l=>l(r)):o[0](r)}};gn=t("__VUE_INSTANCE_SETTERS__",s=>Ne=s),io=t("__VUE_SSR_SETTERS__",s=>Vs=s)}const Js=e=>{const t=Ne;return gn(e),e.scope.on(),()=>{e.scope.off(),gn(t)}},or=()=>{Ne&&Ne.scope.off(),gn(null)};function nl(e){return e.vnode.shapeFlag&4}let Vs=!1;function hc(e,t=!1,s=!1){t&&io(t);const{props:n,children:o}=e.vnode,r=nl(e);Da(e,n,r,t),Ka(e,o,s||t);const l=r?vc(e,t):void 0;return t&&io(!1),l}function vc(e,t){const s=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Ra);const{setup:n}=s;if(n){St();const o=e.setupContext=n.length>1?mc(e):null,r=Js(e),l=Gs(n,e,0,[e.props,o]),a=Kr(l);if(Tt(),r(),(a||e.sp)&&!Es(e)&&Ri(e),a){if(l.then(or,or),t)return l.then(c=>{rr(e,c)}).catch(c=>{Cn(c,e,0)});e.asyncDep=l}else rr(e,l)}else ol(e)}function rr(e,t,s){ie(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ke(t)&&(e.setupState=gi(t)),ol(e)}function ol(e,t,s){const n=e.type;e.render||(e.render=n.render||ot);{const o=Js(e);St();try{La(e)}finally{Tt(),o()}}}const gc={get(e,t){return je(e,"get",""),e[t]}};function mc(e){const t=s=>{e.exposed=s||{}};return{attrs:new Proxy(e.attrs,gc),slots:e.slots,emit:e.emit,expose:t}}function On(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(gi(hi(e.exposed)),{get(t,s){if(s in t)return t[s];if(s in As)return As[s](e)},has(t,s){return s in t||s in As}})):e.proxy}function yc(e,t=!0){return ie(e)?e.displayName||e.name:e.name||t&&e.__name}function bc(e){return ie(e)&&"__vccOpts"in e}const ve=(e,t)=>da(e,t,Vs);function Ro(e,t,s){const n=arguments.length;return n===2?ke(t)&&!ee(t)?vn(t)?Be(e,null,[t]):Be(e,t):Be(e,null,t):(n>3?s=Array.prototype.slice.call(arguments,2):n===3&&vn(s)&&(s=[s]),Be(e,t,s))}const _c="3.5.17";/** +* @vue/runtime-dom v3.5.17 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let lo;const ir=typeof window<"u"&&window.trustedTypes;if(ir)try{lo=ir.createPolicy("vue",{createHTML:e=>e})}catch{}const rl=lo?e=>lo.createHTML(e):e=>e,wc="http://www.w3.org/2000/svg",kc="http://www.w3.org/1998/Math/MathML",wt=typeof document<"u"?document:null,lr=wt&&wt.createElement("template"),xc={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const o=t==="svg"?wt.createElementNS(wc,e):t==="mathml"?wt.createElementNS(kc,e):s?wt.createElement(e,{is:s}):wt.createElement(e);return e==="select"&&n&&n.multiple!=null&&o.setAttribute("multiple",n.multiple),o},createText:e=>wt.createTextNode(e),createComment:e=>wt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>wt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,o,r){const l=s?s.previousSibling:t.lastChild;if(o&&(o===r||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),s),!(o===r||!(o=o.nextSibling)););else{lr.innerHTML=rl(n==="svg"?`${e}`:n==="mathml"?`${e}`:e);const a=lr.content;if(n==="svg"||n==="mathml"){const c=a.firstChild;for(;c.firstChild;)a.appendChild(c.firstChild);a.removeChild(c)}t.insertBefore(a,s)}return[l?l.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},Mt="transition",ys="animation",Fs=Symbol("_vtc"),il={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Cc=Re({},Ei,il),$c=e=>(e.displayName="Transition",e.props=Cc,e),Sc=$c((e,{slots:t})=>Ro(_a,Tc(e),t)),qt=(e,t=[])=>{ee(e)?e.forEach(s=>s(...t)):e&&e(...t)},ar=e=>e?ee(e)?e.some(t=>t.length>1):e.length>1:!1;function Tc(e){const t={};for(const k in e)k in il||(t[k]=e[k]);if(e.css===!1)return t;const{name:s="v",type:n,duration:o,enterFromClass:r=`${s}-enter-from`,enterActiveClass:l=`${s}-enter-active`,enterToClass:a=`${s}-enter-to`,appearFromClass:c=r,appearActiveClass:d=l,appearToClass:u=a,leaveFromClass:f=`${s}-leave-from`,leaveActiveClass:v=`${s}-leave-active`,leaveToClass:g=`${s}-leave-to`}=e,x=Ec(o),L=x&&x[0],J=x&&x[1],{onBeforeEnter:j,onEnter:V,onEnterCancelled:T,onLeave:y,onLeaveCancelled:P,onBeforeAppear:I=j,onAppear:C=V,onAppearCancelled:$=T}=t,O=(k,_,B,ne)=>{k._enterCancelled=ne,Kt(k,_?u:a),Kt(k,_?d:l),B&&B()},W=(k,_)=>{k._isLeaving=!1,Kt(k,f),Kt(k,g),Kt(k,v),_&&_()},te=k=>(_,B)=>{const ne=k?C:V,oe=()=>O(_,k,B);qt(ne,[_,oe]),cr(()=>{Kt(_,k?c:r),yt(_,k?u:a),ar(ne)||ur(_,n,L,oe)})};return Re(t,{onBeforeEnter(k){qt(j,[k]),yt(k,r),yt(k,l)},onBeforeAppear(k){qt(I,[k]),yt(k,c),yt(k,d)},onEnter:te(!1),onAppear:te(!0),onLeave(k,_){k._isLeaving=!0;const B=()=>W(k,_);yt(k,f),k._enterCancelled?(yt(k,v),pr()):(pr(),yt(k,v)),cr(()=>{k._isLeaving&&(Kt(k,f),yt(k,g),ar(y)||ur(k,n,J,B))}),qt(y,[k,B])},onEnterCancelled(k){O(k,!1,void 0,!0),qt(T,[k])},onAppearCancelled(k){O(k,!0,void 0,!0),qt($,[k])},onLeaveCancelled(k){W(k),qt(P,[k])}})}function Ec(e){if(e==null)return null;if(ke(e))return[Dn(e.enter),Dn(e.leave)];{const t=Dn(e);return[t,t]}}function Dn(e){return Rl(e)}function yt(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.add(s)),(e[Fs]||(e[Fs]=new Set)).add(t)}function Kt(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.remove(n));const s=e[Fs];s&&(s.delete(t),s.size||(e[Fs]=void 0))}function cr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Ac=0;function ur(e,t,s,n){const o=e._endId=++Ac,r=()=>{o===e._endId&&n()};if(s!=null)return setTimeout(r,s);const{type:l,timeout:a,propCount:c}=Mc(e,t);if(!l)return n();const d=l+"end";let u=0;const f=()=>{e.removeEventListener(d,v),r()},v=g=>{g.target===e&&++u>=c&&f()};setTimeout(()=>{u(s[x]||"").split(", "),o=n(`${Mt}Delay`),r=n(`${Mt}Duration`),l=dr(o,r),a=n(`${ys}Delay`),c=n(`${ys}Duration`),d=dr(a,c);let u=null,f=0,v=0;t===Mt?l>0&&(u=Mt,f=l,v=r.length):t===ys?d>0&&(u=ys,f=d,v=c.length):(f=Math.max(l,d),u=f>0?l>d?Mt:ys:null,v=u?u===Mt?r.length:c.length:0);const g=u===Mt&&/\b(transform|all)(,|$)/.test(n(`${Mt}Property`).toString());return{type:u,timeout:f,propCount:v,hasTransform:g}}function dr(e,t){for(;e.lengthfr(s)+fr(e[n])))}function fr(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function pr(){return document.body.offsetHeight}function Oc(e,t,s){const n=e[Fs];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const hr=Symbol("_vod"),Pc=Symbol("_vsh"),ll=Symbol("");function Rc(e){const t=sl();if(!t)return;const s=t.ut=(o=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(r=>mn(r,o))},n=()=>{const o=e(t.proxy);t.ce?mn(t.ce,o):ao(t.subTree,o),s(o)};Ii(()=>{bi(n)}),Dt(()=>{as(n,ot,{flush:"post"});const o=new MutationObserver(n);o.observe(t.subTree.el.parentNode,{childList:!0}),En(()=>o.disconnect())})}function ao(e,t){if(e.shapeFlag&128){const s=e.suspense;e=s.activeBranch,s.pendingBranch&&!s.isHydrating&&s.effects.push(()=>{ao(s.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)mn(e.el,t);else if(e.type===ye)e.children.forEach(s=>ao(s,t));else if(e.type===Ms){let{el:s,anchor:n}=e;for(;s&&(mn(s,t),s!==n);)s=s.nextSibling}}function mn(e,t){if(e.nodeType===1){const s=e.style;let n="";for(const o in t)s.setProperty(`--${o}`,t[o]),n+=`--${o}: ${t[o]};`;s[ll]=n}}const Lc=/(^|;)\s*display\s*:/;function Ic(e,t,s){const n=e.style,o=Te(s);let r=!1;if(s&&!o){if(t)if(Te(t))for(const l of t.split(";")){const a=l.slice(0,l.indexOf(":")).trim();s[a]==null&&ln(n,a,"")}else for(const l in t)s[l]==null&&ln(n,l,"");for(const l in s)l==="display"&&(r=!0),ln(n,l,s[l])}else if(o){if(t!==s){const l=n[ll];l&&(s+=";"+l),n.cssText=s,r=Lc.test(s)}}else t&&e.removeAttribute("style");hr in e&&(e[hr]=r?n.display:"",e[Pc]&&(n.display="none"))}const vr=/\s*!important$/;function ln(e,t,s){if(ee(s))s.forEach(n=>ln(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=Nc(e,t);vr.test(s)?e.setProperty(Ht(n),s.replace(vr,""),"important"):e[n]=s}}const gr=["Webkit","Moz","ms"],Vn={};function Nc(e,t){const s=Vn[t];if(s)return s;let n=nt(t);if(n!=="filter"&&n in e)return Vn[t]=n;n=wn(n);for(let o=0;oFn||(Hc.then(()=>Fn=0),Fn=Date.now());function Vc(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;it(Fc(n,s.value),t,5,[n])};return s.value=e,s.attached=Dc(),s}function Fc(e,t){if(ee(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>o=>!o._stopped&&n&&n(o))}else return t}const kr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,qc=(e,t,s,n,o,r)=>{const l=o==="svg";t==="class"?Oc(e,n,l):t==="style"?Ic(e,s,n):bn(t)?go(t)||Bc(e,t,s,n,r):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Kc(e,t,n,l))?(br(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&yr(e,t,n,l,r,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!Te(n))?br(e,nt(t),n,r,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),yr(e,t,n,l))};function Kc(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&kr(t)&&ie(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return kr(t)&&Te(s)?!1:t in e}const Ut=e=>{const t=e.props["onUpdate:modelValue"]||!1;return ee(t)?s=>sn(t,s):t};function zc(e){e.target.composing=!0}function xr(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const st=Symbol("_assign"),qe={created(e,{modifiers:{lazy:t,trim:s,number:n}},o){e[st]=Ut(o);const r=n||o.props&&o.props.type==="number";Ct(e,t?"change":"input",l=>{if(l.target.composing)return;let a=e.value;s&&(a=a.trim()),r&&(a=cn(a)),e[st](a)}),s&&Ct(e,"change",()=>{e.value=e.value.trim()}),t||(Ct(e,"compositionstart",zc),Ct(e,"compositionend",xr),Ct(e,"change",xr))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:o,number:r}},l){if(e[st]=Ut(l),e.composing)return;const a=(r||e.type==="number")&&!/^0\d/.test(e.value)?cn(e.value):e.value,c=t??"";a!==c&&(document.activeElement===e&&e.type!=="range"&&(n&&t===s||o&&e.value.trim()===c)||(e.value=c))}},an={deep:!0,created(e,t,s){e[st]=Ut(s),Ct(e,"change",()=>{const n=e._modelValue,o=us(e),r=e.checked,l=e[st];if(ee(n)){const a=bo(n,o),c=a!==-1;if(r&&!c)l(n.concat(o));else if(!r&&c){const d=[...n];d.splice(a,1),l(d)}}else if(ps(n)){const a=new Set(n);r?a.add(o):a.delete(o),l(a)}else l(al(e,r))})},mounted:Cr,beforeUpdate(e,t,s){e[st]=Ut(s),Cr(e,t,s)}};function Cr(e,{value:t,oldValue:s},n){e._modelValue=t;let o;if(ee(t))o=bo(t,n.props.value)>-1;else if(ps(t))o=t.has(n.props.value);else{if(t===s)return;o=Yt(t,al(e,!0))}e.checked!==o&&(e.checked=o)}const Gc={created(e,{value:t},s){e.checked=Yt(t,s.props.value),e[st]=Ut(s),Ct(e,"change",()=>{e[st](us(e))})},beforeUpdate(e,{value:t,oldValue:s},n){e[st]=Ut(n),t!==s&&(e.checked=Yt(t,n.props.value))}},vt={deep:!0,created(e,{value:t,modifiers:{number:s}},n){const o=ps(t);Ct(e,"change",()=>{const r=Array.prototype.filter.call(e.options,l=>l.selected).map(l=>s?cn(us(l)):us(l));e[st](e.multiple?o?new Set(r):r:r[0]),e._assigning=!0,$n(()=>{e._assigning=!1})}),e[st]=Ut(n)},mounted(e,{value:t}){$r(e,t)},beforeUpdate(e,t,s){e[st]=Ut(s)},updated(e,{value:t}){e._assigning||$r(e,t)}};function $r(e,t){const s=e.multiple,n=ee(t);if(!(s&&!n&&!ps(t))){for(let o=0,r=e.options.length;oString(d)===String(a)):l.selected=bo(t,a)>-1}else l.selected=t.has(a);else if(Yt(us(l),t)){e.selectedIndex!==o&&(e.selectedIndex=o);return}}!s&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function us(e){return"_value"in e?e._value:e.value}function al(e,t){const s=t?"_trueValue":"_falseValue";return s in e?e[s]:t}const Jc={created(e,t,s){en(e,t,s,null,"created")},mounted(e,t,s){en(e,t,s,null,"mounted")},beforeUpdate(e,t,s,n){en(e,t,s,n,"beforeUpdate")},updated(e,t,s,n){en(e,t,s,n,"updated")}};function Wc(e,t){switch(e){case"SELECT":return vt;case"TEXTAREA":return qe;default:switch(t){case"checkbox":return an;case"radio":return Gc;default:return qe}}}function en(e,t,s,n,o){const l=Wc(e.tagName,s.props&&s.props.type)[o];l&&l(e,t,s,n)}const Qc=["ctrl","shift","alt","meta"],Yc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Qc.some(s=>e[`${s}Key`]&&!t.includes(s))},Ps=(e,t)=>{const s=e._withMods||(e._withMods={}),n=t.join(".");return s[n]||(s[n]=(o,...r)=>{for(let l=0;l{const s=e._withKeys||(e._withKeys={}),n=t.join(".");return s[n]||(s[n]=o=>{if(!("key"in o))return;const r=Ht(o.key);if(t.some(l=>l===r||Zc[l]===r))return e(o)})},Xc=Re({patchProp:qc},xc);let Sr;function eu(){return Sr||(Sr=Ga(Xc))}const ul=(...e)=>{const t=eu().createApp(...e),{mount:s}=t;return t.mount=n=>{const o=su(n);if(!o)return;const r=t._component;!ie(r)&&!r.render&&!r.template&&(r.template=o.innerHTML),o.nodeType===1&&(o.textContent="");const l=s(o,!1,tu(o));return o instanceof Element&&(o.removeAttribute("v-cloak"),o.setAttribute("data-v-app","")),l},t};function tu(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function su(e){return Te(e)?document.querySelector(e):e}/*! + * pinia v3.0.3 + * (c) 2025 Eduardo San Martin Morote + * @license MIT + */const nu=Symbol();var Tr;(function(e){e.direct="direct",e.patchObject="patch object",e.patchFunction="patch function"})(Tr||(Tr={}));function ou(){const e=Dl(!0),t=e.run(()=>Z({}));let s=[],n=[];const o=hi({install(r){o._a=r,r.provide(nu,o),r.config.globalProperties.$pinia=o,n.forEach(l=>s.push(l)),n=[]},use(r){return this._a?s.push(r):n.push(r),this},_p:s,_a:null,_e:e,_s:new Map,state:t});return o}const ru={class:"toast-content"},iu={class:"toast-icon-wrapper"},lu={class:"toast-icon"},au={class:"toast-message"},cu={key:0,class:"toast-title"},uu={class:"toast-text"},du={key:0,class:"toast-progress"},fu=at({__name:"Toast",props:{type:{default:"info"},title:{},message:{},duration:{default:2e3},closable:{type:Boolean,default:!0},showProgress:{type:Boolean,default:!0},position:{default:"center"},offset:{default:0},id:{}},emits:["close"],setup(e,{expose:t,emit:s}){const n={template:` + + + + `},o={template:` + + + + `},r={template:` + + + + `},l={template:` + + + + `},a=e,c=s,d=Z(!1),u=Z(100),f=Z(!1);let v=null,g=0,x=0;const L=ve(()=>{const $={position:"fixed",zIndex:"9999",pointerEvents:"none"};switch(a.position){case"top":return{...$,top:`${20+a.offset}px`,left:"50%",transform:"translateX(-50%)"};case"center":return{...$,top:"50%",left:"50%",transform:`translate(-50%, calc(-50% + ${a.offset}px))`};case"bottom":return{...$,bottom:`${20+a.offset}px`,left:"50%",transform:"translateX(-50%)"};default:return $}}),J=ve(()=>{switch(a.type){case"success":return n;case"error":return o;case"warning":return r;case"info":return l;default:return l}}),j=()=>{d.value=!0,g=Date.now(),x=a.duration,a.duration>0&&V()},V=()=>{v&&clearTimeout(v),v=setTimeout(()=>{T()},x),g=Date.now()},T=()=>{d.value=!1,v&&(clearTimeout(v),v=null),c("close")},y=()=>{v&&!f.value&&(f.value=!0,clearTimeout(v),x=x-(Date.now()-g))},P=()=>{f.value&&a.duration>0&&(f.value=!1,V())},I=$=>{},C=$=>{};return Dt(()=>{j()}),En(()=>{v&&clearTimeout(v)}),t({show:j,close:T,pause:y,resume:P}),($,O)=>(w(),Ze(ma,{to:"body"},[Be(Sc,{name:"toast","enter-active-class":"toast-enter-active","leave-active-class":"toast-leave-active","enter-from-class":"toast-enter-from","leave-to-class":"toast-leave-to",onEnter:I,onLeave:C},{default:xi(()=>[d.value?(w(),E("div",{key:0,class:be(["toast-overlay",`toast-${$.type}`]),style:Ns(L.value),onMouseenter:y,onMouseleave:P},[i("div",{class:be(["toast-container",`toast-${$.type}`])},[i("div",ru,[i("div",iu,[i("div",lu,[(w(),Ze(Oa(J.value)))])]),i("div",au,[$.title?(w(),E("div",cu,U($.title),1)):le("",!0),i("div",uu,U($.message),1)]),$.closable?(w(),E("button",{key:0,onClick:T,class:"toast-close"},O[0]||(O[0]=[i("svg",{width:"18",height:"18",viewBox:"0 0 18 18",fill:"currentColor"},[i("path",{d:"M9 7.586L13.657 3l1.414 1.414L10.414 9l4.657 4.586-1.414 1.414L9 10.414 4.343 15 3 13.657 7.586 9 3 4.343 4.343 3 9 7.586z"})],-1)]))):le("",!0)]),$.showProgress&&$.duration>0?(w(),E("div",du,[i("div",{class:"toast-progress-bar",style:Ns({width:u.value+"%",animationDuration:$.duration+"ms",animationPlayState:f.value?"paused":"running"})},null,4)])):le("",!0)],2)],38)):le("",!0)]),_:1})]))}}),At=(e,t)=>{const s=e.__vccOpts||e;for(const[n,o]of t)s[n]=o;return s},pu=At(fu,[["__scopeId","data-v-4c886fb0"]]),bt={success:3e3,error:6e3,warning:4e3,info:3e3,short:2e3};class hu{constructor(){gs(this,"toasts",[]);gs(this,"toastCounter",0)}show(t){const s=`toast-${++this.toastCounter}`,n=document.createElement("div"),o=this.calculateOffset(t.position||"top"),r=ul(pu,{...t,id:s,offset:o,onClose:()=>{this.remove(r)}});return r.mount(n),this.toasts.push({app:r,container:n,id:s}),r}calculateOffset(t){return this.toasts.filter(n=>!0).length*80}remove(t){const s=this.toasts.findIndex(({app:n})=>n===t);if(s>-1){const{app:n,container:o}=this.toasts[s];this.toasts.splice(s,1),n.unmount(),o.parentNode&&o.parentNode.removeChild(o),this.updatePositions()}}updatePositions(){this.toasts.forEach((t,s)=>{})}success(t,s,n){return typeof t=="string"?this.show({type:"success",title:s||"成功",message:t,duration:bt.success,...n}):this.show({type:"success",title:t.title||"成功",duration:bt.success,...t})}error(t,s,n){return typeof t=="string"?this.show({type:"error",title:s||"错误",message:t,duration:bt.error,...n}):this.show({type:"error",title:t.title||"错误",duration:bt.error,...t})}warning(t,s,n){return typeof t=="string"?this.show({type:"warning",title:s||"警告",message:t,duration:bt.warning,...n}):this.show({type:"warning",title:t.title||"警告",duration:bt.warning,...t})}info(t,s,n){return typeof t=="string"?this.show({type:"info",title:s||"提示",message:t,duration:bt.info,...n}):this.show({type:"info",title:t.title||"提示",duration:bt.info,...t})}quick(t,s="info"){return this.show({type:s,message:t,duration:bt.short,showProgress:!1,closable:!1})}persistent(t,s,n="info"){return this.show({type:n,title:s,message:t,duration:0,closable:!0})}clear(){this.toasts.forEach(({app:t})=>{this.remove(t)})}}const We=new hu;let es=null;async function vu(){if(es)return es;try{const s=await(await fetch("/sf-chain/config/api-info")).json();return es={baseUrl:s.baseUrl||"",endpoints:s.endpoints||{AI_MODELS:"/sf-chain/models",AI_OPERATIONS:"/sf-chain/operations",AI_CALL_LOGS:"/sf-chain/call-logs",AI_SYSTEM:"/sf-chain/system"}},es}catch(e){return console.warn("Failed to fetch API config, using defaults:",e),es={baseUrl:"",endpoints:{AI_MODELS:"/sf-chain/models",AI_OPERATIONS:"/sf-chain/operations",AI_CALL_LOGS:"/sf-chain/call-logs",AI_SYSTEM:"/sf-chain/system"}},es}}const Me={BASE_URL:""};function Lo(){return localStorage.getItem("token")}function gu(e){localStorage.setItem("token",e)}function mu(){localStorage.removeItem("token"),localStorage.removeItem("userInfo")}function yu(){mu(),We.error({title:"认证失败",message:"请检查您的token是否正确",duration:4e3})}function bu(){We.error({title:"需要认证",message:"请在页面顶部输入有效的token",duration:4e3})}async function _u(e,t={}){const{method:s="GET",headers:n={},body:o,signal:r,requireAuth:l=!0,isAIRequest:a=!1}=t,c={"Content-Type":"application/json",...n},d=a||e.includes("/sf-chain/");if(d||l){const u=Lo();if(u)c.Authorization=`${u}`;else throw bu(),new Error("未提供认证token");d&&(console.log("🔍 AI请求调试信息:"),console.log("URL:",e),console.log("Authorization Token:",u),console.log("请求头:",c))}try{console.log("📤 发送请求:",{url:e,method:s,headers:c});const u=await fetch(e,{method:s,headers:c,body:o,signal:r});if(u.status===401)throw d?We.error({title:"AI接口认证失败",message:"缺少Authorization请求头",duration:4e3}):yu(),new Error("认证失败");if(u.status===403)throw d?We.error({title:"AI接口权限不足",message:"Authorization验证失败",duration:4e3}):We.error({title:"权限不足",message:"您没有权限执行此操作",duration:4e3}),new Error("权限不足");if(u.status===404)throw We.error({title:"资源不存在",message:"请求的资源不存在或已被删除",duration:3e3}),new Error("资源不存在");if(u.status>=500)throw We.error({title:"服务器错误",message:"服务器暂时无法处理您的请求,请稍后重试",duration:5e3,closable:!0}),new Error("服务器错误");return u}catch(u){const f=u;throw f.name==="TypeError"&&f.message.includes("fetch")?We.error({title:"网络连接失败",message:"无法连接到服务器,请检查网络连接",duration:5e3,closable:!0}):f.name==="AbortError"?console.log("请求已取消"):!f.message.includes("认证失败")&&!f.message.includes("权限不足")&&!f.message.includes("资源不存在")&&!f.message.includes("未提供认证token")&&We.error({title:"请求失败",message:f.message||"请求处理失败,请稍后重试",duration:4e3}),u}}async function Oe(e,t={}){const s=await _u(e,t);if(!s.ok)throw new Error(`HTTP error! status: ${s.status}`);try{return await s.json()}catch{throw We.error({title:"数据解析失败",message:"服务器返回的数据格式有误",duration:3e3}),new Error("数据解析失败")}}const Jt=class Jt{constructor(){gs(this,"baseUrl","")}static getInstance(){return Jt.instance||(Jt.instance=new Jt),Jt.instance}async initialize(){const t=await vu();this.baseUrl=t.baseUrl}async request(t,s={}){this.baseUrl||await this.initialize();const n=`${this.baseUrl}${t}`,o=await fetch(n,{headers:{"Content-Type":"application/json",...s.headers},...s});if(!o.ok)throw new Error(`HTTP error! status: ${o.status}`);return o.json()}async get(t){return this.request(t,{method:"GET"})}async post(t,s){return this.request(t,{method:"POST",body:s?JSON.stringify(s):void 0})}async put(t,s){return this.request(t,{method:"PUT",body:s?JSON.stringify(s):void 0})}async delete(t){return this.request(t,{method:"DELETE"})}};gs(Jt,"instance");let co=Jt;const wu=co.getInstance(),ku={class:"header-bar"},xu={class:"header-content"},Cu={class:"header-right"},$u={class:"auth-section"},Su={class:"status-label"},Tu={class:"auth-controls"},Eu={class:"token-input-group"},Au={class:"input-actions"},Mu=["disabled"],Ou=["disabled"],Pu=at({__name:"HeaderBar",props:{onRefresh:{type:Function,default:()=>{}}},setup(e){const t=e,s=Z(""),n=Z(Lo()),o=()=>{if(!s.value.trim()){We.error({title:"输入错误",message:"请输入有效的访问令牌",duration:3e3});return}gu(s.value.trim()),n.value=s.value.trim(),s.value="",We.success({title:"令牌已保存",message:"访问令牌已成功保存并生效",duration:3e3}),r()},r=async()=>{if(!n.value){We.error({title:"未设置令牌",message:"请先设置访问令牌",duration:3e3});return}try{await t.onRefresh(),We.success({title:"数据已刷新",message:"系统数据已成功更新",duration:3e3})}catch(l){console.error("Refresh data error:",l)}};return(l,a)=>(w(),E("div",ku,[i("div",xu,[a[4]||(a[4]=Oo('

SF-Chain

AI智能调度框架

',1)),i("div",Cu,[i("div",$u,[i("div",{class:be(["connection-status",{connected:n.value}])},[a[1]||(a[1]=i("div",{class:"status-indicator"},[i("div",{class:"status-pulse"})],-1)),i("span",Su,U(n.value?"已连接":"未连接"),1)],2),i("div",Tu,[i("div",Eu,[$e(i("input",{"onUpdate:modelValue":a[0]||(a[0]=c=>s.value=c),type:"password",class:"token-input",placeholder:"输入访问令牌",onKeyup:cl(o,["enter"])},null,544),[[qe,s.value]]),i("div",Au,[i("button",{class:"action-btn save-btn",onClick:o,disabled:!s.value.trim(),title:"保存令牌"},a[2]||(a[2]=[i("svg",{viewBox:"0 0 20 20",fill:"currentColor"},[i("path",{"fill-rule":"evenodd",d:"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z","clip-rule":"evenodd"})],-1)]),8,Mu),i("button",{class:"action-btn refresh-btn",onClick:r,disabled:!n.value,title:"刷新数据"},a[3]||(a[3]=[i("svg",{viewBox:"0 0 20 20",fill:"currentColor"},[i("path",{"fill-rule":"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z","clip-rule":"evenodd"})],-1)]),8,Ou)])])])])])])]))}}),Ru=At(Pu,[["__scopeId","data-v-677538a9"]]),ks={async getAllModels(){return Oe(`${Me.BASE_URL}/sf-chain/models/list`,{method:"GET",requireAuth:!0})},async getModel(e){return Oe(`${Me.BASE_URL}/sf-chain/models/get`,{method:"POST",body:JSON.stringify({modelName:e}),requireAuth:!0})},async saveModel(e,t){return Oe(`${Me.BASE_URL}/sf-chain/models/save`,{method:"POST",body:JSON.stringify({...t,modelName:e}),requireAuth:!0})},async deleteModel(e){return Oe(`${Me.BASE_URL}/sf-chain/models/delete`,{method:"POST",body:JSON.stringify({modelName:e}),requireAuth:!0})},async testModel(e){return Oe(`${Me.BASE_URL}/sf-chain/models/test`,{method:"POST",body:JSON.stringify({modelName:e}),requireAuth:!0})}},Zt={openai:{key:"openai",name:"OpenAI",icon:"/icons/openai.svg",displayOrder:1,category:"commercial",description:"OpenAI GPT系列模型",defaultConfig:{baseUrl:"https://api.openai.com/v1",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!1}},anthropic:{key:"anthropic",name:"Anthropic",icon:"/icons/anthropic.svg",displayOrder:2,category:"commercial",description:"Anthropic Claude系列模型",defaultConfig:{baseUrl:"https://api.anthropic.com/v1",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!0}},google:{key:"google",name:"Google",icon:"/icons/google.svg",displayOrder:3,category:"commercial",description:"Google Gemini系列模型",defaultConfig:{baseUrl:"https://generativelanguage.googleapis.com/v1",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!1}},deepseek:{key:"deepseek",name:"DeepSeek",icon:"/icons/deepseek.svg",displayOrder:4,category:"domestic",description:"DeepSeek系列模型",defaultConfig:{baseUrl:"https://api.deepseek.com/v1",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!0}},doubao:{key:"doubao",name:"豆包",icon:"/icons/doubao.svg",displayOrder:5,category:"domestic",description:"字节跳动豆包系列模型",defaultConfig:{baseUrl:"https://ark.cn-beijing.volces.com/api/v3",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!1}},qianwen:{key:"qianwen",name:"千问",icon:"/icons/qianwen.svg",displayOrder:6,category:"domestic",description:"阿里云千问系列模型",defaultConfig:{baseUrl:"https://dashscope.aliyuncs.com/api/v1",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!1}},other:{key:"other",name:"其他",icon:"/icons/default.svg",displayOrder:999,category:"custom",description:"自定义或其他提供商",defaultConfig:{baseUrl:"",maxTokens:4096,temperature:.7,supportStream:!0,supportJsonOutput:!1,supportThinking:!1}}},Qt=e=>{var t;return((t=Zt[e])==null?void 0:t.name)||Zt.other.name},cs=e=>{var t;return((t=Zt[e])==null?void 0:t.icon)||Zt.other.icon},Rs=e=>{const t=e.toLowerCase();return t.includes("gpt")||t.includes("openai")?"openai":t.includes("claude")||t.includes("anthropic")?"anthropic":t.includes("gemini")||t.includes("google")?"google":t.includes("deepseek")?"deepseek":t.includes("doubao")||t.includes("豆包")?"doubao":t.includes("qwen")||t.includes("千问")||t.includes("qianwen")?"qianwen":"other"},dl=()=>Object.values(Zt).sort((e,t)=>e.displayOrder-t.displayOrder),Lu=()=>{const e={};return dl().forEach(s=>{const n=s.category||"other";e[n]||(e[n]=[]),e[n].push(s)}),e},Iu=e=>{var t;return((t=Zt[e])==null?void 0:t.defaultConfig)||Zt.other.defaultConfig},Nu=e=>({commercial:"商业模型",domestic:"国产模型",custom:"自定义",other:"其他"})[e]||e,ju=()=>dl().map(e=>e.key),Bu={class:"api-config-content"},Uu={class:"content-header"},Hu={class:"header-actions"},Du=["disabled"],Vu={key:0,class:"w-4 h-4 animate-spin",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Fu={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},qu={key:0,class:"stats-section"},Ku={class:"stat-content"},zu={class:"stat-number"},Gu=["onClick"],Ju={class:"stat-content"},Wu={class:"stat-number"},Qu={class:"stat-label"},Yu={class:"stat-icon"},Zu=["src"],Xu={class:"main-content"},ed={key:0,class:"loading-state"},td={key:1,class:"empty-state"},sd={key:2,class:"models-list"},nd={class:"list-header"},od={class:"header-info"},rd={class:"search-box"},id={class:"models-container"},ld={class:"model-cell"},ad={class:"model-identity"},cd=["onClick"],ud=["src"],dd={class:"model-info"},fd=["title","onClick"],pd={class:"base-url-cell"},hd=["title","onClick"],vd={class:"params-cell"},gd={class:"params-text"},md={class:"description-cell"},yd=["title"],bd={class:"status-cell"},_d={class:"actions-cell"},wd={class:"action-buttons"},kd=["onClick","disabled","title"],xd={key:0,class:"w-4 h-4 animate-spin",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Cd={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},$d=["onClick"],Sd=["onClick"],Td=["onClick"],Ed={class:"modal-header"},Ad={class:"form-grid"},Md={class:"form-column"},Od={class:"form-group"},Pd=["disabled"],Rd={class:"form-group"},Ld=["label"],Id=["value"],Nd={class:"form-group"},jd={class:"form-row"},Bd={class:"form-group"},Ud={class:"form-group"},Hd={class:"form-column"},Dd={class:"form-group"},Vd={class:"input-wrapper"},Fd=["type"],qd={key:0,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Kd={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},zd={class:"form-group"},Gd={class:"form-group"},Jd={class:"form-actions"},Wd=["disabled"],Qd={key:0,class:"btn-loading"},Yd={key:2,class:"copy-toast"},Zd=at({__name:"ApiInfoConfig",setup(e){const t=Z(!1),s=Z(!1),n=Z(null),o=Z(!1),r=Z(null),l=Z(!1),a=Z(null),c=Z(""),d=Z("all"),u=Z(!1),f=hs({modelName:"",baseUrl:"",apiKey:"",defaultMaxTokens:4096,defaultTemperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!1,description:"",provider:"openai",enabled:!0}),v=ve(()=>{var _;if(!((_=a.value)!=null&&_.models))return{};let k={...a.value.models};if(d.value!=="all"&&(k=Object.fromEntries(Object.entries(k).filter(([,B])=>B.provider===d.value))),c.value){const B=c.value.toLowerCase();k=Object.fromEntries(Object.entries(k).filter(([,ne])=>{var oe,ae;return ne.modelName.toLowerCase().includes(B)||((oe=ne.provider)==null?void 0:oe.toLowerCase().includes(B))||ne.baseUrl.toLowerCase().includes(B)||((ae=ne.description)==null?void 0:ae.toLowerCase().includes(B))}))}return k}),g=ve(()=>{var B;if(!((B=a.value)!=null&&B.groupedByProvider))return[];const k=[],_=ju();for(const ne of _)a.value.groupedByProvider[ne]&&k.push({provider:ne,models:a.value.groupedByProvider[ne]});for(const[ne,oe]of Object.entries(a.value.groupedByProvider))_.includes(ne)||k.push({provider:ne,models:oe});return k}),x=ve(()=>Lu()),L=k=>cs(k),J=()=>d.value==="all"?"全部模型":`${Qt(d.value)}模型`,j=k=>{d.value=k,c.value=""},V=()=>{const k=Iu(f.provider);k&&(f.baseUrl||(f.baseUrl=k.baseUrl||""),f.defaultMaxTokens||(f.defaultMaxTokens=k.maxTokens||4096),f.defaultTemperature||(f.defaultTemperature=k.temperature||.7),f.supportStream=k.supportStream??!0,f.supportJsonOutput=k.supportJsonOutput??!0,f.supportThinking=k.supportThinking??!1)},T=async k=>{try{await navigator.clipboard.writeText(k),u.value=!0,setTimeout(()=>{u.value=!1},2e3)}catch(_){console.error("复制失败:",_);const B=document.createElement("textarea");B.value=k,document.body.appendChild(B),B.select(),document.execCommand("copy"),document.body.removeChild(B),u.value=!0,setTimeout(()=>{u.value=!1},2e3)}},y=()=>{Object.assign(f,{modelName:"",baseUrl:"",apiKey:"",defaultMaxTokens:4096,defaultTemperature:.7,supportStream:!0,supportJsonOutput:!0,supportThinking:!1,description:"",provider:"openai",enabled:!0})},P=async()=>{try{t.value=!0,a.value=await ks.getAllModels()}catch(k){console.error("加载模型列表失败:",k),alert("加载模型列表失败,请稍后重试")}finally{t.value=!1}},I=async()=>{try{s.value=!0;const k=await ks.saveModel(f.modelName,f);k.success?(alert(k.message||"模型保存成功"),te(),await P()):alert(k.message||"保存失败")}catch(k){console.error("保存模型失败:",k),alert("保存模型失败,请稍后重试")}finally{s.value=!1}},C=(k,_)=>{Object.assign(f,{..._,modelName:""}),r.value=null,o.value=!0},$=(k,_)=>{r.value=k,Object.assign(f,_),o.value=!1},O=async k=>{if(confirm(`确定要删除模型 "${k}" 吗?`))try{const _=await ks.deleteModel(k);_.success?(alert(_.message||"删除成功"),await P()):alert(_.message||"删除失败")}catch(_){console.error("删除模型失败:",_),alert("删除模型失败,请稍后重试")}},W=async k=>{try{n.value=k;const _=await ks.testModel(k);_.success?alert("模型连接测试成功!"):alert(`测试失败: ${_.message}`)}catch(_){console.error("测试模型失败:",_),alert("测试模型失败,请稍后重试")}finally{n.value=null}},te=()=>{o.value=!1,r.value=null,y(),l.value=!1};return Dt(()=>{P()}),(k,_)=>(w(),E("div",Bu,[i("div",Uu,[_[17]||(_[17]=i("div",{class:"header-left"},[i("h2",null,"AI模型")],-1)),i("div",Hu,[i("button",{onClick:P,class:"btn btn-secondary",disabled:t.value},[t.value?(w(),E("svg",Vu,_[14]||(_[14]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))):(w(),E("svg",Fu,_[15]||(_[15]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))),i("span",null,U(t.value?"加载中":"刷新"),1)],8,Du),i("button",{onClick:_[0]||(_[0]=B=>o.value=!0),class:"btn btn-primary"},_[16]||(_[16]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),i("span",null,"添加模型",-1)]))])]),a.value?(w(),E("div",qu,[i("div",{class:be(["stat-card",{active:d.value==="all"}]),onClick:_[1]||(_[1]=B=>j("all"))},[i("div",Ku,[i("div",zu,U(a.value.total),1),_[18]||(_[18]=i("div",{class:"stat-label"},"全部模型",-1))])],2),(w(!0),E(ye,null,Le(g.value,({provider:B,models:ne})=>(w(),E("div",{class:be(["stat-card",{active:d.value===B}]),key:B,onClick:oe=>j(B)},[i("div",Ju,[i("div",Wu,U(ne.length),1),i("div",Qu,U(Ee(Qt)(B)),1)]),i("div",Yu,[i("img",{src:Ee(cs)(B),alt:"provider icon",class:"stat-provider-icon"},null,8,Zu)])],10,Gu))),128))])):le("",!0),i("div",Xu,[t.value&&!a.value?(w(),E("div",ed,_[19]||(_[19]=[i("div",{class:"loading-spinner"},null,-1),i("p",null,"正在加载模型配置...",-1)]))):a.value&&a.value.total===0?(w(),E("div",td,_[20]||(_[20]=[i("div",{class:"empty-icon"},"🤖",-1),i("h3",null,"暂无模型配置",-1),i("p",null,'点击"添加模型"按钮开始配置您的第一个AI模型',-1)]))):(w(),E("div",sd,[i("div",nd,[i("div",od,[i("h3",null,U(J())+" ("+U(Object.keys(v.value).length)+")",1),i("div",rd,[_[22]||(_[22]=i("svg",{class:"search-icon",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})],-1)),$e(i("input",{"onUpdate:modelValue":_[2]||(_[2]=B=>c.value=B),type:"text",placeholder:"搜索模型...",class:"search-input"},null,512),[[qe,c.value]]),c.value?(w(),E("button",{key:0,onClick:_[3]||(_[3]=B=>c.value=""),class:"clear-search"},_[21]||(_[21]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))):le("",!0)])])]),_[28]||(_[28]=Oo('
模型信息
Base URL
Token/温度
描述
状态
操作
',1)),i("div",id,[(w(!0),E(ye,null,Le(v.value,(B,ne)=>(w(),E("div",{key:ne,class:be(["model-row",{disabled:!B.enabled}])},[i("div",ld,[i("div",ad,[i("div",{class:"model-avatar",title:"点击复制图标",onClick:oe=>T(L(B.provider||"other"))},[i("img",{src:Ee(cs)(B.provider||"other"),alt:"provider icon",class:"provider-icon"},null,8,ud)],8,cd),i("div",dd,[i("h4",{class:"model-name",title:"点击复制: "+B.modelName,onClick:oe=>T(B.modelName)},U(B.modelName),9,fd),i("span",{class:be(["provider-badge",B.provider])},U(Ee(Qt)(B.provider||"other")),3)])])]),i("div",pd,[i("span",{class:"base-url-text",title:"点击复制: "+B.baseUrl,onClick:oe=>T(B.baseUrl)},U(B.baseUrl),9,hd)]),i("div",vd,[i("span",gd,U(B.defaultMaxTokens||"N/A")+" / "+U(B.defaultTemperature||"N/A"),1)]),i("div",md,[i("span",{class:"description-text",title:B.description},U(B.description||"-"),9,yd)]),i("div",bd,[i("span",{class:be(["status-badge",{enabled:B.enabled,disabled:!B.enabled}])},U(B.enabled?"启用":"禁用"),3)]),i("div",_d,[i("div",wd,[i("button",{onClick:oe=>W(String(ne)),class:"action-btn test",disabled:n.value===ne,title:n.value===ne?"测试中...":"测试连接"},[n.value===ne?(w(),E("svg",xd,_[23]||(_[23]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))):(w(),E("svg",Cd,_[24]||(_[24]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)])))],8,kd),i("button",{onClick:oe=>C(String(ne),B),class:"action-btn clone",title:"克隆模型"},_[25]||(_[25]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"})],-1)]),8,$d),i("button",{onClick:oe=>$(String(ne),B),class:"action-btn edit",title:"编辑模型"},_[26]||(_[26]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"})],-1)]),8,Sd),i("button",{onClick:oe=>O(String(ne)),class:"action-btn delete",title:"删除模型"},_[27]||(_[27]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})],-1)]),8,Td)])])],2))),128))])]))]),o.value||r.value?(w(),E("div",{key:1,class:"modal-overlay",onClick:te},[i("div",{class:"modal-content",onClick:_[13]||(_[13]=Ps(()=>{},["stop"]))},[i("div",Ed,[i("h3",null,U(r.value?"编辑模型":"添加模型"),1),i("button",{onClick:te,class:"btn-close"},_[29]||(_[29]=[i("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),i("form",{onSubmit:Ps(I,["prevent"]),class:"modal-body"},[i("div",Ad,[i("div",Md,[i("div",Od,[_[30]||(_[30]=i("label",{for:"modelName"},"模型名称 *",-1)),$e(i("input",{id:"modelName","onUpdate:modelValue":_[4]||(_[4]=B=>f.modelName=B),type:"text",placeholder:"例如: gpt-4o",class:"form-input",required:"",disabled:!!r.value},null,8,Pd),[[qe,f.modelName]])]),i("div",Rd,[_[31]||(_[31]=i("label",{for:"provider"},"提供商",-1)),$e(i("select",{id:"provider","onUpdate:modelValue":_[5]||(_[5]=B=>f.provider=B),class:"form-input",onChange:V},[(w(!0),E(ye,null,Le(x.value,(B,ne)=>(w(),E("optgroup",{key:ne,label:Ee(Nu)(ne)},[(w(!0),E(ye,null,Le(B,oe=>(w(),E("option",{key:oe.key,value:oe.key},U(oe.name),9,Id))),128))],8,Ld))),128))],544),[[vt,f.provider]])]),i("div",Nd,[_[32]||(_[32]=i("label",{for:"baseUrl"},"Base URL *",-1)),$e(i("input",{id:"baseUrl","onUpdate:modelValue":_[6]||(_[6]=B=>f.baseUrl=B),type:"url",placeholder:"https://api.openai.com/v1",class:"form-input",required:""},null,512),[[qe,f.baseUrl]])]),i("div",jd,[i("div",Bd,[_[33]||(_[33]=i("label",{for:"maxTokens"},"Token数",-1)),$e(i("input",{id:"maxTokens","onUpdate:modelValue":_[7]||(_[7]=B=>f.defaultMaxTokens=B),type:"number",placeholder:"4096",class:"form-input"},null,512),[[qe,f.defaultMaxTokens,void 0,{number:!0}]])]),i("div",Ud,[_[34]||(_[34]=i("label",{for:"temperature"},"温度",-1)),$e(i("input",{id:"temperature","onUpdate:modelValue":_[8]||(_[8]=B=>f.defaultTemperature=B),type:"number",step:"0.1",min:"0",max:"2",placeholder:"0.7",class:"form-input"},null,512),[[qe,f.defaultTemperature,void 0,{number:!0}]])])])]),i("div",Hd,[i("div",Dd,[_[37]||(_[37]=i("label",{for:"apiKey"},"API Key *",-1)),i("div",Vd,[$e(i("input",{id:"apiKey","onUpdate:modelValue":_[9]||(_[9]=B=>f.apiKey=B),type:l.value?"text":"password",placeholder:"请输入API Key",class:"form-input",required:""},null,8,Fd),[[Jc,f.apiKey]]),i("button",{onClick:_[10]||(_[10]=B=>l.value=!l.value),class:"toggle-visibility",type:"button"},[l.value?(w(),E("svg",qd,_[35]||(_[35]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"},null,-1),i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"},null,-1)]))):(w(),E("svg",Kd,_[36]||(_[36]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.878 9.878L3 3m6.878 6.878L21 21"},null,-1)])))])])]),i("div",zd,[_[39]||(_[39]=i("label",{for:"enabled"},"状态",-1)),$e(i("select",{id:"enabled","onUpdate:modelValue":_[11]||(_[11]=B=>f.enabled=B),class:"form-input"},_[38]||(_[38]=[i("option",{value:!0},"启用",-1),i("option",{value:!1},"禁用",-1)]),512),[[vt,f.enabled]])]),i("div",Gd,[_[40]||(_[40]=i("label",{for:"description"},"描述",-1)),$e(i("textarea",{id:"description","onUpdate:modelValue":_[12]||(_[12]=B=>f.description=B),placeholder:"模型描述信息",class:"form-textarea",rows:"3"},null,512),[[qe,f.description]])])])]),i("div",Jd,[i("button",{type:"button",onClick:te,class:"btn btn-secondary"}," 取消 "),i("button",{type:"submit",class:"btn btn-primary",disabled:s.value},[s.value?(w(),E("span",Qd)):le("",!0),i("span",null,U(s.value?"保存中...":"保存"),1)],8,Wd)])],32)])])):le("",!0),u.value?(w(),E("div",Yd,_[41]||(_[41]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"})],-1),Pe(" 复制成功 ")]))):le("",!0)]))}}),Xd=At(Zd,[["__scopeId","data-v-48fd4b43"]]),tn={async getAllOperations(){return Oe(`${Me.BASE_URL}/sf-chain/operations`,{method:"GET",requireAuth:!0})},async getOperation(e){return Oe(`${Me.BASE_URL}/sf-chain/operations/get`,{method:"POST",body:JSON.stringify({operationType:e}),requireAuth:!0})},async saveOperationConfig(e,t){const s={...t,operationType:e};return Oe(`${Me.BASE_URL}/sf-chain/operations/save`,{method:"POST",body:JSON.stringify(s),requireAuth:!0})},async setOperationMapping(e,t){return Oe(`${Me.BASE_URL}/sf-chain/operations/mapping`,{method:"POST",body:JSON.stringify({operationType:e,modelName:t}),requireAuth:!0})},async setOperationMappings(e){return Oe(`${Me.BASE_URL}/sf-chain/operations/mappings`,{method:"POST",body:JSON.stringify({mappings:e}),requireAuth:!0})}},ef={class:"ai-node-config-content"},tf={class:"content-header"},sf={class:"header-right"},nf={key:0,class:"header-stats"},of={class:"stat-item"},rf={class:"stat-number"},lf={class:"stat-item success"},af={class:"stat-number"},cf={class:"stat-item warning"},uf={class:"stat-number"},df={class:"stat-item info"},ff={class:"stat-number"},pf={class:"header-actions"},hf=["disabled"],vf={key:0,class:"w-4 h-4 animate-spin",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},gf={key:1,class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},mf={class:"toolbar"},yf={class:"search-container"},bf={class:"filter-tabs"},_f={class:"count"},wf={class:"count"},kf={class:"count"},xf={class:"main-content"},Cf={key:0,class:"loading-state"},$f={key:1,class:"empty-state"},Sf={key:2,class:"operations-grid"},Tf={class:"card-header"},Ef={class:"operation-info"},Af={class:"operation-icon"},Mf=["src"],Of={key:1,class:"default-icon"},Pf={class:"operation-details"},Rf={class:"operation-name"},Lf={class:"operation-desc"},If={class:"model-binding"},Nf={class:"model-selector"},jf=["onUpdate:modelValue","onChange"],Bf=["label"],Uf=["value"],Hf={key:0,class:"config-summary"},Df={class:"config-item"},Vf={class:"config-value"},Ff={class:"config-item"},qf={class:"config-value"},Kf={class:"config-item"},zf={class:"config-value"},Gf={class:"config-features"},Jf={key:0,class:"feature-tag json"},Wf={key:1,class:"feature-tag thinking"},Qf={class:"card-actions"},Yf=["onClick","disabled","title"],Zf={key:0,class:"w-5 h-5 animate-spin",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},Xf={key:1,class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},ep=["onClick"],tp={class:"modal-body compact"},sp={class:"form-row"},np={class:"form-group"},op=["label"],rp=["value"],ip={class:"form-group"},lp={class:"operation-checkboxes"},ap=["value"],cp={class:"checkbox-label"},up={class:"form-actions"},dp=["disabled"],fp={key:0,class:"btn-loading"},pp={class:"modal-header"},hp={class:"form-grid wide"},vp={class:"form-group"},gp=["label"],mp=["value"],yp={class:"form-group"},bp={class:"form-group"},_p={class:"form-group"},wp={class:"form-group checkbox-group"},kp={class:"checkbox-label"},xp={class:"form-group checkbox-group"},Cp={class:"checkbox-label"},$p={class:"form-group full-width"},Sp={class:"form-actions"},Tp=["disabled"],Ep={key:0,class:"btn-loading"},Ap={key:2,class:"success-toast"},Mp={key:3,class:"error-toast"},Op=at({__name:"AiNodeConfig",setup(e){const t=Z(!1),s=Z(!1),n=Z(!1),o=Z(null),r=Z(!1),l=Z(null),a=Z(""),c=Z(""),d=Z("all"),u=Z(!1),f=Z(!1),v=Z(""),g=Z([]),x=Z(null),L=Z(""),J=Z([]),j=hs({operationType:"",description:"",enabled:!0,maxTokens:4096,temperature:.7,jsonOutput:!1,thinkingMode:!1,customParams:{},modelName:""}),V=ve(()=>g.value.filter(G=>G.enabled)),T=ve(()=>{const G={};return V.value.forEach(b=>{const ue=b.provider||"other";G[ue]||(G[ue]=[]),G[ue].push(b)}),G}),y=ve(()=>{var b;if(!((b=x.value)!=null&&b.configs))return{};let G={...x.value.configs};if(c.value){const ue=c.value.toLowerCase();G=Object.fromEntries(Object.entries(G).filter(([Y,me])=>{var Se;return Y.toLowerCase().includes(ue)||((Se=me.description)==null?void 0:Se.toLowerCase().includes(ue))}))}return d.value==="configured"?G=Object.fromEntries(Object.entries(G).filter(([,ue])=>ue.modelName)):d.value==="pending"&&(G=Object.fromEntries(Object.entries(G).filter(([,ue])=>!ue.modelName))),G}),P=ve(()=>{var G;return(G=x.value)!=null&&G.configs?Object.values(x.value.configs).filter(b=>b.modelName).length:0}),I=ve(()=>{var G;return(G=x.value)!=null&&G.configs?Object.values(x.value.configs).filter(b=>!b.modelName).length:0}),C=G=>{const b=g.value.find(ue=>ue.modelName===G);return(b==null?void 0:b.provider)||"other"},$=G=>Qt(G),O=G=>G.enabled?G.modelName?"configured":"pending":"disabled",W=G=>G.enabled?G.modelName?"已配置":"待配置":"已禁用",te=async()=>{try{t.value=!0;const[G,b]=await Promise.all([ks.getAllModels(),tn.getAllOperations()]);g.value=Object.values(G.models||{}).map(ue=>({modelName:ue.modelName,provider:ue.provider||"other",enabled:ue.enabled??!0})),x.value=b}catch(G){console.error("加载数据失败:",G),Ae("加载数据失败,请稍后重试","error")}finally{t.value=!1}},k=async G=>{try{o.value=G,await new Promise(b=>setTimeout(b,2e3)),Ae("测试成功")}catch(b){console.error("测试失败:",b),Ae("测试失败,请检查配置","error")}finally{o.value=null}},_=async(G,b)=>{try{await tn.setOperationMapping(G,b),Ae("映射更新成功"),await te()}catch(ue){console.error("更新映射失败:",ue),Ae("更新映射失败,请稍后重试","error")}},B=(G,b)=>{a.value=G,l.value=b,Object.assign(j,{...b,operationType:G})},ne=()=>{l.value=null,a.value=""},oe=async()=>{try{s.value=!0,await tn.saveOperationConfig(a.value,j),Ae("配置保存成功"),ne(),await te()}catch(G){console.error("保存配置失败:",G),Ae("保存失败: "+(G.message||"未知错误"),"error")}finally{s.value=!1}},ae=()=>{r.value=!1,L.value="",J.value=[]},ce=async()=>{try{n.value=!0;const G={};J.value.forEach(b=>{G[b]=L.value}),await tn.setOperationMappings(G),Ae("批量配置成功"),ae(),await te()}catch(G){console.error("批量配置失败:",G),Ae("批量配置失败: "+(G.message||"未知错误"),"error")}finally{n.value=!1}},Ae=(G,b="success")=>{v.value=G,b==="success"?(u.value=!0,setTimeout(()=>{u.value=!1},3e3)):(f.value=!0,setTimeout(()=>{f.value=!1},3e3))};return te(),(G,b)=>{var ue;return w(),E("div",ef,[i("div",tf,[b[24]||(b[24]=i("div",{class:"header-left"},[i("h2",null,"AI节点")],-1)),i("div",sf,[x.value?(w(),E("div",nf,[i("div",of,[i("span",rf,U(x.value.totalOperations),1),b[17]||(b[17]=i("span",{class:"stat-label"},"总节点",-1))]),i("div",lf,[i("span",af,U(P.value),1),b[18]||(b[18]=i("span",{class:"stat-label"},"已配置",-1))]),i("div",cf,[i("span",uf,U(I.value),1),b[19]||(b[19]=i("span",{class:"stat-label"},"待配置",-1))]),i("div",df,[i("span",ff,U(V.value.length),1),b[20]||(b[20]=i("span",{class:"stat-label"},"可用模型",-1))])])):le("",!0),i("div",pf,[i("button",{onClick:te,class:"btn btn-secondary",disabled:t.value},[t.value?(w(),E("svg",vf,b[21]||(b[21]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))):(w(),E("svg",gf,b[22]||(b[22]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))),i("span",null,U(t.value?"加载中":"刷新"),1)],8,hf),i("button",{onClick:b[0]||(b[0]=Y=>r.value=!0),class:"btn btn-primary"},b[23]||(b[23]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4"})],-1),i("span",null,"批量配置",-1)]))])])]),i("div",mf,[i("div",yf,[b[26]||(b[26]=i("svg",{class:"search-icon",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})],-1)),$e(i("input",{"onUpdate:modelValue":b[1]||(b[1]=Y=>c.value=Y),type:"text",placeholder:"搜索节点...",class:"search-input"},null,512),[[qe,c.value]]),c.value?(w(),E("button",{key:0,onClick:b[2]||(b[2]=Y=>c.value=""),class:"clear-btn"},b[25]||(b[25]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))):le("",!0)]),i("div",bf,[i("button",{onClick:b[3]||(b[3]=Y=>d.value="all"),class:be(["filter-tab",{active:d.value==="all"}])},[b[27]||(b[27]=Pe(" 全部 ")),i("span",_f,U(Object.keys(y.value).length),1)],2),i("button",{onClick:b[4]||(b[4]=Y=>d.value="configured"),class:be(["filter-tab",{active:d.value==="configured"}])},[b[28]||(b[28]=Pe(" 已配置 ")),i("span",wf,U(P.value),1)],2),i("button",{onClick:b[5]||(b[5]=Y=>d.value="pending"),class:be(["filter-tab",{active:d.value==="pending"}])},[b[29]||(b[29]=Pe(" 待配置 ")),i("span",kf,U(I.value),1)],2)])]),i("div",xf,[t.value&&!x.value?(w(),E("div",Cf,b[30]||(b[30]=[i("div",{class:"loading-spinner"},null,-1),i("p",null,"正在加载节点配置...",-1)]))):x.value&&x.value.totalOperations===0?(w(),E("div",$f,b[31]||(b[31]=[i("div",{class:"empty-icon"},"🤖",-1),i("h3",null,"暂无AI操作节点",-1),i("p",null,"系统中还没有注册任何AI操作节点",-1)]))):(w(),E("div",Sf,[(w(!0),E(ye,null,Le(y.value,(Y,me)=>(w(),E("div",{key:me,class:be(["operation-card",{configured:Y.modelName,disabled:!Y.enabled,testing:o.value===me}])},[i("div",Tf,[i("div",Ef,[i("div",Af,[Y.modelName?(w(),E("img",{key:0,src:Ee(cs)(C(Y.modelName)),alt:"provider",class:"provider-icon-large"},null,8,Mf)):(w(),E("div",Of,"🤖"))]),i("div",Pf,[i("h4",Rf,U(String(me)),1),i("p",Lf,U(Y.description||"无描述"),1)])]),i("div",{class:be(["status-badge",O(Y)])},U(W(Y)),3)]),i("div",If,[i("div",Nf,[$e(i("select",{"onUpdate:modelValue":Se=>Y.modelName=Se,onChange:Se=>_(String(me),Y.modelName||""),class:be(["model-select",{"has-value":Y.modelName}])},[b[32]||(b[32]=i("option",{value:""},"选择模型",-1)),(w(!0),E(ye,null,Le(T.value,(Se,He)=>(w(),E("optgroup",{key:He,label:$(He)},[(w(!0),E(ye,null,Le(Se,S=>(w(),E("option",{key:S.modelName,value:S.modelName},U(S.modelName),9,Uf))),128))],8,Bf))),128))],42,jf),[[vt,Y.modelName]])])]),Y.modelName?(w(),E("div",Hf,[i("div",Df,[b[33]||(b[33]=i("span",{class:"config-label"},"模型:",-1)),i("span",Vf,U(Y.modelName),1)]),i("div",Ff,[b[34]||(b[34]=i("span",{class:"config-label"},"Token:",-1)),i("span",qf,U(Y.maxTokens||4096),1)]),i("div",Kf,[b[35]||(b[35]=i("span",{class:"config-label"},"温度:",-1)),i("span",zf,U(Y.temperature||.7),1)]),i("div",Gf,[Y.jsonOutput?(w(),E("span",Jf,"JSON")):le("",!0),Y.thinkingMode?(w(),E("span",Wf,"思考")):le("",!0)])])):le("",!0),i("div",Qf,[Y.modelName?(w(),E("button",{key:0,onClick:Se=>k(String(me)),class:"action-btn test large",disabled:o.value===me,title:o.value===me?"测试中...":"测试操作"},[o.value===me?(w(),E("svg",Zf,b[36]||(b[36]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))):(w(),E("svg",Xf,b[37]||(b[37]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1)])))],8,Yf)):le("",!0),i("button",{onClick:Se=>B(String(me),Y),class:"action-btn edit large",title:"编辑配置"},b[38]||(b[38]=[i("svg",{class:"w-5 h-5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"})],-1)]),8,ep)])],2))),128))]))]),r.value?(w(),E("div",{key:0,class:"modal-overlay",onClick:ae},[i("div",{class:"modal-content wide",onClick:b[8]||(b[8]=Ps(()=>{},["stop"]))},[i("div",{class:"modal-header"},[b[40]||(b[40]=i("h3",null,"批量配置",-1)),i("button",{onClick:ae,class:"btn-close"},b[39]||(b[39]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),i("div",tp,[i("div",sp,[i("div",np,[b[42]||(b[42]=i("label",null,"选择默认模型",-1)),$e(i("select",{"onUpdate:modelValue":b[6]||(b[6]=Y=>L.value=Y),class:"form-input"},[b[41]||(b[41]=i("option",{value:""},"选择模型",-1)),(w(!0),E(ye,null,Le(T.value,(Y,me)=>(w(),E("optgroup",{key:me,label:$(me)},[(w(!0),E(ye,null,Le(Y,Se=>(w(),E("option",{key:Se.modelName,value:Se.modelName},U(Se.modelName),9,rp))),128))],8,op))),128))],512),[[vt,L.value]])]),i("div",ip,[b[43]||(b[43]=i("label",null,"选择操作节点",-1)),i("div",lp,[(w(!0),E(ye,null,Le((ue=x.value)==null?void 0:ue.configs,(Y,me)=>(w(),E("label",{key:me,class:"checkbox-item"},[$e(i("input",{type:"checkbox","onUpdate:modelValue":b[7]||(b[7]=Se=>J.value=Se),value:me,class:"checkbox-input"},null,8,ap),[[an,J.value]]),i("span",cp,U(String(me)),1)]))),128))])])]),i("div",up,[i("button",{type:"button",onClick:ae,class:"btn btn-secondary"}," 取消 "),i("button",{onClick:ce,class:"btn btn-primary",disabled:!L.value||J.value.length===0||n.value},[n.value?(w(),E("span",fp)):le("",!0),i("span",null,U(n.value?"应用中...":"应用配置"),1)],8,dp)])])])])):le("",!0),l.value?(w(),E("div",{key:1,class:"modal-overlay",onClick:ne},[i("div",{class:"modal-content wide",onClick:b[16]||(b[16]=Ps(()=>{},["stop"]))},[i("div",pp,[i("h3",null,"配置节点 - "+U(a.value),1),i("button",{onClick:ne,class:"btn-close"},b[44]||(b[44]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)]))]),i("form",{onSubmit:Ps(oe,["prevent"]),class:"modal-body compact"},[i("div",hp,[i("div",vp,[b[46]||(b[46]=i("label",{for:"operationModel"},"绑定模型",-1)),$e(i("select",{id:"operationModel","onUpdate:modelValue":b[9]||(b[9]=Y=>j.modelName=Y),class:"form-input"},[b[45]||(b[45]=i("option",{value:""},"选择模型",-1)),(w(!0),E(ye,null,Le(T.value,(Y,me)=>(w(),E("optgroup",{key:me,label:$(me)},[(w(!0),E(ye,null,Le(Y,Se=>(w(),E("option",{key:Se.modelName,value:Se.modelName},U(Se.modelName),9,mp))),128))],8,gp))),128))],512),[[vt,j.modelName]])]),i("div",yp,[b[48]||(b[48]=i("label",{for:"operationEnabled"},"状态",-1)),$e(i("select",{id:"operationEnabled","onUpdate:modelValue":b[10]||(b[10]=Y=>j.enabled=Y),class:"form-input"},b[47]||(b[47]=[i("option",{value:!0},"启用",-1),i("option",{value:!1},"禁用",-1)]),512),[[vt,j.enabled]])]),i("div",bp,[b[49]||(b[49]=i("label",{for:"operationMaxTokens"},"Token数",-1)),$e(i("input",{id:"operationMaxTokens","onUpdate:modelValue":b[11]||(b[11]=Y=>j.maxTokens=Y),type:"number",placeholder:"4096",class:"form-input"},null,512),[[qe,j.maxTokens,void 0,{number:!0}]])]),i("div",_p,[b[50]||(b[50]=i("label",{for:"operationTemperature"},"温度",-1)),$e(i("input",{id:"operationTemperature","onUpdate:modelValue":b[12]||(b[12]=Y=>j.temperature=Y),type:"number",step:"0.1",min:"0",max:"2",placeholder:"0.7",class:"form-input"},null,512),[[qe,j.temperature,void 0,{number:!0}]])]),i("div",wp,[i("label",kp,[$e(i("input",{type:"checkbox","onUpdate:modelValue":b[13]||(b[13]=Y=>j.jsonOutput=Y),class:"checkbox-input"},null,512),[[an,j.jsonOutput]]),b[51]||(b[51]=i("span",null,"JSON输出",-1))])]),i("div",xp,[i("label",Cp,[$e(i("input",{type:"checkbox","onUpdate:modelValue":b[14]||(b[14]=Y=>j.thinkingMode=Y),class:"checkbox-input"},null,512),[[an,j.thinkingMode]]),b[52]||(b[52]=i("span",null,"思考模式",-1))])]),i("div",$p,[b[53]||(b[53]=i("label",{for:"operationDescription"},"描述",-1)),$e(i("textarea",{id:"operationDescription","onUpdate:modelValue":b[15]||(b[15]=Y=>j.description=Y),placeholder:"操作描述信息",class:"form-textarea",rows:"2"},null,512),[[qe,j.description]])])]),i("div",Sp,[i("button",{type:"button",onClick:ne,class:"btn btn-secondary"}," 取消 "),i("button",{type:"submit",class:"btn btn-primary",disabled:s.value},[s.value?(w(),E("span",Ep)):le("",!0),i("span",null,U(s.value?"保存中...":"保存"),1)],8,Tp)])],32)])])):le("",!0),u.value?(w(),E("div",Ap,[b[54]||(b[54]=i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"})],-1)),Pe(" "+U(v.value),1)])):le("",!0),f.value?(w(),E("div",Mp,[b[55]||(b[55]=i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)),Pe(" "+U(v.value),1)])):le("",!0)])}}}),Pp=At(Op,[["__scopeId","data-v-de75772f"]]),Rp={class:"system-content"},Lp={class:"system-operations"},Ip={class:"operation-card"},Np={class:"card-content"},jp=["disabled"],Bp={key:0,class:"btn-loading"},Up={class:"operation-card"},Hp={class:"card-content"},Dp=["disabled"],Vp={key:0,class:"btn-loading"},Fp={class:"operation-card"},qp={class:"card-content"},Kp=["disabled"],zp={key:0,class:"btn-loading"},qn="/api/sf-chain",Gp=at({__name:"SystemManagement",props:{systemOverview:{}},emits:["update-overview"],setup(e,{emit:t}){const s=t,n=Z(!1),o=Z(!1),r=Z(!1),l=async()=>{n.value=!0;try{const u=await(await fetch(`${qn}/system/backup`,{method:"POST"})).json();if(u.success)alert("系统配置备份创建成功"),u.data&&s("update-overview",u.data);else throw new Error(u.message||"备份失败")}catch(d){const u=d instanceof Error?d.message:"创建备份时出错";alert("备份失败: "+u)}finally{n.value=!1}},a=async()=>{o.value=!0;try{const u=await(await fetch(`${qn}/system/refresh`,{method:"POST"})).json();if(u.success)alert("系统配置刷新成功"),u.data&&s("update-overview",u.data);else throw new Error(u.message||"刷新失败")}catch(d){const u=d instanceof Error?d.message:"刷新系统配置时出错";alert("刷新失败: "+u)}finally{o.value=!1}},c=async()=>{if(confirm("确定要重置系统配置吗?此操作不可撤销!")){r.value=!0;try{const u=await(await fetch(`${qn}/system/reset`,{method:"POST"})).json();if(u.success)alert("系统配置重置成功"),u.data&&s("update-overview",u.data);else throw new Error(u.message||"重置失败")}catch(d){const u=d instanceof Error?d.message:"重置系统配置时出错";alert("重置失败: "+u)}finally{r.value=!1}}};return(d,u)=>(w(),E("div",Rp,[u[9]||(u[9]=i("div",{class:"content-header"},[i("h2",null,"系统管理"),i("p",null,"系统配置备份、刷新和重置操作")],-1)),i("div",Lp,[i("div",Ip,[u[2]||(u[2]=i("div",{class:"card-icon"},[i("span",null,"💾")],-1)),i("div",Np,[u[0]||(u[0]=i("h3",null,"配置备份",-1)),u[1]||(u[1]=i("p",null,"创建当前系统配置的备份文件",-1)),i("button",{onClick:l,class:"btn btn-primary",disabled:n.value},[n.value?(w(),E("span",Bp)):le("",!0),i("span",null,U(n.value?"备份中...":"创建备份"),1)],8,jp)])]),i("div",Up,[u[5]||(u[5]=i("div",{class:"card-icon"},[i("span",null,"🔄")],-1)),i("div",Hp,[u[3]||(u[3]=i("h3",null,"刷新配置",-1)),u[4]||(u[4]=i("p",null,"重新加载系统配置信息",-1)),i("button",{onClick:a,class:"btn btn-secondary",disabled:o.value},[o.value?(w(),E("span",Vp)):le("",!0),i("span",null,U(o.value?"刷新中...":"刷新配置"),1)],8,Dp)])]),i("div",Fp,[u[8]||(u[8]=i("div",{class:"card-icon"},[i("span",null,"⚠️")],-1)),i("div",qp,[u[6]||(u[6]=i("h3",null,"重置系统",-1)),u[7]||(u[7]=i("p",null,"将系统配置重置为默认状态",-1)),i("button",{onClick:c,class:"btn btn-danger",disabled:r.value},[r.value?(w(),E("span",zp)):le("",!0),i("span",null,U(r.value?"重置中...":"重置系统"),1)],8,Kp)])])])]))}}),Jp=At(Gp,[["__scopeId","data-v-038ec4b3"]]),uo={async getAllLogSummaries(){return Oe(`${Me.BASE_URL}/sf-chain/ai-logs`,{method:"GET",requireAuth:!0})},async getFullLog(e){return Oe(`${Me.BASE_URL}/sf-chain/ai-logs/${encodeURIComponent(e)}`,{method:"GET",requireAuth:!0})},async getLogSummariesByOperation(e){return Oe(`${Me.BASE_URL}/sf-chain/ai-logs/operation/${encodeURIComponent(e)}`,{method:"GET",requireAuth:!0})},async getLogSummariesByModel(e){return Oe(`${Me.BASE_URL}/sf-chain/ai-logs/model/${encodeURIComponent(e)}`,{method:"GET",requireAuth:!0})},async getStatistics(){return Oe(`${Me.BASE_URL}/sf-chain/ai-logs/statistics`,{method:"GET",requireAuth:!0})},async clearLogs(){return Oe(`${Me.BASE_URL}/sf-chain/ai-logs`,{method:"DELETE",requireAuth:!0})}},Wp={class:"json-viewer"},Qp={class:"json-toolbar"},Yp={class:"toolbar-left"},Zp={viewBox:"0 0 16 16",fill:"currentColor"},Xp=["d"],eh={key:0,viewBox:"0 0 16 16",fill:"currentColor"},th={key:1,viewBox:"0 0 16 16",fill:"currentColor"},sh={class:"toolbar-center"},nh={key:0,class:"search-box"},oh={class:"toolbar-right"},rh={key:0,class:"path-display"},ih={class:"size-info"},lh={key:1,class:"item-count"},ah={class:"json-content",ref:"jsonContainer"},ch={key:0,class:"json-raw"},uh=["innerHTML"],dh=at({__name:"JsonViewer",props:{data:{type:[String,Number,Boolean,null,Object,Array]},maxHeight:{default:"400px"}},setup(e){Rc(P=>({"1b6c152d":t.maxHeight}));const t=e,s=Z(!0),n=Z(!0),o=Z(new Set),r=Z(""),l=Z(!1),a=Z(""),c=ve(()=>{if(!r.value.trim())return new Set;const P=new Set,I=r.value.toLowerCase(),C=($,O="")=>{$!=null&&(typeof $=="object"&&!Array.isArray($)?Object.entries($).forEach(([W,te])=>{const k=O?`${O}.${W}`:W;W.toLowerCase().includes(I)&&P.add(k),typeof te=="string"&&te.toLowerCase().includes(I)&&P.add(k),C(te,k)}):Array.isArray($)&&$.forEach((W,te)=>{const k=`${O}[${te}]`;typeof W=="string"&&W.toLowerCase().includes(I)&&P.add(k),C(W,k)}))};return C(t.data),P}),d=(P,I="",C=0)=>{if(P===null)return'null';if(P===void 0)return'undefined';const $=typeof P,O=" ".repeat(C),te=c.value.has(I)?" search-highlight":"";if($==="string")return`${u(JSON.stringify(P))}`;if($==="number")return`${P}`;if($==="boolean")return`${P}`;if(Array.isArray(P)){if(P.length===0)return'[]';const k=n.value||o.value.has(I)||c.value.has(I),_=k?"expanded":"collapsed";if(!k)return`[...${P.length}项]`;const B=P.map((ne,oe)=>{const ae=`${I}[${oe}]`;return` +${O} ${d(ne,ae,C+1)}`}).join(",");return`[${B} +${O}]`}if($==="object"&&P!==null){const k=Object.entries(P);if(k.length===0)return'{}';const _=n.value||o.value.has(I)||c.value.has(I),B=_?"expanded":"collapsed";if(!_)return`{...${k.length}键}`;const ne=k.map(([oe,ae])=>{const ce=I?`${I}.${oe}`:oe,Ae=c.value.has(ce)?" search-highlight":"";return` +${O} ${u(JSON.stringify(oe))}: ${d(ae,ce,C+1)}`}).join(",");return`{${ne} +${O}}`}return String(P)},u=P=>P.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"),f=ve(()=>d(t.data)),v=()=>{s.value=!s.value,s.value||(r.value="")},g=()=>{n.value=!n.value,o.value.clear()},x=async()=>{try{const P=s.value?JSON.stringify(t.data,null,2):JSON.stringify(t.data);await navigator.clipboard.writeText(P),l.value=!0,setTimeout(()=>{l.value=!1},2e3)}catch(P){console.error("复制失败:",P)}},L=()=>{const P=JSON.stringify(t.data),I=new Blob([P]).size;return I<1024?`${I}B`:I<1024*1024?`${(I/1024).toFixed(1)}KB`:`${(I/(1024*1024)).toFixed(1)}MB`},J=()=>`${j(t.data)}项`,j=P=>P==null||typeof P!="object"?1:Array.isArray(P)?P.reduce((I,C)=>I+j(C),0):Object.values(P).reduce((I,C)=>I+j(C),0),V=P=>{const I=P.target;if(I.classList.contains("json-toggle")){const C=I.dataset.path||"";o.value.has(C)?o.value.delete(C):o.value.add(C)}},T=P=>{const C=P.target.dataset.path;C&&(a.value=C)},y=()=>{a.value=""};return $n(()=>{const P=document.querySelector(".json-formatted");P&&(P.addEventListener("click",V),P.addEventListener("mouseover",T),P.addEventListener("mouseout",y))}),(P,I)=>(w(),E("div",Wp,[i("div",Qp,[i("div",Yp,[i("button",{onClick:v,class:be(["tool-btn",{active:s.value}])},[I[3]||(I[3]=i("svg",{viewBox:"0 0 16 16",fill:"currentColor"},[i("path",{d:"M2 4h12M2 8h12M2 12h12",stroke:"currentColor","stroke-width":"1.5",fill:"none"})],-1)),Pe(" "+U(s.value?"压缩":"格式化"),1)],2),i("button",{onClick:g,class:"tool-btn"},[(w(),E("svg",Zp,[i("path",{d:n.value?"M8 3l5 5-1.5 1.5L8 6l-3.5 3.5L3 8l5-5z":"M3 8l5 5 5-5-1.5-1.5L8 10 4.5 6.5 3 8z"},null,8,Xp)])),Pe(" "+U(n.value?"折叠":"展开"),1)]),i("button",{onClick:x,class:be(["tool-btn",{success:l.value}])},[l.value?(w(),E("svg",th,I[5]||(I[5]=[i("path",{d:"M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"},null,-1)]))):(w(),E("svg",eh,I[4]||(I[4]=[i("path",{d:"M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2Zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H6ZM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1H2Z"},null,-1)]))),Pe(" "+U(l.value?"已复制":"复制"),1)],2)]),i("div",sh,[s.value?(w(),E("div",nh,[I[7]||(I[7]=i("svg",{viewBox:"0 0 16 16",fill:"currentColor"},[i("path",{d:"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"})],-1)),$e(i("input",{"onUpdate:modelValue":I[0]||(I[0]=C=>r.value=C),placeholder:"搜索键或值...",onKeydown:I[1]||(I[1]=cl(C=>r.value="",["esc"])),class:"search-input"},null,544),[[qe,r.value]]),r.value?(w(),E("button",{key:0,onClick:I[2]||(I[2]=C=>r.value=""),class:"clear-search"},I[6]||(I[6]=[i("svg",{viewBox:"0 0 16 16",fill:"currentColor"},[i("path",{d:"M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z"})],-1)]))):le("",!0)])):le("",!0)]),i("div",oh,[a.value?(w(),E("span",rh,U(a.value),1)):le("",!0),i("span",ih,U(L()),1),s.value?(w(),E("span",lh,U(J()),1)):le("",!0)])]),i("div",ah,[s.value?(w(),E("div",{key:1,class:"json-formatted",innerHTML:f.value},null,8,uh)):(w(),E("div",ch,U(JSON.stringify(P.data)),1))],512)]))}}),bs=At(dh,[["__scopeId","data-v-6b3d677c"]]),fh={class:"log-detail-page"},ph={class:"top-nav"},hh={class:"nav-title"},vh={class:"content-wrapper"},gh={key:0,class:"loading-container"},mh={key:1,class:"detail-layout"},yh={class:"info-sidebar"},bh={class:"info-card"},_h={class:"info-list"},wh={class:"info-item"},kh={class:"call-id"},xh={class:"info-item"},Ch={class:"operation-badge"},$h={class:"info-item"},Sh={class:"model-info"},Th={class:"provider-icon-wrapper"},Eh=["src","alt"],Ah={class:"model-details"},Mh={class:"model-name"},Oh={class:"provider-name"},Ph={class:"info-item"},Rh={class:"time-value"},Lh={class:"info-item"},Ih={key:0,class:"info-card"},Nh={class:"params-grid"},jh={class:"param-item"},Bh={class:"param-value"},Uh={class:"param-item"},Hh={class:"param-value"},Dh={class:"param-item"},Vh={class:"param-item"},Fh={class:"content-area"},qh={class:"content-tabs"},Kh=["onClick"],zh={class:"tab-content"},Gh={class:"tab-label"},Jh={key:0,class:"tab-indicator"},Wh={class:"content-display"},Qh={key:0,class:"content-block"},Yh={class:"content-header"},Zh={class:"content-body"},Xh={key:1,class:"text-content"},ev={key:1,class:"content-block"},tv={class:"content-header"},sv={class:"content-body"},nv={key:1,class:"text-content"},ov={key:2,class:"content-block"},rv={class:"content-header"},iv={class:"content-body"},lv={key:1,class:"text-content"},av={key:3,class:"content-block"},cv={class:"content-header"},uv={class:"content-body"},dv={key:1,class:"text-content"},fv={key:4,class:"content-block error"},pv={class:"content-header"},hv={class:"content-body"},vv={key:1,class:"error-content"},gv={key:5,class:"empty-state"},mv=at({__name:"LogDetailModal",props:{log:{}},emits:["goBack"],setup(e,{emit:t}){const s=e,n=t,o=Z(null),r=Z(!1),l=Z("input"),a=T=>{if(typeof T=="object")return!0;if(typeof T=="string")try{return JSON.parse(T),!0}catch{return!1}return!1},c=T=>{if(typeof T=="object")return T;if(typeof T=="string")try{return JSON.parse(T)}catch{return T}return T},d=ve(()=>o.value?[{key:"input",label:"输入",hasContent:!!o.value.input},{key:"prompt",label:"提示词",hasContent:!!o.value.prompt},{key:"rawResponse",label:"原始响应",hasContent:!!o.value.rawResponse},{key:"output",label:"输出",hasContent:!!o.value.output},{key:"error",label:"错误",hasContent:!!o.value.errorMessage}]:[]);as(d,T=>{const y=T.find(P=>P.hasContent);y&&!T.find(P=>P.key===l.value&&P.hasContent)&&(l.value=y.key)},{immediate:!0});const u=ve(()=>{const T=d.value.find(y=>y.key===l.value);return(T==null?void 0:T.hasContent)||!1}),f=async()=>{var T;if((T=s.log)!=null&&T.callId)try{r.value=!0,o.value=await uo.getFullLog(s.log.callId)}catch(y){console.error("获取日志详情失败:",y),alert("获取日志详情失败,请稍后重试")}finally{r.value=!1}},v=()=>{n("goBack")},g=async T=>{try{await navigator.clipboard.writeText(T),alert("已复制到剪贴板")}catch(y){console.error("复制失败:",y),alert("复制失败,请手动复制")}},x=T=>new Date(T).toLocaleString("zh-CN",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"}),L=T=>T<1e3?`${T}ms`:`${(T/1e3).toFixed(2)}s`,J=T=>T<2e3?"fast":T<1e4?"normal":"slow",j=T=>T==="SUCCESS"?"success":"failed",V=T=>T==="SUCCESS"?"成功":"失败";return Dt(()=>{f()}),(T,y)=>{var P,I;return w(),E("div",fh,[i("div",ph,[i("button",{onClick:v,class:"nav-back"},y[5]||(y[5]=[i("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 19l-7-7 7-7"})],-1),Pe(" 返回日志 ")])),i("div",hh,[y[7]||(y[7]=i("h1",null,"调用详情",-1)),i("div",{class:be(["status-badge",j((P=o.value)==null?void 0:P.status)])},[y[6]||(y[6]=i("div",{class:"status-icon"},null,-1)),Pe(" "+U(V((I=o.value)==null?void 0:I.status)),1)],2)])]),i("div",vh,[r.value?(w(),E("div",gh,y[8]||(y[8]=[i("div",{class:"spinner-container"},[i("div",{class:"spinner"}),i("div",{class:"spinner-glow"})],-1),i("span",{class:"loading-text"},"加载详情中...",-1)]))):o.value?(w(),E("div",mh,[i("div",yh,[i("div",bh,[y[15]||(y[15]=i("div",{class:"card-title"},[i("div",{class:"title-icon"},"📊"),Pe(" 基本信息 ")],-1)),i("div",_h,[i("div",wh,[y[9]||(y[9]=i("span",{class:"label"},"调用ID",-1)),i("code",kh,U(o.value.callId),1)]),i("div",xh,[y[10]||(y[10]=i("span",{class:"label"},"操作类型",-1)),i("span",Ch,U(o.value.operationType),1)]),i("div",$h,[y[11]||(y[11]=i("span",{class:"label"},"模型",-1)),i("div",Sh,[i("div",Th,[i("img",{src:Ee(cs)(Ee(Rs)(o.value.modelName)),class:"provider-icon",alt:Ee(Qt)(Ee(Rs)(o.value.modelName))},null,8,Eh)]),i("div",Ah,[i("div",Mh,U(o.value.modelName),1),i("div",Oh,U(Ee(Qt)(Ee(Rs)(o.value.modelName))),1)])])]),i("div",Ph,[y[12]||(y[12]=i("span",{class:"label"},"调用时间",-1)),i("span",Rh,U(x(o.value.callTime)),1)]),i("div",Lh,[y[14]||(y[14]=i("span",{class:"label"},"耗时",-1)),i("span",{class:be(["duration-badge",J(o.value.duration)])},[y[13]||(y[13]=i("div",{class:"duration-icon"},null,-1)),Pe(" "+U(L(o.value.duration)),1)],2)])])]),o.value.requestParams?(w(),E("div",Ih,[y[22]||(y[22]=i("div",{class:"card-title"},[i("div",{class:"title-icon"},"⚙️"),Pe(" 请求参数 ")],-1)),i("div",Nh,[i("div",jh,[y[16]||(y[16]=i("div",{class:"param-label"},"MaxTokens",-1)),i("div",Bh,U(o.value.requestParams.maxTokens||"-"),1)]),i("div",Uh,[y[17]||(y[17]=i("div",{class:"param-label"},"Temperature",-1)),i("div",Hh,U(o.value.requestParams.temperature||"-"),1)]),i("div",Dh,[y[19]||(y[19]=i("div",{class:"param-label"},"JSON输出",-1)),i("div",{class:be(["param-toggle",o.value.requestParams.jsonOutput?"enabled":"disabled"])},[y[18]||(y[18]=i("div",{class:"toggle-indicator"},null,-1)),Pe(" "+U(o.value.requestParams.jsonOutput?"启用":"禁用"),1)],2)]),i("div",Vh,[y[21]||(y[21]=i("div",{class:"param-label"},"思考模式",-1)),i("div",{class:be(["param-toggle",o.value.requestParams.thinking?"enabled":"disabled"])},[y[20]||(y[20]=i("div",{class:"toggle-indicator"},null,-1)),Pe(" "+U(o.value.requestParams.thinking?"启用":"禁用"),1)],2)])])])):le("",!0)]),i("div",Fh,[i("div",qh,[(w(!0),E(ye,null,Le(d.value,C=>(w(),E("button",{key:C.key,onClick:$=>l.value=C.key,class:be(["tab-btn",{active:l.value===C.key}])},[i("div",zh,[i("span",Gh,U(C.label),1),C.hasContent?(w(),E("div",Jh,y[23]||(y[23]=[i("div",{class:"indicator-pulse"},null,-1)]))):le("",!0)])],10,Kh))),128))]),i("div",Wh,[l.value==="input"&&o.value.input?(w(),E("div",Qh,[i("div",Yh,[y[25]||(y[25]=i("div",{class:"header-left"},[i("div",{class:"content-icon"},"📥"),i("h3",null,"输入内容")],-1)),i("button",{onClick:y[0]||(y[0]=C=>g(JSON.stringify(o.value.input,null,2))),class:"copy-btn"},y[24]||(y[24]=[i("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[i("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),i("path",{d:"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"})],-1),i("span",null,"复制",-1)]))]),i("div",Zh,[a(o.value.input)?(w(),Ze(bs,{key:0,data:c(o.value.input),"max-height":"500px"},null,8,["data"])):(w(),E("pre",Xh,U(o.value.input),1))])])):le("",!0),l.value==="prompt"&&o.value.prompt?(w(),E("div",ev,[i("div",tv,[y[27]||(y[27]=i("div",{class:"header-left"},[i("div",{class:"content-icon"},"💭"),i("h3",null,"提示词")],-1)),i("button",{onClick:y[1]||(y[1]=C=>g(o.value.prompt)),class:"copy-btn"},y[26]||(y[26]=[i("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[i("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),i("path",{d:"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"})],-1),i("span",null,"复制",-1)]))]),i("div",sv,[a(o.value.prompt)?(w(),Ze(bs,{key:0,data:c(o.value.prompt),"max-height":"500px"},null,8,["data"])):(w(),E("pre",nv,U(o.value.prompt),1))])])):le("",!0),l.value==="rawResponse"&&o.value.rawResponse?(w(),E("div",ov,[i("div",rv,[y[29]||(y[29]=i("div",{class:"header-left"},[i("div",{class:"content-icon"},"🔄"),i("h3",null,"原始响应")],-1)),i("button",{onClick:y[2]||(y[2]=C=>g(o.value.rawResponse)),class:"copy-btn"},y[28]||(y[28]=[i("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[i("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),i("path",{d:"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"})],-1),i("span",null,"复制",-1)]))]),i("div",iv,[a(o.value.rawResponse)?(w(),Ze(bs,{key:0,data:c(o.value.rawResponse),"max-height":"500px"},null,8,["data"])):(w(),E("pre",lv,U(o.value.rawResponse),1))])])):le("",!0),l.value==="output"&&o.value.output?(w(),E("div",av,[i("div",cv,[y[31]||(y[31]=i("div",{class:"header-left"},[i("div",{class:"content-icon"},"📤"),i("h3",null,"输出内容")],-1)),i("button",{onClick:y[3]||(y[3]=C=>g(JSON.stringify(o.value.output,null,2))),class:"copy-btn"},y[30]||(y[30]=[i("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[i("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),i("path",{d:"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"})],-1),i("span",null,"复制",-1)]))]),i("div",uv,[a(o.value.output)?(w(),Ze(bs,{key:0,data:c(o.value.output),"max-height":"500px"},null,8,["data"])):(w(),E("pre",dv,U(o.value.output),1))])])):le("",!0),l.value==="error"&&o.value.errorMessage?(w(),E("div",fv,[i("div",pv,[y[33]||(y[33]=i("div",{class:"header-left"},[i("div",{class:"content-icon error-icon"},"⚠️"),i("h3",null,"错误信息")],-1)),i("button",{onClick:y[4]||(y[4]=C=>g(o.value.errorMessage)),class:"copy-btn"},y[32]||(y[32]=[i("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[i("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),i("path",{d:"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"})],-1),i("span",null,"复制",-1)]))]),i("div",hv,[a(o.value.errorMessage)?(w(),Ze(bs,{key:0,data:c(o.value.errorMessage),"max-height":"500px"},null,8,["data"])):(w(),E("div",vv,U(o.value.errorMessage),1))])])):le("",!0),u.value?le("",!0):(w(),E("div",gv,y[34]||(y[34]=[i("div",{class:"empty-illustration"},[i("div",{class:"empty-circle"}),i("div",{class:"empty-icon"},"📄")],-1),i("p",{class:"empty-text"},"该标签页暂无内容",-1)])))])])])):le("",!0)])])}}}),yv=At(mv,[["__scopeId","data-v-7246130a"]]),bv={class:"ai-log-content"},_v={key:1},wv={class:"content-header"},kv={class:"header-actions"},xv=["disabled"],Cv={key:0,class:"w-4 h-4 animate-spin",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},$v=["disabled"],Sv={class:"main-content"},Tv={key:0,class:"loading-state"},Ev={key:1,class:"empty-state"},Av={key:2,class:"logs-list"},Mv={class:"list-header"},Ov={class:"header-info"},Pv={class:"log-count"},Rv={class:"search-box"},Lv={class:"filter-controls"},Iv=["value"],Nv=["value"],jv={class:"table-container"},Bv={class:"table-body"},Uv={class:"cell time-cell"},Hv={class:"time-info"},Dv={class:"time-relative"},Vv={class:"time-full"},Fv={class:"cell status-cell"},qv={class:"cell operation-cell"},Kv={class:"operation-tag"},zv={class:"cell model-cell"},Gv={class:"model-info"},Jv={class:"model-avatar"},Wv=["src"],Qv={class:"model-details"},Yv={class:"model-name"},Zv={class:"provider-name"},Xv={class:"cell duration-cell"},e1={class:"cell actions-cell"},t1=["onClick"],s1={key:0,class:"pagination"},n1=["disabled"],o1={class:"page-info"},r1=["disabled"],Kn=20,i1=at({__name:"AICallLogViewer",setup(e){const t=Z(!1),s=Z([]),n=Z(""),o=Z(""),r=Z(""),l=Z(""),a=Z(1),c=Z(!1),d=Z(null),u=ve(()=>[...new Set(s.value.map(C=>C.operationType))]),f=ve(()=>[...new Set(s.value.map(C=>C.modelName))]),v=ve(()=>{let C=s.value;if(n.value){const $=n.value.toLowerCase();C=C.filter(O=>{var W;return O.callId.toLowerCase().includes($)||O.operationType.toLowerCase().includes($)||O.modelName.toLowerCase().includes($)||((W=O.errorMessage)==null?void 0:W.toLowerCase().includes($))})}return o.value&&(C=C.filter($=>$.operationType===o.value)),r.value&&(C=C.filter($=>$.modelName===r.value)),l.value&&(C=C.filter($=>$.status===l.value)),C.sort(($,O)=>new Date(O.callTime).getTime()-new Date($.callTime).getTime())}),g=ve(()=>Math.ceil(v.value.length/Kn)),x=ve(()=>{const C=(a.value-1)*Kn,$=C+Kn;return v.value.slice(C,$)}),L=C=>{const $=Date.now(),O=new Date(C).getTime(),W=$-O,te=Math.floor(W/6e4),k=Math.floor(W/36e5),_=Math.floor(W/864e5);return _>0?`${_}天前`:k>0?`${k}小时前`:te>0?`${te}分钟前`:"刚刚"},J=C=>new Date(C).toLocaleTimeString("zh-CN",{hour:"2-digit",minute:"2-digit",second:"2-digit"}),j=C=>!C&&C!==0?"0ms":C<1e3?`${C}ms`:`${(C/1e3).toFixed(2)}s`,V=C=>!C&&C!==0?"duration-unknown":C<2e3?"duration-fast":C<1e4?"duration-normal":"duration-slow",T=async()=>{try{t.value=!0;const C=await uo.getAllLogSummaries();s.value=C}catch(C){console.error("加载日志失败:",C),alert("加载日志失败,请稍后重试")}finally{t.value=!1}},y=async()=>{if(confirm("确定要清空所有日志吗?此操作不可恢复。"))try{t.value=!0,await uo.clearLogs(),await T(),alert("日志已清空")}catch(C){console.error("清空日志失败:",C),alert("清空日志失败,请稍后重试")}finally{t.value=!1}},P=C=>{d.value=C,c.value=!0},I=()=>{c.value=!1,d.value=null,T()};return Dt(()=>{T()}),(C,$)=>(w(),E("div",bv,[c.value?(w(),Ze(yv,{key:0,log:d.value,onGoBack:I},null,8,["log"])):(w(),E("div",_v,[i("div",wv,[$[8]||($[8]=i("h2",null,"AI调用日志",-1)),i("div",kv,[i("button",{onClick:T,class:"btn btn-secondary",disabled:t.value},[t.value?(w(),E("svg",Cv,$[6]||($[6]=[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"},null,-1)]))):le("",!0),i("span",null,U(t.value?"加载中":"刷新"),1)],8,xv),i("button",{onClick:y,class:"btn btn-danger",disabled:t.value||s.value.length===0},$[7]||($[7]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"})],-1),i("span",null,"清空",-1)]),8,$v)])]),i("div",Sv,[t.value&&s.value.length===0?(w(),E("div",Tv,$[9]||($[9]=[i("div",{class:"loading-spinner"},null,-1),i("p",null,"正在加载调用日志...",-1)]))):s.value.length===0?(w(),E("div",Ev,$[10]||($[10]=[i("div",{class:"empty-icon"},"📋",-1),i("h3",null,"暂无调用日志",-1),i("p",null,"当AI模型被调用时,相关日志将在这里显示",-1)]))):(w(),E("div",Av,[i("div",Mv,[i("div",Ov,[i("span",Pv,"共 "+U(v.value.length)+" 条记录",1),i("div",Rv,[$[11]||($[11]=i("svg",{class:"search-icon",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})],-1)),$e(i("input",{"onUpdate:modelValue":$[0]||($[0]=O=>n.value=O),type:"text",placeholder:"搜索日志...",class:"search-input"},null,512),[[qe,n.value]])])]),i("div",Lv,[$e(i("select",{"onUpdate:modelValue":$[1]||($[1]=O=>o.value=O),class:"filter-select"},[$[12]||($[12]=i("option",{value:""},"全部操作",-1)),(w(!0),E(ye,null,Le(u.value,O=>(w(),E("option",{key:O,value:O},U(O),9,Iv))),128))],512),[[vt,o.value]]),$e(i("select",{"onUpdate:modelValue":$[2]||($[2]=O=>r.value=O),class:"filter-select"},[$[13]||($[13]=i("option",{value:""},"全部模型",-1)),(w(!0),E(ye,null,Le(f.value,O=>(w(),E("option",{key:O,value:O},U(O),9,Nv))),128))],512),[[vt,r.value]]),$e(i("select",{"onUpdate:modelValue":$[3]||($[3]=O=>l.value=O),class:"filter-select"},$[14]||($[14]=[i("option",{value:""},"全部状态",-1),i("option",{value:"SUCCESS"},"成功",-1),i("option",{value:"FAILED"},"失败",-1)]),512),[[vt,l.value]])])]),i("div",jv,[$[17]||($[17]=Oo('
时间
状态
操作
模型
耗时
详情
',1)),i("div",Bv,[(w(!0),E(ye,null,Le(x.value,O=>(w(),E("div",{key:O.callId,class:be(["table-row",{"row-failed":O.status!=="SUCCESS"}])},[i("div",Uv,[i("div",Hv,[i("span",Dv,U(L(O.callTime)),1),i("span",Vv,U(J(O.callTime)),1)])]),i("div",Fv,[i("span",{class:be(["status-indicator",O.status.toLowerCase()])},[$[15]||($[15]=i("span",{class:"status-dot"},null,-1)),Pe(" "+U(O.status==="SUCCESS"?"成功":"失败"),1)],2)]),i("div",qv,[i("span",Kv,U(O.operationType),1)]),i("div",zv,[i("div",Gv,[i("div",Jv,[i("img",{src:Ee(cs)(Ee(Rs)(O.modelName)),alt:"provider",class:"provider-icon"},null,8,Wv)]),i("div",Qv,[i("div",Yv,U(O.modelName),1),i("div",Zv,U(Ee(Qt)(Ee(Rs)(O.modelName))),1)])])]),i("div",Xv,[i("span",{class:be(["duration-value",V(O.duration)])},U(j(O.duration)),3)]),i("div",e1,[i("button",{onClick:W=>P(O),class:"action-btn",title:"查看详情"},$[16]||($[16]=[i("svg",{class:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),i("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"})],-1)]),8,t1)])],2))),128))])]),g.value>1?(w(),E("div",s1,[i("button",{onClick:$[4]||($[4]=O=>a.value=Math.max(1,a.value-1)),disabled:a.value===1,class:"page-btn"}," 上一页 ",8,n1),i("span",o1," 第 "+U(a.value)+" 页,共 "+U(g.value)+" 页 ",1),i("button",{onClick:$[5]||($[5]=O=>a.value=Math.min(g.value,a.value+1)),disabled:a.value===g.value,class:"page-btn"}," 下一页 ",8,r1)])):le("",!0)]))])]))]))}}),l1=At(i1,[["__scopeId","data-v-584cc492"]]),a1={async getSystemOverview(){return Oe(`${Me.BASE_URL}/sf-chain/system/overview`,{method:"GET",requireAuth:!0})},async createBackup(){return Oe(`${Me.BASE_URL}/sf-chain/system/backup`,{method:"POST",requireAuth:!0})},async refreshSystem(){return Oe(`${Me.BASE_URL}/sf-chain/system/refresh`,{method:"POST",requireAuth:!0})},async resetSystem(){return Oe(`${Me.BASE_URL}/sf-chain/system/reset`,{method:"POST",requireAuth:!0})}},c1={class:"api-config"},u1={class:"main-content"},d1={class:"sidebar"},f1={class:"tab-navigation"},p1={class:"tab-list"},h1=["onClick"],v1={class:"tab-icon"},g1=["innerHTML"],m1={class:"tab-content"},y1={class:"tab-title"},b1={class:"tab-description"},_1={class:"status-card"},w1={class:"status-content"},k1={class:"status-item"},x1={class:"status-value"},C1={class:"stats-card"},$1={class:"stats-grid"},S1={class:"stat-item"},T1={class:"stat-value"},E1={class:"stat-item"},A1={class:"stat-value"},M1={class:"stat-item"},O1={class:"stat-value"},P1={class:"content-area"},R1=at({__name:"App",setup(e){const t=[{key:"api",title:"AI模型",description:"配置AI模型API",icon:` + + + + + + + `},{key:"operations",title:"AI节点",description:"管理AI节点映射",icon:` + + + + + + + + + + + + + + + `},{key:"logs",title:"AI日志",description:"查看AI调用日志",icon:` + + + + + + + + `}],s=Z("api"),n=Z({totalModels:0,enabledModels:0,configuredOperations:0,totalOperations:0,enabledOperations:0,lastUpdate:Date.now()}),o=c=>{s.value=c},r=c=>c?new Date(c).toLocaleString():"未知",l=async()=>{try{const c=await a1.getSystemOverview();n.value=c}catch(c){const d=c instanceof Error?c.message:"获取系统概览失败";console.error("Failed to fetch system overview:",d)}},a=c=>{n.value={...n.value,...c}};return Dt(()=>{Lo()&&l()}),(c,d)=>(w(),E("div",c1,[Be(Ru,{"on-refresh":l}),i("div",u1,[i("div",d1,[i("div",f1,[d[1]||(d[1]=i("div",{class:"tab-header"},[i("h3",null,"配置导航")],-1)),i("div",p1,[(w(),E(ye,null,Le(t,u=>i("div",{key:u.key,class:be(["tab-item",{active:s.value===u.key}]),onClick:f=>o(u.key)},[i("div",v1,[i("div",{class:"icon-svg",innerHTML:u.icon},null,8,g1)]),i("div",m1,[i("div",y1,U(u.title),1),i("div",b1,U(u.description),1)]),d[0]||(d[0]=i("div",{class:"tab-indicator"},null,-1))],10,h1)),64))])]),i("div",_1,[d[4]||(d[4]=i("div",{class:"status-header"},[i("span",{class:"status-icon"},"📊"),i("span",{class:"status-title"},"系统状态")],-1)),i("div",w1,[d[3]||(d[3]=i("div",{class:"status-item"},[i("span",{class:"status-label"},"运行状态"),i("span",{class:"status-value running"},"正常运行")],-1)),i("div",k1,[d[2]||(d[2]=i("span",{class:"status-label"},"最后更新",-1)),i("span",x1,U(r(n.value.lastUpdate)),1)])])]),i("div",C1,[d[8]||(d[8]=i("div",{class:"stats-header"},[i("span",{class:"stats-icon"},"📈"),i("span",{class:"stats-title"},"统计信息")],-1)),i("div",$1,[i("div",S1,[i("span",T1,U(n.value.totalModels||0),1),d[5]||(d[5]=i("span",{class:"stat-label"},"总模型",-1))]),i("div",E1,[i("span",A1,U(n.value.enabledModels||0),1),d[6]||(d[6]=i("span",{class:"stat-label"},"已启用",-1))]),i("div",M1,[i("span",O1,U(n.value.configuredOperations||0),1),d[7]||(d[7]=i("span",{class:"stat-label"},"已配置",-1))])])])]),d[9]||(d[9]=i("div",{class:"divider"},null,-1)),i("div",P1,[s.value==="api"?(w(),Ze(Xd,{key:0,"system-overview":n.value,onUpdateOverview:a},null,8,["system-overview"])):le("",!0),s.value==="operations"?(w(),Ze(Pp,{key:1,"system-overview":n.value,onUpdateOverview:a},null,8,["system-overview"])):le("",!0),s.value==="logs"?(w(),Ze(l1,{key:2,"system-overview":n.value,onUpdateOverview:a},null,8,["system-overview"])):le("",!0),s.value==="system"?(w(),Ze(Jp,{key:3,"system-overview":n.value,onUpdateOverview:a},null,8,["system-overview"])):le("",!0)])])]))}}),fl=At(R1,[["__scopeId","data-v-6f5fc07f"]]);/*! + * vue-router v4.5.1 + * (c) 2025 Eduardo San Martin Morote + * @license MIT + */const ss=typeof document<"u";function pl(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function L1(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e.default&&pl(e.default)}const pe=Object.assign;function zn(e,t){const s={};for(const n in t){const o=t[n];s[n]=lt(o)?o.map(e):e(o)}return s}const Ls=()=>{},lt=Array.isArray,hl=/#/g,I1=/&/g,N1=/\//g,j1=/=/g,B1=/\?/g,vl=/\+/g,U1=/%5B/g,H1=/%5D/g,gl=/%5E/g,D1=/%60/g,ml=/%7B/g,V1=/%7C/g,yl=/%7D/g,F1=/%20/g;function Io(e){return encodeURI(""+e).replace(V1,"|").replace(U1,"[").replace(H1,"]")}function q1(e){return Io(e).replace(ml,"{").replace(yl,"}").replace(gl,"^")}function fo(e){return Io(e).replace(vl,"%2B").replace(F1,"+").replace(hl,"%23").replace(I1,"%26").replace(D1,"`").replace(ml,"{").replace(yl,"}").replace(gl,"^")}function K1(e){return fo(e).replace(j1,"%3D")}function z1(e){return Io(e).replace(hl,"%23").replace(B1,"%3F")}function G1(e){return e==null?"":z1(e).replace(N1,"%2F")}function qs(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const J1=/\/$/,W1=e=>e.replace(J1,"");function Gn(e,t,s="/"){let n,o={},r="",l="";const a=t.indexOf("#");let c=t.indexOf("?");return a=0&&(c=-1),c>-1&&(n=t.slice(0,c),r=t.slice(c+1,a>-1?a:t.length),o=e(r)),a>-1&&(n=n||t.slice(0,a),l=t.slice(a,t.length)),n=X1(n??t,s),{fullPath:n+(r&&"?")+r+l,path:n,query:o,hash:qs(l)}}function Q1(e,t){const s=t.query?e(t.query):"";return t.path+(s&&"?")+s+(t.hash||"")}function Er(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function Y1(e,t,s){const n=t.matched.length-1,o=s.matched.length-1;return n>-1&&n===o&&ds(t.matched[n],s.matched[o])&&bl(t.params,s.params)&&e(t.query)===e(s.query)&&t.hash===s.hash}function ds(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function bl(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const s in e)if(!Z1(e[s],t[s]))return!1;return!0}function Z1(e,t){return lt(e)?Ar(e,t):lt(t)?Ar(t,e):e===t}function Ar(e,t){return lt(t)?e.length===t.length&&e.every((s,n)=>s===t[n]):e.length===1&&e[0]===t}function X1(e,t){if(e.startsWith("/"))return e;if(!e)return t;const s=t.split("/"),n=e.split("/"),o=n[n.length-1];(o===".."||o===".")&&n.push("");let r=s.length-1,l,a;for(l=0;l1&&r--;else break;return s.slice(0,r).join("/")+"/"+n.slice(l).join("/")}const Ot={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0};var Ks;(function(e){e.pop="pop",e.push="push"})(Ks||(Ks={}));var Is;(function(e){e.back="back",e.forward="forward",e.unknown=""})(Is||(Is={}));function eg(e){if(!e)if(ss){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),W1(e)}const tg=/^[^#]+#/;function sg(e,t){return e.replace(tg,"#")+t}function ng(e,t){const s=document.documentElement.getBoundingClientRect(),n=e.getBoundingClientRect();return{behavior:t.behavior,left:n.left-s.left-(t.left||0),top:n.top-s.top-(t.top||0)}}const Pn=()=>({left:window.scrollX,top:window.scrollY});function og(e){let t;if("el"in e){const s=e.el,n=typeof s=="string"&&s.startsWith("#"),o=typeof s=="string"?n?document.getElementById(s.slice(1)):document.querySelector(s):s;if(!o)return;t=ng(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function Mr(e,t){return(history.state?history.state.position-t:-1)+e}const po=new Map;function rg(e,t){po.set(e,t)}function ig(e){const t=po.get(e);return po.delete(e),t}let lg=()=>location.protocol+"//"+location.host;function _l(e,t){const{pathname:s,search:n,hash:o}=t,r=e.indexOf("#");if(r>-1){let a=o.includes(e.slice(r))?e.slice(r).length:1,c=o.slice(a);return c[0]!=="/"&&(c="/"+c),Er(c,"")}return Er(s,e)+n+o}function ag(e,t,s,n){let o=[],r=[],l=null;const a=({state:v})=>{const g=_l(e,location),x=s.value,L=t.value;let J=0;if(v){if(s.value=g,t.value=v,l&&l===x){l=null;return}J=L?v.position-L.position:0}else n(g);o.forEach(j=>{j(s.value,x,{delta:J,type:Ks.pop,direction:J?J>0?Is.forward:Is.back:Is.unknown})})};function c(){l=s.value}function d(v){o.push(v);const g=()=>{const x=o.indexOf(v);x>-1&&o.splice(x,1)};return r.push(g),g}function u(){const{history:v}=window;v.state&&v.replaceState(pe({},v.state,{scroll:Pn()}),"")}function f(){for(const v of r)v();r=[],window.removeEventListener("popstate",a),window.removeEventListener("beforeunload",u)}return window.addEventListener("popstate",a),window.addEventListener("beforeunload",u,{passive:!0}),{pauseListeners:c,listen:d,destroy:f}}function Or(e,t,s,n=!1,o=!1){return{back:e,current:t,forward:s,replaced:n,position:window.history.length,scroll:o?Pn():null}}function cg(e){const{history:t,location:s}=window,n={value:_l(e,s)},o={value:t.state};o.value||r(n.value,{back:null,current:n.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function r(c,d,u){const f=e.indexOf("#"),v=f>-1?(s.host&&document.querySelector("base")?e:e.slice(f))+c:lg()+e+c;try{t[u?"replaceState":"pushState"](d,"",v),o.value=d}catch(g){console.error(g),s[u?"replace":"assign"](v)}}function l(c,d){const u=pe({},t.state,Or(o.value.back,c,o.value.forward,!0),d,{position:o.value.position});r(c,u,!0),n.value=c}function a(c,d){const u=pe({},o.value,t.state,{forward:c,scroll:Pn()});r(u.current,u,!0);const f=pe({},Or(n.value,c,null),{position:u.position+1},d);r(c,f,!1),n.value=c}return{location:n,state:o,push:a,replace:l}}function ug(e){e=eg(e);const t=cg(e),s=ag(e,t.state,t.location,t.replace);function n(r,l=!0){l||s.pauseListeners(),history.go(r)}const o=pe({location:"",base:e,go:n,createHref:sg.bind(null,e)},t,s);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}function dg(e){return typeof e=="string"||e&&typeof e=="object"}function wl(e){return typeof e=="string"||typeof e=="symbol"}const kl=Symbol("");var Pr;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(Pr||(Pr={}));function fs(e,t){return pe(new Error,{type:e,[kl]:!0},t)}function _t(e,t){return e instanceof Error&&kl in e&&(t==null||!!(e.type&t))}const Rr="[^/]+?",fg={sensitive:!1,strict:!1,start:!0,end:!0},pg=/[.+*?^${}()[\]/\\]/g;function hg(e,t){const s=pe({},fg,t),n=[];let o=s.start?"^":"";const r=[];for(const d of e){const u=d.length?[]:[90];s.strict&&!d.length&&(o+="/");for(let f=0;ft.length?t.length===1&&t[0]===80?1:-1:0}function xl(e,t){let s=0;const n=e.score,o=t.score;for(;s0&&t[t.length-1]<0}const gg={type:0,value:""},mg=/[a-zA-Z0-9_]/;function yg(e){if(!e)return[[]];if(e==="/")return[[gg]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(g){throw new Error(`ERR (${s})/"${d}": ${g}`)}let s=0,n=s;const o=[];let r;function l(){r&&o.push(r),r=[]}let a=0,c,d="",u="";function f(){d&&(s===0?r.push({type:0,value:d}):s===1||s===2||s===3?(r.length>1&&(c==="*"||c==="+")&&t(`A repeatable param (${d}) must be alone in its segment. eg: '/:ids+.`),r.push({type:1,value:d,regexp:u,repeatable:c==="*"||c==="+",optional:c==="*"||c==="?"})):t("Invalid state to consume buffer"),d="")}function v(){d+=c}for(;a{l(T)}:Ls}function l(f){if(wl(f)){const v=n.get(f);v&&(n.delete(f),s.splice(s.indexOf(v),1),v.children.forEach(l),v.alias.forEach(l))}else{const v=s.indexOf(f);v>-1&&(s.splice(v,1),f.record.name&&n.delete(f.record.name),f.children.forEach(l),f.alias.forEach(l))}}function a(){return s}function c(f){const v=xg(f,s);s.splice(v,0,f),f.record.name&&!jr(f)&&n.set(f.record.name,f)}function d(f,v){let g,x={},L,J;if("name"in f&&f.name){if(g=n.get(f.name),!g)throw fs(1,{location:f});J=g.record.name,x=pe(Ir(v.params,g.keys.filter(T=>!T.optional).concat(g.parent?g.parent.keys.filter(T=>T.optional):[]).map(T=>T.name)),f.params&&Ir(f.params,g.keys.map(T=>T.name))),L=g.stringify(x)}else if(f.path!=null)L=f.path,g=s.find(T=>T.re.test(L)),g&&(x=g.parse(L),J=g.record.name);else{if(g=v.name?n.get(v.name):s.find(T=>T.re.test(v.path)),!g)throw fs(1,{location:f,currentLocation:v});J=g.record.name,x=pe({},v.params,f.params),L=g.stringify(x)}const j=[];let V=g;for(;V;)j.unshift(V.record),V=V.parent;return{name:J,path:L,params:x,matched:j,meta:kg(j)}}e.forEach(f=>r(f));function u(){s.length=0,n.clear()}return{addRoute:r,resolve:d,removeRoute:l,clearRoutes:u,getRoutes:a,getRecordMatcher:o}}function Ir(e,t){const s={};for(const n of t)n in e&&(s[n]=e[n]);return s}function Nr(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:wg(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,"mods",{value:{}}),t}function wg(e){const t={},s=e.props||!1;if("component"in e)t.default=s;else for(const n in e.components)t[n]=typeof s=="object"?s[n]:s;return t}function jr(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function kg(e){return e.reduce((t,s)=>pe(t,s.meta),{})}function Br(e,t){const s={};for(const n in e)s[n]=n in t?t[n]:e[n];return s}function xg(e,t){let s=0,n=t.length;for(;s!==n;){const r=s+n>>1;xl(e,t[r])<0?n=r:s=r+1}const o=Cg(e);return o&&(n=t.lastIndexOf(o,n-1)),n}function Cg(e){let t=e;for(;t=t.parent;)if(Cl(t)&&xl(e,t)===0)return t}function Cl({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function $g(e){const t={};if(e===""||e==="?")return t;const n=(e[0]==="?"?e.slice(1):e).split("&");for(let o=0;or&&fo(r)):[n&&fo(n)]).forEach(r=>{r!==void 0&&(t+=(t.length?"&":"")+s,r!=null&&(t+="="+r))})}return t}function Sg(e){const t={};for(const s in e){const n=e[s];n!==void 0&&(t[s]=lt(n)?n.map(o=>o==null?null:""+o):n==null?n:""+n)}return t}const Tg=Symbol(""),Hr=Symbol(""),No=Symbol(""),$l=Symbol(""),ho=Symbol("");function _s(){let e=[];function t(n){return e.push(n),()=>{const o=e.indexOf(n);o>-1&&e.splice(o,1)}}function s(){e=[]}return{add:t,list:()=>e.slice(),reset:s}}function It(e,t,s,n,o,r=l=>l()){const l=n&&(n.enterCallbacks[o]=n.enterCallbacks[o]||[]);return()=>new Promise((a,c)=>{const d=v=>{v===!1?c(fs(4,{from:s,to:t})):v instanceof Error?c(v):dg(v)?c(fs(2,{from:t,to:v})):(l&&n.enterCallbacks[o]===l&&typeof v=="function"&&l.push(v),a())},u=r(()=>e.call(n&&n.instances[o],t,s,d));let f=Promise.resolve(u);e.length<3&&(f=f.then(d)),f.catch(v=>c(v))})}function Jn(e,t,s,n,o=r=>r()){const r=[];for(const l of e)for(const a in l.components){let c=l.components[a];if(!(t!=="beforeRouteEnter"&&!l.instances[a]))if(pl(c)){const u=(c.__vccOpts||c)[t];u&&r.push(It(u,s,n,l,a,o))}else{let d=c();r.push(()=>d.then(u=>{if(!u)throw new Error(`Couldn't resolve component "${a}" at "${l.path}"`);const f=L1(u)?u.default:u;l.mods[a]=u,l.components[a]=f;const g=(f.__vccOpts||f)[t];return g&&It(g,s,n,l,a,o)()}))}}return r}function Dr(e){const t=$t(No),s=$t($l),n=ve(()=>{const c=Ee(e.to);return t.resolve(c)}),o=ve(()=>{const{matched:c}=n.value,{length:d}=c,u=c[d-1],f=s.matched;if(!u||!f.length)return-1;const v=f.findIndex(ds.bind(null,u));if(v>-1)return v;const g=Vr(c[d-2]);return d>1&&Vr(u)===g&&f[f.length-1].path!==g?f.findIndex(ds.bind(null,c[d-2])):v}),r=ve(()=>o.value>-1&&Pg(s.params,n.value.params)),l=ve(()=>o.value>-1&&o.value===s.matched.length-1&&bl(s.params,n.value.params));function a(c={}){if(Og(c)){const d=t[Ee(e.replace)?"replace":"push"](Ee(e.to)).catch(Ls);return e.viewTransition&&typeof document<"u"&&"startViewTransition"in document&&document.startViewTransition(()=>d),d}return Promise.resolve()}return{route:n,href:ve(()=>n.value.href),isActive:r,isExactActive:l,navigate:a}}function Eg(e){return e.length===1?e[0]:e}const Ag=at({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"},viewTransition:Boolean},useLink:Dr,setup(e,{slots:t}){const s=hs(Dr(e)),{options:n}=$t(No),o=ve(()=>({[Fr(e.activeClass,n.linkActiveClass,"router-link-active")]:s.isActive,[Fr(e.exactActiveClass,n.linkExactActiveClass,"router-link-exact-active")]:s.isExactActive}));return()=>{const r=t.default&&Eg(t.default(s));return e.custom?r:Ro("a",{"aria-current":s.isExactActive?e.ariaCurrentValue:null,href:s.href,onClick:s.navigate,class:o.value},r)}}}),Mg=Ag;function Og(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Pg(e,t){for(const s in t){const n=t[s],o=e[s];if(typeof n=="string"){if(n!==o)return!1}else if(!lt(o)||o.length!==n.length||n.some((r,l)=>r!==o[l]))return!1}return!0}function Vr(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Fr=(e,t,s)=>e??t??s,Rg=at({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:s}){const n=$t(ho),o=ve(()=>e.route||n.value),r=$t(Hr,0),l=ve(()=>{let d=Ee(r);const{matched:u}=o.value;let f;for(;(f=u[d])&&!f.components;)d++;return d}),a=ve(()=>o.value.matched[l.value]);on(Hr,ve(()=>l.value+1)),on(Tg,a),on(ho,o);const c=Z();return as(()=>[c.value,a.value,e.name],([d,u,f],[v,g,x])=>{u&&(u.instances[f]=d,g&&g!==u&&d&&d===v&&(u.leaveGuards.size||(u.leaveGuards=g.leaveGuards),u.updateGuards.size||(u.updateGuards=g.updateGuards))),d&&u&&(!g||!ds(u,g)||!v)&&(u.enterCallbacks[f]||[]).forEach(L=>L(d))},{flush:"post"}),()=>{const d=o.value,u=e.name,f=a.value,v=f&&f.components[u];if(!v)return qr(s.default,{Component:v,route:d});const g=f.props[u],x=g?g===!0?d.params:typeof g=="function"?g(d):g:null,J=Ro(v,pe({},x,t,{onVnodeUnmounted:j=>{j.component.isUnmounted&&(f.instances[u]=null)},ref:c}));return qr(s.default,{Component:J,route:d})||J}}});function qr(e,t){if(!e)return null;const s=e(t);return s.length===1?s[0]:s}const Lg=Rg;function Ig(e){const t=_g(e.routes,e),s=e.parseQuery||$g,n=e.stringifyQuery||Ur,o=e.history,r=_s(),l=_s(),a=_s(),c=la(Ot);let d=Ot;ss&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const u=zn.bind(null,S=>""+S),f=zn.bind(null,G1),v=zn.bind(null,qs);function g(S,z){let q,Q;return wl(S)?(q=t.getRecordMatcher(S),Q=z):Q=S,t.addRoute(Q,q)}function x(S){const z=t.getRecordMatcher(S);z&&t.removeRoute(z)}function L(){return t.getRoutes().map(S=>S.record)}function J(S){return!!t.getRecordMatcher(S)}function j(S,z){if(z=pe({},z||c.value),typeof S=="string"){const m=Gn(s,S,z.path),A=t.resolve({path:m.path},z),R=o.createHref(m.fullPath);return pe(m,A,{params:v(A.params),hash:qs(m.hash),redirectedFrom:void 0,href:R})}let q;if(S.path!=null)q=pe({},S,{path:Gn(s,S.path,z.path).path});else{const m=pe({},S.params);for(const A in m)m[A]==null&&delete m[A];q=pe({},S,{params:f(m)}),z.params=f(z.params)}const Q=t.resolve(q,z),_e=S.hash||"";Q.params=u(v(Q.params));const p=Q1(n,pe({},S,{hash:q1(_e),path:Q.path})),h=o.createHref(p);return pe({fullPath:p,hash:_e,query:n===Ur?Sg(S.query):S.query||{}},Q,{redirectedFrom:void 0,href:h})}function V(S){return typeof S=="string"?Gn(s,S,c.value.path):pe({},S)}function T(S,z){if(d!==S)return fs(8,{from:z,to:S})}function y(S){return C(S)}function P(S){return y(pe(V(S),{replace:!0}))}function I(S){const z=S.matched[S.matched.length-1];if(z&&z.redirect){const{redirect:q}=z;let Q=typeof q=="function"?q(S):q;return typeof Q=="string"&&(Q=Q.includes("?")||Q.includes("#")?Q=V(Q):{path:Q},Q.params={}),pe({query:S.query,hash:S.hash,params:Q.path!=null?{}:S.params},Q)}}function C(S,z){const q=d=j(S),Q=c.value,_e=S.state,p=S.force,h=S.replace===!0,m=I(q);if(m)return C(pe(V(m),{state:typeof m=="object"?pe({},_e,m.state):_e,force:p,replace:h}),z||q);const A=q;A.redirectedFrom=z;let R;return!p&&Y1(n,Q,q)&&(R=fs(16,{to:A,from:Q}),b(Q,Q,!0,!1)),(R?Promise.resolve(R):W(A,Q)).catch(M=>_t(M)?_t(M,2)?M:G(M):ce(M,A,Q)).then(M=>{if(M){if(_t(M,2))return C(pe({replace:h},V(M.to),{state:typeof M.to=="object"?pe({},_e,M.to.state):_e,force:p}),z||A)}else M=k(A,Q,!0,h,_e);return te(A,Q,M),M})}function $(S,z){const q=T(S,z);return q?Promise.reject(q):Promise.resolve()}function O(S){const z=me.values().next().value;return z&&typeof z.runWithContext=="function"?z.runWithContext(S):S()}function W(S,z){let q;const[Q,_e,p]=Ng(S,z);q=Jn(Q.reverse(),"beforeRouteLeave",S,z);for(const m of Q)m.leaveGuards.forEach(A=>{q.push(It(A,S,z))});const h=$.bind(null,S,z);return q.push(h),He(q).then(()=>{q=[];for(const m of r.list())q.push(It(m,S,z));return q.push(h),He(q)}).then(()=>{q=Jn(_e,"beforeRouteUpdate",S,z);for(const m of _e)m.updateGuards.forEach(A=>{q.push(It(A,S,z))});return q.push(h),He(q)}).then(()=>{q=[];for(const m of p)if(m.beforeEnter)if(lt(m.beforeEnter))for(const A of m.beforeEnter)q.push(It(A,S,z));else q.push(It(m.beforeEnter,S,z));return q.push(h),He(q)}).then(()=>(S.matched.forEach(m=>m.enterCallbacks={}),q=Jn(p,"beforeRouteEnter",S,z,O),q.push(h),He(q))).then(()=>{q=[];for(const m of l.list())q.push(It(m,S,z));return q.push(h),He(q)}).catch(m=>_t(m,8)?m:Promise.reject(m))}function te(S,z,q){a.list().forEach(Q=>O(()=>Q(S,z,q)))}function k(S,z,q,Q,_e){const p=T(S,z);if(p)return p;const h=z===Ot,m=ss?history.state:{};q&&(Q||h?o.replace(S.fullPath,pe({scroll:h&&m&&m.scroll},_e)):o.push(S.fullPath,_e)),c.value=S,b(S,z,q,h),G()}let _;function B(){_||(_=o.listen((S,z,q)=>{if(!Se.listening)return;const Q=j(S),_e=I(Q);if(_e){C(pe(_e,{replace:!0,force:!0}),Q).catch(Ls);return}d=Q;const p=c.value;ss&&rg(Mr(p.fullPath,q.delta),Pn()),W(Q,p).catch(h=>_t(h,12)?h:_t(h,2)?(C(pe(V(h.to),{force:!0}),Q).then(m=>{_t(m,20)&&!q.delta&&q.type===Ks.pop&&o.go(-1,!1)}).catch(Ls),Promise.reject()):(q.delta&&o.go(-q.delta,!1),ce(h,Q,p))).then(h=>{h=h||k(Q,p,!1),h&&(q.delta&&!_t(h,8)?o.go(-q.delta,!1):q.type===Ks.pop&&_t(h,20)&&o.go(-1,!1)),te(Q,p,h)}).catch(Ls)}))}let ne=_s(),oe=_s(),ae;function ce(S,z,q){G(S);const Q=oe.list();return Q.length?Q.forEach(_e=>_e(S,z,q)):console.error(S),Promise.reject(S)}function Ae(){return ae&&c.value!==Ot?Promise.resolve():new Promise((S,z)=>{ne.add([S,z])})}function G(S){return ae||(ae=!S,B(),ne.list().forEach(([z,q])=>S?q(S):z()),ne.reset()),S}function b(S,z,q,Q){const{scrollBehavior:_e}=e;if(!ss||!_e)return Promise.resolve();const p=!q&&ig(Mr(S.fullPath,0))||(Q||!q)&&history.state&&history.state.scroll||null;return $n().then(()=>_e(S,z,p)).then(h=>h&&og(h)).catch(h=>ce(h,S,z))}const ue=S=>o.go(S);let Y;const me=new Set,Se={currentRoute:c,listening:!0,addRoute:g,removeRoute:x,clearRoutes:t.clearRoutes,hasRoute:J,getRoutes:L,resolve:j,options:e,push:y,replace:P,go:ue,back:()=>ue(-1),forward:()=>ue(1),beforeEach:r.add,beforeResolve:l.add,afterEach:a.add,onError:oe.add,isReady:Ae,install(S){const z=this;S.component("RouterLink",Mg),S.component("RouterView",Lg),S.config.globalProperties.$router=z,Object.defineProperty(S.config.globalProperties,"$route",{enumerable:!0,get:()=>Ee(c)}),ss&&!Y&&c.value===Ot&&(Y=!0,y(o.location).catch(_e=>{}));const q={};for(const _e in Ot)Object.defineProperty(q,_e,{get:()=>c.value[_e],enumerable:!0});S.provide(No,z),S.provide($l,fi(q)),S.provide(ho,c);const Q=S.unmount;me.add(S),S.unmount=function(){me.delete(S),me.size<1&&(d=Ot,_&&_(),_=null,c.value=Ot,Y=!1,ae=!1),Q()}}};function He(S){return S.reduce((z,q)=>z.then(()=>O(q)),Promise.resolve())}return Se}function Ng(e,t){const s=[],n=[],o=[],r=Math.max(t.matched.length,e.matched.length);for(let l=0;lds(d,a))?n.push(a):s.push(a));const c=e.matched[l];c&&(t.matched.find(d=>ds(d,c))||o.push(c))}return[s,n,o]}const jg=Ig({history:ug("/"),routes:[{path:"/",name:"home",component:fl}]}),yn=ul(fl);yn.use(ou());yn.use(jg);wu.initialize().then(()=>{yn.mount("#app")}).catch(e=>{console.error("Failed to initialize API config:",e),yn.mount("#app")}); diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-DtzThp8m.css b/prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-DtzThp8m.css new file mode 100644 index 0000000..9edc15b --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/assets/index-DtzThp8m.css @@ -0,0 +1 @@ +:root{--vt-c-white: #ffffff;--vt-c-white-soft: #f8f8f8;--vt-c-white-mute: #f2f2f2;--vt-c-black: #181818;--vt-c-black-soft: #222222;--vt-c-black-mute: #282828;--vt-c-indigo: #2c3e50;--vt-c-divider-light-1: rgba(60, 60, 60, .29);--vt-c-divider-light-2: rgba(60, 60, 60, .12);--vt-c-divider-dark-1: rgba(84, 84, 84, .65);--vt-c-divider-dark-2: rgba(84, 84, 84, .48);--vt-c-text-light-1: var(--vt-c-indigo);--vt-c-text-light-2: rgba(60, 60, 60, .66);--vt-c-text-dark-1: var(--vt-c-white);--vt-c-text-dark-2: rgba(235, 235, 235, .64)}:root{--color-background: var(--vt-c-white);--color-background-soft: var(--vt-c-white-soft);--color-background-mute: var(--vt-c-white-mute);--color-border: var(--vt-c-divider-light-2);--color-border-hover: var(--vt-c-divider-light-1);--color-heading: var(--vt-c-text-light-1);--color-text: var(--vt-c-text-light-1);--section-gap: 160px}@media (prefers-color-scheme: dark){:root{--color-background: var(--vt-c-black);--color-background-soft: var(--vt-c-black-soft);--color-background-mute: var(--vt-c-black-mute);--color-border: var(--vt-c-divider-dark-2);--color-border-hover: var(--vt-c-divider-dark-1);--color-heading: var(--vt-c-text-dark-1);--color-text: var(--vt-c-text-dark-2)}}*,*:before,*:after{box-sizing:border-box;margin:0;font-weight:400}body{min-height:100vh;color:var(--color-text);background:var(--color-background);transition:color .5s,background-color .5s;line-height:1.6;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;font-size:15px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*{box-sizing:border-box}body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#fafafa}#app{min-height:100vh;width:100%}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#f1f1f1}::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#a8a8a8}.container{max-width:1400px;margin:0 auto;padding:0 20px}.text-center{text-align:center}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.p-4{padding:1rem}a{text-decoration:none;color:#667eea;transition:.3s}a:hover{color:#764ba2}@media (min-width: 1024px){#app{width:100%;max-width:none}}.toast-overlay[data-v-4c886fb0]{display:flex;align-items:center;justify-content:center;padding:0;pointer-events:none}.toast-container[data-v-4c886fb0]{min-width:320px;max-width:480px;width:auto;background:#fffffffa;border-radius:16px;box-shadow:0 25px 50px -12px #00000026,0 10px 20px -8px #0000001a,0 0 0 1px #fffc;backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);overflow:hidden;pointer-events:auto;border:1px solid rgba(255,255,255,.3);transform:translateZ(0)}.toast-content[data-v-4c886fb0]{display:flex;align-items:flex-start;padding:20px;gap:14px}.toast-icon-wrapper[data-v-4c886fb0]{flex-shrink:0;width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}.toast-icon-wrapper[data-v-4c886fb0]:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;opacity:.12;background:currentColor;transform:scale(0);animation:iconPulse-4c886fb0 .6s cubic-bezier(.34,1.56,.64,1) forwards}@keyframes iconPulse-4c886fb0{to{transform:scale(1)}}.toast-icon[data-v-4c886fb0]{position:relative;z-index:1;transform:scale(0);animation:iconScale-4c886fb0 .5s cubic-bezier(.34,1.56,.64,1) .1s forwards}@keyframes iconScale-4c886fb0{to{transform:scale(1)}}.toast-message[data-v-4c886fb0]{flex:1;min-width:0;padding-top:2px}.toast-title[data-v-4c886fb0]{font-size:15px;font-weight:600;margin-bottom:4px;line-height:1.4;color:#1a1a1a;opacity:0;transform:translateY(8px);animation:slideInUp-4c886fb0 .4s cubic-bezier(.4,0,.2,1) .2s forwards}.toast-text[data-v-4c886fb0]{font-size:14px;line-height:1.5;color:#666;opacity:0;transform:translateY(8px);animation:slideInUp-4c886fb0 .4s cubic-bezier(.4,0,.2,1) .3s forwards}@keyframes slideInUp-4c886fb0{to{opacity:1;transform:translateY(0)}}.toast-close[data-v-4c886fb0]{flex-shrink:0;width:28px;height:28px;border:none;background:#0000000a;cursor:pointer;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#999;transition:all .25s cubic-bezier(.4,0,.2,1);margin-top:8px;opacity:0;transform:scale(.8);animation:fadeInScale-4c886fb0 .3s cubic-bezier(.4,0,.2,1) .4s forwards}@keyframes fadeInScale-4c886fb0{to{opacity:1;transform:scale(1)}}.toast-close[data-v-4c886fb0]:hover{background:#00000014;color:#666;transform:scale(1.05)}.toast-close[data-v-4c886fb0]:active{transform:scale(.95)}.toast-progress[data-v-4c886fb0]{height:3px;background:#0000000a;overflow:hidden}.toast-progress-bar[data-v-4c886fb0]{height:100%;width:100%;transform-origin:left;animation:progressShrink-4c886fb0 linear}@keyframes progressShrink-4c886fb0{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.toast-success[data-v-4c886fb0]{background:linear-gradient(135deg,#10b98108,#34d39908);border:1px solid rgba(16,185,129,.15)}.toast-success .toast-icon-wrapper[data-v-4c886fb0]{color:#10b981}.toast-success .toast-title[data-v-4c886fb0]{color:#065f46}.toast-success .toast-progress-bar[data-v-4c886fb0]{background:linear-gradient(90deg,#10b981,#34d399)}.toast-error[data-v-4c886fb0]{background:linear-gradient(135deg,#ef444408,#f8717108);border:1px solid rgba(239,68,68,.15)}.toast-error .toast-icon-wrapper[data-v-4c886fb0]{color:#ef4444}.toast-error .toast-title[data-v-4c886fb0]{color:#7f1d1d}.toast-error .toast-progress-bar[data-v-4c886fb0]{background:linear-gradient(90deg,#ef4444,#f87171)}.toast-warning[data-v-4c886fb0]{background:linear-gradient(135deg,#f59e0b08,#fbbf2408);border:1px solid rgba(245,158,11,.15)}.toast-warning .toast-icon-wrapper[data-v-4c886fb0]{color:#f59e0b}.toast-warning .toast-title[data-v-4c886fb0]{color:#78350f}.toast-warning .toast-progress-bar[data-v-4c886fb0]{background:linear-gradient(90deg,#f59e0b,#fbbf24)}.toast-info[data-v-4c886fb0]{background:linear-gradient(135deg,#3b82f608,#60a5fa08);border:1px solid rgba(59,130,246,.15)}.toast-info .toast-icon-wrapper[data-v-4c886fb0]{color:#3b82f6}.toast-info .toast-title[data-v-4c886fb0]{color:#1e3a8a}.toast-info .toast-progress-bar[data-v-4c886fb0]{background:linear-gradient(90deg,#3b82f6,#60a5fa)}.toast-enter-active[data-v-4c886fb0]{transition:all .5s cubic-bezier(.34,1.56,.64,1)}.toast-leave-active[data-v-4c886fb0]{transition:all .35s cubic-bezier(.4,0,1,1)}.toast-enter-from[data-v-4c886fb0]{opacity:0;transform:translate(-50%) scale(.85) translateY(-30px);filter:blur(4px)}.toast-leave-to[data-v-4c886fb0]{opacity:0;transform:translate(-50%) scale(.95) translateY(-15px);filter:blur(2px)}@media (max-width: 640px){.toast-container[data-v-4c886fb0]{min-width:auto;max-width:calc(100vw - 32px);margin:0 16px}.toast-content[data-v-4c886fb0]{padding:18px;gap:12px}.toast-icon-wrapper[data-v-4c886fb0]{width:40px;height:40px}.toast-title[data-v-4c886fb0]{font-size:14px}.toast-text[data-v-4c886fb0]{font-size:13px}}@media (prefers-color-scheme: dark){.toast-container[data-v-4c886fb0]{background:#1e1e1efa;border:1px solid rgba(255,255,255,.08)}.toast-title[data-v-4c886fb0]{color:#f5f5f5}.toast-text[data-v-4c886fb0]{color:#a3a3a3}.toast-close[data-v-4c886fb0]{background:#ffffff14;color:#a3a3a3}.toast-close[data-v-4c886fb0]:hover{background:#ffffff26;color:#f5f5f5}}.header-bar[data-v-677538a9]{background:linear-gradient(135deg,#fffffffa,#f8fafcf2);-webkit-backdrop-filter:blur(24px) saturate(200%);backdrop-filter:blur(24px) saturate(200%);border-radius:16px;padding:1.25rem 2rem;margin-bottom:1.5rem;box-shadow:0 8px 32px #667eea14,0 4px 16px #0000000a,inset 0 1px #fffc;border:1px solid rgba(102,126,234,.1);position:relative;overflow:hidden;height:96px}.header-bar[data-v-677538a9]:before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#667eea,#764ba2,#f093fb,#4facfe,#00f2fe);opacity:.9}.header-content[data-v-677538a9]{display:flex;align-items:center;justify-content:space-between;gap:2rem;height:100%}.header-left[data-v-677538a9]{flex:0 0 auto}.brand-section[data-v-677538a9]{display:flex;align-items:center;gap:1rem}.brand-logo[data-v-677538a9]{width:52px;height:52px;background:linear-gradient(135deg,#667eea1a,#764ba21a,#f093fb1a);border-radius:12px;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 20px #667eea33,inset 0 1px #fff6;border:1px solid rgba(102,126,234,.2);position:relative;transition:all .4s cubic-bezier(.4,0,.2,1)}.brand-logo[data-v-677538a9]:hover{transform:translateY(-1px) scale(1.02);box-shadow:0 8px 32px #667eea4d,inset 0 1px #ffffff80}.logo-svg[data-v-677538a9]{width:36px;height:36px;transition:all .4s ease}.brand-logo:hover .logo-svg[data-v-677538a9]{transform:scale(1.05) rotate(3deg)}.brand-text[data-v-677538a9]{display:flex;flex-direction:column;gap:.125rem}.brand-title[data-v-677538a9]{font-size:1.875rem;font-weight:700;background:linear-gradient(135deg,#1e293b,#667eea,#764ba2);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin:0;letter-spacing:-.025em;line-height:1.1}.brand-subtitle[data-v-677538a9]{font-size:.8rem;color:#64748b;font-weight:500;margin:0;letter-spacing:.02em;opacity:.8}.header-right[data-v-677538a9]{flex:1;display:flex;justify-content:flex-end}.auth-section[data-v-677538a9]{display:flex;align-items:center;gap:1.5rem}.connection-status[data-v-677538a9]{display:flex;align-items:center;gap:.625rem;padding:.625rem 1rem;background:#ef44440f;border:1px solid rgba(239,68,68,.12);border-radius:12px;transition:all .3s ease;position:relative;overflow:hidden}.connection-status.connected[data-v-677538a9]{background:#10b9810f;border-color:#10b9811f}.status-indicator[data-v-677538a9]{position:relative;width:10px;height:10px}.status-pulse[data-v-677538a9]{position:absolute;width:100%;height:100%;border-radius:50%;background:#ef4444;animation:pulse-677538a9 2s infinite}.connection-status.connected .status-pulse[data-v-677538a9]{background:#10b981}@keyframes pulse-677538a9{0%{transform:scale(.8);opacity:1}50%{transform:scale(1.2);opacity:.6}to{transform:scale(.8);opacity:1}}.status-label[data-v-677538a9]{font-size:.8rem;font-weight:600;color:#ef4444;transition:color .3s ease}.connection-status.connected .status-label[data-v-677538a9]{color:#10b981}.auth-controls[data-v-677538a9]{display:flex;align-items:center}.token-input-group[data-v-677538a9]{display:flex;align-items:center;background:#fffffff2;border:1.5px solid rgba(226,232,240,.5);border-radius:16px;padding:.375rem;transition:all .3s ease;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);box-shadow:0 2px 12px #0000000a}.token-input-group[data-v-677538a9]:focus-within{border-color:#6366f1;box-shadow:0 0 0 3px #6366f114,0 4px 20px #0000000f;transform:translateY(-1px)}.token-input[data-v-677538a9]{flex:1;min-width:200px;padding:.75rem 1rem;border:none;background:transparent;font-size:.8rem;color:#1e293b;outline:none;font-weight:500;letter-spacing:.02em}.token-input[data-v-677538a9]::placeholder{color:#94a3b8;font-weight:400}.input-actions[data-v-677538a9]{display:flex;gap:.375rem}.action-btn[data-v-677538a9]{width:36px;height:36px;border:none;border-radius:10px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}.action-btn svg[data-v-677538a9]{width:16px;height:16px;transition:all .3s ease}.save-btn[data-v-677538a9]{background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;box-shadow:0 2px 12px #6366f140}.save-btn[data-v-677538a9]:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 4px 20px #6366f159}.save-btn[data-v-677538a9]:disabled{background:#e2e8f0;color:#94a3b8;cursor:not-allowed;transform:none;box-shadow:none}.refresh-btn[data-v-677538a9]{background:linear-gradient(135deg,#10b981,#06b6d4);color:#fff;box-shadow:0 2px 12px #10b98140}.refresh-btn[data-v-677538a9]:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 4px 20px #10b98159}.refresh-btn:hover:not(:disabled) svg[data-v-677538a9]{transform:rotate(180deg)}.refresh-btn[data-v-677538a9]:disabled{background:#e2e8f0;color:#94a3b8;cursor:not-allowed;transform:none;box-shadow:none}@media (max-width: 1200px){.header-content[data-v-677538a9]{flex-direction:column;gap:1.5rem}.header-bar[data-v-677538a9]{height:auto;padding:1.25rem 1.5rem}.header-right[data-v-677538a9]{justify-content:center}.auth-section[data-v-677538a9]{flex-direction:column;gap:1rem}.token-input-group[data-v-677538a9]{width:100%;max-width:360px}}@media (max-width: 768px){.header-bar[data-v-677538a9]{padding:1rem 1.25rem}.brand-title[data-v-677538a9]{font-size:1.5rem}.brand-logo[data-v-677538a9]{width:40px;height:40px}.logo-svg[data-v-677538a9]{width:28px;height:28px}.token-input[data-v-677538a9]{min-width:160px}.auth-section[data-v-677538a9]{width:100%}.token-input-group[data-v-677538a9]{max-width:none}}.content-header[data-v-48fd4b43]{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.header-left[data-v-48fd4b43]{flex:1}.header-left h2[data-v-48fd4b43]{font-size:1.5rem;font-weight:600;color:#1a202c;margin:0 0 .125rem}.header-left p[data-v-48fd4b43]{font-size:.8125rem;color:#718096;margin:0}.header-actions[data-v-48fd4b43]{display:flex;gap:.75rem;align-items:center}.stats-section[data-v-48fd4b43]{display:flex;gap:.75rem;margin-bottom:1rem;flex-wrap:wrap}.stat-card[data-v-48fd4b43]{background:#fff;padding:.75rem 1rem;border-radius:8px;box-shadow:0 1px 3px #0000001a;border:2px solid #e2e8f0;min-width:90px;display:flex;justify-content:space-between;align-items:center;cursor:pointer;transition:all .2s ease;-webkit-user-select:none;user-select:none}.stat-card[data-v-48fd4b43]:hover{border-color:#667eea;transform:translateY(-1px);box-shadow:0 4px 12px #667eea26}.stat-card.active[data-v-48fd4b43]{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;border-color:#667eea}.stat-content[data-v-48fd4b43]{flex:1}.stat-number[data-v-48fd4b43]{font-size:1.25rem;font-weight:700;margin-bottom:.125rem;line-height:1}.stat-label[data-v-48fd4b43]{font-size:.6875rem;opacity:.8;font-weight:500}.stat-icon[data-v-48fd4b43]{font-size:1.25rem;opacity:.7;margin-left:.5rem;display:flex;align-items:center;justify-content:center}.stat-provider-icon[data-v-48fd4b43]{width:20px;height:20px;object-fit:contain}.main-content[data-v-48fd4b43]{background:#fff;border-radius:8px;box-shadow:0 1px 3px #0000001a;border:1px solid #e2e8f0}.list-header[data-v-48fd4b43]{padding:1rem;border-bottom:1px solid #e2e8f0;background:#f8fafc}.header-info[data-v-48fd4b43]{display:flex;justify-content:space-between;align-items:center}.header-info h3[data-v-48fd4b43]{font-size:1rem;font-weight:600;color:#1a202c;margin:0}.search-box[data-v-48fd4b43]{position:relative;width:250px}.search-icon[data-v-48fd4b43]{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);width:.875rem;height:.875rem;color:#9ca3af}.search-input[data-v-48fd4b43]{width:100%;padding:.5rem 2.5rem .5rem 2rem;border:1px solid #e5e7eb;border-radius:6px;font-size:.8125rem;background:#fff}.search-input[data-v-48fd4b43]:focus{outline:none;border-color:#667eea;box-shadow:0 0 0 2px #667eea1a}.clear-search[data-v-48fd4b43]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;color:#9ca3af;padding:.25rem;border-radius:4px;transition:all .2s ease}.clear-search[data-v-48fd4b43]:hover{background:#f3f4f6;color:#374151}.table-header[data-v-48fd4b43]{display:grid;grid-template-columns:2.6fr 1.5fr 1fr 2.2fr .8fr 1.2fr;gap:.75rem;padding:.75rem 1rem;background:#f1f5f9;border-bottom:1px solid #e2e8f0;font-size:.75rem;font-weight:600;color:#475569;text-transform:uppercase;letter-spacing:.025em}.header-cell[data-v-48fd4b43]{display:flex;align-items:center}.models-container[data-v-48fd4b43]{max-height:55vh;overflow-y:auto}.model-row[data-v-48fd4b43]{display:grid;grid-template-columns:2.6fr 1.5fr 1fr 2.2fr .8fr 1.2fr;gap:.75rem;padding:.75rem 1rem;border-bottom:1px solid #f1f5f9;transition:background-color .2s ease;align-items:center}.model-row[data-v-48fd4b43]:hover{background:#f8fafc}.model-row.disabled[data-v-48fd4b43]{opacity:.6}.model-row[data-v-48fd4b43]:last-child{border-bottom:none}.model-cell[data-v-48fd4b43]{display:flex;align-items:center}.model-identity[data-v-48fd4b43]{display:flex;align-items:center;gap:.75rem;width:100%}.model-avatar[data-v-48fd4b43]{width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer;transition:all .2s ease;padding:4px}.model-avatar[data-v-48fd4b43]:hover{background:#0000000d;transform:scale(1.05)}.provider-icon[data-v-48fd4b43]{width:100%;height:100%;object-fit:contain}.model-info[data-v-48fd4b43]{flex:1;min-width:0}.model-name[data-v-48fd4b43]{font-size:.875rem;font-weight:600;color:#1a202c;margin:0 0 .25rem;cursor:pointer;transition:color .2s ease;word-break:break-all}.model-name[data-v-48fd4b43]:hover{color:#667eea}.provider-badge[data-v-48fd4b43]{padding:.125rem .5rem;border-radius:10px;font-size:.625rem;font-weight:600;background:#e2e8f0;color:#4a5568}.provider-badge.openai[data-v-48fd4b43]{background:#dcfce7;color:#166534}.provider-badge.anthropic[data-v-48fd4b43]{background:#fef3c7;color:#92400e}.provider-badge.google[data-v-48fd4b43]{background:#dbeafe;color:#1e40af}.provider-badge.deepseek[data-v-48fd4b43]{background:#e0e7ff;color:#3730a3}.provider-badge.doubao[data-v-48fd4b43]{background:#fce7f3;color:#be185d}.provider-badge.qianwen[data-v-48fd4b43]{background:#c6e0e9;color:#0f0e0e}.base-url-cell[data-v-48fd4b43]{overflow:hidden;display:flex;align-items:center}.base-url-text[data-v-48fd4b43]{font-size:.8125rem;color:#374151;cursor:pointer;transition:color .2s ease;word-break:break-all;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.base-url-text[data-v-48fd4b43]:hover{color:#667eea}.params-cell[data-v-48fd4b43]{display:flex;align-items:center;justify-content:center}.params-text[data-v-48fd4b43]{font-size:.8125rem;color:#374151;font-weight:500;text-align:center}.description-cell[data-v-48fd4b43]{overflow:hidden;display:flex;align-items:center}.description-text[data-v-48fd4b43]{font-size:.8125rem;color:#6b7280;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.status-cell[data-v-48fd4b43]{display:flex;align-items:center;justify-content:center}.status-badge[data-v-48fd4b43]{padding:.25rem .5rem;border-radius:12px;font-size:.6875rem;font-weight:600;display:inline-block}.status-badge.enabled[data-v-48fd4b43]{background:#dcfce7;color:#166534}.status-badge.disabled[data-v-48fd4b43]{background:#fecaca;color:#991b1b}.actions-cell[data-v-48fd4b43]{display:flex;justify-content:center;align-items:center}.action-buttons[data-v-48fd4b43]{display:flex;gap:.25rem}.action-btn[data-v-48fd4b43]{width:32px;height:32px;border:none;border-radius:6px;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;position:relative}.action-btn[data-v-48fd4b43]:disabled{opacity:.6;cursor:not-allowed}.action-btn.test[data-v-48fd4b43]{background:#dcfce7;color:#166534}.action-btn.test[data-v-48fd4b43]:hover:not(:disabled){background:#bbf7d0;transform:translateY(-1px);box-shadow:0 2px 8px #22c55e4d}.action-btn.clone[data-v-48fd4b43]{background:linear-gradient(135deg,#fff9c4,#fef3bd);color:#6b7280;border:1px solid #f3e8ff}.action-btn.clone[data-v-48fd4b43]:hover:not(:disabled){background:linear-gradient(135deg,#fef3bd,#fde68a);transform:translateY(-1px);box-shadow:0 4px 12px #fef3bd4d;border-color:#e5e7eb}.action-btn.edit[data-v-48fd4b43]{background:#dbeafe;color:#1e40af}.action-btn.edit[data-v-48fd4b43]:hover:not(:disabled){background:#bfdbfe;transform:translateY(-1px);box-shadow:0 2px 8px #3b82f64d}.action-btn.delete[data-v-48fd4b43]{background:#fecaca;color:#991b1b}.action-btn.delete[data-v-48fd4b43]:hover:not(:disabled){background:#fca5a5;transform:translateY(-1px);box-shadow:0 2px 8px #ef44444d}.loading-state[data-v-48fd4b43],.empty-state[data-v-48fd4b43]{text-align:center;padding:3rem 2rem;color:#6b7280}.loading-spinner[data-v-48fd4b43]{width:32px;height:32px;border:3px solid #e5e7eb;border-top:3px solid #667eea;border-radius:50%;animation:spin-48fd4b43 1s linear infinite;margin:0 auto 1rem}.empty-icon[data-v-48fd4b43]{font-size:3rem;margin-bottom:1rem;opacity:.5}.empty-state h3[data-v-48fd4b43]{font-size:1.125rem;font-weight:600;color:#374151;margin:0 0 .5rem}.empty-state p[data-v-48fd4b43]{font-size:.875rem;margin:0}.modal-overlay[data-v-48fd4b43]{position:fixed;top:0;left:0;right:0;bottom:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal-content[data-v-48fd4b43]{background:#fff;border-radius:12px;width:90%;max-width:800px;max-height:85vh;overflow:hidden;box-shadow:0 20px 40px #00000040;border:1px solid rgba(255,255,255,.2);display:flex;flex-direction:column}.modal-header[data-v-48fd4b43]{display:flex;justify-content:space-between;align-items:center;padding:.875rem 1.25rem;border-bottom:1px solid #e5e7eb;background:linear-gradient(135deg,#f8fafc,#f1f5f9);border-radius:12px 12px 0 0;flex-shrink:0}.modal-header h3[data-v-48fd4b43]{font-size:1.125rem;font-weight:600;color:#1a202c;margin:0}.btn-close[data-v-48fd4b43]{background:none;border:none;cursor:pointer;color:#6b7280;padding:.25rem;border-radius:4px;transition:all .2s ease}.btn-close[data-v-48fd4b43]:hover{background:#e5e7eb;color:#374151}.modal-body[data-v-48fd4b43]{padding:1.25rem;flex:1;overflow-y:auto}.form-grid[data-v-48fd4b43]{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;margin-bottom:1rem}.form-column[data-v-48fd4b43]{display:flex;flex-direction:column;gap:.875rem}.form-group[data-v-48fd4b43]{margin-bottom:0}.form-row[data-v-48fd4b43]{display:grid;grid-template-columns:1fr 1fr;gap:.75rem}.form-group label[data-v-48fd4b43]{display:block;font-weight:600;color:#374151;margin-bottom:.375rem;font-size:.75rem}.form-input[data-v-48fd4b43],.form-textarea[data-v-48fd4b43]{width:100%;padding:.5rem .75rem;border:1px solid #e5e7eb;border-radius:6px;font-size:.875rem;transition:all .2s ease;background:#fff}.form-input[data-v-48fd4b43]:focus,.form-textarea[data-v-48fd4b43]:focus{outline:none;border-color:#667eea;box-shadow:0 0 0 2px #667eea1a}.form-textarea[data-v-48fd4b43]{resize:vertical;min-height:50px;font-family:inherit}.input-wrapper[data-v-48fd4b43]{position:relative}.toggle-visibility[data-v-48fd4b43]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;color:#6b7280;padding:.25rem;border-radius:4px;transition:all .2s ease}.toggle-visibility[data-v-48fd4b43]:hover{background:#f3f4f6;color:#374151}.form-actions[data-v-48fd4b43]{display:flex;gap:.75rem;justify-content:flex-end;margin-top:.75rem;padding-top:.75rem;border-top:1px solid #e5e7eb}.btn[data-v-48fd4b43]{padding:.5rem 1rem;border:none;border-radius:6px;font-weight:600;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:.375rem;font-size:.75rem;min-width:70px;justify-content:center}.btn[data-v-48fd4b43]:disabled{opacity:.6;cursor:not-allowed}.btn-primary[data-v-48fd4b43]{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.btn-primary[data-v-48fd4b43]:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 4px 12px #667eea4d}.btn-secondary[data-v-48fd4b43]{background:#f3f4f6;color:#374151;border:1px solid #d1d5db}.btn-secondary[data-v-48fd4b43]:hover:not(:disabled){background:#e5e7eb}.btn-loading[data-v-48fd4b43]{width:12px;height:12px;border:2px solid transparent;border-top:2px solid currentColor;border-radius:50%;animation:spin-48fd4b43 1s linear infinite}.copy-toast[data-v-48fd4b43]{position:fixed;top:2rem;right:2rem;background:#059669;color:#fff;padding:.75rem 1rem;border-radius:8px;box-shadow:0 4px 12px #00000026;display:flex;align-items:center;gap:.5rem;font-size:.875rem;font-weight:500;z-index:1001;animation:slideIn-48fd4b43 .3s ease-out}@keyframes slideIn-48fd4b43{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}.w-4[data-v-48fd4b43]{width:1rem}.h-4[data-v-48fd4b43]{height:1rem}.w-5[data-v-48fd4b43]{width:1.25rem}.h-5[data-v-48fd4b43]{height:1.25rem}.animate-spin[data-v-48fd4b43]{animation:spin-48fd4b43 1s linear infinite}@keyframes spin-48fd4b43{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (max-width: 1024px){.table-header[data-v-48fd4b43],.model-row[data-v-48fd4b43]{grid-template-columns:2fr 2.5fr 1fr 1.2fr .8fr 1fr;gap:.5rem}.search-box[data-v-48fd4b43]{width:200px}.modal-content[data-v-48fd4b43]{max-width:600px}}@media (max-width: 768px){.content-header[data-v-48fd4b43]{flex-direction:column;gap:1rem}.stats-section[data-v-48fd4b43]{grid-template-columns:repeat(2,1fr)}.header-info[data-v-48fd4b43]{flex-direction:column;gap:.75rem;align-items:flex-start}.search-box[data-v-48fd4b43]{width:100%}.table-header[data-v-48fd4b43]{display:none}.model-row[data-v-48fd4b43]{display:flex;flex-direction:column;gap:.75rem;padding:1rem;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:.75rem}.models-container[data-v-48fd4b43]{padding:.75rem}.model-identity[data-v-48fd4b43]{width:100%}.action-buttons[data-v-48fd4b43]{justify-content:flex-end;width:100%}.form-row[data-v-48fd4b43]{grid-template-columns:1fr}.form-actions[data-v-48fd4b43]{flex-direction:column}.copy-toast[data-v-48fd4b43]{top:1rem;left:1rem;right:1rem}.modal-content[data-v-48fd4b43]{width:95%;max-width:none;margin:1rem;max-height:70vh}.modal-header[data-v-48fd4b43]{padding:.75rem 1rem}.modal-body[data-v-48fd4b43]{padding:.875rem 1rem}}@media (max-width: 480px){.stats-section[data-v-48fd4b43]{grid-template-columns:1fr}.stat-card[data-v-48fd4b43]{min-width:auto}.modal-content[data-v-48fd4b43]{max-height:75vh}}.ai-node-config-content[data-v-de75772f]{padding:0;min-height:100vh}.content-header[data-v-de75772f]{display:flex;justify-content:space-between;align-items:center;padding:14px 24px;background:#fffffff2;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid rgba(226,232,240,.6);margin:0;box-shadow:0 1px 3px #0000000d}.header-left h2[data-v-de75772f]{margin:0;font-size:20px;font-weight:700;background:linear-gradient(135deg,#1e293b,#475569);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.header-right[data-v-de75772f]{display:flex;align-items:center;gap:20px}.header-stats[data-v-de75772f]{display:flex;gap:12px;align-items:center}.stat-item[data-v-de75772f]{display:flex;flex-direction:column;align-items:center;padding:8px 12px;background:#ffffffe6;border-radius:8px;border:1px solid rgba(226,232,240,.6);min-width:56px;box-shadow:0 1px 4px #0000000f;transition:all .3s ease}.stat-item[data-v-de75772f]:hover{transform:translateY(-1px);box-shadow:0 2px 8px #0000001a}.stat-item.success[data-v-de75772f]{background:linear-gradient(135deg,#f0fdf4,#dcfce7);border-color:#bbf7d0cc}.stat-item.warning[data-v-de75772f]{background:linear-gradient(135deg,#fffbeb,#fef3c7);border-color:#fed7aacc}.stat-item.info[data-v-de75772f]{background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#bfdbfecc}.stat-number[data-v-de75772f]{font-size:16px;font-weight:800;color:#1e293b;line-height:1}.stat-label[data-v-de75772f]{font-size:10px;color:#64748b;margin-top:2px;text-align:center;font-weight:500}.header-actions[data-v-de75772f]{display:flex;gap:8px}.btn[data-v-de75772f]{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;font-size:13px;font-weight:600;border:1px solid transparent;cursor:pointer;transition:all .3s ease;box-shadow:0 1px 4px #00000014;position:relative;overflow:hidden}.btn[data-v-de75772f]:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);transition:left .5s}.btn[data-v-de75772f]:hover:before{left:100%}.btn-secondary[data-v-de75772f]{background:linear-gradient(135deg,#f8fafc,#f1f5f9);color:#475569;border-color:#e2e8f0cc}.btn-secondary[data-v-de75772f]:hover{background:linear-gradient(135deg,#e2e8f0,#cbd5e1);transform:translateY(-1px);box-shadow:0 2px 8px #0000001f}.btn-primary[data-v-de75772f]{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;border-color:transparent}.btn-primary[data-v-de75772f]:hover{background:linear-gradient(135deg,#2563eb,#1d4ed8);transform:translateY(-1px);box-shadow:0 2px 12px #3b82f666}.btn[data-v-de75772f]:disabled{opacity:.5;cursor:not-allowed;transform:none}.toolbar[data-v-de75772f]{display:flex;justify-content:space-between;align-items:center;padding:12px 24px;background:#fffc;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-bottom:1px solid rgba(226,232,240,.4)}.search-container[data-v-de75772f]{position:relative;width:280px}.search-icon[data-v-de75772f]{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:16px;height:16px;color:#9ca3af;transition:color .3s ease}.search-input[data-v-de75772f]{width:100%;padding:8px 12px 8px 32px;border:1px solid rgba(209,213,219,.6);border-radius:8px;font-size:13px;background:#ffffffe6;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all .3s ease;box-shadow:0 1px 4px #0000000a}.search-input[data-v-de75772f]:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a,0 2px 8px #00000014}.search-input:focus+.search-icon[data-v-de75772f]{color:#3b82f6}.clear-btn[data-v-de75772f]{position:absolute;right:8px;top:50%;transform:translateY(-50%);padding:4px;background:none;border:none;color:#9ca3af;cursor:pointer;border-radius:4px;transition:all .3s ease}.clear-btn[data-v-de75772f]:hover{background:#9ca3af1a;color:#6b7280}.filter-tabs[data-v-de75772f]{display:flex;gap:6px;background:#fff9;padding:3px;border-radius:8px;border:1px solid rgba(226,232,240,.6)}.filter-tab[data-v-de75772f]{padding:6px 12px;border:none;background:transparent;border-radius:6px;font-size:12px;font-weight:500;cursor:pointer;transition:all .3s ease;color:#64748b}.filter-tab[data-v-de75772f]:hover{background:#3b82f61a;color:#3b82f6}.filter-tab.active[data-v-de75772f]{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;box-shadow:0 1px 4px #3b82f64d}.count[data-v-de75772f]{background:#ffffff40;padding:1px 6px;border-radius:8px;font-size:10px;margin-left:4px;font-weight:600}.main-content[data-v-de75772f]{padding:20px}.loading-state[data-v-de75772f],.empty-state[data-v-de75772f]{text-align:center;padding:60px 20px}.loading-spinner[data-v-de75772f]{width:36px;height:36px;border:3px solid rgba(59,130,246,.1);border-top:3px solid #3b82f6;border-radius:50%;animation:spin-de75772f 1s linear infinite;margin:0 auto 16px}@keyframes spin-de75772f{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.empty-icon[data-v-de75772f]{font-size:48px;margin-bottom:16px;opacity:.6}.operations-grid[data-v-de75772f]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:16px}.operation-card[data-v-de75772f]{background:#fffffff2;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:12px;border:1px solid rgba(226,232,240,.6);padding:16px;transition:all .4s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 8px #0000000f;position:relative;overflow:hidden}.operation-card[data-v-de75772f]:before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#e2e8f0,#cbd5e1);transition:all .3s ease}.operation-card[data-v-de75772f]:hover{box-shadow:0 4px 16px #0000001f;transform:translateY(-2px)}.operation-card.configured[data-v-de75772f]:before{background:linear-gradient(90deg,#10b981,#059669)}.operation-card.configured[data-v-de75772f]{border-color:#10b9814d;background:linear-gradient(135deg,#fffffffa,#f0fdf499)}.operation-card.disabled[data-v-de75772f]{opacity:.6;background:#f8fafccc}.operation-card.testing[data-v-de75772f]:before{background:linear-gradient(90deg,#3b82f6,#2563eb)}.operation-card.testing[data-v-de75772f]{border-color:#3b82f64d;background:linear-gradient(135deg,#fffffffa,#eff6ff99)}.card-header[data-v-de75772f]{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px}.operation-info[data-v-de75772f]{display:flex;align-items:center;gap:10px;flex:1}.operation-icon[data-v-de75772f]{width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:linear-gradient(135deg,#f8fafc,#f1f5f9);border:1px solid rgba(226,232,240,.6);box-shadow:0 1px 4px #0000000a}.provider-icon-large[data-v-de75772f]{width:24px;height:24px;object-fit:contain}.default-icon[data-v-de75772f]{font-size:18px}.operation-details[data-v-de75772f]{flex:1}.operation-name[data-v-de75772f]{font-size:15px;font-weight:700;color:#1e293b;margin:0 0 4px;line-height:1.2}.operation-desc[data-v-de75772f]{font-size:12px;color:#64748b;margin:0;line-height:1.4}.status-badge[data-v-de75772f]{padding:4px 8px;border-radius:6px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.status-badge.configured[data-v-de75772f]{background:linear-gradient(135deg,#dcfce7,#bbf7d0);color:#166534;border:1px solid rgba(187,247,208,.6)}.status-badge.pending[data-v-de75772f]{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#92400e;border:1px solid rgba(253,230,138,.6)}.status-badge.disabled[data-v-de75772f]{background:linear-gradient(135deg,#f1f5f9,#e2e8f0);color:#64748b;border:1px solid rgba(226,232,240,.6)}.model-binding[data-v-de75772f]{margin-bottom:12px}.model-selector[data-v-de75772f]{position:relative}.model-select[data-v-de75772f]{width:100%;padding:8px 12px;border:1px solid rgba(209,213,219,.6);border-radius:8px;font-size:13px;background:#ffffffe6;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all .3s ease;box-shadow:0 1px 4px #0000000a}.model-select[data-v-de75772f]:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a,0 2px 8px #00000014}.model-select.has-value[data-v-de75772f]{border-color:#10b981;background:linear-gradient(135deg,#fffffff2,#f0fdf44d)}.config-summary[data-v-de75772f]{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px;padding:10px;background:linear-gradient(135deg,#f8fafccc,#f1f5f999);border-radius:8px;border:1px solid rgba(226,232,240,.4)}.config-item[data-v-de75772f]{display:flex;align-items:center;gap:4px}.config-label[data-v-de75772f]{font-size:11px;color:#64748b;font-weight:500}.config-value[data-v-de75772f]{font-size:11px;font-weight:600;color:#1e293b}.config-features[data-v-de75772f]{display:flex;gap:6px}.feature-tag[data-v-de75772f]{padding:2px 6px;border-radius:4px;font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.feature-tag.json[data-v-de75772f]{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1e40af;border:1px solid rgba(191,219,254,.6)}.feature-tag.thinking[data-v-de75772f]{background:linear-gradient(135deg,#fce7f3,#f9a8d4);color:#be185d;border:1px solid rgba(249,168,212,.6)}.card-actions[data-v-de75772f]{display:flex;gap:6px;justify-content:flex-end}.action-btn[data-v-de75772f]{display:flex;align-items:center;justify-content:center;padding:8px;border:1px solid rgba(226,232,240,.6);border-radius:8px;background:#ffffffe6;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);cursor:pointer;transition:all .3s ease;box-shadow:0 1px 4px #0000000a;position:relative;overflow:hidden}.action-btn.large[data-v-de75772f]{padding:8px;min-width:36px;min-height:36px}.action-btn[data-v-de75772f]:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);transition:left .5s}.action-btn[data-v-de75772f]:hover:before{left:100%}.action-btn.test[data-v-de75772f]{border-color:#10b98166;color:#10b981}.action-btn.test[data-v-de75772f]:hover{background:linear-gradient(135deg,#10b981,#059669);color:#fff;transform:translateY(-1px);box-shadow:0 2px 8px #10b9814d}.action-btn.edit[data-v-de75772f]{border-color:#3b82f666;color:#3b82f6}.action-btn.edit[data-v-de75772f]:hover{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;transform:translateY(-1px);box-shadow:0 2px 8px #3b82f64d}.action-btn.clear[data-v-de75772f]{border-color:#ef444466;color:#ef4444}.action-btn.clear[data-v-de75772f]:hover{background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;transform:translateY(-1px);box-shadow:0 2px 8px #ef44444d}.action-btn[data-v-de75772f]:disabled{opacity:.5;cursor:not-allowed;transform:none}.modal-overlay[data-v-de75772f]{position:fixed;top:0;left:0;right:0;bottom:0;background:#0009;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:1000;animation:fadeIn-de75772f .3s ease-out}@keyframes fadeIn-de75772f{0%{opacity:0}to{opacity:1}}.modal-content[data-v-de75772f]{background:#fffffffa;-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);border-radius:12px;box-shadow:0 20px 40px -12px #00000040;max-width:480px;width:90%;max-height:90vh;border:1px solid rgba(255,255,255,.2);animation:slideUp-de75772f .3s ease-out}@keyframes slideUp-de75772f{0%{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}.modal-content.wide[data-v-de75772f]{max-width:720px;width:95%}.modal-header[data-v-de75772f]{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid rgba(226,232,240,.4)}.modal-header h3[data-v-de75772f]{margin:0;font-size:16px;font-weight:700;background:linear-gradient(135deg,#1e293b,#475569);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.btn-close[data-v-de75772f]{padding:6px;background:#f8fafccc;border:1px solid rgba(226,232,240,.6);color:#64748b;cursor:pointer;border-radius:6px;transition:all .3s ease}.btn-close[data-v-de75772f]:hover{background:#ef44441a;border-color:#ef44444d;color:#ef4444}.modal-body[data-v-de75772f]{padding:20px}.modal-body.compact[data-v-de75772f]{padding:16px 20px}.form-grid[data-v-de75772f]{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:16px}.form-grid.wide[data-v-de75772f]{grid-template-columns:repeat(3,1fr)}.form-row[data-v-de75772f]{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:16px}.form-group[data-v-de75772f]{display:flex;flex-direction:column}.form-group.full-width[data-v-de75772f]{grid-column:1 / -1}.form-group.checkbox-group[data-v-de75772f]{flex-direction:row;align-items:center}.form-group label[data-v-de75772f]{font-size:13px;font-weight:600;color:#374151;margin-bottom:6px}.form-input[data-v-de75772f],.form-textarea[data-v-de75772f]{padding:8px 12px;border:1px solid rgba(209,213,219,.6);border-radius:8px;font-size:13px;background:#ffffffe6;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all .3s ease;box-shadow:0 1px 4px #0000000a}.form-input[data-v-de75772f]:focus,.form-textarea[data-v-de75772f]:focus{outline:none;border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a,0 2px 8px #00000014}.checkbox-input[data-v-de75772f]{margin-right:8px;transform:scale(1.1)}.checkbox-label[data-v-de75772f]{display:flex;align-items:center;font-size:13px;cursor:pointer;font-weight:500}.operation-checkboxes[data-v-de75772f]{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;max-height:180px;overflow-y:auto;padding:12px;border:1px solid rgba(226,232,240,.6);border-radius:8px;background:#f8fafc80}.checkbox-item[data-v-de75772f]{display:flex;align-items:center;padding:6px;cursor:pointer;border-radius:4px;transition:all .3s ease;font-size:12px}.checkbox-item[data-v-de75772f]:hover{background:#3b82f61a}.form-actions[data-v-de75772f]{display:flex;justify-content:flex-end;gap:12px;margin-top:16px;padding-top:16px;border-top:1px solid rgba(226,232,240,.4)}.btn-loading[data-v-de75772f]{width:14px;height:14px;border:2px solid transparent;border-top:2px solid currentColor;border-radius:50%;animation:spin-de75772f 1s linear infinite;margin-right:6px}.success-toast[data-v-de75772f]{position:fixed;top:20px;right:20px;background:linear-gradient(135deg,#10b981,#059669);color:#fff;padding:12px 16px;border-radius:8px;display:flex;align-items:center;gap:8px;box-shadow:0 4px 16px #10b9814d;z-index:1001;animation:slideIn-de75772f .4s cubic-bezier(.4,0,.2,1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);font-size:13px}.error-toast[data-v-de75772f]{position:fixed;top:20px;right:20px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;padding:12px 16px;border-radius:8px;display:flex;align-items:center;gap:8px;box-shadow:0 4px 16px #ef44444d;z-index:1001;animation:slideIn-de75772f .4s cubic-bezier(.4,0,.2,1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);font-size:13px}@keyframes slideIn-de75772f{0%{transform:translate(100%) scale(.9);opacity:0}to{transform:translate(0) scale(1);opacity:1}}@media (max-width: 768px){.content-header[data-v-de75772f]{flex-direction:column;gap:12px;align-items:stretch;padding:12px 16px}.header-right[data-v-de75772f]{flex-direction:column;gap:12px}.header-stats[data-v-de75772f]{justify-content:center;gap:8px}.header-actions[data-v-de75772f]{justify-content:center}.toolbar[data-v-de75772f]{flex-direction:column;gap:12px;align-items:stretch;padding:12px 16px}.search-container[data-v-de75772f]{width:100%}.main-content[data-v-de75772f]{padding:16px}.operations-grid[data-v-de75772f]{grid-template-columns:1fr;gap:12px}.form-grid[data-v-de75772f]{grid-template-columns:1fr}.form-grid.wide[data-v-de75772f]{grid-template-columns:repeat(2,1fr)}.operation-checkboxes[data-v-de75772f]{grid-template-columns:1fr}}@media (max-width: 480px){.header-stats[data-v-de75772f]{grid-template-columns:repeat(2,1fr);gap:6px}.form-grid.wide[data-v-de75772f],.form-row[data-v-de75772f]{grid-template-columns:1fr}.modal-content[data-v-de75772f]{margin:16px;width:calc(100% - 32px)}}@media (prefers-color-scheme: dark){.ai-node-config-content[data-v-de75772f]{background:linear-gradient(135deg,#0f172a,#1e293b)}.content-header[data-v-de75772f]{background:#1e293bf2;border-bottom-color:#47556999}.header-left h2[data-v-de75772f]{background:linear-gradient(135deg,#f8fafc,#cbd5e1);-webkit-background-clip:text;-webkit-text-fill-color:transparent}.operation-card[data-v-de75772f]{background:#1e293bf2;border-color:#47556999}.modal-content[data-v-de75772f]{background:#1e293bfa;border-color:#47556966}}.content-header[data-v-038ec4b3]{margin-bottom:2rem}.content-header h2[data-v-038ec4b3]{font-size:2rem;font-weight:700;color:#2d3748;margin:0 0 .5rem}.content-header p[data-v-038ec4b3]{font-size:1.1rem;color:#718096;margin:0}.system-content .system-operations[data-v-038ec4b3]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:2rem}.operation-card[data-v-038ec4b3]{background:#fffc;border-radius:16px;padding:2rem;box-shadow:0 4px 16px #0000000d;border:1px solid rgba(0,0,0,.05);display:flex;flex-direction:column;align-items:center;text-align:center;gap:1rem}.operation-card .card-icon[data-v-038ec4b3]{width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;font-size:2rem;color:#fff}.operation-card .card-content h3[data-v-038ec4b3]{font-size:1.25rem;font-weight:600;color:#2d3748;margin:0 0 .5rem}.operation-card .card-content p[data-v-038ec4b3]{font-size:1rem;color:#718096;margin:0 0 1rem}.btn[data-v-038ec4b3]{padding:.75rem 1.5rem;border:none;border-radius:8px;font-weight:600;cursor:pointer;transition:all .3s ease;display:inline-flex;align-items:center;gap:.5rem;text-decoration:none;font-size:1rem}.btn[data-v-038ec4b3]:disabled{opacity:.6;cursor:not-allowed}.btn-primary[data-v-038ec4b3]{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.btn-primary[data-v-038ec4b3]:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 4px 12px #667eea66}.btn-secondary[data-v-038ec4b3]{background:#e2e8f0;color:#4a5568}.btn-secondary[data-v-038ec4b3]:hover:not(:disabled){background:#cbd5e0}.btn-danger[data-v-038ec4b3]{background:#fed7d7;color:#742a2a}.btn-danger[data-v-038ec4b3]:hover:not(:disabled){background:#feb2b2}.btn-loading[data-v-038ec4b3]{width:16px;height:16px;border:2px solid transparent;border-top:2px solid currentColor;border-radius:50%;animation:spin-038ec4b3 1s linear infinite}@keyframes spin-038ec4b3{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (max-width: 768px){.system-operations[data-v-038ec4b3]{grid-template-columns:1fr}}.json-viewer[data-v-6b3d677c]{border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;background:#fff;font-family:SF Mono,Monaco,Menlo,Ubuntu Mono,monospace;font-size:13px}.json-toolbar[data-v-6b3d677c]{display:flex;align-items:center;padding:8px 12px;background:linear-gradient(135deg,#f8fafc,#f1f5f9);border-bottom:1px solid #e2e8f0;gap:12px;min-height:40px}.toolbar-left[data-v-6b3d677c]{display:flex;gap:6px}.toolbar-center[data-v-6b3d677c]{flex:1;display:flex;justify-content:center}.toolbar-right[data-v-6b3d677c]{display:flex;align-items:center;gap:8px;font-size:11px;color:#64748b}.tool-btn[data-v-6b3d677c]{display:flex;align-items:center;gap:4px;padding:4px 8px;border:1px solid #d1d5db;border-radius:4px;background:#fff;color:#6b7280;font-size:11px;font-weight:500;cursor:pointer;transition:all .15s ease;height:24px}.tool-btn[data-v-6b3d677c]:hover{background:#f9fafb;border-color:#9ca3af;color:#374151}.tool-btn.active[data-v-6b3d677c]{background:#3b82f6;color:#fff;border-color:#3b82f6}.tool-btn.success[data-v-6b3d677c]{background:#10b981;color:#fff;border-color:#10b981}.tool-btn svg[data-v-6b3d677c]{width:12px;height:12px}.search-box[data-v-6b3d677c]{position:relative;display:flex;align-items:center;max-width:200px}.search-box svg[data-v-6b3d677c]{position:absolute;left:8px;width:12px;height:12px;color:#9ca3af;z-index:1}.search-input[data-v-6b3d677c]{width:100%;padding:4px 24px;border:1px solid #d1d5db;border-radius:4px;font-size:11px;background:#fff;outline:none;transition:border-color .15s ease}.search-input[data-v-6b3d677c]:focus{border-color:#3b82f6}.clear-search[data-v-6b3d677c]{position:absolute;right:4px;background:none;border:none;cursor:pointer;padding:2px;color:#9ca3af;display:flex;align-items:center}.clear-search svg[data-v-6b3d677c]{width:10px;height:10px}.path-display[data-v-6b3d677c]{font-family:inherit;color:#3b82f6;font-weight:500;max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.size-info[data-v-6b3d677c],.item-count[data-v-6b3d677c]{font-weight:600;color:#64748b}.json-content[data-v-6b3d677c]{max-height:var(--1b6c152d);overflow:auto;scrollbar-width:thin;scrollbar-color:#cbd5e1 transparent}.json-content[data-v-6b3d677c]::-webkit-scrollbar{width:6px;height:6px}.json-content[data-v-6b3d677c]::-webkit-scrollbar-track{background:transparent}.json-content[data-v-6b3d677c]::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:3px}.json-raw[data-v-6b3d677c]{padding:16px;white-space:pre-wrap;word-break:break-all;color:#374151;line-height:1.5}.json-formatted[data-v-6b3d677c]{padding:16px;line-height:1.4;color:#374151;white-space:pre;overflow-x:auto}[data-v-6b3d677c] .json-string{color:#059669;font-weight:500}[data-v-6b3d677c] .json-number{color:#dc2626;font-weight:600}[data-v-6b3d677c] .json-boolean{color:#7c3aed;font-weight:600}[data-v-6b3d677c] .json-null,[data-v-6b3d677c] .json-undefined{color:#6b7280;font-style:italic;font-weight:500}[data-v-6b3d677c] .json-key{color:#1e40af;font-weight:600}[data-v-6b3d677c] .json-bracket{color:#374151;font-weight:700}[data-v-6b3d677c] .json-colon{color:#6b7280;margin:0 4px}[data-v-6b3d677c] .json-toggle{cursor:pointer;color:#6b7280;margin-right:4px;font-size:10px;transition:color .15s ease;-webkit-user-select:none;user-select:none}[data-v-6b3d677c] .json-toggle:hover{color:#374151}[data-v-6b3d677c] .json-preview{color:#9ca3af;font-style:italic;margin:0 4px;font-size:11px}[data-v-6b3d677c] .search-highlight{background:#fef3c7;padding:1px 2px;border-radius:2px;box-shadow:0 0 0 1px #f59e0b}@media (max-width: 768px){.json-toolbar[data-v-6b3d677c]{flex-wrap:wrap;gap:8px}.toolbar-center[data-v-6b3d677c]{order:3;flex-basis:100%;justify-content:flex-start}.search-box[data-v-6b3d677c]{max-width:none;width:100%}.path-display[data-v-6b3d677c]{display:none}}.log-detail-page[data-v-7246130a]{height:100vh;background:radial-gradient(ellipse at top,#f8fafc,#f1f5f9,#e2e8f0);display:flex;flex-direction:column;overflow:hidden;position:relative}.log-detail-page[data-v-7246130a]:before{content:"";position:absolute;top:0;left:0;right:0;height:200px;background:linear-gradient(135deg,#3b82f608,#93c5fd05);pointer-events:none}.top-nav[data-v-7246130a]{background:#fffffffa;-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border-bottom:1px solid rgba(226,232,240,.6);padding:1.5rem 2rem;display:flex;align-items:center;gap:1.5rem;flex-shrink:0;box-shadow:0 1px 3px #00000008,0 1px 2px #00000005;position:relative;z-index:10}.nav-back[data-v-7246130a]{background:linear-gradient(135deg,#fff,#f8fafc);border:1px solid rgba(226,232,240,.8);display:flex;align-items:center;gap:.625rem;color:#475569;font-weight:600;cursor:pointer;padding:.75rem 1.25rem;border-radius:10px;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;position:relative;overflow:hidden}.nav-back[data-v-7246130a]:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.4),transparent);transition:left .5s}.nav-back[data-v-7246130a]:hover:before{left:100%}.nav-back[data-v-7246130a]:hover{background:linear-gradient(135deg,#f1f5f9,#e2e8f0);color:#1e293b;transform:translateY(-2px);box-shadow:0 4px 12px #00000014,0 2px 4px #0000000a}.nav-back svg[data-v-7246130a]{width:18px;height:18px;stroke-width:2.5;transition:transform .3s ease}.nav-back:hover svg[data-v-7246130a]{transform:translate(-2px)}.nav-title[data-v-7246130a]{flex:1;display:flex;align-items:center;justify-content:space-between}.nav-title h1[data-v-7246130a]{font-size:1.5rem;font-weight:800;margin:0;background:linear-gradient(135deg,#0f172a,#334155,#64748b);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;letter-spacing:-.025em}.status-badge[data-v-7246130a]{padding:.625rem 1.25rem;border-radius:10px;font-size:.875rem;font-weight:700;display:flex;align-items:center;gap:.5rem;box-shadow:0 2px 8px #0000000f,0 1px 3px #0000000a;position:relative;overflow:hidden}.status-badge[data-v-7246130a]:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;opacity:.1;background:radial-gradient(circle at center,currentColor 0%,transparent 70%)}.status-badge.success[data-v-7246130a]{background:linear-gradient(135deg,#dcfce7,#bbf7d0,#a7f3d0);color:#15803d;border:1px solid rgba(134,239,172,.6)}.status-badge.failed[data-v-7246130a]{background:linear-gradient(135deg,#fef2f2,#fecaca,#fca5a5);color:#dc2626;border:1px solid rgba(252,165,165,.6)}.status-icon[data-v-7246130a]{width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor;animation:pulse-7246130a 2s infinite}@keyframes pulse-7246130a{0%,to{opacity:1}50%{opacity:.6}}.content-wrapper[data-v-7246130a]{flex:1;padding:2rem;overflow:hidden}.loading-container[data-v-7246130a]{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1.5rem;height:100%;color:#64748b;font-weight:600}.spinner-container[data-v-7246130a]{position:relative;width:48px;height:48px}.spinner[data-v-7246130a]{width:48px;height:48px;border:4px solid rgba(59,130,246,.1);border-top:4px solid #3b82f6;border-radius:50%;animation:spin-7246130a 1s linear infinite;position:relative;z-index:2}.spinner-glow[data-v-7246130a]{position:absolute;top:-4px;left:-4px;width:56px;height:56px;border-radius:50%;background:radial-gradient(circle,rgba(59,130,246,.2) 0%,transparent 70%);animation:glow-7246130a 2s ease-in-out infinite alternate}@keyframes spin-7246130a{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes glow-7246130a{0%{transform:scale(1);opacity:.5}to{transform:scale(1.1);opacity:.8}}.loading-text[data-v-7246130a]{font-size:1rem;letter-spacing:.025em}.detail-layout[data-v-7246130a]{display:grid;grid-template-columns:380px 1fr;gap:2rem;height:100%;overflow:hidden}.info-sidebar[data-v-7246130a]{display:flex;flex-direction:column;gap:1rem;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(203,213,225,.6) transparent}.info-sidebar[data-v-7246130a]::-webkit-scrollbar{width:6px}.info-sidebar[data-v-7246130a]::-webkit-scrollbar-track{background:transparent}.info-sidebar[data-v-7246130a]::-webkit-scrollbar-thumb{background:#cbd5e199;border-radius:3px}.info-card[data-v-7246130a]{background:#fffffffa;-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border-radius:16px;border:1px solid rgba(226,232,240,.6);overflow:hidden;box-shadow:0 8px 32px #0000000a,0 4px 16px #00000005;transition:all .4s cubic-bezier(.4,0,.2,1);position:relative}.info-card[data-v-7246130a]:before{content:"";position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(59,130,246,.2),transparent)}.info-card[data-v-7246130a]:hover{box-shadow:0 12px 48px #00000014,0 6px 24px #0000000a;transform:translateY(-2px)}.card-title[data-v-7246130a]{background:linear-gradient(135deg,#f8fafc,#f1f5f9);padding:.875rem 1.25rem;font-size:.8125rem;font-weight:800;color:#374151;border-bottom:1px solid rgba(226,232,240,.6);letter-spacing:.05em;display:flex;align-items:center;gap:.625rem}.title-icon[data-v-7246130a]{font-size:1rem;filter:grayscale(.2)}.info-list[data-v-7246130a]{padding:0}.info-item[data-v-7246130a]{padding:.875rem 1.25rem;border-bottom:1px solid rgba(241,245,249,.6);display:flex;flex-direction:column;gap:.5rem;transition:all .3s ease;position:relative}.info-item[data-v-7246130a]:before{content:"";position:absolute;left:0;top:0;bottom:0;width:0;background:linear-gradient(135deg,#3b82f6,#1d4ed8);transition:width .3s ease}.info-item[data-v-7246130a]:hover:before{width:3px}.info-item[data-v-7246130a]:hover{background:#f8fafccc}.info-item[data-v-7246130a]:last-child{border-bottom:none}.label[data-v-7246130a]{font-size:.6875rem;color:#64748b;font-weight:700;text-transform:uppercase;letter-spacing:.1em;line-height:1.2}.call-id[data-v-7246130a]{font-family:SF Mono,Monaco,Menlo,monospace;font-size:.75rem;background:linear-gradient(135deg,#f1f5f9,#e2e8f0);padding:.5rem .75rem;border-radius:8px;color:#475569;word-break:break-all;border:1px solid rgba(226,232,240,.8);font-weight:600;position:relative;overflow:hidden}.call-id[data-v-7246130a]:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:linear-gradient(45deg,transparent 30%,rgba(255,255,255,.3) 50%,transparent 70%);transform:translate(-100%);transition:transform .6s}.call-id[data-v-7246130a]:hover:before{transform:translate(100%)}.operation-badge[data-v-7246130a]{background:linear-gradient(135deg,#dbeafe,#bfdbfe,#93c5fd);color:#1e40af;padding:.5rem 1rem;border-radius:8px;font-size:.75rem;font-weight:800;align-self:flex-start;border:1px solid rgba(147,197,253,.6);box-shadow:0 2px 8px #3b82f626;position:relative;overflow:hidden}.operation-badge[data-v-7246130a]:before{content:"";position:absolute;top:0;left:0;right:0;bottom:0;background:linear-gradient(135deg,rgba(255,255,255,.3),transparent);opacity:0;transition:opacity .3s}.operation-badge[data-v-7246130a]:hover:before{opacity:1}.model-info[data-v-7246130a]{display:flex;align-items:center;gap:1rem}.provider-icon-wrapper[data-v-7246130a]{position:relative;padding:4px;background:linear-gradient(135deg,#f8fafc,#f1f5f9);border-radius:10px;box-shadow:0 2px 8px #0000000f}.provider-icon[data-v-7246130a]{width:32px;height:32px;border-radius:6px;object-fit:contain;flex-shrink:0;transition:transform .3s ease}.provider-icon-wrapper:hover .provider-icon[data-v-7246130a]{transform:scale(1.05)}.model-details[data-v-7246130a]{flex:1;min-width:0}.model-name[data-v-7246130a]{font-size:.875rem;font-weight:800;color:#0f172a;line-height:1.4;word-break:break-all;margin-bottom:2px}.provider-name[data-v-7246130a]{font-size:.75rem;color:#64748b;line-height:1.4;font-weight:600}.time-value[data-v-7246130a]{font-size:.875rem;color:#0f172a;font-weight:700;font-family:SF Mono,Monaco,Menlo,monospace;letter-spacing:-.025em}.duration-badge[data-v-7246130a]{padding:.5rem 1rem;border-radius:8px;font-size:.75rem;font-weight:800;align-self:flex-start;font-family:SF Mono,Monaco,Menlo,monospace;border:1px solid;box-shadow:0 2px 8px #00000014;display:flex;align-items:center;gap:.5rem;position:relative;overflow:hidden}.duration-icon[data-v-7246130a]{width:6px;height:6px;border-radius:50%;background:currentColor;box-shadow:0 0 6px currentColor}.duration-badge.fast[data-v-7246130a]{background:#c6f6d5;color:#22543d;border-color:#86efac99}.duration-badge.normal[data-v-7246130a]{background:#faf089;color:#744210;border-color:#fbbf2499}.duration-badge.slow[data-v-7246130a]{background:#fed7d7;color:#742a2a;border-color:#fca5a599}.params-grid[data-v-7246130a]{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:#f1f5f999}.param-item[data-v-7246130a]{background:#fffc;padding:1rem 1.25rem;display:flex;flex-direction:column;gap:.5rem;transition:all .3s ease;position:relative}.param-item[data-v-7246130a]:before{content:"";position:absolute;left:0;top:0;bottom:0;width:0;background:linear-gradient(135deg,#3b82f6,#1d4ed8);transition:width .3s ease}.param-item[data-v-7246130a]:hover:before{width:2px}.param-item[data-v-7246130a]:hover{background:#f8fafce6}.param-label[data-v-7246130a]{font-size:.75rem;color:#64748b;font-weight:700;text-transform:uppercase;letter-spacing:.05em}.param-value[data-v-7246130a]{font-size:.875rem;font-weight:800;color:#0f172a;font-family:SF Mono,Monaco,Menlo,monospace}.param-toggle[data-v-7246130a]{display:flex;align-items:center;gap:.5rem;font-size:.75rem;font-weight:700;padding:.25rem .5rem;border-radius:6px;align-self:flex-start}.toggle-indicator[data-v-7246130a]{width:8px;height:8px;border-radius:50%;transition:all .3s ease}.param-toggle.enabled[data-v-7246130a]{color:#059669;background:#dcfce799}.param-toggle.enabled .toggle-indicator[data-v-7246130a]{background:#10b981;box-shadow:0 0 8px #10b98166}.param-toggle.disabled[data-v-7246130a]{color:#64748b;background:#f1f5f999}.param-toggle.disabled .toggle-indicator[data-v-7246130a]{background:#94a3b8}.content-area[data-v-7246130a]{display:flex;flex-direction:column;overflow:hidden}.content-tabs[data-v-7246130a]{display:flex;background:#fffffffa;-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border-radius:16px 16px 0 0;border:1px solid rgba(226,232,240,.6);border-bottom:none;overflow-x:auto;box-shadow:0 4px 16px #0000000a;position:relative}.content-tabs[data-v-7246130a]:before{content:"";position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(59,130,246,.2),transparent)}.tab-btn[data-v-7246130a]{background:none;border:none;padding:0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);border-bottom:3px solid transparent;position:relative;overflow:hidden}.tab-content[data-v-7246130a]{padding:1.25rem 1.5rem;display:flex;align-items:center;gap:.75rem;white-space:nowrap}.tab-label[data-v-7246130a]{font-size:.875rem;font-weight:700;color:#64748b;transition:color .3s ease}.tab-btn:hover .tab-label[data-v-7246130a]{color:#334155}.tab-btn.active .tab-label[data-v-7246130a]{color:#3b82f6;font-weight:800}.tab-btn.active[data-v-7246130a]{border-bottom-color:#3b82f6;background:#f8fafccc}.tab-indicator[data-v-7246130a]{position:relative;width:8px;height:8px}.indicator-pulse[data-v-7246130a]{width:8px;height:8px;background:linear-gradient(135deg,#10b981,#059669);border-radius:50%;animation:pulse-glow-7246130a 2s infinite;position:relative}.indicator-pulse[data-v-7246130a]:before{content:"";position:absolute;top:-2px;left:-2px;right:-2px;bottom:-2px;background:radial-gradient(circle,rgba(16,185,129,.3) 0%,transparent 70%);border-radius:50%;animation:pulse-glow-7246130a 2s infinite}@keyframes pulse-glow-7246130a{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.2);opacity:.7}}.content-display[data-v-7246130a]{flex:1;background:#fffffffa;-webkit-backdrop-filter:blur(20px) saturate(180%);backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(226,232,240,.6);border-radius:0 0 16px 16px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 8px 32px #0000000a}.content-block[data-v-7246130a]{height:100%;display:flex;flex-direction:column}.content-block.error[data-v-7246130a]{border-color:#fca5a599}.content-header[data-v-7246130a]{background:linear-gradient(135deg,#f8fafc,#f1f5f9);padding:1.25rem 1.5rem;border-bottom:1px solid rgba(226,232,240,.6);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}.header-left[data-v-7246130a]{display:flex;align-items:center;gap:.75rem}.content-icon[data-v-7246130a]{font-size:1.125rem;filter:grayscale(.2)}.content-icon.error-icon[data-v-7246130a]{filter:none}.content-header h3[data-v-7246130a]{margin:0;font-size:.875rem;font-weight:800;color:#374151;letter-spacing:.025em}.copy-btn[data-v-7246130a]{display:flex;align-items:center;gap:.625rem;background:linear-gradient(135deg,#fff,#f8fafc);border:1px solid rgba(209,213,219,.8);border-radius:8px;padding:.625rem 1rem;font-size:.75rem;color:#6b7280;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);font-weight:700;box-shadow:0 1px 3px #0000000a;position:relative;overflow:hidden}.copy-btn[data-v-7246130a]:before{content:"";position:absolute;top:0;left:-100%;width:100%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.6),transparent);transition:left .5s}.copy-btn[data-v-7246130a]:hover:before{left:100%}.copy-btn[data-v-7246130a]:hover{background:linear-gradient(135deg,#f9fafb,#f3f4f6);border-color:#9ca3af;color:#374151;transform:translateY(-1px);box-shadow:0 4px 12px #00000014}.copy-btn svg[data-v-7246130a]{width:16px;height:16px;stroke-width:2.5;transition:transform .3s ease}.copy-btn:hover svg[data-v-7246130a]{transform:scale(1.1)}.content-body[data-v-7246130a]{flex:1;overflow:hidden;position:relative}.code-content[data-v-7246130a],.text-content[data-v-7246130a]{width:100%;height:100%;padding:1.5rem;font-family:SF Mono,Monaco,Menlo,Ubuntu Mono,monospace;font-size:.8rem;line-height:1.7;color:#374151;background:linear-gradient(135deg,#f8fafc,#f1f5f9);white-space:pre-wrap;word-break:break-word;overflow-y:auto;margin:0;border:none;scrollbar-width:thin;scrollbar-color:rgba(203,213,225,.6) transparent;resize:none}.code-content[data-v-7246130a]::-webkit-scrollbar,.text-content[data-v-7246130a]::-webkit-scrollbar{width:8px}.code-content[data-v-7246130a]::-webkit-scrollbar-track,.text-content[data-v-7246130a]::-webkit-scrollbar-track{background:transparent}.code-content[data-v-7246130a]::-webkit-scrollbar-thumb,.text-content[data-v-7246130a]::-webkit-scrollbar-thumb{background:#cbd5e199;border-radius:4px}.error-content[data-v-7246130a]{width:100%;height:100%;padding:1.5rem;background:linear-gradient(135deg,#fef2f2,#fee2e2);color:#dc2626;font-family:SF Mono,Monaco,Menlo,Ubuntu Mono,monospace;font-size:.8rem;line-height:1.7;white-space:pre-wrap;word-break:break-word;overflow-y:auto;border:1px solid rgba(252,165,165,.4);border-radius:8px;margin:.75rem;font-weight:600}.empty-state[data-v-7246130a]{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#64748b;gap:1.5rem;padding:3rem}.empty-illustration[data-v-7246130a]{position:relative;display:flex;align-items:center;justify-content:center}.empty-circle[data-v-7246130a]{position:absolute;width:80px;height:80px;border:2px solid rgba(203,213,225,.3);border-radius:50%;animation:rotate-7246130a 8s linear infinite}.empty-icon[data-v-7246130a]{font-size:2.5rem;opacity:.7;filter:grayscale(.3);z-index:1}@keyframes rotate-7246130a{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.empty-text[data-v-7246130a]{font-size:.875rem;font-weight:600;margin:0;letter-spacing:.025em}@media (max-width: 1200px){.detail-layout[data-v-7246130a]{grid-template-columns:320px 1fr}}@media (max-width: 1024px){.detail-layout[data-v-7246130a]{grid-template-columns:1fr;grid-template-rows:auto 1fr}.info-sidebar[data-v-7246130a]{max-height:350px}.params-grid[data-v-7246130a]{grid-template-columns:1fr}}@media (max-width: 768px){.content-wrapper[data-v-7246130a]{padding:1rem}.top-nav[data-v-7246130a]{padding:1rem 1.5rem}.nav-title[data-v-7246130a]{flex-direction:column;align-items:flex-start;gap:1rem}.detail-layout[data-v-7246130a]{gap:1.5rem}.info-card[data-v-7246130a],.content-tabs[data-v-7246130a],.content-display[data-v-7246130a]{border-radius:12px}.content-tabs[data-v-7246130a]{border-radius:12px 12px 0 0}.content-display[data-v-7246130a]{border-radius:0 0 12px 12px}}.ai-log-content[data-v-584cc492]{padding:0;background:transparent}.content-header[data-v-584cc492]{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding:0}.content-header h2[data-v-584cc492]{font-size:1.5rem;font-weight:600;color:#2d3748;margin:0}.header-actions[data-v-584cc492]{display:flex;gap:.75rem}.btn[data-v-584cc492]{display:flex;align-items:center;gap:.375rem;padding:.5rem 1rem;border-radius:6px;font-weight:500;transition:all .2s ease;border:none;cursor:pointer;font-size:.875rem}.btn-secondary[data-v-584cc492]{background:#ffffffe6;color:#4a5568;border:1px solid #e2e8f0}.btn-secondary[data-v-584cc492]:hover:not(:disabled){background:#f7fafc;border-color:#cbd5e0}.btn-danger[data-v-584cc492]{background:#e53e3e;color:#fff}.btn-danger[data-v-584cc492]:hover:not(:disabled){background:#c53030}.btn[data-v-584cc492]:disabled{opacity:.6;cursor:not-allowed}.main-content[data-v-584cc492]{background:#fffffff2;border-radius:12px;padding:1.5rem;box-shadow:0 4px 16px #00000014;border:1px solid rgba(255,255,255,.2)}.loading-state[data-v-584cc492],.empty-state[data-v-584cc492]{text-align:center;padding:3rem 2rem}.loading-spinner[data-v-584cc492]{width:32px;height:32px;border:3px solid #e2e8f0;border-top:3px solid #667eea;border-radius:50%;animation:spin-584cc492 1s linear infinite;margin:0 auto 1rem}@keyframes spin-584cc492{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.empty-icon[data-v-584cc492]{font-size:3rem;margin-bottom:1rem}.empty-state h3[data-v-584cc492]{color:#2d3748;margin-bottom:.5rem;font-size:1.125rem}.empty-state p[data-v-584cc492]{color:#718096;font-size:.875rem}.list-header[data-v-584cc492]{margin-bottom:1rem}.header-info[data-v-584cc492]{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem}.log-count[data-v-584cc492]{color:#718096;font-size:.875rem;font-weight:500}.search-box[data-v-584cc492]{position:relative;display:flex;align-items:center}.search-icon[data-v-584cc492]{position:absolute;left:.75rem;width:1rem;height:1rem;color:#a0aec0;z-index:1}.search-input[data-v-584cc492]{padding:.5rem .75rem .5rem 2.25rem;border:1px solid #e2e8f0;border-radius:6px;font-size:.875rem;width:200px;transition:border-color .2s ease}.search-input[data-v-584cc492]:focus{outline:none;border-color:#667eea;box-shadow:0 0 0 3px #667eea1a}.filter-controls[data-v-584cc492]{display:flex;gap:.75rem;align-items:center}.filter-select[data-v-584cc492]{padding:.5rem .75rem;border:1px solid #e2e8f0;border-radius:6px;font-size:.875rem;background:#fff;transition:border-color .2s ease}.filter-select[data-v-584cc492]:focus{outline:none;border-color:#667eea}.table-container[data-v-584cc492]{border-radius:8px;overflow:hidden;border:1px solid #e2e8f0;background:#fff}.table-header[data-v-584cc492]{display:grid;grid-template-columns:140px 100px 400px 2fr 100px 80px;gap:0;background:linear-gradient(135deg,#f8fafc,#f1f5f9);border-bottom:2px solid #e2e8f0}.header-cell[data-v-584cc492]{padding:1rem .75rem;font-weight:600;color:#4a5568;font-size:.875rem;text-align:left;border-right:1px solid #e2e8f0}.header-cell[data-v-584cc492]:last-child{border-right:none}.table-body[data-v-584cc492]{background:#fff}.table-row[data-v-584cc492]{display:grid;grid-template-columns:140px 100px 400px 2fr 100px 80px;gap:0;border-bottom:1px solid #f1f5f9;transition:all .2s ease;position:relative}.table-row[data-v-584cc492]:hover{background:linear-gradient(135deg,#f8fafc,#f1f5f9);transform:translateY(-1px);box-shadow:0 2px 8px #0000000d}.table-row[data-v-584cc492]:last-child{border-bottom:none}.table-row.row-failed[data-v-584cc492]{background:linear-gradient(135deg,#fef5e7,#fed7aa);border-left:4px solid #f56565}.table-row.row-failed[data-v-584cc492]:hover{background:linear-gradient(135deg,#fed7aa,#fbb6ce)}.cell[data-v-584cc492]{padding:1rem .75rem;display:flex;align-items:center;border-right:1px solid #f1f5f9;min-height:60px}.cell[data-v-584cc492]:last-child{border-right:none}.time-info[data-v-584cc492]{display:flex;flex-direction:column;gap:.25rem}.time-relative[data-v-584cc492]{font-size:.875rem;font-weight:500;color:#2d3748}.time-full[data-v-584cc492]{font-size:.75rem;color:#718096}.model-info[data-v-584cc492]{display:flex;align-items:center;gap:.75rem;width:100%}.model-avatar[data-v-584cc492]{width:36px;height:36px;border-radius:8px;background:linear-gradient(135deg,#f7fafc,#edf2f7);display:flex;align-items:center;justify-content:center;border:1px solid #e2e8f0;flex-shrink:0;box-shadow:0 2px 4px #0000000d}.provider-icon[data-v-584cc492]{width:20px;height:20px;object-fit:contain}.model-details[data-v-584cc492]{flex:1;min-width:0}.model-name[data-v-584cc492]{font-size:.875rem;font-weight:600;color:#2d3748;margin:0 0 .25rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.provider-name[data-v-584cc492]{font-size:.75rem;color:#718096;font-weight:500}.operation-tag[data-v-584cc492]{display:inline-flex;align-items:center;padding:.375rem .75rem;border-radius:6px;font-size:.75rem;font-weight:600;background:linear-gradient(135deg,#e6f3ff,#cce7ff);color:#2b6cb0;border:1px solid #90cdf4}.duration-value[data-v-584cc492]{font-size:.875rem;font-weight:600;padding:.25rem .5rem;border-radius:4px}.duration-fast[data-v-584cc492]{color:#22543d;background:#c6f6d5}.duration-normal[data-v-584cc492]{color:#744210;background:#faf089}.duration-slow[data-v-584cc492]{color:#742a2a;background:#fed7d7}.duration-unknown[data-v-584cc492]{color:#718096;background:#edf2f7}.status-indicator[data-v-584cc492]{display:flex;align-items:center;gap:.5rem;font-size:.875rem;font-weight:500}.status-dot[data-v-584cc492]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-indicator.success .status-dot[data-v-584cc492]{background:#48bb78;box-shadow:0 0 0 2px #48bb7833}.status-indicator.success[data-v-584cc492]{color:#22543d}.status-indicator.failed .status-dot[data-v-584cc492]{background:#f56565;box-shadow:0 0 0 2px #f5656533}.status-indicator.failed[data-v-584cc492]{color:#742a2a}.actions-cell[data-v-584cc492]{justify-content:center}.action-btn[data-v-584cc492]{width:36px;height:36px;border-radius:8px;border:1px solid #e2e8f0;background:linear-gradient(135deg,#fff,#f7fafc);color:#718096;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 4px #0000000d}.action-btn[data-v-584cc492]:hover{background:linear-gradient(135deg,#e6f3ff,#cce7ff);border-color:#90cdf4;color:#2b6cb0;transform:translateY(-1px);box-shadow:0 4px 8px #0000001a}.pagination[data-v-584cc492]{display:flex;justify-content:center;align-items:center;gap:1rem;margin-top:1.5rem;padding-top:1rem;border-top:1px solid #e2e8f0}.page-btn[data-v-584cc492]{padding:.5rem 1rem;border:1px solid #e2e8f0;background:#fff;color:#4a5568;border-radius:6px;cursor:pointer;transition:all .2s ease;font-size:.875rem}.page-btn[data-v-584cc492]:hover:not(:disabled){background:#f7fafc;border-color:#cbd5e0}.page-btn[data-v-584cc492]:disabled{opacity:.5;cursor:not-allowed}.page-info[data-v-584cc492]{color:#718096;font-size:.875rem}@media (max-width: 1200px){.table-header[data-v-584cc492],.table-row[data-v-584cc492]{grid-template-columns:120px 80px 250px 2fr 80px 60px}}@media (max-width: 768px){.content-header[data-v-584cc492]{flex-direction:column;gap:1rem;align-items:flex-start}.header-actions[data-v-584cc492]{width:100%;justify-content:flex-end}.list-header[data-v-584cc492]{flex-direction:column;gap:1rem}.header-info[data-v-584cc492]{flex-direction:column;gap:.75rem;align-items:flex-start}.filter-controls[data-v-584cc492]{flex-wrap:wrap;gap:.5rem}.table-header[data-v-584cc492],.table-row[data-v-584cc492]{grid-template-columns:100px 80px 120px 1fr 80px 60px}.search-input[data-v-584cc492]{width:150px}.model-name[data-v-584cc492]{font-size:.75rem}.provider-name[data-v-584cc492]{font-size:.65rem}}.api-config[data-v-6f5fc07f]{min-height:100vh;background:linear-gradient(135deg,#f5f7fa,#c3cfe2);padding:1rem}.main-content[data-v-6f5fc07f]{display:flex;gap:0;height:calc(100vh - 160px)}@media (max-width: 768px){.main-content[data-v-6f5fc07f]{flex-direction:column;height:auto}.sidebar[data-v-6f5fc07f]{width:100%;border-radius:16px;border:1px solid rgba(255,255,255,.2);margin-bottom:1rem}.divider[data-v-6f5fc07f]{display:none}.content-area[data-v-6f5fc07f]{width:100%;border-radius:16px;border:1px solid rgba(255,255,255,.2)}.stats-grid[data-v-6f5fc07f]{grid-template-columns:repeat(3,1fr);gap:.25rem}.stat-item[data-v-6f5fc07f]{flex-direction:column;text-align:center;padding:.5rem}.stat-value[data-v-6f5fc07f]{font-size:1rem;margin-bottom:.25rem}.stat-label[data-v-6f5fc07f]{font-size:.65rem}}.sidebar[data-v-6f5fc07f]{width:15%;background:#ffffffe6;border-radius:16px 0 0 16px;padding:2rem 1.5rem;box-shadow:0 4px 16px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);border-right:none;overflow-y:auto;display:flex;flex-direction:column}.tab-navigation[data-v-6f5fc07f]{flex:1}.tab-header[data-v-6f5fc07f]{margin-bottom:1.5rem}.tab-header h3[data-v-6f5fc07f]{font-size:1.25rem;font-weight:600;color:#2d3748;margin:0}.tab-list[data-v-6f5fc07f]{display:flex;flex-direction:column;gap:.5rem}.tab-item[data-v-6f5fc07f]{position:relative;padding:1rem;border-radius:12px;cursor:pointer;transition:all .3s ease;border:2px solid transparent;display:flex;align-items:center;gap:1rem}.tab-item[data-v-6f5fc07f]:hover{background:#667eea0d;border-color:#667eea33}.tab-item.active[data-v-6f5fc07f]{background:linear-gradient(135deg,#667eea1a,#764ba21a);border-color:#667eea;box-shadow:0 4px 12px #667eea33}.tab-icon[data-v-6f5fc07f]{min-width:2rem;text-align:center;display:flex;align-items:center;justify-content:center}.icon-svg[data-v-6f5fc07f]{width:24px;height:24px;color:#718096;transition:all .3s ease;display:flex;align-items:center;justify-content:center}.icon-svg svg[data-v-6f5fc07f]{width:100%;height:100%;transition:all .3s ease}.tab-item:hover .icon-svg[data-v-6f5fc07f]{color:#667eea;transform:scale(1.1)}.tab-item.active .icon-svg[data-v-6f5fc07f]{color:#667eea;transform:scale(1.15)}.tab-item.active .icon-svg svg[data-v-6f5fc07f]{filter:drop-shadow(0 2px 4px rgba(102,126,234,.3))}.tab-content[data-v-6f5fc07f]{flex:1}.tab-title[data-v-6f5fc07f]{font-weight:600;color:#2d3748;margin-bottom:.25rem;font-size:.9rem}.tab-description[data-v-6f5fc07f]{font-size:.75rem;color:#718096;line-height:1.3}.tab-indicator[data-v-6f5fc07f]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);width:4px;height:20px;background:#667eea;border-radius:2px;opacity:0;transition:opacity .3s ease}.tab-item.active .tab-indicator[data-v-6f5fc07f]{opacity:1}.status-card[data-v-6f5fc07f]{background:#fffc;border-radius:12px;padding:1rem;border:1px solid rgba(0,0,0,.05);margin-bottom:.75rem}.status-header[data-v-6f5fc07f]{display:flex;align-items:center;gap:.5rem;margin-bottom:.75rem}.status-icon[data-v-6f5fc07f]{font-size:1rem}.status-title[data-v-6f5fc07f]{font-weight:600;color:#2d3748;font-size:.85rem}.status-content[data-v-6f5fc07f]{display:flex;flex-direction:column;gap:.5rem}.status-item[data-v-6f5fc07f]{display:flex;justify-content:space-between;align-items:center}.status-label[data-v-6f5fc07f]{font-size:.75rem;color:#718096}.status-value[data-v-6f5fc07f]{font-size:.75rem;font-weight:600;color:#2d3748}.status-value.running[data-v-6f5fc07f]{color:#22c55e}.stats-card[data-v-6f5fc07f]{background:#fffc;border-radius:12px;padding:1rem;border:1px solid rgba(0,0,0,.05)}.stats-header[data-v-6f5fc07f]{display:flex;align-items:center;gap:.5rem;margin-bottom:.75rem}.stats-icon[data-v-6f5fc07f]{font-size:1rem}.stats-title[data-v-6f5fc07f]{font-weight:600;color:#2d3748;font-size:.85rem}.stats-grid[data-v-6f5fc07f]{display:grid;grid-template-columns:repeat(1,1fr);gap:.5rem}.stat-item[data-v-6f5fc07f]{display:flex;justify-content:space-between;align-items:center;padding:.5rem 0}.stat-value[data-v-6f5fc07f]{font-size:1.25rem;font-weight:700;color:#667eea}.stat-label[data-v-6f5fc07f]{font-size:.7rem;color:#718096;font-weight:500}.divider[data-v-6f5fc07f]{width:1px;background:linear-gradient(to bottom,transparent 0%,rgba(226,232,240,.8) 20%,rgba(226,232,240,.8) 80%,transparent 100%);margin:0}.content-area[data-v-6f5fc07f]{flex:1;background:#ffffffe6;border-radius:0 16px 16px 0;padding:2rem;box-shadow:0 4px 16px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);border-left:none;overflow-y:auto} diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/favicon.ico b/prompto-lab-app/SF-Chain/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/anthropic.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/anthropic.svg new file mode 100644 index 0000000..23266e3 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/anthropic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/deepseek.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/deepseek.svg new file mode 100644 index 0000000..6e2cac4 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/deepseek.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/default.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/default.svg new file mode 100644 index 0000000..f72b3a4 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/doubao.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/doubao.svg new file mode 100644 index 0000000..db0bca8 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/doubao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/google.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/google.svg new file mode 100644 index 0000000..db14fba --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/openai.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/openai.svg new file mode 100644 index 0000000..d2a5a1e --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/openai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/icons/qianwen.svg b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/qianwen.svg new file mode 100644 index 0000000..6bc8ba6 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/icons/qianwen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/resources/static/index.html b/prompto-lab-app/SF-Chain/src/main/resources/static/index.html new file mode 100644 index 0000000..f697a51 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/resources/static/index.html @@ -0,0 +1,16 @@ + + + + + + + Vite App + + + + + + +
+ + diff --git a/prompto-lab-app/pom.xml b/prompto-lab-app/pom.xml index a5ec5f2..2ce78c9 100644 --- a/prompto-lab-app/pom.xml +++ b/prompto-lab-app/pom.xml @@ -119,6 +119,13 @@ fastjson 2.0.57 + + + com.suifeng + sf-chain + 1.0.0 + + diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/model/PromptAI.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/model/PromptAI.java index ff6823c..9d91346 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/model/PromptAI.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/model/PromptAI.java @@ -1,6 +1,6 @@ package io.github.timemachinelab.core.model; -import io.github.timemachinelab.sfchain.annotation.AIOp; +import com.suifeng.sfchain.annotation.AIOp; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java index fde0a41..7c98b2a 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java @@ -2,9 +2,9 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.suifeng.sfchain.core.AIService; import io.github.timemachinelab.core.session.domain.entity.ConversationSession; import io.github.timemachinelab.core.session.infrastructure.ai.QuestionGenerationOperation; -import io.github.timemachinelab.sfchain.core.AIService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java index bed5b31..4381229 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java @@ -1,7 +1,7 @@ package io.github.timemachinelab.core.session.infrastructure.ai; -import io.github.timemachinelab.sfchain.annotation.AIOp; -import io.github.timemachinelab.sfchain.core.BaseAIOperation; +import com.suifeng.sfchain.annotation.AIOp; +import com.suifeng.sfchain.core.BaseAIOperation; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/QuestionGenerationOperation.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/QuestionGenerationOperation.java index 4a48c92..bc3d85f 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/QuestionGenerationOperation.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/QuestionGenerationOperation.java @@ -1,10 +1,10 @@ package io.github.timemachinelab.core.session.infrastructure.ai; +import com.suifeng.sfchain.annotation.AIOp; +import com.suifeng.sfchain.core.BaseAIOperation; import io.github.timemachinelab.core.question.BaseQuestion; import io.github.timemachinelab.core.question.QuestionParser; import io.github.timemachinelab.core.question.QuestionParseException; -import io.github.timemachinelab.sfchain.annotation.AIOp; -import io.github.timemachinelab.sfchain.core.BaseAIOperation; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/annotation/AIOp.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/annotation/AIOp.java deleted file mode 100644 index 71f7b27..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/annotation/AIOp.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.timemachinelab.sfchain.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 描述: AI操作注解 - * 用于标识AI操作类,并指定操作类型和默认模型 - * @author suifeng - * 日期: 2025/8/11 - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface AIOp { - - /** - * 操作类型标识 - * 例如: "POSITION_BASIC_INFO_PARSE_OP" - */ - String value(); - - /** - * 默认使用的模型名称 - * 如果不指定,将使用操作映射配置中的模型 - */ - String defaultModel() default ""; - - /** - * 操作描述 - */ - String description() default ""; - - /** - * 是否启用该操作 - */ - boolean enabled() default true; - - /** - * 支持的模型列表(可选) - * 如果指定,将限制该操作只能使用这些模型 - */ - String[] supportedModels() default {}; - - /** - * 是否需要JSON输出 - */ - boolean requireJsonOutput() default true; - - /** - * 是否自动修复JSON格式错误 - * 当requireJsonOutput为true且AI返回的JSON格式有误时,自动调用JSON修复操作 - */ - boolean autoRepairJson() default true; - - /** - * 是否支持思考模式 - */ - boolean supportThinking() default false; - - /** - * 默认最大token数 - */ - int defaultMaxTokens() default 4096; - - /** - * 默认温度参数 - */ - double defaultTemperature() default 0.7; -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIAutoConfiguration.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIAutoConfiguration.java deleted file mode 100644 index 6ee7303..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIAutoConfiguration.java +++ /dev/null @@ -1,74 +0,0 @@ -package io.github.timemachinelab.sfchain.config; - -import io.github.timemachinelab.sfchain.core.AIModel; -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelConfig; -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelFactory; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * 描述: OpenAI兼容模型自动配 - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@Configuration -@EnableConfigurationProperties(OpenAIModelsConfig.class) -@RequiredArgsConstructor -@ConditionalOnProperty(prefix = "ai.openai-models", name = "enabled", havingValue = "true", matchIfMissing = true) -public class OpenAIAutoConfiguration { - - private final OpenAIModelsConfig openAIModelsConfig; - - /** - * 创建OpenAI模型工厂 - */ - @Bean - @Primary - public OpenAIModelFactory openAIModelFactory() { - OpenAIModelFactory factory = new OpenAIModelFactory(); - - // 注册配置文件中的模型 - Map modelConfigs = openAIModelsConfig.getValidModelConfigs(); - modelConfigs.forEach((name, config) -> { - try { - factory.registerModel(config); - log.info("成功注册模型: {} ({})", config.getModelName(), config.getProvider()); - } catch (Exception e) { - log.error("注册模型失败: {} - {}", config.getModelName(), e.getMessage()); - } - }); - - return factory; - } - - /** - * 创建AI模型列表,供ModelRegistry使用 - */ - @Bean - public List aiModels(OpenAIModelFactory factory) { - List models = new ArrayList<>(); - - // 为每个注册的模型创建实例 - factory.getRegisteredModelNames().forEach(modelName -> { - try { - AIModel model = factory.createModel(modelName); - models.add(model); - log.info("创建AI模型实例: {}", modelName); - } catch (Exception e) { - log.error("创建模型实例失败: {} - {}", modelName, e.getMessage()); - } - }); - - return models; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIModelsConfig.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIModelsConfig.java deleted file mode 100644 index 24784cb..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/config/OpenAIModelsConfig.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.github.timemachinelab.sfchain.config; - -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelConfig; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -/** - * 描述: OpenAI兼容模型配置 - * @author suifeng - * 日期: 2025/8/11 - */ -@Data -@Component -@ConfigurationProperties(prefix = "ai.openai-models") -public class OpenAIModelsConfig { - - /** - * 模型配置映射 - * key: 模型名称 - * value: 模型配置 - */ - private Map models = new HashMap<>(); - - /** - * 模型配置属性 - */ - @Data - public static class ModelConfigProperties { - /** - * 模型名称 - */ - private String modelName; - - /** - * API基础URL - */ - private String baseUrl; - - /** - * API密钥 - */ - private String apiKey; - - /** - * 默认最大token数 - */ - private Integer defaultMaxTokens = 4096; - - /** - * 默认温度参数 - */ - private Double defaultTemperature = 0.7; - - /** - * 是否支持流式输出 - */ - private Boolean supportStream = false; - - /** - * 是否支持JSON格式输出 - */ - private Boolean supportJsonOutput = false; - - /** - * 是否支持思考模式 - */ - private Boolean supportThinking = false; - - /** - * 额外的HTTP请求头 - */ - private Map additionalHeaders = new HashMap<>(); - - /** - * 模型描述 - */ - private String description; - - /** - * 模型提供商 - */ - private String provider; - - /** - * 是否启用 - */ - private Boolean enabled = true; - - /** - * 转换为OpenAIModelConfig - */ - public OpenAIModelConfig toOpenAIModelConfig() { - return OpenAIModelConfig.builder() - .modelName(modelName) - .baseUrl(baseUrl) - .apiKey(apiKey) - .defaultMaxTokens(defaultMaxTokens) - .defaultTemperature(defaultTemperature) - .supportStream(supportStream) - .supportJsonOutput(supportJsonOutput) - .supportThinking(supportThinking) - .additionalHeaders(additionalHeaders) - .description(description) - .provider(provider) - .enabled(enabled) - .build(); - } - } - - /** - * 获取所有有效的模型配置 - */ - public Map getValidModelConfigs() { - Map validConfigs = new HashMap<>(); - - models.forEach((key, properties) -> { - if (properties.getModelName() == null) { - properties.setModelName(key); - } - - OpenAIModelConfig config = properties.toOpenAIModelConfig(); - if (config.isValid() && Boolean.TRUE.equals(config.getEnabled())) { - validConfigs.put(key, config); - } - }); - - return validConfigs; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/constants/AIOperationConstant.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/constants/AIOperationConstant.java deleted file mode 100644 index cd80d69..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/constants/AIOperationConstant.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.github.timemachinelab.sfchain.constants; - -/** - * 描述: AI操作常量定义 - * 定义所有AI操作的标识符 - * - * @author suifeng - * 日期: 2025/8/11 - */ -public class AIOperationConstant { - - /** - * JSON修复操作 - */ - public static final String JSON_REPAIR_OP = "JSON_REPAIR_OP"; - - /** - * 模型验证操作 - */ - public static final String MODEL_VALIDATION_OP = "MODEL_VALIDATION_OP"; - - /** - * 私有构造函数,防止实例化 - */ - private AIOperationConstant() { - throw new UnsupportedOperationException("常量类不允许实例化"); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AICallLogController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AICallLogController.java deleted file mode 100644 index 61723d4..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AICallLogController.java +++ /dev/null @@ -1,72 +0,0 @@ -package io.github.timemachinelab.sfchain.controller; - -import io.github.timemachinelab.sfchain.core.logging.AICallLog; -import io.github.timemachinelab.sfchain.core.logging.AICallLogManager; -import io.github.timemachinelab.sfchain.core.logging.AICallLogSummary; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Map; - -/** - * AI调用日志查询控制器 - */ -@RestController -@RequestMapping("/sf/api/ai-logs") -public class AICallLogController { - - @Resource - private AICallLogManager logManager; - - /** - * 获取所有日志摘要(轻量级) - */ - @GetMapping - public ResponseEntity> getAllLogSummaries() { - return ResponseEntity.ok(logManager.getAllLogSummaries()); - } - - /** - * 根据调用ID获取完整日志详情 - */ - @GetMapping("/{callId}") - public ResponseEntity getFullLog(@PathVariable String callId) { - AICallLog log = logManager.getFullLog(callId); - return log != null ? ResponseEntity.ok(log) : ResponseEntity.notFound().build(); - } - - /** - * 根据操作类型获取日志摘要(轻量级) - */ - @GetMapping("/operation/{operationType}") - public ResponseEntity> getLogSummariesByOperation(@PathVariable String operationType) { - return ResponseEntity.ok(logManager.getLogSummariesByOperation(operationType)); - } - - /** - * 根据模型名称获取日志摘要(轻量级) - */ - @GetMapping("/model/{modelName}") - public ResponseEntity> getLogSummariesByModel(@PathVariable String modelName) { - return ResponseEntity.ok(logManager.getLogSummariesByModel(modelName)); - } - - /** - * 获取统计信息 - */ - @GetMapping("/statistics") - public ResponseEntity getStatistics() { - return ResponseEntity.ok(logManager.getStatistics()); - } - - /** - * 清空所有日志 - */ - @DeleteMapping - public ResponseEntity> clearLogs() { - logManager.clearLogs(); - return ResponseEntity.ok(Map.of("message", "所有日志已清空")); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java deleted file mode 100644 index ed34b96..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java +++ /dev/null @@ -1,244 +0,0 @@ -package io.github.timemachinelab.sfchain.controller; - -import io.github.timemachinelab.sfchain.core.AIOperationRegistry; -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelConfig; -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelFactory; -import io.github.timemachinelab.sfchain.operations.ModelValidationOperation; -import io.github.timemachinelab.sfchain.persistence.ModelConfigData; -import io.github.timemachinelab.sfchain.persistence.PersistenceManager; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static io.github.timemachinelab.sfchain.constants.AIOperationConstant.MODEL_VALIDATION_OP; - -/** - * 描述: AI模型配置管理控制器 - * 提供AI模型的增删改查、测试验证等功能 - * - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@RestController -@RequestMapping("/sf/api/models") -@RequiredArgsConstructor -public class AIModelController { - - private final PersistenceManager persistenceManager; - private final AIOperationRegistry operationRegistry; - private final OpenAIModelFactory modelFactory; - - /** - * 获取所有模型配置(包含状态信息) - */ - @GetMapping("/list") - public ResponseEntity> getAllModels() { - try { - Map models = persistenceManager.getAllModelConfigs(); - Map result = new HashMap<>(); - - // 按提供商分组 - Map> groupedByProvider = models.values().stream() - .collect(Collectors.groupingBy(m -> m.getProvider() != null ? m.getProvider() : "未知")); - - result.put("models", models); - result.put("groupedByProvider", groupedByProvider); - result.put("total", models.size()); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("获取模型列表失败: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "获取模型列表失败: " + e.getMessage())); - } - } - - /** - * 获取单个模型配置 - */ - @GetMapping("/{modelName}") - public ResponseEntity getModel(@PathVariable String modelName) { - try { - Map models = persistenceManager.getAllModelConfigs(); - ModelConfigData model = models.get(modelName); - - if (model == null) { - return ResponseEntity.notFound().build(); - } - - return ResponseEntity.ok(model); - } catch (Exception e) { - log.error("获取模型配置失败: {} - {}", modelName, e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "获取模型配置失败: " + e.getMessage())); - } - } - - /** - * 创建或更新模型配置 - */ - @PostMapping("/save") - public ResponseEntity> saveModel( - @Valid @RequestBody ModelConfigData config) { - Map result = new HashMap<>(); - String modelName = config.getModelName(); - try { - // 验证模型配置 - boolean validationResult = validateModelConfig(modelName, config); - - if (!validationResult) { - result.put("success", false); - result.put("message", "模型验证失败,请检查配置参数"); - return ResponseEntity.badRequest().body(result); - } - - // 检查模型是否已存在 - Map existingModels = persistenceManager.getAllModelConfigs(); - boolean modelExists = existingModels.containsKey(modelName); - - // 根据模型是否存在选择添加或更新 - if (modelExists) { - persistenceManager.updateModelConfig(modelName, config); - result.put("operation", "updated"); - result.put("message", "模型配置更新成功"); - log.info("模型配置已更新: {}", modelName); - } else { - persistenceManager.addModelConfig(modelName, config); - result.put("operation", "created"); - result.put("message", "模型配置创建成功"); - log.info("模型配置已创建: {}", modelName); - } - - result.put("success", true); - result.put("modelName", modelName); - result.put("validated", true); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("保存模型配置失败: {} - {}", modelName, e.getMessage()); - result.put("success", false); - result.put("message", "保存失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - /** - * 删除模型配置 - */ - @DeleteMapping("/{modelName}") - public ResponseEntity> deleteModel(@PathVariable String modelName) { - Map result = new HashMap<>(); - try { - persistenceManager.deleteModelConfig(modelName); - result.put("success", true); - result.put("message", "模型配置删除成功"); - result.put("modelName", modelName); - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("删除模型配置失败: {} - {}", modelName, e.getMessage()); - result.put("success", false); - result.put("message", "删除失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - /** - * 测试模型连接 - */ - @PostMapping("/{modelName}/test") - public ResponseEntity> testModel(@PathVariable String modelName) { - Map result = new HashMap<>(); - try { - Map models = persistenceManager.getAllModelConfigs(); - ModelConfigData config = models.get(modelName); - - if (config == null) { - result.put("success", false); - result.put("message", "模型配置不存在"); - return ResponseEntity.notFound().build(); - } - - boolean testResult = validateModelConfig(modelName, config); - result.put("success", testResult); - result.put("message", testResult ? "模型连接测试成功" : "模型连接测试失败"); - result.put("modelName", modelName); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("测试模型连接失败: {} - {}", modelName, e.getMessage()); - result.put("success", false); - result.put("message", "测试失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - // ==================== 私有方法 ==================== - - /** - * 验证模型配置是否可用 - */ - private boolean validateModelConfig(String modelName, ModelConfigData config) { - boolean tempRegistered = false; - try { - log.info("开始验证模型配置: {}", modelName); - - ModelValidationOperation validationOp = (ModelValidationOperation) operationRegistry.getOperation(MODEL_VALIDATION_OP); - if (validationOp == null) { - log.warn("未找到模型验证操作,跳过验证"); - return true; - } - - OpenAIModelConfig tempConfig = convertToOpenAIConfig(config); - modelFactory.registerModel(tempConfig); - tempRegistered = true; - - ModelValidationOperation.ValidationRequest request = - new ModelValidationOperation.ValidationRequest("请回答:2+3等于几?"); - - ModelValidationOperation.ValidationResult result = validationOp.execute(request, modelName); - - return result != null && result.getAnswer() != null && !result.getAnswer().trim().isEmpty(); - - } catch (Exception e) { - log.error("模型验证失败: {} - {}", modelName, e.getMessage()); - return false; - } finally { - if (tempRegistered) { - try { - modelFactory.removeModel(modelName); - } catch (Exception e) { - log.warn("清理临时模型配置失败: {}", e.getMessage()); - } - } - } - } - - /** - * 转换配置格式 - */ - private OpenAIModelConfig convertToOpenAIConfig(ModelConfigData config) { - return OpenAIModelConfig.builder() - .modelName(config.getModelName()) - .baseUrl(config.getBaseUrl()) - .apiKey(config.getApiKey()) - .defaultMaxTokens(config.getDefaultMaxTokens()) - .defaultTemperature(config.getDefaultTemperature()) - .supportStream(config.getSupportStream()) - .supportJsonOutput(config.getSupportJsonOutput()) - .supportThinking(config.getSupportThinking()) - .additionalHeaders(config.getAdditionalHeaders()) - .description(config.getDescription()) - .provider(config.getProvider()) - .enabled(config.getEnabled()) - .build(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java deleted file mode 100644 index 5aa7aa1..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java +++ /dev/null @@ -1,281 +0,0 @@ -package io.github.timemachinelab.sfchain.controller; - -import io.github.timemachinelab.sfchain.persistence.ModelConfigData; -import io.github.timemachinelab.sfchain.persistence.OperationConfigData; -import io.github.timemachinelab.sfchain.persistence.PersistenceManager; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -/** - * 描述: AI操作配置管理控制器 - * 提供AI操作的配置管理、模型映射等功能 - * - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@RestController -@RequestMapping("/sf/api/operations") -@RequiredArgsConstructor -public class AIOperationController { - - private final PersistenceManager persistenceManager; - - /** - * 获取所有AI操作及其配置状态 - */ - @GetMapping - public ResponseEntity> getAllOperations() { - try { - Map configs = persistenceManager.getAllOperationConfigs(); - - // 构建操作模型映射信息(从操作配置中提取) - Map mappings = new HashMap<>(); - configs.forEach((operationType, config) -> { - if (config.getModelName() != null && !config.getModelName().isEmpty()) { - mappings.put(operationType, config.getModelName()); - } - }); - - Map result = new HashMap<>(); - result.put("mappings", mappings); - result.put("configs", configs); - result.put("totalOperations", configs.size()); - result.put("configuredOperations", mappings.size()); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("获取操作列表失败: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "获取操作列表失败: " + e.getMessage())); - } - } - - /** - * 获取操作配置(包含关联的模型信息) - */ - @GetMapping("/{operationType}") - public ResponseEntity getOperation(@PathVariable String operationType) { - try { - Optional operationOpt = persistenceManager.getOperationConfig(operationType); - - if (operationOpt.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - OperationConfigData operation = operationOpt.get(); - - // 如果有关联模型,获取模型信息 - if (operation.getModelName() != null) { - Map models = persistenceManager.getAllModelConfigs(); - ModelConfigData model = models.get(operation.getModelName()); - - Map result = new HashMap<>(); - result.put("operation", operation); - result.put("associatedModel", model); - return ResponseEntity.ok(result); - } - - return ResponseEntity.ok(operation); - } catch (Exception e) { - log.error("获取操作配置失败: {} - {}", operationType, e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "获取操作配置失败: " + e.getMessage())); - } - } - - /** - * 保存操作配置 - 改为统一的save接口,从请求体获取operationType - */ - @PostMapping("/save") - public ResponseEntity> saveOperationConfig( - @Valid @RequestBody OperationConfigData config) { - Map result = new HashMap<>(); - try { - // 从config对象中获取operationType - String operationType = config.getOperationType(); - - if (operationType == null || operationType.trim().isEmpty()) { - result.put("success", false); - result.put("message", "操作类型不能为空"); - return ResponseEntity.badRequest().body(result); - } - - persistenceManager.saveOperationConfig(operationType, config); - - result.put("success", true); - result.put("message", "操作配置保存成功"); - result.put("operationType", operationType); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("保存操作配置失败: {}", e.getMessage()); - result.put("success", false); - result.put("message", "保存失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - /** - * 获取单个操作配置 - 改为POST请求体参数 - */ - @PostMapping("/get") - public ResponseEntity getOperation(@RequestBody Map request) { - try { - String operationType = request.get("operationType"); - - if (operationType == null || operationType.trim().isEmpty()) { - return ResponseEntity.badRequest() - .body(Map.of("error", "操作类型不能为空")); - } - - Optional operationOpt = persistenceManager.getOperationConfig(operationType); - - if (operationOpt.isEmpty()) { - return ResponseEntity.notFound().build(); - } - - OperationConfigData operation = operationOpt.get(); - - // 如果有关联模型,获取模型信息 - if (operation.getModelName() != null) { - Map models = persistenceManager.getAllModelConfigs(); - ModelConfigData model = models.get(operation.getModelName()); - - Map result = new HashMap<>(); - result.put("operation", operation); - result.put("associatedModel", model); - return ResponseEntity.ok(result); - } - - return ResponseEntity.ok(operation); - } catch (Exception e) { - log.error("获取操作配置失败: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "获取操作配置失败: " + e.getMessage())); - } - } - - /** - * 批量设置操作模型映射 - */ - @PostMapping("/mappings") - public ResponseEntity> setOperationMappings( - @RequestBody Map mappings) { - Map result = new HashMap<>(); - int successCount = 0; - int failCount = 0; - Map errors = new HashMap<>(); - - try { - for (Map.Entry entry : mappings.entrySet()) { - try { - String operationType = entry.getKey(); - String modelName = entry.getValue(); - - // 获取现有操作配置 - Optional configOpt = persistenceManager.getOperationConfig(operationType); - OperationConfigData config; - - if (configOpt.isPresent()) { - config = configOpt.get(); - config.setModelName(modelName); - } else { - // 创建新的操作配置 - config = new OperationConfigData(); - config.setModelName(modelName); - config.setEnabled(true); - config.setDescription("通过映射设置创建的配置"); - } - - persistenceManager.saveOperationConfig(operationType, config); - successCount++; - } catch (Exception e) { - failCount++; - errors.put(entry.getKey(), e.getMessage()); - } - } - - result.put("success", failCount == 0); - result.put("successCount", successCount); - result.put("failCount", failCount); - result.put("total", mappings.size()); - - if (failCount > 0) { - result.put("errors", errors); - result.put("message", "部分操作映射设置失败"); - } else { - result.put("message", "所有操作映射设置成功"); - } - - return failCount > 0 ? ResponseEntity.badRequest().body(result) : ResponseEntity.ok(result); - } catch (Exception e) { - log.error("设置操作映射失败: {}", e.getMessage()); - result.put("success", false); - result.put("message", "设置失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - /** - * 设置单个操作模型映射 - 改为POST请求体参数 - */ - @PostMapping("/mapping") - public ResponseEntity> setOperationMapping( - @RequestBody Map request) { - Map result = new HashMap<>(); - try { - String operationType = request.get("operationType"); - String modelName = request.get("modelName"); - - if (operationType == null || operationType.trim().isEmpty()) { - result.put("success", false); - result.put("message", "操作类型不能为空"); - return ResponseEntity.badRequest().body(result); - } - - if (modelName == null || modelName.trim().isEmpty()) { - result.put("success", false); - result.put("message", "模型名称不能为空"); - return ResponseEntity.badRequest().body(result); - } - - // 获取现有操作配置 - Optional configOpt = persistenceManager.getOperationConfig(operationType); - OperationConfigData config; - - if (configOpt.isPresent()) { - config = configOpt.get(); - config.setModelName(modelName); - } else { - // 创建新的操作配置 - config = new OperationConfigData(); - config.setModelName(modelName); - config.setEnabled(true); - config.setDescription("通过映射设置创建的配置"); - } - - persistenceManager.saveOperationConfig(operationType, config); - - result.put("success", true); - result.put("message", "操作映射设置成功"); - result.put("operationType", operationType); - result.put("modelName", modelName); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("设置操作映射失败: {}", e.getMessage()); - result.put("success", false); - result.put("message", "设置失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java deleted file mode 100644 index e443989..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java +++ /dev/null @@ -1,131 +0,0 @@ -package io.github.timemachinelab.sfchain.controller; - -import io.github.timemachinelab.sfchain.persistence.ModelConfigData; -import io.github.timemachinelab.sfchain.persistence.OperationConfigData; -import io.github.timemachinelab.sfchain.persistence.PersistenceManager; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.HashMap; -import java.util.Map; - -/** - * 描述: AI系统管理控制器 - * 提供系统概览、备份、刷新、重置等系统级功能 - * - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@RestController -@RequestMapping("/sf/api/system") -@RequiredArgsConstructor -public class AISystemController { - - private final PersistenceManager persistenceManager; - - /** - * 获取AI系统概览信息 - */ - @GetMapping("/overview") - public ResponseEntity> getSystemOverview() { - Map overview = new HashMap<>(); - try { - // 模型统计 - Map models = persistenceManager.getAllModelConfigs(); - overview.put("totalModels", models.size()); - overview.put("enabledModels", models.values().stream() - .mapToInt(m -> Boolean.TRUE.equals(m.getEnabled()) ? 1 : 0).sum()); - - // 操作统计 - Map configs = persistenceManager.getAllOperationConfigs(); - overview.put("totalOperations", configs.size()); - overview.put("configuredOperations", configs.values().stream() - .mapToInt(config -> config.getModelName() != null && !config.getModelName().isEmpty() ? 1 : 0).sum()); - - // 配置统计 - overview.put("totalConfigs", configs.size()); - - // 系统状态 - overview.put("systemStatus", "running"); - overview.put("lastUpdate", System.currentTimeMillis()); - - return ResponseEntity.ok(overview); - } catch (Exception e) { - log.error("获取系统概览失败: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "获取系统概览失败: " + e.getMessage())); - } - } - - /** - * 系统配置备份 - */ - @PostMapping("/backup") - public ResponseEntity> createBackup() { - Map result = new HashMap<>(); - try { - String backupName = "backup_" + System.currentTimeMillis(); - persistenceManager.createBackup(backupName); - - result.put("success", true); - result.put("message", "配置备份创建成功"); - result.put("backupName", backupName); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("创建备份失败: {}", e.getMessage()); - result.put("success", false); - result.put("message", "备份失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - /** - * 刷新系统配置 - */ - @PostMapping("/refresh") - public ResponseEntity> refreshSystem() { - Map result = new HashMap<>(); - try { - persistenceManager.flushConfigurations(); - - result.put("success", true); - result.put("message", "系统配置刷新成功"); - result.put("timestamp", System.currentTimeMillis()); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("刷新系统配置失败: {}", e.getMessage()); - result.put("success", false); - result.put("message", "刷新失败: " + e.getMessage()); - return ResponseEntity.badRequest().body(result); - } - } - - /** - * 重置系统配置 - */ - @PostMapping("/reset") - public ResponseEntity> resetSystem() { - Map result = new HashMap<>(); - try { - // 重新加载配置(替代原来的resetOperationMappingsToDefault方法) - persistenceManager.reloadConfigurations(); - - result.put("success", true); - result.put("message", "系统配置重置成功"); - result.put("timestamp", System.currentTimeMillis()); - - return ResponseEntity.ok(result); - } catch (Exception e) { - log.error("重置系统配置失败: {}", e.getMessage()); - result.put("success", false); - result.put("message", "重置失败: " + e.getMessage()); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); - } - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIModel.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIModel.java deleted file mode 100644 index 32cd7ba..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIModel.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.timemachinelab.sfchain.core; - -/** - * 描述: AI模型接口 - * @author suifeng - * 日期: 2025/8/11 - */ -public interface AIModel { - - /** - * 获取模型名称 - */ - String getName(); - - /** - * 获取模型描述 - */ - String description(); - - /** - * 生成文本响应 - * @param prompt 提示词 - * @return 生成的文本 - */ - String generate(String prompt); - - /** - * 生成指定类型的响应 - * @param prompt 提示词 - * @param responseType 响应类型 - * @return 生成的响应对象 - */ - T generate(String prompt, Class responseType); - - /** - * 检查模型是否可用 - * @return 是否可用 - */ - boolean isAvailable(); -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIOperationRegistry.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIOperationRegistry.java deleted file mode 100644 index 61af4c7..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIOperationRegistry.java +++ /dev/null @@ -1,152 +0,0 @@ -package io.github.timemachinelab.sfchain.core; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 描述: AI操作注册中心 - 新框架版本 - * 管理AI操作和模型的映射关系 - * - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@Component -@ConfigurationProperties(prefix = "ai.operations") -public class AIOperationRegistry { - - /** - * 操作到实例的映射 - */ - private final Map> operationMap = new ConcurrentHashMap<>(); - - /** - * 操作到模型的映射配置 - * -- GETTER -- - * 获取模型映射配置(用于配置文件绑定) - * -- SETTER -- - * 设置模型映射配置(用于配置文件绑定) - */ - @Setter - @Getter - private Map modelMapping = new ConcurrentHashMap<>(); - - /** - * 操作的默认配置 - * -- GETTER -- - * 获取操作配置(用于配置文件绑定) - * -- SETTER -- - * 设置操作配置(用于配置文件绑定) - */ - @Setter - @Getter - private Map configs = new ConcurrentHashMap<>(); - - @Resource - private ModelRegistry modelRegistry; - - /** - * 注册操作 - * - * @param operationType 操作类型 - * @param operation 操作实例 - */ - public void registerOperation(String operationType, BaseAIOperation operation) { - operationMap.put(operationType, operation); - log.info("注册AI操作: {} -> {}", operationType, operation.getClass().getSimpleName()); - } - - /** - * 获取操作实例 - * - * @param operationType 操作类型 - * @return 操作实例 - */ - public BaseAIOperation getOperation(String operationType) { - BaseAIOperation operation = operationMap.get(operationType); - if (operation == null) { - throw new IllegalArgumentException("未找到操作: " + operationType); - } - return operation; - } - - /** - * 获取操作对应的模型 - * - * @param operationType 操作类型 - * @return 模型名称 - */ - public String getModelForOperation(String operationType) { - // 优先从内存缓存获取 - return modelMapping.get(operationType); - } - - /** - * 设置操作的模型映射 - * - * @param operationType 操作类型 - * @param modelName 模型名称 - */ - public void setModelForOperation(String operationType, String modelName) { - // 验证模型是否存在 - AIModel model = modelRegistry.getModel(modelName); - if (model == null) { - throw new IllegalArgumentException("模型不存在: " + modelName); - } - - modelMapping.put(operationType, modelName); - log.info("设置操作模型映射: {} -> {}", operationType, modelName); - } - - /** - * 获取操作配置 - * - * @param operationType 操作类型 - * @return 操作配置 - */ - public OperationConfig getOperationConfig(String operationType) { - return configs.getOrDefault(operationType, new OperationConfig()); - } - - /** - * 获取所有已注册的操作 - * - * @return 操作类型列表 - */ - public List getAllOperations() { - return List.copyOf(operationMap.keySet()); - } - - /** - * 检查操作是否已注册 - * - * @param operationType 操作类型 - * @return 是否已注册 - */ - public boolean isOperationRegistered(String operationType) { - return operationMap.containsKey(operationType); - } - - /** - * 操作配置类 - */ - @Setter - @Getter - public static class OperationConfig { - private boolean enabled = true; - private int maxTokens = 4096; - private double temperature = 0.7; - private boolean requireJsonOutput = true; - private boolean supportThinking = false; - private int timeoutSeconds = 30; - private int retryCount = 2; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIPromptBuilder.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIPromptBuilder.java deleted file mode 100644 index c624b86..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIPromptBuilder.java +++ /dev/null @@ -1,155 +0,0 @@ -package io.github.timemachinelab.sfchain.core; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; - -/** - * AI提示词构建器 - * 用于构建结构化的AI提示词,支持与AIResponseParser配合使用 - * @author suifeng - * 日期: 2025/04/18 - */ -public class AIPromptBuilder { - - private final StringBuilder promptBuilder = new StringBuilder(); - private boolean hasJsonOutput = false; - - /** - * 创建一个提示词构建器 - * @param title 提示词标题 - */ - public AIPromptBuilder(String title) { - promptBuilder.append("# ").append(title).append("\n\n"); - } - - /** - * 添加角色描述 - * @param roleDescription 角色描述 - * @return 构建器实例 - */ - public AIPromptBuilder addRole(String roleDescription) { - // 获取当前北京时间 - LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); - String formattedTime = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")); - promptBuilder.append("# 当前的时间是北京时间:").append(formattedTime).append("\n"); - promptBuilder.append(roleDescription).append("\n\n"); - return this; - } - - /** - * 添加章节 - * @param sectionTitle 章节标题 - * @param content 章节内容 - * @return 构建器实例 - */ - public AIPromptBuilder addSection(String sectionTitle, String content) { - promptBuilder.append("## ").append(sectionTitle).append("\n"); - promptBuilder.append(content).append("\n\n"); - return this; - } - - /** - * 添加子章节 - * @param subSectionTitle 子章节标题 - * @param content 子章节内容 - * @return 构建器实例 - */ - public AIPromptBuilder addSubSection(String subSectionTitle, String content) { - promptBuilder.append("### ").append(subSectionTitle).append("\n"); - promptBuilder.append(content).append("\n\n"); - return this; - } - - /** - * 添加JSON输出格式 - * @param jsonExample JSON示例 - * @return 构建器实例 - */ - public AIPromptBuilder addJsonOutput(String jsonExample) { - promptBuilder.append("## 输出格式要求\n"); - promptBuilder.append("所有字符串中的引用部分只能用英文单引号 ' 包裹,内容中禁止出现英文双引号, 但是你要注意json格式规范是要加\"的,保证我能正常解析JSON 。\n"); - promptBuilder.append("```json\n"); - promptBuilder.append(jsonExample).append("\n"); - promptBuilder.append("```\n"); - promptBuilder.append("不需要给我任何额外的信息,我只需要最终的json结果,以保证生成和响应的速度\n"); - hasJsonOutput = true; - return this; - } - - /** - * 添加表格输出格式 - * @param headers 表头 - * @param example 示例行 - * @return 构建器实例 - */ - public AIPromptBuilder addTableOutput(String[] headers, String[] example) { - promptBuilder.append("## 输出格式要求\n"); - promptBuilder.append("你必须严格按照以下表格格式返回结果:\n"); - promptBuilder.append("```\n"); - - // 构建表头 - promptBuilder.append("| "); - for (String header : headers) { - promptBuilder.append(header).append(" | "); - } - promptBuilder.append("\n"); - - // 构建分隔行 - promptBuilder.append("| "); - for (int i = 0; i < headers.length; i++) { - promptBuilder.append("--- | "); - } - promptBuilder.append("\n"); - - // 构建示例行 - if (example != null) { - promptBuilder.append("| "); - for (String cell : example) { - promptBuilder.append(cell).append(" | "); - } - promptBuilder.append("\n"); - } - - promptBuilder.append("```\n\n"); - return this; - } - - /** - * 添加自定义输出格式 - * @param formatDescription 格式描述 - * @param example 示例 - * @param formatType 格式类型(用于AIResponseParser识别) - * @return 构建器实例 - */ - public AIPromptBuilder addCustomOutput(String formatDescription, String example, String formatType) { - promptBuilder.append("## 输出格式要求\n"); - promptBuilder.append(formatDescription).append("\n"); - promptBuilder.append("```").append(formatType).append("\n"); - promptBuilder.append(example).append("\n"); - promptBuilder.append("```\n\n"); - return this; - } - - /** - * 添加原始文本(不添加任何格式) - * @param text 原始文本 - * @return 构建器实例 - */ - public AIPromptBuilder addRawText(String text) { - promptBuilder.append(text).append("\n\n"); - return this; - } - - /** - * 构建提示词 - * @return 完整的提示词字符串 - */ - public String build() { - if (!hasJsonOutput) { - // 如果没有指定输出格式,添加默认提示 - promptBuilder.append("请确保你的回答简洁明了,直接提供所需信息。\n"); - } - return promptBuilder.toString(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIService.java deleted file mode 100644 index f914e0f..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/AIService.java +++ /dev/null @@ -1,457 +0,0 @@ -package io.github.timemachinelab.sfchain.core; - -import io.github.timemachinelab.sfchain.persistence.context.ChatContextService; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 描述: AI服务类 - 新框架版本 - * 统一管理AI操作的执行 - * - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@Service -public class AIService { - - @Resource - private AIOperationRegistry operationRegistry; - - @Resource - private ChatContextService chatContextService; - - /** - * 操作执行统计 - */ - private final Map executionStats = new ConcurrentHashMap<>(); - - /** - * 执行AI操作 - * - * @param operationType 操作类型 - * @param input 输入参数 - * @param 输入类型 - * @param 输出类型 - * @return 执行结果 - */ - @SuppressWarnings("unchecked") - public OUTPUT execute(String operationType, INPUT input) { - return execute(operationType, input, null, null); - } - - - @SuppressWarnings("unchecked") - public OUTPUT execute(String operationType, INPUT input, String sessionId) { - return execute(operationType, input,null, sessionId); - } - - /** - * 执行AI操作(带上下文支持) - * - * @param operationType 操作类型 - * @param input 输入参数 - * @param modelName 指定的模型名称 - * @param sessionId 会话ID,用于上下文管理 - * @param 输入类型 - * @param 输出类型 - * @return 执行结果 - */ - @SuppressWarnings("unchecked") - public OUTPUT execute(String operationType, INPUT input, String modelName, String sessionId) { - long startTime = System.currentTimeMillis(); - - try { - // 获取操作实例 - BaseAIOperation operation = (BaseAIOperation) operationRegistry.getOperation(operationType); - - // 检查操作是否启用 - if (!operation.isEnabled()) { - throw new IllegalStateException("操作已禁用: " + operationType); - } - - // 如果有会话ID,记录用户输入到上下文 - if (sessionId != null && input != null) { - chatContextService.addUserMessage(sessionId, input.toString()); - } - - // 执行操作 - OUTPUT result = operation.execute(input, modelName, sessionId); - - // 如果有会话ID,记录AI回复到上下文 - if (sessionId != null && result != null) { - chatContextService.addAiResponse(sessionId, result.toString()); - } - - // 记录执行统计 - recordExecution(operationType, true, System.currentTimeMillis() - startTime); - - log.debug("AI操作执行成功: {} - 耗时: {}ms", operationType, System.currentTimeMillis() - startTime); - - return result; - - } catch (Exception e) { - // 记录执行统计 - recordExecution(operationType, false, System.currentTimeMillis() - startTime); - - log.error("AI操作执行失败: {} - {}", operationType, e.getMessage(), e); - throw new RuntimeException("AI操作执行失败: " + e.getMessage(), e); - } - } - - /** - * 设置会话系统提示词 - * - * @param sessionId 会话ID - * @param systemPrompt 系统提示词 - */ - public void setSystemPrompt(String sessionId, String systemPrompt) { - chatContextService.setSystemPrompt(sessionId, systemPrompt); - log.info("设置会话系统提示词: sessionId={}", sessionId); - } - - /** - * 获取会话上下文 - * - * @param sessionId 会话ID - * @param includeSystemPrompt 是否包含系统提示词 - * @return 上下文字符串 - */ - public String getSessionContext(String sessionId, boolean includeSystemPrompt) { - return chatContextService.getContextAsString(sessionId, includeSystemPrompt); - } - - /** - * 清除会话对话历史 - * - * @param sessionId 会话ID - */ - public void clearSessionConversation(String sessionId) { - chatContextService.clearConversation(sessionId); - log.info("清除会话对话历史: sessionId={}", sessionId); - } - - /** - * 完全清除会话 - * - * @param sessionId 会话ID - */ - public void clearSession(String sessionId) { - chatContextService.clearSession(sessionId); - log.info("完全清除会话: sessionId={}", sessionId); - } - - /** - * 检查会话是否存在 - * - * @param sessionId 会话ID - * @return 是否存在 - */ - public boolean sessionExists(String sessionId) { - return chatContextService.sessionExists(sessionId); - } - - /** - * 异步执行AI操作 - * - * @param operationType 操作类型 - * @param input 输入参数 - * @param 输入类型 - * @param 输出类型 - * @return 异步执行结果 - */ - @SuppressWarnings("unchecked") - public CompletableFuture executeAsync(String operationType, INPUT input) { - return executeAsync(operationType, input, null, null); - } - - /** - * 异步执行AI操作(指定模型) - * - * @param operationType 操作类型 - * @param input 输入参数 - * @param modelName 指定的模型名称 - * @param 输入类型 - * @param 输出类型 - * @return 异步执行结果 - */ - @SuppressWarnings("unchecked") - public CompletableFuture executeAsync(String operationType, INPUT input, String modelName) { - return executeAsync(operationType, input, modelName, null); - } - - /** - * 异步执行AI操作(带上下文支持) - * - * @param operationType 操作类型 - * @param input 输入参数 - * @param modelName 指定的模型名称 - * @param sessionId 会话ID - * @param 输入类型 - * @param 输出类型 - * @return 异步执行结果 - */ - @SuppressWarnings("unchecked") - public CompletableFuture executeAsync(String operationType, INPUT input, String modelName, String sessionId) { - return CompletableFuture.supplyAsync(() -> execute(operationType, input, modelName, sessionId)); - } - - // ... existing code ... - - /** - * 批量执行AI操作 - * - * @param operationType 操作类型 - * @param inputs 输入参数列表 - * @param 输入类型 - * @param 输出类型 - * @return 执行结果列表 - */ - public List executeBatch(String operationType, List inputs) { - return executeBatch(operationType, inputs, null); - } - - /** - * 批量执行AI操作(指定模型) - * - * @param operationType 操作类型 - * @param inputs 输入参数列表 - * @param modelName 指定的模型名称 - * @param 输入类型 - * @param 输出类型 - * @return 执行结果列表 - */ - public List executeBatch(String operationType, List inputs, String modelName) { - return inputs.parallelStream() - .map(input -> this.execute(operationType, input, modelName)) - .toList(); - } - - /** - * 异步批量执行AI操作 - * - * @param operationType 操作类型 - * @param inputs 输入参数列表 - * @param 输入类型 - * @param 输出类型 - * @return 异步执行结果列表 - */ - public CompletableFuture> executeBatchAsync(String operationType, List inputs) { - return executeBatchAsync(operationType, inputs, null); - } - - /** - * 异步批量执行AI操作(指定模型) - * - * @param operationType 操作类型 - * @param inputs 输入参数列表 - * @param modelName 指定的模型名称 - * @param 输入类型 - * @param 输出类型 - * @return 异步执行结果列表 - */ - public CompletableFuture> executeBatchAsync(String operationType, List inputs, String modelName) { - List> futures = inputs.stream() - .map(input -> this.executeAsync(operationType, input, modelName)) - .toList(); - - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .thenApply(v -> futures.stream() - .map(CompletableFuture::join) - .toList()); - } - - /** - * 获取所有可用的操作 - * - * @return 操作类型列表 - */ - public List getAvailableOperations() { - return operationRegistry.getAllOperations(); - } - - /** - * 检查操作是否可用 - * - * @param operationType 操作类型 - * @return 是否可用 - */ - public boolean isOperationAvailable(String operationType) { - try { - BaseAIOperation operation = operationRegistry.getOperation(operationType); - return operation != null && operation.isEnabled(); - } catch (Exception e) { - return false; - } - } - - /** - * 获取操作信息 - * - * @param operationType 操作类型 - * @return 操作信息 - */ - public OperationInfo getOperationInfo(String operationType) { - BaseAIOperation operation = operationRegistry.getOperation(operationType); - if (operation == null) { - return null; - } - - return OperationInfo.builder() - .operationType(operationType) - .description(operation.getDescription()) - .inputType(operation.getInputType()) - .outputType(operation.getOutputType()) - .enabled(operation.isEnabled()) - .supportedModels(operation.getSupportedModels()) - .defaultModel(operation.getAnnotation().defaultModel()) - .build(); - } - - /** - * 获取操作执行统计 - * - * @param operationType 操作类型 - * @return 执行统计 - */ - public ExecutionStats getExecutionStats(String operationType) { - return executionStats.getOrDefault(operationType, new ExecutionStats()); - } - - /** - * 获取所有操作的执行统计 - * - * @return 执行统计映射 - */ - public Map getAllExecutionStats() { - return Map.copyOf(executionStats); - } - - /** - * 清空执行统计 - */ - public void clearExecutionStats() { - executionStats.clear(); - } - - /** - * 记录执行统计 - * - * @param operationType 操作类型 - * @param success 是否成功 - * @param duration 执行时长 - */ - private void recordExecution(String operationType, boolean success, long duration) { - executionStats.computeIfAbsent(operationType, k -> new ExecutionStats()) - .record(success, duration); - } - - // ... existing inner classes remain the same ... - - /** - * 操作信息类 - */ - @Getter - public static class OperationInfo { - // Getters - private String operationType; - private String description; - private Class inputType; - private Class outputType; - private boolean enabled; - private String[] supportedModels; - private String defaultModel; - - public static OperationInfoBuilder builder() { - return new OperationInfoBuilder(); - } - - public static class OperationInfoBuilder { - private OperationInfo info = new OperationInfo(); - - public OperationInfoBuilder operationType(String operationType) { - info.operationType = operationType; - return this; - } - - public OperationInfoBuilder description(String description) { - info.description = description; - return this; - } - - public OperationInfoBuilder inputType(Class inputType) { - info.inputType = inputType; - return this; - } - - public OperationInfoBuilder outputType(Class outputType) { - info.outputType = outputType; - return this; - } - - public OperationInfoBuilder enabled(boolean enabled) { - info.enabled = enabled; - return this; - } - - public OperationInfoBuilder supportedModels(String[] supportedModels) { - info.supportedModels = supportedModels; - return this; - } - - public OperationInfoBuilder defaultModel(String defaultModel) { - info.defaultModel = defaultModel; - return this; - } - - public OperationInfo build() { - return info; - } - } - } - - /** - * 执行统计类 - */ - public static class ExecutionStats { - private long totalExecutions = 0; - @Getter - private long successfulExecutions = 0; - @Getter - private long failedExecutions = 0; - @Getter - private long totalDuration = 0; - private long minDuration = Long.MAX_VALUE; - private long maxDuration = 0; - - public synchronized void record(boolean success, long duration) { - totalExecutions++; - if (success) { - successfulExecutions++; - } else { - failedExecutions++; - } - - totalDuration += duration; - minDuration = Math.min(minDuration, duration); - maxDuration = Math.max(maxDuration, duration); - } - - public double getSuccessRate() { - return totalExecutions > 0 ? (double) successfulExecutions / totalExecutions : 0.0; - } - - public double getAverageDuration() { - return totalExecutions > 0 ? (double) totalDuration / totalExecutions : 0.0; - } - - public long getMinDuration() { return minDuration == Long.MAX_VALUE ? 0 : minDuration; } - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/BaseAIOperation.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/BaseAIOperation.java deleted file mode 100644 index 1b6bc95..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/BaseAIOperation.java +++ /dev/null @@ -1,540 +0,0 @@ -package io.github.timemachinelab.sfchain.core; - -import com.alibaba.fastjson.JSONObject; -import io.github.timemachinelab.sfchain.annotation.AIOp; -import io.github.timemachinelab.sfchain.core.logging.AICallLog; -import io.github.timemachinelab.sfchain.core.logging.AICallLogManager; -import io.github.timemachinelab.sfchain.core.openai.OpenAICompatibleModel; -import io.github.timemachinelab.sfchain.persistence.context.ChatContextService; -import io.github.timemachinelab.sfchain.persistence.context.ChatMessage; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.annotation.PostConstruct; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; - -import static io.github.timemachinelab.sfchain.constants.AIOperationConstant.JSON_REPAIR_OP; - -/** - * 描述: AI操作抽象基类 - 新框架版本 - * 提供统一的AI操作接口和实现 - * - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -public abstract class BaseAIOperation { - - @Autowired - protected AIOperationRegistry operationRegistry; - - @Autowired - protected ModelRegistry modelRegistry; - - @Autowired - protected ObjectMapper objectMapper; - - @Autowired - protected ChatContextService chatContextService; - - /** - * 操作的注解信息 - * -- GETTER -- - * 获取注解信息 - * - * @return 注解信息 - */ - @Getter - private AIOp annotation; - - /** - * 输入类型 - * -- GETTER -- - * 获取输入类型 - * - * @return 输入类型 - - */ - @Getter - private Class inputType; - - /** - * 输出类型 - * -- GETTER -- - * 获取输出类型 - * - * @return 输出类型 - - */ - @Getter - private Class outputType; - - /** - * 初始化方法 - */ - @PostConstruct - @SuppressWarnings("unchecked") - public void init() { - // 获取注解信息 - this.annotation = this.getClass().getAnnotation(AIOp.class); - if (annotation == null) { - throw new IllegalStateException("AI操作类必须使用@AIOp注解: " + this.getClass().getSimpleName()); - } - - // 获取泛型类型 - Type superClass = this.getClass().getGenericSuperclass(); - if (superClass instanceof ParameterizedType parameterizedType) { - Type[] typeArguments = parameterizedType.getActualTypeArguments(); - if (typeArguments.length >= 2) { - this.inputType = (Class) typeArguments[0]; - this.outputType = (Class) typeArguments[1]; - } - } - - // 注册到操作注册中心 - operationRegistry.registerOperation(annotation.value(), this); - - // 如果注解中有默认模型且当前没有设置模型映射,则自动设置 - if (!annotation.defaultModel().isEmpty()) { - String currentModel = operationRegistry.getModelForOperation(annotation.value()); - if (currentModel == null) { - try { - // 验证模型是否存在 - if (modelRegistry.getModel(annotation.defaultModel()) != null) { - operationRegistry.setModelForOperation(annotation.value(), annotation.defaultModel()); - log.info("自动设置操作默认模型映射: {} -> {}", annotation.value(), annotation.defaultModel()); - } - } catch (Exception e) { - log.warn("无法设置默认模型映射 {} -> {}: {}", annotation.value(), annotation.defaultModel(), e.getMessage()); - } - } - } - - log.info("初始化AI操作: {} [{}] -> 输入类型: {}, 输出类型: {}", - annotation.value(), this.getClass().getSimpleName(), - inputType != null ? inputType.getSimpleName() : "Unknown", - outputType != null ? outputType.getSimpleName() : "Unknown"); - } - - /** - * 执行AI操作 - * - * @param input 输入参数 - * @return 输出结果 - */ - public OUTPUT execute(INPUT input) { - return execute(input, null, null); - } - - /** - * 执行AI操作(指定模型) - * - * @param input 输入参数 - * @param modelName 指定的模型名称,为null时使用默认模型 - * @return 输出结果 - */ - public OUTPUT execute(INPUT input, String modelName) { - return execute(input, modelName, null); - } - - /** - * 执行AI操作(带上下文支持) - * - * @param input 输入参数 - * @param modelName 指定的模型名称,为null时使用默认模型 - * @param sessionId 会话ID,用于上下文管理 - * @return 输出结果 - */ - // 在BaseAIOperation类中添加以下字段和方法 - - @Autowired - private AICallLogManager logManager; - - // 在execute方法中添加详细日志记录和上下文支持 - public OUTPUT execute(INPUT input, String modelName, String sessionId) { - String callId = UUID.randomUUID().toString(); - LocalDateTime startTime = LocalDateTime.now(); - long startMillis = System.currentTimeMillis(); - - AICallLog.AICallLogBuilder logBuilder = AICallLog.builder() - .callId(callId) - .operationType(annotation.value()) - .callTime(startTime) - .input(input) - .modelName(modelName) - .frequency(1) - .lastAccessTime(startTime); - - try { - // 获取模型 - AIModel model = getModel(modelName); - logBuilder.modelName(model.getName()); - - // 构建提示词(带上下文支持) - String prompt = buildPromptWithContext(input, sessionId); - logBuilder.prompt(prompt); - - // 获取操作配置 - AIOperationRegistry.OperationConfig config = operationRegistry.getOperationConfig(annotation.value()); - - // 合并配置 - Integer finalMaxTokens = config.getMaxTokens() > 0 ? Integer.valueOf(config.getMaxTokens()) : (annotation.defaultMaxTokens() > 0 ? annotation.defaultMaxTokens() : null); - Double finalTemperature = config.getTemperature() >= 0 ? Double.valueOf(config.getTemperature()) : (annotation.defaultTemperature() >= 0 ? annotation.defaultTemperature() : null); - Boolean finalJsonOutput = config.isRequireJsonOutput() || annotation.requireJsonOutput(); - boolean finalThinking = config.isSupportThinking() || annotation.supportThinking(); - - // 记录请求参数 - AICallLog.AIRequestParams requestParams = AICallLog.AIRequestParams.builder() - .maxTokens(finalMaxTokens) - .temperature(finalTemperature) - .jsonOutput(finalJsonOutput) - .thinking(finalThinking) - .build(); - logBuilder.requestParams(requestParams); - - // 调用AI模型 - String response; - if (model instanceof OpenAICompatibleModel openAIModel) { - if (finalThinking) { - response = openAIModel.generateWithThinking(prompt, finalMaxTokens, finalTemperature); - } else { - response = openAIModel.generate(prompt, finalMaxTokens, finalTemperature, finalJsonOutput); - } - } else { - response = model.generate(prompt); - } - - logBuilder.rawResponse(response); - - // 解析响应 - OUTPUT result = parseResponse(response, input); - - // 记录成功日志 - long duration = System.currentTimeMillis() - startMillis; - AICallLog log = logBuilder - .status(AICallLog.CallStatus.SUCCESS) - .duration(duration) - .output(result) - .build(); - - logManager.addLog(log); - - return result; - - } catch (Exception e) { - // 记录失败日志 - long duration = System.currentTimeMillis() - startMillis; - AICallLog callLog = logBuilder - .status(AICallLog.CallStatus.FAILED) - .duration(duration) - .errorMessage(e.getMessage()) - .build(); - - logManager.addLog(callLog); - - log.error("执行AI操作失败: {} - {}", annotation.value(), e.getMessage(), e); - throw new RuntimeException("AI操作执行失败: " + e.getMessage(), e); - } - } - - /** - * 构建带上下文的提示词 - * - * @param input 输入参数 - * @param sessionId 会话ID - * @return 完整的提示词 - */ - protected String buildPromptWithContext(INPUT input, String sessionId) { - // 构建基础提示词 - String basePrompt = buildPrompt(input); - - // 如果没有会话ID,直接返回基础提示词 - if (sessionId == null || !chatContextService.sessionExists(sessionId)) { - return basePrompt; - } - - // 获取上下文信息 - List contextMessages = chatContextService.getFullContext(sessionId); - if (contextMessages.isEmpty()) { - return basePrompt; - } - - // 构建带上下文的提示词 - StringBuilder contextPrompt = new StringBuilder(); - - // 添加系统提示词(如果存在) - String systemPrompt = chatContextService.getSystemPrompt(sessionId); - if (systemPrompt != null && !systemPrompt.trim().isEmpty()) { - contextPrompt.append("系统提示: ").append(systemPrompt).append("\n\n"); - } - - // 添加对话历史 - List conversationHistory = chatContextService.getConversationHistory(sessionId); - if (!conversationHistory.isEmpty()) { - contextPrompt.append("对话历史:\n"); - for (ChatMessage message : conversationHistory) { - String role = getRoleString(message.getType()); - contextPrompt.append(role).append(": ").append(message.getContent()).append("\n"); - } - contextPrompt.append("\n"); - } - - // 添加当前任务提示词 - contextPrompt.append("当前任务:\n").append(basePrompt); - - return contextPrompt.toString(); - } - - /** - * 获取角色字符串 - * - * @param type 消息类型 - * @return 角色字符串 - */ - private String getRoleString(ChatMessage.MessageType type) { - switch (type) { - case SYSTEM: - return "系统"; - case USER: - return "用户"; - case ASSISTANT: - return "助手"; - default: - return "未知"; - } - } - - /** - * 构建提示词(子类实现) - * - * @param input 输入参数 - * @return 提示词 - */ - protected abstract String buildPrompt(INPUT input); - - // ... existing code remains the same ... - - /** - * 解析AI响应(最终方法,子类不应重写) - * - * @param response AI响应 - * @param input 输入参数 - * @return 解析后的结果 - */ - protected final OUTPUT parseResponse(String response, INPUT input) { - if (outputType == String.class) { - return (OUTPUT) response; - } - - try { - // 1. 预处理响应(子类可自定义) - String processedResponse = preprocessResponse(response, input); - - // 2. 提取JSON内容 - String jsonContent = extractJsonFromResponse(processedResponse); - - // 3. 预处理JSON内容(子类可自定义) - String processedJson = preprocessJson(jsonContent, input); - - // 4. 解析为对象(子类可自定义解析逻辑) - return parseJsonToResult(processedJson, input, response); - - } catch (JsonProcessingException e) { - // 如果启用了自动JSON修复且需要JSON输出,尝试修复JSON - if (annotation.requireJsonOutput() && annotation.autoRepairJson()) { - log.warn("JSON解析失败,尝试自动修复: {}", e.getMessage()); - try { - // 通过操作注册中心获取JSON修复操作,避免循环依赖 - BaseAIOperation jsonRepairOp = operationRegistry.getOperation(JSON_REPAIR_OP); - if (jsonRepairOp != null) { - String jsonContent = extractJsonFromResponse(response); - @SuppressWarnings("unchecked") - BaseAIOperation repairOperation = (BaseAIOperation) jsonRepairOp; - JSONObject repairedJson = repairOperation.execute(jsonContent); - String repairedJsonStr = repairedJson.toJSONString(); - return parseJsonToResult(repairedJsonStr, input, response); - } - } catch (Exception repairException) { - log.error("JSON修复也失败: {}", repairException.getMessage(), repairException); - throw new RuntimeException("JSON解析和修复都失败: 原始错误=" + e.getMessage() + ", 修复错误=" + repairException.getMessage(), e); - } - } - - log.error("解析AI响应失败: {}", e.getMessage(), e); - throw new RuntimeException("解析AI响应失败: " + e.getMessage(), e); - } - } - - /** - * 预处理AI响应(子类可重写) - * 在提取JSON之前对原始响应进行处理 - * - * @param response 原始AI响应 - * @param input 输入参数 - * @return 处理后的响应 - */ - protected String preprocessResponse(String response, INPUT input) { - return response; - } - - /** - * 预处理JSON内容(子类可重写) - * 在JSON解析之前对提取的JSON字符串进行处理 - * - * @param jsonContent 提取的JSON字符串 - * @param input 输入参数 - * @return 处理后的JSON字符串 - */ - protected String preprocessJson(String jsonContent, INPUT input) { - return jsonContent; - } - - /** - * 将JSON字符串解析为结果对象(高级用法,一般用户无需重写) - * - * @param jsonContent JSON内容 - * @param input 输入参数 - * @param originalResponse 原始响应 - * @return 解析后的结果对象 - * @throws JsonProcessingException JSON解析异常 - */ - protected OUTPUT parseJsonToResult(String jsonContent, INPUT input, String originalResponse) throws JsonProcessingException { - // 先尝试用户自定义的解析方法 - OUTPUT customResult = parseResult(jsonContent, input); - if (customResult != null) { - return customResult; - } - - // 如果用户没有自定义解析,使用默认的JSON解析 - return objectMapper.readValue(jsonContent, outputType); - } - - /** - * 解析AI返回的JSON为最终结果(推荐用户重写此方法) - * 用户可以在此方法中处理AI返回的原始JSON,并转换为最终的结果对象 - * - * @param jsonContent AI返回的JSON字符串 - * @param input 输入参数 - * @return 最终结果对象,如果返回null则使用默认的JSON解析 - */ - protected OUTPUT parseResult(String jsonContent, INPUT input) { - return null; // 默认返回null,表示使用框架的默认JSON解析 - } - - /** - * 工具方法:将JSON字符串解析为指定类型的对象 - * - * @param jsonContent JSON字符串 - * @param clazz 目标类型 - * @param 泛型类型 - * @return 解析后的对象 - * @throws JsonProcessingException JSON解析异常 - */ - protected T parseJsonToObject(String jsonContent, Class clazz) throws JsonProcessingException { - return objectMapper.readValue(jsonContent, clazz); - } - - /** - * 从响应中提取JSON内容 - * - * @param response 原始响应 - * @return JSON字符串 - */ - protected String extractJsonFromResponse(String response) { - // 查找JSON代码块 - String jsonStart = "```json"; - String jsonEnd = "```"; - - int startIndex = response.indexOf(jsonStart); - if (startIndex != -1) { - startIndex += jsonStart.length(); - int endIndex = response.indexOf(jsonEnd, startIndex); - if (endIndex != -1) { - return response.substring(startIndex, endIndex).trim(); - } - } - - // 查找花括号包围的JSON - int braceStart = response.indexOf('{'); - int braceEnd = response.lastIndexOf('}'); - if (braceStart != -1 && braceEnd != -1 && braceEnd > braceStart) { - return response.substring(braceStart, braceEnd + 1); - } - - // 如果都找不到,返回原始响应 - return response; - } - - /** - * 获取模型实例 - * - * @param modelName 模型名称,为null时使用默认模型 - * @return 模型实例 - */ - private AIModel getModel(String modelName) { - if (modelName == null) { - // 使用注册中心配置的模型 - modelName = operationRegistry.getModelForOperation(annotation.value()); - } - - if (modelName == null) { - // 使用注解中的默认模型 - modelName = annotation.defaultModel(); - } - - if (modelName == null || modelName.isEmpty()) { - throw new IllegalStateException("未配置模型: " + annotation.value()); - } - - AIModel model = modelRegistry.getModel(modelName); - if (model == null) { - throw new IllegalArgumentException("模型不存在: " + modelName); - } - - return model; - } - - /** - * 获取操作类型 - * - * @return 操作类型 - */ - public String getOperationType() { - return annotation != null ? annotation.value() : null; - } - - /** - * 检查操作是否启用 - * - * @return 是否启用 - */ - public boolean isEnabled() { - AIOperationRegistry.OperationConfig config = operationRegistry.getOperationConfig(annotation.value()); - return config.isEnabled() && annotation.enabled(); - } - - /** - * 获取操作描述 - * - * @return 操作描述 - */ - public String getDescription() { - return annotation.description(); - } - - /** - * 获取支持的模型列表 - * - * @return 支持的模型列表 - */ - public String[] getSupportedModels() { - return annotation.supportedModels(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/ModelRegistry.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/ModelRegistry.java deleted file mode 100644 index cc7d3b7..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/ModelRegistry.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.timemachinelab.sfchain.core; - -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelFactory; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Set; - -/** - * 描述: AI模型注册中心 - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class ModelRegistry { - - private final OpenAIModelFactory modelFactory; - - /** - * 获取模型实例 - * @param modelName 模型名称 - * @return AI模型实例 - */ - public AIModel getModel(String modelName) { - try { - return modelFactory.createModel(modelName); - } catch (Exception e) { - log.error("获取模型失败: {} - {}", modelName, e.getMessage()); - throw new RuntimeException("无法获取模型: " + modelName, e); - } - } - - /** - * 检查模型是否已注册 - * @param modelName 模型名称 - * @return 是否已注册 - */ - public boolean isModelRegistered(String modelName) { - return modelFactory.isModelRegistered(modelName); - } - - /** - * 获取所有已注册的模型名称 - * @return 模型名称集合 - */ - public Set getRegisteredModelNames() { - return modelFactory.getRegisteredModelNames(); - } - - /** - * 获取可用的模型列表 - * @return 可用模型列表 - */ - public List getAvailableModels() { - return getRegisteredModelNames().stream() - .filter(modelName -> { - try { - AIModel model = getModel(modelName); - return model.isAvailable(); - } catch (Exception e) { - log.warn("检查模型可用性失败: {} - {}", modelName, e.getMessage()); - return false; - } - }) - .toList(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLog.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLog.java deleted file mode 100644 index f956bad..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLog.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.github.timemachinelab.sfchain.core.logging; - -import lombok.Builder; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.Map; - -/** - * AI调用日志实体 - */ -@Data -@Builder -public class AICallLog { - - /** 调用ID */ - private String callId; - - /** 操作类型 */ - private String operationType; - - /** 模型名称 */ - private String modelName; - - /** 调用时间 */ - private LocalDateTime callTime; - - /** 执行耗时(毫秒) */ - private long duration; - - /** 调用状态 */ - private CallStatus status; - - /** 原始输入参数 */ - private Object input; - - /** 构建的提示词 */ - private String prompt; - - /** AI请求参数 */ - private AIRequestParams requestParams; - - /** 模型原始返回结果 */ - private String rawResponse; - - /** 最终输出结果 */ - private Object output; - - /** 错误信息(如果有) */ - private String errorMessage; - - /** 调用频次(用于LFU) */ - private int frequency; - - /** 最后访问时间(用于LFU) */ - private LocalDateTime lastAccessTime; - - public enum CallStatus { - SUCCESS, FAILED, TIMEOUT - } - - @Data - @Builder - public static class AIRequestParams { - private Integer maxTokens; - private Double temperature; - private Boolean jsonOutput; - private Boolean thinking; - private Map additionalParams; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogAspect.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogAspect.java deleted file mode 100644 index b5928fe..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogAspect.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.github.timemachinelab.sfchain.core.logging; - -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.util.UUID; - -/** - * AI调用日志记录切面 - */ -@Slf4j -@Aspect -@Component -public class AICallLogAspect { - - @Autowired - private AICallLogManager logManager; - - /** - * 拦截BaseAIOperation的execute方法 - */ - @Around("execution(* io.github.timemachinelab.sfchain.core.BaseAIOperation.execute(..))") - public Object logAIOperation(ProceedingJoinPoint joinPoint) throws Throwable { - String callId = UUID.randomUUID().toString(); - LocalDateTime startTime = LocalDateTime.now(); - long startMillis = System.currentTimeMillis(); - - AICallLog.AICallLogBuilder logBuilder = AICallLog.builder() - .callId(callId) - .callTime(startTime) - .frequency(1) - .lastAccessTime(startTime); - - try { - // 获取输入参数 - Object[] args = joinPoint.getArgs(); - Object input = args.length > 0 ? args[0] : null; - String modelName = args.length > 1 ? (String) args[1] : null; - - logBuilder.input(input).modelName(modelName); - - // 执行原方法 - Object result = joinPoint.proceed(); - - // 记录成功日志 - long duration = System.currentTimeMillis() - startMillis; - AICallLog log = logBuilder - .status(AICallLog.CallStatus.SUCCESS) - .duration(duration) - .output(result) - .build(); - - logManager.addLog(log); - - return result; - - } catch (Exception e) { - // 记录失败日志 - long duration = System.currentTimeMillis() - startMillis; - AICallLog log = logBuilder - .status(AICallLog.CallStatus.FAILED) - .duration(duration) - .errorMessage(e.getMessage()) - .build(); - - logManager.addLog(log); - - throw e; - } - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogManager.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogManager.java deleted file mode 100644 index 5391b49..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogManager.java +++ /dev/null @@ -1,238 +0,0 @@ -package io.github.timemachinelab.sfchain.core.logging; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; - -/** - * AI调用日志管理器 - 基于LFU算法 - */ -@Slf4j -@Component -public class AICallLogManager { - - private static final int MAX_CAPACITY = 100; - - /** 日志存储 */ - private final Map logStorage = new ConcurrentHashMap<>(); - - /** 频次计数器 */ - private final Map frequencyMap = new ConcurrentHashMap<>(); - - /** 频次分组 - 频次 -> 调用ID集合 */ - private final Map> frequencyGroups = new ConcurrentHashMap<>(); - - /** 最小频次 */ - private volatile int minFrequency = 1; - - /** 读写锁 */ - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - /** - * 添加调用日志 - */ - public void addLog(AICallLog callLog) { - lock.writeLock().lock(); - try { - String callId = callLog.getCallId(); - - // 如果已达到容量上限,移除最少使用的日志 - if (logStorage.size() >= MAX_CAPACITY && !logStorage.containsKey(callId)) { - evictLFU(); - } - - // 添加或更新日志 - logStorage.put(callId, callLog); - updateFrequency(callId); - - log.debug("添加AI调用日志: {}", callId); - - } finally { - lock.writeLock().unlock(); - } - } - - /** - * 获取完整调用日志(包含所有详细信息) - */ - public AICallLog getFullLog(String callId) { - lock.readLock().lock(); - try { - AICallLog callLog = logStorage.get(callId); - if (callLog != null) { - // 更新访问时间和频次 - callLog.setLastAccessTime(LocalDateTime.now()); - updateFrequency(callId); - } - return callLog; - } finally { - lock.readLock().unlock(); - } - } - - /** - * 获取所有日志摘要(按时间倒序) - */ - public List getAllLogSummaries() { - lock.readLock().lock(); - try { - return logStorage.values().stream() - .sorted((a, b) -> b.getCallTime().compareTo(a.getCallTime())) - .map(AICallLogSummary::fromFullLog) - .collect(Collectors.toList()); - } finally { - lock.readLock().unlock(); - } - } - - /** - * 根据操作类型获取日志摘要 - */ - public List getLogSummariesByOperation(String operationType) { - lock.readLock().lock(); - try { - return logStorage.values().stream() - .filter(log -> operationType.equals(log.getOperationType())) - .sorted((a, b) -> b.getCallTime().compareTo(a.getCallTime())) - .map(AICallLogSummary::fromFullLog) - .collect(Collectors.toList()); - } finally { - lock.readLock().unlock(); - } - } - - /** - * 根据模型名称获取日志摘要 - */ - public List getLogSummariesByModel(String modelName) { - lock.readLock().lock(); - try { - return logStorage.values().stream() - .filter(log -> modelName.equals(log.getModelName())) - .sorted((a, b) -> b.getCallTime().compareTo(a.getCallTime())) - .map(AICallLogSummary::fromFullLog) - .collect(Collectors.toList()); - } finally { - lock.readLock().unlock(); - } - } - - /** - * 获取统计信息 - */ - public LogStatistics getStatistics() { - lock.readLock().lock(); - try { - long totalCalls = logStorage.size(); - long successCalls = logStorage.values().stream() - .mapToLong(log -> log.getStatus() == AICallLog.CallStatus.SUCCESS ? 1 : 0) - .sum(); - - double avgDuration = logStorage.values().stream() - .mapToLong(AICallLog::getDuration) - .average() - .orElse(0.0); - - Map operationCounts = logStorage.values().stream() - .collect(Collectors.groupingBy( - AICallLog::getOperationType, - Collectors.counting() - )); - - Map modelCounts = logStorage.values().stream() - .collect(Collectors.groupingBy( - AICallLog::getModelName, - Collectors.counting() - )); - - return LogStatistics.builder() - .totalCalls(totalCalls) - .successCalls(successCalls) - .successRate(totalCalls > 0 ? (double) successCalls / totalCalls : 0.0) - .averageDuration(avgDuration) - .operationCounts(operationCounts) - .modelCounts(modelCounts) - .build(); - } finally { - lock.readLock().unlock(); - } - } - - /** - * 清空所有日志 - */ - public void clearLogs() { - lock.writeLock().lock(); - try { - logStorage.clear(); - frequencyMap.clear(); - frequencyGroups.clear(); - minFrequency = 1; - log.info("已清空所有AI调用日志"); - } finally { - lock.writeLock().unlock(); - } - } - - /** - * 更新频次 - */ - private void updateFrequency(String callId) { - int oldFreq = frequencyMap.getOrDefault(callId, 0); - int newFreq = oldFreq + 1; - - frequencyMap.put(callId, newFreq); - - // 从旧频次组中移除 - if (oldFreq > 0) { - frequencyGroups.get(oldFreq).remove(callId); - if (frequencyGroups.get(oldFreq).isEmpty() && oldFreq == minFrequency) { - minFrequency++; - } - } - - // 添加到新频次组 - frequencyGroups.computeIfAbsent(newFreq, k -> new LinkedHashSet<>()).add(callId); - - // 更新最小频次 - if (newFreq < minFrequency) { - minFrequency = newFreq; - } - } - - /** - * 淘汰最少使用的日志 - */ - private void evictLFU() { - // 找到最小频次组中最早的元素 - LinkedHashSet minFreqGroup = frequencyGroups.get(minFrequency); - if (minFreqGroup != null && !minFreqGroup.isEmpty()) { - String evictCallId = minFreqGroup.iterator().next(); - - // 移除日志 - logStorage.remove(evictCallId); - frequencyMap.remove(evictCallId); - minFreqGroup.remove(evictCallId); - - log.debug("淘汰AI调用日志: {}", evictCallId); - } - } - - @lombok.Data - @lombok.Builder - public static class LogStatistics { - private long totalCalls; - private long successCalls; - private double successRate; - private double averageDuration; - private Map operationCounts; - private Map modelCounts; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogSummary.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogSummary.java deleted file mode 100644 index 10e9890..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/logging/AICallLogSummary.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.github.timemachinelab.sfchain.core.logging; - -import lombok.Builder; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * AI调用日志摘要 - 用于列表展示,不包含大数据量字段 - */ -@Data -@Builder -public class AICallLogSummary { - - /** 调用ID */ - private String callId; - - /** 操作类型 */ - private String operationType; - - /** 模型名称 */ - private String modelName; - - /** 调用时间 */ - private LocalDateTime callTime; - - /** 执行耗时(毫秒) */ - private long duration; - - /** 调用状态 */ - private AICallLog.CallStatus status; - - /** 错误信息(如果有) */ - private String errorMessage; - - /** 调用频次(用于LFU) */ - private int frequency; - - /** 最后访问时间(用于LFU) */ - private LocalDateTime lastAccessTime; - - /** AI请求参数摘要 */ - private RequestParamsSummary requestParams; - - @Data - @Builder - public static class RequestParamsSummary { - private Integer maxTokens; - private Double temperature; - private Boolean jsonOutput; - private Boolean thinking; - } - - /** - * 从完整日志创建摘要 - */ - public static AICallLogSummary fromFullLog(AICallLog fullLog) { - RequestParamsSummary paramsSummary = null; - if (fullLog.getRequestParams() != null) { - paramsSummary = RequestParamsSummary.builder() - .maxTokens(fullLog.getRequestParams().getMaxTokens()) - .temperature(fullLog.getRequestParams().getTemperature()) - .jsonOutput(fullLog.getRequestParams().getJsonOutput()) - .thinking(fullLog.getRequestParams().getThinking()) - .build(); - } - - return AICallLogSummary.builder() - .callId(fullLog.getCallId()) - .operationType(fullLog.getOperationType()) - .modelName(fullLog.getModelName()) - .callTime(fullLog.getCallTime()) - .duration(fullLog.getDuration()) - .status(fullLog.getStatus()) - .errorMessage(fullLog.getErrorMessage()) - .frequency(fullLog.getFrequency()) - .lastAccessTime(fullLog.getLastAccessTime()) - .requestParams(paramsSummary) - .build(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAICompatibleModel.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAICompatibleModel.java deleted file mode 100644 index 3337b02..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAICompatibleModel.java +++ /dev/null @@ -1,155 +0,0 @@ -package io.github.timemachinelab.sfchain.core.openai; - -import com.alibaba.fastjson2.JSON; -import io.github.timemachinelab.sfchain.core.AIModel; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.util.List; -import java.util.Map; - -/** - * 描述: OpenAI兼容的通用模型实现 - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -public class OpenAICompatibleModel implements AIModel { - - /** - * -- GETTER -- - * 获取模型配置 - */ - @Getter - private final OpenAIModelConfig config; - private final OpenAIHttpClient httpClient; - - public OpenAICompatibleModel(OpenAIModelConfig config) { - if (!config.isValid()) { - throw new IllegalArgumentException("模型配置无效: " + config); - } - - this.config = config; - this.httpClient = new OpenAIHttpClient( - config.getBaseUrl(), - config.getApiKey(), - config.getAdditionalHeaders() - ); - - log.info("初始化OpenAI兼容模型: {} ({})", config.getModelName(), config.getProvider()); - } - - @Override - public String getName() { - return config.getModelName(); - } - - @Override - public String description() { - return config.getDescription() != null ? config.getDescription() : - String.format("%s模型 (提供商: %s)", config.getModelName(), config.getProvider()); - } - - @Override - public String generate(String prompt) { - return generate(prompt, null, null, null); - } - - @Override - public T generate(String prompt, Class responseType) { - String result = generate(prompt); - if (responseType == String.class) { - return responseType.cast(result); - } - - try { - return JSON.parseObject(result, responseType); - } catch (Exception e) { - log.error("解析响应为{}类型失败: {}", responseType.getSimpleName(), e.getMessage()); - throw new RuntimeException("响应解析失败: " + e.getMessage(), e); - } - } - - /** - * 生成响应 - 支持自定义参数 - */ - public String generate(String prompt, Integer maxTokens, Double temperature, Boolean jsonOutput) { - try { - OpenAIRequest request = buildRequest(prompt, maxTokens, temperature, jsonOutput); - OpenAIResponse response = httpClient.chatCompletion(request); - return httpClient.extractContent(response); - } catch (Exception e) { - log.error("模型{}生成失败", config.getModelName(), e); - throw new RuntimeException("模型生成失败: " + e.getMessage(), e); - } - } - - /** - * 生成响应 - 支持思考模式 - */ - public String generateWithThinking(String prompt, Integer maxTokens, Double temperature) { - if (!Boolean.TRUE.equals(config.getSupportThinking())) { - log.warn("模型{}不支持思考模式,使用普通模式", config.getModelName()); - return generate(prompt, maxTokens, temperature, null); - } - - try { - OpenAIRequest request = buildRequestWithThinking(prompt, maxTokens, temperature); - OpenAIResponse response = httpClient.chatCompletion(request); - return httpClient.extractContent(response); - } catch (Exception e) { - log.error("模型{}思考模式生成失败", config.getModelName(), e); - throw new RuntimeException("思考模式生成失败: " + e.getMessage(), e); - } - } - - /** - * 构建请求对象 - */ - private OpenAIRequest buildRequest(String prompt, Integer maxTokens, Double temperature, Boolean jsonOutput) { - var builder = OpenAIRequest.builder() - .model(config.getModelName()) - .messages(List.of( - OpenAIRequest.Message.builder() - .role("user") - .content(prompt) - .build() - )) - .max_tokens(maxTokens != null ? maxTokens : config.getDefaultMaxTokens()) - .temperature(temperature != null ? temperature : config.getDefaultTemperature()) - .stream(false); - - // 设置JSON输出格式 - if (Boolean.TRUE.equals(jsonOutput) && Boolean.TRUE.equals(config.getSupportJsonOutput())) { - builder.response_format(Map.of("type", "json_object")); - } - - return builder.build(); - } - - /** - * 构建带思考模式的请求对象 - */ - private OpenAIRequest buildRequestWithThinking(String prompt, Integer maxTokens, Double temperature) { - return OpenAIRequest.builder() - .model(config.getModelName()) - .messages(List.of( - OpenAIRequest.Message.builder() - .role("user") - .content(prompt) - .build() - )) - .max_tokens(maxTokens != null ? maxTokens : config.getDefaultMaxTokens()) - .temperature(temperature != null ? temperature : config.getDefaultTemperature()) - .stream(false) - .enable_thinking(true) - .build(); - } - - /** - * 检查模型是否可用 - */ - public boolean isAvailable() { - return Boolean.TRUE.equals(config.getEnabled()) && config.isValid(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIHttpClient.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIHttpClient.java deleted file mode 100644 index be43113..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIHttpClient.java +++ /dev/null @@ -1,143 +0,0 @@ -package io.github.timemachinelab.sfchain.core.openai; - -import com.alibaba.fastjson2.JSON; -import lombok.extern.slf4j.Slf4j; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -/** - * 描述: OpenAI兼容的HTTP客户端 - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -public class OpenAIHttpClient { - - private final String baseUrl; - private final String apiKey; - private final Map defaultHeaders; - - public OpenAIHttpClient(String baseUrl, String apiKey) { - this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; - this.apiKey = apiKey; - this.defaultHeaders = Map.of( - "Content-Type", "application/json", - "Authorization", "Bearer " + apiKey - ); - } - - public OpenAIHttpClient(String baseUrl, String apiKey, Map additionalHeaders) { - this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; - this.apiKey = apiKey; - this.defaultHeaders = new HashMap<>(); - this.defaultHeaders.put("Content-Type", "application/json"); - this.defaultHeaders.put("Authorization", "Bearer " + apiKey); - if (additionalHeaders != null) { - this.defaultHeaders.putAll(additionalHeaders); - } - } - - /** - * 发送聊天完成请求 - */ - public OpenAIResponse chatCompletion(OpenAIRequest request) { - try { - // 智能构建endpoint,避免重复的/v1路径 - String endpoint; - if (baseUrl.endsWith("/v1") || baseUrl.contains("/v1/")) { - // baseUrl已包含v1路径,直接添加chat/completions - endpoint = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "chat/completions"; - } else { - // baseUrl不包含v1路径,添加完整路径 - endpoint = baseUrl + "/v1/chat/completions"; - } - String requestBody = JSON.toJSONString(request); - - log.debug("发送请求到: {}", endpoint); - log.debug("请求体: {}", requestBody); - log.info("构建的API端点: {}", endpoint); - - HttpURLConnection connection = createConnection(endpoint); - - // 发送请求体 - try (OutputStream os = connection.getOutputStream()) { - byte[] input = requestBody.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); - } - - // 读取响应 - StringBuilder response = new StringBuilder(); - int responseCode = connection.getResponseCode(); - - if (responseCode == HttpURLConnection.HTTP_OK) { - try (BufferedReader br = new BufferedReader( - new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - } - } else { - // 读取错误响应 - try (BufferedReader br = new BufferedReader( - new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = br.readLine()) != null) { - response.append(line); - } - } - throw new RuntimeException("HTTP请求失败,状态码: " + responseCode + ", 响应: " + response.toString()); - } - - String responseBody = response.toString(); - log.debug("响应体: {}", responseBody); - - return JSON.parseObject(responseBody, OpenAIResponse.class); - - } catch (Exception e) { - log.error("OpenAI API调用失败", e); - throw new RuntimeException("OpenAI API调用失败: " + e.getMessage(), e); - } - } - - /** - * 创建HTTP连接 - */ - private HttpURLConnection createConnection(String endpoint) throws Exception { - URL url = new URL(endpoint); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setConnectTimeout(30000); // 30秒连接超时 - connection.setReadTimeout(120000); // 120秒读取超时 - - // 设置请求头 - defaultHeaders.forEach(connection::setRequestProperty); - - return connection; - } - - /** - * 提取响应内容 - */ - public String extractContent(OpenAIResponse response) { - if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) { - return ""; - } - - OpenAIResponse.Choice choice = response.getChoices().get(0); - if (choice.getMessage() != null && choice.getMessage().getContent() != null) { - return choice.getMessage().getContent(); - } - - return ""; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelConfig.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelConfig.java deleted file mode 100644 index f7b2a42..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelConfig.java +++ /dev/null @@ -1,120 +0,0 @@ -package io.github.timemachinelab.sfchain.core.openai; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.HashMap; -import java.util.Map; - -/** - * 描述: OpenAI兼容的模型配置 - * @author suifeng - * 日期: 2025/8/11 - */ -@Data -@Builder(toBuilder = true) -@AllArgsConstructor -@NoArgsConstructor -public class OpenAIModelConfig { - - /** - * 模型名称 - */ - private String modelName; - - /** - * API基础URL - */ - private String baseUrl; - - /** - * API密钥 - */ - private String apiKey; - - /** - * 默认最大token数 - */ - private Integer defaultMaxTokens; - - /** - * 默认温度参数 - */ - private Double defaultTemperature; - - /** - * 是否支持流式输出 - */ - private Boolean supportStream; - - /** - * 是否支持JSON格式输出 - */ - private Boolean supportJsonOutput; - - /** - * 是否支持思考模式 - */ - private Boolean supportThinking; - - /** - * 额外的HTTP请求头 - */ - private Map additionalHeaders; - - /** - * 模型描述 - */ - private String description; - - /** - * 模型提供商 - */ - private String provider; - - /** - * 是否启用 - */ - private Boolean enabled; - - /** - * 获取额外请求头,如果为null则返回空Map - */ - public Map getAdditionalHeaders() { - return additionalHeaders != null ? additionalHeaders : new HashMap<>(); - } - - /** - * 添加额外请求头 - */ - public void addHeader(String key, String value) { - if (additionalHeaders == null) { - additionalHeaders = new HashMap<>(); - } - additionalHeaders.put(key, value); - } - - /** - * 检查配置是否有效 - */ - public boolean isValid() { - return modelName != null && !modelName.trim().isEmpty() && - baseUrl != null && !baseUrl.trim().isEmpty() && - apiKey != null && !apiKey.trim().isEmpty(); - } - - /** - * 获取默认配置的构建器 - */ - public static OpenAIModelConfigBuilder defaultConfig() { - return OpenAIModelConfig.builder() - .defaultMaxTokens(4096) - .defaultTemperature(0.7) - .supportStream(false) - .supportJsonOutput(false) - .supportThinking(false) - .enabled(true); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelFactory.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelFactory.java deleted file mode 100644 index 1f9c3ad..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIModelFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.github.timemachinelab.sfchain.core.openai; - -import io.github.timemachinelab.sfchain.core.AIModel; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 描述: OpenAI兼容模型工厂 - * @author suifeng - * 日期: 2025/8/11 - */ -@Slf4j -public class OpenAIModelFactory { - - private final Map modelConfigs = new ConcurrentHashMap<>(); - private final Map modelInstances = new ConcurrentHashMap<>(); - - /** - * 注册模型配置 - */ - public void registerModel(OpenAIModelConfig config) { - if (!config.isValid()) { - throw new IllegalArgumentException("无效的模型配置: " + config.getModelName()); - } - - modelConfigs.put(config.getModelName(), config); - log.info("注册模型配置: {} ({})", config.getModelName(), config.getProvider()); - } - - /** - * 创建模型实例 - */ - public AIModel createModel(String modelName) { - return modelInstances.computeIfAbsent(modelName, name -> { - OpenAIModelConfig config = modelConfigs.get(name); - if (config == null) { - throw new IllegalArgumentException("未找到模型配置: " + name); - } - - if (!Boolean.TRUE.equals(config.getEnabled())) { - throw new IllegalStateException("模型已禁用: " + name); - } - - return new OpenAICompatibleModel(config); - }); - } - - /** - * 获取所有已注册的模型名称 - */ - public java.util.Set getRegisteredModelNames() { - return modelConfigs.keySet(); - } - - /** - * 获取模型配置 - */ - public OpenAIModelConfig getModelConfig(String modelName) { - return modelConfigs.get(modelName); - } - - /** - * 检查模型是否已注册 - */ - public boolean isModelRegistered(String modelName) { - return modelConfigs.containsKey(modelName); - } - - /** - * 移除模型 - */ - public void removeModel(String modelName) { - modelConfigs.remove(modelName); - modelInstances.remove(modelName); - log.info("移除模型: {}", modelName); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIRequest.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIRequest.java deleted file mode 100644 index 181cdcf..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIRequest.java +++ /dev/null @@ -1,102 +0,0 @@ -package io.github.timemachinelab.sfchain.core.openai; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; -import java.util.Map; - -/** - * 描述: OpenAI兼容的请求体 - * @author suifeng - * 日期: 2025/8/11 - */ -@Data -@Builder(toBuilder = true) -@AllArgsConstructor -@NoArgsConstructor -public class OpenAIRequest { - - /** - * 模型名称 - */ - private String model; - - /** - * 消息列表 - */ - private List messages; - - /** - * 最大token数 - */ - private Integer max_tokens; - - /** - * 温度参数 (0.0-2.0) - */ - private Double temperature; - - /** - * 是否流式输出 - */ - private Boolean stream; - - /** - * 响应格式 - */ - private Map response_format; - - /** - * 是否启用思考模式 (部分模型支持) - */ - private Boolean enable_thinking; - - /** - * top_p参数 - */ - private Double top_p; - - /** - * 频率惩罚 - */ - private Double frequency_penalty; - - /** - * 存在惩罚 - */ - private Double presence_penalty; - - /** - * 停止词 - */ - private List stop; - - /** - * 用户标识 - */ - private String user; - - @Data - @Builder(toBuilder = true) - @AllArgsConstructor - @NoArgsConstructor - public static class Message { - /** - * 角色: system, user, assistant - */ - private String role; - - /** - * 消息内容 - */ - private String content; - - /** - * 消息名称 (可选) - */ - private String name; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIResponse.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIResponse.java deleted file mode 100644 index 83a80da..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/core/openai/OpenAIResponse.java +++ /dev/null @@ -1,108 +0,0 @@ -package io.github.timemachinelab.sfchain.core.openai; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -/** - * 描述: OpenAI兼容的响应体 - * @author suifeng - * 日期: 2025/8/11 - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class OpenAIResponse { - - /** - * 响应ID - */ - private String id; - - /** - * 对象类型 - */ - private String object; - - /** - * 创建时间戳 - */ - private Long created; - - /** - * 模型名称 - */ - private String model; - - /** - * 选择列表 - */ - private List choices; - - /** - * 使用情况 - */ - private Usage usage; - - /** - * 系统指纹 - */ - private String system_fingerprint; - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class Choice { - /** - * 选择索引 - */ - private Integer index; - - /** - * 消息内容 - */ - private OpenAIRequest.Message message; - - /** - * 完成原因 - */ - private String finish_reason; - - /** - * logprobs (可选) - */ - private Object logprobs; - } - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class Usage { - /** - * 提示token数 - */ - private Integer prompt_tokens; - - /** - * 完成token数 - */ - private Integer completion_tokens; - - /** - * 总token数 - */ - private Integer total_tokens; - - /** - * 提示token详情 (可选) - */ - private Object prompt_tokens_details; - - /** - * 完成token详情 (可选) - */ - private Object completion_tokens_details; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/operations/JSONRepairOperation.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/operations/JSONRepairOperation.java deleted file mode 100644 index ce2dd49..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/operations/JSONRepairOperation.java +++ /dev/null @@ -1,140 +0,0 @@ -package io.github.timemachinelab.sfchain.operations; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import io.github.timemachinelab.sfchain.annotation.AIOp; -import io.github.timemachinelab.sfchain.core.BaseAIOperation; -import org.springframework.stereotype.Component; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static io.github.timemachinelab.sfchain.constants.AIOperationConstant.JSON_REPAIR_OP; - - -/** - * 描述: JSON修复操作 - * 专门用于修复AI返回的格式错误的JSON字符串 - * @author suifeng - * 日期: 2025/8/11 - */ -@AIOp( - value = JSON_REPAIR_OP, - description = "修复格式错误的JSON字符串", - autoRepairJson = false -) -@Component -public class JSONRepairOperation extends BaseAIOperation { - - /** - * 当前输入的JSON字符串 - */ - private String currentInput; - - @Override - public String buildPrompt(String brokenJson) { - this.currentInput = brokenJson; // 保存当前输入 - - // 如果输入已经是有效JSON,尝试直接解析 - if (isValidJson(brokenJson)) { - return ""; // 返回空字符串表示不需要AI修复 - } - - return String.format(""" - 你是一位专业的JSON格式修复专家,需要将格式错误的JSON字符串修复为有效的JSON格式。 - - ## 任务描述 - 我将提供一个可能包含格式错误的JSON字符串。你的任务是: - 1. 识别并修复所有格式错误,包括但不限于: - - 缺失或多余的引号、逗号、括号 - - 非法的转义字符 - - 重复的键 - - 不符合JSON规范的值格式 - 2. 保留原始JSON的所有数据和结构 - 3. 返回修复后的有效JSON字符串 - - ## 需要修复的JSON - ``` - %s - ``` - - ## 输出要求 - 1. 只返回修复后的JSON字符串,不要有任何解释或额外文字 - 2. 确保输出是有效的JSON格式 - 3. 保持原始数据的完整性,除非格式错误导致数据冗余 - 4. 如果某部分无法修复,使用最合理的猜测进行修复 - - ## 注意事项 - - 不要给我任何多余的东西,只需要正确的JSON对象 - - 不要添加原始JSON中不存在的键或值 - - 如果原始字符串中包含多个JSON对象,只修复第一个完整的JSON对象 - - 确保所有字符串值使用双引号包围 - - 确保数字、布尔值和null值不使用引号 - - 移除任何注释或非JSON元素 - """, brokenJson); - } - - @Override - protected String preprocessResponse(String aiResponse, String brokenJson) { - // 如果提示为空(表示原始输入已是有效JSON),直接返回原始输入 - if (aiResponse.isEmpty()) { - return this.currentInput; - } - return aiResponse; - } - - @Override - protected String preprocessJson(String jsonContent, String brokenJson) { - // 如果提取失败,尝试本地修复 - if (!isValidJson(jsonContent)) { - return localJsonRepair(jsonContent); - } - return jsonContent; - } - - /** - * 检查字符串是否为有效的JSON - */ - private boolean isValidJson(String jsonStr) { - try { - JSON.parseObject(jsonStr); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 本地JSON修复逻辑,用于简单错误的修复 - */ - private String localJsonRepair(String brokenJson) { - // 1. 移除可能的代码块标记 - String json = brokenJson.replaceAll("```json|```", "").trim(); - - // 2. 确保对象以 { 开始,以 } 结束 - Pattern objectPattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL); - Matcher objectMatcher = objectPattern.matcher(json); - if (objectMatcher.find()) { - json = objectMatcher.group(); - } - - // 3. 修复常见引号问题 - json = json.replaceAll("(? { - - @Override - protected String buildPrompt(ValidationRequest input) { - return String.format( - """ - 请回答一个简单的问题来验证模型是否正常工作。 - 问题: %s - 请严格作答,并以JSON格式返回结果: - ```json - { - "answer": "5" - } - ``` - 注意:请确保返回有效的JSON格式。""", - input.getQuestion() - ); - } - - @Override - protected ValidationResult parseResult(String jsonContent, ValidationRequest input) { - try { - return objectMapper.readValue(jsonContent, ValidationResult.class); - } catch (Exception e) { - log.warn("解析验证响应失败,使用默认结果: {}", e.getMessage()); - ValidationResult result = new ValidationResult(); - result.setAnswer("模型响应解析失败,但模型可以正常通信"); - return result; - } - } - - @Override - public String getDescription() { - return "模型验证操作 - 通过简单问答验证模型配置是否可用"; - } - - /** - * 验证请求 - */ - @Data - public static class ValidationRequest { - @JsonProperty("question") - private String question; - - public ValidationRequest() { - this.question = "1+1等于几?"; - } - - public ValidationRequest(String question) { - this.question = question; - } - } - - /** - * 验证结果 - */ - @Data - public static class ValidationResult { - @JsonProperty("answer") - private String answer; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/DynamicOperationConfigService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/DynamicOperationConfigService.java deleted file mode 100644 index 63f5e13..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/DynamicOperationConfigService.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence; - -import io.github.timemachinelab.sfchain.annotation.AIOp; -import io.github.timemachinelab.sfchain.core.AIOperationRegistry; -import io.github.timemachinelab.sfchain.core.BaseAIOperation; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -/** - * 描述: 动态操作配置服务 - * 从@AIOp注解中获取操作配置信息 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class DynamicOperationConfigService { - - private final AIOperationRegistry operationRegistry; - - /** - * 从@AIOp注解获取操作配置 - * - * @param operationType 操作类型 - * @return 操作配置 - */ - public Optional getOperationConfig(String operationType) { - try { - if (!operationRegistry.isOperationRegistered(operationType)) { - return Optional.empty(); - } - - BaseAIOperation operation = operationRegistry.getOperation(operationType); - AIOp annotation = operation.getAnnotation(); - - if (annotation == null) { - return Optional.empty(); - } - - // 从注解构建配置 - OperationConfigData config = OperationConfigData.builder() - .operationType(operationType) - .description(annotation.description()) - .enabled(annotation.enabled()) - .maxTokens(annotation.defaultMaxTokens() > 0 ? annotation.defaultMaxTokens() : null) - .temperature(annotation.defaultTemperature() >= 0 ? annotation.defaultTemperature() : null) - .jsonOutput(annotation.requireJsonOutput()) - .thinkingMode(annotation.supportThinking()) - .modelName(annotation.defaultModel().isEmpty() ? null : annotation.defaultModel()) - .build(); - - return Optional.of(config); - } catch (Exception e) { - log.warn("获取操作 {} 的动态配置失败: {}", operationType, e.getMessage()); - return Optional.empty(); - } - } - - /** - * 获取所有操作的动态配置 - * - * @return 所有动态配置 - */ - public Map getAllOperationConfigs() { - Map configs = new HashMap<>(); - - for (String operationType : operationRegistry.getAllOperations()) { - getOperationConfig(operationType).ifPresent(config -> - configs.put(operationType, config) - ); - } - - return configs; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/ModelConfigData.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/ModelConfigData.java deleted file mode 100644 index c539f41..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/ModelConfigData.java +++ /dev/null @@ -1,125 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.HashMap; -import java.util.Map; - -/** - * 描述: 模型配置数据类 - * 用于持久化存储的模型配置信息 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class ModelConfigData { - - /** - * 模型名称 - */ - private String modelName; - - /** - * API基础URL - */ - private String baseUrl; - - /** - * API密钥 - */ - private String apiKey; - - /** - * 默认最大token数 - */ - @Builder.Default - private Integer defaultMaxTokens = 4096; - - /** - * 默认温度参数 - */ - @Builder.Default - private Double defaultTemperature = 0.7; - - /** - * 是否支持流式输出 - */ - @Builder.Default - private Boolean supportStream = false; - - /** - * 是否支持JSON格式输出 - */ - @Builder.Default - private Boolean supportJsonOutput = false; - - /** - * 是否支持思考模式 - */ - @Builder.Default - private Boolean supportThinking = false; - - /** - * 额外的HTTP请求头 - */ - @Builder.Default - private Map additionalHeaders = new HashMap<>(); - - /** - * 模型描述 - */ - private String description; - - /** - * 模型提供商 - */ - private String provider; - - /** - * 是否启用 - */ - @Builder.Default - private Boolean enabled = true; - - /** - * 创建时间戳 - */ - private Long createdAt; - - /** - * 更新时间戳 - */ - private Long updatedAt; - - /** - * 验证配置是否有效 - * - * @return 是否有效 - */ - public boolean isValid() { - return modelName != null && !modelName.trim().isEmpty() && - baseUrl != null && !baseUrl.trim().isEmpty() && - apiKey != null && !apiKey.trim().isEmpty(); - } - - /** - * 更新时间戳 - */ - public void updateTimestamp() { - this.updatedAt = System.currentTimeMillis(); - if (this.createdAt == null) { - this.createdAt = this.updatedAt; - } - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/OperationConfigData.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/OperationConfigData.java deleted file mode 100644 index 220b446..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/OperationConfigData.java +++ /dev/null @@ -1,143 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.HashMap; -import java.util.Map; - -/** - * 描述: 操作配置数据类 - * 用于持久化存储的操作配置信息 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonIgnoreProperties(ignoreUnknown = true) -public class OperationConfigData { - - /** - * 操作类型 - */ - private String operationType; - - /** - * 操作描述 - */ - private String description; - - /** - * 是否启用 - */ - @Builder.Default - private Boolean enabled = true; - - /** - * 最大token数 - */ - private Integer maxTokens; - - /** - * 温度参数 - */ - private Double temperature; - - /** - * 超时时间(毫秒) - */ - private Long timeout; - - /** - * 重试次数 - */ - @Builder.Default - private Integer retryCount = 3; - - /** - * 是否启用JSON输出 - */ - @Builder.Default - private Boolean jsonOutput = false; - - /** - * 是否启用流式输出 - */ - @Builder.Default - private Boolean streamOutput = false; - - /** - * 是否启用思考模式 - */ - @Builder.Default - private Boolean thinkingMode = false; - - /** - * 自定义提示词前缀 - */ - private String promptPrefix; - - /** - * 自定义提示词后缀 - */ - private String promptSuffix; - - /** - * 系统提示词 - */ - private String systemPrompt; - - /** - * 输出格式说明 - */ - private String outputFormat; - - /** - * 自定义参数 - */ - @Builder.Default - private Map customParams = new HashMap<>(); - - /** - * 关联的模型名称 - */ - private String modelName; - - /** - * 验证配置是否有效 - * @return 是否有效 - */ - public boolean isValid() { - // 基本验证:操作类型不能为空 - if (operationType == null || operationType.trim().isEmpty()) { - return false; - } - - // 验证数值范围 - if (maxTokens != null && maxTokens <= 0) { - return false; - } - - if (temperature != null && (temperature < 0.0 || temperature > 2.0)) { - return false; - } - - if (retryCount != null && retryCount < 0) { - return false; - } - - if (timeout != null && timeout <= 0) { - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceManager.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceManager.java deleted file mode 100644 index e081e33..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceManager.java +++ /dev/null @@ -1,669 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence; - -import io.github.timemachinelab.sfchain.core.AIOperationRegistry; -import io.github.timemachinelab.sfchain.core.ModelRegistry; -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelConfig; -import io.github.timemachinelab.sfchain.core.openai.OpenAIModelFactory; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * 描述: 持久化管理器 - * 负责协调持久化服务与现有的模型注册和操作注册系统 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class PersistenceManager { - - private final PersistenceService persistenceService; - private final ModelRegistry modelRegistry; - private final AIOperationRegistry operationRegistry; - private final OpenAIModelFactory modelFactory; - private final DynamicOperationConfigService dynamicOperationConfigService; - - /** - * 应用启动完成后同步配置 - */ - @EventListener(ApplicationReadyEvent.class) - public void onApplicationReady() { - log.info("开始同步持久化配置..."); - - // 同步现有配置到持久化存储 - syncExistingConfigurations(); - - // 从持久化存储加载额外配置 - loadPersistedConfigurations(); - - log.info("持久化配置同步完成"); - } - - // ==================== 模型配置管理 ==================== - - /** - * 添加模型配置 - * - * @param modelName 模型名称 - * @param config 模型配置数据 - */ - public void addModelConfig(String modelName, ModelConfigData config) { - try { - // 验证配置 - if (!config.isValid()) { - throw new IllegalArgumentException("无效的模型配置: " + modelName); - } - - // 转换为OpenAI模型配置 - OpenAIModelConfig openAIConfig = convertToOpenAIConfig(config); - - // 注册到模型工厂 - modelFactory.registerModel(openAIConfig); - - // 保存到持久化存储 - persistenceService.saveModelConfig(modelName, config); - - log.info("成功添加模型配置: {}", modelName); - } catch (Exception e) { - log.error("添加模型配置失败: {} - {}", modelName, e.getMessage()); - throw new RuntimeException("添加模型配置失败: " + modelName, e); - } - } - - /** - * 更新模型配置 - * - * @param modelName 模型名称 - * @param config 模型配置数据 - */ - public void updateModelConfig(String modelName, ModelConfigData config) { - try { - // 检查模型是否存在 - if (!persistenceService.existsModelConfig(modelName)) { - throw new IllegalArgumentException("模型配置不存在: " + modelName); - } - - // 验证配置 - if (!config.isValid()) { - throw new IllegalArgumentException("无效的模型配置: " + modelName); - } - - // 移除旧配置 - modelFactory.removeModel(modelName); - - // 转换为OpenAI模型配置 - OpenAIModelConfig openAIConfig = convertToOpenAIConfig(config); - - // 重新注册到模型工厂 - modelFactory.registerModel(openAIConfig); - - // 更新持久化存储 - persistenceService.saveModelConfig(modelName, config); - - log.info("成功更新模型配置: {}", modelName); - } catch (Exception e) { - log.error("更新模型配置失败: {} - {}", modelName, e.getMessage()); - throw new RuntimeException("更新模型配置失败: " + modelName, e); - } - } - - /** - * 删除模型配置 - * - * @param modelName 模型名称 - */ - public void deleteModelConfig(String modelName) { - try { - // 检查是否有操作正在使用此模型(从操作配置中检查) - Map allConfigs = persistenceService.getAllOperationConfigs(); - boolean inUse = allConfigs.values().stream() - .anyMatch(config -> modelName.equals(config.getModelName())); - if (inUse) { - throw new IllegalStateException("模型正在被操作使用,无法删除: " + modelName); - } - - // 从模型工厂移除 - modelFactory.removeModel(modelName); - - // 从持久化存储删除 - persistenceService.deleteModelConfig(modelName); - - log.info("成功删除模型配置: {}", modelName); - } catch (Exception e) { - log.error("删除模型配置失败: {} - {}", modelName, e.getMessage()); - throw new RuntimeException("删除模型配置失败: " + modelName, e); - } - } - - /** - * 获取模型配置 - * - * @param modelName 模型名称 - * @return 模型配置 - */ - public Optional getModelConfig(String modelName) { - return persistenceService.getModelConfig(modelName); - } - - /** - * 获取所有模型配置 - * - * @return 所有模型配置 - */ - public Map getAllModelConfigs() { - return persistenceService.getAllModelConfigs(); - } - - // ==================== 操作配置管理 ==================== - - /** - * 保存操作配置 - * 同时更新数据库和内存容器 - * - * @param operationType 操作类型 - * @param config 操作配置 - */ - public void saveOperationConfig(String operationType, OperationConfigData config) { - try { - // 验证操作是否已注册 - if (!operationRegistry.isOperationRegistered(operationType)) { - throw new IllegalArgumentException("操作未注册: " + operationType); - } - - // 验证配置 - if (!config.isValid()) { - throw new IllegalArgumentException("无效的操作配置: " + operationType); - } - - // 如果配置中指定了模型,验证模型是否存在 - if (config.getModelName() != null && !config.getModelName().isEmpty()) { - if (!persistenceService.existsModelConfig(config.getModelName()) && - !modelRegistry.isModelRegistered(config.getModelName())) { - throw new IllegalArgumentException("指定的模型不存在: " + config.getModelName()); - } - } - - // 1. 先保存到数据库 - persistenceService.saveOperationConfig(operationType, config); - - // 2. ✅ 完整同步所有配置到框架(包括模型映射和其他参数) - syncDatabaseConfigToFramework(operationType, config); - - log.info("成功保存操作配置并完整同步到框架: {} -> 模型: {}", operationType, config.getModelName()); - } catch (Exception e) { - log.error("保存操作配置失败: {} - {}", operationType, e.getMessage()); - throw new RuntimeException("保存操作配置失败: " + operationType, e); - } - } - - /** - * 获取操作配置 - * 优先从数据库获取,如果不存在则从@AIOp注解获取 - * - * @param operationType 操作类型 - * @return 操作配置 - */ - public Optional getOperationConfig(String operationType) { - // 优先从数据库获取 - Optional persistedConfig = persistenceService.getOperationConfig(operationType); - if (persistedConfig.isPresent()) { - log.debug("从数据库获取操作配置: {}", operationType); - return persistedConfig; - } - - // 如果数据库配置不存在,则从注解获取 - Optional dynamicConfig = dynamicOperationConfigService.getOperationConfig(operationType); - if (dynamicConfig.isPresent()) { - log.debug("从注解获取操作配置: {}", operationType); - } - - return dynamicConfig; - } - - /** - * 获取所有操作配置 - * 优先使用数据库配置,注解配置作为补充 - * - * @return 所有操作配置 - */ - public Map getAllOperationConfigs() { - // 获取持久化配置(数据库) - Map persistedConfigs = persistenceService.getAllOperationConfigs(); - - // 获取动态配置(从注解) - Map dynamicConfigs = dynamicOperationConfigService.getAllOperationConfigs(); - - // 合并配置,数据库配置优先 - Map allConfigs = new HashMap<>(dynamicConfigs); - allConfigs.putAll(persistedConfigs); // 数据库配置覆盖注解配置 - - log.debug("获取所有操作配置: 持久化配置{}个, 动态配置{}个, 总计{}个", - persistedConfigs.size(), dynamicConfigs.size(), allConfigs.size()); - - return allConfigs; - } - - /** - * 删除操作配置 - * - * @param operationType 操作类型 - */ - public void deleteOperationConfig(String operationType) { - try { - persistenceService.deleteOperationConfig(operationType); - - // 同时从操作注册中心移除模型映射 - operationRegistry.getModelMapping().remove(operationType); - - log.info("成功删除操作配置: {}", operationType); - } catch (Exception e) { - log.error("删除操作配置失败: {} - {}", operationType, e.getMessage()); - throw new RuntimeException("删除操作配置失败: " + operationType, e); - } - } - - // ==================== 备份和恢复 ==================== - - /** - * 创建配置备份 - * - * @param backupName 备份名称 - */ - public void createBackup(String backupName) { - persistenceService.backup(backupName); - } - - /** - * 从备份恢复配置 - * - * @param backupName 备份名称 - */ - public void restoreFromBackup(String backupName) { - try { - // 恢复配置 - persistenceService.restoreFromBackup(backupName); - - // 重新同步配置 - loadPersistedConfigurations(); - - log.info("成功从备份恢复配置: {}", backupName); - } catch (Exception e) { - log.error("从备份恢复配置失败: {} - {}", backupName, e.getMessage()); - throw new RuntimeException("从备份恢复配置失败: " + backupName, e); - } - } - - /** - * 获取所有备份名称 - * - * @return 备份名称列表 - */ - public List getAllBackupNames() { - return persistenceService.getAllBackupNames(); - } - - /** - * 刷新配置到持久化存储 - */ - public void flushConfigurations() { - syncExistingConfigurations(); - log.info("配置已刷新到持久化存储"); - } - - /** - * 重新加载配置 - */ - public void reloadConfigurations() { - try { - // 重新加载配置 - loadPersistedConfigurations(); - - log.info("配置重新加载完成"); - } catch (Exception e) { - log.error("重新加载配置失败: {}", e.getMessage(), e); - throw new RuntimeException("重新加载配置失败", e); - } - } - - // ==================== 私有方法 ==================== - - /** - * 同步现有配置到持久化存储 - */ - private void syncExistingConfigurations() { - try { - // 同步现有的模型配置 - for (String modelName : modelFactory.getRegisteredModelNames()) { - OpenAIModelConfig openAIConfig = modelFactory.getModelConfig(modelName); - if (openAIConfig != null && !persistenceService.existsModelConfig(modelName)) { - ModelConfigData configData = convertFromOpenAIConfig(openAIConfig); - persistenceService.saveModelConfig(modelName, configData); - } - } - - // 初始化操作配置:从@AIOp注解获取配置并保存到数据库 - initializeOperationConfigs(); - - } catch (Exception e) { - log.warn("同步现有配置时出现警告: {}", e.getMessage()); - } - } - - /** - * 从持久化存储加载配置 - */ - private void loadPersistedConfigurations() { - try { - // 加载模型配置 - Map modelConfigs = persistenceService.getAllModelConfigs(); - for (Map.Entry entry : modelConfigs.entrySet()) { - String modelName = entry.getKey(); - ModelConfigData config = entry.getValue(); - - if (!modelFactory.isModelRegistered(modelName)) { - try { - OpenAIModelConfig openAIConfig = convertToOpenAIConfig(config); - modelFactory.registerModel(openAIConfig); - log.info("从持久化存储加载模型配置: {}", modelName); - } catch (Exception e) { - log.warn("加载模型配置失败: {} - {}", modelName, e.getMessage()); - } - } - } - - // 加载操作配置并同步模型映射到操作注册中心 - Map operationConfigs = persistenceService.getAllOperationConfigs(); - for (Map.Entry entry : operationConfigs.entrySet()) { - String operationType = entry.getKey(); - OperationConfigData config = entry.getValue(); - - // 如果操作配置中指定了模型,同步到操作注册中心 - if (config.getModelName() != null && !config.getModelName().isEmpty() && - operationRegistry.isOperationRegistered(operationType)) { - try { - operationRegistry.setModelForOperation(operationType, config.getModelName()); - log.debug("从持久化存储同步操作模型映射: {} -> {}", operationType, config.getModelName()); - } catch (Exception e) { - log.warn("同步操作模型映射失败: {} -> {} - {}", operationType, config.getModelName(), e.getMessage()); - } - } - } - - } catch (Exception e) { - log.error("加载持久化配置失败: {}", e.getMessage(), e); - } - } - - /** - * 初始化操作配置 - * 1. 从数据库获取所有操作配置 - * 2. 与框架中的操作进行对比 - * 3. 数据库没有的操作新增到数据库 - * 4. 数据库有的操作以数据库配置为准同步到框架 - */ - private void initializeOperationConfigs() { - try { - // 1. 获取数据库中所有的操作配置 - Map dbConfigs = persistenceService.getAllOperationConfigs(); - - // 2. 获取框架中所有已注册的操作 - List frameworkOperations = operationRegistry.getAllOperations(); - - // 3. 处理框架中的操作 - for (String operationType : frameworkOperations) { - if (dbConfigs.containsKey(operationType)) { - // 数据库中存在该操作配置,以数据库配置为准同步到框架 - OperationConfigData dbConfig = dbConfigs.get(operationType); - syncDatabaseConfigToFramework(operationType, dbConfig); - log.info("使用数据库配置同步到框架: {} -> 模型: {}", operationType, dbConfig.getModelName()); - } else { - // 数据库中不存在该操作配置,从注解获取并新增到数据库 - Optional annotationConfig = dynamicOperationConfigService.getOperationConfig(operationType); - if (annotationConfig.isPresent()) { - OperationConfigData config = annotationConfig.get(); - persistenceService.saveOperationConfig(operationType, config); - syncDatabaseConfigToFramework(operationType, config); - log.info("从注解新增操作配置到数据库: {} -> 模型: {}", operationType, config.getModelName()); - } else { - log.debug("操作 {} 没有@AIOp注解配置,跳过初始化", operationType); - } - } - } - - // 4. 处理数据库中存在但框架中不存在的操作(可能是已删除的操作) - for (String dbOperationType : dbConfigs.keySet()) { - if (!frameworkOperations.contains(dbOperationType)) { - log.warn("数据库中存在操作配置但框架中未注册该操作: {},建议清理数据库配置", dbOperationType); - // 可选:自动删除数据库中的无效配置 - persistenceService.deleteOperationConfig(dbOperationType); - } - } - - } catch (Exception e) { - log.error("初始化操作配置时发生错误: {}", e.getMessage(), e); - } - } - - /** - * 将数据库配置同步到框架中 - * - * @param operationType 操作类型 - * @param dbConfig 数据库配置 - */ - private void syncDatabaseConfigToFramework(String operationType, OperationConfigData dbConfig) { - try { - log.debug("同步数据库配置到框架: {} -> {}", operationType, dbConfig); - - // 1. 更新AIOperationRegistry中的操作配置 - updateOperationConfigInRegistry(operationType, dbConfig); - - // 2. 同步模型映射关系 - if (dbConfig.getModelName() != null && !dbConfig.getModelName().trim().isEmpty()) { - syncModelMapping(operationType, dbConfig.getModelName()); - } - - log.info("成功同步操作配置到框架: {}", operationType); - - } catch (Exception e) { - log.error("同步操作配置到框架失败: {} - {}", operationType, e.getMessage(), e); - throw new RuntimeException("同步配置失败: " + operationType, e); - } - } - - /** - * 更新AIOperationRegistry中的操作配置 - * - * @param operationType 操作类型 - * @param dbConfig 数据库配置 - */ - private void updateOperationConfigInRegistry(String operationType, OperationConfigData dbConfig) { - // 获取或创建操作配置 - AIOperationRegistry.OperationConfig registryConfig = - operationRegistry.getOperationConfig(operationType); - - if (registryConfig == null) { - registryConfig = new AIOperationRegistry.OperationConfig(); - } - - // 将数据库配置映射到注册中心配置 - if (dbConfig.getEnabled() != null) { - registryConfig.setEnabled(dbConfig.getEnabled()); - } - - if (dbConfig.getMaxTokens() != null && dbConfig.getMaxTokens() > 0) { - registryConfig.setMaxTokens(dbConfig.getMaxTokens()); - } - - if (dbConfig.getTemperature() != null && dbConfig.getTemperature() >= 0) { - registryConfig.setTemperature(dbConfig.getTemperature()); - } - - if (dbConfig.getJsonOutput() != null) { - registryConfig.setRequireJsonOutput(dbConfig.getJsonOutput()); - } - - if (dbConfig.getThinkingMode() != null) { - registryConfig.setSupportThinking(dbConfig.getThinkingMode()); - } - - if (dbConfig.getRetryCount() != null && dbConfig.getRetryCount() >= 0) { - registryConfig.setRetryCount(dbConfig.getRetryCount()); - } - - // 处理超时时间转换(数据库存储毫秒,注册中心使用秒) - if (dbConfig.getTimeout() != null && dbConfig.getTimeout() > 0) { - int timeoutSeconds = (int) (dbConfig.getTimeout() / 1000); - registryConfig.setTimeoutSeconds(Math.max(1, timeoutSeconds)); - } - - // 将更新后的配置设置回注册中心 - operationRegistry.getConfigs().put(operationType, registryConfig); - - log.debug("已更新操作配置到注册中心: {} -> {}", operationType, registryConfig); - } - - /** - * 同步模型映射关系 - * - * @param operationType 操作类型 - * @param modelName 模型名称 - */ - private void syncModelMapping(String operationType, String modelName) { - try { - // 验证模型是否存在 - if (!isValidModel(modelName)) { - log.warn("模型不存在,跳过映射同步: {} -> {}", operationType, modelName); - return; - } - - // 设置模型映射 - operationRegistry.setModelForOperation(operationType, modelName); - log.debug("已同步模型映射: {} -> {}", operationType, modelName); - - } catch (Exception e) { - log.warn("同步模型映射失败: {} -> {} - {}", operationType, modelName, e.getMessage()); - // 模型映射失败不应该阻止整个同步过程 - } - } - - /** - * 验证模型是否有效 - * - * @param modelName 模型名称 - * @return 是否有效 - */ - private boolean isValidModel(String modelName) { - try { - // 检查模型是否在模型注册中心中存在 - return modelRegistry.getModel(modelName) != null; - } catch (Exception e) { - log.debug("验证模型时出错: {} - {}", modelName, e.getMessage()); - return false; - } - } - - /** - * 添加操作配置更新方法,供外部调用 - * - * @param operationType 操作类型 - * @param configData 配置数据 - */ - public void updateOperationConfig(String operationType, OperationConfigData configData) { - try { - // 验证配置 - if (!configData.isValid()) { - throw new IllegalArgumentException("无效的操作配置: " + operationType); - } - - // 同步到框架 - syncDatabaseConfigToFramework(operationType, configData); - - log.info("操作配置更新成功: {}", operationType); - - } catch (Exception e) { - log.error("更新操作配置失败: {} - {}", operationType, e.getMessage(), e); - throw new RuntimeException("更新操作配置失败: " + operationType, e); - } - } - - /** - * 批量同步所有数据库配置到框架 - */ - public void syncAllDatabaseConfigsToFramework() { - try { - Map allConfigs = persistenceService.getAllOperationConfigs(); - - log.info("开始批量同步 {} 个操作配置到框架", allConfigs.size()); - - int successCount = 0; - int failCount = 0; - - for (Map.Entry entry : allConfigs.entrySet()) { - try { - syncDatabaseConfigToFramework(entry.getKey(), entry.getValue()); - successCount++; - } catch (Exception e) { - log.error("同步操作配置失败: {} - {}", entry.getKey(), e.getMessage()); - failCount++; - } - } - - log.info("批量同步完成 - 成功: {}, 失败: {}", successCount, failCount); - - } catch (Exception e) { - log.error("批量同步配置失败: {}", e.getMessage(), e); - throw new RuntimeException("批量同步配置失败", e); - } - } - - /** - * 转换为OpenAI模型配置 - */ - private OpenAIModelConfig convertToOpenAIConfig(ModelConfigData config) { - return OpenAIModelConfig.builder() - .modelName(config.getModelName()) - .baseUrl(config.getBaseUrl()) - .apiKey(config.getApiKey()) - .defaultMaxTokens(config.getDefaultMaxTokens()) - .defaultTemperature(config.getDefaultTemperature()) - .supportStream(config.getSupportStream()) - .supportJsonOutput(config.getSupportJsonOutput()) - .supportThinking(config.getSupportThinking()) - .additionalHeaders(config.getAdditionalHeaders()) - .description(config.getDescription()) - .provider(config.getProvider()) - .enabled(config.getEnabled()) - .build(); - } - - /** - * 从OpenAI模型配置转换 - */ - private ModelConfigData convertFromOpenAIConfig(OpenAIModelConfig config) { - ModelConfigData data = new ModelConfigData(); - data.setModelName(config.getModelName()); - data.setBaseUrl(config.getBaseUrl()); - data.setApiKey(config.getApiKey()); - data.setDefaultMaxTokens(config.getDefaultMaxTokens()); - data.setDefaultTemperature(config.getDefaultTemperature()); - data.setSupportStream(config.getSupportStream()); - data.setSupportJsonOutput(config.getSupportJsonOutput()); - data.setSupportThinking(config.getSupportThinking()); - data.setAdditionalHeaders(config.getAdditionalHeaders()); - data.setDescription(config.getDescription()); - data.setProvider(config.getProvider()); - data.setEnabled(config.getEnabled()); - data.updateTimestamp(); - return data; - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceService.java deleted file mode 100644 index 3f9c7a5..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PersistenceService.java +++ /dev/null @@ -1,127 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * 描述: 持久化服务接口 - * 提供模型配置和操作映射的增删改查功能 - * - * @author suifeng - * 日期: 2025/1/27 - */ -public interface PersistenceService { - - // ==================== 模型配置管理 ==================== - /** - * 保存模型配置 - * - * @param modelName 模型名称 - * @param config 模型配置 - */ - void saveModelConfig(String modelName, ModelConfigData config); - - /** - * 获取模型配置 - * - * @param modelName 模型名称 - * @return 模型配置,如果不存在则返回空 - */ - Optional getModelConfig(String modelName); - - /** - * 获取所有模型配置 - * - * @return 所有模型配置的映射 - */ - Map getAllModelConfigs(); - - /** - * 删除模型配置 - * - * @param modelName 模型名称 - * @return 是否删除成功 - */ - boolean deleteModelConfig(String modelName); - - /** - * 检查模型配置是否存在 - * - * @param modelName 模型名称 - * @return 是否存在 - */ - boolean existsModelConfig(String modelName); - - /** - * 获取所有模型名称 - * - * @return 模型名称列表 - */ - List getAllModelNames(); - - // 移除整个 "操作模型映射管理" 部分 - - // ==================== 操作配置管理 ==================== - /** - * 保存操作配置 - * - * @param operationType 操作类型 - * @param config 操作配置 - */ - void saveOperationConfig(String operationType, OperationConfigData config); - - /** - * 获取操作配置 - * - * @param operationType 操作类型 - * @return 操作配置,如果不存在则返回空 - */ - Optional getOperationConfig(String operationType); - - /** - * 获取所有操作配置 - * - * @return 所有操作配置的映射 - */ - Map getAllOperationConfigs(); - - /** - * 删除操作配置 - * - * @param operationType 操作类型 - * @return 是否删除成功 - */ - boolean deleteOperationConfig(String operationType); - - // ==================== 数据同步和备份 ==================== - - /** - * 刷新数据到持久化存储 - */ - void flush(); - - /** - * 从持久化存储重新加载数据 - */ - void reload(); - - /** - * 备份当前配置 - * - * @param backupName 备份名称 - */ - void backup(String backupName); - - /** - * 从备份恢复配置 - * @param backupName 备份名称 - */ - void restoreFromBackup(String backupName); - - /** - * 获取所有备份名称 - * @return 备份名称列表 - */ - List getAllBackupNames(); -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PostgreSQLPersistenceService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PostgreSQLPersistenceService.java deleted file mode 100644 index 670b646..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/PostgreSQLPersistenceService.java +++ /dev/null @@ -1,319 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence; -import io.github.timemachinelab.sfchain.persistence.entity.ModelConfigEntity; -import io.github.timemachinelab.sfchain.persistence.entity.OperationConfigEntity; -import io.github.timemachinelab.sfchain.persistence.repository.ModelConfigRepository; -import io.github.timemachinelab.sfchain.persistence.repository.OperationConfigRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * 描述: 基于PostgreSQL的持久化服务实现 - * 替代原有的JSON文件持久化方案 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class PostgreSQLPersistenceService implements PersistenceService { - - private final ModelConfigRepository modelConfigRepository; - private final OperationConfigRepository operationConfigRepository; - - @Override - public void saveModelConfig(String modelName, ModelConfigData config) { - log.debug("保存模型配置: {}", modelName); - config.setModelName(modelName); - - // 查找现有实体并更新,或创建新实体 - ModelConfigEntity entity = modelConfigRepository.findByModelName(modelName) - .map(existing -> { - // 更新现有实体,保留 id 和 createdAt - Long id = existing.getId(); - LocalDateTime createdAt = existing.getCreatedAt(); - - // 使用新的转换方法,而不是 BeanUtils.copyProperties - ModelConfigEntity updatedEntity = convertToEntity(config); - updatedEntity.setId(id); - updatedEntity.setCreatedAt(createdAt); - - return updatedEntity; - }) - .orElseGet(() -> { - // 创建新实体,使用新的转换方法 - return convertToEntity(config); - }); - - modelConfigRepository.save(entity); - log.info("模型配置已保存: {}", modelName); - } - - @Override - public Optional getModelConfig(String modelName) { - log.debug("加载模型配置: {}", modelName); - return modelConfigRepository.findByModelName(modelName) - .map(this::convertToData); - } - - @Override - public Map getAllModelConfigs() { - log.debug("加载所有模型配置"); - return modelConfigRepository.findAll().stream() - .collect(Collectors.toMap( - ModelConfigEntity::getModelName, - this::convertToData - )); - } - - @Override - public boolean deleteModelConfig(String modelName) { - log.debug("删除模型配置: {}", modelName); - Optional entity = modelConfigRepository.findByModelName(modelName); - if (entity.isPresent()) { - modelConfigRepository.deleteById(entity.get().getId()); - log.info("模型配置已删除: {}", modelName); - return true; - } else { - log.warn("模型配置不存在,无法删除: {}", modelName); - return false; - } - } - - @Override - public boolean existsModelConfig(String modelName) { - return modelConfigRepository.existsByModelName(modelName); - } - - @Override - public List getAllModelNames() { - log.debug("获取所有模型名称"); - return modelConfigRepository.findEnabledModelNames(); - } - - @Override - public void saveOperationConfig(String operationType, OperationConfigData config) { - log.debug("保存操作配置: {}", operationType); - config.setOperationType(operationType); - - // 查找现有实体并更新,或创建新实体 - OperationConfigEntity entity = operationConfigRepository.findByOperationType(operationType) - .map(existing -> { - // 更新现有实体,保留 id 和 createdAt - Long id = existing.getId(); - LocalDateTime createdAt = existing.getCreatedAt(); - - // 转换新的配置数据 - OperationConfigEntity updatedEntity = convertToEntity(config); - updatedEntity.setId(id); - updatedEntity.setCreatedAt(createdAt); - - return updatedEntity; - }) - .orElseGet(() -> { - // 创建新实体 - return convertToEntity(config); - }); - - operationConfigRepository.save(entity); - log.info("操作配置已保存: {}", operationType); - } - - @Override - public Optional getOperationConfig(String operationType) { - log.debug("加载操作配置: {}", operationType); - return operationConfigRepository.findByOperationType(operationType) - .map(this::convertToData); - } - - @Override - public Map getAllOperationConfigs() { - log.debug("加载所有操作配置"); - return operationConfigRepository.findAll().stream() - .collect(Collectors.toMap( - OperationConfigEntity::getOperationType, - this::convertToData - )); - } - - @Override - public boolean deleteOperationConfig(String operationType) { - log.debug("删除操作配置: {}", operationType); - Optional entity = operationConfigRepository.findByOperationType(operationType); - if (entity.isPresent()) { - operationConfigRepository.deleteById(entity.get().getId()); - log.info("操作配置已删除: {}", operationType); - return true; - } else { - log.warn("操作配置不存在,无法删除: {}", operationType); - return false; - } - } - - @Override - public void flush() { - // PostgreSQL自动提交事务,无需手动flush - log.debug("PostgreSQL持久化服务flush操作(无需手动操作)"); - } - - @Override - public void reload() { - // PostgreSQL数据实时同步,无需手动reload - log.debug("PostgreSQL持久化服务reload操作(数据实时同步)"); - } - - @Override - @Transactional - public void backup(String backupName) { - // PostgreSQL备份通常使用pg_dump等工具,这里记录备份请求 - log.info("PostgreSQL备份请求,备份名称: {}。请使用pg_dump等工具进行数据库备份。", backupName); - } - - @Override - @Transactional - public void restoreFromBackup(String backupName) { - // PostgreSQL恢复通常使用pg_restore等工具,这里记录恢复请求 - log.info("PostgreSQL恢复请求,备份名称: {}。请使用pg_restore等工具进行数据库恢复。", backupName); - } - - @Override - public List getAllBackupNames() { - // PostgreSQL备份文件管理通常在文件系统层面,这里返回空列表 - log.debug("PostgreSQL备份文件列表查询(需要在文件系统层面管理)"); - return List.of(); - } - - /** - * 将ModelConfigData转换为ModelConfigEntity - */ - private ModelConfigEntity convertToEntity(ModelConfigData data) { - ModelConfigEntity entity = new ModelConfigEntity(); - - // 映射基本字段 - entity.setModelName(data.getModelName()); - entity.setProvider(data.getProvider()); - entity.setApiKey(data.getApiKey()); - entity.setBaseUrl(data.getBaseUrl()); - entity.setEnabled(data.getEnabled()); - entity.setDescription(data.getDescription()); - - // 将扩展字段映射到customParams - Map customParams = new HashMap<>(); - if (data.getDefaultMaxTokens() != null) { - customParams.put("defaultMaxTokens", data.getDefaultMaxTokens()); - } - if (data.getDefaultTemperature() != null) { - customParams.put("defaultTemperature", data.getDefaultTemperature()); - } - if (data.getSupportStream() != null) { - customParams.put("supportStream", data.getSupportStream()); - } - if (data.getSupportJsonOutput() != null) { - customParams.put("supportJsonOutput", data.getSupportJsonOutput()); - } - if (data.getSupportThinking() != null) { - customParams.put("supportThinking", data.getSupportThinking()); - } - if (data.getAdditionalHeaders() != null && !data.getAdditionalHeaders().isEmpty()) { - customParams.put("additionalHeaders", data.getAdditionalHeaders()); - } - if (data.getCreatedAt() != null) { - customParams.put("createdAt", data.getCreatedAt()); - } - if (data.getUpdatedAt() != null) { - customParams.put("updatedAt", data.getUpdatedAt()); - } - - entity.setCustomParams(customParams); - return entity; - } - - /** - * 将ModelConfigEntity转换为ModelConfigData - */ - private ModelConfigData convertToData(ModelConfigEntity entity) { - ModelConfigData.ModelConfigDataBuilder builder = ModelConfigData.builder() - .modelName(entity.getModelName()) - .provider(entity.getProvider()) - .apiKey(entity.getApiKey()) - .baseUrl(entity.getBaseUrl()) - .enabled(entity.getEnabled()) - .description(entity.getDescription()); - - // 从customParams中提取扩展字段 - Map customParams = entity.getCustomParams(); - if (customParams != null) { - if (customParams.containsKey("defaultMaxTokens")) { - builder.defaultMaxTokens((Integer) customParams.get("defaultMaxTokens")); - } - if (customParams.containsKey("defaultTemperature")) { - builder.defaultTemperature((Double) customParams.get("defaultTemperature")); - } - if (customParams.containsKey("supportStream")) { - builder.supportStream((Boolean) customParams.get("supportStream")); - } - if (customParams.containsKey("supportJsonOutput")) { - builder.supportJsonOutput((Boolean) customParams.get("supportJsonOutput")); - } - if (customParams.containsKey("supportThinking")) { - builder.supportThinking((Boolean) customParams.get("supportThinking")); - } - if (customParams.containsKey("additionalHeaders")) { - @SuppressWarnings("unchecked") - Map headers = (Map) customParams.get("additionalHeaders"); - builder.additionalHeaders(headers != null ? headers : new HashMap<>()); - } - if (customParams.containsKey("createdAt")) { - builder.createdAt((Long) customParams.get("createdAt")); - } - if (customParams.containsKey("updatedAt")) { - builder.updatedAt((Long) customParams.get("updatedAt")); - } - } - - return builder.build(); - } - - /** - * 将OperationConfigData转换为OperationConfigEntity - */ - private OperationConfigEntity convertToEntity(OperationConfigData data) { - OperationConfigEntity entity = new OperationConfigEntity(); - entity.setOperationType(data.getOperationType()); - entity.setDescription(data.getDescription()); - entity.setEnabled(data.getEnabled()); - entity.setMaxTokens(data.getMaxTokens()); - entity.setTemperature(data.getTemperature()); - entity.setJsonOutput(data.getJsonOutput()); - entity.setThinkingMode(data.getThinkingMode()); - entity.setCustomParams(data.getCustomParams()); - entity.setModelName(data.getModelName()); - return entity; - } - - /** - * 将OperationConfigEntity转换为OperationConfigData - */ - private OperationConfigData convertToData(OperationConfigEntity entity) { - return OperationConfigData.builder() - .operationType(entity.getOperationType()) - .description(entity.getDescription()) - .enabled(entity.getEnabled()) - .maxTokens(entity.getMaxTokens()) - .temperature(entity.getTemperature()) - .jsonOutput(entity.getJsonOutput()) - .thinkingMode(entity.getThinkingMode()) - .customParams(entity.getCustomParams()) - .modelName(entity.getModelName()) - .build(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatContextService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatContextService.java deleted file mode 100644 index cf6bec3..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatContextService.java +++ /dev/null @@ -1,87 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.context; - -import java.util.List; - -/** - * 描述: - * @author suifeng - * 日期: 2025/8/14 - */ -public interface ChatContextService { - - /** - * 设置或更新系统提示词 - * @param sessionId 会话ID - * @param systemPrompt 系统提示词内容 - */ - void setSystemPrompt(String sessionId, String systemPrompt); - - /** - * 获取系统提示词 - * @param sessionId 会话ID - * @return 系统提示词内容,如果不存在返回null - */ - String getSystemPrompt(String sessionId); - - /** - * 添加用户消息到会话上下文 - * @param sessionId 会话ID - * @param userMessage 用户消息内容 - */ - void addUserMessage(String sessionId, String userMessage); - - /** - * 添加AI回复到会话上下文 - * @param sessionId 会话ID - * @param aiResponse AI回复内容 - */ - void addAiResponse(String sessionId, String aiResponse); - - /** - * 获取完整的对话上下文(包含系统提示词) - * @param sessionId 会话ID - * @return 完整的消息列表,系统提示词在首位 - */ - List getFullContext(String sessionId); - - /** - * 获取对话历史(不包含系统提示词) - * @param sessionId 会话ID - * @return 用户和AI的对话历史 - */ - List getConversationHistory(String sessionId); - - /** - * 获取格式化的上下文字符串 - * @param sessionId 会话ID - * @param includeSystemPrompt 是否包含系统提示词 - * @return 格式化的上下文字符串 - */ - String getContextAsString(String sessionId, boolean includeSystemPrompt); - - /** - * 清除会话的对话历史(保留系统提示词) - * @param sessionId 会话ID - */ - void clearConversation(String sessionId); - - /** - * 完全清除会话(包括系统提示词) - * @param sessionId 会话ID - */ - void clearSession(String sessionId); - - /** - * 检查会话是否存在 - * @param sessionId 会话ID - * @return 是否存在 - */ - boolean sessionExists(String sessionId); - - /** - * 获取对话消息数量(不包含系统提示词) - * @param sessionId 会话ID - * @return 消息数量 - */ - int getConversationMessageCount(String sessionId); -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatMessage.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatMessage.java deleted file mode 100644 index 138b5d8..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/ChatMessage.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.context; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class ChatMessage { - - /** - * 消息类型:SYSTEM, USER, ASSISTANT - */ - public enum MessageType { - SYSTEM, // 系统提示词 - USER, // 用户消息 - ASSISTANT // AI回复 - } - - private String id; - private MessageType type; - private String content; - private LocalDateTime timestamp; - private String sessionId; - - public static ChatMessage systemMessage(String sessionId, String content) { - return new ChatMessage( - generateId(), - MessageType.SYSTEM, - content, - LocalDateTime.now(), - sessionId - ); - } - - public static ChatMessage userMessage(String sessionId, String content) { - return new ChatMessage( - generateId(), - MessageType.USER, - content, - LocalDateTime.now(), - sessionId - ); - } - - public static ChatMessage assistantMessage(String sessionId, String content) { - return new ChatMessage( - generateId(), - MessageType.ASSISTANT, - content, - LocalDateTime.now(), - sessionId - ); - } - - private static String generateId() { - return System.currentTimeMillis() + "_" + (int)(Math.random() * 1000); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/MapBasedChatContextService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/MapBasedChatContextService.java deleted file mode 100644 index 29e4c3b..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/context/MapBasedChatContextService.java +++ /dev/null @@ -1,177 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.context; - - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 描述: - * @author suifeng - * 日期: 2025/8/14 - */ -@Slf4j -@Service -public class MapBasedChatContextService implements ChatContextService { - - // 系统提示词存储 - private final Map systemPrompts = new ConcurrentHashMap<>(); - - // 对话历史存储 - private final Map> conversationHistories = new ConcurrentHashMap<>(); - - private static final int MAX_MESSAGES_PER_SESSION = 20; - - @Override - public void setSystemPrompt(String sessionId, String systemPrompt) { - if (sessionId == null || systemPrompt == null) { - log.warn("会话ID或系统提示词为空,跳过设置"); - return; - } - - systemPrompts.put(sessionId, systemPrompt); - log.debug("设置系统提示词: sessionId={}", sessionId); - } - - @Override - public String getSystemPrompt(String sessionId) { - if (sessionId == null) { - return null; - } - return systemPrompts.get(sessionId); - } - - @Override - public void addUserMessage(String sessionId, String userMessage) { - if (sessionId == null || userMessage == null) { - log.warn("会话ID或用户消息为空,跳过添加"); - return; - } - - ChatMessage message = ChatMessage.userMessage(sessionId, userMessage); - addConversationMessage(sessionId, message); - log.debug("添加用户消息: sessionId={}", sessionId); - } - - @Override - public void addAiResponse(String sessionId, String aiResponse) { - if (sessionId == null || aiResponse == null) { - log.warn("会话ID或AI回复为空,跳过添加"); - return; - } - - ChatMessage message = ChatMessage.assistantMessage(sessionId, aiResponse); - addConversationMessage(sessionId, message); - log.debug("添加AI回复: sessionId={}", sessionId); - } - - private void addConversationMessage(String sessionId, ChatMessage message) { - conversationHistories.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(message); - - List messages = conversationHistories.get(sessionId); - while (messages.size() > MAX_MESSAGES_PER_SESSION) { - messages.remove(0); - log.debug("对话历史超限,移除最旧消息: sessionId={}", sessionId); - } - } - - @Override - public List getFullContext(String sessionId) { - if (sessionId == null) { - return new ArrayList<>(); - } - - List fullContext = new ArrayList<>(); - - // 添加系统提示词 - String systemPrompt = getSystemPrompt(sessionId); - if (systemPrompt != null) { - fullContext.add(ChatMessage.systemMessage(sessionId, systemPrompt)); - } - - // 添加对话历史 - fullContext.addAll(getConversationHistory(sessionId)); - - return fullContext; - } - - @Override - public List getConversationHistory(String sessionId) { - if (sessionId == null) { - return new ArrayList<>(); - } - - return new ArrayList<>(conversationHistories.getOrDefault(sessionId, new ArrayList<>())); - } - - @Override - public String getContextAsString(String sessionId, boolean includeSystemPrompt) { - List messages; - - if (includeSystemPrompt) { - messages = getFullContext(sessionId); - } else { - messages = getConversationHistory(sessionId); - } - - if (messages.isEmpty()) { - return ""; - } - - StringBuilder context = new StringBuilder(); - for (ChatMessage message : messages) { - String role = getRoleString(message.getType()); - context.append(role).append(": ").append(message.getContent()).append("\n"); - } - - return context.toString(); - } - - @Override - public void clearConversation(String sessionId) { - if (sessionId != null) { - conversationHistories.remove(sessionId); - log.info("清除对话历史: sessionId={}", sessionId); - } - } - - @Override - public void clearSession(String sessionId) { - if (sessionId != null) { - systemPrompts.remove(sessionId); - conversationHistories.remove(sessionId); - log.info("完全清除会话: sessionId={}", sessionId); - } - } - - @Override - public boolean sessionExists(String sessionId) { - return sessionId != null && - (systemPrompts.containsKey(sessionId) || conversationHistories.containsKey(sessionId)); - } - - @Override - public int getConversationMessageCount(String sessionId) { - if (sessionId == null) { - return 0; - } - return conversationHistories.getOrDefault(sessionId, new ArrayList<>()).size(); - } - - private String getRoleString(ChatMessage.MessageType type) { - switch (type) { - case SYSTEM: - return "系统"; - case USER: - return "用户"; - case ASSISTANT: - return "助手"; - default: - return "未知"; - } - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/ModelConfigEntity.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/ModelConfigEntity.java deleted file mode 100644 index 1a9f024..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/ModelConfigEntity.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.entity; - -import com.vladmihalcea.hibernate.type.json.JsonBinaryType; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; - -import javax.persistence.*; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 描述: 模型配置实体类 - * 用于存储AI模型的配置信息 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Entity -@Table(name = "ai_model_configs") -@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -@Data -@NoArgsConstructor -@AllArgsConstructor -public class ModelConfigEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Column(name = "model_name", length = 100, nullable = false, unique = true) - private String modelName; - - @Column(name = "provider", length = 50, nullable = false) - private String provider; - - @Column(name = "api_key", length = 500) - private String apiKey; - - @Column(name = "base_url", length = 500) - private String baseUrl; - - @Column(name = "enabled", nullable = false) - private Boolean enabled = true; - - @Column(name = "description", length = 1000) - private String description; - - @Type(type = "jsonb") - @Column(name = "custom_params", columnDefinition = "jsonb") - private Map customParams; - - @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; - - @PrePersist - protected void onCreate() { - LocalDateTime now = LocalDateTime.now(); - createdAt = now; - updatedAt = now; - } - - @PreUpdate - protected void onUpdate() { - updatedAt = LocalDateTime.now(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/OperationConfigEntity.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/OperationConfigEntity.java deleted file mode 100644 index 3edfe73..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/entity/OperationConfigEntity.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.entity; - -import com.vladmihalcea.hibernate.type.json.JsonBinaryType; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; - -import javax.persistence.*; -import java.time.LocalDateTime; -import java.util.Map; - -/** - * 描述: 操作配置实体类 - * 用于存储AI操作的配置信息 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Entity -@Table(name = "ai_operation_configs") -@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -@Data -@NoArgsConstructor -@AllArgsConstructor -public class OperationConfigEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Column(name = "operation_type", length = 100, nullable = false, unique = true) - private String operationType; - - @Column(name = "description", length = 1000) - private String description; - - @Column(name = "enabled", nullable = false) - private Boolean enabled = true; - - @Column(name = "max_tokens") - private Integer maxTokens; - - @Column(name = "temperature") - private Double temperature; - - @Column(name = "json_output", nullable = false) - private Boolean jsonOutput = false; - - @Column(name = "thinking_mode", nullable = false) - private Boolean thinkingMode = false; - - @Type(type = "jsonb") - @Column(name = "custom_params", columnDefinition = "jsonb") - private Map customParams; - - /** - * 关联的模型名称 - */ - @Column(name = "model_name", length = 100) - private String modelName; - - @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at", nullable = false) - private LocalDateTime updatedAt; - - @PrePersist - protected void onCreate() { - LocalDateTime now = LocalDateTime.now(); - createdAt = now; - updatedAt = now; - } - - @PreUpdate - protected void onUpdate() { - updatedAt = LocalDateTime.now(); - } -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/ModelConfigRepository.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/ModelConfigRepository.java deleted file mode 100644 index 0f71087..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/ModelConfigRepository.java +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.repository; - -import io.github.timemachinelab.sfchain.persistence.entity.ModelConfigEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * 描述: 模型配置Repository接口 - * 用于模型配置的数据库操作 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Repository -public interface ModelConfigRepository extends JpaRepository { - - /** - * 根据模型名称查找配置 - * @param modelName 模型名称 - * @return 模型配置 - */ - Optional findByModelName(String modelName); - - /** - * 根据提供商查找配置 - * @param provider 提供商 - * @return 模型配置列表 - */ - List findByProvider(String provider); - - /** - * 根据启用状态查找配置 - * @param enabled 启用状态 - * @return 模型配置列表 - */ - List findByEnabled(Boolean enabled); - - /** - * 检查模型名称是否存在配置 - * @param modelName 模型名称 - * @return 是否存在 - */ - boolean existsByModelName(String modelName); - - /** - * 根据模型名称删除配置 - * @param modelName 模型名称 - */ - void deleteByModelName(String modelName); - - /** - * 获取所有提供商 - * @return 提供商列表 - */ - @Query("SELECT DISTINCT m.provider FROM ModelConfigEntity m") - List findAllProviders(); - - /** - * 获取所有启用的模型名称 - * @return 启用的模型名称列表 - */ - @Query("SELECT m.modelName FROM ModelConfigEntity m WHERE m.enabled = true") - List findEnabledModelNames(); -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/OperationConfigRepository.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/OperationConfigRepository.java deleted file mode 100644 index aa911bc..0000000 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/persistence/repository/OperationConfigRepository.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.github.timemachinelab.sfchain.persistence.repository; - -import io.github.timemachinelab.sfchain.persistence.entity.OperationConfigEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -/** - * 描述: 操作配置Repository接口 - * 用于操作配置的数据库操作 - * - * @author suifeng - * 日期: 2025/1/27 - */ -@Repository -public interface OperationConfigRepository extends JpaRepository { - - /** - * 根据操作类型查找配置 - * @param operationType 操作类型 - * @return 操作配置 - */ - Optional findByOperationType(String operationType); - - /** - * 根据启用状态查找配置 - * @param enabled 启用状态 - * @return 操作配置列表 - */ - List findByEnabled(Boolean enabled); - - /** - * 检查操作类型是否存在配置 - * @param operationType 操作类型 - * @return 是否存在 - */ - boolean existsByOperationType(String operationType); - - /** - * 根据操作类型删除配置 - * @param operationType 操作类型 - */ - void deleteByOperationType(String operationType); - - /** - * 根据JSON输出模式查找配置 - * @param jsonOutput JSON输出模式 - * @return 操作配置列表 - */ - List findByJsonOutput(Boolean jsonOutput); - - /** - * 根据思考模式查找配置 - * @param thinkingMode 思考模式 - * @return 操作配置列表 - */ - List findByThinkingMode(Boolean thinkingMode); - - /** - * 获取所有操作类型 - * @return 操作类型列表 - */ - @Query("SELECT c.operationType FROM OperationConfigEntity c") - List findAllOperationTypes(); - - /** - * 获取所有启用的操作类型 - * @return 启用的操作类型列表 - */ - @Query("SELECT c.operationType FROM OperationConfigEntity c WHERE c.enabled = true") - List findEnabledOperationTypes(); -} \ No newline at end of file diff --git a/prompto-lab-app/src/main/resources/application.yml b/prompto-lab-app/src/main/resources/application.yml index 64ee53e..c4a9f5f 100644 --- a/prompto-lab-app/src/main/resources/application.yml +++ b/prompto-lab-app/src/main/resources/application.yml @@ -45,3 +45,14 @@ spring: open-in-view: false +# =========================================== +# SF-Chain AI框架配置 +# =========================================== +sf-chain: + # 授权配置 + auth-token: "suifeng666" # 授权Token(生产环境请修改) + authEnabled: true + persistence: + database-type: postgresql # 数据库类型 + + diff --git a/prompto-lab-app/src/test/java/io/github/timemachinelab/testnode/ainode/AIChatOperation.java b/prompto-lab-app/src/test/java/io/github/timemachinelab/testnode/ainode/AIChatOperation.java index de7a4b6..716fdd4 100644 --- a/prompto-lab-app/src/test/java/io/github/timemachinelab/testnode/ainode/AIChatOperation.java +++ b/prompto-lab-app/src/test/java/io/github/timemachinelab/testnode/ainode/AIChatOperation.java @@ -1,7 +1,8 @@ package io.github.timemachinelab.testnode.ainode; -import io.github.timemachinelab.sfchain.annotation.AIOp; -import io.github.timemachinelab.sfchain.core.BaseAIOperation; + +import com.suifeng.sfchain.annotation.AIOp; +import com.suifeng.sfchain.core.BaseAIOperation; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; From f47940b048183f718d25cf3c1a921dd888306ed2 Mon Sep 17 00:00:00 2001 From: suifeng <369202865@qq.com> Date: Sun, 24 Aug 2025 21:59:05 +0800 Subject: [PATCH 2/2] =?UTF-8?q?[dev]=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/suifeng/sfchain/annotation/AIOp.java | 71 + .../config/AuthorizationInterceptor.java | 74 + .../config/OpenAIAutoConfiguration.java | 74 + .../sfchain/config/OpenAIModelsConfig.java | 133 ++ .../config/SfChainAutoConfiguration.java | 100 + .../sfchain/config/SfChainWebConfig.java | 51 + .../com/suifeng/sfchain/config/WebConfig.java | 65 + .../constants/AIOperationConstant.java | 33 + .../controller/AICallLogController.java | 72 + .../sfchain/controller/AIModelController.java | 255 +++ .../controller/AIOperationController.java | 281 +++ .../controller/AISystemController.java | 134 ++ .../controller/SfChainConfigController.java | 40 + .../com/suifeng/sfchain/core/AIModel.java | 40 + .../sfchain/core/AIOperationRegistry.java | 152 ++ .../suifeng/sfchain/core/AIPromptBuilder.java | 155 ++ .../com/suifeng/sfchain/core/AIService.java | 520 +++++ .../suifeng/sfchain/core/BaseAIOperation.java | 621 ++++++ .../suifeng/sfchain/core/ModelRegistry.java | 71 + .../sfchain/core/logging/AICallLog.java | 71 + .../sfchain/core/logging/AICallLogAspect.java | 76 + .../core/logging/AICallLogManager.java | 238 +++ .../core/logging/AICallLogSummary.java | 81 + .../core/openai/OpenAICompatibleModel.java | 237 +++ .../sfchain/core/openai/OpenAIHttpClient.java | 256 +++ .../core/openai/OpenAIModelConfig.java | 120 ++ .../core/openai/OpenAIModelFactory.java | 79 + .../sfchain/core/openai/OpenAIRequest.java | 102 + .../sfchain/core/openai/OpenAIResponse.java | 108 ++ .../core/openai/OpenAIStreamResponse.java | 88 + .../operations/JSONRepairOperation.java | 140 ++ .../operations/ModelValidationOperation.java | 86 + .../DatabaseInitializationService.java | 125 ++ .../DynamicOperationConfigService.java | 82 + .../sfchain/persistence/ModelConfigData.java | 125 ++ .../persistence/MySQLPersistenceService.java | 314 +++ .../persistence/OperationConfigData.java | 143 ++ .../persistence/PersistenceManager.java | 674 +++++++ .../persistence/PersistenceService.java | 127 ++ .../PersistenceServiceFactory.java | 33 + .../PostgreSQLPersistenceService.java | 321 ++++ .../persistence/config/DatabaseType.java | 45 + .../persistence/config/PersistenceConfig.java | 29 + .../context/ChatContextService.java | 87 + .../persistence/context/ChatMessage.java | 62 + .../context/MapBasedChatContextService.java | 177 ++ .../persistence/entity/ModelConfigEntity.java | 79 + .../entity/OperationConfigEntity.java | 85 + .../repository/ModelConfigRepository.java | 68 + .../repository/OperationConfigRepository.java | 75 + .../src/components/Chat/AiNodeConfig.vue | 1711 ----------------- .../components/SFChain/AICallLogViewer.vue | 998 ---------- .../src/components/SFChain/ApiInfoConfig.vue | 1646 ---------------- .../src/components/SFChain/JsonViewer.vue | 569 ------ .../src/components/SFChain/LogDetailModal.vue | 1408 -------------- .../components/SFChain/SystemManagement.vue | 286 --- .../src/components/layout/AppHeader.vue | 9 - prompto-lab-ui/src/router/index.ts | 18 +- prompto-lab-ui/src/services/aiCallLogApi.ts | 53 - prompto-lab-ui/src/services/aiModelApi.ts | 49 - prompto-lab-ui/src/services/aiOperationApi.ts | 55 - prompto-lab-ui/src/services/systemApi.ts | 37 - prompto-lab-ui/src/views/ApiConfigView.vue | 898 --------- prompto-lab-ui/src/views/SettingsView.vue | 731 ------- 64 files changed, 7276 insertions(+), 8467 deletions(-) create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/annotation/AIOp.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/AuthorizationInterceptor.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIAutoConfiguration.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIModelsConfig.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainAutoConfiguration.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainWebConfig.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/WebConfig.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/constants/AIOperationConstant.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AICallLogController.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIModelController.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIOperationController.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AISystemController.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/SfChainConfigController.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIModel.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIOperationRegistry.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIPromptBuilder.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/BaseAIOperation.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/ModelRegistry.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLog.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogAspect.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogManager.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogSummary.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAICompatibleModel.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIHttpClient.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelConfig.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelFactory.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIRequest.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIResponse.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIStreamResponse.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/operations/JSONRepairOperation.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/operations/ModelValidationOperation.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DatabaseInitializationService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DynamicOperationConfigService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/ModelConfigData.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/MySQLPersistenceService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/OperationConfigData.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceManager.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceServiceFactory.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PostgreSQLPersistenceService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/DatabaseType.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/PersistenceConfig.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatContextService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatMessage.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/MapBasedChatContextService.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/ModelConfigEntity.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/OperationConfigEntity.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/ModelConfigRepository.java create mode 100644 prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/OperationConfigRepository.java delete mode 100644 prompto-lab-ui/src/components/Chat/AiNodeConfig.vue delete mode 100644 prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue delete mode 100644 prompto-lab-ui/src/components/SFChain/ApiInfoConfig.vue delete mode 100644 prompto-lab-ui/src/components/SFChain/JsonViewer.vue delete mode 100644 prompto-lab-ui/src/components/SFChain/LogDetailModal.vue delete mode 100644 prompto-lab-ui/src/components/SFChain/SystemManagement.vue delete mode 100644 prompto-lab-ui/src/services/aiCallLogApi.ts delete mode 100644 prompto-lab-ui/src/services/aiModelApi.ts delete mode 100644 prompto-lab-ui/src/services/aiOperationApi.ts delete mode 100644 prompto-lab-ui/src/services/systemApi.ts delete mode 100644 prompto-lab-ui/src/views/ApiConfigView.vue delete mode 100644 prompto-lab-ui/src/views/SettingsView.vue diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/annotation/AIOp.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/annotation/AIOp.java new file mode 100644 index 0000000..6766fcb --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/annotation/AIOp.java @@ -0,0 +1,71 @@ +package com.suifeng.sfchain.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 描述: AI操作注解 + * 用于标识AI操作类,并指定操作类型和默认模型 + * @author suifeng + * 日期: 2025/8/11 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AIOp { + + /** + * 操作类型标识 + * 例如: "POSITION_BASIC_INFO_PARSE_OP" + */ + String value(); + + /** + * 默认使用的模型名称 + * 如果不指定,将使用操作映射配置中的模型 + */ + String defaultModel() default ""; + + /** + * 操作描述 + */ + String description() default ""; + + /** + * 是否启用该操作 + */ + boolean enabled() default true; + + /** + * 支持的模型列表(可选) + * 如果指定,将限制该操作只能使用这些模型 + */ + String[] supportedModels() default {}; + + /** + * 是否需要JSON输出 + */ + boolean requireJsonOutput() default true; + + /** + * 是否自动修复JSON格式错误 + * 当requireJsonOutput为true且AI返回的JSON格式有误时,自动调用JSON修复操作 + */ + boolean autoRepairJson() default true; + + /** + * 是否支持思考模式 + */ + boolean supportThinking() default false; + + /** + * 默认最大token数 + */ + int defaultMaxTokens() default 4096; + + /** + * 默认温度参数 + */ + double defaultTemperature() default 0.7; +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/AuthorizationInterceptor.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/AuthorizationInterceptor.java new file mode 100644 index 0000000..b9867e6 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/AuthorizationInterceptor.java @@ -0,0 +1,74 @@ +package com.suifeng.sfchain.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * SF-Chain接口Authorization拦截器 + * 验证Authorization头是否为指定值 + * + * @author suifeng + */ +@Slf4j +@Component +public class AuthorizationInterceptor implements HandlerInterceptor { + + @Value("${sf-chain.auth-token:suifeng666}") + private String validToken; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String requestURI = request.getRequestURI(); + String method = request.getMethod(); + + // 跳过OPTIONS预检请求 + if ("OPTIONS".equalsIgnoreCase(method)) { + log.debug("跳过OPTIONS预检请求: {}", requestURI); + return true; + } + + log.debug("拦截请求: {} [{}]", requestURI, method); + + // 获取Authorization头,同时检查大小写版本 + String authorization = request.getHeader("Authorization"); + if (authorization == null || authorization.trim().isEmpty()) { + authorization = request.getHeader("authorization"); + } + + if (authorization == null || authorization.trim().isEmpty()) { + log.warn("请求缺少Authorization头: {} [{}]", requestURI, method); + sendUnauthorizedResponse(response, "缺少Authorization请求头"); + return false; + } + + // 验证token值 + String trimmedAuth = authorization.trim(); + if (!validToken.equals(trimmedAuth)) { + log.warn("Authorization验证失败: {} [{}] - 期望: [{}], 实际: [{}]", + requestURI, method, validToken, trimmedAuth); + sendUnauthorizedResponse(response, "Authorization验证失败"); + return false; + } + + log.debug("Authorization验证通过: {} [{}]", requestURI, method); + return true; + } + + private void sendUnauthorizedResponse(HttpServletResponse response, String message) throws IOException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write(String.format( + "{\"error\":\"%s\",\"code\":%d,\"message\":\"%s\"}", + "Unauthorized", + HttpStatus.UNAUTHORIZED.value(), + message + )); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIAutoConfiguration.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIAutoConfiguration.java new file mode 100644 index 0000000..d22596e --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIAutoConfiguration.java @@ -0,0 +1,74 @@ +package com.suifeng.sfchain.config; + +import com.suifeng.sfchain.core.AIModel; +import com.suifeng.sfchain.core.openai.OpenAIModelConfig; +import com.suifeng.sfchain.core.openai.OpenAIModelFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 描述: OpenAI兼容模型自动配 + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@Configuration +@EnableConfigurationProperties(OpenAIModelsConfig.class) +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "ai.openai-models", name = "enabled", havingValue = "true", matchIfMissing = true) +public class OpenAIAutoConfiguration { + + private final OpenAIModelsConfig openAIModelsConfig; + + /** + * 创建OpenAI模型工厂 + */ + @Bean + @Primary + public OpenAIModelFactory openAIModelFactory() { + OpenAIModelFactory factory = new OpenAIModelFactory(); + + // 注册配置文件中的模型 + Map modelConfigs = openAIModelsConfig.getValidModelConfigs(); + modelConfigs.forEach((name, config) -> { + try { + factory.registerModel(config); + log.info("成功注册模型: {} ({})", config.getModelName(), config.getProvider()); + } catch (Exception e) { + log.error("注册模型失败: {} - {}", config.getModelName(), e.getMessage()); + } + }); + + return factory; + } + + /** + * 创建AI模型列表,供ModelRegistry使用 + */ + @Bean + public List aiModels(OpenAIModelFactory factory) { + List models = new ArrayList<>(); + + // 为每个注册的模型创建实例 + factory.getRegisteredModelNames().forEach(modelName -> { + try { + AIModel model = factory.createModel(modelName); + models.add(model); + log.info("创建AI模型实例: {}", modelName); + } catch (Exception e) { + log.error("创建模型实例失败: {} - {}", modelName, e.getMessage()); + } + }); + + return models; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIModelsConfig.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIModelsConfig.java new file mode 100644 index 0000000..30f5ca1 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/OpenAIModelsConfig.java @@ -0,0 +1,133 @@ +package com.suifeng.sfchain.config; + +import com.suifeng.sfchain.core.openai.OpenAIModelConfig; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 描述: OpenAI兼容模型配置 + * @author suifeng + * 日期: 2025/8/11 + */ +@Data +@Component +@ConfigurationProperties(prefix = "ai.openai-models") +public class OpenAIModelsConfig { + + /** + * 模型配置映射 + * key: 模型名称 + * value: 模型配置 + */ + private Map models = new HashMap<>(); + + /** + * 模型配置属性 + */ + @Data + public static class ModelConfigProperties { + /** + * 模型名称 + */ + private String modelName; + + /** + * API基础URL + */ + private String baseUrl; + + /** + * API密钥 + */ + private String apiKey; + + /** + * 默认最大token数 + */ + private Integer defaultMaxTokens = 4096; + + /** + * 默认温度参数 + */ + private Double defaultTemperature = 0.7; + + /** + * 是否支持流式输出 + */ + private Boolean supportStream = false; + + /** + * 是否支持JSON格式输出 + */ + private Boolean supportJsonOutput = false; + + /** + * 是否支持思考模式 + */ + private Boolean supportThinking = false; + + /** + * 额外的HTTP请求头 + */ + private Map additionalHeaders = new HashMap<>(); + + /** + * 模型描述 + */ + private String description; + + /** + * 模型提供商 + */ + private String provider; + + /** + * 是否启用 + */ + private Boolean enabled = true; + + /** + * 转换为OpenAIModelConfig + */ + public OpenAIModelConfig toOpenAIModelConfig() { + return OpenAIModelConfig.builder() + .modelName(modelName) + .baseUrl(baseUrl) + .apiKey(apiKey) + .defaultMaxTokens(defaultMaxTokens) + .defaultTemperature(defaultTemperature) + .supportStream(supportStream) + .supportJsonOutput(supportJsonOutput) + .supportThinking(supportThinking) + .additionalHeaders(additionalHeaders) + .description(description) + .provider(provider) + .enabled(enabled) + .build(); + } + } + + /** + * 获取所有有效的模型配置 + */ + public Map getValidModelConfigs() { + Map validConfigs = new HashMap<>(); + + models.forEach((key, properties) -> { + if (properties.getModelName() == null) { + properties.setModelName(key); + } + + OpenAIModelConfig config = properties.toOpenAIModelConfig(); + if (config.isValid() && Boolean.TRUE.equals(config.getEnabled())) { + validConfigs.put(key, config); + } + }); + + return validConfigs; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainAutoConfiguration.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainAutoConfiguration.java new file mode 100644 index 0000000..9d2d1af --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainAutoConfiguration.java @@ -0,0 +1,100 @@ +package com.suifeng.sfchain.config; + +import com.suifeng.sfchain.core.*; +import com.suifeng.sfchain.core.openai.OpenAIModelFactory; +import com.suifeng.sfchain.persistence.DatabaseInitializationService; +import com.suifeng.sfchain.persistence.DynamicOperationConfigService; +import com.suifeng.sfchain.persistence.PersistenceManager; +import com.suifeng.sfchain.persistence.PersistenceServiceFactory; +import com.suifeng.sfchain.persistence.config.PersistenceConfig; +import com.suifeng.sfchain.persistence.context.ChatContextService; +import com.suifeng.sfchain.persistence.context.MapBasedChatContextService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; + +/** + * SF-Chain 自动配置类 + * 提供框架的自动装配功能 + * + * @author suifeng + */ +@Slf4j +@AutoConfiguration +@ComponentScan(basePackages = "com.suifeng.sfchain") +@EnableJpaRepositories(basePackages = "com.suifeng.sfchain.persistence.repository") +@EntityScan(basePackages = "com.suifeng.sfchain.persistence.entity") +@EnableConfigurationProperties(PersistenceConfig.class) +@ConditionalOnProperty(prefix = "sf-chain", name = "enabled", havingValue = "true", matchIfMissing = true) +public class SfChainAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public AIOperationRegistry aiOperationRegistry() { + log.info("初始化SF-Chain AI操作注册表"); + return new AIOperationRegistry(); + } + + @Bean + @ConditionalOnMissingBean + public ModelRegistry modelRegistry(OpenAIModelFactory openAIModelFactory) { + log.info("初始化SF-Chain 模型注册表"); + return new ModelRegistry(openAIModelFactory); + } + + @Bean + @ConditionalOnMissingBean + public AIService aiService() { + log.info("初始化SF-Chain AI服务"); + return new AIService(); + } + + @Bean + @ConditionalOnMissingBean + public ChatContextService chatContextService() { + log.info("初始化SF-Chain 聊天上下文服务"); + return new MapBasedChatContextService(); + } + + @Bean + @ConditionalOnMissingBean + public PersistenceManager persistenceManager( + PersistenceServiceFactory persistenceServiceFactory, + ModelRegistry modelRegistry, + AIOperationRegistry operationRegistry, + OpenAIModelFactory modelFactory, + DynamicOperationConfigService dynamicOperationConfigService) { + log.info("初始化SF-Chain 持久化管理器"); + return new PersistenceManager( + persistenceServiceFactory, + modelRegistry, + operationRegistry, + modelFactory, + dynamicOperationConfigService + ); + } + + /** + * 数据库初始化服务 + * 只有在配置了数据库类型时才会创建 + */ + @Bean + @ConditionalOnProperty(prefix = "sf-chain.persistence", name = "database-type") + @ConditionalOnMissingBean + public DatabaseInitializationService databaseInitializationService( + PersistenceConfig persistenceConfig, + DataSource dataSource, + JdbcTemplate jdbcTemplate) { + log.info("初始化SF-Chain 数据库初始化服务"); + return new DatabaseInitializationService(persistenceConfig, dataSource, jdbcTemplate); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainWebConfig.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainWebConfig.java new file mode 100644 index 0000000..4cbdd57 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/SfChainWebConfig.java @@ -0,0 +1,51 @@ +package com.suifeng.sfchain.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * SF-Chain Web配置 + * 注册Authorization拦截器 + * + * @author suifeng + */ +@Slf4j +@Configuration +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "sf-chain", name = "authEnabled", havingValue = "true") +public class SfChainWebConfig implements WebMvcConfigurer { + + private final AuthorizationInterceptor authorizationInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + log.info("注册SF-Chain Authorization拦截器"); + + registry.addInterceptor(authorizationInterceptor) + .addPathPatterns("/sf-chain/**") + .excludePathPatterns( + "/sf-chain/config/**", // 排除配置接口 + "/**/*.js", // 排除JS文件 + "/**/*.css", // 排除CSS文件 + "/**/*.html", // 排除HTML文件 + "/**/*.ico", // 排除图标文件 + "/**/*.svg", // 排除SVG文件 + "/**/*.png", // 排除PNG文件 + "/**/*.jpg", // 排除JPG文件 + "/**/*.jpeg", // 排除JPEG文件 + "/**/*.gif", // 排除GIF文件 + "/**/*.woff", // 排除字体文件 + "/**/*.woff2", // 排除字体文件 + "/**/*.ttf", // 排除字体文件 + "/assets/**", // 排除静态资源 + "/static/**", // 排除静态资源 + "/", // 排除根路径 + "/index.html" // 排除首页 + ) + .order(1); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/WebConfig.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/WebConfig.java new file mode 100644 index 0000000..60f7f2c --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/config/WebConfig.java @@ -0,0 +1,65 @@ +package com.suifeng.sfchain.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.resource.PathResourceResolver; + +import java.io.IOException; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 配置静态资源处理 + registry.addResourceHandler("/assets/**") + .addResourceLocations("classpath:/static/assets/") + .setCachePeriod(3600); + + registry.addResourceHandler("/favicon.ico") + .addResourceLocations("classpath:/static/favicon.ico") + .setCachePeriod(3600); + + registry.addResourceHandler("/icons/**") + .addResourceLocations("classpath:/static/icons/") + .setCachePeriod(3600); + + // 配置SPA路由支持 + registry.addResourceHandler("/**") + .addResourceLocations("classpath:/static/") + .resourceChain(true) + .addResolver(new PathResourceResolver() { + @Override + protected Resource getResource(String resourcePath, Resource location) throws IOException { + Resource requestedResource = location.createRelative(resourcePath); + + // 如果请求的资源存在,直接返回 + if (requestedResource.exists() && requestedResource.isReadable()) { + return requestedResource; + } + + // 排除API请求,对于SPA路由返回index.html + if (!resourcePath.startsWith("api/") && + !resourcePath.startsWith("sf-chain/") && + !resourcePath.contains(".")) { // 不包含文件扩展名的请求视为路由 + Resource indexHtml = new ClassPathResource("/static/index.html"); + if (indexHtml.exists()) { + return indexHtml; + } + } + + return null; + } + }); + } + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + // 显式配置根路径映射到index.html + registry.addViewController("/").setViewName("forward:/index.html"); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/constants/AIOperationConstant.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/constants/AIOperationConstant.java new file mode 100644 index 0000000..c07c95f --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/constants/AIOperationConstant.java @@ -0,0 +1,33 @@ +package com.suifeng.sfchain.constants; + +/** + * 描述: AI操作常量定义 + * 定义所有AI操作的标识符 + * + * @author suifeng + * 日期: 2025/8/11 + */ +public class AIOperationConstant { + + /** + * JSON修复操作 + */ + public static final String JSON_REPAIR_OP = "JSON_REPAIR_OP"; + + /** + * 模型验证操作 + */ + public static final String MODEL_VALIDATION_OP = "MODEL_VALIDATION_OP"; + + /** + * 聊天对话操作 + */ + public static final String CHAT_OP = "CHAT"; + + /** + * 私有构造函数,防止实例化 + */ + private AIOperationConstant() { + throw new UnsupportedOperationException("常量类不允许实例化"); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AICallLogController.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AICallLogController.java new file mode 100644 index 0000000..237cbbf --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AICallLogController.java @@ -0,0 +1,72 @@ +package com.suifeng.sfchain.controller; + +import com.suifeng.sfchain.core.logging.AICallLog; +import com.suifeng.sfchain.core.logging.AICallLogManager; +import com.suifeng.sfchain.core.logging.AICallLogSummary; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +/** + * AI调用日志查询控制器 + */ +@RestController +@RequestMapping("/sf-chain/ai-logs") +public class AICallLogController { + + @Resource + private AICallLogManager logManager; + + /** + * 获取所有日志摘要(轻量级) + */ + @GetMapping + public ResponseEntity> getAllLogSummaries() { + return ResponseEntity.ok(logManager.getAllLogSummaries()); + } + + /** + * 根据调用ID获取完整日志详情 + */ + @GetMapping("/{callId}") + public ResponseEntity getFullLog(@PathVariable String callId) { + AICallLog log = logManager.getFullLog(callId); + return log != null ? ResponseEntity.ok(log) : ResponseEntity.notFound().build(); + } + + /** + * 根据操作类型获取日志摘要(轻量级) + */ + @GetMapping("/operation/{operationType}") + public ResponseEntity> getLogSummariesByOperation(@PathVariable String operationType) { + return ResponseEntity.ok(logManager.getLogSummariesByOperation(operationType)); + } + + /** + * 根据模型名称获取日志摘要(轻量级) + */ + @GetMapping("/model/{modelName}") + public ResponseEntity> getLogSummariesByModel(@PathVariable String modelName) { + return ResponseEntity.ok(logManager.getLogSummariesByModel(modelName)); + } + + /** + * 获取统计信息 + */ + @GetMapping("/statistics") + public ResponseEntity getStatistics() { + return ResponseEntity.ok(logManager.getStatistics()); + } + + /** + * 清空所有日志 + */ + @DeleteMapping + public ResponseEntity> clearLogs() { + logManager.clearLogs(); + return ResponseEntity.ok(Map.of("message", "所有日志已清空")); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIModelController.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIModelController.java new file mode 100644 index 0000000..2b1c08b --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIModelController.java @@ -0,0 +1,255 @@ +package com.suifeng.sfchain.controller; + +import com.suifeng.sfchain.core.AIOperationRegistry; +import com.suifeng.sfchain.core.openai.OpenAIModelConfig; +import com.suifeng.sfchain.core.openai.OpenAIModelFactory; +import com.suifeng.sfchain.operations.ModelValidationOperation; +import com.suifeng.sfchain.persistence.ModelConfigData; +import com.suifeng.sfchain.persistence.PersistenceManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.suifeng.sfchain.constants.AIOperationConstant.MODEL_VALIDATION_OP; + +/** + * 描述: AI模型配置管理控制器 + * 提供AI模型的增删改查、测试验证等功能 + * + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@RestController +@RequestMapping("/sf-chain/models") +@RequiredArgsConstructor +public class AIModelController { + + private final PersistenceManager persistenceManager; + private final AIOperationRegistry operationRegistry; + private final OpenAIModelFactory modelFactory; + + /** + * 获取所有模型配置(包含状态信息) + */ + @GetMapping("/list") + public ResponseEntity> getAllModels() { + try { + Map models = persistenceManager.getAllModelConfigs(); + Map result = new HashMap<>(); + + // 按提供商分组 + Map> groupedByProvider = models.values().stream() + .collect(Collectors.groupingBy(m -> m.getProvider() != null ? m.getProvider() : "未知")); + + result.put("models", models); + result.put("groupedByProvider", groupedByProvider); + result.put("total", models.size()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("获取模型列表失败: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "获取模型列表失败: " + e.getMessage())); + } + } + + /** + * 获取单个模型配置 + */ + @GetMapping("/{modelName}") + public ResponseEntity getModel(@PathVariable String modelName) { + try { + Map models = persistenceManager.getAllModelConfigs(); + ModelConfigData model = models.get(modelName); + + if (model == null) { + return ResponseEntity.notFound().build(); + } + + return ResponseEntity.ok(model); + } catch (Exception e) { + log.error("获取模型配置失败: {} - {}", modelName, e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "获取模型配置失败: " + e.getMessage())); + } + } + + /** + * 创建或更新模型配置 + */ + @PostMapping("/save") + public ResponseEntity> saveModel( + @Valid @RequestBody ModelConfigData config) { + Map result = new HashMap<>(); + String modelName = config.getModelName(); + try { + // 检查模型是否已存在 + Map existingModels = persistenceManager.getAllModelConfigs(); + boolean modelExists = existingModels.containsKey(modelName); + + // 验证模型配置(临时验证,不影响现有模型) + boolean validationResult = validateModelConfig(modelName, config, true); + + if (!validationResult) { + result.put("success", false); + result.put("message", "模型验证失败,请检查配置参数"); + return ResponseEntity.badRequest().body(result); + } + + // 根据模型是否存在选择添加或更新 + if (modelExists) { + persistenceManager.updateModelConfig(modelName, config); + result.put("operation", "updated"); + result.put("message", "模型配置更新成功"); + log.info("模型配置已更新: {}", modelName); + } else { + persistenceManager.addModelConfig(modelName, config); + result.put("operation", "created"); + result.put("message", "模型配置创建成功"); + log.info("模型配置已创建: {}", modelName); + } + + result.put("success", true); + result.put("modelName", modelName); + result.put("validated", true); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("保存模型配置失败: {} - {}", modelName, e.getMessage()); + result.put("success", false); + result.put("message", "保存失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 测试模型连接 + */ + @PostMapping("/test") + public ResponseEntity> testModel(@RequestBody Map request) { + String modelName = request.get("modelName"); + Map result = new HashMap<>(); + try { + Map models = persistenceManager.getAllModelConfigs(); + ModelConfigData config = models.get(modelName); + + if (config == null) { + result.put("success", false); + result.put("message", "模型配置不存在"); + return ResponseEntity.notFound().build(); + } + + // 测试已存在的模型,不需要临时注册 + boolean testResult = validateModelConfig(modelName, config, false); + result.put("success", testResult); + result.put("message", testResult ? "模型连接测试成功" : "模型连接测试失败"); + result.put("modelName", modelName); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("测试模型连接失败: {} - {}", modelName, e.getMessage()); + result.put("success", false); + result.put("message", "测试失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + // ==================== 私有方法 ==================== + + /** + * 验证模型配置是否可用 + * @param modelName 模型名称 + * @param config 模型配置 + * @param isTemporaryValidation 是否为临时验证(true: 验证后移除临时模型,false: 验证已存在的模型) + */ + private boolean validateModelConfig(String modelName, ModelConfigData config, boolean isTemporaryValidation) { + boolean tempRegistered = false; + try { + log.info("开始验证模型配置: {} (临时验证: {})", modelName, isTemporaryValidation); + + ModelValidationOperation validationOp = (ModelValidationOperation) operationRegistry.getOperation(MODEL_VALIDATION_OP); + if (validationOp == null) { + log.warn("未找到模型验证操作,跳过验证"); + return true; + } + + // 如果是临时验证,需要临时注册模型 + if (isTemporaryValidation) { + OpenAIModelConfig tempConfig = convertToOpenAIConfig(config); + modelFactory.registerModel(tempConfig); + tempRegistered = true; + } + + ModelValidationOperation.ValidationRequest request = + new ModelValidationOperation.ValidationRequest("请回答:2+3等于几?"); + + ModelValidationOperation.ValidationResult result = validationOp.execute(request, modelName); + + return result != null && result.getAnswer() != null && !result.getAnswer().trim().isEmpty(); + + } catch (Exception e) { + log.error("模型验证失败: {} - {}", modelName, e.getMessage()); + return false; + } finally { + // 只有在临时验证时才清理临时模型 + if (tempRegistered && isTemporaryValidation) { + try { + modelFactory.removeModel(modelName); + log.debug("已清理临时模型配置: {}", modelName); + } catch (Exception e) { + log.warn("清理临时模型配置失败: {}", e.getMessage()); + } + } + } + } + + + /** + * 删除模型配置 + */ + @DeleteMapping("/{modelName}") + public ResponseEntity> deleteModel(@PathVariable String modelName) { + Map result = new HashMap<>(); + try { + persistenceManager.deleteModelConfig(modelName); + result.put("success", true); + result.put("message", "模型配置删除成功"); + result.put("modelName", modelName); + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("删除模型配置失败: {} - {}", modelName, e.getMessage()); + result.put("success", false); + result.put("message", "删除失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 转换配置格式 + */ + private OpenAIModelConfig convertToOpenAIConfig(ModelConfigData config) { + return OpenAIModelConfig.builder() + .modelName(config.getModelName()) + .baseUrl(config.getBaseUrl()) + .apiKey(config.getApiKey()) + .defaultMaxTokens(config.getDefaultMaxTokens()) + .defaultTemperature(config.getDefaultTemperature()) + .supportStream(config.getSupportStream()) + .supportJsonOutput(config.getSupportJsonOutput()) + .supportThinking(config.getSupportThinking()) + .additionalHeaders(config.getAdditionalHeaders()) + .description(config.getDescription()) + .provider(config.getProvider()) + .enabled(config.getEnabled()) + .build(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIOperationController.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIOperationController.java new file mode 100644 index 0000000..cc68df3 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AIOperationController.java @@ -0,0 +1,281 @@ +package com.suifeng.sfchain.controller; + +import com.suifeng.sfchain.persistence.ModelConfigData; +import com.suifeng.sfchain.persistence.OperationConfigData; +import com.suifeng.sfchain.persistence.PersistenceManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * 描述: AI操作配置管理控制器 + * 提供AI操作的配置管理、模型映射等功能 + * + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@RestController +@RequestMapping("/sf-chain/operations") +@RequiredArgsConstructor +public class AIOperationController { + + private final PersistenceManager persistenceManager; + + /** + * 获取所有AI操作及其配置状态 + */ + @GetMapping + public ResponseEntity> getAllOperations() { + try { + Map configs = persistenceManager.getAllOperationConfigs(); + + // 构建操作模型映射信息(从操作配置中提取) + Map mappings = new HashMap<>(); + configs.forEach((operationType, config) -> { + if (config.getModelName() != null && !config.getModelName().isEmpty()) { + mappings.put(operationType, config.getModelName()); + } + }); + + Map result = new HashMap<>(); + result.put("mappings", mappings); + result.put("configs", configs); + result.put("totalOperations", configs.size()); + result.put("configuredOperations", mappings.size()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("获取操作列表失败: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "获取操作列表失败: " + e.getMessage())); + } + } + + /** + * 获取操作配置(包含关联的模型信息) + */ + @GetMapping("/{operationType}") + public ResponseEntity getOperation(@PathVariable String operationType) { + try { + Optional operationOpt = persistenceManager.getOperationConfig(operationType); + + if (operationOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + OperationConfigData operation = operationOpt.get(); + + // 如果有关联模型,获取模型信息 + if (operation.getModelName() != null) { + Map models = persistenceManager.getAllModelConfigs(); + ModelConfigData model = models.get(operation.getModelName()); + + Map result = new HashMap<>(); + result.put("operation", operation); + result.put("associatedModel", model); + return ResponseEntity.ok(result); + } + + return ResponseEntity.ok(operation); + } catch (Exception e) { + log.error("获取操作配置失败: {} - {}", operationType, e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "获取操作配置失败: " + e.getMessage())); + } + } + + /** + * 保存操作配置 - 改为统一的save接口,从请求体获取operationType + */ + @PostMapping("/save") + public ResponseEntity> saveOperationConfig( + @Valid @RequestBody OperationConfigData config) { + Map result = new HashMap<>(); + try { + // 从config对象中获取operationType + String operationType = config.getOperationType(); + + if (operationType == null || operationType.trim().isEmpty()) { + result.put("success", false); + result.put("message", "操作类型不能为空"); + return ResponseEntity.badRequest().body(result); + } + + persistenceManager.saveOperationConfig(operationType, config); + + result.put("success", true); + result.put("message", "操作配置保存成功"); + result.put("operationType", operationType); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("保存操作配置失败: {}", e.getMessage()); + result.put("success", false); + result.put("message", "保存失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 获取单个操作配置 - 改为POST请求体参数 + */ + @PostMapping("/get") + public ResponseEntity getOperation(@RequestBody Map request) { + try { + String operationType = request.get("operationType"); + + if (operationType == null || operationType.trim().isEmpty()) { + return ResponseEntity.badRequest() + .body(Map.of("error", "操作类型不能为空")); + } + + Optional operationOpt = persistenceManager.getOperationConfig(operationType); + + if (operationOpt.isEmpty()) { + return ResponseEntity.notFound().build(); + } + + OperationConfigData operation = operationOpt.get(); + + // 如果有关联模型,获取模型信息 + if (operation.getModelName() != null) { + Map models = persistenceManager.getAllModelConfigs(); + ModelConfigData model = models.get(operation.getModelName()); + + Map result = new HashMap<>(); + result.put("operation", operation); + result.put("associatedModel", model); + return ResponseEntity.ok(result); + } + + return ResponseEntity.ok(operation); + } catch (Exception e) { + log.error("获取操作配置失败: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "获取操作配置失败: " + e.getMessage())); + } + } + + /** + * 批量设置操作模型映射 + */ + @PostMapping("/mappings") + public ResponseEntity> setOperationMappings( + @RequestBody Map mappings) { + Map result = new HashMap<>(); + int successCount = 0; + int failCount = 0; + Map errors = new HashMap<>(); + + try { + for (Map.Entry entry : mappings.entrySet()) { + try { + String operationType = entry.getKey(); + String modelName = entry.getValue(); + + // 获取现有操作配置 + Optional configOpt = persistenceManager.getOperationConfig(operationType); + OperationConfigData config; + + if (configOpt.isPresent()) { + config = configOpt.get(); + config.setModelName(modelName); + } else { + // 创建新的操作配置 + config = new OperationConfigData(); + config.setModelName(modelName); + config.setEnabled(true); + config.setDescription("通过映射设置创建的配置"); + } + + persistenceManager.saveOperationConfig(operationType, config); + successCount++; + } catch (Exception e) { + failCount++; + errors.put(entry.getKey(), e.getMessage()); + } + } + + result.put("success", failCount == 0); + result.put("successCount", successCount); + result.put("failCount", failCount); + result.put("total", mappings.size()); + + if (failCount > 0) { + result.put("errors", errors); + result.put("message", "部分操作映射设置失败"); + } else { + result.put("message", "所有操作映射设置成功"); + } + + return failCount > 0 ? ResponseEntity.badRequest().body(result) : ResponseEntity.ok(result); + } catch (Exception e) { + log.error("设置操作映射失败: {}", e.getMessage()); + result.put("success", false); + result.put("message", "设置失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 设置单个操作模型映射 - 改为POST请求体参数 + */ + @PostMapping("/mapping") + public ResponseEntity> setOperationMapping( + @RequestBody Map request) { + Map result = new HashMap<>(); + try { + String operationType = request.get("operationType"); + String modelName = request.get("modelName"); + + if (operationType == null || operationType.trim().isEmpty()) { + result.put("success", false); + result.put("message", "操作类型不能为空"); + return ResponseEntity.badRequest().body(result); + } + + if (modelName == null || modelName.trim().isEmpty()) { + result.put("success", false); + result.put("message", "模型名称不能为空"); + return ResponseEntity.badRequest().body(result); + } + + // 获取现有操作配置 + Optional configOpt = persistenceManager.getOperationConfig(operationType); + OperationConfigData config; + + if (configOpt.isPresent()) { + config = configOpt.get(); + config.setModelName(modelName); + } else { + // 创建新的操作配置 + config = new OperationConfigData(); + config.setModelName(modelName); + config.setEnabled(true); + config.setDescription("通过映射设置创建的配置"); + } + + persistenceManager.saveOperationConfig(operationType, config); + + result.put("success", true); + result.put("message", "操作映射设置成功"); + result.put("operationType", operationType); + result.put("modelName", modelName); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("设置操作映射失败: {}", e.getMessage()); + result.put("success", false); + result.put("message", "设置失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AISystemController.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AISystemController.java new file mode 100644 index 0000000..5c510d4 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/AISystemController.java @@ -0,0 +1,134 @@ +package com.suifeng.sfchain.controller; + +import com.suifeng.sfchain.persistence.ModelConfigData; +import com.suifeng.sfchain.persistence.OperationConfigData; +import com.suifeng.sfchain.persistence.PersistenceManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * 描述: AI系统管理控制器 + * 提供系统概览、备份、刷新、重置等系统级功能 + * + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@RestController +@RequestMapping("/sf-chain/system") +@RequiredArgsConstructor +public class AISystemController { + + private final PersistenceManager persistenceManager; + + /** + * 获取AI系统概览信息 + */ + @GetMapping("/overview") + public ResponseEntity> getSystemOverview() { + Map overview = new HashMap<>(); + try { + // 模型统计 + Map models = persistenceManager.getAllModelConfigs(); + overview.put("totalModels", models.size()); + overview.put("enabledModels", models.values().stream() + .mapToInt(m -> Boolean.TRUE.equals(m.getEnabled()) ? 1 : 0).sum()); + + // 操作统计 + Map configs = persistenceManager.getAllOperationConfigs(); + overview.put("totalOperations", configs.size()); + overview.put("configuredOperations", configs.values().stream() + .mapToInt(config -> config.getModelName() != null && !config.getModelName().isEmpty() ? 1 : 0).sum()); + + // 配置统计 + overview.put("totalConfigs", configs.size()); + + // 系统状态 + overview.put("systemStatus", "running"); + overview.put("lastUpdate", System.currentTimeMillis()); + + return ResponseEntity.ok(overview); + } catch (Exception e) { + log.error("获取系统概览失败: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("error", "获取系统概览失败: " + e.getMessage())); + } + } + + /** + * 系统配置备份 + */ + @PostMapping("/backup") + public ResponseEntity> createBackup() { + Map result = new HashMap<>(); + try { + String backupName = "backup_" + System.currentTimeMillis(); + persistenceManager.createBackup(backupName); + + result.put("success", true); + result.put("message", "配置备份创建成功"); + result.put("backupName", backupName); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("创建备份失败: {}", e.getMessage()); + result.put("success", false); + result.put("message", "备份失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 刷新系统配置 + */ + @PostMapping("/refresh") + public ResponseEntity> refreshSystem() { + Map result = new HashMap<>(); + try { + persistenceManager.flushConfigurations(); + + result.put("success", true); + result.put("message", "系统配置刷新成功"); + result.put("timestamp", System.currentTimeMillis()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("刷新系统配置失败: {}", e.getMessage()); + result.put("success", false); + result.put("message", "刷新失败: " + e.getMessage()); + return ResponseEntity.badRequest().body(result); + } + } + + /** + * 重置系统配置 + */ + @PostMapping("/reset") + public ResponseEntity> resetSystem() { + Map result = new HashMap<>(); + try { + // 重新加载配置(替代原来的resetOperationMappingsToDefault方法) + persistenceManager.reloadConfigurations(); + + result.put("success", true); + result.put("message", "系统配置重置成功"); + result.put("timestamp", System.currentTimeMillis()); + + return ResponseEntity.ok(result); + } catch (Exception e) { + log.error("重置系统配置失败: {}", e.getMessage()); + result.put("success", false); + result.put("message", "重置失败: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/SfChainConfigController.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/SfChainConfigController.java new file mode 100644 index 0000000..e4fb00f --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/controller/SfChainConfigController.java @@ -0,0 +1,40 @@ +package com.suifeng.sfchain.controller; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/sf-chain/config") +public class SfChainConfigController { + + @Value("${server.port:8080}") + private String serverPort; + + @Value("${server.servlet.context-path:}") + private String contextPath; + + @GetMapping("/api-info") + public Map getApiInfo() { + Map config = new HashMap<>(); + + // 构建基础URL + String baseUrl = contextPath.isEmpty() ? "" : contextPath; + + config.put("baseUrl", baseUrl); + config.put("port", serverPort); + config.put("contextPath", contextPath); + config.put("endpoints", Map.of( + "AI_MODELS", baseUrl + "/sf-chain/models", + "AI_OPERATIONS", baseUrl + "/sf-chain/operations", + "AI_CALL_LOGS", baseUrl + "/sf-chain/call-logs", + "AI_SYSTEM", baseUrl + "/sf-chain/system" + )); + + return config; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIModel.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIModel.java new file mode 100644 index 0000000..e67b2b6 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIModel.java @@ -0,0 +1,40 @@ +package com.suifeng.sfchain.core; + +/** + * 描述: AI模型接口 + * @author suifeng + * 日期: 2025/8/11 + */ +public interface AIModel { + + /** + * 获取模型名称 + */ + String getName(); + + /** + * 获取模型描述 + */ + String description(); + + /** + * 生成文本响应 + * @param prompt 提示词 + * @return 生成的文本 + */ + String generate(String prompt); + + /** + * 生成指定类型的响应 + * @param prompt 提示词 + * @param responseType 响应类型 + * @return 生成的响应对象 + */ + T generate(String prompt, Class responseType); + + /** + * 检查模型是否可用 + * @return 是否可用 + */ + boolean isAvailable(); +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIOperationRegistry.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIOperationRegistry.java new file mode 100644 index 0000000..33cbe6f --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIOperationRegistry.java @@ -0,0 +1,152 @@ +package com.suifeng.sfchain.core; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 描述: AI操作注册中心 - 新框架版本 + * 管理AI操作和模型的映射关系 + * + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@Component +@ConfigurationProperties(prefix = "ai.operations") +public class AIOperationRegistry { + + /** + * 操作到实例的映射 + */ + private final Map> operationMap = new ConcurrentHashMap<>(); + + /** + * 操作到模型的映射配置 + * -- GETTER -- + * 获取模型映射配置(用于配置文件绑定) + * -- SETTER -- + * 设置模型映射配置(用于配置文件绑定) + */ + @Setter + @Getter + private Map modelMapping = new ConcurrentHashMap<>(); + + /** + * 操作的默认配置 + * -- GETTER -- + * 获取操作配置(用于配置文件绑定) + * -- SETTER -- + * 设置操作配置(用于配置文件绑定) + */ + @Setter + @Getter + private Map configs = new ConcurrentHashMap<>(); + + @Resource + private ModelRegistry modelRegistry; + + /** + * 注册操作 + * + * @param operationType 操作类型 + * @param operation 操作实例 + */ + public void registerOperation(String operationType, BaseAIOperation operation) { + operationMap.put(operationType, operation); + log.info("注册AI操作: {} -> {}", operationType, operation.getClass().getSimpleName()); + } + + /** + * 获取操作实例 + * + * @param operationType 操作类型 + * @return 操作实例 + */ + public BaseAIOperation getOperation(String operationType) { + BaseAIOperation operation = operationMap.get(operationType); + if (operation == null) { + throw new IllegalArgumentException("未找到操作: " + operationType); + } + return operation; + } + + /** + * 获取操作对应的模型 + * + * @param operationType 操作类型 + * @return 模型名称 + */ + public String getModelForOperation(String operationType) { + // 优先从内存缓存获取 + return modelMapping.get(operationType); + } + + /** + * 设置操作的模型映射 + * + * @param operationType 操作类型 + * @param modelName 模型名称 + */ + public void setModelForOperation(String operationType, String modelName) { + // 验证模型是否存在 + AIModel model = modelRegistry.getModel(modelName); + if (model == null) { + throw new IllegalArgumentException("模型不存在: " + modelName); + } + + modelMapping.put(operationType, modelName); + log.info("设置操作模型映射: {} -> {}", operationType, modelName); + } + + /** + * 获取操作配置 + * + * @param operationType 操作类型 + * @return 操作配置 + */ + public OperationConfig getOperationConfig(String operationType) { + return configs.getOrDefault(operationType, new OperationConfig()); + } + + /** + * 获取所有已注册的操作 + * + * @return 操作类型列表 + */ + public List getAllOperations() { + return List.copyOf(operationMap.keySet()); + } + + /** + * 检查操作是否已注册 + * + * @param operationType 操作类型 + * @return 是否已注册 + */ + public boolean isOperationRegistered(String operationType) { + return operationMap.containsKey(operationType); + } + + /** + * 操作配置类 + */ + @Setter + @Getter + public static class OperationConfig { + private boolean enabled = true; + private int maxTokens = 4096; + private double temperature = 0.7; + private boolean requireJsonOutput = true; + private boolean supportThinking = false; + private int timeoutSeconds = 30; + private int retryCount = 2; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIPromptBuilder.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIPromptBuilder.java new file mode 100644 index 0000000..b00848b --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIPromptBuilder.java @@ -0,0 +1,155 @@ +package com.suifeng.sfchain.core; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +/** + * AI提示词构建器 + * 用于构建结构化的AI提示词,支持与AIResponseParser配合使用 + * @author suifeng + * 日期: 2025/04/18 + */ +public class AIPromptBuilder { + + private final StringBuilder promptBuilder = new StringBuilder(); + private boolean hasJsonOutput = false; + + /** + * 创建一个提示词构建器 + * @param title 提示词标题 + */ + public AIPromptBuilder(String title) { + promptBuilder.append("# ").append(title).append("\n\n"); + } + + /** + * 添加角色描述 + * @param roleDescription 角色描述 + * @return 构建器实例 + */ + public AIPromptBuilder addRole(String roleDescription) { + // 获取当前北京时间 + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); + String formattedTime = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")); + promptBuilder.append("# 当前的时间是北京时间:").append(formattedTime).append("\n"); + promptBuilder.append(roleDescription).append("\n\n"); + return this; + } + + /** + * 添加章节 + * @param sectionTitle 章节标题 + * @param content 章节内容 + * @return 构建器实例 + */ + public AIPromptBuilder addSection(String sectionTitle, String content) { + promptBuilder.append("## ").append(sectionTitle).append("\n"); + promptBuilder.append(content).append("\n\n"); + return this; + } + + /** + * 添加子章节 + * @param subSectionTitle 子章节标题 + * @param content 子章节内容 + * @return 构建器实例 + */ + public AIPromptBuilder addSubSection(String subSectionTitle, String content) { + promptBuilder.append("### ").append(subSectionTitle).append("\n"); + promptBuilder.append(content).append("\n\n"); + return this; + } + + /** + * 添加JSON输出格式 + * @param jsonExample JSON示例 + * @return 构建器实例 + */ + public AIPromptBuilder addJsonOutput(String jsonExample) { + promptBuilder.append("## 输出格式要求\n"); + promptBuilder.append("所有字符串中的引用部分只能用英文单引号 ' 包裹,内容中禁止出现英文双引号, 但是你要注意json格式规范是要加\"的,保证我能正常解析JSON 。\n"); + promptBuilder.append("```json\n"); + promptBuilder.append(jsonExample).append("\n"); + promptBuilder.append("```\n"); + promptBuilder.append("不需要给我任何额外的信息,我只需要最终的json结果,以保证生成和响应的速度\n"); + hasJsonOutput = true; + return this; + } + + /** + * 添加表格输出格式 + * @param headers 表头 + * @param example 示例行 + * @return 构建器实例 + */ + public AIPromptBuilder addTableOutput(String[] headers, String[] example) { + promptBuilder.append("## 输出格式要求\n"); + promptBuilder.append("你必须严格按照以下表格格式返回结果:\n"); + promptBuilder.append("```\n"); + + // 构建表头 + promptBuilder.append("| "); + for (String header : headers) { + promptBuilder.append(header).append(" | "); + } + promptBuilder.append("\n"); + + // 构建分隔行 + promptBuilder.append("| "); + for (int i = 0; i < headers.length; i++) { + promptBuilder.append("--- | "); + } + promptBuilder.append("\n"); + + // 构建示例行 + if (example != null) { + promptBuilder.append("| "); + for (String cell : example) { + promptBuilder.append(cell).append(" | "); + } + promptBuilder.append("\n"); + } + + promptBuilder.append("```\n\n"); + return this; + } + + /** + * 添加自定义输出格式 + * @param formatDescription 格式描述 + * @param example 示例 + * @param formatType 格式类型(用于AIResponseParser识别) + * @return 构建器实例 + */ + public AIPromptBuilder addCustomOutput(String formatDescription, String example, String formatType) { + promptBuilder.append("## 输出格式要求\n"); + promptBuilder.append(formatDescription).append("\n"); + promptBuilder.append("```").append(formatType).append("\n"); + promptBuilder.append(example).append("\n"); + promptBuilder.append("```\n\n"); + return this; + } + + /** + * 添加原始文本(不添加任何格式) + * @param text 原始文本 + * @return 构建器实例 + */ + public AIPromptBuilder addRawText(String text) { + promptBuilder.append(text).append("\n\n"); + return this; + } + + /** + * 构建提示词 + * @return 完整的提示词字符串 + */ + public String build() { + if (!hasJsonOutput) { + // 如果没有指定输出格式,添加默认提示 + promptBuilder.append("请确保你的回答简洁明了,直接提供所需信息。\n"); + } + return promptBuilder.toString(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIService.java new file mode 100644 index 0000000..16addf8 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/AIService.java @@ -0,0 +1,520 @@ +package com.suifeng.sfchain.core; + +import com.suifeng.sfchain.persistence.context.ChatContextService; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 描述: AI服务类 - 新框架版本 + * 统一管理AI操作的执行 + * + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@Service +public class AIService { + + @Resource + private AIOperationRegistry operationRegistry; + + @Resource + private ChatContextService chatContextService; + + /** + * 操作执行统计 + */ + private final Map executionStats = new ConcurrentHashMap<>(); + + /** + * 执行AI操作 + * + * @param operationType 操作类型 + * @param input 输入参数 + * @param 输入类型 + * @param 输出类型 + * @return 执行结果 + */ + @SuppressWarnings("unchecked") + public OUTPUT execute(String operationType, INPUT input) { + return execute(operationType, input, null, null); + } + + + @SuppressWarnings("unchecked") + public OUTPUT execute(String operationType, INPUT input, String sessionId) { + return execute(operationType, input,null, sessionId); + } + + /** + * 执行AI操作(带上下文支持) + * + * @param operationType 操作类型 + * @param input 输入参数 + * @param modelName 指定的模型名称 + * @param sessionId 会话ID,用于上下文管理 + * @param 输入类型 + * @param 输出类型 + * @return 执行结果 + */ + @SuppressWarnings("unchecked") + public OUTPUT execute(String operationType, INPUT input, String modelName, String sessionId) { + long startTime = System.currentTimeMillis(); + + try { + // 获取操作实例 + BaseAIOperation operation = (BaseAIOperation) operationRegistry.getOperation(operationType); + + // 检查操作是否启用 + if (!operation.isEnabled()) { + throw new IllegalStateException("操作已禁用: " + operationType); + } + + // 如果有会话ID,记录用户输入到上下文 + if (sessionId != null && input != null) { + chatContextService.addUserMessage(sessionId, input.toString()); + } + + // 执行操作 + OUTPUT result = operation.execute(input, modelName, sessionId); + + // 如果有会话ID,记录AI回复到上下文 + if (sessionId != null && result != null) { + chatContextService.addAiResponse(sessionId, result.toString()); + } + + // 记录执行统计 + recordExecution(operationType, true, System.currentTimeMillis() - startTime); + + log.debug("AI操作执行成功: {} - 耗时: {}ms", operationType, System.currentTimeMillis() - startTime); + + return result; + + } catch (Exception e) { + // 记录执行统计 + recordExecution(operationType, false, System.currentTimeMillis() - startTime); + + log.error("AI操作执行失败: {} - {}", operationType, e.getMessage(), e); + throw new RuntimeException("AI操作执行失败: " + e.getMessage(), e); + } + } + + /** + * 设置会话系统提示词 + * + * @param sessionId 会话ID + * @param systemPrompt 系统提示词 + */ + public void setSystemPrompt(String sessionId, String systemPrompt) { + chatContextService.setSystemPrompt(sessionId, systemPrompt); + log.info("设置会话系统提示词: sessionId={}", sessionId); + } + + /** + * 获取会话上下文 + * + * @param sessionId 会话ID + * @param includeSystemPrompt 是否包含系统提示词 + * @return 上下文字符串 + */ + public String getSessionContext(String sessionId, boolean includeSystemPrompt) { + return chatContextService.getContextAsString(sessionId, includeSystemPrompt); + } + + /** + * 清除会话对话历史 + * + * @param sessionId 会话ID + */ + public void clearSessionConversation(String sessionId) { + chatContextService.clearConversation(sessionId); + log.info("清除会话对话历史: sessionId={}", sessionId); + } + + /** + * 完全清除会话 + * + * @param sessionId 会话ID + */ + public void clearSession(String sessionId) { + chatContextService.clearSession(sessionId); + log.info("完全清除会话: sessionId={}", sessionId); + } + + /** + * 检查会话是否存在 + * + * @param sessionId 会话ID + * @return 是否存在 + */ + public boolean sessionExists(String sessionId) { + return chatContextService.sessionExists(sessionId); + } + + /** + * 异步执行AI操作 + * + * @param operationType 操作类型 + * @param input 输入参数 + * @param 输入类型 + * @param 输出类型 + * @return 异步执行结果 + */ + @SuppressWarnings("unchecked") + public CompletableFuture executeAsync(String operationType, INPUT input) { + return executeAsync(operationType, input, null, null); + } + + /** + * 异步执行AI操作(指定模型) + * + * @param operationType 操作类型 + * @param input 输入参数 + * @param modelName 指定的模型名称 + * @param 输入类型 + * @param 输出类型 + * @return 异步执行结果 + */ + @SuppressWarnings("unchecked") + public CompletableFuture executeAsync(String operationType, INPUT input, String modelName) { + return executeAsync(operationType, input, modelName, null); + } + + /** + * 异步执行AI操作(带上下文支持) + * + * @param operationType 操作类型 + * @param input 输入参数 + * @param modelName 指定的模型名称 + * @param sessionId 会话ID + * @param 输入类型 + * @param 输出类型 + * @return 异步执行结果 + */ + @SuppressWarnings("unchecked") + public CompletableFuture executeAsync(String operationType, INPUT input, String modelName, String sessionId) { + return CompletableFuture.supplyAsync(() -> execute(operationType, input, modelName, sessionId)); + } + + /** + * 流式执行AI操作 + */ + @SuppressWarnings("unchecked") + public Flux executeStream(String operationType, INPUT input) { + return executeStream(operationType, input, null, null); + } + + /** + * 流式执行AI操作(指定模型) + */ + @SuppressWarnings("unchecked") + public Flux executeStream(String operationType, INPUT input, String modelName) { + return executeStream(operationType, input, modelName, null); + } + + /** + * 流式执行AI操作(带上下文支持) + */ + @SuppressWarnings("unchecked") + public Flux executeStream(String operationType, INPUT input, String modelName, String sessionId) { + try { + // 获取操作实例 + BaseAIOperation operation = (BaseAIOperation) operationRegistry.getOperation(operationType); + + // 检查操作是否启用 + if (!operation.isEnabled()) { + return Flux.error(new IllegalStateException("操作已禁用: " + operationType)); + } + + // 如果有会话ID,记录用户输入到上下文 + if (sessionId != null && input != null) { + chatContextService.addUserMessage(sessionId, input.toString()); + } + + // 用于收集流式响应的StringBuilder + StringBuilder responseBuilder = new StringBuilder(); + + // 执行流式操作 - 修复参数传递 + return operation.executeStream(input, modelName, sessionId) + .doOnNext(chunk -> { + // 收集每个响应片段 + if (sessionId != null && chunk != null) { + responseBuilder.append(chunk); + } + }) + .doOnSubscribe(subscription -> { + log.debug("开始流式执行AI操作: {}", operationType); + }) + .doOnComplete(() -> { + // 流完成时,将完整的AI响应添加到上下文 + if (sessionId != null && responseBuilder.length() > 0) { + chatContextService.addAiResponse(sessionId, responseBuilder.toString()); + } + log.debug("流式AI操作执行完成: {}", operationType); + }) + .doOnError(error -> { + log.error("流式AI操作执行失败: {} - {}", operationType, error.getMessage(), error); + }); + + } catch (Exception e) { + log.error("流式AI操作执行失败: {} - {}", operationType, e.getMessage(), e); + return Flux.error(new RuntimeException("流式AI操作执行失败: " + e.getMessage(), e)); + } + } + + /** + * 批量执行AI操作 + * + * @param operationType 操作类型 + * @param inputs 输入参数列表 + * @param 输入类型 + * @param 输出类型 + * @return 执行结果列表 + */ + public List executeBatch(String operationType, List inputs) { + return executeBatch(operationType, inputs, null); + } + + /** + * 批量执行AI操作(指定模型) + * + * @param operationType 操作类型 + * @param inputs 输入参数列表 + * @param modelName 指定的模型名称 + * @param 输入类型 + * @param 输出类型 + * @return 执行结果列表 + */ + public List executeBatch(String operationType, List inputs, String modelName) { + return inputs.parallelStream() + .map(input -> this.execute(operationType, input, modelName)) + .toList(); + } + + /** + * 异步批量执行AI操作 + * + * @param operationType 操作类型 + * @param inputs 输入参数列表 + * @param 输入类型 + * @param 输出类型 + * @return 异步执行结果列表 + */ + public CompletableFuture> executeBatchAsync(String operationType, List inputs) { + return executeBatchAsync(operationType, inputs, null); + } + + /** + * 异步批量执行AI操作(指定模型) + * + * @param operationType 操作类型 + * @param inputs 输入参数列表 + * @param modelName 指定的模型名称 + * @param 输入类型 + * @param 输出类型 + * @return 异步执行结果列表 + */ + public CompletableFuture> executeBatchAsync(String operationType, List inputs, String modelName) { + List> futures = inputs.stream() + .map(input -> this.executeAsync(operationType, input, modelName)) + .toList(); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream() + .map(CompletableFuture::join) + .toList()); + } + + /** + * 获取所有可用的操作 + * + * @return 操作类型列表 + */ + public List getAvailableOperations() { + return operationRegistry.getAllOperations(); + } + + /** + * 检查操作是否可用 + * + * @param operationType 操作类型 + * @return 是否可用 + */ + public boolean isOperationAvailable(String operationType) { + try { + BaseAIOperation operation = operationRegistry.getOperation(operationType); + return operation != null && operation.isEnabled(); + } catch (Exception e) { + return false; + } + } + + /** + * 获取操作信息 + * + * @param operationType 操作类型 + * @return 操作信息 + */ + public OperationInfo getOperationInfo(String operationType) { + BaseAIOperation operation = operationRegistry.getOperation(operationType); + if (operation == null) { + return null; + } + + return OperationInfo.builder() + .operationType(operationType) + .description(operation.getDescription()) + .inputType(operation.getInputType()) + .outputType(operation.getOutputType()) + .enabled(operation.isEnabled()) + .supportedModels(operation.getSupportedModels()) + .defaultModel(operation.getAnnotation().defaultModel()) + .build(); + } + + /** + * 获取操作执行统计 + * + * @param operationType 操作类型 + * @return 执行统计 + */ + public ExecutionStats getExecutionStats(String operationType) { + return executionStats.getOrDefault(operationType, new ExecutionStats()); + } + + /** + * 获取所有操作的执行统计 + * + * @return 执行统计映射 + */ + public Map getAllExecutionStats() { + return Map.copyOf(executionStats); + } + + /** + * 清空执行统计 + */ + public void clearExecutionStats() { + executionStats.clear(); + } + + /** + * 记录执行统计 + * + * @param operationType 操作类型 + * @param success 是否成功 + * @param duration 执行时长 + */ + private void recordExecution(String operationType, boolean success, long duration) { + executionStats.computeIfAbsent(operationType, k -> new ExecutionStats()) + .record(success, duration); + } + + /** + * 操作信息类 + */ + @Getter + public static class OperationInfo { + // Getters + private String operationType; + private String description; + private Class inputType; + private Class outputType; + private boolean enabled; + private String[] supportedModels; + private String defaultModel; + + public static OperationInfoBuilder builder() { + return new OperationInfoBuilder(); + } + + public static class OperationInfoBuilder { + private OperationInfo info = new OperationInfo(); + + public OperationInfoBuilder operationType(String operationType) { + info.operationType = operationType; + return this; + } + + public OperationInfoBuilder description(String description) { + info.description = description; + return this; + } + + public OperationInfoBuilder inputType(Class inputType) { + info.inputType = inputType; + return this; + } + + public OperationInfoBuilder outputType(Class outputType) { + info.outputType = outputType; + return this; + } + + public OperationInfoBuilder enabled(boolean enabled) { + info.enabled = enabled; + return this; + } + + public OperationInfoBuilder supportedModels(String[] supportedModels) { + info.supportedModels = supportedModels; + return this; + } + + public OperationInfoBuilder defaultModel(String defaultModel) { + info.defaultModel = defaultModel; + return this; + } + + public OperationInfo build() { + return info; + } + } + } + + /** + * 执行统计类 + */ + public static class ExecutionStats { + private long totalExecutions = 0; + @Getter + private long successfulExecutions = 0; + @Getter + private long failedExecutions = 0; + @Getter + private long totalDuration = 0; + private long minDuration = Long.MAX_VALUE; + private long maxDuration = 0; + + public synchronized void record(boolean success, long duration) { + totalExecutions++; + if (success) { + successfulExecutions++; + } else { + failedExecutions++; + } + + totalDuration += duration; + minDuration = Math.min(minDuration, duration); + maxDuration = Math.max(maxDuration, duration); + } + + public double getSuccessRate() { + return totalExecutions > 0 ? (double) successfulExecutions / totalExecutions : 0.0; + } + + public double getAverageDuration() { + return totalExecutions > 0 ? (double) totalDuration / totalExecutions : 0.0; + } + + public long getMinDuration() { return minDuration == Long.MAX_VALUE ? 0 : minDuration; } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/BaseAIOperation.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/BaseAIOperation.java new file mode 100644 index 0000000..adc5800 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/BaseAIOperation.java @@ -0,0 +1,621 @@ +package com.suifeng.sfchain.core; + +import com.alibaba.fastjson2.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.suifeng.sfchain.annotation.AIOp; +import com.suifeng.sfchain.core.logging.AICallLog; +import com.suifeng.sfchain.core.logging.AICallLogManager; +import com.suifeng.sfchain.core.openai.OpenAICompatibleModel; +import com.suifeng.sfchain.persistence.context.ChatContextService; +import com.suifeng.sfchain.persistence.context.ChatMessage; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import reactor.core.publisher.Flux; + +import javax.annotation.PostConstruct; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static com.suifeng.sfchain.constants.AIOperationConstant.JSON_REPAIR_OP; + +/** + * 描述: AI操作抽象基类 - 新框架版本 + * 提供统一的AI操作接口和实现 + * + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +public abstract class BaseAIOperation { + + @Autowired + protected AIOperationRegistry operationRegistry; + + @Autowired + protected ModelRegistry modelRegistry; + + @Autowired + protected ObjectMapper objectMapper; + + @Autowired + protected ChatContextService chatContextService; + + /** + * 操作的注解信息 + * -- GETTER -- + * 获取注解信息 + * + * @return 注解信息 + */ + @Getter + private AIOp annotation; + + /** + * 输入类型 + * -- GETTER -- + * 获取输入类型 + * + * @return 输入类型 + */ + @Getter + private Class inputType; + + /** + * 输出类型 + * -- GETTER -- + * 获取输出类型 + * + * @return 输出类型 + */ + @Getter + private Class outputType; + + /** + * 初始化方法 + */ + @PostConstruct + @SuppressWarnings("unchecked") + public void init() { + // 获取注解信息 + this.annotation = this.getClass().getAnnotation(AIOp.class); + if (annotation == null) { + throw new IllegalStateException("AI操作类必须使用@AIOp注解: " + this.getClass().getSimpleName()); + } + + // 获取泛型类型 + Type superClass = this.getClass().getGenericSuperclass(); + if (superClass instanceof ParameterizedType parameterizedType) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + if (typeArguments.length >= 2) { + this.inputType = (Class) typeArguments[0]; + this.outputType = (Class) typeArguments[1]; + } + } + + // 注册到操作注册中心 + operationRegistry.registerOperation(annotation.value(), this); + + // 如果注解中有默认模型且当前没有设置模型映射,则自动设置 + if (!annotation.defaultModel().isEmpty()) { + String currentModel = operationRegistry.getModelForOperation(annotation.value()); + if (currentModel == null) { + try { + // 验证模型是否存在 + if (modelRegistry.getModel(annotation.defaultModel()) != null) { + operationRegistry.setModelForOperation(annotation.value(), annotation.defaultModel()); + log.info("自动设置操作默认模型映射: {} -> {}", annotation.value(), annotation.defaultModel()); + } + } catch (Exception e) { + log.warn("无法设置默认模型映射 {} -> {}: {}", annotation.value(), annotation.defaultModel(), e.getMessage()); + } + } + } + + log.info("初始化AI操作: {} [{}] -> 输入类型: {}, 输出类型: {}", + annotation.value(), this.getClass().getSimpleName(), + inputType != null ? inputType.getSimpleName() : "Unknown", + outputType != null ? outputType.getSimpleName() : "Unknown"); + } + + /** + * 流式执行AI操作 + */ + @SuppressWarnings("unchecked") + public Flux executeStream(INPUT input) { + return executeStream(input, null, null); + } + + /** + * 流式执行AI操作(指定模型) + */ + @SuppressWarnings("unchecked") + public Flux executeStream(INPUT input, String modelName) { + return executeStream(input, modelName, null); + } + + /** + * 流式执行AI操作(带上下文支持) + */ + @SuppressWarnings("unchecked") + public Flux executeStream(INPUT input, String modelName, String sessionId) { + try { + // 检查操作是否启用 + if (!isEnabled()) { + return Flux.error(new IllegalStateException("操作已禁用: " + annotation.value())); + } + + // 获取模型 + AIModel model = getModel(modelName); + + // 构建带上下文的提示词 + String prompt = buildPromptWithContext(input, sessionId); + + // 获取操作配置 + AIOperationRegistry.OperationConfig config = operationRegistry.getOperationConfig(annotation.value()); + + // 合并配置 + Integer finalMaxTokens = config.getMaxTokens() > 0 ? Integer.valueOf(config.getMaxTokens()) : + (annotation.defaultMaxTokens() > 0 ? annotation.defaultMaxTokens() : null); + Double finalTemperature = config.getTemperature() >= 0 ? Double.valueOf(config.getTemperature()) : + (annotation.defaultTemperature() >= 0 ? annotation.defaultTemperature() : null); + Boolean finalJsonOutput = config.isRequireJsonOutput() || annotation.requireJsonOutput(); + boolean finalThinking = config.isSupportThinking() || annotation.supportThinking(); + + // 调用模型的流式生成方法 + if (model instanceof OpenAICompatibleModel openAIModel) { + if (finalThinking) { + return openAIModel.generateStreamWithThinking(prompt, finalMaxTokens, finalTemperature); + } else { + return openAIModel.generateStream(prompt, finalMaxTokens, finalTemperature, finalJsonOutput); + } + } else { + // 对于不支持流式的模型,返回错误 + return Flux.error(new UnsupportedOperationException("模型不支持流式输出: " + model.getName())); + } + + } catch (Exception e) { + log.error("流式AI操作执行失败: {} - {}", annotation.value(), e.getMessage(), e); + return Flux.error(new RuntimeException("流式AI操作执行失败: " + e.getMessage(), e)); + } + } + + /** + * 执行AI操作 + * + * @param input 输入参数 + * @return 输出结果 + */ + public OUTPUT execute(INPUT input) { + return execute(input, null, null); + } + + /** + * 执行AI操作(指定模型) + * + * @param input 输入参数 + * @param modelName 指定的模型名称,为null时使用默认模型 + * @return 输出结果 + */ + public OUTPUT execute(INPUT input, String modelName) { + return execute(input, modelName, null); + } + + /** + * 执行AI操作(带上下文支持) + * + * @param input 输入参数 + * @param modelName 指定的模型名称,为null时使用默认模型 + * @param sessionId 会话ID,用于上下文管理 + * @return 输出结果 + */ + // 在BaseAIOperation类中添加以下字段和方法 + + @Autowired + private AICallLogManager logManager; + + // 在execute方法中添加详细日志记录和上下文支持 + public OUTPUT execute(INPUT input, String modelName, String sessionId) { + String callId = UUID.randomUUID().toString(); + LocalDateTime startTime = LocalDateTime.now(); + long startMillis = System.currentTimeMillis(); + + AICallLog.AICallLogBuilder logBuilder = AICallLog.builder() + .callId(callId) + .operationType(annotation.value()) + .callTime(startTime) + .input(input) + .modelName(modelName) + .frequency(1) + .lastAccessTime(startTime); + + try { + // 获取模型 + AIModel model = getModel(modelName); + logBuilder.modelName(model.getName()); + + // 构建提示词(带上下文支持) + String prompt = buildPromptWithContext(input, sessionId); + logBuilder.prompt(prompt); + + // 获取操作配置 + AIOperationRegistry.OperationConfig config = operationRegistry.getOperationConfig(annotation.value()); + + // 合并配置 + Integer finalMaxTokens = config.getMaxTokens() > 0 ? Integer.valueOf(config.getMaxTokens()) : (annotation.defaultMaxTokens() > 0 ? annotation.defaultMaxTokens() : null); + Double finalTemperature = config.getTemperature() >= 0 ? Double.valueOf(config.getTemperature()) : (annotation.defaultTemperature() >= 0 ? annotation.defaultTemperature() : null); + Boolean finalJsonOutput = config.isRequireJsonOutput() || annotation.requireJsonOutput(); + boolean finalThinking = config.isSupportThinking() || annotation.supportThinking(); + + // 记录请求参数 + AICallLog.AIRequestParams requestParams = AICallLog.AIRequestParams.builder() + .maxTokens(finalMaxTokens) + .temperature(finalTemperature) + .jsonOutput(finalJsonOutput) + .thinking(finalThinking) + .build(); + logBuilder.requestParams(requestParams); + + // 调用AI模型 + String response; + if (model instanceof OpenAICompatibleModel openAIModel) { + if (finalThinking) { + response = openAIModel.generateWithThinking(prompt, finalMaxTokens, finalTemperature); + } else { + response = openAIModel.generate(prompt, finalMaxTokens, finalTemperature, finalJsonOutput); + } + } else { + response = model.generate(prompt); + } + + logBuilder.rawResponse(response); + + // 解析响应 + OUTPUT result = parseResponse(response, input); + + // 记录成功日志 + long duration = System.currentTimeMillis() - startMillis; + AICallLog log = logBuilder + .status(AICallLog.CallStatus.SUCCESS) + .duration(duration) + .output(result) + .build(); + + logManager.addLog(log); + + return result; + + } catch (Exception e) { + // 记录失败日志 + long duration = System.currentTimeMillis() - startMillis; + AICallLog callLog = logBuilder + .status(AICallLog.CallStatus.FAILED) + .duration(duration) + .errorMessage(e.getMessage()) + .build(); + + logManager.addLog(callLog); + + log.error("执行AI操作失败: {} - {}", annotation.value(), e.getMessage(), e); + throw new RuntimeException("AI操作执行失败: " + e.getMessage(), e); + } + } + + /** + * 构建带上下文的提示词 + * + * @param input 输入参数 + * @param sessionId 会话ID + * @return 完整的提示词 + */ + protected String buildPromptWithContext(INPUT input, String sessionId) { + // 构建基础提示词 + String basePrompt = buildPrompt(input); + + // 如果没有会话ID,直接返回基础提示词 + if (sessionId == null || !chatContextService.sessionExists(sessionId)) { + return basePrompt; + } + + // 获取上下文信息 + List contextMessages = chatContextService.getFullContext(sessionId); + if (contextMessages.isEmpty()) { + return basePrompt; + } + + // 构建带上下文的提示词 + StringBuilder contextPrompt = new StringBuilder(); + + // 添加系统提示词(如果存在) + String systemPrompt = chatContextService.getSystemPrompt(sessionId); + if (systemPrompt != null && !systemPrompt.trim().isEmpty()) { + contextPrompt.append("系统提示: ").append(systemPrompt).append("\n\n"); + } + + // 添加对话历史 + List conversationHistory = chatContextService.getConversationHistory(sessionId); + if (!conversationHistory.isEmpty()) { + contextPrompt.append("对话历史:\n"); + for (ChatMessage message : conversationHistory) { + String role = getRoleString(message.getType()); + contextPrompt.append(role).append(": ").append(message.getContent()).append("\n"); + } + contextPrompt.append("\n"); + } + + // 添加当前任务提示词 + contextPrompt.append("当前任务:\n").append(basePrompt); + + return contextPrompt.toString(); + } + + /** + * 获取角色字符串 + * + * @param type 消息类型 + * @return 角色字符串 + */ + private String getRoleString(ChatMessage.MessageType type) { + switch (type) { + case SYSTEM: + return "系统"; + case USER: + return "用户"; + case ASSISTANT: + return "助手"; + default: + return "未知"; + } + } + + /** + * 构建提示词(子类实现) + * + * @param input 输入参数 + * @return 提示词 + */ + protected abstract String buildPrompt(INPUT input); + + /** + * 解析AI响应(最终方法,子类不应重写) + * 重写版本:支持非JSON格式输出 + * + * @param response AI响应 + * @param input 输入参数 + * @return 解析后的结果 + */ + protected final OUTPUT parseResponse(String response, INPUT input) { + // 如果输出类型是String,直接返回响应内容 + if (outputType == String.class) { + // 先尝试子类自定义解析 + OUTPUT customResult = parseResult(response, input); + if (customResult != null) { + return customResult; + } + // 默认返回原始响应 + return (OUTPUT) response; + } + + // 如果不要求JSON输出,也尝试直接解析 + if (!annotation.requireJsonOutput()) { + OUTPUT customResult = parseResult(response, input); + if (customResult != null) { + return customResult; + } + // 如果子类没有自定义解析且不要求JSON,返回原始响应(如果类型兼容) + if (outputType.isAssignableFrom(String.class)) { + return (OUTPUT) response; + } + } + + // 以下是原有的JSON解析逻辑 + try { + // 1. 预处理响应(子类可自定义) + String processedResponse = preprocessResponse(response, input); + + // 2. 提取JSON内容 + String jsonContent = extractJsonFromResponse(processedResponse); + + // 3. 预处理JSON内容(子类可自定义) + String processedJson = preprocessJson(jsonContent, input); + + // 4. 解析为对象(子类可自定义解析逻辑) + return parseJsonToResult(processedJson, input, response); + + } catch (JsonProcessingException e) { + // 如果启用了自动JSON修复且需要JSON输出,尝试修复JSON + if (annotation.requireJsonOutput() && annotation.autoRepairJson()) { + log.warn("JSON解析失败,尝试自动修复: {}", e.getMessage()); + try { + // 通过操作注册中心获取JSON修复操作,避免循环依赖 + BaseAIOperation jsonRepairOp = operationRegistry.getOperation(JSON_REPAIR_OP); + if (jsonRepairOp != null) { + String jsonContent = extractJsonFromResponse(response); + @SuppressWarnings("unchecked") + BaseAIOperation repairOperation = (BaseAIOperation) jsonRepairOp; + JSONObject repairedJson = repairOperation.execute(jsonContent); + String repairedJsonStr = repairedJson.toJSONString(); + return parseJsonToResult(repairedJsonStr, input, response); + } + } catch (Exception repairException) { + log.error("JSON修复也失败: {}", repairException.getMessage(), repairException); + throw new RuntimeException("JSON解析和修复都失败: 原始错误=" + e.getMessage() + ", 修复错误=" + repairException.getMessage(), e); + } + } + + log.error("解析AI响应失败: {}", e.getMessage(), e); + throw new RuntimeException("解析AI响应失败: " + e.getMessage(), e); + } + } + + /** + * 预处理AI响应(子类可重写) + * 在提取JSON之前对原始响应进行处理 + * + * @param response 原始AI响应 + * @param input 输入参数 + * @return 处理后的响应 + */ + protected String preprocessResponse(String response, INPUT input) { + return response; + } + + /** + * 预处理JSON内容(子类可重写) + * 在JSON解析之前对提取的JSON字符串进行处理 + * + * @param jsonContent 提取的JSON字符串 + * @param input 输入参数 + * @return 处理后的JSON字符串 + */ + protected String preprocessJson(String jsonContent, INPUT input) { + return jsonContent; + } + + /** + * 将JSON字符串解析为结果对象(高级用法,一般用户无需重写) + * + * @param jsonContent JSON内容 + * @param input 输入参数 + * @param originalResponse 原始响应 + * @return 解析后的结果对象 + * @throws JsonProcessingException JSON解析异常 + */ + protected OUTPUT parseJsonToResult(String jsonContent, INPUT input, String originalResponse) throws JsonProcessingException { + // 先尝试用户自定义的解析方法 + OUTPUT customResult = parseResult(jsonContent, input); + if (customResult != null) { + return customResult; + } + + // 如果用户没有自定义解析,使用默认的JSON解析 + return objectMapper.readValue(jsonContent, outputType); + } + + /** + * 解析AI返回的JSON为最终结果(推荐用户重写此方法) + * 用户可以在此方法中处理AI返回的原始JSON,并转换为最终的结果对象 + * + * @param jsonContent AI返回的JSON字符串 + * @param input 输入参数 + * @return 最终结果对象,如果返回null则使用默认的JSON解析 + */ + protected OUTPUT parseResult(String jsonContent, INPUT input) { + return null; // 默认返回null,表示使用框架的默认JSON解析 + } + + /** + * 工具方法:将JSON字符串解析为指定类型的对象 + * + * @param jsonContent JSON字符串 + * @param clazz 目标类型 + * @param 泛型类型 + * @return 解析后的对象 + * @throws JsonProcessingException JSON解析异常 + */ + protected T parseJsonToObject(String jsonContent, Class clazz) throws JsonProcessingException { + return objectMapper.readValue(jsonContent, clazz); + } + + /** + * 从响应中提取JSON内容 + * + * @param response 原始响应 + * @return JSON字符串 + */ + protected String extractJsonFromResponse(String response) { + // 查找JSON代码块 + String jsonStart = "```json"; + String jsonEnd = "```"; + + int startIndex = response.indexOf(jsonStart); + if (startIndex != -1) { + startIndex += jsonStart.length(); + int endIndex = response.indexOf(jsonEnd, startIndex); + if (endIndex != -1) { + return response.substring(startIndex, endIndex).trim(); + } + } + + // 查找花括号包围的JSON + int braceStart = response.indexOf('{'); + int braceEnd = response.lastIndexOf('}'); + if (braceStart != -1 && braceEnd != -1 && braceEnd > braceStart) { + return response.substring(braceStart, braceEnd + 1); + } + + // 如果都找不到,返回原始响应 + return response; + } + + /** + * 获取模型实例 + * + * @param modelName 模型名称,为null时使用默认模型 + * @return 模型实例 + */ + private AIModel getModel(String modelName) { + if (modelName == null) { + // 使用注册中心配置的模型 + modelName = operationRegistry.getModelForOperation(annotation.value()); + } + + if (modelName == null) { + // 使用注解中的默认模型 + modelName = annotation.defaultModel(); + } + + if (modelName == null || modelName.isEmpty()) { + throw new IllegalStateException("未配置模型: " + annotation.value()); + } + + AIModel model = modelRegistry.getModel(modelName); + if (model == null) { + throw new IllegalArgumentException("模型不存在: " + modelName); + } + + return model; + } + + /** + * 获取操作类型 + * + * @return 操作类型 + */ + public String getOperationType() { + return annotation != null ? annotation.value() : null; + } + + /** + * 检查操作是否启用 + * + * @return 是否启用 + */ + public boolean isEnabled() { + AIOperationRegistry.OperationConfig config = operationRegistry.getOperationConfig(annotation.value()); + return config.isEnabled() && annotation.enabled(); + } + + /** + * 获取操作描述 + * + * @return 操作描述 + */ + public String getDescription() { + return annotation.description(); + } + + /** + * 获取支持的模型列表 + * + * @return 支持的模型列表 + */ + public String[] getSupportedModels() { + return annotation.supportedModels(); + } + +} diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/ModelRegistry.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/ModelRegistry.java new file mode 100644 index 0000000..dcce7f2 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/ModelRegistry.java @@ -0,0 +1,71 @@ +package com.suifeng.sfchain.core; + +import com.suifeng.sfchain.core.openai.OpenAIModelFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; + +/** + * 描述: AI模型注册中心 + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ModelRegistry { + + private final OpenAIModelFactory modelFactory; + + /** + * 获取模型实例 + * @param modelName 模型名称 + * @return AI模型实例 + */ + public AIModel getModel(String modelName) { + try { + return modelFactory.createModel(modelName); + } catch (Exception e) { + log.error("获取模型失败: {} - {}", modelName, e.getMessage()); + throw new RuntimeException("无法获取模型: " + modelName, e); + } + } + + /** + * 检查模型是否已注册 + * @param modelName 模型名称 + * @return 是否已注册 + */ + public boolean isModelRegistered(String modelName) { + return modelFactory.isModelRegistered(modelName); + } + + /** + * 获取所有已注册的模型名称 + * @return 模型名称集合 + */ + public Set getRegisteredModelNames() { + return modelFactory.getRegisteredModelNames(); + } + + /** + * 获取可用的模型列表 + * @return 可用模型列表 + */ + public List getAvailableModels() { + return getRegisteredModelNames().stream() + .filter(modelName -> { + try { + AIModel model = getModel(modelName); + return model.isAvailable(); + } catch (Exception e) { + log.warn("检查模型可用性失败: {} - {}", modelName, e.getMessage()); + return false; + } + }) + .toList(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLog.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLog.java new file mode 100644 index 0000000..779a3b7 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLog.java @@ -0,0 +1,71 @@ +package com.suifeng.sfchain.core.logging; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * AI调用日志实体 + */ +@Data +@Builder +public class AICallLog { + + /** 调用ID */ + private String callId; + + /** 操作类型 */ + private String operationType; + + /** 模型名称 */ + private String modelName; + + /** 调用时间 */ + private LocalDateTime callTime; + + /** 执行耗时(毫秒) */ + private long duration; + + /** 调用状态 */ + private CallStatus status; + + /** 原始输入参数 */ + private Object input; + + /** 构建的提示词 */ + private String prompt; + + /** AI请求参数 */ + private AIRequestParams requestParams; + + /** 模型原始返回结果 */ + private String rawResponse; + + /** 最终输出结果 */ + private Object output; + + /** 错误信息(如果有) */ + private String errorMessage; + + /** 调用频次(用于LFU) */ + private int frequency; + + /** 最后访问时间(用于LFU) */ + private LocalDateTime lastAccessTime; + + public enum CallStatus { + SUCCESS, FAILED, TIMEOUT + } + + @Data + @Builder + public static class AIRequestParams { + private Integer maxTokens; + private Double temperature; + private Boolean jsonOutput; + private Boolean thinking; + private Map additionalParams; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogAspect.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogAspect.java new file mode 100644 index 0000000..6c2c450 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogAspect.java @@ -0,0 +1,76 @@ +package com.suifeng.sfchain.core.logging; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * AI调用日志记录切面 + */ +@Slf4j +@Aspect +@Component +public class AICallLogAspect { + + @Autowired + private AICallLogManager logManager; + + /** + * 拦截BaseAIOperation的execute方法 + */ + @Around("execution(* com.suifeng.sfchain.core.BaseAIOperation.execute(..))") + public Object logAIOperation(ProceedingJoinPoint joinPoint) throws Throwable { + String callId = UUID.randomUUID().toString(); + LocalDateTime startTime = LocalDateTime.now(); + long startMillis = System.currentTimeMillis(); + + AICallLog.AICallLogBuilder logBuilder = AICallLog.builder() + .callId(callId) + .callTime(startTime) + .frequency(1) + .lastAccessTime(startTime); + + try { + // 获取输入参数 + Object[] args = joinPoint.getArgs(); + Object input = args.length > 0 ? args[0] : null; + String modelName = args.length > 1 ? (String) args[1] : null; + + logBuilder.input(input).modelName(modelName); + + // 执行原方法 + Object result = joinPoint.proceed(); + + // 记录成功日志 + long duration = System.currentTimeMillis() - startMillis; + AICallLog log = logBuilder + .status(AICallLog.CallStatus.SUCCESS) + .duration(duration) + .output(result) + .build(); + + logManager.addLog(log); + + return result; + + } catch (Exception e) { + // 记录失败日志 + long duration = System.currentTimeMillis() - startMillis; + AICallLog log = logBuilder + .status(AICallLog.CallStatus.FAILED) + .duration(duration) + .errorMessage(e.getMessage()) + .build(); + + logManager.addLog(log); + + throw e; + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogManager.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogManager.java new file mode 100644 index 0000000..8e956d3 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogManager.java @@ -0,0 +1,238 @@ +package com.suifeng.sfchain.core.logging; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +/** + * AI调用日志管理器 - 基于LFU算法 + */ +@Slf4j +@Component +public class AICallLogManager { + + private static final int MAX_CAPACITY = 100; + + /** 日志存储 */ + private final Map logStorage = new ConcurrentHashMap<>(); + + /** 频次计数器 */ + private final Map frequencyMap = new ConcurrentHashMap<>(); + + /** 频次分组 - 频次 -> 调用ID集合 */ + private final Map> frequencyGroups = new ConcurrentHashMap<>(); + + /** 最小频次 */ + private volatile int minFrequency = 1; + + /** 读写锁 */ + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + /** + * 添加调用日志 + */ + public void addLog(AICallLog callLog) { + lock.writeLock().lock(); + try { + String callId = callLog.getCallId(); + + // 如果已达到容量上限,移除最少使用的日志 + if (logStorage.size() >= MAX_CAPACITY && !logStorage.containsKey(callId)) { + evictLFU(); + } + + // 添加或更新日志 + logStorage.put(callId, callLog); + updateFrequency(callId); + + log.debug("添加AI调用日志: {}", callId); + + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 获取完整调用日志(包含所有详细信息) + */ + public AICallLog getFullLog(String callId) { + lock.readLock().lock(); + try { + AICallLog callLog = logStorage.get(callId); + if (callLog != null) { + // 更新访问时间和频次 + callLog.setLastAccessTime(LocalDateTime.now()); + updateFrequency(callId); + } + return callLog; + } finally { + lock.readLock().unlock(); + } + } + + /** + * 获取所有日志摘要(按时间倒序) + */ + public List getAllLogSummaries() { + lock.readLock().lock(); + try { + return logStorage.values().stream() + .sorted((a, b) -> b.getCallTime().compareTo(a.getCallTime())) + .map(AICallLogSummary::fromFullLog) + .collect(Collectors.toList()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * 根据操作类型获取日志摘要 + */ + public List getLogSummariesByOperation(String operationType) { + lock.readLock().lock(); + try { + return logStorage.values().stream() + .filter(log -> operationType.equals(log.getOperationType())) + .sorted((a, b) -> b.getCallTime().compareTo(a.getCallTime())) + .map(AICallLogSummary::fromFullLog) + .collect(Collectors.toList()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * 根据模型名称获取日志摘要 + */ + public List getLogSummariesByModel(String modelName) { + lock.readLock().lock(); + try { + return logStorage.values().stream() + .filter(log -> modelName.equals(log.getModelName())) + .sorted((a, b) -> b.getCallTime().compareTo(a.getCallTime())) + .map(AICallLogSummary::fromFullLog) + .collect(Collectors.toList()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * 获取统计信息 + */ + public LogStatistics getStatistics() { + lock.readLock().lock(); + try { + long totalCalls = logStorage.size(); + long successCalls = logStorage.values().stream() + .mapToLong(log -> log.getStatus() == AICallLog.CallStatus.SUCCESS ? 1 : 0) + .sum(); + + double avgDuration = logStorage.values().stream() + .mapToLong(AICallLog::getDuration) + .average() + .orElse(0.0); + + Map operationCounts = logStorage.values().stream() + .collect(Collectors.groupingBy( + AICallLog::getOperationType, + Collectors.counting() + )); + + Map modelCounts = logStorage.values().stream() + .collect(Collectors.groupingBy( + AICallLog::getModelName, + Collectors.counting() + )); + + return LogStatistics.builder() + .totalCalls(totalCalls) + .successCalls(successCalls) + .successRate(totalCalls > 0 ? (double) successCalls / totalCalls : 0.0) + .averageDuration(avgDuration) + .operationCounts(operationCounts) + .modelCounts(modelCounts) + .build(); + } finally { + lock.readLock().unlock(); + } + } + + /** + * 清空所有日志 + */ + public void clearLogs() { + lock.writeLock().lock(); + try { + logStorage.clear(); + frequencyMap.clear(); + frequencyGroups.clear(); + minFrequency = 1; + log.info("已清空所有AI调用日志"); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 更新频次 + */ + private void updateFrequency(String callId) { + int oldFreq = frequencyMap.getOrDefault(callId, 0); + int newFreq = oldFreq + 1; + + frequencyMap.put(callId, newFreq); + + // 从旧频次组中移除 + if (oldFreq > 0) { + frequencyGroups.get(oldFreq).remove(callId); + if (frequencyGroups.get(oldFreq).isEmpty() && oldFreq == minFrequency) { + minFrequency++; + } + } + + // 添加到新频次组 + frequencyGroups.computeIfAbsent(newFreq, k -> new LinkedHashSet<>()).add(callId); + + // 更新最小频次 + if (newFreq < minFrequency) { + minFrequency = newFreq; + } + } + + /** + * 淘汰最少使用的日志 + */ + private void evictLFU() { + // 找到最小频次组中最早的元素 + LinkedHashSet minFreqGroup = frequencyGroups.get(minFrequency); + if (minFreqGroup != null && !minFreqGroup.isEmpty()) { + String evictCallId = minFreqGroup.iterator().next(); + + // 移除日志 + logStorage.remove(evictCallId); + frequencyMap.remove(evictCallId); + minFreqGroup.remove(evictCallId); + + log.debug("淘汰AI调用日志: {}", evictCallId); + } + } + + @lombok.Data + @lombok.Builder + public static class LogStatistics { + private long totalCalls; + private long successCalls; + private double successRate; + private double averageDuration; + private Map operationCounts; + private Map modelCounts; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogSummary.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogSummary.java new file mode 100644 index 0000000..36ccd91 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/logging/AICallLogSummary.java @@ -0,0 +1,81 @@ +package com.suifeng.sfchain.core.logging; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * AI调用日志摘要 - 用于列表展示,不包含大数据量字段 + */ +@Data +@Builder +public class AICallLogSummary { + + /** 调用ID */ + private String callId; + + /** 操作类型 */ + private String operationType; + + /** 模型名称 */ + private String modelName; + + /** 调用时间 */ + private LocalDateTime callTime; + + /** 执行耗时(毫秒) */ + private long duration; + + /** 调用状态 */ + private AICallLog.CallStatus status; + + /** 错误信息(如果有) */ + private String errorMessage; + + /** 调用频次(用于LFU) */ + private int frequency; + + /** 最后访问时间(用于LFU) */ + private LocalDateTime lastAccessTime; + + /** AI请求参数摘要 */ + private RequestParamsSummary requestParams; + + @Data + @Builder + public static class RequestParamsSummary { + private Integer maxTokens; + private Double temperature; + private Boolean jsonOutput; + private Boolean thinking; + } + + /** + * 从完整日志创建摘要 + */ + public static AICallLogSummary fromFullLog(AICallLog fullLog) { + RequestParamsSummary paramsSummary = null; + if (fullLog.getRequestParams() != null) { + paramsSummary = RequestParamsSummary.builder() + .maxTokens(fullLog.getRequestParams().getMaxTokens()) + .temperature(fullLog.getRequestParams().getTemperature()) + .jsonOutput(fullLog.getRequestParams().getJsonOutput()) + .thinking(fullLog.getRequestParams().getThinking()) + .build(); + } + + return AICallLogSummary.builder() + .callId(fullLog.getCallId()) + .operationType(fullLog.getOperationType()) + .modelName(fullLog.getModelName()) + .callTime(fullLog.getCallTime()) + .duration(fullLog.getDuration()) + .status(fullLog.getStatus()) + .errorMessage(fullLog.getErrorMessage()) + .frequency(fullLog.getFrequency()) + .lastAccessTime(fullLog.getLastAccessTime()) + .requestParams(paramsSummary) + .build(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAICompatibleModel.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAICompatibleModel.java new file mode 100644 index 0000000..07ec355 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAICompatibleModel.java @@ -0,0 +1,237 @@ +package com.suifeng.sfchain.core.openai; + +import com.alibaba.fastjson2.JSON; +import com.suifeng.sfchain.core.AIModel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.Map; + +/** + * 描述: OpenAI兼容的通用模型实现 + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +public class OpenAICompatibleModel implements AIModel { + + /** + * -- GETTER -- + * 获取模型配置 + */ + @Getter + private final OpenAIModelConfig config; + private final OpenAIHttpClient httpClient; + + public OpenAICompatibleModel(OpenAIModelConfig config) { + if (!config.isValid()) { + throw new IllegalArgumentException("模型配置无效: " + config); + } + + this.config = config; + this.httpClient = new OpenAIHttpClient( + config.getBaseUrl(), + config.getApiKey(), + config.getAdditionalHeaders() + ); + + log.info("初始化OpenAI兼容模型: {} ({})", config.getModelName(), config.getProvider()); + } + + @Override + public String getName() { + return config.getModelName(); + } + + @Override + public String description() { + return config.getDescription() != null ? config.getDescription() : + String.format("%s模型 (提供商: %s)", config.getModelName(), config.getProvider()); + } + + @Override + public String generate(String prompt) { + return generate(prompt, null, null, null); + } + + @Override + public T generate(String prompt, Class responseType) { + String result = generate(prompt); + if (responseType == String.class) { + return responseType.cast(result); + } + + try { + return JSON.parseObject(result, responseType); + } catch (Exception e) { + log.error("解析响应为{}类型失败: {}", responseType.getSimpleName(), e.getMessage()); + throw new RuntimeException("响应解析失败: " + e.getMessage(), e); + } + } + + /** + * 生成响应 - 支持自定义参数 + */ + public String generate(String prompt, Integer maxTokens, Double temperature, Boolean jsonOutput) { + try { + OpenAIRequest request = buildRequest(prompt, maxTokens, temperature, jsonOutput); + OpenAIResponse response = httpClient.chatCompletion(request); + return httpClient.extractContent(response); + } catch (Exception e) { + log.error("模型{}生成失败", config.getModelName(), e); + throw new RuntimeException("模型生成失败: " + e.getMessage(), e); + } + } + + /** + * 生成响应 - 支持思考模式 + */ + public String generateWithThinking(String prompt, Integer maxTokens, Double temperature) { + if (!Boolean.TRUE.equals(config.getSupportThinking())) { + log.warn("模型{}不支持思考模式,使用普通模式", config.getModelName()); + return generate(prompt, maxTokens, temperature, null); + } + + try { + OpenAIRequest request = buildRequestWithThinking(prompt, maxTokens, temperature); + OpenAIResponse response = httpClient.chatCompletion(request); + return httpClient.extractContent(response); + } catch (Exception e) { + log.error("模型{}思考模式生成失败", config.getModelName(), e); + throw new RuntimeException("思考模式生成失败: " + e.getMessage(), e); + } + } + + /** + * 构建请求对象 + */ + private OpenAIRequest buildRequest(String prompt, Integer maxTokens, Double temperature, Boolean jsonOutput) { + var builder = OpenAIRequest.builder() + .model(config.getModelName()) + .messages(List.of( + OpenAIRequest.Message.builder() + .role("user") + .content(prompt) + .build() + )) + .max_tokens(maxTokens != null ? maxTokens : config.getDefaultMaxTokens()) + .temperature(temperature != null ? temperature : config.getDefaultTemperature()) + .stream(false); + + // 设置JSON输出格式 + if (Boolean.TRUE.equals(jsonOutput) && Boolean.TRUE.equals(config.getSupportJsonOutput())) { + builder.response_format(Map.of("type", "json_object")); + } + + return builder.build(); + } + + /** + * 构建带思考模式的请求对象 + */ + private OpenAIRequest buildRequestWithThinking(String prompt, Integer maxTokens, Double temperature) { + return OpenAIRequest.builder() + .model(config.getModelName()) + .messages(List.of( + OpenAIRequest.Message.builder() + .role("user") + .content(prompt) + .build() + )) + .max_tokens(maxTokens != null ? maxTokens : config.getDefaultMaxTokens()) + .temperature(temperature != null ? temperature : config.getDefaultTemperature()) + .stream(false) + .enable_thinking(true) + .build(); + } + + /** + * 检查模型是否可用 + */ + public boolean isAvailable() { + return Boolean.TRUE.equals(config.getEnabled()) && config.isValid(); + } + + /** + * 流式生成响应 + */ + public Flux generateStream(String prompt) { + return generateStream(prompt, null, null, null); + } + + /** + * 流式生成响应 - 支持自定义参数 + */ + public Flux generateStream(String prompt, Integer maxTokens, Double temperature, Boolean jsonOutput) { + try { + OpenAIRequest request = buildStreamRequest(prompt, maxTokens, temperature, jsonOutput); + return httpClient.chatCompletionStream(request); + } catch (Exception e) { + log.error("模型{}流式生成失败", config.getModelName(), e); + return Flux.error(new RuntimeException("模型流式生成失败: " + e.getMessage(), e)); + } + } + + /** + * 流式生成响应 - 支持思考模式 + */ + public Flux generateStreamWithThinking(String prompt, Integer maxTokens, Double temperature) { + if (!Boolean.TRUE.equals(config.getSupportThinking())) { + log.warn("模型{}不支持思考模式,使用普通流式模式", config.getModelName()); + return generateStream(prompt, maxTokens, temperature, null); + } + + try { + OpenAIRequest request = buildStreamRequestWithThinking(prompt, maxTokens, temperature); + return httpClient.chatCompletionStream(request); + } catch (Exception e) { + log.error("模型{}思考模式流式生成失败", config.getModelName(), e); + return Flux.error(new RuntimeException("思考模式流式生成失败: " + e.getMessage(), e)); + } + } + + /** + * 构建流式请求对象 + */ + private OpenAIRequest buildStreamRequest(String prompt, Integer maxTokens, Double temperature, Boolean jsonOutput) { + var builder = OpenAIRequest.builder() + .model(config.getModelName()) + .messages(List.of( + OpenAIRequest.Message.builder() + .role("user") + .content(prompt) + .build() + )) + .max_tokens(maxTokens != null ? maxTokens : config.getDefaultMaxTokens()) + .temperature(temperature != null ? temperature : config.getDefaultTemperature()) + .stream(true); // 设置为true以启用流式输出 + + // 设置JSON输出格式 + if (Boolean.TRUE.equals(jsonOutput) && Boolean.TRUE.equals(config.getSupportJsonOutput())) { + builder.response_format(Map.of("type", "json_object")); + } + + return builder.build(); + } + + /** + * 构建带思考模式的流式请求对象 + */ + private OpenAIRequest buildStreamRequestWithThinking(String prompt, Integer maxTokens, Double temperature) { + return OpenAIRequest.builder() + .model(config.getModelName()) + .messages(List.of( + OpenAIRequest.Message.builder() + .role("user") + .content(prompt) + .build() + )) + .max_tokens(maxTokens != null ? maxTokens : config.getDefaultMaxTokens()) + .temperature(temperature != null ? temperature : config.getDefaultTemperature()) + .stream(true) + .enable_thinking(true) + .build(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIHttpClient.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIHttpClient.java new file mode 100644 index 0000000..3415373 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIHttpClient.java @@ -0,0 +1,256 @@ +package com.suifeng.sfchain.core.openai; + +import com.alibaba.fastjson2.JSON; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * 描述: OpenAI兼容的HTTP客户端 + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +public class OpenAIHttpClient { + + private final String baseUrl; + private final String apiKey; + private final Map defaultHeaders; + + public OpenAIHttpClient(String baseUrl, String apiKey) { + this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; + this.apiKey = apiKey; + this.defaultHeaders = Map.of( + "Content-Type", "application/json", + "Authorization", "Bearer " + apiKey + ); + } + + public OpenAIHttpClient(String baseUrl, String apiKey, Map additionalHeaders) { + this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl; + this.apiKey = apiKey; + this.defaultHeaders = new HashMap<>(); + this.defaultHeaders.put("Content-Type", "application/json"); + this.defaultHeaders.put("Authorization", "Bearer " + apiKey); + if (additionalHeaders != null) { + this.defaultHeaders.putAll(additionalHeaders); + } + } + + /** + * 发送聊天完成请求 + */ + public OpenAIResponse chatCompletion(OpenAIRequest request) { + try { + // 智能构建endpoint,避免重复的/v1路径 + String endpoint; + if (baseUrl.endsWith("/v1") || baseUrl.contains("/v1/")) { + // baseUrl已包含v1路径,直接添加chat/completions + endpoint = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "chat/completions"; + } else { + // baseUrl不包含v1路径,添加完整路径 + endpoint = baseUrl + "/v1/chat/completions"; + } + String requestBody = JSON.toJSONString(request); + + log.debug("发送请求到: {}", endpoint); + log.debug("请求体: {}", requestBody); + log.info("构建的API端点: {}", endpoint); + + HttpURLConnection connection = createConnection(endpoint); + + // 发送请求体 + try (OutputStream os = connection.getOutputStream()) { + byte[] input = requestBody.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + // 读取响应 + StringBuilder response = new StringBuilder(); + int responseCode = connection.getResponseCode(); + + if (responseCode == HttpURLConnection.HTTP_OK) { + try (BufferedReader br = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + response.append(line); + } + } + } else { + // 读取错误响应 + try (BufferedReader br = new BufferedReader( + new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + response.append(line); + } + } + throw new RuntimeException("HTTP请求失败,状态码: " + responseCode + ", 响应: " + response.toString()); + } + + String responseBody = response.toString(); + log.debug("响应体: {}", responseBody); + + return JSON.parseObject(responseBody, OpenAIResponse.class); + + } catch (Exception e) { + log.error("OpenAI API调用失败", e); + throw new RuntimeException("OpenAI API调用失败: " + e.getMessage(), e); + } + } + + /** + * 创建HTTP连接 + */ + private HttpURLConnection createConnection(String endpoint) throws Exception { + URL url = new URL(endpoint); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setConnectTimeout(30000); // 30秒连接超时 + connection.setReadTimeout(120000); // 120秒读取超时 + + // 设置请求头 + defaultHeaders.forEach(connection::setRequestProperty); + + return connection; + } + + /** + * 提取响应内容 + */ + public String extractContent(OpenAIResponse response) { + if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) { + return ""; + } + + OpenAIResponse.Choice choice = response.getChoices().get(0); + if (choice.getMessage() != null && choice.getMessage().getContent() != null) { + return choice.getMessage().getContent(); + } + + return ""; + } + + /** + * 发送流式聊天完成请求 + */ + public Flux chatCompletionStream(OpenAIRequest request) { + return Flux.create(sink -> { + // 在新线程中异步处理流式响应 + CompletableFuture.runAsync(() -> { + try { + // 设置流式请求 + OpenAIRequest streamRequest = request.toBuilder().stream(true).build(); + + // 智能构建endpoint + String endpoint; + if (baseUrl.endsWith("/v1") || baseUrl.contains("/v1/")) { + endpoint = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + "chat/completions"; + } else { + endpoint = baseUrl + "/v1/chat/completions"; + } + + String requestBody = JSON.toJSONString(streamRequest); + log.debug("发送流式请求到: {}", endpoint); + log.debug("请求体: {}", requestBody); + + HttpURLConnection connection = createConnection(endpoint); + + // 发送请求体 + try (OutputStream os = connection.getOutputStream()) { + byte[] input = requestBody.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + // 读取流式响应 + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + + String line; + while ((line = reader.readLine()) != null && !sink.isCancelled()) { + if (line.trim().isEmpty()) { + continue; + } + + // 处理SSE格式的数据 + if (line.startsWith("data: ")) { + String data = line.substring(6).trim(); + + // 检查是否为结束标记 + if ("[DONE]".equals(data)) { + sink.complete(); + break; + } + + try { + // 解析流式响应 + OpenAIStreamResponse streamResponse = JSON.parseObject(data, OpenAIStreamResponse.class); + String content = extractStreamContent(streamResponse); + if (content != null && !content.isEmpty()) { + // 立即发送内容,而不是缓存 + sink.next(content); + // 添加小延迟以确保流式效果 + Thread.sleep(10); + } + } catch (Exception e) { + log.warn("解析流式响应失败: {}", data, e); + } + } + } + + if (!sink.isCancelled()) { + sink.complete(); + } + } + } else { + // 读取错误响应 + StringBuilder errorResponse = new StringBuilder(); + try (BufferedReader br = new BufferedReader( + new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = br.readLine()) != null) { + errorResponse.append(line); + } + } + sink.error(new RuntimeException("HTTP请求失败,状态码: " + responseCode + ", 响应: " + errorResponse.toString())); + } + + } catch (Exception e) { + log.error("流式OpenAI API调用失败", e); + sink.error(new RuntimeException("流式OpenAI API调用失败: " + e.getMessage(), e)); + } + }); + }, FluxSink.OverflowStrategy.BUFFER); + } + + /** + * 提取流式响应内容 + */ + private String extractStreamContent(OpenAIStreamResponse response) { + if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) { + return null; + } + + OpenAIStreamResponse.StreamChoice choice = response.getChoices().get(0); + if (choice.getDelta() != null && choice.getDelta().getContent() != null) { + return choice.getDelta().getContent(); + } + + return null; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelConfig.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelConfig.java new file mode 100644 index 0000000..d8c9efb --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelConfig.java @@ -0,0 +1,120 @@ +package com.suifeng.sfchain.core.openai; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * 描述: OpenAI兼容的模型配置 + * @author suifeng + * 日期: 2025/8/11 + */ +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +@NoArgsConstructor +public class OpenAIModelConfig { + + /** + * 模型名称 + */ + private String modelName; + + /** + * API基础URL + */ + private String baseUrl; + + /** + * API密钥 + */ + private String apiKey; + + /** + * 默认最大token数 + */ + private Integer defaultMaxTokens; + + /** + * 默认温度参数 + */ + private Double defaultTemperature; + + /** + * 是否支持流式输出 + */ + private Boolean supportStream; + + /** + * 是否支持JSON格式输出 + */ + private Boolean supportJsonOutput; + + /** + * 是否支持思考模式 + */ + private Boolean supportThinking; + + /** + * 额外的HTTP请求头 + */ + private Map additionalHeaders; + + /** + * 模型描述 + */ + private String description; + + /** + * 模型提供商 + */ + private String provider; + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 获取额外请求头,如果为null则返回空Map + */ + public Map getAdditionalHeaders() { + return additionalHeaders != null ? additionalHeaders : new HashMap<>(); + } + + /** + * 添加额外请求头 + */ + public void addHeader(String key, String value) { + if (additionalHeaders == null) { + additionalHeaders = new HashMap<>(); + } + additionalHeaders.put(key, value); + } + + /** + * 检查配置是否有效 + */ + public boolean isValid() { + return modelName != null && !modelName.trim().isEmpty() && + baseUrl != null && !baseUrl.trim().isEmpty() && + apiKey != null && !apiKey.trim().isEmpty(); + } + + /** + * 获取默认配置的构建器 + */ + public static OpenAIModelConfigBuilder defaultConfig() { + return OpenAIModelConfig.builder() + .defaultMaxTokens(4096) + .defaultTemperature(0.7) + .supportStream(false) + .supportJsonOutput(false) + .supportThinking(false) + .enabled(true); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelFactory.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelFactory.java new file mode 100644 index 0000000..7394cd9 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIModelFactory.java @@ -0,0 +1,79 @@ +package com.suifeng.sfchain.core.openai; + +import com.suifeng.sfchain.core.AIModel; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 描述: OpenAI兼容模型工厂 + * @author suifeng + * 日期: 2025/8/11 + */ +@Slf4j +public class OpenAIModelFactory { + + private final Map modelConfigs = new ConcurrentHashMap<>(); + private final Map modelInstances = new ConcurrentHashMap<>(); + + /** + * 注册模型配置 + */ + public void registerModel(OpenAIModelConfig config) { + if (!config.isValid()) { + throw new IllegalArgumentException("无效的模型配置: " + config.getModelName()); + } + + modelConfigs.put(config.getModelName(), config); + log.info("注册模型配置: {} ({})", config.getModelName(), config.getProvider()); + } + + /** + * 创建模型实例 + */ + public AIModel createModel(String modelName) { + return modelInstances.computeIfAbsent(modelName, name -> { + OpenAIModelConfig config = modelConfigs.get(name); + if (config == null) { + throw new IllegalArgumentException("未找到模型配置: " + name); + } + + if (!Boolean.TRUE.equals(config.getEnabled())) { + throw new IllegalStateException("模型已禁用: " + name); + } + + return new OpenAICompatibleModel(config); + }); + } + + /** + * 获取所有已注册的模型名称 + */ + public java.util.Set getRegisteredModelNames() { + return modelConfigs.keySet(); + } + + /** + * 获取模型配置 + */ + public OpenAIModelConfig getModelConfig(String modelName) { + return modelConfigs.get(modelName); + } + + /** + * 检查模型是否已注册 + */ + public boolean isModelRegistered(String modelName) { + return modelConfigs.containsKey(modelName); + } + + /** + * 移除模型 + */ + public void removeModel(String modelName) { + modelConfigs.remove(modelName); + modelInstances.remove(modelName); + log.info("移除模型: {}", modelName); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIRequest.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIRequest.java new file mode 100644 index 0000000..66d78cf --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIRequest.java @@ -0,0 +1,102 @@ +package com.suifeng.sfchain.core.openai; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +/** + * 描述: OpenAI兼容的请求体 + * @author suifeng + * 日期: 2025/8/11 + */ +@Data +@Builder(toBuilder = true) +@AllArgsConstructor +@NoArgsConstructor +public class OpenAIRequest { + + /** + * 模型名称 + */ + private String model; + + /** + * 消息列表 + */ + private List messages; + + /** + * 最大token数 + */ + private Integer max_tokens; + + /** + * 温度参数 (0.0-2.0) + */ + private Double temperature; + + /** + * 是否流式输出 + */ + private Boolean stream; + + /** + * 响应格式 + */ + private Map response_format; + + /** + * 是否启用思考模式 (部分模型支持) + */ + private Boolean enable_thinking; + + /** + * top_p参数 + */ + private Double top_p; + + /** + * 频率惩罚 + */ + private Double frequency_penalty; + + /** + * 存在惩罚 + */ + private Double presence_penalty; + + /** + * 停止词 + */ + private List stop; + + /** + * 用户标识 + */ + private String user; + + @Data + @Builder(toBuilder = true) + @AllArgsConstructor + @NoArgsConstructor + public static class Message { + /** + * 角色: system, user, assistant + */ + private String role; + + /** + * 消息内容 + */ + private String content; + + /** + * 消息名称 (可选) + */ + private String name; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIResponse.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIResponse.java new file mode 100644 index 0000000..ff23ffd --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIResponse.java @@ -0,0 +1,108 @@ +package com.suifeng.sfchain.core.openai; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 描述: OpenAI兼容的响应体 + * @author suifeng + * 日期: 2025/8/11 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OpenAIResponse { + + /** + * 响应ID + */ + private String id; + + /** + * 对象类型 + */ + private String object; + + /** + * 创建时间戳 + */ + private Long created; + + /** + * 模型名称 + */ + private String model; + + /** + * 选择列表 + */ + private List choices; + + /** + * 使用情况 + */ + private Usage usage; + + /** + * 系统指纹 + */ + private String system_fingerprint; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Choice { + /** + * 选择索引 + */ + private Integer index; + + /** + * 消息内容 + */ + private OpenAIRequest.Message message; + + /** + * 完成原因 + */ + private String finish_reason; + + /** + * logprobs (可选) + */ + private Object logprobs; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Usage { + /** + * 提示token数 + */ + private Integer prompt_tokens; + + /** + * 完成token数 + */ + private Integer completion_tokens; + + /** + * 总token数 + */ + private Integer total_tokens; + + /** + * 提示token详情 (可选) + */ + private Object prompt_tokens_details; + + /** + * 完成token详情 (可选) + */ + private Object completion_tokens_details; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIStreamResponse.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIStreamResponse.java new file mode 100644 index 0000000..cd6b00b --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/core/openai/OpenAIStreamResponse.java @@ -0,0 +1,88 @@ +package com.suifeng.sfchain.core.openai; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 描述: OpenAI兼容的流式响应体 + * @author suifeng + * 日期: 2025/8/11 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OpenAIStreamResponse { + + /** + * 响应ID + */ + private String id; + + /** + * 对象类型 + */ + private String object; + + /** + * 创建时间戳 + */ + private Long created; + + /** + * 模型名称 + */ + private String model; + + /** + * 选择列表 + */ + private List choices; + + /** + * 系统指纹 + */ + private String system_fingerprint; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class StreamChoice { + /** + * 选择索引 + */ + private Integer index; + + /** + * 增量消息内容 + */ + private Delta delta; + + /** + * 完成原因 + */ + private String finish_reason; + + /** + * logprobs (可选) + */ + private Object logprobs; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class Delta { + /** + * 角色 (仅在第一个chunk中出现) + */ + private String role; + + /** + * 增量内容 + */ + private String content; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/operations/JSONRepairOperation.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/operations/JSONRepairOperation.java new file mode 100644 index 0000000..ac35ec3 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/operations/JSONRepairOperation.java @@ -0,0 +1,140 @@ +package com.suifeng.sfchain.operations; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.suifeng.sfchain.annotation.AIOp; +import com.suifeng.sfchain.core.BaseAIOperation; +import org.springframework.stereotype.Component; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.suifeng.sfchain.constants.AIOperationConstant.JSON_REPAIR_OP; + + +/** + * 描述: JSON修复操作 + * 专门用于修复AI返回的格式错误的JSON字符串 + * @author suifeng + * 日期: 2025/8/11 + */ +@AIOp( + value = JSON_REPAIR_OP, + description = "修复格式错误的JSON字符串", + autoRepairJson = false +) +@Component +public class JSONRepairOperation extends BaseAIOperation { + + /** + * 当前输入的JSON字符串 + */ + private String currentInput; + + @Override + public String buildPrompt(String brokenJson) { + this.currentInput = brokenJson; // 保存当前输入 + + // 如果输入已经是有效JSON,尝试直接解析 + if (isValidJson(brokenJson)) { + return ""; // 返回空字符串表示不需要AI修复 + } + + return String.format(""" + 你是一位专业的JSON格式修复专家,需要将格式错误的JSON字符串修复为有效的JSON格式。 + + ## 任务描述 + 我将提供一个可能包含格式错误的JSON字符串。你的任务是: + 1. 识别并修复所有格式错误,包括但不限于: + - 缺失或多余的引号、逗号、括号 + - 非法的转义字符 + - 重复的键 + - 不符合JSON规范的值格式 + 2. 保留原始JSON的所有数据和结构 + 3. 返回修复后的有效JSON字符串 + + ## 需要修复的JSON + ``` + %s + ``` + + ## 输出要求 + 1. 只返回修复后的JSON字符串,不要有任何解释或额外文字 + 2. 确保输出是有效的JSON格式 + 3. 保持原始数据的完整性,除非格式错误导致数据冗余 + 4. 如果某部分无法修复,使用最合理的猜测进行修复 + + ## 注意事项 + - 不要给我任何多余的东西,只需要正确的JSON对象 + - 不要添加原始JSON中不存在的键或值 + - 如果原始字符串中包含多个JSON对象,只修复第一个完整的JSON对象 + - 确保所有字符串值使用双引号包围 + - 确保数字、布尔值和null值不使用引号 + - 移除任何注释或非JSON元素 + """, brokenJson); + } + + @Override + protected String preprocessResponse(String aiResponse, String brokenJson) { + // 如果提示为空(表示原始输入已是有效JSON),直接返回原始输入 + if (aiResponse.isEmpty()) { + return this.currentInput; + } + return aiResponse; + } + + @Override + protected String preprocessJson(String jsonContent, String brokenJson) { + // 如果提取失败,尝试本地修复 + if (!isValidJson(jsonContent)) { + return localJsonRepair(jsonContent); + } + return jsonContent; + } + + /** + * 检查字符串是否为有效的JSON + */ + private boolean isValidJson(String jsonStr) { + try { + JSON.parseObject(jsonStr); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 本地JSON修复逻辑,用于简单错误的修复 + */ + private String localJsonRepair(String brokenJson) { + // 1. 移除可能的代码块标记 + String json = brokenJson.replaceAll("```json|```", "").trim(); + + // 2. 确保对象以 { 开始,以 } 结束 + Pattern objectPattern = Pattern.compile("\\{.*\\}", Pattern.DOTALL); + Matcher objectMatcher = objectPattern.matcher(json); + if (objectMatcher.find()) { + json = objectMatcher.group(); + } + + // 3. 修复常见引号问题 + json = json.replaceAll("(? { + + @Override + protected String buildPrompt(ValidationRequest input) { + return String.format( + """ + 请回答一个简单的问题来验证模型是否正常工作。 + 问题: %s + 请严格作答,并以JSON格式返回结果: + ```json + { + "answer": "5" + } + ``` + 注意:请确保返回有效的JSON格式。""", + input.getQuestion() + ); + } + + @Override + protected ValidationResult parseResult(String jsonContent, ValidationRequest input) { + try { + return objectMapper.readValue(jsonContent, ValidationResult.class); + } catch (Exception e) { + log.warn("解析验证响应失败,使用默认结果: {}", e.getMessage()); + ValidationResult result = new ValidationResult(); + result.setAnswer("模型响应解析失败,但模型可以正常通信"); + return result; + } + } + + @Override + public String getDescription() { + return "模型验证操作 - 通过简单问答验证模型配置是否可用"; + } + + /** + * 验证请求 + */ + @Data + public static class ValidationRequest { + @JsonProperty("question") + private String question; + + public ValidationRequest() { + this.question = "1+1等于几?"; + } + + public ValidationRequest(String question) { + this.question = question; + } + } + + /** + * 验证结果 + */ + @Data + public static class ValidationResult { + @JsonProperty("answer") + private String answer; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DatabaseInitializationService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DatabaseInitializationService.java new file mode 100644 index 0000000..9d50e38 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DatabaseInitializationService.java @@ -0,0 +1,125 @@ +package com.suifeng.sfchain.persistence; + +import com.suifeng.sfchain.persistence.config.DatabaseType; +import com.suifeng.sfchain.persistence.config.PersistenceConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.init.ScriptUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * 数据库初始化服务 + * 根据配置的数据库类型自动执行对应的migration脚本 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "sf-chain.persistence", name = "database-type") +public class DatabaseInitializationService { + + private final PersistenceConfig persistenceConfig; + private final DataSource dataSource; + private final JdbcTemplate jdbcTemplate; + + /** + * 应用启动后自动执行数据库初始化 + */ + @PostConstruct + @Transactional + public void initializeDatabase() { + try { + DatabaseType databaseType = persistenceConfig.getDatabaseTypeEnum(); + log.info("检测到数据库类型配置: {},开始执行数据库初始化", databaseType); + + // 检查是否需要初始化 + if (shouldInitialize(databaseType)) { + executeMigrationScript(databaseType); + log.info("数据库初始化完成: {}", databaseType); + } else { + log.info("数据库已存在相关表,跳过初始化: {}", databaseType); + } + + } catch (Exception e) { + log.error("数据库初始化失败", e); + throw new RuntimeException("数据库初始化失败", e); + } + } + + /** + * 检查是否需要执行数据库初始化 + * 通过检查核心表是否存在来判断 + */ + private boolean shouldInitialize(DatabaseType databaseType) { + try { + String checkTableSql; + switch (databaseType) { + case MYSQL: + checkTableSql = "SELECT COUNT(*) FROM information_schema.tables " + + "WHERE table_schema = DATABASE() AND table_name = 'sfchain_model_configs'"; + break; + case POSTGRESQL: + checkTableSql = "SELECT COUNT(*) FROM information_schema.tables " + + "WHERE table_name = 'sfchain_model_configs' AND table_schema = 'public'"; + break; + default: + log.warn("不支持的数据库类型: {}", databaseType); + return false; + } + + Integer count = jdbcTemplate.queryForObject(checkTableSql, Integer.class); + return count == null || count == 0; + + } catch (Exception e) { + log.debug("检查表存在性时出现异常,假设需要初始化: {}", e.getMessage()); + return true; + } + } + + /** + * 执行对应数据库类型的migration脚本 + */ + private void executeMigrationScript(DatabaseType databaseType) throws SQLException { + String scriptPath = getMigrationScriptPath(databaseType); + log.info("执行数据库脚本: {}", scriptPath); + + try (Connection connection = dataSource.getConnection()) { + ClassPathResource resource = new ClassPathResource(scriptPath); + if (!resource.exists()) { + throw new RuntimeException("Migration脚本不存在: " + scriptPath); + } + + ScriptUtils.executeSqlScript(connection, resource); + log.info("成功执行数据库脚本: {}", scriptPath); + + } catch (Exception e) { + log.error("执行数据库脚本失败: {}", scriptPath, e); + throw new SQLException("执行数据库脚本失败: " + scriptPath, e); + } + } + + /** + * 根据数据库类型获取对应的migration脚本路径 + */ + private String getMigrationScriptPath(DatabaseType databaseType) { + switch (databaseType) { + case MYSQL: + return "migration/v1_mysql.sql"; + case POSTGRESQL: + return "migration/v1_postgresql.sql"; + default: + throw new IllegalArgumentException("不支持的数据库类型: " + databaseType); + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DynamicOperationConfigService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DynamicOperationConfigService.java new file mode 100644 index 0000000..9f49f6d --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/DynamicOperationConfigService.java @@ -0,0 +1,82 @@ +package com.suifeng.sfchain.persistence; + +import com.suifeng.sfchain.annotation.AIOp; +import com.suifeng.sfchain.core.AIOperationRegistry; +import com.suifeng.sfchain.core.BaseAIOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * 描述: 动态操作配置服务 + * 从@AIOp注解中获取操作配置信息 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DynamicOperationConfigService { + + private final AIOperationRegistry operationRegistry; + + /** + * 从@AIOp注解获取操作配置 + * + * @param operationType 操作类型 + * @return 操作配置 + */ + public Optional getOperationConfig(String operationType) { + try { + if (!operationRegistry.isOperationRegistered(operationType)) { + return Optional.empty(); + } + + BaseAIOperation operation = operationRegistry.getOperation(operationType); + AIOp annotation = operation.getAnnotation(); + + if (annotation == null) { + return Optional.empty(); + } + + // 从注解构建配置 + OperationConfigData config = OperationConfigData.builder() + .operationType(operationType) + .description(annotation.description()) + .enabled(annotation.enabled()) + .maxTokens(annotation.defaultMaxTokens() > 0 ? annotation.defaultMaxTokens() : null) + .temperature(annotation.defaultTemperature() >= 0 ? annotation.defaultTemperature() : null) + .jsonOutput(annotation.requireJsonOutput()) + .thinkingMode(annotation.supportThinking()) + .modelName(annotation.defaultModel().isEmpty() ? null : annotation.defaultModel()) + .build(); + + return Optional.of(config); + } catch (Exception e) { + log.warn("获取操作 {} 的动态配置失败: {}", operationType, e.getMessage()); + return Optional.empty(); + } + } + + /** + * 获取所有操作的动态配置 + * + * @return 所有动态配置 + */ + public Map getAllOperationConfigs() { + Map configs = new HashMap<>(); + + for (String operationType : operationRegistry.getAllOperations()) { + getOperationConfig(operationType).ifPresent(config -> + configs.put(operationType, config) + ); + } + + return configs; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/ModelConfigData.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/ModelConfigData.java new file mode 100644 index 0000000..d7784c5 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/ModelConfigData.java @@ -0,0 +1,125 @@ +package com.suifeng.sfchain.persistence; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * 描述: 模型配置数据类 + * 用于持久化存储的模型配置信息 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ModelConfigData { + + /** + * 模型名称 + */ + private String modelName; + + /** + * API基础URL + */ + private String baseUrl; + + /** + * API密钥 + */ + private String apiKey; + + /** + * 默认最大token数 + */ + @Builder.Default + private Integer defaultMaxTokens = 4096; + + /** + * 默认温度参数 + */ + @Builder.Default + private Double defaultTemperature = 0.7; + + /** + * 是否支持流式输出 + */ + @Builder.Default + private Boolean supportStream = false; + + /** + * 是否支持JSON格式输出 + */ + @Builder.Default + private Boolean supportJsonOutput = false; + + /** + * 是否支持思考模式 + */ + @Builder.Default + private Boolean supportThinking = false; + + /** + * 额外的HTTP请求头 + */ + @Builder.Default + private Map additionalHeaders = new HashMap<>(); + + /** + * 模型描述 + */ + private String description; + + /** + * 模型提供商 + */ + private String provider; + + /** + * 是否启用 + */ + @Builder.Default + private Boolean enabled = true; + + /** + * 创建时间戳 + */ + private Long createdAt; + + /** + * 更新时间戳 + */ + private Long updatedAt; + + /** + * 验证配置是否有效 + * + * @return 是否有效 + */ + public boolean isValid() { + return modelName != null && !modelName.trim().isEmpty() && + baseUrl != null && !baseUrl.trim().isEmpty() && + apiKey != null && !apiKey.trim().isEmpty(); + } + + /** + * 更新时间戳 + */ + public void updateTimestamp() { + this.updatedAt = System.currentTimeMillis(); + if (this.createdAt == null) { + this.createdAt = this.updatedAt; + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/MySQLPersistenceService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/MySQLPersistenceService.java new file mode 100644 index 0000000..67d6405 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/MySQLPersistenceService.java @@ -0,0 +1,314 @@ +package com.suifeng.sfchain.persistence; + +import com.suifeng.sfchain.persistence.entity.ModelConfigEntity; +import com.suifeng.sfchain.persistence.entity.OperationConfigEntity; +import com.suifeng.sfchain.persistence.repository.ModelConfigRepository; +import com.suifeng.sfchain.persistence.repository.OperationConfigRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 描述: 基于MySQL的持久化服务实现 + * 针对MySQL数据库的特定优化 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnProperty(name = "sf-chain.persistence.database-type", havingValue = "mysql") +public class MySQLPersistenceService implements PersistenceService { + + private final ModelConfigRepository modelConfigRepository; + private final OperationConfigRepository operationConfigRepository; + + @Override + @Transactional + public void saveModelConfig(String modelName, ModelConfigData config) { + log.debug("[MySQL] 保存模型配置: {}", modelName); + config.setModelName(modelName); + + // MySQL特定的保存逻辑,使用ON DUPLICATE KEY UPDATE + ModelConfigEntity entity = modelConfigRepository.findByModelName(modelName) + .map(existing -> { + // 更新现有实体 + Long id = existing.getId(); + LocalDateTime createdAt = existing.getCreatedAt(); + + ModelConfigEntity updatedEntity = convertToEntity(config); + updatedEntity.setId(id); + updatedEntity.setCreatedAt(createdAt); + + return updatedEntity; + }) + .orElseGet(() -> convertToEntity(config)); + + modelConfigRepository.save(entity); + log.info("[MySQL] 模型配置保存成功: {}", modelName); + } + + @Override + public Optional getModelConfig(String modelName) { + log.debug("[MySQL] 获取模型配置: {}", modelName); + return modelConfigRepository.findByModelName(modelName) + .map(this::convertToData); + } + + @Override + public Map getAllModelConfigs() { + log.debug("[MySQL] 获取所有模型配置"); + return modelConfigRepository.findAll().stream() + .collect(Collectors.toMap( + ModelConfigEntity::getModelName, + this::convertToData + )); + } + + @Override + @Transactional + public boolean deleteModelConfig(String modelName) { + log.debug("[MySQL] 删除模型配置: {}", modelName); + try { + modelConfigRepository.deleteByModelName(modelName); + log.info("[MySQL] 模型配置删除成功: {}", modelName); + return true; + } catch (Exception e) { + log.error("[MySQL] 删除模型配置失败: {}", modelName, e); + return false; + } + } + + @Override + public boolean existsModelConfig(String modelName) { + return modelConfigRepository.existsByModelName(modelName); + } + + @Override + public List getAllModelNames() { + return modelConfigRepository.findEnabledModelNames(); + } + + @Override + @Transactional + public void saveOperationConfig(String operationType, OperationConfigData config) { + log.debug("[MySQL] 保存操作配置: {}", operationType); + config.setOperationType(operationType); + + OperationConfigEntity entity = operationConfigRepository.findByOperationType(operationType) + .map(existing -> { + Long id = existing.getId(); + LocalDateTime createdAt = existing.getCreatedAt(); + + OperationConfigEntity updatedEntity = convertToEntity(config); + updatedEntity.setId(id); + updatedEntity.setCreatedAt(createdAt); + + return updatedEntity; + }) + .orElseGet(() -> convertToEntity(config)); + + operationConfigRepository.save(entity); + log.info("[MySQL] 操作配置保存成功: {}", operationType); + } + + @Override + public Optional getOperationConfig(String operationType) { + log.debug("[MySQL] 获取操作配置: {}", operationType); + return operationConfigRepository.findByOperationType(operationType) + .map(this::convertToData); + } + + @Override + public Map getAllOperationConfigs() { + log.debug("[MySQL] 获取所有操作配置"); + return operationConfigRepository.findAll().stream() + .collect(Collectors.toMap( + OperationConfigEntity::getOperationType, + this::convertToData + )); + } + + @Override + @Transactional + public boolean deleteOperationConfig(String operationType) { + log.debug("[MySQL] 删除操作配置: {}", operationType); + try { + operationConfigRepository.deleteByOperationType(operationType); + log.info("[MySQL] 操作配置删除成功: {}", operationType); + return true; + } catch (Exception e) { + log.error("[MySQL] 删除操作配置失败: {}", operationType, e); + return false; + } + } + + @Override + public void flush() { + log.debug("[MySQL] 刷新缓存"); + // MySQL特定的刷新逻辑 + } + + @Override + public void reload() { + log.debug("[MySQL] 重新加载配置"); + // MySQL特定的重载逻辑 + } + + @Override + @Transactional + public void backup(String backupName) { + log.info("[MySQL] 创建备份: {}", backupName); + // MySQL特定的备份实现 + // 可以使用CREATE TABLE ... AS SELECT语句 + } + + @Override + @Transactional + public void restoreFromBackup(String backupName) { + log.info("[MySQL] 从备份恢复: {}", backupName); + // MySQL特定的恢复实现 + } + + @Override + public List getAllBackupNames() { + log.debug("[MySQL] 获取所有备份名称"); + // 返回备份列表 + return List.of(); + } + + /** + * 将ModelConfigData转换为ModelConfigEntity + */ + private ModelConfigEntity convertToEntity(ModelConfigData data) { + ModelConfigEntity entity = new ModelConfigEntity(); + + // 映射基本字段 + entity.setModelName(data.getModelName()); + entity.setProvider(data.getProvider()); + entity.setApiKey(data.getApiKey()); + entity.setBaseUrl(data.getBaseUrl()); + entity.setEnabled(data.getEnabled()); + entity.setDescription(data.getDescription()); + + // 将扩展字段映射到customParams + Map customParams = new HashMap<>(); + if (data.getDefaultMaxTokens() != null) { + customParams.put("defaultMaxTokens", data.getDefaultMaxTokens()); + } + if (data.getDefaultTemperature() != null) { + customParams.put("defaultTemperature", data.getDefaultTemperature()); + } + if (data.getSupportStream() != null) { + customParams.put("supportStream", data.getSupportStream()); + } + if (data.getSupportJsonOutput() != null) { + customParams.put("supportJsonOutput", data.getSupportJsonOutput()); + } + if (data.getSupportThinking() != null) { + customParams.put("supportThinking", data.getSupportThinking()); + } + if (data.getAdditionalHeaders() != null && !data.getAdditionalHeaders().isEmpty()) { + customParams.put("additionalHeaders", data.getAdditionalHeaders()); + } + if (data.getCreatedAt() != null) { + customParams.put("createdAt", data.getCreatedAt()); + } + if (data.getUpdatedAt() != null) { + customParams.put("updatedAt", data.getUpdatedAt()); + } + + entity.setCustomParams(customParams); + return entity; + } + + /** + * 将ModelConfigEntity转换为ModelConfigData + */ + private ModelConfigData convertToData(ModelConfigEntity entity) { + ModelConfigData.ModelConfigDataBuilder builder = ModelConfigData.builder() + .modelName(entity.getModelName()) + .provider(entity.getProvider()) + .apiKey(entity.getApiKey()) + .baseUrl(entity.getBaseUrl()) + .enabled(entity.getEnabled()) + .description(entity.getDescription()); + + // 从customParams中提取扩展字段 + Map customParams = entity.getCustomParams(); + if (customParams != null) { + if (customParams.containsKey("defaultMaxTokens")) { + builder.defaultMaxTokens((Integer) customParams.get("defaultMaxTokens")); + } + if (customParams.containsKey("defaultTemperature")) { + builder.defaultTemperature((Double) customParams.get("defaultTemperature")); + } + if (customParams.containsKey("supportStream")) { + builder.supportStream((Boolean) customParams.get("supportStream")); + } + if (customParams.containsKey("supportJsonOutput")) { + builder.supportJsonOutput((Boolean) customParams.get("supportJsonOutput")); + } + if (customParams.containsKey("supportThinking")) { + builder.supportThinking((Boolean) customParams.get("supportThinking")); + } + if (customParams.containsKey("additionalHeaders")) { + @SuppressWarnings("unchecked") + Map headers = (Map) customParams.get("additionalHeaders"); + builder.additionalHeaders(headers != null ? headers : new HashMap<>()); + } + if (customParams.containsKey("createdAt")) { + builder.createdAt((Long) customParams.get("createdAt")); + } + if (customParams.containsKey("updatedAt")) { + builder.updatedAt((Long) customParams.get("updatedAt")); + } + } + + return builder.build(); + } + + /** + * 将OperationConfigData转换为OperationConfigEntity + */ + private OperationConfigEntity convertToEntity(OperationConfigData data) { + OperationConfigEntity entity = new OperationConfigEntity(); + entity.setOperationType(data.getOperationType()); + entity.setDescription(data.getDescription()); + entity.setEnabled(data.getEnabled()); + entity.setMaxTokens(data.getMaxTokens()); + entity.setTemperature(data.getTemperature()); + entity.setJsonOutput(data.getJsonOutput()); + entity.setThinkingMode(data.getThinkingMode()); + entity.setCustomParams(data.getCustomParams()); + entity.setModelName(data.getModelName()); + return entity; + } + + /** + * 将OperationConfigEntity转换为OperationConfigData + */ + private OperationConfigData convertToData(OperationConfigEntity entity) { + return OperationConfigData.builder() + .operationType(entity.getOperationType()) + .description(entity.getDescription()) + .enabled(entity.getEnabled()) + .maxTokens(entity.getMaxTokens()) + .temperature(entity.getTemperature()) + .jsonOutput(entity.getJsonOutput()) + .thinkingMode(entity.getThinkingMode()) + .customParams(entity.getCustomParams()) + .modelName(entity.getModelName()) + .build(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/OperationConfigData.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/OperationConfigData.java new file mode 100644 index 0000000..6e18d81 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/OperationConfigData.java @@ -0,0 +1,143 @@ +package com.suifeng.sfchain.persistence; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +/** + * 描述: 操作配置数据类 + * 用于持久化存储的操作配置信息 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class OperationConfigData { + + /** + * 操作类型 + */ + private String operationType; + + /** + * 操作描述 + */ + private String description; + + /** + * 是否启用 + */ + @Builder.Default + private Boolean enabled = true; + + /** + * 最大token数 + */ + private Integer maxTokens; + + /** + * 温度参数 + */ + private Double temperature; + + /** + * 超时时间(毫秒) + */ + private Long timeout; + + /** + * 重试次数 + */ + @Builder.Default + private Integer retryCount = 3; + + /** + * 是否启用JSON输出 + */ + @Builder.Default + private Boolean jsonOutput = false; + + /** + * 是否启用流式输出 + */ + @Builder.Default + private Boolean streamOutput = false; + + /** + * 是否启用思考模式 + */ + @Builder.Default + private Boolean thinkingMode = false; + + /** + * 自定义提示词前缀 + */ + private String promptPrefix; + + /** + * 自定义提示词后缀 + */ + private String promptSuffix; + + /** + * 系统提示词 + */ + private String systemPrompt; + + /** + * 输出格式说明 + */ + private String outputFormat; + + /** + * 自定义参数 + */ + @Builder.Default + private Map customParams = new HashMap<>(); + + /** + * 关联的模型名称 + */ + private String modelName; + + /** + * 验证配置是否有效 + * @return 是否有效 + */ + public boolean isValid() { + // 基本验证:操作类型不能为空 + if (operationType == null || operationType.trim().isEmpty()) { + return false; + } + + // 验证数值范围 + if (maxTokens != null && maxTokens <= 0) { + return false; + } + + if (temperature != null && (temperature < 0.0 || temperature > 2.0)) { + return false; + } + + if (retryCount != null && retryCount < 0) { + return false; + } + + if (timeout != null && timeout <= 0) { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceManager.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceManager.java new file mode 100644 index 0000000..80b92ee --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceManager.java @@ -0,0 +1,674 @@ +package com.suifeng.sfchain.persistence; + +import com.suifeng.sfchain.core.AIOperationRegistry; +import com.suifeng.sfchain.core.ModelRegistry; +import com.suifeng.sfchain.core.openai.OpenAIModelConfig; +import com.suifeng.sfchain.core.openai.OpenAIModelFactory; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Slf4j +@Component +public class PersistenceManager { + + private final PersistenceService persistenceService; + private final ModelRegistry modelRegistry; + private final AIOperationRegistry operationRegistry; + private final OpenAIModelFactory modelFactory; + private final DynamicOperationConfigService dynamicOperationConfigService; + + // 统一使用构造函数注入 + public PersistenceManager( + PersistenceServiceFactory persistenceServiceFactory, + ModelRegistry modelRegistry, + AIOperationRegistry operationRegistry, + OpenAIModelFactory modelFactory, + DynamicOperationConfigService dynamicOperationConfigService) { + this.persistenceService = persistenceServiceFactory.createPersistenceService(); + this.modelRegistry = modelRegistry; + this.operationRegistry = operationRegistry; + this.modelFactory = modelFactory; + this.dynamicOperationConfigService = dynamicOperationConfigService; + } + + /** + * 应用启动完成后同步配置 + */ + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReady() { + log.info("开始同步持久化配置..."); + + // 同步现有配置到持久化存储 + syncExistingConfigurations(); + + // 从持久化存储加载额外配置 + loadPersistedConfigurations(); + + log.info("持久化配置同步完成"); + } + + // ==================== 模型配置管理 ==================== + + /** + * 添加模型配置 + * + * @param modelName 模型名称 + * @param config 模型配置数据 + */ + public void addModelConfig(String modelName, ModelConfigData config) { + try { + // 验证配置 + if (!config.isValid()) { + throw new IllegalArgumentException("无效的模型配置: " + modelName); + } + + // 转换为OpenAI模型配置 + OpenAIModelConfig openAIConfig = convertToOpenAIConfig(config); + + // 注册到模型工厂 + modelFactory.registerModel(openAIConfig); + + // 保存到持久化存储 + persistenceService.saveModelConfig(modelName, config); + + log.info("成功添加模型配置: {}", modelName); + } catch (Exception e) { + log.error("添加模型配置失败: {} - {}", modelName, e.getMessage()); + throw new RuntimeException("添加模型配置失败: " + modelName, e); + } + } + + /** + * 更新模型配置 + * + * @param modelName 模型名称 + * @param config 模型配置数据 + */ + public void updateModelConfig(String modelName, ModelConfigData config) { + try { + // 检查模型是否存在 + if (!persistenceService.existsModelConfig(modelName)) { + throw new IllegalArgumentException("模型配置不存在: " + modelName); + } + + // 验证配置 + if (!config.isValid()) { + throw new IllegalArgumentException("无效的模型配置: " + modelName); + } + + // 移除旧配置 + modelFactory.removeModel(modelName); + + // 转换为OpenAI模型配置 + OpenAIModelConfig openAIConfig = convertToOpenAIConfig(config); + + // 重新注册到模型工厂 + modelFactory.registerModel(openAIConfig); + + // 更新持久化存储 + persistenceService.saveModelConfig(modelName, config); + + log.info("成功更新模型配置: {}", modelName); + } catch (Exception e) { + log.error("更新模型配置失败: {} - {}", modelName, e.getMessage()); + throw new RuntimeException("更新模型配置失败: " + modelName, e); + } + } + + /** + * 删除模型配置 + * + * @param modelName 模型名称 + */ + public void deleteModelConfig(String modelName) { + try { + // 检查是否有操作正在使用此模型(从操作配置中检查) + Map allConfigs = persistenceService.getAllOperationConfigs(); + boolean inUse = allConfigs.values().stream() + .anyMatch(config -> modelName.equals(config.getModelName())); + if (inUse) { + throw new IllegalStateException("模型正在被操作使用,无法删除: " + modelName); + } + + // 从模型工厂移除 + modelFactory.removeModel(modelName); + + // 从持久化存储删除 + persistenceService.deleteModelConfig(modelName); + + log.info("成功删除模型配置: {}", modelName); + } catch (Exception e) { + log.error("删除模型配置失败: {} - {}", modelName, e.getMessage()); + throw new RuntimeException("删除模型配置失败: " + modelName, e); + } + } + + /** + * 获取模型配置 + * + * @param modelName 模型名称 + * @return 模型配置 + */ + public Optional getModelConfig(String modelName) { + return persistenceService.getModelConfig(modelName); + } + + /** + * 获取所有模型配置 + * + * @return 所有模型配置 + */ + public Map getAllModelConfigs() { + return persistenceService.getAllModelConfigs(); + } + + // ==================== 操作配置管理 ==================== + + /** + * 保存操作配置 + * 同时更新数据库和内存容器 + * + * @param operationType 操作类型 + * @param config 操作配置 + */ + public void saveOperationConfig(String operationType, OperationConfigData config) { + try { + // 验证操作是否已注册 + if (!operationRegistry.isOperationRegistered(operationType)) { + throw new IllegalArgumentException("操作未注册: " + operationType); + } + + // 验证配置 + if (!config.isValid()) { + throw new IllegalArgumentException("无效的操作配置: " + operationType); + } + + // 如果配置中指定了模型,验证模型是否存在 + if (config.getModelName() != null && !config.getModelName().isEmpty()) { + if (!persistenceService.existsModelConfig(config.getModelName()) && + !modelRegistry.isModelRegistered(config.getModelName())) { + throw new IllegalArgumentException("指定的模型不存在: " + config.getModelName()); + } + } + + // 1. 先保存到数据库 + persistenceService.saveOperationConfig(operationType, config); + + // 2. ✅ 完整同步所有配置到框架(包括模型映射和其他参数) + syncDatabaseConfigToFramework(operationType, config); + + log.info("成功保存操作配置并完整同步到框架: {} -> 模型: {}", operationType, config.getModelName()); + } catch (Exception e) { + log.error("保存操作配置失败: {} - {}", operationType, e.getMessage()); + throw new RuntimeException("保存操作配置失败: " + operationType, e); + } + } + + /** + * 获取操作配置 + * 优先从数据库获取,如果不存在则从@AIOp注解获取 + * + * @param operationType 操作类型 + * @return 操作配置 + */ + public Optional getOperationConfig(String operationType) { + // 优先从数据库获取 + Optional persistedConfig = persistenceService.getOperationConfig(operationType); + if (persistedConfig.isPresent()) { + log.debug("从数据库获取操作配置: {}", operationType); + return persistedConfig; + } + + // 如果数据库配置不存在,则从注解获取 + Optional dynamicConfig = dynamicOperationConfigService.getOperationConfig(operationType); + if (dynamicConfig.isPresent()) { + log.debug("从注解获取操作配置: {}", operationType); + } + + return dynamicConfig; + } + + /** + * 获取所有操作配置 + * 优先使用数据库配置,注解配置作为补充 + * + * @return 所有操作配置 + */ + public Map getAllOperationConfigs() { + // 获取持久化配置(数据库) + Map persistedConfigs = persistenceService.getAllOperationConfigs(); + + // 获取动态配置(从注解) + Map dynamicConfigs = dynamicOperationConfigService.getAllOperationConfigs(); + + // 合并配置,数据库配置优先 + Map allConfigs = new HashMap<>(dynamicConfigs); + allConfigs.putAll(persistedConfigs); // 数据库配置覆盖注解配置 + + log.debug("获取所有操作配置: 持久化配置{}个, 动态配置{}个, 总计{}个", + persistedConfigs.size(), dynamicConfigs.size(), allConfigs.size()); + + return allConfigs; + } + + /** + * 删除操作配置 + * + * @param operationType 操作类型 + */ + public void deleteOperationConfig(String operationType) { + try { + persistenceService.deleteOperationConfig(operationType); + + // 同时从操作注册中心移除模型映射 + operationRegistry.getModelMapping().remove(operationType); + + log.info("成功删除操作配置: {}", operationType); + } catch (Exception e) { + log.error("删除操作配置失败: {} - {}", operationType, e.getMessage()); + throw new RuntimeException("删除操作配置失败: " + operationType, e); + } + } + + // ==================== 备份和恢复 ==================== + + /** + * 创建配置备份 + * + * @param backupName 备份名称 + */ + public void createBackup(String backupName) { + persistenceService.backup(backupName); + } + + /** + * 从备份恢复配置 + * + * @param backupName 备份名称 + */ + public void restoreFromBackup(String backupName) { + try { + // 恢复配置 + persistenceService.restoreFromBackup(backupName); + + // 重新同步配置 + loadPersistedConfigurations(); + + log.info("成功从备份恢复配置: {}", backupName); + } catch (Exception e) { + log.error("从备份恢复配置失败: {} - {}", backupName, e.getMessage()); + throw new RuntimeException("从备份恢复配置失败: " + backupName, e); + } + } + + /** + * 获取所有备份名称 + * + * @return 备份名称列表 + */ + public List getAllBackupNames() { + return persistenceService.getAllBackupNames(); + } + + /** + * 刷新配置到持久化存储 + */ + public void flushConfigurations() { + syncExistingConfigurations(); + log.info("配置已刷新到持久化存储"); + } + + /** + * 重新加载配置 + */ + public void reloadConfigurations() { + try { + // 重新加载配置 + loadPersistedConfigurations(); + + log.info("配置重新加载完成"); + } catch (Exception e) { + log.error("重新加载配置失败: {}", e.getMessage(), e); + throw new RuntimeException("重新加载配置失败", e); + } + } + + // ==================== 私有方法 ==================== + + /** + * 同步现有配置到持久化存储 + */ + private void syncExistingConfigurations() { + try { + // 同步现有的模型配置 + for (String modelName : modelFactory.getRegisteredModelNames()) { + OpenAIModelConfig openAIConfig = modelFactory.getModelConfig(modelName); + if (openAIConfig != null && !persistenceService.existsModelConfig(modelName)) { + ModelConfigData configData = convertFromOpenAIConfig(openAIConfig); + persistenceService.saveModelConfig(modelName, configData); + } + } + + // 初始化操作配置:从@AIOp注解获取配置并保存到数据库 + initializeOperationConfigs(); + + } catch (Exception e) { + log.warn("同步现有配置时出现警告: {}", e.getMessage()); + } + } + + /** + * 从持久化存储加载配置 + */ + private void loadPersistedConfigurations() { + try { + // 加载模型配置 + Map modelConfigs = persistenceService.getAllModelConfigs(); + for (Map.Entry entry : modelConfigs.entrySet()) { + String modelName = entry.getKey(); + ModelConfigData config = entry.getValue(); + + if (!modelFactory.isModelRegistered(modelName)) { + try { + OpenAIModelConfig openAIConfig = convertToOpenAIConfig(config); + modelFactory.registerModel(openAIConfig); + log.info("从持久化存储加载模型配置: {}", modelName); + } catch (Exception e) { + log.warn("加载模型配置失败: {} - {}", modelName, e.getMessage()); + } + } + } + + // 加载操作配置并同步模型映射到操作注册中心 + Map operationConfigs = persistenceService.getAllOperationConfigs(); + for (Map.Entry entry : operationConfigs.entrySet()) { + String operationType = entry.getKey(); + OperationConfigData config = entry.getValue(); + + // 如果操作配置中指定了模型,同步到操作注册中心 + if (config.getModelName() != null && !config.getModelName().isEmpty() && + operationRegistry.isOperationRegistered(operationType)) { + try { + operationRegistry.setModelForOperation(operationType, config.getModelName()); + log.debug("从持久化存储同步操作模型映射: {} -> {}", operationType, config.getModelName()); + } catch (Exception e) { + log.warn("同步操作模型映射失败: {} -> {} - {}", operationType, config.getModelName(), e.getMessage()); + } + } + } + + } catch (Exception e) { + log.error("加载持久化配置失败: {}", e.getMessage(), e); + } + } + + /** + * 初始化操作配置 + * 1. 从数据库获取所有操作配置 + * 2. 与框架中的操作进行对比 + * 3. 数据库没有的操作新增到数据库 + * 4. 数据库有的操作以数据库配置为准同步到框架 + */ + private void initializeOperationConfigs() { + try { + // 1. 获取数据库中所有的操作配置 + Map dbConfigs = persistenceService.getAllOperationConfigs(); + + // 2. 获取框架中所有已注册的操作 + List frameworkOperations = operationRegistry.getAllOperations(); + + // 3. 处理框架中的操作 + for (String operationType : frameworkOperations) { + if (dbConfigs.containsKey(operationType)) { + // 数据库中存在该操作配置,以数据库配置为准同步到框架 + OperationConfigData dbConfig = dbConfigs.get(operationType); + syncDatabaseConfigToFramework(operationType, dbConfig); + log.info("使用数据库配置同步到框架: {} -> 模型: {}", operationType, dbConfig.getModelName()); + } else { + // 数据库中不存在该操作配置,从注解获取并新增到数据库 + Optional annotationConfig = dynamicOperationConfigService.getOperationConfig(operationType); + if (annotationConfig.isPresent()) { + OperationConfigData config = annotationConfig.get(); + persistenceService.saveOperationConfig(operationType, config); + syncDatabaseConfigToFramework(operationType, config); + log.info("从注解新增操作配置到数据库: {} -> 模型: {}", operationType, config.getModelName()); + } else { + log.debug("操作 {} 没有@AIOp注解配置,跳过初始化", operationType); + } + } + } + + // 4. 处理数据库中存在但框架中不存在的操作(可能是已删除的操作) + for (String dbOperationType : dbConfigs.keySet()) { + if (!frameworkOperations.contains(dbOperationType)) { + log.warn("数据库中存在操作配置但框架中未注册该操作: {},建议清理数据库配置", dbOperationType); + // 可选:自动删除数据库中的无效配置 + persistenceService.deleteOperationConfig(dbOperationType); + } + } + + } catch (Exception e) { + log.error("初始化操作配置时发生错误: {}", e.getMessage(), e); + } + } + + /** + * 将数据库配置同步到框架中 + * + * @param operationType 操作类型 + * @param dbConfig 数据库配置 + */ + private void syncDatabaseConfigToFramework(String operationType, OperationConfigData dbConfig) { + try { + log.debug("同步数据库配置到框架: {} -> {}", operationType, dbConfig); + + // 1. 更新AIOperationRegistry中的操作配置 + updateOperationConfigInRegistry(operationType, dbConfig); + + // 2. 同步模型映射关系 + if (dbConfig.getModelName() != null && !dbConfig.getModelName().trim().isEmpty()) { + syncModelMapping(operationType, dbConfig.getModelName()); + } + + log.info("成功同步操作配置到框架: {}", operationType); + + } catch (Exception e) { + log.error("同步操作配置到框架失败: {} - {}", operationType, e.getMessage(), e); + throw new RuntimeException("同步配置失败: " + operationType, e); + } + } + + /** + * 更新AIOperationRegistry中的操作配置 + * + * @param operationType 操作类型 + * @param dbConfig 数据库配置 + */ + private void updateOperationConfigInRegistry(String operationType, OperationConfigData dbConfig) { + // 获取或创建操作配置 + AIOperationRegistry.OperationConfig registryConfig = + operationRegistry.getOperationConfig(operationType); + + if (registryConfig == null) { + registryConfig = new AIOperationRegistry.OperationConfig(); + } + + // 将数据库配置映射到注册中心配置 + if (dbConfig.getEnabled() != null) { + registryConfig.setEnabled(dbConfig.getEnabled()); + } + + if (dbConfig.getMaxTokens() != null && dbConfig.getMaxTokens() > 0) { + registryConfig.setMaxTokens(dbConfig.getMaxTokens()); + } + + if (dbConfig.getTemperature() != null && dbConfig.getTemperature() >= 0) { + registryConfig.setTemperature(dbConfig.getTemperature()); + } + + if (dbConfig.getJsonOutput() != null) { + registryConfig.setRequireJsonOutput(dbConfig.getJsonOutput()); + } + + if (dbConfig.getThinkingMode() != null) { + registryConfig.setSupportThinking(dbConfig.getThinkingMode()); + } + + if (dbConfig.getRetryCount() != null && dbConfig.getRetryCount() >= 0) { + registryConfig.setRetryCount(dbConfig.getRetryCount()); + } + + // 处理超时时间转换(数据库存储毫秒,注册中心使用秒) + if (dbConfig.getTimeout() != null && dbConfig.getTimeout() > 0) { + int timeoutSeconds = (int) (dbConfig.getTimeout() / 1000); + registryConfig.setTimeoutSeconds(Math.max(1, timeoutSeconds)); + } + + // 将更新后的配置设置回注册中心 + operationRegistry.getConfigs().put(operationType, registryConfig); + + log.debug("已更新操作配置到注册中心: {} -> {}", operationType, registryConfig); + } + + /** + * 同步模型映射关系 + * + * @param operationType 操作类型 + * @param modelName 模型名称 + */ + private void syncModelMapping(String operationType, String modelName) { + try { + // 验证模型是否存在 + if (!isValidModel(modelName)) { + log.warn("模型不存在,跳过映射同步: {} -> {}", operationType, modelName); + return; + } + + // 设置模型映射 + operationRegistry.setModelForOperation(operationType, modelName); + log.debug("已同步模型映射: {} -> {}", operationType, modelName); + + } catch (Exception e) { + log.warn("同步模型映射失败: {} -> {} - {}", operationType, modelName, e.getMessage()); + // 模型映射失败不应该阻止整个同步过程 + } + } + + /** + * 验证模型是否有效 + * + * @param modelName 模型名称 + * @return 是否有效 + */ + private boolean isValidModel(String modelName) { + try { + // 检查模型是否在模型注册中心中存在 + return modelRegistry.getModel(modelName) != null; + } catch (Exception e) { + log.debug("验证模型时出错: {} - {}", modelName, e.getMessage()); + return false; + } + } + + /** + * 添加操作配置更新方法,供外部调用 + * + * @param operationType 操作类型 + * @param configData 配置数据 + */ + public void updateOperationConfig(String operationType, OperationConfigData configData) { + try { + // 验证配置 + if (!configData.isValid()) { + throw new IllegalArgumentException("无效的操作配置: " + operationType); + } + + // 同步到框架 + syncDatabaseConfigToFramework(operationType, configData); + + log.info("操作配置更新成功: {}", operationType); + + } catch (Exception e) { + log.error("更新操作配置失败: {} - {}", operationType, e.getMessage(), e); + throw new RuntimeException("更新操作配置失败: " + operationType, e); + } + } + + /** + * 批量同步所有数据库配置到框架 + */ + public void syncAllDatabaseConfigsToFramework() { + try { + Map allConfigs = persistenceService.getAllOperationConfigs(); + + log.info("开始批量同步 {} 个操作配置到框架", allConfigs.size()); + + int successCount = 0; + int failCount = 0; + + for (Map.Entry entry : allConfigs.entrySet()) { + try { + syncDatabaseConfigToFramework(entry.getKey(), entry.getValue()); + successCount++; + } catch (Exception e) { + log.error("同步操作配置失败: {} - {}", entry.getKey(), e.getMessage()); + failCount++; + } + } + + log.info("批量同步完成 - 成功: {}, 失败: {}", successCount, failCount); + + } catch (Exception e) { + log.error("批量同步配置失败: {}", e.getMessage(), e); + throw new RuntimeException("批量同步配置失败", e); + } + } + + /** + * 转换为OpenAI模型配置 + */ + private OpenAIModelConfig convertToOpenAIConfig(ModelConfigData config) { + return OpenAIModelConfig.builder() + .modelName(config.getModelName()) + .baseUrl(config.getBaseUrl()) + .apiKey(config.getApiKey()) + .defaultMaxTokens(config.getDefaultMaxTokens()) + .defaultTemperature(config.getDefaultTemperature()) + .supportStream(config.getSupportStream()) + .supportJsonOutput(config.getSupportJsonOutput()) + .supportThinking(config.getSupportThinking()) + .additionalHeaders(config.getAdditionalHeaders()) + .description(config.getDescription()) + .provider(config.getProvider()) + .enabled(config.getEnabled()) + .build(); + } + + /** + * 从OpenAI模型配置转换 + */ + private ModelConfigData convertFromOpenAIConfig(OpenAIModelConfig config) { + ModelConfigData data = new ModelConfigData(); + data.setModelName(config.getModelName()); + data.setBaseUrl(config.getBaseUrl()); + data.setApiKey(config.getApiKey()); + data.setDefaultMaxTokens(config.getDefaultMaxTokens()); + data.setDefaultTemperature(config.getDefaultTemperature()); + data.setSupportStream(config.getSupportStream()); + data.setSupportJsonOutput(config.getSupportJsonOutput()); + data.setSupportThinking(config.getSupportThinking()); + data.setAdditionalHeaders(config.getAdditionalHeaders()); + data.setDescription(config.getDescription()); + data.setProvider(config.getProvider()); + data.setEnabled(config.getEnabled()); + data.updateTimestamp(); + return data; + } +} diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceService.java new file mode 100644 index 0000000..37cd0b0 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceService.java @@ -0,0 +1,127 @@ +package com.suifeng.sfchain.persistence; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * 描述: 持久化服务接口 + * 提供模型配置和操作映射的增删改查功能 + * + * @author suifeng + * 日期: 2025/1/27 + */ +public interface PersistenceService { + + // ==================== 模型配置管理 ==================== + /** + * 保存模型配置 + * + * @param modelName 模型名称 + * @param config 模型配置 + */ + void saveModelConfig(String modelName, ModelConfigData config); + + /** + * 获取模型配置 + * + * @param modelName 模型名称 + * @return 模型配置,如果不存在则返回空 + */ + Optional getModelConfig(String modelName); + + /** + * 获取所有模型配置 + * + * @return 所有模型配置的映射 + */ + Map getAllModelConfigs(); + + /** + * 删除模型配置 + * + * @param modelName 模型名称 + * @return 是否删除成功 + */ + boolean deleteModelConfig(String modelName); + + /** + * 检查模型配置是否存在 + * + * @param modelName 模型名称 + * @return 是否存在 + */ + boolean existsModelConfig(String modelName); + + /** + * 获取所有模型名称 + * + * @return 模型名称列表 + */ + List getAllModelNames(); + + // 移除整个 "操作模型映射管理" 部分 + + // ==================== 操作配置管理 ==================== + /** + * 保存操作配置 + * + * @param operationType 操作类型 + * @param config 操作配置 + */ + void saveOperationConfig(String operationType, OperationConfigData config); + + /** + * 获取操作配置 + * + * @param operationType 操作类型 + * @return 操作配置,如果不存在则返回空 + */ + Optional getOperationConfig(String operationType); + + /** + * 获取所有操作配置 + * + * @return 所有操作配置的映射 + */ + Map getAllOperationConfigs(); + + /** + * 删除操作配置 + * + * @param operationType 操作类型 + * @return 是否删除成功 + */ + boolean deleteOperationConfig(String operationType); + + // ==================== 数据同步和备份 ==================== + + /** + * 刷新数据到持久化存储 + */ + void flush(); + + /** + * 从持久化存储重新加载数据 + */ + void reload(); + + /** + * 备份当前配置 + * + * @param backupName 备份名称 + */ + void backup(String backupName); + + /** + * 从备份恢复配置 + * @param backupName 备份名称 + */ + void restoreFromBackup(String backupName); + + /** + * 获取所有备份名称 + * @return 备份名称列表 + */ + List getAllBackupNames(); +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceServiceFactory.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceServiceFactory.java new file mode 100644 index 0000000..3805e94 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PersistenceServiceFactory.java @@ -0,0 +1,33 @@ +package com.suifeng.sfchain.persistence; + +import com.suifeng.sfchain.persistence.config.DatabaseType; +import com.suifeng.sfchain.persistence.config.PersistenceConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * 持久化服务工厂 + */ +@Component +public class PersistenceServiceFactory { + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private PersistenceConfig persistenceConfig; + + public PersistenceService createPersistenceService() { + DatabaseType dbType = persistenceConfig.getDatabaseTypeEnum(); + + switch (dbType) { + case MYSQL: + return applicationContext.getBean(MySQLPersistenceService.class); + case POSTGRESQL: + return applicationContext.getBean(PostgreSQLPersistenceService.class); + default: + throw new IllegalArgumentException("Unsupported database type: " + dbType); + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PostgreSQLPersistenceService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PostgreSQLPersistenceService.java new file mode 100644 index 0000000..3fe64da --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/PostgreSQLPersistenceService.java @@ -0,0 +1,321 @@ +package com.suifeng.sfchain.persistence; +import com.suifeng.sfchain.persistence.entity.ModelConfigEntity; +import com.suifeng.sfchain.persistence.entity.OperationConfigEntity; +import com.suifeng.sfchain.persistence.repository.ModelConfigRepository; +import com.suifeng.sfchain.persistence.repository.OperationConfigRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 描述: 基于PostgreSQL的持久化服务实现 + * 替代原有的JSON文件持久化方案 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Slf4j +@Service +@RequiredArgsConstructor +@ConditionalOnProperty(name = "sf-chain.persistence.database-type", havingValue = "postgresql", matchIfMissing = true) +public class PostgreSQLPersistenceService implements PersistenceService { + + private final ModelConfigRepository modelConfigRepository; + private final OperationConfigRepository operationConfigRepository; + + @Override + public void saveModelConfig(String modelName, ModelConfigData config) { + log.debug("保存模型配置: {}", modelName); + config.setModelName(modelName); + + // 查找现有实体并更新,或创建新实体 + ModelConfigEntity entity = modelConfigRepository.findByModelName(modelName) + .map(existing -> { + // 更新现有实体,保留 id 和 createdAt + Long id = existing.getId(); + LocalDateTime createdAt = existing.getCreatedAt(); + + // 使用新的转换方法,而不是 BeanUtils.copyProperties + ModelConfigEntity updatedEntity = convertToEntity(config); + updatedEntity.setId(id); + updatedEntity.setCreatedAt(createdAt); + + return updatedEntity; + }) + .orElseGet(() -> { + // 创建新实体,使用新的转换方法 + return convertToEntity(config); + }); + + modelConfigRepository.save(entity); + log.info("模型配置已保存: {}", modelName); + } + + @Override + public Optional getModelConfig(String modelName) { + log.debug("加载模型配置: {}", modelName); + return modelConfigRepository.findByModelName(modelName) + .map(this::convertToData); + } + + @Override + public Map getAllModelConfigs() { + log.debug("加载所有模型配置"); + return modelConfigRepository.findAll().stream() + .collect(Collectors.toMap( + ModelConfigEntity::getModelName, + this::convertToData + )); + } + + @Override + public boolean deleteModelConfig(String modelName) { + log.debug("删除模型配置: {}", modelName); + Optional entity = modelConfigRepository.findByModelName(modelName); + if (entity.isPresent()) { + modelConfigRepository.deleteById(entity.get().getId()); + log.info("模型配置已删除: {}", modelName); + return true; + } else { + log.warn("模型配置不存在,无法删除: {}", modelName); + return false; + } + } + + @Override + public boolean existsModelConfig(String modelName) { + return modelConfigRepository.existsByModelName(modelName); + } + + @Override + public List getAllModelNames() { + log.debug("获取所有模型名称"); + return modelConfigRepository.findEnabledModelNames(); + } + + @Override + public void saveOperationConfig(String operationType, OperationConfigData config) { + log.debug("保存操作配置: {}", operationType); + config.setOperationType(operationType); + + // 查找现有实体并更新,或创建新实体 + OperationConfigEntity entity = operationConfigRepository.findByOperationType(operationType) + .map(existing -> { + // 更新现有实体,保留 id 和 createdAt + Long id = existing.getId(); + LocalDateTime createdAt = existing.getCreatedAt(); + + // 转换新的配置数据 + OperationConfigEntity updatedEntity = convertToEntity(config); + updatedEntity.setId(id); + updatedEntity.setCreatedAt(createdAt); + + return updatedEntity; + }) + .orElseGet(() -> { + // 创建新实体 + return convertToEntity(config); + }); + + operationConfigRepository.save(entity); + log.info("操作配置已保存: {}", operationType); + } + + @Override + public Optional getOperationConfig(String operationType) { + log.debug("加载操作配置: {}", operationType); + return operationConfigRepository.findByOperationType(operationType) + .map(this::convertToData); + } + + @Override + public Map getAllOperationConfigs() { + log.debug("加载所有操作配置"); + return operationConfigRepository.findAll().stream() + .collect(Collectors.toMap( + OperationConfigEntity::getOperationType, + this::convertToData + )); + } + + @Override + public boolean deleteOperationConfig(String operationType) { + log.debug("删除操作配置: {}", operationType); + Optional entity = operationConfigRepository.findByOperationType(operationType); + if (entity.isPresent()) { + operationConfigRepository.deleteById(entity.get().getId()); + log.info("操作配置已删除: {}", operationType); + return true; + } else { + log.warn("操作配置不存在,无法删除: {}", operationType); + return false; + } + } + + @Override + public void flush() { + // PostgreSQL自动提交事务,无需手动flush + log.debug("PostgreSQL持久化服务flush操作(无需手动操作)"); + } + + @Override + public void reload() { + // PostgreSQL数据实时同步,无需手动reload + log.debug("PostgreSQL持久化服务reload操作(数据实时同步)"); + } + + @Override + @Transactional + public void backup(String backupName) { + // PostgreSQL备份通常使用pg_dump等工具,这里记录备份请求 + log.info("PostgreSQL备份请求,备份名称: {}。请使用pg_dump等工具进行数据库备份。", backupName); + } + + @Override + @Transactional + public void restoreFromBackup(String backupName) { + // PostgreSQL恢复通常使用pg_restore等工具,这里记录恢复请求 + log.info("PostgreSQL恢复请求,备份名称: {}。请使用pg_restore等工具进行数据库恢复。", backupName); + } + + @Override + public List getAllBackupNames() { + // PostgreSQL备份文件管理通常在文件系统层面,这里返回空列表 + log.debug("PostgreSQL备份文件列表查询(需要在文件系统层面管理)"); + return List.of(); + } + + /** + * 将ModelConfigData转换为ModelConfigEntity + */ + private ModelConfigEntity convertToEntity(ModelConfigData data) { + ModelConfigEntity entity = new ModelConfigEntity(); + + // 映射基本字段 + entity.setModelName(data.getModelName()); + entity.setProvider(data.getProvider()); + entity.setApiKey(data.getApiKey()); + entity.setBaseUrl(data.getBaseUrl()); + entity.setEnabled(data.getEnabled()); + entity.setDescription(data.getDescription()); + + // 将扩展字段映射到customParams + Map customParams = new HashMap<>(); + if (data.getDefaultMaxTokens() != null) { + customParams.put("defaultMaxTokens", data.getDefaultMaxTokens()); + } + if (data.getDefaultTemperature() != null) { + customParams.put("defaultTemperature", data.getDefaultTemperature()); + } + if (data.getSupportStream() != null) { + customParams.put("supportStream", data.getSupportStream()); + } + if (data.getSupportJsonOutput() != null) { + customParams.put("supportJsonOutput", data.getSupportJsonOutput()); + } + if (data.getSupportThinking() != null) { + customParams.put("supportThinking", data.getSupportThinking()); + } + if (data.getAdditionalHeaders() != null && !data.getAdditionalHeaders().isEmpty()) { + customParams.put("additionalHeaders", data.getAdditionalHeaders()); + } + if (data.getCreatedAt() != null) { + customParams.put("createdAt", data.getCreatedAt()); + } + if (data.getUpdatedAt() != null) { + customParams.put("updatedAt", data.getUpdatedAt()); + } + + entity.setCustomParams(customParams); + return entity; + } + + /** + * 将ModelConfigEntity转换为ModelConfigData + */ + private ModelConfigData convertToData(ModelConfigEntity entity) { + ModelConfigData.ModelConfigDataBuilder builder = ModelConfigData.builder() + .modelName(entity.getModelName()) + .provider(entity.getProvider()) + .apiKey(entity.getApiKey()) + .baseUrl(entity.getBaseUrl()) + .enabled(entity.getEnabled()) + .description(entity.getDescription()); + + // 从customParams中提取扩展字段 + Map customParams = entity.getCustomParams(); + if (customParams != null) { + if (customParams.containsKey("defaultMaxTokens")) { + builder.defaultMaxTokens((Integer) customParams.get("defaultMaxTokens")); + } + if (customParams.containsKey("defaultTemperature")) { + builder.defaultTemperature((Double) customParams.get("defaultTemperature")); + } + if (customParams.containsKey("supportStream")) { + builder.supportStream((Boolean) customParams.get("supportStream")); + } + if (customParams.containsKey("supportJsonOutput")) { + builder.supportJsonOutput((Boolean) customParams.get("supportJsonOutput")); + } + if (customParams.containsKey("supportThinking")) { + builder.supportThinking((Boolean) customParams.get("supportThinking")); + } + if (customParams.containsKey("additionalHeaders")) { + @SuppressWarnings("unchecked") + Map headers = (Map) customParams.get("additionalHeaders"); + builder.additionalHeaders(headers != null ? headers : new HashMap<>()); + } + if (customParams.containsKey("createdAt")) { + builder.createdAt((Long) customParams.get("createdAt")); + } + if (customParams.containsKey("updatedAt")) { + builder.updatedAt((Long) customParams.get("updatedAt")); + } + } + + return builder.build(); + } + + /** + * 将OperationConfigData转换为OperationConfigEntity + */ + private OperationConfigEntity convertToEntity(OperationConfigData data) { + OperationConfigEntity entity = new OperationConfigEntity(); + entity.setOperationType(data.getOperationType()); + entity.setDescription(data.getDescription()); + entity.setEnabled(data.getEnabled()); + entity.setMaxTokens(data.getMaxTokens()); + entity.setTemperature(data.getTemperature()); + entity.setJsonOutput(data.getJsonOutput()); + entity.setThinkingMode(data.getThinkingMode()); + entity.setCustomParams(data.getCustomParams()); + entity.setModelName(data.getModelName()); + return entity; + } + + /** + * 将OperationConfigEntity转换为OperationConfigData + */ + private OperationConfigData convertToData(OperationConfigEntity entity) { + return OperationConfigData.builder() + .operationType(entity.getOperationType()) + .description(entity.getDescription()) + .enabled(entity.getEnabled()) + .maxTokens(entity.getMaxTokens()) + .temperature(entity.getTemperature()) + .jsonOutput(entity.getJsonOutput()) + .thinkingMode(entity.getThinkingMode()) + .customParams(entity.getCustomParams()) + .modelName(entity.getModelName()) + .build(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/DatabaseType.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/DatabaseType.java new file mode 100644 index 0000000..1d4e1f0 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/DatabaseType.java @@ -0,0 +1,45 @@ +package com.suifeng.sfchain.persistence.config; + +/** + * 数据库类型枚举 + * + * @author suifeng + * 日期: 2025/1/27 + */ +public enum DatabaseType { + + MYSQL("mysql"), + POSTGRESQL("postgresql"); + + private final String value; + + DatabaseType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + /** + * 从字符串转换为枚举 + */ + public static DatabaseType fromString(String value) { + if (value == null) { + return MYSQL; // 默认值 + } + + for (DatabaseType type : DatabaseType.values()) { + if (type.value.equalsIgnoreCase(value)) { + return type; + } + } + + throw new IllegalArgumentException("不支持的数据库类型: " + value); + } + + @Override + public String toString() { + return value; + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/PersistenceConfig.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/PersistenceConfig.java new file mode 100644 index 0000000..d3a00c3 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/config/PersistenceConfig.java @@ -0,0 +1,29 @@ +package com.suifeng.sfchain.persistence.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 持久化配置类 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Data +@Component +@ConfigurationProperties(prefix = "sf-chain.persistence") +public class PersistenceConfig { + + /** + * 数据库类型,默认为MySQL + */ + private String databaseType = "mysql"; + + /** + * 获取数据库类型枚举 + */ + public DatabaseType getDatabaseTypeEnum() { + return DatabaseType.fromString(databaseType); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatContextService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatContextService.java new file mode 100644 index 0000000..2a9f6ce --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatContextService.java @@ -0,0 +1,87 @@ +package com.suifeng.sfchain.persistence.context; + +import java.util.List; + +/** + * 描述: + * @author suifeng + * 日期: 2025/8/14 + */ +public interface ChatContextService { + + /** + * 设置或更新系统提示词 + * @param sessionId 会话ID + * @param systemPrompt 系统提示词内容 + */ + void setSystemPrompt(String sessionId, String systemPrompt); + + /** + * 获取系统提示词 + * @param sessionId 会话ID + * @return 系统提示词内容,如果不存在返回null + */ + String getSystemPrompt(String sessionId); + + /** + * 添加用户消息到会话上下文 + * @param sessionId 会话ID + * @param userMessage 用户消息内容 + */ + void addUserMessage(String sessionId, String userMessage); + + /** + * 添加AI回复到会话上下文 + * @param sessionId 会话ID + * @param aiResponse AI回复内容 + */ + void addAiResponse(String sessionId, String aiResponse); + + /** + * 获取完整的对话上下文(包含系统提示词) + * @param sessionId 会话ID + * @return 完整的消息列表,系统提示词在首位 + */ + List getFullContext(String sessionId); + + /** + * 获取对话历史(不包含系统提示词) + * @param sessionId 会话ID + * @return 用户和AI的对话历史 + */ + List getConversationHistory(String sessionId); + + /** + * 获取格式化的上下文字符串 + * @param sessionId 会话ID + * @param includeSystemPrompt 是否包含系统提示词 + * @return 格式化的上下文字符串 + */ + String getContextAsString(String sessionId, boolean includeSystemPrompt); + + /** + * 清除会话的对话历史(保留系统提示词) + * @param sessionId 会话ID + */ + void clearConversation(String sessionId); + + /** + * 完全清除会话(包括系统提示词) + * @param sessionId 会话ID + */ + void clearSession(String sessionId); + + /** + * 检查会话是否存在 + * @param sessionId 会话ID + * @return 是否存在 + */ + boolean sessionExists(String sessionId); + + /** + * 获取对话消息数量(不包含系统提示词) + * @param sessionId 会话ID + * @return 消息数量 + */ + int getConversationMessageCount(String sessionId); +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatMessage.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatMessage.java new file mode 100644 index 0000000..8cca1bb --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/ChatMessage.java @@ -0,0 +1,62 @@ +package com.suifeng.sfchain.persistence.context; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ChatMessage { + + /** + * 消息类型:SYSTEM, USER, ASSISTANT + */ + public enum MessageType { + SYSTEM, // 系统提示词 + USER, // 用户消息 + ASSISTANT // AI回复 + } + + private String id; + private MessageType type; + private String content; + private LocalDateTime timestamp; + private String sessionId; + + public static ChatMessage systemMessage(String sessionId, String content) { + return new ChatMessage( + generateId(), + MessageType.SYSTEM, + content, + LocalDateTime.now(), + sessionId + ); + } + + public static ChatMessage userMessage(String sessionId, String content) { + return new ChatMessage( + generateId(), + MessageType.USER, + content, + LocalDateTime.now(), + sessionId + ); + } + + public static ChatMessage assistantMessage(String sessionId, String content) { + return new ChatMessage( + generateId(), + MessageType.ASSISTANT, + content, + LocalDateTime.now(), + sessionId + ); + } + + private static String generateId() { + return System.currentTimeMillis() + "_" + (int)(Math.random() * 1000); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/MapBasedChatContextService.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/MapBasedChatContextService.java new file mode 100644 index 0000000..9195eeb --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/context/MapBasedChatContextService.java @@ -0,0 +1,177 @@ +package com.suifeng.sfchain.persistence.context; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 描述: + * @author suifeng + * 日期: 2025/8/14 + */ +@Slf4j +@Service +public class MapBasedChatContextService implements ChatContextService { + + // 系统提示词存储 + private final Map systemPrompts = new ConcurrentHashMap<>(); + + // 对话历史存储 + private final Map> conversationHistories = new ConcurrentHashMap<>(); + + private static final int MAX_MESSAGES_PER_SESSION = 20; + + @Override + public void setSystemPrompt(String sessionId, String systemPrompt) { + if (sessionId == null || systemPrompt == null) { + log.warn("会话ID或系统提示词为空,跳过设置"); + return; + } + + systemPrompts.put(sessionId, systemPrompt); + log.debug("设置系统提示词: sessionId={}", sessionId); + } + + @Override + public String getSystemPrompt(String sessionId) { + if (sessionId == null) { + return null; + } + return systemPrompts.get(sessionId); + } + + @Override + public void addUserMessage(String sessionId, String userMessage) { + if (sessionId == null || userMessage == null) { + log.warn("会话ID或用户消息为空,跳过添加"); + return; + } + + ChatMessage message = ChatMessage.userMessage(sessionId, userMessage); + addConversationMessage(sessionId, message); + log.debug("添加用户消息: sessionId={}", sessionId); + } + + @Override + public void addAiResponse(String sessionId, String aiResponse) { + if (sessionId == null || aiResponse == null) { + log.warn("会话ID或AI回复为空,跳过添加"); + return; + } + + ChatMessage message = ChatMessage.assistantMessage(sessionId, aiResponse); + addConversationMessage(sessionId, message); + log.debug("添加AI回复: sessionId={}", sessionId); + } + + private void addConversationMessage(String sessionId, ChatMessage message) { + conversationHistories.computeIfAbsent(sessionId, k -> new ArrayList<>()).add(message); + + List messages = conversationHistories.get(sessionId); + while (messages.size() > MAX_MESSAGES_PER_SESSION) { + messages.remove(0); + log.debug("对话历史超限,移除最旧消息: sessionId={}", sessionId); + } + } + + @Override + public List getFullContext(String sessionId) { + if (sessionId == null) { + return new ArrayList<>(); + } + + List fullContext = new ArrayList<>(); + + // 添加系统提示词 + String systemPrompt = getSystemPrompt(sessionId); + if (systemPrompt != null) { + fullContext.add(ChatMessage.systemMessage(sessionId, systemPrompt)); + } + + // 添加对话历史 + fullContext.addAll(getConversationHistory(sessionId)); + + return fullContext; + } + + @Override + public List getConversationHistory(String sessionId) { + if (sessionId == null) { + return new ArrayList<>(); + } + + return new ArrayList<>(conversationHistories.getOrDefault(sessionId, new ArrayList<>())); + } + + @Override + public String getContextAsString(String sessionId, boolean includeSystemPrompt) { + List messages; + + if (includeSystemPrompt) { + messages = getFullContext(sessionId); + } else { + messages = getConversationHistory(sessionId); + } + + if (messages.isEmpty()) { + return ""; + } + + StringBuilder context = new StringBuilder(); + for (ChatMessage message : messages) { + String role = getRoleString(message.getType()); + context.append(role).append(": ").append(message.getContent()).append("\n"); + } + + return context.toString(); + } + + @Override + public void clearConversation(String sessionId) { + if (sessionId != null) { + conversationHistories.remove(sessionId); + log.info("清除对话历史: sessionId={}", sessionId); + } + } + + @Override + public void clearSession(String sessionId) { + if (sessionId != null) { + systemPrompts.remove(sessionId); + conversationHistories.remove(sessionId); + log.info("完全清除会话: sessionId={}", sessionId); + } + } + + @Override + public boolean sessionExists(String sessionId) { + return sessionId != null && + (systemPrompts.containsKey(sessionId) || conversationHistories.containsKey(sessionId)); + } + + @Override + public int getConversationMessageCount(String sessionId) { + if (sessionId == null) { + return 0; + } + return conversationHistories.getOrDefault(sessionId, new ArrayList<>()).size(); + } + + private String getRoleString(ChatMessage.MessageType type) { + switch (type) { + case SYSTEM: + return "系统"; + case USER: + return "用户"; + case ASSISTANT: + return "助手"; + default: + return "未知"; + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/ModelConfigEntity.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/ModelConfigEntity.java new file mode 100644 index 0000000..9b8a4e0 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/ModelConfigEntity.java @@ -0,0 +1,79 @@ +package com.suifeng.sfchain.persistence.entity; + +import com.vladmihalcea.hibernate.type.json.JsonBinaryType; +import com.vladmihalcea.hibernate.type.json.JsonType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.TypeDefs; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 描述: 模型配置实体类 + * 用于存储AI模型的配置信息 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Entity +@Table(name = "sfchain_model_configs") +@TypeDefs({ + @TypeDef(name = "json", typeClass = JsonType.class), + @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) +}) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ModelConfigEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "model_name", length = 100, nullable = false, unique = true) + private String modelName; + + @Column(name = "provider", length = 50, nullable = false) + private String provider; + + @Column(name = "api_key", length = 500) + private String apiKey; + + @Column(name = "base_url", length = 500) + private String baseUrl; + + @Column(name = "enabled", nullable = false) + private Boolean enabled = true; + + @Column(name = "description", length = 1000) + private String description; + + // 使用固定的json类型,通过配置决定实际的数据库类型 + @Type(type = "json") + @Column(name = "custom_params", columnDefinition = "JSON") + private Map customParams; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + LocalDateTime now = LocalDateTime.now(); + createdAt = now; + updatedAt = now; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/OperationConfigEntity.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/OperationConfigEntity.java new file mode 100644 index 0000000..1eb83c5 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/entity/OperationConfigEntity.java @@ -0,0 +1,85 @@ +package com.suifeng.sfchain.persistence.entity; + +import com.vladmihalcea.hibernate.type.json.JsonBinaryType; +import com.vladmihalcea.hibernate.type.json.JsonType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.TypeDefs; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 描述: 操作配置实体类 + * 用于存储AI操作的配置信息 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Entity +@Table(name = "sfchain_operation_configs") +@TypeDefs({ + @TypeDef(name = "json", typeClass = JsonType.class), + @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) +}) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class OperationConfigEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "operation_type", length = 100, nullable = false, unique = true) + private String operationType; + + @Column(name = "description", length = 1000) + private String description; + + @Column(name = "enabled", nullable = false) + private Boolean enabled = true; + + @Column(name = "max_tokens") + private Integer maxTokens; + + @Column(name = "temperature") + private Double temperature; + + @Column(name = "json_output", nullable = false) + private Boolean jsonOutput = false; + + @Column(name = "thinking_mode", nullable = false) + private Boolean thinkingMode = false; + + // 使用固定的json类型,通过配置决定实际的数据库类型 + @Type(type = "json") + @Column(name = "custom_params", columnDefinition = "JSON") + private Map customParams; + + @Column(name = "model_name", length = 100) + private String modelName; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + LocalDateTime now = LocalDateTime.now(); + createdAt = now; + updatedAt = now; + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/ModelConfigRepository.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/ModelConfigRepository.java new file mode 100644 index 0000000..1e60944 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/ModelConfigRepository.java @@ -0,0 +1,68 @@ +package com.suifeng.sfchain.persistence.repository; + +import com.suifeng.sfchain.persistence.entity.ModelConfigEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 描述: 模型配置Repository接口 + * 用于模型配置的数据库操作 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Repository +public interface ModelConfigRepository extends JpaRepository { + + /** + * 根据模型名称查找配置 + * @param modelName 模型名称 + * @return 模型配置 + */ + Optional findByModelName(String modelName); + + /** + * 根据提供商查找配置 + * @param provider 提供商 + * @return 模型配置列表 + */ + List findByProvider(String provider); + + /** + * 根据启用状态查找配置 + * @param enabled 启用状态 + * @return 模型配置列表 + */ + List findByEnabled(Boolean enabled); + + /** + * 检查模型名称是否存在配置 + * @param modelName 模型名称 + * @return 是否存在 + */ + boolean existsByModelName(String modelName); + + /** + * 根据模型名称删除配置 + * @param modelName 模型名称 + */ + void deleteByModelName(String modelName); + + /** + * 获取所有提供商 + * @return 提供商列表 + */ + @Query("SELECT DISTINCT m.provider FROM ModelConfigEntity m") + List findAllProviders(); + + /** + * 获取所有启用的模型名称 + * @return 启用的模型名称列表 + */ + @Query("SELECT m.modelName FROM ModelConfigEntity m WHERE m.enabled = true") + List findEnabledModelNames(); +} \ No newline at end of file diff --git a/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/OperationConfigRepository.java b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/OperationConfigRepository.java new file mode 100644 index 0000000..0829317 --- /dev/null +++ b/prompto-lab-app/SF-Chain/src/main/java/com/suifeng/sfchain/persistence/repository/OperationConfigRepository.java @@ -0,0 +1,75 @@ +package com.suifeng.sfchain.persistence.repository; + +import com.suifeng.sfchain.persistence.entity.OperationConfigEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 描述: 操作配置Repository接口 + * 用于操作配置的数据库操作 + * + * @author suifeng + * 日期: 2025/1/27 + */ +@Repository +public interface OperationConfigRepository extends JpaRepository { + + /** + * 根据操作类型查找配置 + * @param operationType 操作类型 + * @return 操作配置 + */ + Optional findByOperationType(String operationType); + + /** + * 根据启用状态查找配置 + * @param enabled 启用状态 + * @return 操作配置列表 + */ + List findByEnabled(Boolean enabled); + + /** + * 检查操作类型是否存在配置 + * @param operationType 操作类型 + * @return 是否存在 + */ + boolean existsByOperationType(String operationType); + + /** + * 根据操作类型删除配置 + * @param operationType 操作类型 + */ + void deleteByOperationType(String operationType); + + /** + * 根据JSON输出模式查找配置 + * @param jsonOutput JSON输出模式 + * @return 操作配置列表 + */ + List findByJsonOutput(Boolean jsonOutput); + + /** + * 根据思考模式查找配置 + * @param thinkingMode 思考模式 + * @return 操作配置列表 + */ + List findByThinkingMode(Boolean thinkingMode); + + /** + * 获取所有操作类型 + * @return 操作类型列表 + */ + @Query("SELECT c.operationType FROM OperationConfigEntity c") + List findAllOperationTypes(); + + /** + * 获取所有启用的操作类型 + * @return 启用的操作类型列表 + */ + @Query("SELECT c.operationType FROM OperationConfigEntity c WHERE c.enabled = true") + List findEnabledOperationTypes(); +} \ No newline at end of file diff --git a/prompto-lab-ui/src/components/Chat/AiNodeConfig.vue b/prompto-lab-ui/src/components/Chat/AiNodeConfig.vue deleted file mode 100644 index 13c931c..0000000 --- a/prompto-lab-ui/src/components/Chat/AiNodeConfig.vue +++ /dev/null @@ -1,1711 +0,0 @@ - - - - - diff --git a/prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue b/prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue deleted file mode 100644 index df1f54c..0000000 --- a/prompto-lab-ui/src/components/SFChain/AICallLogViewer.vue +++ /dev/null @@ -1,998 +0,0 @@ - - - - - diff --git a/prompto-lab-ui/src/components/SFChain/ApiInfoConfig.vue b/prompto-lab-ui/src/components/SFChain/ApiInfoConfig.vue deleted file mode 100644 index 33b21d7..0000000 --- a/prompto-lab-ui/src/components/SFChain/ApiInfoConfig.vue +++ /dev/null @@ -1,1646 +0,0 @@ - - - - - - diff --git a/prompto-lab-ui/src/components/SFChain/JsonViewer.vue b/prompto-lab-ui/src/components/SFChain/JsonViewer.vue deleted file mode 100644 index 88cc099..0000000 --- a/prompto-lab-ui/src/components/SFChain/JsonViewer.vue +++ /dev/null @@ -1,569 +0,0 @@ - - - - - diff --git a/prompto-lab-ui/src/components/SFChain/LogDetailModal.vue b/prompto-lab-ui/src/components/SFChain/LogDetailModal.vue deleted file mode 100644 index 8af13e1..0000000 --- a/prompto-lab-ui/src/components/SFChain/LogDetailModal.vue +++ /dev/null @@ -1,1408 +0,0 @@ - - - - - diff --git a/prompto-lab-ui/src/components/SFChain/SystemManagement.vue b/prompto-lab-ui/src/components/SFChain/SystemManagement.vue deleted file mode 100644 index 5e822d8..0000000 --- a/prompto-lab-ui/src/components/SFChain/SystemManagement.vue +++ /dev/null @@ -1,286 +0,0 @@ - - - - - \ No newline at end of file diff --git a/prompto-lab-ui/src/components/layout/AppHeader.vue b/prompto-lab-ui/src/components/layout/AppHeader.vue index e7f685e..522d32a 100644 --- a/prompto-lab-ui/src/components/layout/AppHeader.vue +++ b/prompto-lab-ui/src/components/layout/AppHeader.vue @@ -143,15 +143,6 @@ onUnmounted(() => { - - - - diff --git a/prompto-lab-ui/src/router/index.ts b/prompto-lab-ui/src/router/index.ts index 439feeb..dc0d652 100644 --- a/prompto-lab-ui/src/router/index.ts +++ b/prompto-lab-ui/src/router/index.ts @@ -24,23 +24,7 @@ const router = createRouter({ meta: { title: 'AI构建器 - PromptoLab' } - }, - { - path: '/settings', - name: 'settings', - component: () => import('../views/SettingsView.vue'), - meta: { - title: '系统设置 - AI诗人' - } - }, - { - path: '/settings/api-config', - name: 'api-config', - component: () => import('../views/ApiConfigView.vue'), - meta: { - title: 'API配置 - AI诗人' - } - }, + } ] }) diff --git a/prompto-lab-ui/src/services/aiCallLogApi.ts b/prompto-lab-ui/src/services/aiCallLogApi.ts deleted file mode 100644 index d15085a..0000000 --- a/prompto-lab-ui/src/services/aiCallLogApi.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { API_CONFIG } from './apiConfig' -import { apiJsonRequest } from './apiUtils' -import type { AICallLogSummary, AICallLog, LogStatistics, ApiResponse } from '@/types/system' - -export const aiCallLogApi = { - // 获取所有日志摘要(轻量级) - async getAllLogSummaries(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/ai-logs`, { - method: 'GET', - requireAuth: true - }) - }, - - // 根据调用ID获取完整日志详情 - async getFullLog(callId: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/ai-logs/${encodeURIComponent(callId)}`, { - method: 'GET', - requireAuth: true - }) - }, - - // 根据操作类型获取日志摘要 - async getLogSummariesByOperation(operationType: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/ai-logs/operation/${encodeURIComponent(operationType)}`, { - method: 'GET', - requireAuth: true - }) - }, - - // 根据模型名称获取日志摘要 - async getLogSummariesByModel(modelName: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/ai-logs/model/${encodeURIComponent(modelName)}`, { - method: 'GET', - requireAuth: true - }) - }, - - // 获取统计信息 - async getStatistics(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/ai-logs/statistics`, { - method: 'GET', - requireAuth: true - }) - }, - - // 清空所有日志 - async clearLogs(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/ai-logs`, { - method: 'DELETE', - requireAuth: true - }) - } -} diff --git a/prompto-lab-ui/src/services/aiModelApi.ts b/prompto-lab-ui/src/services/aiModelApi.ts deleted file mode 100644 index 05e5e6b..0000000 --- a/prompto-lab-ui/src/services/aiModelApi.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { API_CONFIG } from './apiConfig' -import { apiJsonRequest } from './apiUtils' -import type { ModelConfigData, ApiResponse, ModelsResponse, TestConnectionResponse } from '@/types/system' - -export const aiModelApi = { - // 获取所有模型配置 - async getAllModels(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/models/list`, { - method: 'GET', - requireAuth: true - }) - }, - - // 获取单个模型配置 - 改为POST请求 - async getModel(modelName: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/models/get`, { - method: 'POST', - body: JSON.stringify({ modelName }), - requireAuth: true - }) - }, - - // 保存模型配置(创建或更新) - async saveModel(modelName: string, config: ModelConfigData): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/models/save`, { - method: 'POST', - body: JSON.stringify({ ...config, modelName }), - requireAuth: true - }) - }, - - // 删除模型配置 - 改为POST请求 - async deleteModel(modelName: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/models/delete`, { - method: 'POST', - body: JSON.stringify({ modelName }), - requireAuth: true - }) - }, - - // 测试模型连接 - 改为POST请求 - async testModel(modelName: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/models/test`, { - method: 'POST', - body: JSON.stringify({ modelName }), - requireAuth: true - }) - } -} diff --git a/prompto-lab-ui/src/services/aiOperationApi.ts b/prompto-lab-ui/src/services/aiOperationApi.ts deleted file mode 100644 index 0b8172b..0000000 --- a/prompto-lab-ui/src/services/aiOperationApi.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { API_CONFIG } from './apiConfig' -import { apiJsonRequest } from './apiUtils' -import type { OperationConfigData, OperationsResponse, OperationDetailResponse, ApiResponse } from '@/types/system' - -export const aiOperationApi = { - // 获取所有操作配置 - async getAllOperations(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/operations`, { - method: 'GET', - requireAuth: true - }) - }, - - // 获取单个操作配置 - 改为POST请求 - async getOperation(operationType: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/operations/get`, { - method: 'POST', - body: JSON.stringify({ operationType }), - requireAuth: true - }) - }, - - // 保存操作配置 - 修正数据结构 - async saveOperationConfig(operationType: string, config: OperationConfigData): Promise { - // 确保operationType设置在config对象中 - const configWithType = { - ...config, - operationType: operationType - } - - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/operations/save`, { - method: 'POST', - body: JSON.stringify(configWithType), - requireAuth: true - }) - }, - - // 设置单个操作模型映射 - 改为请求体参数 - async setOperationMapping(operationType: string, modelName: string): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/operations/mapping`, { - method: 'POST', - body: JSON.stringify({ operationType, modelName }), - requireAuth: true - }) - }, - - // 批量设置操作模型映射 - async setOperationMappings(mappings: Record): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/operations/mappings`, { - method: 'POST', - body: JSON.stringify({ mappings }), - requireAuth: true - }) - } -} diff --git a/prompto-lab-ui/src/services/systemApi.ts b/prompto-lab-ui/src/services/systemApi.ts deleted file mode 100644 index c5eea45..0000000 --- a/prompto-lab-ui/src/services/systemApi.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { API_CONFIG } from './apiConfig' -import { apiJsonRequest } from './apiUtils' -import type { SystemOverview, ApiResponse } from '@/types/system' - -export const systemApi = { - // 获取系统概览信息 - async getSystemOverview(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/system/overview`, { - method: 'GET', - requireAuth: true - }) - }, - - // 创建系统配置备份 - async createBackup(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/system/backup`, { - method: 'POST', - requireAuth: true - }) - }, - - // 刷新系统配置 - async refreshSystem(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/system/refresh`, { - method: 'POST', - requireAuth: true - }) - }, - - // 重置系统配置 - async resetSystem(): Promise { - return apiJsonRequest(`${API_CONFIG.BASE_URL}/sf/api/system/reset`, { - method: 'POST', - requireAuth: true - }) - } -} \ No newline at end of file diff --git a/prompto-lab-ui/src/views/ApiConfigView.vue b/prompto-lab-ui/src/views/ApiConfigView.vue deleted file mode 100644 index d835e77..0000000 --- a/prompto-lab-ui/src/views/ApiConfigView.vue +++ /dev/null @@ -1,898 +0,0 @@ - - - - - diff --git a/prompto-lab-ui/src/views/SettingsView.vue b/prompto-lab-ui/src/views/SettingsView.vue deleted file mode 100644 index 9ba1e90..0000000 --- a/prompto-lab-ui/src/views/SettingsView.vue +++ /dev/null @@ -1,731 +0,0 @@ - - - - -