diff --git a/frontend/build/plugin-bridge.ts b/frontend/build/plugin-bridge.ts index 5cd632739..58d1c1b40 100644 --- a/frontend/build/plugin-bridge.ts +++ b/frontend/build/plugin-bridge.ts @@ -10,7 +10,9 @@ export function pluginBridge(): Plugin { const dependencies = new Set() return { name: 'plugin-management-bridge', - resolveId: source => source === id ? resolvedId : undefined, + resolveId: source => source === id + ? resolvedId + : undefined, async load(source) { if (source !== resolvedId) return @@ -20,11 +22,19 @@ export function pluginBridge(): Plugin { build: { write: false, minify: true, - lib: { entry, name: 'CodexProxyPluginBridge', formats: ['iife'] }, + lib: { + entry, + name: 'CodexProxyPluginBridge', + formats: ['iife'], + }, }, }) - const outputs = (Array.isArray(result) ? result : [result]) - .flatMap(output => 'output' in output ? output.output : []) + const outputs = (Array.isArray(result) + ? result + : [result]) + .flatMap(output => 'output' in output + ? output.output + : []) const [chunk] = outputs if (outputs.length !== 1 || chunk?.type !== 'chunk' || chunk.imports.length || chunk.dynamicImports.length) throw new Error('插件管理桥必须构建为单个独立脚本') @@ -38,7 +48,9 @@ export function pluginBridge(): Plugin { handleHotUpdate({ file, server }) { if (dependencies.has(file)) { const module = server.moduleGraph.getModuleById(resolvedId) - return module ? [module] : [] + return module + ? [module] + : [] } }, } diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index 5c4feca34..ee93f89a7 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -88,15 +88,21 @@ export default antfu( }, rules: { 'template-style/single-line-simple-interpolation': 'error', - 'vue/html-closing-bracket-newline': ['error', { - singleline: 'never', - multiline: 'always', - }], - 'vue/multiline-html-element-content-newline': ['error', { - ignores: ['pre', 'textarea'], - ignoreWhenEmpty: true, - allowEmptyLines: false, - }], + 'vue/html-closing-bracket-newline': [ + 'error', + { + singleline: 'never', + multiline: 'always', + }, + ], + 'vue/multiline-html-element-content-newline': [ + 'error', + { + ignores: ['pre', 'textarea'], + ignoreWhenEmpty: true, + allowEmptyLines: false, + }, + ], 'vue/mustache-interpolation-spacing': ['error', 'always'], }, }, @@ -118,7 +124,12 @@ export default antfu( '@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error', '@intlify/vue-i18n/valid-message-syntax': 'error', '@intlify/vue-i18n/valid-plural-forms': 'error', - '@intlify/vue-i18n/no-unused-keys': ['error', { extensions: ['.ts', '.vue'] }], + '@intlify/vue-i18n/no-unused-keys': [ + 'error', + { + extensions: ['.ts', '.vue'], + }, + ], }, }, ) diff --git a/frontend/package.json b/frontend/package.json index 8ea2c4db5..9ddc437ac 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -38,7 +38,7 @@ "vue-router": "^5.4.0" }, "devDependencies": { - "@antfu/eslint-config": "^9.5.1", + "@antfu/eslint-config": "^10.0.0", "@intlify/eslint-plugin-vue-i18n": "4.5.1", "@intlify/unplugin-vue-i18n": "11.2.7", "@tailwindcss/vite": "^4.3.3", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 7f66b99bc..b8c772881 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -253,8 +253,8 @@ importers: version: 5.4.0(@vue/compiler-sfc@3.5.44)(pinia@4.1.0(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.44(typescript@6.0.3)))(rolldown@1.2.8)(vite@8.3.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.44(typescript@6.0.3)) devDependencies: '@antfu/eslint-config': - specifier: ^9.5.1 - version: 9.5.1(@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3))(@typescript-eslint/utils@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.44)(eslint-plugin-format@2.0.1(eslint@10.13.0(jiti@2.7.0)))(eslint-plugin-vuejs-accessibility@2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.12.0))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + specifier: ^10.0.0 + version: 10.0.0(@typescript-eslint/typescript-estree@8.72.0(typescript@6.0.3))(@typescript-eslint/utils@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.44)(eslint-plugin-format@2.0.1(eslint@10.13.0(jiti@2.7.0)))(eslint-plugin-vuejs-accessibility@2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.13.0))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) '@intlify/eslint-plugin-vue-i18n': specifier: 4.5.1 version: 4.5.1(eslint@10.13.0(jiti@2.7.0))(jsonc-eslint-parser@3.3.0)(vue-eslint-parser@10.4.1(eslint@10.13.0(jiti@2.7.0)))(yaml-eslint-parser@2.1.0) @@ -290,7 +290,7 @@ importers: version: 2.0.1(eslint@10.13.0(jiti@2.7.0)) eslint-plugin-vuejs-accessibility: specifier: ^2.6.3 - version: 2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.12.0) + version: 2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.13.0) tailwindcss: specifier: ^4.3.3 version: 4.3.3 @@ -313,8 +313,8 @@ packages: resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} engines: {node: '>=8.x'} - '@antfu/eslint-config@9.5.1': - resolution: {integrity: sha512-BoQGe5lY9spYmGqtUOC6hWohxob4SNn+7o5nHsOXoLn+9vJqNGqC/2/vRoXbcOa3dT4Rx/ZV/Qkr6uf72KTbxQ==} + '@antfu/eslint-config@10.0.0': + resolution: {integrity: sha512-SwFOW3AHjzUAhWn56bycanHEeSV2LLkVvMLY14hoFNi5zYqfwf92mWj0wX/8p24iR8IwvRmsNwxMTXQZMcOsHQ==} hasBin: true peerDependencies: '@angular-eslint/eslint-plugin': ^21.1.0 @@ -383,8 +383,8 @@ packages: svelte-eslint-parser: optional: true - '@antfu/install-pkg@2.0.1': - resolution: {integrity: sha512-iCKVQcIC0e3oDxEfs3SHQGW+ovhBMZmS1TE+bTk50rVyMCBmCfClv7Qi3HQKlumYwvjb/iIMeWCW2i67q6kFfQ==} + '@antfu/install-pkg@2.1.0': + resolution: {integrity: sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==} '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} @@ -435,12 +435,12 @@ packages: '@cacheable/utils@2.5.0': resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} - '@clack/core@1.4.3': - resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} engines: {node: '>= 20.12.0'} - '@clack/prompts@1.7.0': - resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} engines: {node: '>= 20.12.0'} '@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/0692ca034be88de6faf88bc76f1a6e9e7ad65225': @@ -463,8 +463,8 @@ packages: '@dprint/toml@0.7.0': resolution: {integrity: sha512-eFaQTcfxKHB+YyTh83x7GEv+gDPuj9q5NFOTaoj5rZmQTbj6OgjjMxUicmS1R8zYcx8YAq5oA9J3YFa5U6x2gA==} - '@e18e/eslint-plugin@0.8.0': - resolution: {integrity: sha512-js/TeM+XJyoJ2Zk4if5uTEchv3MEbqugc9gLgq8YdB6Dy+LvwGACpQiAwcul0r4LUl0TvhuzAKUeIUPtGp2cew==} + '@e18e/eslint-plugin@0.8.1': + resolution: {integrity: sha512-OV3T7l0r52/IglskdAuoO+BJyzsCs7AcIHpwpBMlIMO0+3RHskM2h5MuHdLuesIhSRhwCURGMKtp/XwnfXrfxg==} peerDependencies: eslint: ^9.0.0 || ^10.0.0 oxlint: ^1.72.0 @@ -474,12 +474,25 @@ packages: oxlint: optional: true + '@es-joy/escodegen@4.2.0': + resolution: {integrity: sha512-jWqqS3StW6vGwJeT8yu6alS4nipOBi3kD/pR63+9hG2NfGafCJ4viDME15mVf+fyupyptcUJDXfv63gFF5Gzfg==} + engines: {node: '>=6.0'} + hasBin: true + + '@es-joy/estraverse@7.1.1': + resolution: {integrity: sha512-h2d737iS0LFAqypZOmXizu54qAbHofI/RpD30Zom03O6BCXKHuFevWKPcGarZbGq82h7qSBb5G6kLdhTMO+caw==} + engines: {node: '>=4.0'} + + '@es-joy/jsdoccomment@0.62.0': + resolution: {integrity: sha512-yWi6sm7INEwnfS7IJvE0dU+RTrwzLPFcY7e7eGpu/l5Q9lWfQ2ROwZ0qVnc242jw2TUPsfHX3XMIISkGBv57RQ==} + engines: {node: '>=20.11.0'} + '@es-joy/jsdoccomment@0.92.0': resolution: {integrity: sha512-WivZqV5jPTYDQJgIMp0hTsyQESKYNY6yCJr4b0l84tYtQntkAMkc6zARh2vlRQyeg4aYHPUkCUDpvr2IWAiGpQ==} engines: {node: ^22.22.2 || >=24.15.0} - '@es-joy/jsdoccomment@0.97.0': - resolution: {integrity: sha512-EP8uoFfh6+GsdGCduYtmWAW0h7AO+Ayik9Vh5YbA2r/3N6lmJKkCNZX+q3QBXC1K6ixjQ/9igF2b7WVvLm063g==} + '@es-joy/jsdoccomment@1.0.0': + resolution: {integrity: sha512-jiVfUnvMioo3upHpAoxE4XkqfrL6nc/XQx7Uw7tQL6QbQDJcALm+fhdj6tjiORDF7uADJbTlmnYWR9DvJE/eiw==} engines: {node: ^22.22.2 || >=24.15.0} '@es-joy/resolve.exports@1.2.0': @@ -642,8 +655,8 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-plugin-eslint-comments@4.7.2': - resolution: {integrity: sha512-LF03qURSwEWm2dz5wtdDCzNk+7Opl0X7q6I3undsaIuNsEiNvRV3BCtqu14Q/6Pzg1tBj44LcxpW2EpSLZStZw==} + '@eslint-community/eslint-plugin-eslint-comments@4.8.1': + resolution: {integrity: sha512-vafrdi15JHBFh1qWOAucyJwlEEuhdoMof28jOdKdgnwZ7bXwd6v4wzHYSmAVLwnx0yc7bqAtfJ0EZQV1WAzV/Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 @@ -683,8 +696,8 @@ packages: resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/css-tree@4.1.0': - resolution: {integrity: sha512-cg0ohyrAG3swyGqt8t1K/OK97DqBw/ftDvlvyY1fmEst5B40UOmsimwLENq74z2dyw5CDM+3zJIW+CV2nFNDdA==} + '@eslint/css-tree@4.1.2': + resolution: {integrity: sha512-rVcxLmwSkEL3aWS3vAgsTRrQHoYE1b0XcgDtKHDJ92XOkyUpcbS/g0tUJKhvCtlEhYzgKBWsgDd+Z/aPhd166w==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/eslintrc@3.3.7': @@ -1329,16 +1342,16 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} - '@typescript-eslint/eslint-plugin@8.69.0': - resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} + '@typescript-eslint/eslint-plugin@8.72.0': + resolution: {integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.69.0 + '@typescript-eslint/parser': ^8.72.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.69.0': - resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} + '@typescript-eslint/parser@8.72.0': + resolution: {integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1350,18 +1363,34 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/project-service@8.72.0': + resolution: {integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + '@typescript-eslint/scope-manager@8.69.0': resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.72.0': + resolution: {integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.69.0': resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.69.0': - resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} + '@typescript-eslint/tsconfig-utils@8.72.0': + resolution: {integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.72.0': + resolution: {integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1371,14 +1400,24 @@ packages: resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.72.0': + resolution: {integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.69.0': resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.69.0': - resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} + '@typescript-eslint/typescript-estree@8.72.0': + resolution: {integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.72.0': + resolution: {integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1388,6 +1427,10 @@ packages: resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.72.0': + resolution: {integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-vue@6.0.10': resolution: {integrity: sha512-b80aUQh3n+qEzzZm5oQp3rT2QImeCx58K6eFl/RkJ3d65KW0kTuv5lNsLRVAjbW9YRUpo/wzmvzMgV34bTAItA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1395,13 +1438,13 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 || ^3.6.0-rc.10 - '@vitest/eslint-plugin@1.6.27': - resolution: {integrity: sha512-X1RCAfwbatG4GFbJ/1PIHP9MSZMt0JJThqbYID+vS4L783k6QGlLPe421wQE8dHXuGCcenVLsydiM4qzsYWxhg==} - engines: {node: '>=18'} + '@vitest/eslint-plugin@1.6.28': + resolution: {integrity: sha512-2QorUqhTIR7gaDkANEBTjuG2SE5MJgFaWElINyq9bIaOEJXycZUliu5xYSzG6QRLunWopYshgcRs2vW2CTRqGA==} + engines: {node: '>=22'} peerDependencies: '@typescript-eslint/eslint-plugin': '*' eslint: '>=8.57.0' - typescript: '>=5.0.0' + typescript: '>=6.0.0' vitest: '*' peerDependenciesMeta: '@typescript-eslint/eslint-plugin': @@ -1582,8 +1625,8 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@4.3.1: - resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + ansis@4.4.0: + resolution: {integrity: sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==} engines: {node: '>=14'} are-docs-informative@0.1.1: @@ -1593,6 +1636,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argparse@3.0.4: + resolution: {integrity: sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -1627,6 +1673,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + baseline-browser-mapping@2.11.32: + resolution: {integrity: sha512-UvuxFU9P3hM29LrQdwu0cD6w6/+SFr+UlB9s/knI7+O2rK9xFxKUwSDWxGdbbqQ9/V7BJ5s0NI5nQAE4uJp3gQ==} + engines: {node: '>=6.0.0'} + hasBin: true + birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} @@ -1648,12 +1699,17 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.29.4: + resolution: {integrity: sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + builtin-modules@5.3.0: resolution: {integrity: sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==} engines: {node: '>=18.20'} - cac@7.0.0: - resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + cac@7.1.0: + resolution: {integrity: sha512-LMDU/U2yA4IPIY8KT0NsNPWEYZvAMkbgyspxJ6btJaYV+WMDOHAZCmB0lsTObiHaRS1V/ztKAl7+TSkTqXyeoA==} engines: {node: '>=20.19.0'} cacheable@2.5.0: @@ -1670,6 +1726,9 @@ packages: caniuse-lite@1.0.30001810: resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + caniuse-lite@1.0.30001819: + resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1705,6 +1764,10 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + comment-parser@1.4.7: resolution: {integrity: sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==} engines: {node: '>= 12.0.0'} @@ -1713,6 +1776,10 @@ packages: resolution: {integrity: sha512-rKZTGo4fzKYna8UcL0isTg5wkBNla7bxTypLwZQXjIdi++IdP1OJ41rI5Mti3/jltkPujbu4i9LIARYA+zpotQ==} engines: {node: '>= 12.0.0'} + comment-parser@1.4.9: + resolution: {integrity: sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==} + engines: {node: '>= 12.0.0'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1722,10 +1789,6 @@ packages: confbox@0.3.1: resolution: {integrity: sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==} - convert-hrtime@5.0.0: - resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==} - engines: {node: '>=12'} - core-js-compat@3.50.0: resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} engines: {node: '>=6.4.0'} @@ -1806,6 +1869,9 @@ packages: electron-to-chromium@1.5.422: resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} + electron-to-chromium@1.5.464: + resolution: {integrity: sha512-nRa65aFge40viPAKxMKULIY8tcXKwKboJWNn4RoKwyWXJ6SP7BnrDGKUTzc64818jIciH0PK7LSF8QzZes19GA==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1832,6 +1898,10 @@ packages: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} + entities@8.1.0: + resolution: {integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==} + engines: {node: '>=20.19.0'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1912,8 +1982,8 @@ packages: eslint-parser-plain@0.1.1: resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==} - eslint-plugin-antfu@3.2.3: - resolution: {integrity: sha512-U2fnz/H0gFPxpuC7QpaHa0Jv2AgCZ5hunp36SOP/yWo8yFzgvMh8X4pZ4uN4IKoqtBhk7G3HuVa93Urf51+sZg==} + eslint-plugin-antfu@3.3.1: + resolution: {integrity: sha512-SP1Wmq8EPgXOMWE3SNGS6T7sCcKYOId5vLU8hUO0VFMJ7PwaMYEqFNdp0jlL6vnsT1h1LCKuRmoed1ktRjWHjA==} peerDependencies: eslint: '*' @@ -1942,11 +2012,15 @@ packages: peerDependencies: eslint: ^9.0.0 || ^10.0.0 - eslint-plugin-jsdoc@64.3.6: - resolution: {integrity: sha512-lo7IXmgUUNy88SxW7KnJmmD2iPQBIRMomfCFPHidW1M3zNNVuzxlB2uoNrNyE1g4v2/L+RtYmiROPwQhSNzL8Q==} + eslint-plugin-jsdoc@65.4.2: + resolution: {integrity: sha512-+1XHeMXZxHK0+uiHwKuZzwCIlZjw9SuZ/enuLMtZ8ZT7cxBLTZ4lCHd3kO6BhPcsUQ59fMg6DKWcCNxnRAhMjg==} engines: {node: ^22.22.2 || >=24.15.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true eslint-plugin-jsonc@3.4.2: resolution: {integrity: sha512-q5xzjCYFQuhFomTbctXzd3STfDJ8XduvaigjQmMEbP2gzSKrc58y3Fv6D775/cIK1/sRUn+4kpljiU2iW8k0zw==} @@ -1954,8 +2028,8 @@ packages: peerDependencies: eslint: '>=9.38.0' - eslint-plugin-n@18.3.0: - resolution: {integrity: sha512-cPVguuDe6DrIPb/qUXHf8P89MaVTUmiYWwpt5gX5AILsvRIiZAxMFXcFR6QHYBksqKJpjfUBlL/RleCJUWcD7w==} + eslint-plugin-n@18.5.0: + resolution: {integrity: sha512-M7uF6MTN/An+AXqvyIbXq7npJIZOkd5eKo8iMMvWrcDVNyZVdA6TF2lw01aKQfCEidRfpnR78GhAjZorlXNHiw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: '>=8.57.1' @@ -1971,19 +2045,22 @@ packages: resolution: {integrity: sha512-4S3/9Nb7A2tiMcpzEQE9bQSlpeOz6WJkgryBuou/SA8W2x2c8Zf4j0NvTKBjv6qNhF9T79tmkecm/0CHqV0UGg==} engines: {node: '>=5.0.0'} - eslint-plugin-perfectionist@5.11.0: - resolution: {integrity: sha512-kZV1otBcu4xT5R1p+0x1N1wRv4pS+OIbxrA47n5a1fTnN3MWbexOx5eQgbQhGyCNbpvF/rqrbnpkGjumHF+Y0Q==} + eslint-plugin-perfectionist@5.13.0: + resolution: {integrity: sha512-kPlqE2Kdh0njsad9S9xL/xnq+NNVUsga/uS3yf6axE0RddACM3Q+idJyethC7R+C9b73+NRhAWvKyXPrkl0nFA==} engines: {node: ^20.0.0 || >=22.0.0} peerDependencies: eslint: ^8.45.0 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true eslint-plugin-pnpm@1.9.1: resolution: {integrity: sha512-8++1Egww2cqc9Wylmt7P+xXNMQVCosAZ4m4W4Gc0V5lGea8HFByHTRvUI+ZvvpO8fLXGg2O4MQc90EhuI3mMdQ==} peerDependencies: eslint: ^9.0.0 || ^10.0.0 - eslint-plugin-regexp@3.3.0: - resolution: {integrity: sha512-TT0JTQbW7CRKohntpGAsMdQ1K3MsmJK4gdAhKJtIwlJ0JvuYxauymFgJdvqxIY4lqNj0EuRZWVrymQtEdJYEFg==} + eslint-plugin-regexp@3.3.1: + resolution: {integrity: sha512-58bGUci/AaWAo1ETj6Lsnmy6QXwnSyVONRA805828nnqp7vrTwdaQMc02vtowmYB3A5OB3SLr3r5HgmfxX4dLg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: '>=9.38.0' @@ -1994,11 +2071,15 @@ packages: peerDependencies: eslint: '>=9.38.0' - eslint-plugin-unicorn@74.0.0: - resolution: {integrity: sha512-AGnsGi2SxHg1HEAXxn9nSnZfyjvTWkxm8E8hpd/9tD6dLjBUdcD7+D6ZN64HmmCXTSXlrwVyUqe20Uyb2CaurA==} + eslint-plugin-unicorn@78.0.0: + resolution: {integrity: sha512-XTDqxcJeAmh2lhgU75TpuMDdFtPDnAU+DCW56nR5UzXTG+uOyvOGjV80O11my7kNb2bYSCPlWa8c4K8/rwlYog==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true eslint-plugin-unused-imports@4.4.1: resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} @@ -2009,8 +2090,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vue@10.10.0: - resolution: {integrity: sha512-dL9x9rBHqqNcByWiLOHK6L0SB97V82/NC0cZRn9cXPjM7pCuWlpQQP9bFH4vjBv80ej1ZpzAkuD8zWH1o9bZbA==} + eslint-plugin-vue@10.11.1: + resolution: {integrity: sha512-pasVXhl7wUmIvhujoqs/m0mJ9r59QW1IR9XXYKGR0FETzSM+Hhlf4qKIYgWiDDP+9YKv4YfbwskD7LqIdVcgQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 @@ -2030,8 +2111,8 @@ packages: eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 globals: '>= 13.12.1' - eslint-plugin-yml@3.8.1: - resolution: {integrity: sha512-E/70psRwxz5EJ8dBtzrFfqSiiInuSytbdtFCjueb/GcKjsWzPBfxfhtQePImMAPjHwMA/VDurO7DJwStDJ4wWg==} + eslint-plugin-yml@3.9.0: + resolution: {integrity: sha512-o4ByMVFd/mGc0Q/vJmbG9pz7Kyg/BiKAGnWCzvMLqzpa2KMdpUrLgF5a6yYeL2mf1Br/9dckd0ADl3vw7uF+jA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} peerDependencies: eslint: '>=9.38.0' @@ -2080,6 +2161,11 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.7.0: resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} @@ -2184,10 +2270,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function-timeout@1.0.2: - resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} - engines: {node: '>=18'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2223,6 +2305,10 @@ packages: resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==} engines: {node: '>=18'} + globals@17.13.0: + resolution: {integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==} + engines: {node: '>=18'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -2312,10 +2398,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-identifier@1.1.0: - resolution: {integrity: sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==} - engines: {node: '>=18'} - is-language-code@3.1.0: resolution: {integrity: sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==} @@ -2333,6 +2415,14 @@ packages: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true + js-yaml@5.5.0: + resolution: {integrity: sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==} + hasBin: true + + jsdoc-type-pratt-parser@5.9.2: + resolution: {integrity: sha512-TYzkACp/wPvDJLRY7qpHXtrhgwoAaojIOnLaaVNi+AbPU2u1kkjfKd9hXXTq0qSAGsyYXvwUXt99h9I5iCmjjw==} + engines: {node: '>=12.0.0'} + jsdoc-type-pratt-parser@9.0.1: resolution: {integrity: sha512-1LJ/negRKJxH0+ulOsJYEpUgzIs1DAOz9QctuS4In2LW/Ro3l8C4ej5al+5FU2hBGEOAY3R3SCnP6x8226JMIw==} engines: {node: ^22.22.2 || >=24.15.0} @@ -2341,6 +2431,10 @@ packages: resolution: {integrity: sha512-V4Ww4EHnTcTLSOMoB0FsF72JhQvcAsriCm/LWnxJeGWoxIjEL2l9na11abQok5SYShq8m0Gl02el/xAbTCulvQ==} engines: {node: ^22.22.2 || >=24.15.0} + jsdoc-type-pratt-parser@9.4.0: + resolution: {integrity: sha512-orXhi2Y06rag47r4SW4JPII6WlUJbp0xe9cIcqLaSbb/8tREcEZSq99Lu3AFfra6PAlMqTYIxV+ZHxl1dFUrWw==} + engines: {node: ^22.22.2 || >=24.15.0} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -2548,10 +2642,6 @@ packages: magic-string@1.4.3: resolution: {integrity: sha512-z12OxmaPGE0F4xlpGdjuAUckipLpQlTAuAmyGhNoD7ODpYUzvDfvtxUbjM/jwznyP178oju/KDdyi220wNJ0RQ==} - make-asynchronous@1.1.0: - resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} - engines: {node: '>=18'} - markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2606,8 +2696,8 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} - mdn-data@2.34.0: - resolution: {integrity: sha512-OgIlLv0NxJKVW4GTSAoEgpRGd4F2XCqGinK0MsMlBCCS/Zcm2/LsbercNWNA7PeMMcjl75NnI97eqyo7zkdxWA==} + mdn-data@2.37.2: + resolution: {integrity: sha512-rz9YOBGItdSbe7HcSArUbhiAv4SB/4DKRYg96CSFS2CcIYMbUvBtZlzA1NCbk2AdgbmYwMqwNblLpR441lLeiQ==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2672,6 +2762,9 @@ packages: micromark-util-decode-string@2.0.1: resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + micromark-util-edit-map@1.0.0: + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} + micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} @@ -2699,6 +2792,9 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromark@4.0.3: + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -2750,6 +2846,10 @@ packages: resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} engines: {node: '>=18'} + node-releases@2.0.60: + resolution: {integrity: sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==} + engines: {node: '>=18'} + nostics@1.2.0: resolution: {integrity: sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==} @@ -2774,10 +2874,6 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - p-event@6.0.1: - resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} - engines: {node: '>=16.17'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -2786,10 +2882,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-timeout@6.1.4: - resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} - engines: {node: '>=14.16'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -2813,6 +2905,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -2930,8 +3025,8 @@ packages: resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - regjsparser@0.13.2: - resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + regjsparser@0.13.4: + resolution: {integrity: sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==} hasBin: true reserved-identifiers@1.2.0: @@ -2989,6 +3084,10 @@ packages: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + spdx-exceptions@2.5.0: resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} @@ -3022,10 +3121,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - super-regex@1.1.0: - resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} - engines: {node: '>=18'} - synckit@0.11.13: resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3037,10 +3132,6 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - time-span@5.1.0: - resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} - engines: {node: '>=12'} - tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} @@ -3074,10 +3165,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -3154,6 +3241,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.3.4: + resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3164,6 +3257,10 @@ packages: resolution: {integrity: sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==} engines: {node: '>=18.12.0'} + verkit@0.4.1: + resolution: {integrity: sha512-mMVzj0TXExtVdlDEq+Mzp0eyOuyznNpFobNM3uAqe3HVm/Ps2c+u17BligMkZjZCA6EiXpoon8iRTer3iu40SQ==} + engines: {node: '>=18.12.0'} + vite@8.3.5: resolution: {integrity: sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3254,9 +3351,6 @@ packages: typescript: optional: true - web-worker@1.5.0: - resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} - webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} @@ -3294,6 +3388,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3312,40 +3411,40 @@ snapshots: '@ant-design/fast-color@3.0.1': {} - '@antfu/eslint-config@9.5.1(@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3))(@typescript-eslint/utils@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.44)(eslint-plugin-format@2.0.1(eslint@10.13.0(jiti@2.7.0)))(eslint-plugin-vuejs-accessibility@2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.12.0))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + '@antfu/eslint-config@10.0.0(@typescript-eslint/typescript-estree@8.72.0(typescript@6.0.3))(@typescript-eslint/utils@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.44)(eslint-plugin-format@2.0.1(eslint@10.13.0(jiti@2.7.0)))(eslint-plugin-vuejs-accessibility@2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.13.0))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@antfu/install-pkg': 2.0.1 - '@clack/prompts': 1.7.0 - '@e18e/eslint-plugin': 0.8.0(eslint@10.13.0(jiti@2.7.0)) - '@eslint-community/eslint-plugin-eslint-comments': 4.7.2(eslint@10.13.0(jiti@2.7.0)) + '@antfu/install-pkg': 2.1.0 + '@clack/prompts': 1.8.1 + '@e18e/eslint-plugin': 0.8.1(eslint@10.13.0(jiti@2.7.0)) + '@eslint-community/eslint-plugin-eslint-comments': 4.8.1(eslint@10.13.0(jiti@2.7.0)) '@eslint/markdown': 8.0.3 '@stylistic/eslint-plugin': 5.10.0(eslint@10.13.0(jiti@2.7.0)) - '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/parser': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - '@vitest/eslint-plugin': 1.6.27(@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - ansis: 4.3.1 - cac: 7.0.0 + '@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@vitest/eslint-plugin': 1.6.28(@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + ansis: 4.4.0 + cac: 7.1.0 eslint: 10.13.0(jiti@2.7.0) eslint-config-flat-gitignore: 2.4.0(eslint@10.13.0(jiti@2.7.0)) eslint-flat-config-utils: 3.2.0 eslint-merge-processors: 2.0.0(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-antfu: 3.2.3(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-command: 4.0.0(@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3))(@typescript-eslint/utils@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)) + eslint-plugin-antfu: 3.3.1(eslint@10.13.0(jiti@2.7.0)) + eslint-plugin-command: 4.0.0(@typescript-eslint/typescript-estree@8.72.0(typescript@6.0.3))(@typescript-eslint/utils@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)) eslint-plugin-import-lite: 0.6.0(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-jsdoc: 64.3.6(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-jsdoc: 65.4.2(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) eslint-plugin-jsonc: 3.4.2(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-n: 18.3.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-n: 18.5.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) eslint-plugin-no-only-tests: 3.4.0 - eslint-plugin-perfectionist: 5.11.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-perfectionist: 5.13.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) eslint-plugin-pnpm: 1.9.1(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-regexp: 3.3.0(eslint@10.13.0(jiti@2.7.0)) + eslint-plugin-regexp: 3.3.1(eslint@10.13.0(jiti@2.7.0)) eslint-plugin-toml: 1.5.0(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-unicorn: 74.0.0(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-vue: 10.10.0(@stylistic/eslint-plugin@5.10.0(eslint@10.13.0(jiti@2.7.0)))(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.13.0(jiti@2.7.0))) - eslint-plugin-yml: 3.8.1(eslint@10.13.0(jiti@2.7.0)) + eslint-plugin-unicorn: 78.0.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-unused-imports: 4.4.1(@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)) + eslint-plugin-vue: 10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@10.13.0(jiti@2.7.0)))(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.13.0(jiti@2.7.0))) + eslint-plugin-yml: 3.9.0(eslint@10.13.0(jiti@2.7.0)) eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.44)(eslint@10.13.0(jiti@2.7.0)) - globals: 17.12.0 + globals: 17.13.0 local-pkg: 1.2.1 parse-gitignore: 2.0.0 toml-eslint-parser: 1.0.3 @@ -3353,7 +3452,7 @@ snapshots: yaml-eslint-parser: 2.1.0 optionalDependencies: eslint-plugin-format: 2.0.1(eslint@10.13.0(jiti@2.7.0)) - eslint-plugin-vuejs-accessibility: 2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.12.0) + eslint-plugin-vuejs-accessibility: 2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.13.0) transitivePeerDependencies: - '@eslint/json' - '@typescript-eslint/typescript-estree' @@ -3365,7 +3464,7 @@ snapshots: - typescript - vitest - '@antfu/install-pkg@2.0.1': + '@antfu/install-pkg@2.1.0': dependencies: package-manager-detector: 1.8.0 tinyexec: 1.3.1 @@ -3414,14 +3513,14 @@ snapshots: hashery: 1.5.1 keyv: 5.6.0 - '@clack/core@1.4.3': + '@clack/core@1.5.1': dependencies: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@clack/prompts@1.7.0': + '@clack/prompts@1.8.1': dependencies: - '@clack/core': 1.4.3 + '@clack/core': 1.5.1 fast-string-width: 3.0.2 fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 @@ -3462,14 +3561,34 @@ snapshots: '@dprint/toml@0.7.0': {} - '@e18e/eslint-plugin@0.8.0(eslint@10.13.0(jiti@2.7.0))': + '@e18e/eslint-plugin@0.8.1(eslint@10.13.0(jiti@2.7.0))': dependencies: empathic: 2.0.1 module-replacements: 3.3.0 - semver: 7.8.5 + verkit: 0.4.1 optionalDependencies: eslint: 10.13.0(jiti@2.7.0) + '@es-joy/escodegen@4.2.0': + dependencies: + '@es-joy/estraverse': 7.1.1 + '@es-joy/jsdoccomment': 0.62.0 + esprima: 4.0.1 + esutils: 2.0.3 + optionator: 0.9.4 + optionalDependencies: + source-map: 0.7.6 + + '@es-joy/estraverse@7.1.1': {} + + '@es-joy/jsdoccomment@0.62.0': + dependencies: + '@types/estree': 1.0.9 + '@typescript-eslint/types': 8.69.0 + comment-parser: 1.4.1 + esquery: 1.7.0 + jsdoc-type-pratt-parser: 5.9.2 + '@es-joy/jsdoccomment@0.92.0': dependencies: '@types/estree': 1.0.9 @@ -3478,13 +3597,13 @@ snapshots: esquery: 1.7.0 jsdoc-type-pratt-parser: 9.0.1 - '@es-joy/jsdoccomment@0.97.0': + '@es-joy/jsdoccomment@1.0.0': dependencies: '@types/estree': 1.0.9 - '@typescript-eslint/types': 8.69.0 - comment-parser: 1.4.8 + '@typescript-eslint/types': 8.72.0 + comment-parser: 1.4.9 esquery: 1.7.0 - jsdoc-type-pratt-parser: 9.2.1 + jsdoc-type-pratt-parser: 9.4.0 '@es-joy/resolve.exports@1.2.0': {} @@ -3566,7 +3685,7 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.13.0(jiti@2.7.0))': + '@eslint-community/eslint-plugin-eslint-comments@4.8.1(eslint@10.13.0(jiti@2.7.0))': dependencies: escape-string-regexp: 4.0.0 eslint: 10.13.0(jiti@2.7.0) @@ -3605,9 +3724,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/css-tree@4.1.0': + '@eslint/css-tree@4.1.2': dependencies: - mdn-data: 2.34.0 + mdn-data: 2.37.2 source-map-js: 1.2.2 '@eslint/eslintrc@3.3.7': @@ -4084,14 +4203,14 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/type-utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.69.0 + '@typescript-eslint/parser': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/type-utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.72.0 eslint: 10.13.0(jiti@2.7.0) ignore: 7.0.9 natural-compare: 1.4.0 @@ -4100,12 +4219,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.69.0 + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.72.0 debug: 4.4.3 eslint: 10.13.0(jiti@2.7.0) typescript: 6.0.3 @@ -4121,20 +4240,38 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.72.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.72.0(typescript@6.0.3) + '@typescript-eslint/types': 8.72.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.69.0': dependencies: '@typescript-eslint/types': 8.69.0 '@typescript-eslint/visitor-keys': 8.69.0 + '@typescript-eslint/scope-manager@8.72.0': + dependencies: + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/visitor-keys': 8.72.0 + '@typescript-eslint/tsconfig-utils@8.69.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.72.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 eslint: 10.13.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) @@ -4144,6 +4281,8 @@ snapshots: '@typescript-eslint/types@8.69.0': {} + '@typescript-eslint/types@8.72.0': {} + '@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3)': dependencies: '@typescript-eslint/project-service': 8.69.0(typescript@6.0.3) @@ -4159,12 +4298,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.72.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.72.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.72.0(typescript@6.0.3) + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/visitor-keys': 8.72.0 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(jiti@2.7.0)) - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/types': 8.69.0 - '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(typescript@6.0.3) eslint: 10.13.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: @@ -4175,19 +4329,24 @@ snapshots: '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.72.0': + dependencies: + '@typescript-eslint/types': 8.72.0 + eslint-visitor-keys: 5.0.1 + '@vitejs/plugin-vue@6.0.10(vite@8.3.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.44(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.3.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.0) vue: 3.5.44(typescript@6.0.3) - '@vitest/eslint-plugin@1.6.27(@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': + '@vitest/eslint-plugin@1.6.28(@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.69.0 - '@typescript-eslint/utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.13.0(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -4354,12 +4513,14 @@ snapshots: ansi-styles@6.2.3: {} - ansis@4.3.1: {} + ansis@4.4.0: {} are-docs-informative@0.1.1: {} argparse@2.0.1: {} + argparse@3.0.4: {} + aria-query@5.3.2: {} ast-kit@2.2.0: @@ -4397,6 +4558,8 @@ snapshots: baseline-browser-mapping@2.11.21: {} + baseline-browser-mapping@2.11.32: {} + birpc@2.9.0: {} boolbase@1.0.0: {} @@ -4422,9 +4585,17 @@ snapshots: node-releases: 2.0.54 update-browserslist-db: 1.3.2(browserslist@4.28.9) + browserslist@4.29.4: + dependencies: + baseline-browser-mapping: 2.11.32 + caniuse-lite: 1.0.30001819 + electron-to-chromium: 1.5.464 + node-releases: 2.0.60 + update-browserslist-db: 1.3.4(browserslist@4.29.4) + builtin-modules@5.3.0: {} - cac@7.0.0: {} + cac@7.1.0: {} cacheable@2.5.0: dependencies: @@ -4443,6 +4614,8 @@ snapshots: caniuse-lite@1.0.30001810: {} + caniuse-lite@1.0.30001819: {} + ccount@2.0.1: {} change-case@5.4.4: {} @@ -4469,18 +4642,20 @@ snapshots: commander@8.3.0: {} + comment-parser@1.4.1: {} + comment-parser@1.4.7: {} comment-parser@1.4.8: {} + comment-parser@1.4.9: {} + concat-map@0.0.1: {} confbox@0.1.8: {} confbox@0.3.1: {} - convert-hrtime@5.0.0: {} - core-js-compat@3.50.0: dependencies: browserslist: 4.28.9 @@ -4547,6 +4722,8 @@ snapshots: electron-to-chromium@1.5.422: {} + electron-to-chromium@1.5.464: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -4564,6 +4741,8 @@ snapshots: entities@8.0.0: {} + entities@8.1.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -4653,15 +4832,15 @@ snapshots: eslint-parser-plain@0.1.1: {} - eslint-plugin-antfu@3.2.3(eslint@10.13.0(jiti@2.7.0)): + eslint-plugin-antfu@3.3.1(eslint@10.13.0(jiti@2.7.0)): dependencies: eslint: 10.13.0(jiti@2.7.0) - eslint-plugin-command@4.0.0(@typescript-eslint/typescript-estree@8.69.0(typescript@6.0.3))(@typescript-eslint/utils@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)): + eslint-plugin-command@4.0.0(@typescript-eslint/typescript-estree@8.72.0(typescript@6.0.3))(@typescript-eslint/utils@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)): dependencies: '@es-joy/jsdoccomment': 0.92.0 - '@typescript-eslint/typescript-estree': 8.69.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.72.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) eslint: 10.13.0(jiti@2.7.0) eslint-plugin-es-x@7.8.0(eslint@10.13.0(jiti@2.7.0)): @@ -4688,13 +4867,14 @@ snapshots: dependencies: eslint: 10.13.0(jiti@2.7.0) - eslint-plugin-jsdoc@64.3.6(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): + eslint-plugin-jsdoc@65.4.2(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@es-joy/jsdoccomment': 0.97.0 + '@es-joy/escodegen': 4.2.0 + '@es-joy/jsdoccomment': 1.0.0 '@es-joy/resolve.exports': 1.2.0 - '@typescript-eslint/utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) are-docs-informative: 0.1.1 - comment-parser: 1.4.8 + comment-parser: 1.4.9 debug: 4.4.3 escape-string-regexp: 5.0.0 eslint: 10.13.0(jiti@2.7.0) @@ -4706,9 +4886,10 @@ snapshots: semver: 7.8.5 spdx-expression-parse: 5.0.0 to-valid-identifier: 1.0.0 + optionalDependencies: + typescript: 6.0.3 transitivePeerDependencies: - supports-color - - typescript eslint-plugin-jsonc@3.4.2(eslint@10.13.0(jiti@2.7.0)): dependencies: @@ -4725,7 +4906,7 @@ snapshots: transitivePeerDependencies: - '@eslint/json' - eslint-plugin-n@18.3.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): + eslint-plugin-n@18.5.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(jiti@2.7.0)) enhanced-resolve: 5.24.5 @@ -4735,17 +4916,20 @@ snapshots: globals: 15.15.0 globrex: 0.1.2 ignore: 5.3.2 + js-yaml: 5.5.0 semver: 7.8.5 optionalDependencies: typescript: 6.0.3 eslint-plugin-no-only-tests@3.4.0: {} - eslint-plugin-perfectionist@5.11.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): + eslint-plugin-perfectionist@5.13.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - eslint: 10.13.0(jiti@2.7.0) + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/utils': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) natural-orderby: 5.0.0 + optionalDependencies: + eslint: 10.13.0(jiti@2.7.0) transitivePeerDependencies: - supports-color - typescript @@ -4764,7 +4948,7 @@ snapshots: transitivePeerDependencies: - '@eslint/json' - eslint-plugin-regexp@3.3.0(eslint@10.13.0(jiti@2.7.0)): + eslint-plugin-regexp@3.3.1(eslint@10.13.0(jiti@2.7.0)): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 @@ -4786,37 +4970,43 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@74.0.0(eslint@10.13.0(jiti@2.7.0)): + eslint-plugin-unicorn@78.0.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(jiti@2.7.0)) - '@eslint/css-tree': 4.1.0 - browserslist: 4.28.9 + '@eslint/css-tree': 4.1.2 + browserslist: 4.29.4 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.50.0 detect-indent: 7.0.2 - entities: 8.0.0 + entities: 8.1.0 eslint: 10.13.0(jiti@2.7.0) find-up-simple: 1.0.1 globals: 17.12.0 + identifier-regex: 1.1.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 - is-identifier: 1.1.0 + micromark: 4.0.3 + parse5: 8.0.1 pluralize: 8.0.0 quote-js-string: 0.1.0 - regjsparser: 0.13.2 + regjsparser: 0.13.4 reserved-identifiers: 1.2.0 semver: 7.8.5 strip-indent: 4.1.1 - yaml: 2.9.0 + yaml: 2.9.1 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0)): dependencies: eslint: 10.13.0(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.72.0(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - eslint-plugin-vue@10.10.0(@stylistic/eslint-plugin@5.10.0(eslint@10.13.0(jiti@2.7.0)))(@typescript-eslint/parser@8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.13.0(jiti@2.7.0))): + eslint-plugin-vue@10.11.1(@stylistic/eslint-plugin@5.10.0(eslint@10.13.0(jiti@2.7.0)))(@typescript-eslint/parser@8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.13.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.13.0(jiti@2.7.0))): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.13.0(jiti@2.7.0)) eslint: 10.13.0(jiti@2.7.0) @@ -4828,18 +5018,18 @@ snapshots: xml-name-validator: 5.0.0 optionalDependencies: '@stylistic/eslint-plugin': 5.10.0(eslint@10.13.0(jiti@2.7.0)) - '@typescript-eslint/parser': 8.69.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.72.0(eslint@10.13.0(jiti@2.7.0))(typescript@6.0.3) - eslint-plugin-vuejs-accessibility@2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.12.0): + eslint-plugin-vuejs-accessibility@2.6.3(eslint@10.13.0(jiti@2.7.0))(globals@17.13.0): dependencies: aria-query: 5.3.2 eslint: 10.13.0(jiti@2.7.0) - globals: 17.12.0 + globals: 17.13.0 vue-eslint-parser: 10.4.1(eslint@10.13.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-yml@3.8.1(eslint@10.13.0(jiti@2.7.0)): + eslint-plugin-yml@3.9.0(eslint@10.13.0(jiti@2.7.0)): dependencies: '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.3 @@ -4923,6 +5113,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -5012,8 +5204,6 @@ snapshots: function-bind@1.1.2: {} - function-timeout@1.0.2: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5057,6 +5247,8 @@ snapshots: globals@17.12.0: {} + globals@17.13.0: {} + globrex@0.1.2: {} gopd@1.2.0: {} @@ -5125,11 +5317,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-identifier@1.1.0: - dependencies: - identifier-regex: 1.1.0 - super-regex: 1.1.0 - is-language-code@3.1.0: dependencies: '@babel/runtime': 7.29.10 @@ -5148,6 +5335,12 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@5.5.0: + dependencies: + argparse: 3.0.4 + + jsdoc-type-pratt-parser@5.9.2: {} + jsdoc-type-pratt-parser@9.0.1: dependencies: '@types/estree': 1.0.9 @@ -5157,6 +5350,10 @@ snapshots: '@types/estree': 1.0.9 '@types/node': 26.6.5 + jsdoc-type-pratt-parser@9.4.0: + dependencies: + '@types/estree': 1.0.9 + jsesc@3.1.0: {} json-schema-traverse@0.4.1: {} @@ -5315,12 +5512,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 - make-asynchronous@1.1.0: - dependencies: - p-event: 6.0.1 - type-fest: 4.41.0 - web-worker: 1.5.0 - markdown-table@3.0.4: {} marked@18.1.0: {} @@ -5454,7 +5645,7 @@ snapshots: mdn-data@2.27.1: {} - mdn-data@2.34.0: {} + mdn-data@2.37.2: {} micromark-core-commonmark@2.0.3: dependencies: @@ -5613,6 +5804,10 @@ snapshots: micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 + micromark-util-edit-map@1.0.0: + dependencies: + micromark-util-types: 2.0.2 + micromark-util-encode@2.0.1: {} micromark-util-html-tag-name@2.0.1: {} @@ -5664,6 +5859,29 @@ snapshots: transitivePeerDependencies: - supports-color + micromark@4.0.3: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-edit-map: 1.0.0 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + mime-db@1.52.0: {} mime-types@2.1.35: @@ -5705,6 +5923,8 @@ snapshots: node-releases@2.0.54: {} + node-releases@2.0.60: {} + nostics@1.2.0: {} nostics@1.3.0: {} @@ -5750,10 +5970,6 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.35.0 '@oxfmt/binding-win32-x64-msvc': 0.35.0 - p-event@6.0.1: - dependencies: - p-timeout: 6.1.4 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -5762,8 +5978,6 @@ snapshots: dependencies: p-limit: 3.1.0 - p-timeout@6.1.4: {} - package-json-from-dist@1.0.1: {} package-manager-detector@1.8.0: {} @@ -5784,6 +5998,10 @@ snapshots: dependencies: entities: 6.0.1 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + path-browserify@1.0.1: {} path-exists@4.0.0: {} @@ -5877,7 +6095,7 @@ snapshots: '@eslint-community/regexpp': 4.12.2 refa: 0.12.1 - regjsparser@0.13.2: + regjsparser@0.13.4: dependencies: jsesc: 3.1.0 @@ -5954,6 +6172,9 @@ snapshots: source-map-js@1.2.2: {} + source-map@0.7.6: + optional: true + spdx-exceptions@2.5.0: {} spdx-expression-parse@5.0.0: @@ -5987,12 +6208,6 @@ snapshots: strip-json-comments@3.1.1: {} - super-regex@1.1.0: - dependencies: - function-timeout: 1.0.2 - make-asynchronous: 1.1.0 - time-span: 5.1.0 - synckit@0.11.13: dependencies: '@pkgr/core': 0.3.6 @@ -6001,10 +6216,6 @@ snapshots: tapable@2.3.3: {} - time-span@5.1.0: - dependencies: - convert-hrtime: 5.0.0 - tinyexec@1.3.1: {} tinyglobby@0.2.17: @@ -6033,8 +6244,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@4.41.0: {} - typescript@6.0.3: {} ufo@1.6.4: {} @@ -6092,6 +6301,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.3.4(browserslist@4.29.4): + dependencies: + browserslist: 4.29.4 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -6100,6 +6315,8 @@ snapshots: verkit@0.3.2: {} + verkit@0.4.1: {} + vite@8.3.5(@types/node@26.6.5)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 @@ -6185,8 +6402,6 @@ snapshots: optionalDependencies: typescript: 6.0.3 - web-worker@1.5.0: {} - webpack-virtual-modules@0.6.2: {} which@2.0.2: @@ -6221,6 +6436,8 @@ snapshots: yaml@2.9.0: {} + yaml@2.9.1: {} + yocto-queue@0.1.0: {} zrender@6.1.0: diff --git a/frontend/src/api/constants.ts b/frontend/src/api/constants.ts index 5b7386cfe..19df3c371 100644 --- a/frontend/src/api/constants.ts +++ b/frontend/src/api/constants.ts @@ -1,2 +1,4 @@ -export const API_BASE_URL = import.meta.env.DEV ? '/dev' : '' +export const API_BASE_URL = import.meta.env.DEV + ? '/dev' + : '' export const API_TIMEOUT_MS = 120_000 diff --git a/frontend/src/api/modules/accounts.ts b/frontend/src/api/modules/accounts.ts index 040350fda..f75b9d58e 100644 --- a/frontend/src/api/modules/accounts.ts +++ b/frontend/src/api/modules/accounts.ts @@ -711,7 +711,10 @@ export function startAccountOAuth(data: AccountOAuthStartParam, options: Request function providerInputBody(data: object) { // Provider 文档是不透明 JSON,先编码,避免 HTTP 客户端合并配置时过滤特殊字段名。 - return { headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data) } + return { + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify(data), + } } export function completeAccountOAuth(data: AccountOAuthCompleteParam, options: RequestOptions = {}) { diff --git a/frontend/src/api/modules/plugins/extensions.ts b/frontend/src/api/modules/plugins/extensions.ts index e6aaa52e8..13e79ec6c 100644 --- a/frontend/src/api/modules/plugins/extensions.ts +++ b/frontend/src/api/modules/plugins/extensions.ts @@ -92,12 +92,16 @@ export function callPluginManagementRoute( call: PluginManagementRawRequest, options: RequestOptions = {}, ) { - const query = call.query ? `?${call.query}` : '' + const query = call.query + ? `?${call.query}` + : '' return requestPluginManagementRaw({ url: `${pluginManagementTargetPath(target)}/api/${encodePluginPath(call.path)}${query}`, method: call.method, data: call.body?.byteLength ? call.body : undefined, - headers: call.contentType ? { 'Content-Type': call.contentType } : undefined, + headers: call.contentType + ? { 'Content-Type': call.contentType } + : undefined, timeout: PLUGIN_MANAGEMENT_TIMEOUT_MS, ...options, }) diff --git a/frontend/src/api/request/index.ts b/frontend/src/api/request/index.ts index 654ca7148..2e29d4478 100644 --- a/frontend/src/api/request/index.ts +++ b/frontend/src/api/request/index.ts @@ -33,10 +33,21 @@ const http = axios.create({ }) // 从外到内:提示、会话恢复、重试、错误归一化、原始响应处理、实际传输 -const plugins: RequestPlugin[] = [feedbackPlugin, sessionPlugin, retryPlugin, errorPlugin, rawPlugin] +const plugins: RequestPlugin[] = [ + feedbackPlugin, + sessionPlugin, + retryPlugin, + errorPlugin, + rawPlugin, +] function execute(config: RequestConfig, raw?: RawResponseOptions, stream = false) { - const context: RequestContext = { config, raw, stream, isCurrent: () => !config.signal?.aborted } + const context: RequestContext = { + config, + raw, + stream, + isCurrent: () => !config.signal?.aborted, + } const transport: RequestNext = () => http.request(context.config) return plugins.reduceRight((next, plugin) => () => plugin(context, next), transport)() } @@ -57,9 +68,16 @@ export async function requestRaw(config: RequestConfig, options: RawResponseOpti export async function requestStream(config: RequestConfig, options: RawResponseOptions): Promise { // 成功正文保持流式交付;宿主错误仍由同一管线识别和恢复,禁止网络错误重放写操作 const controller = new AbortController() - const signal = config.signal ? AbortSignal.any([config.signal, controller.signal]) : controller.signal + const signal = config.signal + ? AbortSignal.any([config.signal, controller.signal]) + : controller.signal try { - const { data } = await execute({ ...config, adapter: 'fetch', retry: false, signal }, options, true) + const { data } = await execute({ + ...config, + adapter: 'fetch', + retry: false, + signal, + }, options, true) return data as Response } catch (error) { diff --git a/frontend/src/api/request/plugins/error.ts b/frontend/src/api/request/plugins/error.ts index 4e8d1f03f..bf9584428 100644 --- a/frontend/src/api/request/plugins/error.ts +++ b/frontend/src/api/request/plugins/error.ts @@ -19,7 +19,9 @@ export async function errorPlugin(_context: RequestContext, next: RequestNext) { return response } catch (error) { - throw isAxiosError(error) ? normalizeApiError(error) : error + throw isAxiosError(error) + ? normalizeApiError(error) + : error } } @@ -64,7 +66,9 @@ export function normalizeApiResponseError(response: AxiosResponse) { const envelope = adminErrorEnvelope(response.data) if (!envelope || envelope.code === 200) return undefined - const message = adminErrorMessage(envelope) || httpFallback(response.status >= 400 ? response.status : 0) + const message = adminErrorMessage(envelope) || httpFallback(response.status >= 400 + ? response.status + : 0) return new ApiError( message, response.status, @@ -80,7 +84,9 @@ function adminErrorEnvelope(value: unknown): AdminErrorEnvelope | null { const record = value as Record if (typeof record.code !== 'number' || !Number.isSafeInteger(record.code)) return null - const message = typeof record.message === 'string' ? record.message.trim() : '' + const message = typeof record.message === 'string' + ? record.message.trim() + : '' return { code: record.code, message } } @@ -109,8 +115,12 @@ function responseHeader( ) { if (!headers) return undefined - const value = typeof headers.get === 'function' ? headers.get(name) : headers[name] - return typeof value === 'string' && value ? value : undefined + const value = typeof headers.get === 'function' + ? headers.get(name) + : headers[name] + return typeof value === 'string' && value + ? value + : undefined } function httpFallback(status: number) { @@ -144,7 +154,9 @@ function httpFallback(status: number) { case 504: return t('common.errors.upstreamTimeout') default: - return status > 0 ? t('common.errors.http', { status }) : t('common.errors.request') + return status > 0 + ? t('common.errors.http', { status }) + : t('common.errors.request') } } diff --git a/frontend/src/api/request/plugins/raw.ts b/frontend/src/api/request/plugins/raw.ts index 07fdbf95b..95f8f55aa 100644 --- a/frontend/src/api/request/plugins/raw.ts +++ b/frontend/src/api/request/plugins/raw.ts @@ -70,7 +70,9 @@ export async function rawPlugin(context: RequestContext, next: RequestNext) { if (normalized) throw normalized throw new ApiError( - response.status >= 400 ? t('common.errors.http', { status: response.status }) : t('common.errors.unsupportedResponse'), + response.status >= 400 + ? t('common.errors.http', { status: response.status }) + : t('common.errors.unsupportedResponse'), response.status, undefined, rawHeader(response.headers, 'x-request-id'), @@ -111,8 +113,12 @@ function rawHeader(headers: unknown, name: string) { if (!headers || typeof headers !== 'object') return undefined const source = headers as { get?: (key: string) => unknown, [key: string]: unknown } - const value = typeof source.get === 'function' ? source.get(name) : source[name] - return typeof value === 'string' && value ? value : undefined + const value = typeof source.get === 'function' + ? source.get(name) + : source[name] + return typeof value === 'string' && value + ? value + : undefined } function decodeRawEnvelope(body: ArrayBuffer): unknown { diff --git a/frontend/src/api/request/plugins/session.ts b/frontend/src/api/request/plugins/session.ts index 0c53bb85a..36b22d82b 100644 --- a/frontend/src/api/request/plugins/session.ts +++ b/frontend/src/api/request/plugins/session.ts @@ -84,7 +84,9 @@ export async function sessionPlugin(context: RequestContext, send: RequestNext) if (!isSessionRequired(error) || config.signal?.aborted || generation !== sessionGeneration) throw error if (!config.skipSessionRecovery && sessionRecoveryHandler - && await (sessionRecovery ?? (successfulRecoveries !== recovered ? true : recoverSession()))) { + && await (sessionRecovery ?? (successfulRecoveries !== recovered + ? true + : recoverSession()))) { // 迟到的 40101 也须等待当前续期;Cookie 更新完成后原请求最多重放一次 if (config.signal?.aborted || generation !== sessionGeneration) throw error diff --git a/frontend/src/components/AccountGroupMarks.vue b/frontend/src/components/AccountGroupMarks.vue index 2597886e4..026034bd3 100644 --- a/frontend/src/components/AccountGroupMarks.vue +++ b/frontend/src/components/AccountGroupMarks.vue @@ -11,7 +11,9 @@ const props = defineProps<{ const { t } = useI18n({ useScope: 'global' }) const fullNames = computed(() => - props.groups.map(group => (group.enabled ? group.name : t('common.status.disabledName', { name: group.name }))).join('、'), + props.groups.map(group => (group.enabled + ? group.name + : t('common.status.disabledName', { name: group.name }))).join('、'), ) diff --git a/frontend/src/components/AnimatedMetricValue.vue b/frontend/src/components/AnimatedMetricValue.vue index 2e4a812ec..df87e55f4 100644 --- a/frontend/src/components/AnimatedMetricValue.vue +++ b/frontend/src/components/AnimatedMetricValue.vue @@ -24,7 +24,9 @@ const hasAnimatedValue = computed( && Number.isFinite(props.rawValue) && Boolean(props.formatter), ) -const sourceValue = computed(() => (hasAnimatedValue.value ? (props.rawValue ?? 0) : 0)) +const sourceValue = computed(() => (hasAnimatedValue.value + ? (props.rawValue ?? 0) + : 0)) const disabled = computed(() => preferredMotion.value === 'reduce' || !hasAnimatedValue.value) const animatedValue = useTransition(sourceValue, { duration: computed(() => props.duration), @@ -33,7 +35,9 @@ const animatedValue = useTransition(sourceValue, { }) const displayValue = computed(() => - hasAnimatedValue.value && props.formatter ? props.formatter(animatedValue.value) : props.value, + hasAnimatedValue.value && props.formatter + ? props.formatter(animatedValue.value) + : props.value, ) diff --git a/frontend/src/components/AppLanguageMenu.vue b/frontend/src/components/AppLanguageMenu.vue index c4d667757..b2b7acadf 100644 --- a/frontend/src/components/AppLanguageMenu.vue +++ b/frontend/src/components/AppLanguageMenu.vue @@ -39,7 +39,9 @@ async function selectLocale(value: Locale, close: () => void) { close() } catch { - notification.error({ message: t('navigation.languageLoadFailed') }) + notification.error({ + message: t('navigation.languageLoadFailed'), + }) } finally { if (pendingLocale.value === value) diff --git a/frontend/src/components/LastUsedAtCell.vue b/frontend/src/components/LastUsedAtCell.vue index 5520cf616..5f48956f4 100644 --- a/frontend/src/components/LastUsedAtCell.vue +++ b/frontend/src/components/LastUsedAtCell.vue @@ -4,7 +4,12 @@ import { useI18n } from 'vue-i18n' const props = defineProps<{ value: string | null, fullDisplay: string | null }>() const { locale } = useI18n({ useScope: 'global' }) -const shortTimeUnits: Record = { sec: 's', min: 'min', hr: 'h', day: 'd' } +const shortTimeUnits: Record = { + sec: 's', + min: 'min', + hr: 'h', + day: 'd', +} const display = useTimeAgoIntl(() => props.value ?? 0, { get locale() { return locale.value }, diff --git a/frontend/src/components/ProviderFilter.vue b/frontend/src/components/ProviderFilter.vue index 9a986dce6..66fbe0c49 100644 --- a/frontend/src/components/ProviderFilter.vue +++ b/frontend/src/components/ProviderFilter.vue @@ -13,8 +13,16 @@ const { t } = useI18n({ useScope: 'global' }) const provider = defineModel({ required: true }) const options = computed(() => ([ - { label: t('common.providers.all'), value: '', icon: LayoutGrid }, - ...PROVIDER_IDS.map(value => ({ label: formatProviderLabel(value), value, icon: providerIcon(value) })), + { + label: t('common.providers.all'), + value: '', + icon: LayoutGrid, + }, + ...PROVIDER_IDS.map(value => ({ + label: formatProviderLabel(value), + value, + icon: providerIcon(value), + })), ])) diff --git a/frontend/src/components/ProviderIconGroup.vue b/frontend/src/components/ProviderIconGroup.vue index a297edbc1..28ca24021 100644 --- a/frontend/src/components/ProviderIconGroup.vue +++ b/frontend/src/components/ProviderIconGroup.vue @@ -18,23 +18,31 @@ const props = withDefaults( const { t } = useI18n({ useScope: 'global' }) const normalizedProvider = computed(() => (props.provider ?? '').trim().toLowerCase()) -const providerIconComponent = computed(() => normalizedProvider.value ? providerIcon(normalizedProvider.value) : undefined) +const providerIconComponent = computed(() => normalizedProvider.value + ? providerIcon(normalizedProvider.value) + : undefined) const authenticationIconComponent = computed(() => authenticationIcon(props.authenticationKind)) const showAuthenticationKind = computed(() => props.authenticationKind !== undefined) const groupGapClass = computed(() => { if (props.size === 'xs') return 'gap-0.75' - return props.size === 'sm' ? 'gap-1' : 'gap-2' + return props.size === 'sm' + ? 'gap-1' + : 'gap-2' }) const iconContainerClass = computed(() => { if (props.size === 'xs') return 'size-4.5 rounded-md' - return props.size === 'sm' ? 'size-5 rounded-md' : 'size-7 rounded-lg' + return props.size === 'sm' + ? 'size-5 rounded-md' + : 'size-7 rounded-lg' }) const iconClass = computed(() => { if (props.size === 'xs') return 'size-2.5' - return props.size === 'sm' ? 'size-3' : 'size-4' + return props.size === 'sm' + ? 'size-3' + : 'size-4' }) const providerLabel = computed(() => formatProviderLabel(props.provider, t('common.providers.unknown'))) diff --git a/frontend/src/components/account/AccountPlanBadge.vue b/frontend/src/components/account/AccountPlanBadge.vue index a49adf4bd..f5a9e71fc 100644 --- a/frontend/src/components/account/AccountPlanBadge.vue +++ b/frontend/src/components/account/AccountPlanBadge.vue @@ -30,8 +30,12 @@ const fallbackPalettes = [ 'bg-cp-orange-container text-cp-orange-on-container', ] as const -const rawPlanType = computed(() => props.authenticationKind === 'api_key' ? 'api' : props.planType?.trim() || '') -const displayText = computed(() => props.authenticationKind === 'api_key' ? 'API' : props.planTypeDisplay) +const rawPlanType = computed(() => props.authenticationKind === 'api_key' + ? 'api' + : props.planType?.trim() || '') +const displayText = computed(() => props.authenticationKind === 'api_key' + ? 'API' + : props.planTypeDisplay) const sizeClass = computed(() => { if (props.size === 'xs') diff --git a/frontend/src/components/account/account-usage-window/presenter.ts b/frontend/src/components/account/account-usage-window/presenter.ts index 2a806446e..42a5803b0 100644 --- a/frontend/src/components/account/account-usage-window/presenter.ts +++ b/frontend/src/components/account/account-usage-window/presenter.ts @@ -130,7 +130,9 @@ export function resolveAccountUsageWindowPresentation( const quota = input.window ? quotaWindowPresentation(input.window, definition.minimumBarWidth) : null - const label = input.window ? quotaWindowLabel(input.window) : undefined + const label = input.window + ? quotaWindowLabel(input.window) + : undefined return { mode, @@ -205,11 +207,13 @@ function requestBuckets(value: unknown) { ) { return [] } - return [{ - bucketStart: item.bucketStart, - label: item.label, - requestCount: item.requestCount, - }] + return [ + { + bucketStart: item.bucketStart, + label: item.label, + requestCount: item.requestCount, + }, + ] }) } @@ -241,11 +245,26 @@ export function quotaWindowLocalUsageDisplay(window: AccountQuotaWindow) { } const quotaWindowPeriods = [ - { seconds: 18_000, label: () => t('accounts.quota.windows.fiveHour') }, - { seconds: 86_400, label: () => t('accounts.quota.windows.daily') }, - { seconds: 604_800, label: () => t('accounts.quota.windows.weekly') }, - { seconds: 2_592_000, label: () => t('accounts.quota.windows.monthly') }, - { seconds: 31_536_000, label: () => t('accounts.quota.windows.annual') }, + { + seconds: 18_000, + label: () => t('accounts.quota.windows.fiveHour'), + }, + { + seconds: 86_400, + label: () => t('accounts.quota.windows.daily'), + }, + { + seconds: 604_800, + label: () => t('accounts.quota.windows.weekly'), + }, + { + seconds: 2_592_000, + label: () => t('accounts.quota.windows.monthly'), + }, + { + seconds: 31_536_000, + label: () => t('accounts.quota.windows.annual'), + }, ] export function quotaWindowLabel(window: AccountQuotaWindow, includeLimitName = true) { @@ -261,12 +280,21 @@ export function quotaWindowLabel(window: AccountQuotaWindow, includeLimitName = label = period.label() } else if (seconds !== null && seconds > 0) { - if (seconds % 86_400 === 0) - label = t('accounts.quota.windows.days', { duration: formatInteger(seconds / 86_400) }) - else if (seconds % 3_600 === 0) - label = t('accounts.quota.windows.hours', { duration: formatInteger(seconds / 3_600) }) - else - label = t('accounts.quota.windows.minutes', { duration: formatInteger(Math.ceil(seconds / 60)) }) + if (seconds % 86_400 === 0) { + label = t('accounts.quota.windows.days', { + duration: formatInteger(seconds / 86_400), + }) + } + else if (seconds % 3_600 === 0) { + label = t('accounts.quota.windows.hours', { + duration: formatInteger(seconds / 3_600), + }) + } + else { + label = t('accounts.quota.windows.minutes', { + duration: formatInteger(Math.ceil(seconds / 60)), + }) + } } else if (window.role === 'monthly') { label = t('accounts.quota.windows.monthly') @@ -291,7 +319,9 @@ function requestCountDisplay(localUsage: AccountLocalUsage | null) { if (typeof display === 'string' && display.trim()) return display.trim() const count = localUsage?.requestCount - return typeof count === 'number' ? formatInteger(count) : '0' + return typeof count === 'number' + ? formatInteger(count) + : '0' } function rollingWindowDurationDisplay(windowSeconds: number | null | undefined) { @@ -300,13 +330,24 @@ function rollingWindowDurationDisplay(windowSeconds: number | null | undefined) const hourSeconds = 60 * 60 const daySeconds = 24 * hourSeconds - if (windowSeconds <= daySeconds * 2 && windowSeconds % hourSeconds === 0) - return t('accounts.quota.h', { duration: formatInteger(windowSeconds / hourSeconds) }) - if (windowSeconds % daySeconds === 0) - return t('accounts.quota.d', { duration: formatInteger(windowSeconds / daySeconds) }) - if (windowSeconds % hourSeconds === 0) - return t('accounts.quota.h', { duration: formatInteger(windowSeconds / hourSeconds) }) - return t('accounts.quota.s', { duration: formatInteger(windowSeconds) }) + if (windowSeconds <= daySeconds * 2 && windowSeconds % hourSeconds === 0) { + return t('accounts.quota.h', { + duration: formatInteger(windowSeconds / hourSeconds), + }) + } + if (windowSeconds % daySeconds === 0) { + return t('accounts.quota.d', { + duration: formatInteger(windowSeconds / daySeconds), + }) + } + if (windowSeconds % hourSeconds === 0) { + return t('accounts.quota.h', { + duration: formatInteger(windowSeconds / hourSeconds), + }) + } + return t('accounts.quota.s', { + duration: formatInteger(windowSeconds), + }) } export function quotaWindowPresentation(window: AccountQuotaWindow, minimumWidth: string) { @@ -315,7 +356,9 @@ export function quotaWindowPresentation(window: AccountQuotaWindow, minimumWidth return { barStyle: { width: `${percent}%`, - minWidth: percent > 0 ? minimumWidth : '0', + minWidth: percent > 0 + ? minimumWidth + : '0', }, barClass: quotaToneClasses[tone].bar, percentTextClass: quotaToneClasses[tone].text, @@ -337,7 +380,9 @@ function requestTimeline(buckets: NonNullable ({ key: bucket.bucketStart, requestCount: bucket.requestCount, - height: bucket.requestCount === 0 ? '0' : `${Math.max(25, Math.round(bucket.requestCount / maximum * 100))}%`, + height: bucket.requestCount === 0 + ? '0' + : `${Math.max(25, Math.round(bucket.requestCount / maximum * 100))}%`, title: bucket.label, })) } diff --git a/frontend/src/components/charts/timeSeriesGap.ts b/frontend/src/components/charts/timeSeriesGap.ts index 05cb773fc..d22767fb9 100644 --- a/frontend/src/components/charts/timeSeriesGap.ts +++ b/frontend/src/components/charts/timeSeriesGap.ts @@ -76,7 +76,10 @@ export function requestActivityByBucket( activity: Array<{ bucket: string, totalRequests: number }>, ) { const requestsByBucket = new Map( - activity.map(point => [point.bucket, Math.max(0, point.totalRequests)]), + activity.map(point => [ + point.bucket, + Math.max(0, point.totalRequests), + ]), ) return buckets.map(bucket => (requestsByBucket.get(bucket) ?? 0) > 0) } @@ -85,11 +88,15 @@ export function zeroInactiveValues( values: TemporalValue[], active: boolean[], ) { - return values.map((value, index) => normalizeValue(value) ?? (active[index] === false ? 0 : null)) + return values.map((value, index) => normalizeValue(value) ?? (active[index] === false + ? 0 + : null)) } function normalizeValue(value: TemporalValue) { - return typeof value === 'number' && Number.isFinite(value) ? value : null + return typeof value === 'number' && Number.isFinite(value) + ? value + : null } function boundedGapRuns(values: Array) { diff --git a/frontend/src/components/charts/tooltip.ts b/frontend/src/components/charts/tooltip.ts index e6d50eea2..8436f69ab 100644 --- a/frontend/src/components/charts/tooltip.ts +++ b/frontend/src/components/charts/tooltip.ts @@ -47,17 +47,23 @@ export function chartTooltipStyle( } export function chartTooltipContent(label: string, lines: string[]) { - const title = label ? `${escape(label)}` : '' + const title = label + ? `${escape(label)}` + : '' return [title, ...lines].filter(Boolean).join('
') } export function tooltipRows(params: unknown): Record[] { - const values = Array.isArray(params) ? params : [params] + const values = Array.isArray(params) + ? params + : [params] return values.filter(isRecord) } export function tooltipIndex(source: unknown) { if (!isRecord(source)) return -1 - return typeof source.dataIndex === 'number' ? source.dataIndex : -1 + return typeof source.dataIndex === 'number' + ? source.dataIndex + : -1 } diff --git a/frontend/src/components/client-profile/ClientProfileEditor.vue b/frontend/src/components/client-profile/ClientProfileEditor.vue index fde7ef92c..021a5aeab 100644 --- a/frontend/src/components/client-profile/ClientProfileEditor.vue +++ b/frontend/src/components/client-profile/ClientProfileEditor.vue @@ -31,14 +31,18 @@ const previewing = shallowRef(false) const independentDraft = shallowRef() const effective = computed(() => model.value ?? options.value?.globalConfiguration) const inherited = computed(() => props.allowInherit && model.value === null) -const preset = computed(() => effective.value && !effective.value.mode ? effective.value : undefined) +const preset = computed(() => effective.value && !effective.value.mode + ? effective.value + : undefined) const custom = computed(() => effective.value?.mode === 'custom' || preset.value?.versionMode === 'fixed') const needsInput = computed(() => effective.value?.mode === 'custom' ? !effective.value.userAgent.trim() : preset.value?.versionMode === 'fixed' && (!preset.value.codexVersion || (preset.value.client === 'desktop' && (!preset.value.desktopVersion || !preset.value.desktopBuild)))) const profileSource = computed({ - get: () => model.value === null ? 'global' : 'independent', + get: () => model.value === null + ? 'global' + : 'independent', set: (value: string) => { if (value === 'global') { if (model.value) @@ -46,7 +50,9 @@ const profileSource = computed({ model.value = null } else if (independentDraft.value ?? options.value?.globalConfiguration) { - model.value = { ...(independentDraft.value ?? options.value!.globalConfiguration) } + model.value = { + ...(independentDraft.value ?? options.value!.globalConfiguration), + } } }, }) @@ -64,7 +70,10 @@ async function load() { } } -watch([model, () => props.active], ([configuration, active], _, onCleanup) => { +watch([ + model, + () => props.active, +], ([configuration, active], _, onCleanup) => { if (!active) return let cancelled = false diff --git a/frontend/src/components/client-profile/ClientProfilePresetFields.vue b/frontend/src/components/client-profile/ClientProfilePresetFields.vue index 8b54f02cc..34fbc68b8 100644 --- a/frontend/src/components/client-profile/ClientProfilePresetFields.vue +++ b/frontend/src/components/client-profile/ClientProfilePresetFields.vue @@ -21,12 +21,16 @@ const { t } = useI18n({ useScope: 'global' }) const model = defineModel({ required: true }) const presetDraft = shallowRef() const customDraft = shallowRef() -const preset = computed(() => model.value && !model.value.mode ? model.value : undefined) +const preset = computed(() => model.value && !model.value.mode + ? model.value + : undefined) const custom = computed(() => model.value?.mode === 'custom' || preset.value?.versionMode === 'fixed') const platforms = { macos: 'MacOS', linux: 'Linux', windows: 'Windows' } const presetOptions = computed(() => props.presets.map(({ configuration }) => ({ value: `${configuration.platform}-${configuration.client}`, - label: `${platforms[configuration.platform]} · ${configuration.client === 'desktop' ? 'Desktop' : 'CLI'}`, + label: `${platforms[configuration.platform]} · ${configuration.client === 'desktop' + ? 'Desktop' + : 'CLI'}`, }))) const currentPreset = computed(() => props.presets.find(({ configuration }) => configuration.client === preset.value?.client && configuration.platform === preset.value?.platform, @@ -34,13 +38,18 @@ const currentPreset = computed(() => props.presets.find(({ configuration }) => const selectedPreset = computed({ get: () => { const selection = preset.value ?? presetDraft.value - return selection ? `${selection.platform}-${selection.client}` : '' + return selection + ? `${selection.platform}-${selection.client}` + : '' }, set: (value: string) => { const selected = props.presets.find(({ configuration }) => `${configuration.platform}-${configuration.client}` === value) if (selected) { customDraft.value = undefined - model.value = { ...selected.configuration, versionMode: selected.automaticAvailable ? 'latest' : 'fixed' } + model.value = { + ...selected.configuration, + versionMode: selected.automaticAvailable ? 'latest' : 'fixed', + } } }, }) @@ -49,7 +58,12 @@ const cliEntry = computed({ set: (value: string) => { if (preset.value && (value === 'core' || value === 'tui' || value === 'exec')) { customDraft.value = undefined - model.value = { ...preset.value, cliEntry: value === 'core' ? null : value } + model.value = { + ...preset.value, + cliEntry: value === 'core' + ? null + : value, + } } }, }) @@ -61,23 +75,39 @@ const versionMode = computed({ return if (preset.value) presetDraft.value = { ...preset.value } - model.value = { ...(customDraft.value ?? { mode: 'custom', userAgent: props.preview!.userAgent }) } + model.value = { + ...(customDraft.value ?? { mode: 'custom', userAgent: props.preview!.userAgent }), + } } else { if (model.value?.mode === 'custom') customDraft.value = { ...model.value } const selection = preset.value ?? presetDraft.value ?? props.presets[0]?.configuration if (selection) { - model.value = { ...selection, versionMode: 'latest', codexVersion: null, desktopVersion: null, desktopBuild: null } + model.value = { + ...selection, + versionMode: 'latest', + codexVersion: null, + desktopVersion: null, + desktopBuild: null, + } } } }, }) const versionLag = computed({ - get: () => preset.value?.versionLag == null ? '' : String(preset.value.versionLag), + get: () => preset.value?.versionLag == null + ? '' + : String(preset.value.versionLag), set: (value: string) => { - if (preset.value) - model.value = { ...preset.value, versionLag: value === '' ? null : Number(value) } + if (preset.value) { + model.value = { + ...preset.value, + versionLag: value === '' + ? null + : Number(value), + } + } }, }) const versionLagError = computed(() => { @@ -89,21 +119,37 @@ const versionLagError = computed(() => { const terminal = computed({ get: () => preset.value?.terminal ?? '', set: (value: string) => { - if (preset.value) - model.value = { ...preset.value, terminal: value === '' ? null : value } + if (preset.value) { + model.value = { + ...preset.value, + terminal: value === '' + ? null + : value, + } + } }, }) const osVersion = computed({ get: () => preset.value?.osVersion ?? '', set: (value: string) => { - if (preset.value) - model.value = { ...preset.value, osVersion: value === '' ? null : value } + if (preset.value) { + model.value = { + ...preset.value, + osVersion: value === '' + ? null + : value, + } + } }, }) const userAgent = computed({ - get: () => model.value?.mode === 'custom' ? model.value.userAgent : props.preview?.userAgent ?? '', + get: () => model.value?.mode === 'custom' + ? model.value.userAgent + : props.preview?.userAgent ?? '', set: (value: string) => { - const previous = model.value?.mode === 'custom' ? model.value : undefined + const previous = model.value?.mode === 'custom' + ? model.value + : undefined if (preset.value) presetDraft.value = { ...preset.value } // 已知客户端的配套头重新派生,避免修改版本后继续携带旧值。 @@ -111,7 +157,9 @@ const userAgent = computed({ model.value = { mode: 'custom', userAgent: value, - ...(!recognized && previous ? { originator: previous.originator, codexVersion: previous.codexVersion } : {}), + ...(!recognized && previous + ? { originator: previous.originator, codexVersion: previous.codexVersion } + : {}), } }, }) diff --git a/frontend/src/components/client-profile/XaiClientProfileEditor.vue b/frontend/src/components/client-profile/XaiClientProfileEditor.vue index b3160f853..ab459ff9e 100644 --- a/frontend/src/components/client-profile/XaiClientProfileEditor.vue +++ b/frontend/src/components/client-profile/XaiClientProfileEditor.vue @@ -24,14 +24,28 @@ const previewError = shallowRef('') const previewing = shallowRef(false) const needsVersionInput = computed(() => model.value?.versionMode === 'fixed' && !model.value.clientVersion) const fields = computed(() => ([ - { key: 'clientIdentifier', label: t('settings.clientProfile.clientId') }, - { key: 'clientMode', label: t('settings.clientProfile.operatingMode') }, - { key: 'targetOs', label: t('settings.clientProfile.operatingSystem') }, - { key: 'targetArch', label: t('settings.clientProfile.cpuArchitecture') }, + { + key: 'clientIdentifier', + label: t('settings.clientProfile.clientId'), + }, + { + key: 'clientMode', + label: t('settings.clientProfile.operatingMode'), + }, + { + key: 'targetOs', + label: t('settings.clientProfile.operatingSystem'), + }, + { + key: 'targetArch', + label: t('settings.clientProfile.cpuArchitecture'), + }, ] as const)) const source = computed({ - get: () => model.value === null ? 'global' : 'independent', + get: () => model.value === null + ? 'global' + : 'independent', set: (value: string) => { if (value === 'global') model.value = null @@ -45,16 +59,26 @@ const versionMode = computed({ if (model.value) { model.value = { ...model.value, - versionMode: value === 'fixed' ? 'fixed' : 'latest', - clientVersion: value === 'fixed' ? preview.value?.clientVersion ?? null : null, + versionMode: value === 'fixed' + ? 'fixed' + : 'latest', + clientVersion: value === 'fixed' + ? preview.value?.clientVersion ?? null + : null, } } }, }) function updateField(key: keyof XaiClientProfileSelection, value: string) { - if (model.value) - model.value = { ...model.value, [key]: key === 'clientVersion' ? value || null : value } + if (model.value) { + model.value = { + ...model.value, + [key]: key === 'clientVersion' + ? value || null + : value, + } + } } async function load() { @@ -67,7 +91,10 @@ async function load() { } } -watch([model, () => props.active], ([configuration, active], _, onCleanup) => { +watch([ + model, + () => props.active, +], ([configuration, active], _, onCleanup) => { if (!active) return let cancelled = false diff --git a/frontend/src/components/schema-form/index.vue b/frontend/src/components/schema-form/index.vue index 1bfc84bef..299ee7cb0 100644 --- a/frontend/src/components/schema-form/index.vue +++ b/frontend/src/components/schema-form/index.vue @@ -33,7 +33,9 @@ const incompatibleValue = computed(() => fields.value?.some((field) => { return !field.values.some(option => option === item) if (field.type === 'number' || field.type === 'integer') return typeof item !== 'number' && typeof item !== 'string' - return field.type === 'boolean' ? typeof item !== 'boolean' : typeof item !== 'string' + return field.type === 'boolean' + ? typeof item !== 'boolean' + : typeof item !== 'string' }) ?? false) const showJson = computed(() => jsonMode.value || fields.value === null || hasExtra.value || incompatibleValue.value || Boolean(error.value)) const empty = computed(() => fields.value?.length === 0 && props.schema.additionalProperties === false && !showJson.value) @@ -48,14 +50,34 @@ function fieldValue(field: InputField) { } function inputValue(key: string) { - return value.value && Object.hasOwn(value.value, key) ? value.value[key] : undefined + return value.value && Object.hasOwn(value.value, key) + ? value.value[key] + : undefined } function fieldOptions(field: InputField) { const options = field.values - ? field.values.map((item, index) => ({ value: String(index), label: String(item) })) - : [{ value: 'true', label: t('common.values.yes') }, { value: 'false', label: t('common.values.no') }] - return [{ value: '', label: t('common.values.unspecified') }, ...options] + ? field.values.map((item, index) => ({ + value: String(index), + label: String(item), + })) + : [ + { + value: 'true', + label: t('common.values.yes'), + }, + { + value: 'false', + label: t('common.values.no'), + }, + ] + return [ + { + value: '', + label: t('common.values.unspecified'), + }, + ...options, + ] } function update(field: InputField, input: string) { @@ -71,7 +93,9 @@ function update(field: InputField, input: string) { } else if (field.type === 'number' || field.type === 'integer') { // 不把尚未输入完整的数字静默替换成 0,保留原值供服务端校验。 - next = Number.isFinite(Number(input)) ? Number(input) : input + next = Number.isFinite(Number(input)) + ? Number(input) + : input } else { next = input @@ -87,7 +111,9 @@ async function focusField(key?: string) { await nextTick() const control = editableField && !showJson.value - ? [...(root.value?.querySelectorAll('[data-schema-control]') ?? [])] + ? [ + ...(root.value?.querySelectorAll('[data-schema-control]') ?? []), + ] .find(element => element.dataset.schemaControl === key) : root.value?.querySelector('[data-schema-json-control], textarea') control?.focus() diff --git a/frontend/src/components/schema-form/schema.ts b/frontend/src/components/schema-form/schema.ts index dff594b01..2cb2409c2 100644 --- a/frontend/src/components/schema-form/schema.ts +++ b/frontend/src/components/schema-form/schema.ts @@ -12,29 +12,62 @@ export interface InputField { // 仅将能无损编辑的平面字段映射为控件,复杂 schema 保留 JSON 编辑,完整校验由服务端负责。 export function inputFields(schema: Record): InputField[] | null { - if (['$ref', 'allOf', 'anyOf', 'oneOf', 'if', 'dependentSchemas'].some(key => key in schema)) + if ([ + '$ref', + 'allOf', + 'anyOf', + 'oneOf', + 'if', + 'dependentSchemas', + ].some(key => key in schema)) { return null - if (!isRecord(schema.properties)) - return schema.additionalProperties === false ? [] : null - const required = Array.isArray(schema.required) ? schema.required : [] + } + if (!isRecord(schema.properties)) { + return schema.additionalProperties === false + ? [] + : null + } + const required = Array.isArray(schema.required) + ? schema.required + : [] const fields: InputField[] = [] for (const [key, value] of Object.entries(schema.properties)) { - if (!isRecord(value) || !['string', 'number', 'integer', 'boolean'].includes(String(value.type))) + if (!isRecord(value) || ![ + 'string', + 'number', + 'integer', + 'boolean', + ].includes(String(value.type))) { return null - if (['$ref', 'allOf', 'anyOf', 'oneOf', 'const', 'readOnly'].some(key => key in value)) + } + if ([ + '$ref', + 'allOf', + 'anyOf', + 'oneOf', + 'const', + 'readOnly', + ].some(key => key in value)) { return null + } const values = value.enum if (values !== undefined && (!Array.isArray(values) || !values.every(item => item === null || ['string', 'number', 'boolean'].includes(typeof item)))) return null fields.push({ key, - label: typeof value.title === 'string' ? value.title : key, - description: typeof value.description === 'string' ? value.description : undefined, + label: typeof value.title === 'string' + ? value.title + : key, + description: typeof value.description === 'string' + ? value.description + : undefined, type: value.type as InputField['type'], required: required.includes(key), secret: value.writeOnly === true || value.format === 'password', values: values as InputField['values'], }) } - return fields.length || schema.additionalProperties === false ? fields : null + return fields.length || schema.additionalProperties === false + ? fields + : null } diff --git a/frontend/src/components/usage/RequestHealthTimelineCard.vue b/frontend/src/components/usage/RequestHealthTimelineCard.vue index a7af0a5ef..78061204b 100644 --- a/frontend/src/components/usage/RequestHealthTimelineCard.vue +++ b/frontend/src/components/usage/RequestHealthTimelineCard.vue @@ -118,7 +118,9 @@ function animatePointWave(centerIndex: number) { const cellsByDistance: HTMLElement[][] = [[], [], []] for (let distance = 0; distance <= 2; distance += 1) { - const candidateIndexes = distance === 0 ? [centerIndex] : [centerIndex - distance, centerIndex + distance] + const candidateIndexes = distance === 0 + ? [centerIndex] + : [centerIndex - distance, centerIndex + distance] for (const index of candidateIndexes) { const button = buttons[index] @@ -140,7 +142,10 @@ function animatePointWave(centerIndex: number) { if (preferredMotion.value === 'reduce') { const cells = cellsByDistance.flat() - gsap.set([...cells, ...cellsToRelease], { clearProps: 'transform,willChange' }) + gsap.set([ + ...cells, + ...cellsToRelease, + ], { clearProps: 'transform,willChange' }) wavedCellIndexes = nextCellIndexes return } @@ -197,7 +202,12 @@ function releasePointWave() { function pointAccessibilityLabel(point: DashboardHealthTimelinePoint) { const eligibleRequests = healthEligibleRequestCount(point) - return t('usage.health.validRequestsAvailability', { time: point.time, label: healthStatusMeta.value[point.status].label, count: formatHealthCount(eligibleRequests), reliabilityDisplay: point.reliabilityDisplay }) + return t('usage.health.validRequestsAvailability', { + time: point.time, + label: healthStatusMeta.value[point.status].label, + count: formatHealthCount(eligibleRequests), + reliabilityDisplay: point.reliabilityDisplay, + }) } watch(popoverOpen, (open) => { diff --git a/frontend/src/components/usage/UsageBillingCell.vue b/frontend/src/components/usage/UsageBillingCell.vue index 5d188591b..b94e34fbe 100644 --- a/frontend/src/components/usage/UsageBillingCell.vue +++ b/frontend/src/components/usage/UsageBillingCell.vue @@ -21,10 +21,26 @@ const billingItems = computed(() => { return [] return [ - { label: t('usage.billing.serviceLevel'), value: value.serviceTierDisplay, tone: 'info' }, - { label: t('settings.pricing.multiplier'), value: value.multiplierDisplay, tone: 'info' }, - { label: t('usage.billing.totalCost'), value: value.totalAmountDisplay, tone: 'success' }, - { label: t('usage.billing.standardFee'), value: value.standardAmountDisplay, tone: 'default' }, + { + label: t('usage.billing.serviceLevel'), + value: value.serviceTierDisplay, + tone: 'info', + }, + { + label: t('settings.pricing.multiplier'), + value: value.multiplierDisplay, + tone: 'info', + }, + { + label: t('usage.billing.totalCost'), + value: value.totalAmountDisplay, + tone: 'success', + }, + { + label: t('usage.billing.standardFee'), + value: value.standardAmountDisplay, + tone: 'default', + }, ] }) @@ -34,21 +50,75 @@ const amountItems = computed(() => { return [] return [ - { label: value.image ? t('usage.billing.textInputCost') : t('usage.billing.inputCost'), value: value.inputAmountDisplay, accent: false }, + { + label: value.image + ? t('usage.billing.textInputCost') + : t('usage.billing.inputCost'), + value: value.inputAmountDisplay, + accent: false, + }, ...(value.image ? [ - { label: t('usage.billing.imageInputFee'), value: value.image.inputAmountDisplay, accent: false }, - { label: t('usage.billing.imageCachingCharges'), value: value.image.cacheReadAmountDisplay, accent: false }, - { label: t('usage.billing.imageInputUnitPrice'), value: value.image.inputPriceDisplay, accent: true }, - { label: t('usage.billing.imageCacheUnitPrice'), value: value.image.cacheReadPriceDisplay, accent: true }, + { + label: t('usage.billing.imageInputFee'), + value: value.image.inputAmountDisplay, + accent: false, + }, + { + label: t('usage.billing.imageCachingCharges'), + value: value.image.cacheReadAmountDisplay, + accent: false, + }, + { + label: t('usage.billing.imageInputUnitPrice'), + value: value.image.inputPriceDisplay, + accent: true, + }, + { + label: t('usage.billing.imageCacheUnitPrice'), + value: value.image.cacheReadPriceDisplay, + accent: true, + }, ] : []), - { label: value.image ? t('usage.billing.imageOutputFee') : t('usage.billing.outputFee'), value: value.outputAmountDisplay, accent: false }, - { label: value.image ? t('usage.billing.textInputUnitPrice') : t('usage.billing.inputUnitPrice'), value: value.inputPriceDisplay, accent: true }, - { label: value.image ? t('usage.billing.imageOutputUnitPrice') : t('usage.billing.outputUnitPrice'), value: value.outputPriceDisplay, accent: true }, - { label: value.image ? t('usage.billing.textCacheReadCost') : t('usage.billing.cacheReadCost'), value: value.cacheReadAmountDisplay, accent: false }, - { label: t('usage.billing.cacheWriteCost'), value: value.cacheWriteAmountDisplay, accent: false }, - { label: t('usage.billing.cacheWriteUnitPrice'), value: value.cacheWritePriceDisplay, accent: true }, + { + label: value.image + ? t('usage.billing.imageOutputFee') + : t('usage.billing.outputFee'), + value: value.outputAmountDisplay, + accent: false, + }, + { + label: value.image + ? t('usage.billing.textInputUnitPrice') + : t('usage.billing.inputUnitPrice'), + value: value.inputPriceDisplay, + accent: true, + }, + { + label: value.image + ? t('usage.billing.imageOutputUnitPrice') + : t('usage.billing.outputUnitPrice'), + value: value.outputPriceDisplay, + accent: true, + }, + { + label: value.image + ? t('usage.billing.textCacheReadCost') + : t('usage.billing.cacheReadCost'), + value: value.cacheReadAmountDisplay, + accent: false, + }, + { + label: t('usage.billing.cacheWriteCost'), + value: value.cacheWriteAmountDisplay, + accent: false, + }, + { + label: t('usage.billing.cacheWriteUnitPrice'), + value: value.cacheWritePriceDisplay, + accent: true, + }, ] }) diff --git a/frontend/src/components/usage/UsageRecordStatusFilter.vue b/frontend/src/components/usage/UsageRecordStatusFilter.vue index 6b477a469..12653e24d 100644 --- a/frontend/src/components/usage/UsageRecordStatusFilter.vue +++ b/frontend/src/components/usage/UsageRecordStatusFilter.vue @@ -11,9 +11,21 @@ const { t } = useI18n({ useScope: 'global' }) const model = defineModel({ required: true }) const options = computed(() => ([ - { label: t('common.status.success'), value: 'success', icon: Sun }, - { label: t('usage.filters.error'), value: 'error', icon: CloudRain }, - { label: t('common.values.all'), value: 'all', icon: List }, + { + label: t('common.status.success'), + value: 'success', + icon: Sun, + }, + { + label: t('usage.filters.error'), + value: 'error', + icon: CloudRain, + }, + { + label: t('common.values.all'), + value: 'all', + icon: List, + }, ])) diff --git a/frontend/src/components/usage/UsageTokenCell.vue b/frontend/src/components/usage/UsageTokenCell.vue index 6596240f8..554903077 100644 --- a/frontend/src/components/usage/UsageTokenCell.vue +++ b/frontend/src/components/usage/UsageTokenCell.vue @@ -17,11 +17,26 @@ const { t } = useI18n({ useScope: 'global' }) const tokenDetails = computed(() => usageTokenDetails(props.record.tokenDetails)) const tokenItems = computed(() => [ - { label: t('usage.tokens.enterToken'), value: tokenDetails.value.inputTokensDisplay }, - { label: t('usage.tokens.outputToken'), value: tokenDetails.value.outputTokensDisplay }, - { label: t('usage.tokens.cacheReadTokens'), value: tokenDetails.value.cachedInputTokensDisplay }, - { label: t('usage.tokens.cacheWriteTokens'), value: tokenDetails.value.cacheWriteInputTokensDisplay }, - { label: t('usage.tokens.reasoningToken'), value: tokenDetails.value.reasoningOutputTokensDisplay }, + { + label: t('usage.tokens.enterToken'), + value: tokenDetails.value.inputTokensDisplay, + }, + { + label: t('usage.tokens.outputToken'), + value: tokenDetails.value.outputTokensDisplay, + }, + { + label: t('usage.tokens.cacheReadTokens'), + value: tokenDetails.value.cachedInputTokensDisplay, + }, + { + label: t('usage.tokens.cacheWriteTokens'), + value: tokenDetails.value.cacheWriteInputTokensDisplay, + }, + { + label: t('usage.tokens.reasoningToken'), + value: tokenDetails.value.reasoningOutputTokensDisplay, + }, ]) diff --git a/frontend/src/components/usage/shared/columns.ts b/frontend/src/components/usage/shared/columns.ts index b0ae3078c..11bafb59a 100644 --- a/frontend/src/components/usage/shared/columns.ts +++ b/frontend/src/components/usage/shared/columns.ts @@ -6,22 +6,85 @@ import { t } from '@/i18n' import { formatProviderLabel } from '@/utils/providers' export const usageDataColumns = computed(() => ([ - { key: 'model', label: t('usage.fields.model'), kind: 'custom', size: 'lg' }, - { key: 'reasoningEffort', label: t('usage.fields.reasoningEffort'), kind: 'status', size: 'md' }, - { key: 'route', label: t('usage.fields.endpoint'), kind: 'mono' }, - { key: 'upstreamTransport', label: t('usage.fields.upstream'), kind: 'status', size: 'md' }, - { key: 'clientTransport', label: t('usage.fields.access'), kind: 'status', size: 'md' }, - { key: 'tokenDetails', label: 'TOKEN', kind: 'numeric', size: 'xl' }, - { key: 'billing', label: t('usage.fields.cost'), kind: 'numeric', size: 'xl' }, - { key: 'latency', label: t('usage.fields.latency'), kind: 'numeric', size: 'xl' }, - { key: 'performance', label: t('usage.outputThroughput'), kind: 'numeric', size: 'lg' }, - { key: 'createdAtDisplay', label: t('usage.fields.time'), kind: 'datetime' }, - { key: 'clientIp', label: 'IP', kind: 'custom', size: '3xl' }, - { key: 'userAgent', label: 'User-Agent', kind: 'custom', size: '4xl' }, + { + key: 'model', + label: t('usage.fields.model'), + kind: 'custom', + size: 'lg', + }, + { + key: 'reasoningEffort', + label: t('usage.fields.reasoningEffort'), + kind: 'status', + size: 'md', + }, + { + key: 'route', + label: t('usage.fields.endpoint'), + kind: 'mono', + }, + { + key: 'upstreamTransport', + label: t('usage.fields.upstream'), + kind: 'status', + size: 'md', + }, + { + key: 'clientTransport', + label: t('usage.fields.access'), + kind: 'status', + size: 'md', + }, + { + key: 'tokenDetails', + label: 'TOKEN', + kind: 'numeric', + size: 'xl', + }, + { + key: 'billing', + label: t('usage.fields.cost'), + kind: 'numeric', + size: 'xl', + }, + { + key: 'latency', + label: t('usage.fields.latency'), + kind: 'numeric', + size: 'xl', + }, + { + key: 'performance', + label: t('usage.outputThroughput'), + kind: 'numeric', + size: 'lg', + }, + { + key: 'createdAtDisplay', + label: t('usage.fields.time'), + kind: 'datetime', + }, + { + key: 'clientIp', + label: 'IP', + kind: 'custom', + size: '3xl', + }, + { + key: 'userAgent', + label: 'User-Agent', + kind: 'custom', + size: '4xl', + }, ] satisfies TableColumn[])) export const usageRecordColumns = computed(() => (defineTableColumns([ - { key: 'clientApiKeyName', label: t('usage.fields.keyName'), kind: 'identity', size: 'xl' }, + { + key: 'clientApiKeyName', + label: t('usage.fields.keyName'), + kind: 'identity', + size: 'xl', + }, { key: 'accountEmail', hideable: false, @@ -29,15 +92,29 @@ export const usageRecordColumns = computed(() => (defineTableColumns formatProviderLabel(typeof value === 'string' ? value : null), + formatter: (value: unknown) => formatProviderLabel(typeof value === 'string' + ? value + : null), }, ...usageDataColumns.value, - { key: 'actions', label: t('common.fields.actions'), kind: 'actions', size: 'sm', hideable: false }, + { + key: 'actions', + label: t('common.fields.actions'), + kind: 'actions', + size: 'sm', + hideable: false, + }, ]))) diff --git a/frontend/src/components/usage/shared/health.ts b/frontend/src/components/usage/shared/health.ts index 875c78d94..671f2a120 100644 --- a/frontend/src/components/usage/shared/health.ts +++ b/frontend/src/components/usage/shared/health.ts @@ -11,11 +11,26 @@ interface DashboardHealthStatusMeta { } export const healthLegend = computed(() => ([ - { status: 'no_data', label: t('usage.noValidSample') }, - { status: 'unavailable', label: t('usage.notReachable') }, - { status: 'unstable', label: t('usage.unstable') }, - { status: 'low_sample', label: t('usage.lowSample') }, - { status: 'stable', label: t('usage.stable') }, + { + status: 'no_data', + label: t('usage.noValidSample'), + }, + { + status: 'unavailable', + label: t('usage.notReachable'), + }, + { + status: 'unstable', + label: t('usage.unstable'), + }, + { + status: 'low_sample', + label: t('usage.lowSample'), + }, + { + status: 'stable', + label: t('usage.stable'), + }, ] satisfies { status: DashboardHealthStatus, label: string }[])) export const healthStatusMeta = computed>(() => ({ diff --git a/frontend/src/components/usage/shared/presenter.ts b/frontend/src/components/usage/shared/presenter.ts index fdfb2b852..63c26fd36 100644 --- a/frontend/src/components/usage/shared/presenter.ts +++ b/frontend/src/components/usage/shared/presenter.ts @@ -38,7 +38,9 @@ export function usageTokenDetails(details: UsageTokenDetails): UsageTokenDetails return { ...details, inputTokens, - inputTokensDisplay: inputTokens === null ? details.inputTokensDisplay : formatInteger(inputTokens), + inputTokensDisplay: inputTokens === null + ? details.inputTokensDisplay + : formatInteger(inputTokens), } } @@ -106,7 +108,9 @@ export function usagePerformanceDetails(record: UsagePerformanceRecord) { : null return { - throughputDisplay: throughput === null ? '—' : `${formatCompactNumber(throughput)} tok/s`, + throughputDisplay: throughput === null + ? '—' + : `${formatCompactNumber(throughput)} tok/s`, firstTokenDisplay: formatDuration(durationValue(record.firstTokenLatencyMs)), } } @@ -121,17 +125,25 @@ export function usageLatencyDetails(record: UsageLatencyRecord) { const requestItems: UsageTimingItem[] = [] if (firstTokenMs !== null && totalMs !== null && firstTokenMs <= totalMs) { - requestItems.push({ label: t('usage.status.firstOutputWaiting'), value: formatDuration(firstTokenMs) }) + requestItems.push({ + label: t('usage.status.firstOutputWaiting'), + value: formatDuration(firstTokenMs), + }) if (firstTextMs !== null && firstTextMs >= firstTokenMs && firstTextMs <= totalMs) { const beforeTextMs = firstTextMs - firstTokenMs if (beforeTextMs > 0) { requestItems.push({ - label: firstReasoningMs === firstTokenMs ? t('usage.status.reasoningToText') : t('usage.status.firstOneIsOutputToText'), + label: firstReasoningMs === firstTokenMs + ? t('usage.status.reasoningToText') + : t('usage.status.firstOneIsOutputToText'), value: formatDuration(beforeTextMs), }) } - requestItems.push({ label: t('usage.status.textToCompletion'), value: formatDuration(totalMs - firstTextMs) }) + requestItems.push({ + label: t('usage.status.textToCompletion'), + value: formatDuration(totalMs - firstTextMs), + }) } else { requestItems.push({ @@ -141,21 +153,43 @@ export function usageLatencyDetails(record: UsageLatencyRecord) { } } - requestItems.push({ label: t('usage.latency.totalLatency'), value: formatDuration(totalMs), emphasized: true }) + requestItems.push({ + label: t('usage.latency.totalLatency'), + value: formatDuration(totalMs), + emphasized: true, + }) const transportItems: UsageTimingItem[] = [ - { label: t('usage.status.admissionDecision'), value: durationValue(latencyDetails?.admissionDecisionMs) }, - { label: t('usage.status.accountSelectionWaiting'), value: durationValue(latencyDetails?.accountSelectionWaitMs) }, + { + label: t('usage.status.admissionDecision'), + value: durationValue(latencyDetails?.admissionDecisionMs), + }, + { + label: t('usage.status.accountSelectionWaiting'), + value: durationValue(latencyDetails?.accountSelectionWaitMs), + }, { label: t('usage.status.transferDecisionWaiting'), value: durationValue(latencyDetails?.transportDecisionWaitMs), }, - { label: t('usage.status.websocketConnection'), value: durationValue(latencyDetails?.wsConnectMs) }, - { label: t('usage.status.upstreamResponseHeader'), value: durationValue(latencyDetails?.upstreamHeadersMs) }, - { label: t('usage.status.firstUpstreamEvent'), value: durationValue(latencyDetails?.firstEventMs) }, + { + label: t('usage.status.websocketConnection'), + value: durationValue(latencyDetails?.wsConnectMs), + }, + { + label: t('usage.status.upstreamResponseHeader'), + value: durationValue(latencyDetails?.upstreamHeadersMs), + }, + { + label: t('usage.status.firstUpstreamEvent'), + value: durationValue(latencyDetails?.firstEventMs), + }, ] .filter(item => item.value !== null) - .map(item => ({ ...item, value: formatDuration(item.value) })) + .map(item => ({ + ...item, + value: formatDuration(item.value), + })) if ( latencyDetails?.capacityUsedSlots != null @@ -169,19 +203,37 @@ export function usageLatencyDetails(record: UsageLatencyRecord) { const upstreamDisplay = formatDuration(upstreamMs) const upstreamItems: UsageTimingItem[] = [] - if (upstreamMs !== null) - upstreamItems.push({ label: t('accounts.connectionTest.duration'), value: upstreamDisplay }) + if (upstreamMs !== null) { + upstreamItems.push({ + label: t('accounts.connectionTest.duration'), + value: upstreamDisplay, + }) + } const processingMs = durationValue(latencyDetails?.openaiProcessingMs) - if (processingMs !== null) - upstreamItems.push({ label: t('usage.status.processingTime'), value: formatDuration(processingMs) }) + if (processingMs !== null) { + upstreamItems.push({ + label: t('usage.status.processingTime'), + value: formatDuration(processingMs), + }) + } const performanceItems = [ - { label: t('usage.status.apiOverhead'), value: durationValue(latencyDetails?.upstreamApiOverheadMs) }, - { label: t('usage.status.engineTime'), value: durationValue(latencyDetails?.upstreamEngineMs) }, + { + label: t('usage.status.apiOverhead'), + value: durationValue(latencyDetails?.upstreamApiOverheadMs), + }, + { + label: t('usage.status.engineTime'), + value: durationValue(latencyDetails?.upstreamEngineMs), + }, ] for (const item of performanceItems) { - if (item.value !== null) - upstreamItems.push({ label: item.label, value: formatUpstreamDuration(item.value) }) + if (item.value !== null) { + upstreamItems.push({ + label: item.label, + value: formatUpstreamDuration(item.value), + }) + } } const sections: UsageTimingSection[] = [] @@ -192,7 +244,11 @@ export function usageLatencyDetails(record: UsageLatencyRecord) { items: upstreamItems, }) } - sections.push({ title: t('usage.status.requestObservation'), source: 'local', items: requestItems }) + sections.push({ + title: t('usage.status.requestObservation'), + source: 'local', + items: requestItems, + }) if (transportItems.length) { sections.push({ title: t('usage.status.transport'), @@ -229,7 +285,9 @@ export function usageBillingText(record: Pick) { } function durationValue(value: unknown) { - return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null + return typeof value === 'number' && Number.isFinite(value) && value >= 0 + ? value + : null } function formatUpstreamDuration(value: number) { @@ -242,5 +300,7 @@ function formatUpstreamDuration(value: number) { function upstreamResponseMs(details: UsageCommonRecord['latencyDetails']) { const value = durationValue(details?.upstreamResponseMs) - return value !== null && value > 0 ? value : null + return value !== null && value > 0 + ? value + : null } diff --git a/frontend/src/composables/useAsyncAction.ts b/frontend/src/composables/useAsyncAction.ts index 9377ce34a..d62536918 100644 --- a/frontend/src/composables/useAsyncAction.ts +++ b/frontend/src/composables/useAsyncAction.ts @@ -31,7 +31,10 @@ export function useAsyncAction(defaults: AsyncActionRunOptions = {}) { } loading.value = true - const options = { ...defaults, ...overrides } + const options = { + ...defaults, + ...overrides, + } try { const execute = async () => task() return options.minimumMs === undefined diff --git a/frontend/src/composables/useCopyText.ts b/frontend/src/composables/useCopyText.ts index df8b3994d..8986751c4 100644 --- a/frontend/src/composables/useCopyText.ts +++ b/frontend/src/composables/useCopyText.ts @@ -28,7 +28,11 @@ export function useCopyText() { ZNotification.success({ message: options.successText }) } catch (error: unknown) { - ZNotification.error({ message: options.errorFromException ? errorMessage(error, t('common.errors.copy')) : t('common.errors.copy') }) + ZNotification.error({ + message: options.errorFromException + ? errorMessage(error, t('common.errors.copy')) + : t('common.errors.copy'), + }) } } } diff --git a/frontend/src/composables/usePagedQuery.ts b/frontend/src/composables/usePagedQuery.ts index e1d51ca63..7fa739e66 100644 --- a/frontend/src/composables/usePagedQuery.ts +++ b/frontend/src/composables/usePagedQuery.ts @@ -64,7 +64,10 @@ export function usePagedQuery(options: { return false if (result.items.length === 0 && result.page.total > 0 && result.page.page > 1) { - return execute({ ...execution, page: clamp(result.page.totalPages, 1, Number.POSITIVE_INFINITY) }) + return execute({ + ...execution, + page: clamp(result.page.totalPages, 1, Number.POSITIVE_INFINITY), + }) } items.value = result.items diff --git a/frontend/src/composables/useTableSort.ts b/frontend/src/composables/useTableSort.ts index eebc22b35..d5db6f4fe 100644 --- a/frontend/src/composables/useTableSort.ts +++ b/frontend/src/composables/useTableSort.ts @@ -15,7 +15,9 @@ export function useTableSort( const { key, direction } = parsed if (typeof key === 'string' && (direction === 'asc' || direction === 'desc') - && toValue(source).some(column => column.sortable && (column.sortable === true ? column.key : column.sortable) === key)) { + && toValue(source).some(column => column.sortable && (column.sortable === true + ? column.key + : column.sortable) === key)) { return { key, direction } } } diff --git a/frontend/src/i18n/locale.ts b/frontend/src/i18n/locale.ts index 04cc80482..d0442c9bf 100644 --- a/frontend/src/i18n/locale.ts +++ b/frontend/src/i18n/locale.ts @@ -20,7 +20,9 @@ export function normalizeLocale(language: string): Locale { return 'zh-Hant' if (script === 'Hans') return 'zh-Hans' - return region && ['TW', 'HK', 'MO'].includes(region) ? 'zh-Hant' : 'zh-Hans' + return region && ['TW', 'HK', 'MO'].includes(region) + ? 'zh-Hant' + : 'zh-Hans' } } catch { diff --git a/frontend/src/layout/components/AppSidebar.vue b/frontend/src/layout/components/AppSidebar.vue index 0e80d0b50..3d7d4aff5 100644 --- a/frontend/src/layout/components/AppSidebar.vue +++ b/frontend/src/layout/components/AppSidebar.vue @@ -74,15 +74,51 @@ const pluginMenuId = useId() const pluginGroupExpanded = shallowRef(route.path.startsWith('/plugins')) const navItems = computed(() => ([ - { label: t('navigation.overview'), icon: LayoutDashboard, path: '/' }, - { label: t('navigation.accounts'), icon: Users, path: '/accounts' }, - { label: t('navigation.proxies'), icon: Network, path: '/proxies' }, - { label: t('navigation.groups'), icon: FolderTree, path: '/groups' }, - { label: t('navigation.apiKeys'), icon: KeyRound, path: '/keys' }, - { label: t('navigation.usage'), icon: ChartNoAxesColumn, path: '/usage' }, - { label: t('navigation.plugins'), icon: Puzzle, path: '/plugins' }, - { label: t('navigation.appearance'), icon: Palette, path: '/theme' }, - { label: t('navigation.settings'), icon: Settings, path: '/settings' }, + { + label: t('navigation.overview'), + icon: LayoutDashboard, + path: '/', + }, + { + label: t('navigation.accounts'), + icon: Users, + path: '/accounts', + }, + { + label: t('navigation.proxies'), + icon: Network, + path: '/proxies', + }, + { + label: t('navigation.groups'), + icon: FolderTree, + path: '/groups', + }, + { + label: t('navigation.apiKeys'), + icon: KeyRound, + path: '/keys', + }, + { + label: t('navigation.usage'), + icon: ChartNoAxesColumn, + path: '/usage', + }, + { + label: t('navigation.plugins'), + icon: Puzzle, + path: '/plugins', + }, + { + label: t('navigation.appearance'), + icon: Palette, + path: '/theme', + }, + { + label: t('navigation.settings'), + icon: Settings, + path: '/settings', + }, ])) const pluginNavIndex = navItems.value.findIndex(item => item.path === '/plugins') const pluginMenuVisible = computed(() => pluginGroupExpanded.value && !isCollapsed.value) @@ -106,15 +142,21 @@ const activeNavIndex = computed(() => { return Math.max(0, index) }) const activeNavIndicatorStyle = computed(() => ({ - transform: `translate3d(0, ${activeNavIndex.value * 58 + (activeNavIndex.value > pluginNavIndex ? pluginChildrenHeight.value : 0)}px, 0)`, - opacity: isActive('/plugins') && pluginMenuVisible.value ? 0 : 1, + transform: `translate3d(0, ${activeNavIndex.value * 58 + (activeNavIndex.value > pluginNavIndex + ? pluginChildrenHeight.value + : 0)}px, 0)`, + opacity: isActive('/plugins') && pluginMenuVisible.value + ? 0 + : 1, })) const activePluginIndex = computed(() => route.name === 'plugins' ? 0 : pluginPageViews.value.findIndex(isPluginViewActive) + 1) const activePluginIndicatorStyle = computed(() => ({ transform: `translate3d(0, ${activePluginIndex.value * 44}px, 0)`, - opacity: isActive('/plugins') && pluginMenuVisible.value ? 1 : 0, + opacity: isActive('/plugins') && pluginMenuVisible.value + ? 1 + : 0, })) const navFeedbackMuted = shallowRef(false) const { start: restoreNavFeedback, stop: stopNavFeedbackRestore } = useTimeoutFn( @@ -153,7 +195,9 @@ function navigatePluginView(view: PluginManagementView) { function isPluginViewActive(view: PluginManagementView) { const value = route.params.instanceId - const routeInstanceId = Array.isArray(value) ? value[0] : value + const routeInstanceId = Array.isArray(value) + ? value[0] + : value return route.name === 'plugin-page' && routeInstanceId === view.target.instanceId } @@ -162,7 +206,9 @@ function pluginViewLabel(view: PluginManagementView) { if (pluginViewNameCounts.value.get(title) === 1) return title const duplicateName = pluginPageViews.value.some(item => item !== view && item.name === view.name && item.pages[0]?.title === title) - return `${title} · ${duplicateName ? shortPluginInstanceId(view.target.instanceId) : view.name}` + return `${title} · ${duplicateName + ? shortPluginInstanceId(view.target.instanceId) + : view.name}` } function openSystemUpdate() { @@ -184,11 +230,15 @@ const collapsedSidebarWidth = 88 const expandedSidebarWidth = 251 const sidebarWidth = computed(() => (isCollapsed.value ? collapsedSidebarWidth : expandedSidebarWidth)) const brandLabelVisible = shallowRef(!isCollapsed.value) -const themeToggleLabel = computed(() => (effectiveTheme.value === 'dark' ? t('appearance.switchToLight') : t('appearance.switchToDark'))) +const themeToggleLabel = computed(() => (effectiveTheme.value === 'dark' + ? t('appearance.switchToLight') + : t('appearance.switchToDark'))) const versionText = computed(() => version.value?.version.trim() ?? '') const hasVersionLabel = computed(() => versionText.value.length > 0) const versionLabel = computed(() => `v${versionText.value}`) -const updateButtonLabel = computed(() => (hasUpdate.value ? t('navigation.update.available') : t('navigation.update.open'))) +const updateButtonLabel = computed(() => (hasUpdate.value + ? t('navigation.update.available') + : t('navigation.update.open'))) function prefersReducedMotion() { return preferredMotion.value === 'reduce' @@ -204,14 +254,18 @@ function animateSidebarLabels(collapsed: boolean) { if (prefersReducedMotion()) { gsap.set(labels, { opacity: collapsed ? 0 : 1, - x: collapsed ? -6 : 0, + x: collapsed + ? -6 + : 0, }) return } gsap.to(labels, { opacity: collapsed ? 0 : 1, - x: collapsed ? -6 : 0, + x: collapsed + ? -6 + : 0, duration: collapsed ? 0.16 : 0.2, ease: collapsed ? 'power2.in' : 'power3.out', stagger: collapsed ? 0 : 0.018, @@ -295,7 +349,9 @@ function animateSidebarWidth(collapsed: boolean) { } function animateNavSignal() { - const signal = isActive('/plugins') && pluginMenuVisible.value ? pluginNavSignalEls.value?.[0] : navSignalEl.value + const signal = isActive('/plugins') && pluginMenuVisible.value + ? pluginNavSignalEls.value?.[0] + : navSignalEl.value if (!signal) return @@ -371,7 +427,12 @@ watch( onBeforeUnmount(() => { stopNavFeedbackRestore() - const targets = [sidebarEl.value, brandLabelEl.value, navSignalEl.value, ...(pluginNavSignalEls.value ?? [])].filter((target): target is HTMLElement => + const targets = [ + sidebarEl.value, + brandLabelEl.value, + navSignalEl.value, + ...(pluginNavSignalEls.value ?? []), + ].filter((target): target is HTMLElement => Boolean(target), ) gsap.killTweensOf(targets) diff --git a/frontend/src/layout/components/FloatingSidebarToggle.vue b/frontend/src/layout/components/FloatingSidebarToggle.vue index 28c6c03ba..f55ce5049 100644 --- a/frontend/src/layout/components/FloatingSidebarToggle.vue +++ b/frontend/src/layout/components/FloatingSidebarToggle.vue @@ -148,7 +148,9 @@ function restorePosition() { return } - const sideX = storedPosition.value.side === 'right' ? bounds.value.maxX : bounds.value.minX + const sideX = storedPosition.value.side === 'right' + ? bounds.value.maxX + : bounds.value.minX applyPosition({ initialized: true, side: storedPosition.value.side, @@ -159,12 +161,16 @@ function restorePosition() { function snapPosition(rawX: number, rawY: number) { const { minX, maxX, minY, maxY } = bounds.value - const side = rawX <= (minX + maxX) / 2 ? 'left' : 'right' + const side = rawX <= (minX + maxX) / 2 + ? 'left' + : 'right' return { initialized: true, side, - x: side === 'left' ? minX : maxX, + x: side === 'left' + ? minX + : maxX, y: clamp(rawY, minY, maxY), } satisfies StoredPosition } @@ -184,12 +190,16 @@ function applyPosition(position: StoredPosition) { } function positiveNumber(value: number, fallback: number) { - return Number.isFinite(value) && value > 0 ? value : fallback + return Number.isFinite(value) && value > 0 + ? value + : fallback } function parseCssPixel(value: string) { const parsed = Number.parseFloat(value) - return Number.isFinite(parsed) ? parsed : 0 + return Number.isFinite(parsed) + ? parsed + : 0 } onMounted(async () => { diff --git a/frontend/src/layout/components/system-update-modal/index.vue b/frontend/src/layout/components/system-update-modal/index.vue index 45a59847e..edaa79319 100644 --- a/frontend/src/layout/components/system-update-modal/index.vue +++ b/frontend/src/layout/components/system-update-modal/index.vue @@ -88,15 +88,36 @@ const updateLogRows = computed(() => ) const channelLabels = computed(() => ({ - stable: { label: t('systemUpdate.channels.stable.label'), description: t('systemUpdate.channels.stable.description') }, - rc: { label: t('systemUpdate.channels.rc.label'), description: t('systemUpdate.channels.rc.description') }, - beta: { label: t('systemUpdate.channels.beta.label'), description: t('systemUpdate.channels.beta.description') }, - alpha: { label: t('systemUpdate.channels.alpha.label'), description: t('systemUpdate.channels.alpha.description') }, - exp: { label: t('systemUpdate.channels.experimental.label'), description: t('systemUpdate.channels.experimental.description') }, + stable: { + label: t('systemUpdate.channels.stable.label'), + description: t('systemUpdate.channels.stable.description'), + }, + rc: { + label: t('systemUpdate.channels.rc.label'), + description: t('systemUpdate.channels.rc.description'), + }, + beta: { + label: t('systemUpdate.channels.beta.label'), + description: t('systemUpdate.channels.beta.description'), + }, + alpha: { + label: t('systemUpdate.channels.alpha.label'), + description: t('systemUpdate.channels.alpha.description'), + }, + exp: { + label: t('systemUpdate.channels.experimental.label'), + description: t('systemUpdate.channels.experimental.description'), + }, })) const channelOptions = computed(() => availableChannels.value.map(value => ({ value, - label: { stable: 'Stable', rc: 'RC', beta: 'Beta', alpha: 'Alpha', exp: 'Exp' }[value], + label: { + stable: 'Stable', + rc: 'RC', + beta: 'Beta', + alpha: 'Alpha', + exp: 'Exp', + }[value], }))) async function handleChannelChange(value: string) { @@ -133,7 +154,11 @@ async function handleCheckUpdates(force = true) { ZNotification.error({ message: data.warning }) return } - ZNotification.success({ message: data?.hasUpdate ? t('systemUpdate.feedback.available') : t('systemUpdate.feedback.noUpdates') }) + ZNotification.success({ + message: data?.hasUpdate + ? t('systemUpdate.feedback.available') + : t('systemUpdate.feedback.noUpdates'), + }) } catch {} } @@ -151,12 +176,16 @@ async function handleUpdateRequest() { return } if (!data?.hasUpdate || !canUpdate.value) { - ZNotification.success({ message: t('systemUpdate.feedback.noUpdates') }) + ZNotification.success({ + message: t('systemUpdate.feedback.noUpdates'), + }) return } const remoteTargetVersion = normalizeSystemVersion(data.latestVersion) if (!remoteTargetVersion) { - ZNotification.error({ message: t('systemUpdate.errors.remoteTargetMissing') }) + ZNotification.error({ + message: t('systemUpdate.errors.remoteTargetMissing'), + }) return } if (previousTargetVersion && previousTargetVersion !== remoteTargetVersion) { @@ -177,7 +206,9 @@ async function runConfirmedUpdate(targetVersion: string, channel: SystemUpdateCh try { const result = await updateNow(targetVersion, channel) if (result) { - ZNotification.success({ message: t('systemUpdate.feedback.started') }) + ZNotification.success({ + message: t('systemUpdate.feedback.started'), + }) } } catch {} @@ -209,7 +240,9 @@ async function handleRestart() { await restartNow(restartPlan.value) } catch (error: unknown) { - ZNotification.error({ message: errorMessage(error, t('systemUpdate.errors.preRestartCheck')) }) + ZNotification.error({ + message: errorMessage(error, t('systemUpdate.errors.preRestartCheck')), + }) } finally { checkingRestart.value = false @@ -225,7 +258,9 @@ async function handleConfirmRestart() { } catch (error: unknown) { restartConfirmOpen.value = false - ZNotification.error({ message: errorMessage(error, t('systemUpdate.errors.restart')) }) + ZNotification.error({ + message: errorMessage(error, t('systemUpdate.errors.restart')), + }) } } diff --git a/frontend/src/layout/components/system-update-modal/presenter.ts b/frontend/src/layout/components/system-update-modal/presenter.ts index 778cb55ca..e1ba0e955 100644 --- a/frontend/src/layout/components/system-update-modal/presenter.ts +++ b/frontend/src/layout/components/system-update-modal/presenter.ts @@ -26,8 +26,12 @@ export function resolveSystemUpdatePresentation(input: SystemUpdatePresentationI releaseVersion: displayValue(input.updateInfo?.latestVersion), streamStatusLabel: input.updateStreaming ? t('systemUpdate.logs.live') - : input.updateStreamError ? t('systemUpdate.logs.disconnectedState') : t('systemUpdate.logs.waiting'), - restartButtonLabel: input.restarting ? t('systemUpdate.status.restarting') : t('systemUpdate.actions.restart'), + : input.updateStreamError + ? t('systemUpdate.logs.disconnectedState') + : t('systemUpdate.logs.waiting'), + restartButtonLabel: input.restarting + ? t('systemUpdate.status.restarting') + : t('systemUpdate.actions.restart'), } } @@ -47,7 +51,11 @@ export function resolveSystemUpdateLogClasses(level: string) { function resolveStatus(input: SystemUpdatePresentationInput) { if (input.restarting || input.updating || input.checking || input.loading) { return { - label: input.restarting ? t('systemUpdate.status.restarting') : input.updating ? t('systemUpdate.status.updating') : t('systemUpdate.status.checking'), + label: input.restarting + ? t('systemUpdate.status.restarting') + : input.updating + ? t('systemUpdate.status.updating') + : t('systemUpdate.status.checking'), icon: RefreshCw, badge: 'bg-cp-info-container text-cp-info-on-container', iconClass: 'text-cp-info', @@ -63,7 +71,11 @@ function resolveStatus(input: SystemUpdatePresentationInput) { } if (input.needRestart || input.hasUpdate || input.updateInfo) { return { - label: input.needRestart ? t('systemUpdate.status.restartRequired') : input.hasUpdate ? t('systemUpdate.status.available') : t('systemUpdate.status.upToDate'), + label: input.needRestart + ? t('systemUpdate.status.restartRequired') + : input.hasUpdate + ? t('systemUpdate.status.available') + : t('systemUpdate.status.upToDate'), icon: input.hasUpdate ? ArrowUpCircle : CheckCircle2, badge: 'bg-cp-success-container text-cp-success-on-container', iconClass: 'text-cp-success', @@ -82,7 +94,9 @@ function resolveSummaryItems(input: SystemUpdatePresentationInput) { { key: 'current', label: t('systemUpdate.currentVersion'), - value: input.loading ? '...' : versionLabel(input.updateInfo?.currentVersion ?? input.version?.version), + value: input.loading + ? '...' + : versionLabel(input.updateInfo?.currentVersion ?? input.version?.version), title: input.updateInfo?.currentVersion ?? input.version?.version, }, { @@ -109,9 +123,21 @@ function resolveSummaryItems(input: SystemUpdatePresentationInput) { function resolveConfirmRows(input: SystemUpdatePresentationInput) { return [ - { key: 'current', label: t('systemUpdate.currentVersion'), value: versionLabel(input.version?.version) }, - { key: 'previous', label: t('systemUpdate.displayedTarget'), value: versionLabel(input.previousTargetVersion) }, - { key: 'target', label: t('systemUpdate.remoteTarget'), value: versionLabel(input.confirmedTargetVersion) }, + { + key: 'current', + label: t('systemUpdate.currentVersion'), + value: versionLabel(input.version?.version), + }, + { + key: 'previous', + label: t('systemUpdate.displayedTarget'), + value: versionLabel(input.previousTargetVersion), + }, + { + key: 'target', + label: t('systemUpdate.remoteTarget'), + value: versionLabel(input.confirmedTargetVersion), + }, ] } @@ -135,9 +161,13 @@ function deploymentModeLabel(value?: string) { } function versionLabel(value?: string | null) { - return value ? `v${value}` : '-' + return value + ? `v${value}` + : '-' } function displayValue(value: unknown) { - return value ? String(value) : '-' + return value + ? String(value) + : '-' } diff --git a/frontend/src/plugins/auth.ts b/frontend/src/plugins/auth.ts index ad69957b0..8187596f8 100644 --- a/frontend/src/plugins/auth.ts +++ b/frontend/src/plugins/auth.ts @@ -11,8 +11,12 @@ export const authPlugin: Plugin = { async function redirectToLogin(loginMode = authStore.session?.role ?? 'admin') { authStore.invalidateSession() - if (router.currentRoute.value.name !== 'login') - await router.replace({ name: 'login', state: { loginMode } }) + if (router.currentRoute.value.name !== 'login') { + await router.replace({ + name: 'login', + state: { loginMode }, + }) + } } setUnauthorizedHandler(() => redirectToLogin()) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 834743877..7d701ab22 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -43,7 +43,9 @@ router.beforeEach(async (to, from) => { } catch { // 首次进入时没有页面可保留,转到登录页;已有页面则保留,且不清除会话。 - return from === START_LOCATION ? login : false + return from === START_LOCATION + ? login + : false } } diff --git a/frontend/src/stores/modules/auth.ts b/frontend/src/stores/modules/auth.ts index 5280a650c..caad1b97f 100644 --- a/frontend/src/stores/modules/auth.ts +++ b/frontend/src/stores/modules/auth.ts @@ -23,7 +23,9 @@ export const useAuthStore = defineStore('auth', () => { return pendingCheck.value const check = loadSession(async () => { const status = await getAuthStatus() - return status.authenticated ? status : refreshAuthSession() + return status.authenticated + ? status + : refreshAuthSession() }).finally(() => { if (pendingCheck.value === check) pendingCheck.value = undefined @@ -108,5 +110,18 @@ export const useAuthStore = defineStore('auth', () => { resetUnauthorizedHandling() } - return { session, sessionGeneration, isAuthenticated, isAdmin, sessionChecked, loading, checking, checkAuth, refreshSession, login, logout, invalidateSession } + return { + session, + sessionGeneration, + isAuthenticated, + isAdmin, + sessionChecked, + loading, + checking, + checkAuth, + refreshSession, + login, + logout, + invalidateSession, + } }) diff --git a/frontend/src/stores/modules/system-update.ts b/frontend/src/stores/modules/system-update.ts index d2d13682d..bb408af81 100644 --- a/frontend/src/stores/modules/system-update.ts +++ b/frontend/src/stores/modules/system-update.ts @@ -106,7 +106,9 @@ export const useSystemUpdateStore = defineStore('system-update', () => { }) const needRestart = computed(() => Boolean(updateStatus.value?.needRestart)) - const restartTargetVersion = computed(() => needRestart.value ? normalizeSystemVersion(updateStatus.value?.currentVersion) : '') + const restartTargetVersion = computed(() => needRestart.value + ? normalizeSystemVersion(updateStatus.value?.currentVersion) + : '') const selectedChannel = computed(() => policy.value?.channel ?? 'stable') const availableChannels = computed(() => policy.value?.availableChannels ?? []) const canChangeChannel = computed(() => availableChannels.value.length > 1 @@ -274,7 +276,11 @@ export const useSystemUpdateStore = defineStore('system-update', () => { } function settlePhase() { - setPhase(needRestart.value ? { kind: 'restart_required' } : updateError.value ? { kind: 'failed' } : { kind: 'ready' }) + setPhase(needRestart.value + ? { kind: 'restart_required' } + : updateError.value + ? { kind: 'failed' } + : { kind: 'ready' }) } async function loadDetail(refresh: boolean, generation: number, channel?: SystemUpdateChannel) { @@ -289,7 +295,13 @@ export const useSystemUpdateStore = defineStore('system-update', () => { return if (version.value && detail.policy.channel === version.value.updateChannel) { // 侧栏只提示当前运行通道的更新,临时查看其他通道不会改变全局提示。 - version.value = { ...version.value, latestVersion: detail.latestVersion, hasUpdate: detail.hasUpdate, updateCached: detail.cached, updateWarning: detail.warning } + version.value = { + ...version.value, + latestVersion: detail.latestVersion, + hasUpdate: detail.hasUpdate, + updateCached: detail.cached, + updateWarning: detail.warning, + } } } diff --git a/frontend/src/stores/modules/theme.ts b/frontend/src/stores/modules/theme.ts index 420d8ecdc..c51e274a3 100644 --- a/frontend/src/stores/modules/theme.ts +++ b/frontend/src/stores/modules/theme.ts @@ -269,7 +269,9 @@ export const useThemeStore = defineStore( if (themeTransitioning.value) return requestThemeTransition(event) - themeMode.value = effectiveTheme.value === 'dark' ? 'light' : 'dark' + themeMode.value = effectiveTheme.value === 'dark' + ? 'light' + : 'dark' } function requestThemeTransition(event?: MouseEvent): void { diff --git a/frontend/src/utils/client.ts b/frontend/src/utils/client.ts index f8d74f847..9cbd3f783 100644 --- a/frontend/src/utils/client.ts +++ b/frontend/src/utils/client.ts @@ -91,7 +91,10 @@ export function buildCodexCcSwitchImportDeeplink(input: CodexCcSwitchImportInput ['endpoint', baseUrl], ['apiKey', input.apiKey], ['usageEnabled', 'true'], - ['usageScript', encodeBase64(buildUsageScript(input.apiKey, baseUrl))], + [ + 'usageScript', + encodeBase64(buildUsageScript(input.apiKey, baseUrl)), + ], ['usageAutoInterval', '30'], ] @@ -116,5 +119,7 @@ export function resolveServiceRootUrl() { const origin = window.location.origin.replace(/\/+$/, '') if (!normalizedApiBase) return origin - return `${origin}${normalizedApiBase.startsWith('/') ? normalizedApiBase : `/${normalizedApiBase}`}` + return `${origin}${normalizedApiBase.startsWith('/') + ? normalizedApiBase + : `/${normalizedApiBase}`}` } diff --git a/frontend/src/utils/data.ts b/frontend/src/utils/data.ts index 005249d7f..63325bf4e 100644 --- a/frontend/src/utils/data.ts +++ b/frontend/src/utils/data.ts @@ -14,11 +14,22 @@ export function parseJsonObject(text: string, maximumBytes?: number): JsonObject value = JSON.parse(text) } catch { - return { value: undefined, error: t('common.validation.invalidJson') } + return { + value: undefined, + error: t('common.validation.invalidJson'), + } + } + if (!isRecord(value)) { + return { + value: undefined, + error: t('common.validation.jsonObject'), + } + } + if (maximumBytes !== undefined && new TextEncoder().encode(JSON.stringify(value)).byteLength > maximumBytes) { + return { + value: undefined, + error: t('common.validation.sizeLimit', { value: maximumBytes / 1024 }), + } } - if (!isRecord(value)) - return { value: undefined, error: t('common.validation.jsonObject') } - if (maximumBytes !== undefined && new TextEncoder().encode(JSON.stringify(value)).byteLength > maximumBytes) - return { value: undefined, error: t('common.validation.sizeLimit', { value: maximumBytes / 1024 }) } return { value, error: undefined } } diff --git a/frontend/src/utils/format.ts b/frontend/src/utils/format.ts index b6e83a155..fc76587d2 100644 --- a/frontend/src/utils/format.ts +++ b/frontend/src/utils/format.ts @@ -1,7 +1,9 @@ import { locale } from '@/i18n' export function numberLocale() { - return locale.value === 'en' ? 'en-US' : locale.value + return locale.value === 'en' + ? 'en-US' + : locale.value } const metricUnits = [ @@ -20,7 +22,9 @@ export function formatLocalizedCompactNumber(value: number) { return new Intl.NumberFormat(numberLocale(), { notation: 'compact', maximumFractionDigits: 1, - }).format(Number.isFinite(value) ? value : 0) + }).format(Number.isFinite(value) + ? value + : 0) } export function formatCompactNumber(value: number) { @@ -33,7 +37,9 @@ export function formatCompactNumber(value: number) { continue const scaled = normalized / threshold - const rounded = scaled >= 10 ? scaled.toFixed(1) : scaled.toFixed(2) + const rounded = scaled >= 10 + ? scaled.toFixed(1) + : scaled.toFixed(2) return `${rounded.replace(/\.?0+$/, '')}${unit}` } @@ -53,7 +59,9 @@ export function formatDuration(value?: number | null) { return `${Math.round(value)} ms` if (value < 60_000) { const seconds = value / 1_000 - return `${seconds.toFixed(seconds >= 10 ? 1 : 2).replace(/\.0+$|(?<=\.\d)0$/, '')} s` + return `${seconds.toFixed(seconds >= 10 + ? 1 + : 2).replace(/\.0+$|(?<=\.\d)0$/, '')} s` } return `${(value / 60_000).toFixed(1).replace(/\.0$/, '')} min` } @@ -61,15 +69,21 @@ export function formatDuration(value?: number | null) { export function decimalDisplayNumber(value?: string | number | null) { if (value == null) return null - const parsed = typeof value === 'number' ? value : Number(value) - return Number.isFinite(parsed) ? parsed : null + const parsed = typeof value === 'number' + ? value + : Number(value) + return Number.isFinite(parsed) + ? parsed + : null } export function formatUsd(value?: string | number | null, precise = false) { const parsed = decimalDisplayNumber(value) if (parsed == null) return '—' - const fractionDigits = precise || (Math.abs(parsed) > 0 && Math.abs(parsed) < 0.01) ? 4 : 2 + const fractionDigits = precise || (Math.abs(parsed) > 0 && Math.abs(parsed) < 0.01) + ? 4 + : 2 return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', diff --git a/frontend/src/utils/operation.ts b/frontend/src/utils/operation.ts index 9607ea20b..ccadc2188 100644 --- a/frontend/src/utils/operation.ts +++ b/frontend/src/utils/operation.ts @@ -10,7 +10,9 @@ export function errorMessage(error: unknown, fallback = t('common.errors.request : error && typeof error === 'object' && 'message' in error ? (error as { message?: unknown }).message : undefined - return typeof message === 'string' && message ? message : fallback + return typeof message === 'string' && message + ? message + : fallback } export async function withMinimumDuration( @@ -19,7 +21,9 @@ export async function withMinimumDuration( ): Promise { const startedAt = Date.now() try { - return await (typeof task === 'function' ? task() : task) + return await (typeof task === 'function' + ? task() + : task) } finally { const remaining = minimumMs - (Date.now() - startedAt) diff --git a/frontend/src/utils/providers.ts b/frontend/src/utils/providers.ts index bb8154f05..1b2c8c189 100644 --- a/frontend/src/utils/providers.ts +++ b/frontend/src/utils/providers.ts @@ -19,7 +19,9 @@ export function isSupportedProvider(value: unknown): value is SupportedProvider export function formatProviderLabel(value?: string | null, fallback = '—') { const normalized = value?.trim() const provider = normalized?.toLowerCase() - return isSupportedProvider(provider) ? PROVIDER_DISPLAY_NAMES[provider] : (normalized || fallback) + return isSupportedProvider(provider) + ? PROVIDER_DISPLAY_NAMES[provider] + : (normalized || fallback) } export function providerIcon(value: string): Component { @@ -35,7 +37,9 @@ export function formatAuthenticationLabel(value?: string | null) { switch (normalized?.toLowerCase()) { case 'oauth': return 'OAuth' case 'api_key': return t('common.fields.apiKey') - default: return normalized ? t('common.providers.customAuth', { normalized }) : t('common.providers.unknownAuth') + default: return normalized + ? t('common.providers.customAuth', { normalized }) + : t('common.providers.unknownAuth') } } diff --git a/frontend/src/views/accounts/components/AccountApiKeyFields.vue b/frontend/src/views/accounts/components/AccountApiKeyFields.vue index 45a89a35c..869b68491 100644 --- a/frontend/src/views/accounts/components/AccountApiKeyFields.vue +++ b/frontend/src/views/accounts/components/AccountApiKeyFields.vue @@ -13,7 +13,10 @@ const { t } = useI18n({ useScope: 'global' }) const form = defineModel({ required: true }) const transportOptions = computed(() => ([ { label: 'HTTP / SSE', value: 'http' }, - { label: t('accounts.credentials.wsFirst'), value: 'prefer_websocket' }, + { + label: t('accounts.credentials.wsFirst'), + value: 'prefer_websocket', + }, ])) diff --git a/frontend/src/views/accounts/components/AccountConcurrencyTable.vue b/frontend/src/views/accounts/components/AccountConcurrencyTable.vue index 5af0e0fb3..ed4e6cd40 100644 --- a/frontend/src/views/accounts/components/AccountConcurrencyTable.vue +++ b/frontend/src/views/accounts/components/AccountConcurrencyTable.vue @@ -17,11 +17,37 @@ defineProps<{ const { t } = useI18n({ useScope: 'global' }) const columns = computed(() => (defineTableColumns([ - { key: 'key', label: t('accounts.concurrency.key'), kind: 'identity', size: '2xl' }, - { key: 'model', label: t('accounts.concurrency.model'), kind: 'mono', size: 'lg' }, - { key: 'clientIp', label: t('accounts.concurrency.sourceIp'), kind: 'custom', size: '4xl' }, - { key: 'startedAtDisplay', label: t('accounts.concurrency.startTime'), kind: 'datetime', size: 'xl' }, - { key: 'occupiedMs', label: t('accounts.concurrency.elapsed'), kind: 'numeric', size: 'lg', formatter: (_, row) => formatDuration(row.occupiedMs) }, + { + key: 'key', + label: t('accounts.concurrency.key'), + kind: 'identity', + size: '2xl', + }, + { + key: 'model', + label: t('accounts.concurrency.model'), + kind: 'mono', + size: 'lg', + }, + { + key: 'clientIp', + label: t('accounts.concurrency.sourceIp'), + kind: 'custom', + size: '4xl', + }, + { + key: 'startedAtDisplay', + label: t('accounts.concurrency.startTime'), + kind: 'datetime', + size: 'xl', + }, + { + key: 'occupiedMs', + label: t('accounts.concurrency.elapsed'), + kind: 'numeric', + size: 'lg', + formatter: (_, row) => formatDuration(row.occupiedMs), + }, ]))) diff --git a/frontend/src/views/accounts/components/AccountFilters.vue b/frontend/src/views/accounts/components/AccountFilters.vue index 7a1df24be..36ea29133 100644 --- a/frontend/src/views/accounts/components/AccountFilters.vue +++ b/frontend/src/views/accounts/components/AccountFilters.vue @@ -35,10 +35,18 @@ const status = defineModel('status', { required: true }) const provider = defineModel('provider', { required: true }) const group = defineModel('group', { required: true }) const groupOptions = computed(() => [ - { label: t('accounts.filters.allGroups'), value: '' }, - { label: t('accounts.filters.ungroupedAccounts'), value: 'ungrouped' }, + { + label: t('accounts.filters.allGroups'), + value: '', + }, + { + label: t('accounts.filters.ungroupedAccounts'), + value: 'ungrouped', + }, ...props.groups.map(group => ({ - label: group.enabled ? group.name : t('accounts.filters.disabled', { name: group.name }), + label: group.enabled + ? group.name + : t('accounts.filters.disabled', { name: group.name }), value: group.id, })), ]) diff --git a/frontend/src/views/accounts/components/AccountIdentityCell.vue b/frontend/src/views/accounts/components/AccountIdentityCell.vue index 19d70f46f..e13964501 100644 --- a/frontend/src/views/accounts/components/AccountIdentityCell.vue +++ b/frontend/src/views/accounts/components/AccountIdentityCell.vue @@ -36,17 +36,23 @@ const emailText = computed(() => accountIdentityLabel(props.account)) const visibleNotes = computed(() => props.showNotes ? props.account.notes : undefined) const displayTitle = computed(() => - visibleNotes.value || props.titleMode === 'email' || props.account.authenticationKind === 'api_key' ? emailText.value : emailText.value.split('@')[0], + visibleNotes.value || props.titleMode === 'email' || props.account.authenticationKind === 'api_key' + ? emailText.value + : emailText.value.split('@')[0], ) const secondaryText = computed(() => - props.titleMode === 'email' || props.account.authenticationKind === 'api_key' ? null : emailText.value, + props.titleMode === 'email' || props.account.authenticationKind === 'api_key' + ? null + : emailText.value, ) const initial = computed(() => displayTitle.value.slice(0, 1).toUpperCase()) const avatarSizeClass = computed(() => - props.size === 'lg' ? 'size-10 text-cp-xl' : 'size-9 text-cp', + props.size === 'lg' + ? 'size-10 text-cp-xl' + : 'size-9 text-cp', ) const secondaryClass = computed(() => @@ -55,7 +61,9 @@ const secondaryClass = computed(() => : 'mt-0.5 font-mono text-cp-xs text-cp-text-quaternary', ) -const metaGapClass = computed(() => props.metaSize === 'xs' ? 'gap-1' : 'gap-1.5') +const metaGapClass = computed(() => props.metaSize === 'xs' + ? 'gap-1' + : 'gap-1.5') const avatarToneClass = computed(() => { const identity = props.account.id || props.account.email || displayTitle.value diff --git a/frontend/src/views/accounts/components/AccountModelAccessField.vue b/frontend/src/views/accounts/components/AccountModelAccessField.vue index 59298f6ee..7ee8a15e2 100644 --- a/frontend/src/views/accounts/components/AccountModelAccessField.vue +++ b/frontend/src/views/accounts/components/AccountModelAccessField.vue @@ -25,10 +25,26 @@ const inputError = ref('') const request = useRequestState() const { loading, error } = request const modes = computed(() => [ - ...(props.allowPreserve ? [{ value: 'preserve', label: t('accounts.modelAccess.reserve') }] : []), - { value: 'all', label: t('common.values.unlimited') }, - { value: 'allowlist', label: t('accounts.modelAccess.allowlist') }, - { value: 'denylist', label: t('accounts.modelAccess.blocklist') }, + ...(props.allowPreserve + ? [ + { + value: 'preserve', + label: t('accounts.modelAccess.reserve'), + }, + ] + : []), + { + value: 'all', + label: t('common.values.unlimited'), + }, + { + value: 'allowlist', + label: t('accounts.modelAccess.allowlist'), + }, + { + value: 'denylist', + label: t('accounts.modelAccess.blocklist'), + }, ]) const mode = computed({ get: () => model.value?.mode ?? 'preserve', @@ -38,19 +54,31 @@ const mode = computed({ model.value = undefined return } - model.value = { mode: value as AccountModelAccess['mode'], models: value === 'all' ? [] : [...(model.value?.models ?? [])] } + model.value = { + mode: value as AccountModelAccess['mode'], + models: value === 'all' + ? [] + : [ + ...(model.value?.models ?? []), + ], + } }, }) const restricted = computed(() => mode.value === 'allowlist' || mode.value === 'denylist') const canAdd = computed(() => Boolean(search.value.trim()) && !model.value?.models.includes(search.value.trim())) const models = computed(() => { - const entries = new Map(catalog.value.map(item => [item.id, { ...item, unavailable: false }])) + const entries = new Map(catalog.value.map(item => [ + item.id, + { ...item, unavailable: false }, + ])) for (const id of model.value?.models ?? []) { if (!entries.has(id)) entries.set(id, { id, label: id, unavailable: true }) } const query = search.value.trim().toLowerCase() - return [...entries.values()].filter(item => `${item.id} ${item.label}`.toLowerCase().includes(query)) + return [ + ...entries.values(), + ].filter(item => `${item.id} ${item.label}`.toLowerCase().includes(query)) }) function select(id: string, selected: boolean) { @@ -60,8 +88,13 @@ function select(id: string, selected: boolean) { if (selected) ids.add(id) else ids.delete(id) - const next = { ...model.value, models: [...ids] } - inputError.value = selected ? accountModelAccessError(next) ?? '' : '' + const next = { + ...model.value, + models: [...ids], + } + inputError.value = selected + ? accountModelAccessError(next) ?? '' + : '' if (!inputError.value) model.value = next } @@ -99,7 +132,10 @@ async function load(refresh = false) { } } -watch([() => props.accountId, restricted], () => { +watch([ + () => props.accountId, + restricted, +], () => { request.invalidate() catalog.value = [] search.value = '' diff --git a/frontend/src/views/accounts/components/AccountProxyField.vue b/frontend/src/views/accounts/components/AccountProxyField.vue index 82c445779..973e7d229 100644 --- a/frontend/src/views/accounts/components/AccountProxyField.vue +++ b/frontend/src/views/accounts/components/AccountProxyField.vue @@ -16,34 +16,56 @@ const { proxies, loading } = useProxyCatalog() const currentSelection = computed(() => props.currentProxyId || (props.endpoint ? 'preserve' : 'direct')) const options = computed(() => [ ...(props.preserve && !props.accountId - ? [{ - label: t('accounts.proxy.keepCurrent'), - value: 'preserve', - description: t('accounts.proxy.keepExisting'), - }] + ? [ + { + label: t('accounts.proxy.keepCurrent'), + value: 'preserve', + description: t('accounts.proxy.keepExisting'), + }, + ] : []), ...(props.preserve && props.accountId && props.endpoint && !proxies.value.some(proxy => proxy.id === props.currentProxyId) - ? [{ label: props.endpoint, value: currentSelection.value }] + ? [ + { label: props.endpoint, value: currentSelection.value }, + ] : []), - { label: t('accounts.proxy.directConnection'), value: 'direct', description: t('accounts.proxy.notUsingProxy') }, + { + label: t('accounts.proxy.directConnection'), + value: 'direct', + description: t('accounts.proxy.notUsingProxy'), + }, ...proxies.value.map(proxy => ({ - label: `${proxy.name.trim() || proxy.endpoint}${!proxy.enabled ? t('accounts.proxy.disabledSuffix') : proxy.lastTest?.success ? '' : proxy.lastTest ? t('accounts.proxy.testFailed') : t('accounts.proxy.notTested')}`, + label: `${proxy.name.trim() || proxy.endpoint}${!proxy.enabled + ? t('accounts.proxy.disabledSuffix') + : proxy.lastTest?.success + ? '' + : proxy.lastTest + ? t('accounts.proxy.testFailed') + : t('accounts.proxy.notTested')}`, value: proxy.id, - description: proxy.name.trim() ? proxy.endpoint : undefined, + description: proxy.name.trim() + ? proxy.endpoint + : undefined, disabled: !proxy.enabled, })), ]) const selection = computed({ get: () => mode.value === 'proxy' ? proxyId.value - : mode.value === 'preserve' && props.accountId ? currentSelection.value : mode.value, + : mode.value === 'preserve' && props.accountId + ? currentSelection.value + : mode.value, set: (value: string) => { // 显示已绑定代理,未改选择时仍省略写入,保留禁用代理及仅地址配置 const nextMode = props.preserve && props.accountId && value === currentSelection.value ? 'preserve' - : value === 'preserve' || value === 'direct' ? value : 'proxy' + : value === 'preserve' || value === 'direct' + ? value + : 'proxy' mode.value = nextMode - proxyId.value = nextMode === 'proxy' ? value : '' + proxyId.value = nextMode === 'proxy' + ? value + : '' }, }) diff --git a/frontend/src/views/accounts/components/AccountUsagePanel.vue b/frontend/src/views/accounts/components/AccountUsagePanel.vue index 554706451..42a3afabf 100644 --- a/frontend/src/views/accounts/components/AccountUsagePanel.vue +++ b/frontend/src/views/accounts/components/AccountUsagePanel.vue @@ -22,15 +22,61 @@ const totalBillingDisplay = computed(() => totalBilling.value?.estimatedAmountDi const hasUsageSummary = computed(() => (props.account.usage.totalTokens ?? 0) > 0 || totalBilling.value !== undefined) const modelUsageColumns = computed(() => (defineTableColumns([ - { key: 'model', label: t('accounts.concurrency.model'), kind: 'text', size: 'md' }, - { key: 'requestCountDisplay', label: t('accounts.usage.call'), kind: 'numeric', size: 'xs' }, - { key: 'successRateDisplay', label: t('accounts.usage.successRate'), kind: 'numeric', size: 'sm' }, - { key: 'inputTokensDisplay', label: t('accounts.usage.input'), kind: 'numeric', size: 'xs' }, - { key: 'outputTokensDisplay', label: t('accounts.usage.output'), kind: 'numeric', size: 'xs' }, - { key: 'cachedInputTokensDisplay', label: t('accounts.usage.cache'), kind: 'numeric', size: 'xs' }, - { key: 'totalTokensDisplay', label: t('accounts.usage.total'), kind: 'numeric', size: 'xs' }, - { key: 'billingAmountUsdDisplay', label: t('accounts.usage.cost'), kind: 'numeric', size: 'sm' }, - { key: 'lastUsedAt', label: t('accounts.fields.lastUsed'), kind: 'datetime', size: 'md', align: 'right' }, + { + key: 'model', + label: t('accounts.concurrency.model'), + kind: 'text', + size: 'md', + }, + { + key: 'requestCountDisplay', + label: t('accounts.usage.call'), + kind: 'numeric', + size: 'xs', + }, + { + key: 'successRateDisplay', + label: t('accounts.usage.successRate'), + kind: 'numeric', + size: 'sm', + }, + { + key: 'inputTokensDisplay', + label: t('accounts.usage.input'), + kind: 'numeric', + size: 'xs', + }, + { + key: 'outputTokensDisplay', + label: t('accounts.usage.output'), + kind: 'numeric', + size: 'xs', + }, + { + key: 'cachedInputTokensDisplay', + label: t('accounts.usage.cache'), + kind: 'numeric', + size: 'xs', + }, + { + key: 'totalTokensDisplay', + label: t('accounts.usage.total'), + kind: 'numeric', + size: 'xs', + }, + { + key: 'billingAmountUsdDisplay', + label: t('accounts.usage.cost'), + kind: 'numeric', + size: 'sm', + }, + { + key: 'lastUsedAt', + label: t('accounts.fields.lastUsed'), + kind: 'datetime', + size: 'md', + align: 'right', + }, ]))) diff --git a/frontend/src/views/accounts/components/account-create-modal/AccountProviderChooser.vue b/frontend/src/views/accounts/components/account-create-modal/AccountProviderChooser.vue index d4e89f15f..e7d0ecd08 100644 --- a/frontend/src/views/accounts/components/account-create-modal/AccountProviderChooser.vue +++ b/frontend/src/views/accounts/components/account-create-modal/AccountProviderChooser.vue @@ -13,7 +13,11 @@ const { t } = useI18n({ useScope: 'global' }) const source = defineModel({ required: true }) const options = computed(() => ([ - { value: 'bundle', label: t('accounts.onboarding.batchImport'), icon: LayoutGrid }, + { + value: 'bundle', + label: t('accounts.onboarding.batchImport'), + icon: LayoutGrid, + }, ...PROVIDER_IDS.map(provider => ({ value: `provider:${provider}`, label: formatProviderLabel(provider), @@ -21,11 +25,18 @@ const options = computed(() => ([ })), ])) const selected = computed({ - get: () => source.value?.kind === 'provider' ? `provider:${source.value.id}` : source.value?.kind ?? '', + get: () => source.value?.kind === 'provider' + ? `provider:${source.value.id}` + : source.value?.kind ?? '', set: (value: string) => { if (!options.value.some(option => option.value === value)) return - source.value = value === 'bundle' ? { kind: 'bundle' } : { kind: 'provider', id: value.slice('provider:'.length) } + source.value = value === 'bundle' + ? { kind: 'bundle' } + : { + kind: 'provider', + id: value.slice('provider:'.length), + } }, }) diff --git a/frontend/src/views/accounts/components/account-create-modal/index.vue b/frontend/src/views/accounts/components/account-create-modal/index.vue index 2921a6635..79c748891 100644 --- a/frontend/src/views/accounts/components/account-create-modal/index.vue +++ b/frontend/src/views/accounts/components/account-create-modal/index.vue @@ -30,7 +30,12 @@ const props = withDefaults(defineProps<{ reauthorizing?: boolean account?: Account | null authorization: AccountAuthorizationView -}>(), { saving: false, oauthLoading: false, reauthorizing: false, account: null }) +}>(), { + saving: false, + oauthLoading: false, + reauthorizing: false, + account: null, +}) const emit = defineEmits<{ create: [], generateOauth: [] }>() @@ -67,7 +72,9 @@ const mode = computed({ }, }) const importText = computed({ - get: () => form.value.mode === 'oauth' || form.value.mode === 'api_key' ? '' : form.value.importTexts[form.value.mode], + get: () => form.value.mode === 'oauth' || form.value.mode === 'api_key' + ? '' + : form.value.importTexts[form.value.mode], set: (value: string) => { if (form.value.mode !== 'oauth' && form.value.mode !== 'api_key') form.value.importTexts[form.value.mode] = value diff --git a/frontend/src/views/accounts/components/account-create-modal/presenter.ts b/frontend/src/views/accounts/components/account-create-modal/presenter.ts index c70b99885..2c90a5650 100644 --- a/frontend/src/views/accounts/components/account-create-modal/presenter.ts +++ b/frontend/src/views/accounts/components/account-create-modal/presenter.ts @@ -48,24 +48,56 @@ export function resolveAccountCreatePresentation(input: { modeOptions: modes, available, modal: { - title: configuring ? t('accounts.onboarding.accountSettings') : input.reauthorizing ? t('accounts.onboarding.reauthorizeAccount') : t('accounts.filters.importAccount'), - description: configuring ? t('accounts.onboarding.settingsDescription') : input.reauthorizing ? t('accounts.onboarding.reauthorizeDescription') : description, + title: configuring + ? t('accounts.onboarding.accountSettings') + : input.reauthorizing + ? t('accounts.onboarding.reauthorizeAccount') + : t('accounts.filters.importAccount'), + description: configuring + ? t('accounts.onboarding.settingsDescription') + : input.reauthorizing + ? t('accounts.onboarding.reauthorizeDescription') + : description, type: configuring ? 'default' as const : 'info' as const, width: configuring ? '42rem' : '36rem', }, oauth: { - callbackLabel: id === 'xai' ? t('accounts.onboarding.callbackLabel') : t('accounts.onboarding.callbackAddress'), - callbackPlaceholder: id === 'openai' ? 'http://localhost:1455/auth/callback?code=...&state=...' : t('accounts.onboarding.callbackPlaceholder'), + callbackLabel: id === 'xai' + ? t('accounts.onboarding.callbackLabel') + : t('accounts.onboarding.callbackAddress'), + callbackPlaceholder: id === 'openai' + ? 'http://localhost:1455/auth/callback?code=...&state=...' + : t('accounts.onboarding.callbackPlaceholder'), description: t('accounts.onboarding.callbackHelp'), }, importInput: form.mode === 'access_token' - ? { label: 'Access Token', placeholder: t('accounts.onboarding.pasteOneAccessTokenPerLine'), uploadable: false } + ? { + label: 'Access Token', + placeholder: t('accounts.onboarding.pasteOneAccessTokenPerLine'), + uploadable: false, + } : form.mode === 'refresh_token' - ? { label: 'Refresh Token', placeholder: t('accounts.onboarding.pasteOneRefreshTokenPerLine'), uploadable: false } - : { label: isBatch ? t('accounts.onboarding.batchAccountFiles') : t('accounts.onboarding.accountFile'), placeholder: isBatch ? t('accounts.onboarding.packagePlaceholder') : t('accounts.onboarding.pasteAccountJsonContent'), uploadable: true }, + ? { + label: 'Refresh Token', + placeholder: t('accounts.onboarding.pasteOneRefreshTokenPerLine'), + uploadable: false, + } + : { + label: isBatch + ? t('accounts.onboarding.batchAccountFiles') + : t('accounts.onboarding.accountFile'), + placeholder: isBatch + ? t('accounts.onboarding.packagePlaceholder') + : t('accounts.onboarding.pasteAccountJsonContent'), + uploadable: true, + }, canSubmit, submitLabel: form.mode === 'oauth' - ? input.reauthorizing ? t('accounts.onboarding.completeReauthorization') : t('accounts.onboarding.completeImport') - : isBatch ? t('accounts.onboarding.batchImport') : t('accounts.filters.importAccount'), + ? input.reauthorizing + ? t('accounts.onboarding.completeReauthorization') + : t('accounts.onboarding.completeImport') + : isBatch + ? t('accounts.onboarding.batchImport') + : t('accounts.filters.importAccount'), } } diff --git a/frontend/src/views/accounts/components/account-import-tasks/TaskDetail.vue b/frontend/src/views/accounts/components/account-import-tasks/TaskDetail.vue index 464fce23d..14bd7c445 100644 --- a/frontend/src/views/accounts/components/account-import-tasks/TaskDetail.vue +++ b/frontend/src/views/accounts/components/account-import-tasks/TaskDetail.vue @@ -16,8 +16,14 @@ const { t } = useI18n({ useScope: 'global' }) const filter = shallowRef('all') const filterOptions = computed(() => ([ - { label: t('common.values.all'), value: 'all' }, - { label: t('accounts.importTasks.needProcessing'), value: 'attention' }, + { + label: t('common.values.all'), + value: 'all', + }, + { + label: t('accounts.importTasks.needProcessing'), + value: 'attention', + }, ])) const hasAttention = computed(() => props.task.counts.failed + props.task.counts.unknown > 0) const visibleItems = computed(() => filter.value === 'attention' diff --git a/frontend/src/views/accounts/components/account-import-tasks/index.vue b/frontend/src/views/accounts/components/account-import-tasks/index.vue index e40fbd9c1..d34c43595 100644 --- a/frontend/src/views/accounts/components/account-import-tasks/index.vue +++ b/frontend/src/views/accounts/components/account-import-tasks/index.vue @@ -27,7 +27,10 @@ const retentionId = useId() const taskOptions = computed(() => props.tasks.map(task => ({ value: task.taskId, label: task.createdAtDisplay, - description: t('accounts.importTasks.items', { total: task.total, value: taskLabel(task) }), + description: t('accounts.importTasks.items', { + total: task.total, + value: taskLabel(task), + }), }))) watch(open, () => { diff --git a/frontend/src/views/accounts/components/account-import-tasks/presenter.ts b/frontend/src/views/accounts/components/account-import-tasks/presenter.ts index ebc16c2ca..767b9ad59 100644 --- a/frontend/src/views/accounts/components/account-import-tasks/presenter.ts +++ b/frontend/src/views/accounts/components/account-import-tasks/presenter.ts @@ -3,22 +3,60 @@ import { computed } from 'vue' import { t } from '@/i18n' export const itemStates = computed>(() => ({ - pending: { label: t('common.status.pending'), color: 'bg-cp-fill-alter text-cp-text-secondary', fill: 'bg-cp-fill-secondary' }, - running: { label: t('accounts.importTasks.processing'), color: 'bg-cp-primary-container text-cp-primary-on-container', fill: 'bg-cp-primary' }, - succeeded: { label: t('accounts.importTasks.alreadyInStock'), color: 'bg-cp-success-container text-cp-success-on-container', fill: 'bg-cp-success' }, - failed: { label: t('common.status.failed'), color: 'bg-cp-error-container text-cp-error-on-container', fill: 'bg-cp-error' }, - unknown: { label: t('accounts.importTasks.toBeVerified'), color: 'bg-cp-warning-container text-cp-warning-on-container', fill: 'bg-cp-warning' }, - skipped: { label: t('accounts.importTasks.notExecuted'), color: 'bg-cp-fill-tertiary text-cp-text-secondary', fill: 'bg-cp-text-quaternary' }, + pending: { + label: t('common.status.pending'), + color: 'bg-cp-fill-alter text-cp-text-secondary', + fill: 'bg-cp-fill-secondary', + }, + running: { + label: t('accounts.importTasks.processing'), + color: 'bg-cp-primary-container text-cp-primary-on-container', + fill: 'bg-cp-primary', + }, + succeeded: { + label: t('accounts.importTasks.alreadyInStock'), + color: 'bg-cp-success-container text-cp-success-on-container', + fill: 'bg-cp-success', + }, + failed: { + label: t('common.status.failed'), + color: 'bg-cp-error-container text-cp-error-on-container', + fill: 'bg-cp-error', + }, + unknown: { + label: t('accounts.importTasks.toBeVerified'), + color: 'bg-cp-warning-container text-cp-warning-on-container', + fill: 'bg-cp-warning', + }, + skipped: { + label: t('accounts.importTasks.notExecuted'), + color: 'bg-cp-fill-tertiary text-cp-text-secondary', + fill: 'bg-cp-text-quaternary', + }, })) -export const outcomeOrder: ImportItemStatus[] = ['succeeded', 'failed', 'unknown', 'running', 'pending', 'skipped'] +export const outcomeOrder: ImportItemStatus[] = [ + 'succeeded', + 'failed', + 'unknown', + 'running', + 'pending', + 'skipped', +] export function taskLabel(task: AccountImportTask) { - if (!task.finishedAt) - return task.stopRequested ? t('accounts.importTasks.stopping') : task.counts.running > 0 ? t('accounts.importTasks.importing') : t('accounts.importTasks.queuing') + if (!task.finishedAt) { + return task.stopRequested + ? t('accounts.importTasks.stopping') + : task.counts.running > 0 + ? t('accounts.importTasks.importing') + : t('accounts.importTasks.queuing') + } if (task.stopRequested) return t('accounts.importTasks.stopped') - return task.counts.failed + task.counts.unknown > 0 ? t('accounts.importTasks.endedPendingItems') : t('accounts.importTasks.allDone') + return task.counts.failed + task.counts.unknown > 0 + ? t('accounts.importTasks.endedPendingItems') + : t('accounts.importTasks.allDone') } export function processed(task: AccountImportTask) { diff --git a/frontend/src/views/accounts/components/account-profile-modal/ActivityInsights.vue b/frontend/src/views/accounts/components/account-profile-modal/ActivityInsights.vue index e8afbf1f5..0fad5e7ce 100644 --- a/frontend/src/views/accounts/components/account-profile-modal/ActivityInsights.vue +++ b/frontend/src/views/accounts/components/account-profile-modal/ActivityInsights.vue @@ -14,14 +14,26 @@ const props = defineProps<{ const { t } = useI18n({ useScope: 'global' }) const insightRows = computed(() => [ - { label: t('accounts.profile.activity.quickMode'), value: formatPercent(props.insights.fastModePercent) }, + { + label: t('accounts.profile.activity.quickMode'), + value: formatPercent(props.insights.fastModePercent), + }, { label: t('accounts.profile.activity.reasoningEffort'), value: formatReasoning(props.insights.reasoningEffort, props.insights.reasoningEffortPercent), }, - { label: t('accounts.profile.activity.exploredSkills'), value: formatNumber(props.insights.skillsExplored) }, - { label: t('accounts.profile.activity.totalNumberOfSkillsUsed'), value: formatNumber(props.insights.totalSkillsUsed) }, - { label: t('accounts.profile.activity.totalNumberOfChats'), value: formatNumber(props.insights.totalThreads) }, + { + label: t('accounts.profile.activity.exploredSkills'), + value: formatNumber(props.insights.skillsExplored), + }, + { + label: t('accounts.profile.activity.totalNumberOfSkillsUsed'), + value: formatNumber(props.insights.totalSkillsUsed), + }, + { + label: t('accounts.profile.activity.totalNumberOfChats'), + value: formatNumber(props.insights.totalThreads), + }, ]) const invocations = computed(() => (props.insights.invocations ?? []) @@ -30,7 +42,9 @@ const invocations = computed(() => ) function formatNumber(value: number | null) { - return value === null ? '—' : formatInteger(value) + return value === null + ? '—' + : formatInteger(value) } function formatPercent(value: number | null) { @@ -42,7 +56,9 @@ function formatPercent(value: number | null) { function formatReasoning(effort: string | null, percent: number | null) { if (!effort) return '—' - return percent === null ? effort : `${effort} · ${formatPercent(percent)}` + return percent === null + ? effort + : `${effort} · ${formatPercent(percent)}` } function invocationView(invocation: AccountProfileInvocation) { diff --git a/frontend/src/views/accounts/components/account-profile-modal/Metrics.vue b/frontend/src/views/accounts/components/account-profile-modal/Metrics.vue index 1b6d802f9..b269dd133 100644 --- a/frontend/src/views/accounts/components/account-profile-modal/Metrics.vue +++ b/frontend/src/views/accounts/components/account-profile-modal/Metrics.vue @@ -39,15 +39,23 @@ const metrics = computed(() => [ ]) function formatMetric(value: number | null | undefined) { - return value == null ? '—' : formatCompactNumber(value) + return value == null + ? '—' + : formatCompactNumber(value) } function formatExact(value: number | null | undefined) { - return value == null ? undefined : `${formatInteger(value)} Tokens` + return value == null + ? undefined + : `${formatInteger(value)} Tokens` } function formatDays(value: number | null | undefined) { - return value == null ? '—' : t('accounts.profile.metrics.dayCount', { integer: formatInteger(value) }) + return value == null + ? '—' + : t('accounts.profile.metrics.dayCount', { + integer: formatInteger(value), + }) } function formatDuration(value: number | null | undefined) { @@ -57,13 +65,21 @@ function formatDuration(value: number | null | undefined) { const days = Math.floor(totalMinutes / (24 * 60)) const hours = Math.floor((totalMinutes % (24 * 60)) / 60) const minutes = totalMinutes % 60 - if (days > 0) - return hours > 0 ? t('accounts.profile.metrics.dH', { days, hours }) : t('accounts.profile.metrics.elapsedDays', { days }) - if (hours > 0) - return minutes > 0 ? t('accounts.profile.metrics.hMin', { hours, minutes }) : t('accounts.profile.metrics.elapsedHours', { hours }) + if (days > 0) { + return hours > 0 + ? t('accounts.profile.metrics.dH', { days, hours }) + : t('accounts.profile.metrics.elapsedDays', { days }) + } + if (hours > 0) { + return minutes > 0 + ? t('accounts.profile.metrics.hMin', { hours, minutes }) + : t('accounts.profile.metrics.elapsedHours', { hours }) + } if (totalMinutes > 0) return t('accounts.profile.metrics.min', { totalMinutes }) - return t('accounts.profile.metrics.durationSeconds', { value: Math.max(0, Math.floor(value / 1000)) }) + return t('accounts.profile.metrics.durationSeconds', { + value: Math.max(0, Math.floor(value / 1000)), + }) } diff --git a/frontend/src/views/accounts/components/account-profile-modal/ProfileHero.vue b/frontend/src/views/accounts/components/account-profile-modal/ProfileHero.vue index f77866d91..622fc5619 100644 --- a/frontend/src/views/accounts/components/account-profile-modal/ProfileHero.vue +++ b/frontend/src/views/accounts/components/account-profile-modal/ProfileHero.vue @@ -21,7 +21,9 @@ const imageFailed = shallowRef(false) const displayName = computed( () => props.profile?.displayName || props.profile?.username || props.account.email || props.account.name || t('accounts.profile.accountUser'), ) -const username = computed(() => (props.profile?.username ? `@${props.profile?.username.replace(/^@/, '')}` : null)) +const username = computed(() => (props.profile?.username + ? `@${props.profile?.username.replace(/^@/, '')}` + : null)) const accountIdentity = computed(() => props.account.email?.trim() || props.account.accountId?.trim()) const initial = computed(() => Array.from(displayName.value.trim())[0]?.toUpperCase() || null) const avatarToneClass = computed(() => @@ -29,7 +31,9 @@ const avatarToneClass = computed(() => ) const avatarUrl = computed(() => { const sourceUrl = props.profile?.imageUrl?.trim() - return props.account.capabilities.avatar && sourceUrl ? accountProfileAvatarUrl(props.account.id, sourceUrl) : null + return props.account.capabilities.avatar && sourceUrl + ? accountProfileAvatarUrl(props.account.id, sourceUrl) + : null }) watch( avatarUrl, diff --git a/frontend/src/views/accounts/components/account-profile-modal/Subscription.vue b/frontend/src/views/accounts/components/account-profile-modal/Subscription.vue index 44cbdb8e1..aee62d54c 100644 --- a/frontend/src/views/accounts/components/account-profile-modal/Subscription.vue +++ b/frontend/src/views/accounts/components/account-profile-modal/Subscription.vue @@ -21,13 +21,27 @@ const billingPeriod = computed(() => { const period = props.subscription?.billingPeriod if (!period) return null - const labels: Record = { month: t('accounts.profile.subscription.byMonth'), monthly: t('accounts.profile.subscription.byMonth'), year: t('accounts.profile.subscription.byYear'), yearly: t('accounts.profile.subscription.byYear'), annual: t('accounts.profile.subscription.byYear') } + const labels: Record = { + month: t('accounts.profile.subscription.byMonth'), + monthly: t('accounts.profile.subscription.byMonth'), + year: t('accounts.profile.subscription.byYear'), + yearly: t('accounts.profile.subscription.byYear'), + annual: t('accounts.profile.subscription.byYear'), + } return labels[period.toLowerCase()] ?? period }) const billing = computed(() => [billingPeriod.value, props.subscription?.billingCurrency].filter(Boolean).join(' · ') || t('common.values.unknown')) const dates = computed(() => [ - { label: t('accounts.profile.subscription.subscriptionStarts'), value: props.subscription?.startsAt, display: props.subscription?.startsAtDisplay }, - { label: t('accounts.profile.subscription.subscriptionEnds'), value: props.subscription?.expiresAt, display: props.subscription?.expiresAtDisplay }, + { + label: t('accounts.profile.subscription.subscriptionStarts'), + value: props.subscription?.startsAt, + display: props.subscription?.startsAtDisplay, + }, + { + label: t('accounts.profile.subscription.subscriptionEnds'), + value: props.subscription?.expiresAt, + display: props.subscription?.expiresAtDisplay, + }, ]) diff --git a/frontend/src/views/accounts/components/account-profile-modal/TokenActivity.vue b/frontend/src/views/accounts/components/account-profile-modal/TokenActivity.vue index c58101858..374905224 100644 --- a/frontend/src/views/accounts/components/account-profile-modal/TokenActivity.vue +++ b/frontend/src/views/accounts/components/account-profile-modal/TokenActivity.vue @@ -16,9 +16,18 @@ const { t } = useI18n({ useScope: 'global' }) const mode = shallowRef('daily') const modeOptions = computed(() => ([ - { label: t('accounts.profile.activity.daily'), value: 'daily' }, - { label: t('accounts.profile.activity.weekly'), value: 'weekly' }, - { label: t('accounts.profile.activity.cumulative'), value: 'cumulative' }, + { + label: t('accounts.profile.activity.daily'), + value: 'daily', + }, + { + label: t('accounts.profile.activity.weekly'), + value: 'weekly', + }, + { + label: t('accounts.profile.activity.cumulative'), + value: 'cumulative', + }, ])) const levelClasses: Record = { 0: 'bg-cp-activity-level-0', @@ -31,7 +40,11 @@ const grid = computed(() => buildProfileActivityGrid(props.calendar, mode.value) const rangeLabel = computed(() => grid.value.rangeLabel) const monthLabels = computed(() => grid.value.weeks.flatMap((week, weekIndex) => - week.monthLabel ? [{ label: week.monthLabel, weekIndex }] : [], + week.monthLabel + ? [ + { label: week.monthLabel, weekIndex }, + ] + : [], ), ) diff --git a/frontend/src/views/accounts/components/account-profile-modal/activity.ts b/frontend/src/views/accounts/components/account-profile-modal/activity.ts index 1e179ce05..8630ff039 100644 --- a/frontend/src/views/accounts/components/account-profile-modal/activity.ts +++ b/frontend/src/views/accounts/components/account-profile-modal/activity.ts @@ -9,14 +9,28 @@ export function buildProfileActivityGrid(calendar: ProfileActivityCalendar | nul let cumulative = 0 const weeks = (calendar?.weeks ?? []).map((week) => { const weekly = week.cells.reduce((sum, cell) => sum + cell.tokens, 0) - return { ...week, cells: week.cells.map((cell) => { - cumulative += cell.isFuture ? 0 : cell.tokens - return { ...cell, value: mode === 'weekly' ? weekly : mode === 'cumulative' ? cumulative : cell.tokens, level: 0 as ProfileActivityLevel } - }) } + return { + ...week, + cells: week.cells.map((cell) => { + cumulative += cell.isFuture ? 0 : cell.tokens + return { + ...cell, + value: mode === 'weekly' + ? weekly + : mode === 'cumulative' + ? cumulative + : cell.tokens, + level: 0 as ProfileActivityLevel, + } + }), + } }) const maximum = Math.max(0, ...weeks.flatMap(week => week.cells).filter(cell => !cell.isFuture).map(cell => cell.value)) - for (const cell of weeks.flatMap(week => week.cells)) - cell.level = cell.value <= 0 || maximum <= 0 ? 0 : Math.min(4, Math.max(1, Math.ceil(cell.value / maximum * 4))) as ProfileActivityLevel + for (const cell of weeks.flatMap(week => week.cells)) { + cell.level = cell.value <= 0 || maximum <= 0 + ? 0 + : Math.min(4, Math.max(1, Math.ceil(cell.value / maximum * 4))) as ProfileActivityLevel + } return { weeks, rangeLabel: calendar?.rangeLabel ?? '' } } diff --git a/frontend/src/views/accounts/components/account-quota-forecast-modal/index.vue b/frontend/src/views/accounts/components/account-quota-forecast-modal/index.vue index 9a71f6ea6..11f8f9a3b 100644 --- a/frontend/src/views/accounts/components/account-quota-forecast-modal/index.vue +++ b/frontend/src/views/accounts/components/account-quota-forecast-modal/index.vue @@ -33,11 +33,19 @@ const { now, pause, resume } = useNow({ scheduler: callback => useIntervalFn(callback, 30_000), }) const options = computed(() => report.value?.forecasts.map(item => ({ - label: item.period === 'weekly' ? t('accounts.quotaForecast.weeklyQuota') : t('accounts.quotaForecast.monthlyQuota'), + label: item.period === 'weekly' + ? t('accounts.quotaForecast.weeklyQuota') + : t('accounts.quotaForecast.monthlyQuota'), value: item.period, })) ?? [ - { label: t('accounts.quotaForecast.weeklyQuota'), value: 'weekly' }, - { label: t('accounts.quotaForecast.monthlyQuota'), value: 'monthly' }, + { + label: t('accounts.quotaForecast.weeklyQuota'), + value: 'weekly', + }, + { + label: t('accounts.quotaForecast.monthlyQuota'), + value: 'monthly', + }, ]) const forecast = computed(() => report.value?.forecasts.find(item => item.period === period.value)) const unavailableReason = computed(() => { diff --git a/frontend/src/views/accounts/components/account-quota-panel/Credits.vue b/frontend/src/views/accounts/components/account-quota-panel/Credits.vue index 67d224357..17ddc15fb 100644 --- a/frontend/src/views/accounts/components/account-quota-panel/Credits.vue +++ b/frontend/src/views/accounts/components/account-quota-panel/Credits.vue @@ -19,7 +19,9 @@ const balanceDisplay = computed(() => { const [, sign, integer, fraction] = decimal // 点数可能包含高精度小数,整数分组与小数文本分别处理,避免转成浮点数。 const grouped = `${sign}${new Intl.NumberFormat(numberLocale()).format(BigInt(integer!))}` - return fraction ? `${grouped}.${fraction}` : grouped + return fraction + ? `${grouped}.${fraction}` + : grouped }) const value = computed(() => { if (props.credits?.unlimited) diff --git a/frontend/src/views/accounts/components/account-quota-panel/ResetCreditAutomation.vue b/frontend/src/views/accounts/components/account-quota-panel/ResetCreditAutomation.vue index c2ce030eb..753de611a 100644 --- a/frontend/src/views/accounts/components/account-quota-panel/ResetCreditAutomation.vue +++ b/frontend/src/views/accounts/components/account-quota-panel/ResetCreditAutomation.vue @@ -23,7 +23,9 @@ const saveAction = useAsyncAction() const busy = computed(() => loadAction.loading.value || saveAction.loading.value) const resultText = computed(() => { switch (status.value?.lastResult) { - case 'pending': return status.value?.enabled ? t('accounts.quotaReset.automation.confirming') : t('accounts.quotaReset.automation.unconfirmed') + case 'pending': return status.value?.enabled + ? t('accounts.quotaReset.automation.confirming') + : t('accounts.quotaReset.automation.unconfirmed') case 'expired_unknown': return t('accounts.quotaReset.automation.expiredUnconfirmed') case 'discovery_failed': return t('accounts.quotaReset.automation.queryFailed') case 'weekly_quota_above_threshold': return t('accounts.quotaReset.automation.aboveThreshold') diff --git a/frontend/src/views/accounts/components/account-quota-panel/ResetCredits.vue b/frontend/src/views/accounts/components/account-quota-panel/ResetCredits.vue index f15b8fbe4..ed40aeb38 100644 --- a/frontend/src/views/accounts/components/account-quota-panel/ResetCredits.vue +++ b/frontend/src/views/accounts/components/account-quota-panel/ResetCredits.vue @@ -52,7 +52,9 @@ const { const modalTitle = computed(() => { if (!confirmation.value) return t('accounts.quotaReset.limitReset') - return confirmation.value.ambiguous ? t('accounts.quotaReset.confirmLastReset') : t('accounts.quotaReset.confirmResetAmount') + return confirmation.value.ambiguous + ? t('accounts.quotaReset.confirmLastReset') + : t('accounts.quotaReset.confirmResetAmount') }) const triggerLabel = computed(() => { if (ambiguous.value) @@ -62,7 +64,9 @@ const triggerLabel = computed(() => { ? t('accounts.quotaReset.queryFailedWithCount', { count: availableCount.value }) : t('accounts.quotaReset.queryFailedLabel') } - return hasSnapshot.value ? t('accounts.quotaReset.availableCount', { count: availableCount.value }) : t('accounts.quotaReset.viewCredits') + return hasSnapshot.value + ? t('accounts.quotaReset.availableCount', { count: availableCount.value }) + : t('accounts.quotaReset.viewCredits') }) const showTriggerCount = computed(() => hasSnapshot.value && availableCount.value > 0) const confirmCreditTitle = computed(() => confirmation.value?.credit @@ -71,15 +75,20 @@ const confirmCreditTitle = computed(() => confirmation.value?.credit const creditItems = computed(() => availableCredits.value.map(credit => ({ id: credit.id, title: creditTitle(credit), - expiry: credit.expiresAtDisplay ? t('accounts.quotaReset.expires', { expiresAtDisplay: credit.expiresAtDisplay }) : t('accounts.quotaReset.expiryHelp'), + expiry: credit.expiresAtDisplay + ? t('accounts.quotaReset.expires', { expiresAtDisplay: credit.expiresAtDisplay }) + : t('accounts.quotaReset.expiryHelp'), }))) const showCountOnlyAction = computed(() => !loadError.value && hasSnapshot.value && availableCount.value > 0 && creditItems.value.length === 0) const countLabel = computed(() => { - if (!hasSnapshot.value) - return loading.value ? t('accounts.quotaReset.querying') : t('accounts.quotaReset.toBeQueried') + if (!hasSnapshot.value) { + return loading.value + ? t('accounts.quotaReset.querying') + : t('accounts.quotaReset.toBeQueried') + } return t('accounts.quotaReset.usesAvailable', { count: availableCount.value }) }) @@ -94,8 +103,11 @@ watch(panelOpen, (isOpen) => { }) watch(showConfirm, (confirming) => { - if (panelOpen.value) - confirmation.value = confirming ? { credit: consumptionCredit.value, ambiguous: ambiguous.value } : null + if (panelOpen.value) { + confirmation.value = confirming + ? { credit: consumptionCredit.value, ambiguous: ambiguous.value } + : null + } }) function creditTitle(credit: AccountResetCredit | undefined) { diff --git a/frontend/src/views/accounts/components/account-quota-panel/UsageLimits.vue b/frontend/src/views/accounts/components/account-quota-panel/UsageLimits.vue index 23b4eb298..4c09933b1 100644 --- a/frontend/src/views/accounts/components/account-quota-panel/UsageLimits.vue +++ b/frontend/src/views/accounts/components/account-quota-panel/UsageLimits.vue @@ -25,7 +25,9 @@ const items = computed(() => orderedPanelQuotaWindows(props.windows) label: quotaWindowLabel(window), resetAtDisplay: window.resetAtDisplay, remaining, - remainingDisplay: remaining === null ? t('accounts.quota.awaitingData') : t('accounts.quota.remaining', { remaining }), + remainingDisplay: remaining === null + ? t('accounts.quota.awaitingData') + : t('accounts.quota.remaining', { remaining }), barClass: presentation.barClass, percentTextClass: presentation.percentTextClass, barStyle: { width: `${remaining ?? 0}%` }, diff --git a/frontend/src/views/accounts/components/account-quota-panel/index.vue b/frontend/src/views/accounts/components/account-quota-panel/index.vue index f757df10c..ee165d922 100644 --- a/frontend/src/views/accounts/components/account-quota-panel/index.vue +++ b/frontend/src/views/accounts/components/account-quota-panel/index.vue @@ -37,7 +37,9 @@ const forecastOpen = shallowRef(false) const hasForecast = computed(() => props.account.authenticationKind !== 'api_key') const hasPersonalInfo = computed(() => props.account.capabilities.profile || props.account.capabilities.subscription) const hasActions = computed(() => hasPersonalInfo.value || hasForecast.value || props.account.capabilities.quotaRefresh || props.account.capabilities.resetCredits) -const lastRefreshed = computed(() => t('accounts.quota.lastRefreshed', { refreshedAtDisplay: formatRelativeTime(props.account.quota.refreshedAtRelative) })) +const lastRefreshed = computed(() => t('accounts.quota.lastRefreshed', { + refreshedAtDisplay: formatRelativeTime(props.account.quota.refreshedAtRelative), +})) watch(hasPersonalInfo, (available) => { if (!available) profileOpen.value = false diff --git a/frontend/src/views/accounts/components/account-quota-summary-cell/Entry.vue b/frontend/src/views/accounts/components/account-quota-summary-cell/Entry.vue index 95ec80fed..a481b9bc8 100644 --- a/frontend/src/views/accounts/components/account-quota-summary-cell/Entry.vue +++ b/frontend/src/views/accounts/components/account-quota-summary-cell/Entry.vue @@ -22,8 +22,12 @@ const props = withDefaults(defineProps<{ const { t } = useI18n({ useScope: 'global' }) const detailHeading = computed(() => props.label) -const detailTitle = computed(() => detailHeading.value ?? (props.windows[0] ? quotaWindowLabel(props.windows[0]) : t('accounts.quota.quotaDetails'))) -const summaryLabel = computed(() => props.label ?? (props.windows[0] ? quotaWindowLabel(props.windows[0]) : t('usage.quota'))) +const detailTitle = computed(() => detailHeading.value ?? (props.windows[0] + ? quotaWindowLabel(props.windows[0]) + : t('accounts.quota.quotaDetails'))) +const summaryLabel = computed(() => props.label ?? (props.windows[0] + ? quotaWindowLabel(props.windows[0]) + : t('usage.quota'))) const hasQuotaWindow = computed(() => props.windows.some(window => typeof window.usedPercent === 'number')) const detailItems = computed(() => props.windows.map((window) => { diff --git a/frontend/src/views/accounts/components/account-status-badge/presenter.ts b/frontend/src/views/accounts/components/account-status-badge/presenter.ts index 7d6586c2e..acb30d3b8 100644 --- a/frontend/src/views/accounts/components/account-status-badge/presenter.ts +++ b/frontend/src/views/accounts/components/account-status-badge/presenter.ts @@ -171,7 +171,9 @@ export function resolveAccountStatusPresentation( ? errorRecoveryHints.value[input.errorReason] : definition.recoveryHint const hasDetail = input.status === 'error' || mode === 'rate_limited' || isBackoff - const retry = nextRefreshDisplay ? t('accounts.status.nextAttempt', { nextRefreshDisplay }) : '' + const retry = nextRefreshDisplay + ? t('accounts.status.nextAttempt', { nextRefreshDisplay }) + : '' const triggerLabel = t('accounts.status.clickOrFocusToViewDetails', { title, description: definition.description, retry }) return { @@ -186,7 +188,9 @@ export function resolveAccountStatusPresentation( errorText: input.errorMessage || null, nextRefreshDisplay, rateLimitRecovery, - recoveryTimeLabel: waitsForProbe ? t('accounts.status.resumeDetection') : t('accounts.status.expectedToRecover'), + recoveryTimeLabel: waitsForProbe + ? t('accounts.status.resumeDetection') + : t('accounts.status.expectedToRecover'), triggerLabel, } } diff --git a/frontend/src/views/accounts/composables/useAccountAuthorization.ts b/frontend/src/views/accounts/composables/useAccountAuthorization.ts index e16218f32..595ccf0c5 100644 --- a/frontend/src/views/accounts/composables/useAccountAuthorization.ts +++ b/frontend/src/views/accounts/composables/useAccountAuthorization.ts @@ -67,10 +67,12 @@ export function useAccountAuthorization(onComplete: () => Promise) { return const expiresAt = Date.parse(result.expiresAt) if (!Number.isFinite(expiresAt)) - throw new Error(t('accounts.onboarding.invalidExpiry')) + throw new RangeError(t('accounts.onboarding.invalidExpiry')) context = { provider: request.start.provider, settings: request.settings } flow.value = result - status.value = expired() ? 'expired' : 'waiting' + status.value = expired() + ? 'expired' + : 'waiting' expiryTimer = setTimeout(() => { // 不取消正在确认的提交,过期后仍接收服务端已经提交的结果。 if (status.value !== 'completing') @@ -109,12 +111,22 @@ export function useAccountAuthorization(onComplete: () => Promise) { } catch (cause) { if (!current.signal.aborted) { - status.value = expired() ? 'expired' : 'paused' + status.value = expired() + ? 'expired' + : 'paused' error.value = errorMessage(cause, t('accounts.onboarding.authorizationFailed')) } } } onScopeDispose(reset) - return { view, callback, busy, start, complete, cancel, reset } + return { + view, + callback, + busy, + start, + complete, + cancel, + reset, + } } diff --git a/frontend/src/views/accounts/composables/useAccountBatchEditor.ts b/frontend/src/views/accounts/composables/useAccountBatchEditor.ts index acd69c6ac..e45d44186 100644 --- a/frontend/src/views/accounts/composables/useAccountBatchEditor.ts +++ b/frontend/src/views/accounts/composables/useAccountBatchEditor.ts @@ -67,9 +67,15 @@ export function useAccountBatchEditor(options: { ZNotification.warning({ message: modelError }) return } - const scheduling = parseAccountSchedulingForm(editedFields.value.has('concurrencyLimit') ? concurrencyLimit.value : '', editedFields.value.has('weight') ? weight.value : '1') + const scheduling = parseAccountSchedulingForm(editedFields.value.has('concurrencyLimit') + ? concurrencyLimit.value + : '', editedFields.value.has('weight') + ? weight.value + : '1') if (proxyMode.value === 'proxy' && !proxyId.value.trim()) { - ZNotification.warning({ message: t('accounts.batchEdit.proxyRequired') }) + ZNotification.warning({ + message: t('accounts.batchEdit.proxyRequired'), + }) return } if (!scheduling.valid) { @@ -82,17 +88,36 @@ export function useAccountBatchEditor(options: { await batchUpdateAccounts({ accountIds, modelAccess: modelAccess.value, - outboundProxyId: proxyMode.value === 'preserve' ? undefined : proxyMode.value === 'direct' ? '' : proxyId.value.trim(), + outboundProxyId: proxyMode.value === 'preserve' + ? undefined + : proxyMode.value === 'direct' + ? '' + : proxyId.value.trim(), enabled: schedulingEnabled.value, - concurrencyLimit: editedFields.value.has('concurrencyLimit') ? scheduling.values.concurrencyLimit : undefined, - weight: editedFields.value.has('weight') ? scheduling.values.weight : undefined, - groupIds: editedFields.value.has('groupIds') ? [...new Set(selectedGroupIds.value)] : undefined, + concurrencyLimit: editedFields.value.has('concurrencyLimit') + ? scheduling.values.concurrencyLimit + : undefined, + weight: editedFields.value.has('weight') + ? scheduling.values.weight + : undefined, + groupIds: editedFields.value.has('groupIds') + ? [ + ...new Set(selectedGroupIds.value), + ] + : undefined, }) showBatchEditModal.value = false options.selectedIds.value = new Set() - ZNotification.success({ message: t('accounts.batchEdit.saved', { count: accountIds.length }) }) - void Promise.allSettled([options.reloadAccounts(), options.reloadGroups()]) - }, { onError: () => void options.reloadAccounts() }) + ZNotification.success({ + message: t('accounts.batchEdit.saved', { count: accountIds.length }), + }) + void Promise.allSettled([ + options.reloadAccounts(), + options.reloadGroups(), + ]) + }, { + onError: () => void options.reloadAccounts(), + }) } function selectedAccounts() { @@ -145,7 +170,9 @@ function sharedConcurrencyLimit(accounts: Account[]) { function sharedWeight(accounts: Account[]) { const first = accounts[0]?.weight ?? 1 - return accounts.every(account => account.weight === first) ? String(first) : '1' + return accounts.every(account => account.weight === first) + ? String(first) + : '1' } function sharedGroupIds(accounts: Account[]) { diff --git a/frontend/src/views/accounts/composables/useAccountConcurrency.ts b/frontend/src/views/accounts/composables/useAccountConcurrency.ts index 55ff27ee7..d04fb5d2f 100644 --- a/frontend/src/views/accounts/composables/useAccountConcurrency.ts +++ b/frontend/src/views/accounts/composables/useAccountConcurrency.ts @@ -45,7 +45,10 @@ export function useAccountConcurrency(account: Ref, open: Ref [entry.leaseId, { entry, receivedAt: firstReceivedAt }])) + const collected = new Map(result.entries.map(entry => [ + entry.leaseId, + { entry, receivedAt: firstReceivedAt }, + ])) // 按首次总数限定本轮读取,避免持续新增占用让刷新无界延长 const pages = Math.ceil(result.total / result.pageSize) for (let page = 2; page <= pages; page++) { @@ -60,9 +63,13 @@ export function useAccountConcurrency(account: Ref, open: Ref ({ + entries: [ + ...collected.values(), + ].map(({ entry, receivedAt }) => ({ ...entry, - occupiedMs: entry.occupiedMs === null ? null : entry.occupiedMs + snapshotAt - receivedAt, + occupiedMs: entry.occupiedMs === null + ? null + : entry.occupiedMs + snapshotAt - receivedAt, })), } request.error.value = '' @@ -80,7 +87,10 @@ export function useAccountConcurrency(account: Ref, open: Ref account.value?.id], ([isActive, accountId], [, previousAccountId]) => { + watch([ + active, + () => account.value?.id, + ], ([isActive, accountId], [, previousAccountId]) => { stop() // 同一账号保留已确认内容,暂停轮询和弹窗退场不提前清空 if (accountId !== previousAccountId) { @@ -94,5 +104,10 @@ export function useAccountConcurrency(account: Ref, open: Ref Promise - itemIndex === index ? { ...next, time: item.time } : item, + itemIndex === index + ? { ...next, time: item.time } + : item, ) } @@ -425,7 +429,9 @@ export function useAccountConnectionTest(options: { reload: () => Promise Promise testingAccount.value?.id], ([open]) => { + watch([ + showConnectionTestModal, + () => testingAccount.value?.id, + ], ([open]) => { modelsRequest.invalidate({ resetLoading: open }) if (!open) { abortConnectionTest() diff --git a/frontend/src/views/accounts/composables/useAccountEditor.ts b/frontend/src/views/accounts/composables/useAccountEditor.ts index 091f6f6c8..9ee7080e8 100644 --- a/frontend/src/views/accounts/composables/useAccountEditor.ts +++ b/frontend/src/views/accounts/composables/useAccountEditor.ts @@ -28,7 +28,9 @@ export function useAccountEditor(options: { const saveAction = useAsyncAction() const saving = saveAction.loading const apiKey = ref(emptyApiKeyAccountForm()) - const configurationRequest = useRequestState(() => ZNotification.error({ message: t('accounts.editor.loadError') })) + const configurationRequest = useRequestState(() => ZNotification.error({ + message: t('accounts.editor.loadError'), + })) const savedConfiguration = shallowRef() const oauthTransport = shallowRef('') const savedOAuthTransport = shallowRef() @@ -51,7 +53,10 @@ export function useAccountEditor(options: { const configuration = parseApiKeyConfiguration(detail.credentialConfiguration) if (!configuration) throw new Error(t('accounts.editor.apiKeyUnavailable')) - apiKey.value = { ...emptyApiKeyAccountForm(), ...configuration } + apiKey.value = { + ...emptyApiKeyAccountForm(), + ...configuration, + } savedConfiguration.value = configuration } catch (error) { @@ -71,7 +76,10 @@ export function useAccountEditor(options: { schedulingEnabled.value = account.enabled concurrencyLimit.value = concurrencyLimitInput(account.concurrencyLimit) weight.value = String(account.weight) - modelAccess.value = { ...account.modelAccess, models: [...account.modelAccess.models] } + modelAccess.value = { + ...account.modelAccess, + models: [...account.modelAccess.models], + } selectedGroupIds.value = account.groups.map(group => group.id) showEditModal.value = true if (isOpenAiApiKeyAccount(account) || isOpenAiOAuthAccount(account)) @@ -84,7 +92,9 @@ export function useAccountEditor(options: { return const isApiKey = isOpenAiApiKeyAccount(editingAccount.value) const isOAuth = isOpenAiOAuthAccount(editingAccount.value) - const configuration = isApiKey && configurationReady.value ? parseApiKeyConfiguration(apiKey.value) : undefined + const configuration = isApiKey && configurationReady.value + ? parseApiKeyConfiguration(apiKey.value) + : undefined if (isApiKey && !configuration) return if (isApiKey) { @@ -101,7 +111,9 @@ export function useAccountEditor(options: { } const scheduling = parseAccountSchedulingForm(concurrencyLimit.value, weight.value) if (proxyMode.value === 'proxy' && !proxyId.value.trim()) { - ZNotification.warning({ message: t('accounts.batchEdit.proxyRequired') }) + ZNotification.warning({ + message: t('accounts.batchEdit.proxyRequired'), + }) return } if (!scheduling.valid) { @@ -113,12 +125,18 @@ export function useAccountEditor(options: { const settings = { accountId, notes: notes.value, - outboundProxyId: proxyMode.value === 'preserve' ? undefined : proxyMode.value === 'direct' ? '' : proxyId.value.trim(), + outboundProxyId: proxyMode.value === 'preserve' + ? undefined + : proxyMode.value === 'direct' + ? '' + : proxyId.value.trim(), enabled: schedulingEnabled.value, concurrencyLimit: scheduling.values.concurrencyLimit, weight: scheduling.values.weight, modelAccess: modelAccess.value, - groupIds: [...new Set(selectedGroupIds.value)], + groupIds: [ + ...new Set(selectedGroupIds.value), + ], } const connectionChanged = isApiKey && ( apiKey.value.apiKey !== '' @@ -128,14 +146,23 @@ export function useAccountEditor(options: { await updateAccount({ ...settings, connection: connectionChanged && configuration - ? { baseUrl: configuration.base_url.trim(), transport: configuration.transport, apiKey: apiKey.value.apiKey || undefined } + ? { + baseUrl: configuration.base_url.trim(), + transport: configuration.transport, + apiKey: apiKey.value.apiKey || undefined, + } : isOAuth && configurationReady.value && oauthTransport.value && oauthTransport.value !== savedOAuthTransport.value ? { transport: oauthTransport.value } : undefined, }) showEditModal.value = false - ZNotification.success({ message: t('accounts.editor.saved') }) - void Promise.allSettled([options.reloadAccounts(), options.reloadGroups()]) + ZNotification.success({ + message: t('accounts.editor.saved'), + }) + void Promise.allSettled([ + options.reloadAccounts(), + options.reloadGroups(), + ]) }) } @@ -146,7 +173,10 @@ export function useAccountEditor(options: { function clearCredentials() { configurationRequest.invalidate() - apiKey.value = { ...emptyApiKeyAccountForm(), transport: '' } + apiKey.value = { + ...emptyApiKeyAccountForm(), + transport: '', + } savedConfiguration.value = undefined oauthTransport.value = '' savedOAuthTransport.value = undefined diff --git a/frontend/src/views/accounts/composables/useAccountImportTasks.ts b/frontend/src/views/accounts/composables/useAccountImportTasks.ts index d46f86a9e..4fc0c026e 100644 --- a/frontend/src/views/accounts/composables/useAccountImportTasks.ts +++ b/frontend/src/views/accounts/composables/useAccountImportTasks.ts @@ -80,7 +80,10 @@ export function useAccountImportTasks(options: { reload: () => Promise } function created(task: AccountImportTask, reveal = true) { - tasks.value = [task, ...tasks.value.filter(entry => entry.taskId !== task.taskId)] + tasks.value = [ + task, + ...tasks.value.filter(entry => entry.taskId !== task.taskId), + ] selectedId.value = task.taskId detail.value = null if (open.value || !reveal) @@ -117,5 +120,18 @@ export function useAccountImportTasks(options: { reload: () => Promise clearTimeout(timer) }) - return { open, tasks, detail, selectedId, error, loading, activeCount, stopping: stopAction.loading, select, created, refresh, stop } + return { + open, + tasks, + detail, + selectedId, + error, + loading, + activeCount, + stopping: stopAction.loading, + select, + created, + refresh, + stop, + } } diff --git a/frontend/src/views/accounts/composables/useAccountMutations.ts b/frontend/src/views/accounts/composables/useAccountMutations.ts index 1316ed449..26c67c37d 100644 --- a/frontend/src/views/accounts/composables/useAccountMutations.ts +++ b/frontend/src/views/accounts/composables/useAccountMutations.ts @@ -105,7 +105,9 @@ export function useAccountMutations(options: { options.selectedIds.value = remaining showSingleDeleteModal.value = false await loadAccounts() - ZNotification.success({ message: t('accounts.actions.deleted') }) + ZNotification.success({ + message: t('accounts.actions.deleted'), + }) }, ) } @@ -129,15 +131,22 @@ export function useAccountMutations(options: { } showDeleteModal.value = false await loadAccounts() - ZNotification.success({ message: t('accounts.actions.deletedAccountDeletedAccounts', { count: deletedCount }) }) + ZNotification.success({ + message: t('accounts.actions.deletedAccountDeletedAccounts', { count: deletedCount }), + }) }, { errorText: false, onError: (error) => { void loadAccounts().catch(() => undefined) - ZNotification.error({ message: deletedCount > 0 - ? t('accounts.actions.partialDeletion', { count: deletedCount, value: errorMessage(error, t('common.errors.operation')) }) - : errorMessage(error, t('accounts.actions.batchDeletionFailed')) }) + ZNotification.error({ + message: deletedCount > 0 + ? t('accounts.actions.partialDeletion', { + count: deletedCount, + value: errorMessage(error, t('common.errors.operation')), + }) + : errorMessage(error, t('accounts.actions.batchDeletionFailed')), + }) }, }, ) @@ -148,7 +157,9 @@ export function useAccountMutations(options: { return const selected = [...options.selectedIds.value] if (selected.length === 0) { - ZNotification.warning({ message: t('accounts.actions.exportSelectionRequired') }) + ZNotification.warning({ + message: t('accounts.actions.exportSelectionRequired'), + }) return } if (exportDisabledReason.value) { @@ -164,9 +175,13 @@ export function useAccountMutations(options: { }) const fileName = payload.fileName await downloadJson(payload, fileName) - ZNotification.success({ message: t('accounts.actions.exported', { count: selected.length }) }) + ZNotification.success({ + message: t('accounts.actions.exported', { count: selected.length }), + }) + }, + { + errorText: t('accounts.actions.exportFailed'), }, - { errorText: t('accounts.actions.exportFailed') }, ) } @@ -177,7 +192,9 @@ export function useAccountMutations(options: { const plan = account.planTypeDisplay.trim().replace(/[^\p{L}\p{N}_-]/gu, '_') || 'unknown-plan' const name = account.name.trim().replace(/[^\p{L}\p{N}_-]/gu, '_') || 'account' await downloadJson(result.catalog, `cpr-model-catalog-${plan}-${name}.json`) - ZNotification.success({ message: t('accounts.actions.modelCatalogDownloadedModels', { count: result.modelCount }) }) + ZNotification.success({ + message: t('accounts.actions.modelCatalogDownloadedModels', { count: result.modelCount }), + }) } catch {} }) @@ -200,7 +217,9 @@ export function useAccountMutations(options: { ZNotification.error({ message: result.error || t('accounts.actions.refreshFailed') }) return } - ZNotification.success({ message: t('accounts.actions.tokenRefreshed') }) + ZNotification.success({ + message: t('accounts.actions.tokenRefreshed'), + }) } catch {} }) @@ -209,9 +228,16 @@ export function useAccountMutations(options: { async function handleToggleScheduling(account: Account) { await togglingSchedulingAccounts.run(account.id, async () => { try { - await batchUpdateAccounts({ accountIds: [account.id], enabled: !account.enabled }) + await batchUpdateAccounts({ + accountIds: [account.id], + enabled: !account.enabled, + }) await loadAccounts() - ZNotification.success({ message: account.enabled ? t('accounts.actions.schedulingIsDisabled') : t('accounts.actions.schedulingIsEnabled') }) + ZNotification.success({ + message: account.enabled + ? t('accounts.actions.schedulingIsDisabled') + : t('accounts.actions.schedulingIsEnabled'), + }) } catch {} }) @@ -227,7 +253,9 @@ export function useAccountMutations(options: { selectedIds.delete(accountId) options.selectedIds.value = selectedIds } - ZNotification.success({ message: t('accounts.actions.quotaRefreshed') }) + ZNotification.success({ + message: t('accounts.actions.quotaRefreshed'), + }) } catch {} }) @@ -241,7 +269,12 @@ export function useAccountMutations(options: { await options.replaceAccount(result.account) } catch (error: unknown) { - ZNotification.warning({ message: t('accounts.actions.resetRefreshFailed', { value: errorMessage(error, t('accounts.actions.refreshQuotaManually')) }), ...({ duration: 5000 }) }) + ZNotification.warning({ + message: t('accounts.actions.resetRefreshFailed', { + value: errorMessage(error, t('accounts.actions.refreshQuotaManually')), + }), + ...({ duration: 5000 }), + }) } } @@ -255,7 +288,9 @@ export function useAccountMutations(options: { selectedIds.delete(accountId) options.selectedIds.value = selectedIds } - ZNotification.success({ message: t('accounts.actions.statusRestored') }) + ZNotification.success({ + message: t('accounts.actions.statusRestored'), + }) } catch {} }) diff --git a/frontend/src/views/accounts/composables/useAccountOnboarding.ts b/frontend/src/views/accounts/composables/useAccountOnboarding.ts index f2e36bfcf..cef97aa36 100644 --- a/frontend/src/views/accounts/composables/useAccountOnboarding.ts +++ b/frontend/src/views/accounts/composables/useAccountOnboarding.ts @@ -19,7 +19,9 @@ export function useAccountOnboarding(options: { const reauthorizingAccount = shallowRef(null) const creatingAccountAction = useAsyncAction() const createForm = ref(emptyAccountCreateForm()) - const authorization = useAccountAuthorization(() => finishCreate(reauthorizingAccount.value ? t('accounts.onboarding.reauthorized') : t('accounts.onboarding.accountAdded'))) + const authorization = useAccountAuthorization(() => finishCreate(reauthorizingAccount.value + ? t('accounts.onboarding.reauthorized') + : t('accounts.onboarding.accountAdded'))) let submissionIds: string[] = [] watch(createForm, () => { submissionIds = [] @@ -60,7 +62,9 @@ export function useAccountOnboarding(options: { if (proxyError) throw new Error(proxyError) const settings = accountImportSettings(form) - const outboundProxyId = form.proxyMode === 'proxy' ? form.proxyId.trim() : undefined + const outboundProxyId = form.proxyMode === 'proxy' + ? form.proxyId.trim() + : undefined const mode = form.mode if (mode === 'oauth') return @@ -75,7 +79,14 @@ export function useAccountOnboarding(options: { provider: 'openai', settings, outboundProxyId, - data: { provider: 'openai', authentication_kind: 'api_key', name: form.apiKey.name.trim(), base_url: form.apiKey.base_url.trim(), api_key: form.apiKey.apiKey, transport: form.apiKey.transport }, + data: { + provider: 'openai', + authentication_kind: 'api_key', + name: form.apiKey.name.trim(), + base_url: form.apiKey.base_url.trim(), + api_key: form.apiKey.apiKey, + transport: form.apiKey.transport, + }, }) await finishCreate(t('accounts.onboarding.apiKeyAccountAdded')) return @@ -85,7 +96,12 @@ export function useAccountOnboarding(options: { : accountImportDocuments(requireImportProvider(provider), mode, form.importTexts[mode]) for (const document of documents) requireImportProvider(document.provider) - const batches = accountImportBatches(documents.map(entry => ({ provider: entry.provider, data: entry.document, settings, outboundProxyId }))) + const batches = accountImportBatches(documents.map(entry => ({ + provider: entry.provider, + data: entry.document, + settings, + outboundProxyId, + }))) // 同一输入重试时复用每批标识,恢复丢失响应或部分提交,不重复执行 RT 交换 const ids = submissionIds for (const [index, items] of batches.entries()) { @@ -96,11 +112,18 @@ export function useAccountOnboarding(options: { showCreateModal.value = false options.onImportTaskCreated(task, finished) } - ZNotification.success({ message: submitted > 1 ? t('accounts.onboarding.importTasksCreated', { submitted }) : t('accounts.onboarding.importTaskCreated') }) + ZNotification.success({ + message: submitted > 1 + ? t('accounts.onboarding.importTasksCreated', { submitted }) + : t('accounts.onboarding.importTaskCreated'), + }) }, { onError: () => { - if (submitted) - ZNotification.warning({ message: t('accounts.onboarding.partialSubmission') }) + if (submitted) { + ZNotification.warning({ + message: t('accounts.onboarding.partialSubmission'), + }) + } }, }) } @@ -120,15 +143,25 @@ export function useAccountOnboarding(options: { await authorization.start({ start: { provider, - name: account?.name || account?.email || t('accounts.onboarding.accountName', { provider: formatProviderLabel(provider) }), + name: account?.name || account?.email || t('accounts.onboarding.accountName', { + provider: formatProviderLabel(provider), + }), accountId: account?.id, - outboundProxyId: account ? undefined : form.proxyMode === 'proxy' ? form.proxyId.trim() : undefined, + outboundProxyId: account + ? undefined + : form.proxyMode === 'proxy' + ? form.proxyId.trim() + : undefined, }, - settings: account ? undefined : accountImportSettings(form), + settings: account + ? undefined + : accountImportSettings(form), }) } catch (cause) { - ZNotification.error({ message: errorMessage(cause, t('accounts.onboarding.failedToStartAuthorization')) }) + ZNotification.error({ + message: errorMessage(cause, t('accounts.onboarding.failedToStartAuthorization')), + }) } } @@ -144,7 +177,11 @@ export function useAccountOnboarding(options: { return authorization.reset() reauthorizingAccount.value = account - createForm.value = { ...emptyAccountCreateForm(), source: { kind: 'provider', id: account.provider }, step: 'import' } + createForm.value = { + ...emptyAccountCreateForm(), + source: { kind: 'provider', id: account.provider }, + step: 'import', + } showCreateModal.value = true void handleAuthorizeOAuth() } @@ -158,7 +195,11 @@ export function useAccountOnboarding(options: { watch(() => accountCreateSourceKey(createForm.value), () => { createForm.value = { ...createForm.value, - mode: reauthorizingAccount.value ? 'oauth' : createForm.value.source?.kind === 'bundle' ? 'json' : accountImportModes(accountCreateProvider(createForm.value))[0]?.value ?? 'json', + mode: reauthorizingAccount.value + ? 'oauth' + : createForm.value.source?.kind === 'bundle' + ? 'json' + : accountImportModes(accountCreateProvider(createForm.value))[0]?.value ?? 'json', apiKey: emptyApiKeyAccountForm(), importTexts: { access_token: '', refresh_token: '', json: '' }, } @@ -169,7 +210,9 @@ export function useAccountOnboarding(options: { () => createForm.value.mode, () => createForm.value.step, () => createForm.value.proxyMode, - () => createForm.value.proxyMode === 'proxy' ? createForm.value.proxyId.trim() : '', + () => createForm.value.proxyMode === 'proxy' + ? createForm.value.proxyId.trim() + : '', ], () => { authorization.reset() }, { flush: 'sync' }) diff --git a/frontend/src/views/accounts/composables/useAccountPersonalInfo.ts b/frontend/src/views/accounts/composables/useAccountPersonalInfo.ts index 0ac6f134b..8a963be2d 100644 --- a/frontend/src/views/accounts/composables/useAccountPersonalInfo.ts +++ b/frontend/src/views/accounts/composables/useAccountPersonalInfo.ts @@ -13,8 +13,12 @@ export function useAccountPersonalInfo({ accountId, open, capabilities }: { const info = shallowRef(null) const request = useRequestState() const { loading } = request - const profile = computed(() => capabilities.value.profile ? info.value?.profile ?? null : null) - const subscription = computed(() => capabilities.value.subscription ? info.value?.subscription ?? null : null) + const profile = computed(() => capabilities.value.profile + ? info.value?.profile ?? null + : null) + const subscription = computed(() => capabilities.value.subscription + ? info.value?.subscription ?? null + : null) const error = computed(() => request.error.value || (capabilities.value.profile ? info.value?.profileError : '') || '') async function load() { @@ -39,7 +43,12 @@ export function useAccountPersonalInfo({ accountId, open, capabilities }: { } // 打开或切换账号只请求一次;关闭取消等待,刷新按钮复用同一入口。 - watch([open, accountId, () => capabilities.value.profile, () => capabilities.value.subscription], ([isOpen]) => { + watch([ + open, + accountId, + () => capabilities.value.profile, + () => capabilities.value.subscription, + ], ([isOpen]) => { request.invalidate({ resetLoading: isOpen }) if (!isOpen) return diff --git a/frontend/src/views/accounts/composables/useAccountQuotaForecast.ts b/frontend/src/views/accounts/composables/useAccountQuotaForecast.ts index bdf1915c4..6851bca94 100644 --- a/frontend/src/views/accounts/composables/useAccountQuotaForecast.ts +++ b/frontend/src/views/accounts/composables/useAccountQuotaForecast.ts @@ -51,8 +51,11 @@ export function useAccountQuotaForecast( if (disposed) return onAccountUpdated(result.account) - if (accountId.value === targetAccountId && await load()) - ZNotification.success({ message: t('accounts.actions.quotaRefreshed') }) + if (accountId.value === targetAccountId && await load()) { + ZNotification.success({ + message: t('accounts.actions.quotaRefreshed'), + }) + } } catch (cause) { if (!disposed && accountId.value === targetAccountId) { @@ -78,5 +81,12 @@ export function useAccountQuotaForecast( disposed = true }) - return { report, loading, refreshing, error, load, refresh } + return { + report, + loading, + refreshing, + error, + load, + refresh, + } } diff --git a/frontend/src/views/accounts/composables/useAccountResetCredits.ts b/frontend/src/views/accounts/composables/useAccountResetCredits.ts index c9b8e9e92..f808bbc53 100644 --- a/frontend/src/views/accounts/composables/useAccountResetCredits.ts +++ b/frontend/src/views/accounts/composables/useAccountResetCredits.ts @@ -190,7 +190,9 @@ export function useAccountResetCredits(options: { || (result.code === 'already_redeemed' && operation.hasTransportFailure) target.pendingOperation = null if (!confirmed) { - ZNotification.error({ message: resetResultMessage(result.code) }) + ZNotification.error({ + message: resetResultMessage(result.code), + }) await loadSessionCredits(target, true) return false } @@ -213,11 +215,16 @@ export function useAccountResetCredits(options: { ...operation, hasTransportFailure: true, } - ZNotification.warning({ message: t('accounts.quotaReset.resultUnknown'), ...({ duration: 5000 }) }) + ZNotification.warning({ + message: t('accounts.quotaReset.resultUnknown'), + ...({ duration: 5000 }), + }) } else { target.pendingOperation = null - ZNotification.error({ message: errorMessage(error, t('accounts.quotaReset.quotaResetFailed')) }) + ZNotification.error({ + message: errorMessage(error, t('accounts.quotaReset.quotaResetFailed')), + }) await loadSessionCredits(target, true) } return false diff --git a/frontend/src/views/accounts/composables/useAccountsQuery.ts b/frontend/src/views/accounts/composables/useAccountsQuery.ts index 966f66ec0..2579462f8 100644 --- a/frontend/src/views/accounts/composables/useAccountsQuery.ts +++ b/frontend/src/views/accounts/composables/useAccountsQuery.ts @@ -62,7 +62,9 @@ export function useAccountsQuery() { async function replaceAccount(updated: Account) { // 先取消旧查询并应用接口返回的账号,避免旧响应覆盖最新行数据。 query.invalidate() - query.items.value = query.items.value.map(account => account.id === updated.id ? updated : account) + query.items.value = query.items.value.map(account => account.id === updated.id + ? updated + : account) // 筛选、排序、概览和末页回退仍由回读校准,但不触发整表加载。 if (!await query.execute({ background: true })) diff --git a/frontend/src/views/accounts/constants.ts b/frontend/src/views/accounts/constants.ts index 641e7a36a..f2e0da6ec 100644 --- a/frontend/src/views/accounts/constants.ts +++ b/frontend/src/views/accounts/constants.ts @@ -40,12 +40,37 @@ export const accountColumns = computed(() => (defineTableColumns([ kind: 'meta', size: 'md', align: 'center', - formatter: value => formatProviderLabel(typeof value === 'string' ? value : null), + formatter: value => formatProviderLabel(typeof value === 'string' + ? value + : null), + }, + { + key: 'status', + label: t('common.fields.status'), + kind: 'status', + align: 'center', + sortable: true, + }, + { + key: 'planType', + label: t('accounts.profile.subscription.label'), + kind: 'status', + size: 'sm', + sortable: true, + }, + { + key: 'usage', + label: t('usage.usage'), + kind: 'custom', + size: '2xl', + sortable: true, + }, + { + key: 'groups', + label: t('accounts.fields.accountGrouping'), + kind: 'status', + size: 'sm', }, - { key: 'status', label: t('common.fields.status'), kind: 'status', align: 'center', sortable: true }, - { key: 'planType', label: t('accounts.profile.subscription.label'), kind: 'status', size: 'sm', sortable: true }, - { key: 'usage', label: t('usage.usage'), kind: 'custom', size: '2xl', sortable: true }, - { key: 'groups', label: t('accounts.fields.accountGrouping'), kind: 'status', size: 'sm' }, { key: 'lastUsedAt', label: t('accounts.fields.lastUsed'), @@ -62,7 +87,13 @@ export const accountColumns = computed(() => (defineTableColumns([ formatter: value => optionalAccountCell(value), emptyText: '', }, - { key: 'actions', label: t('common.fields.actions'), kind: 'actions', size: 'lg', hideable: false }, + { + key: 'actions', + label: t('common.fields.actions'), + kind: 'actions', + size: 'lg', + hideable: false, + }, ]))) export const statusLabels = computed>(() => ({ @@ -82,7 +113,10 @@ export const statusTones: Record ([ - { label: t('common.status.all'), value: '' }, + { + label: t('common.status.all'), + value: '', + }, { label: statusLabels.value.normal, value: 'normal' }, { label: statusLabels.value.quota_exhausted, value: 'quota_exhausted' }, { label: statusLabels.value.rate_limited, value: 'rate_limited' }, @@ -103,7 +137,9 @@ export function visibleSummaryQuotaWindows(windows: AccountQuotaWindow[]) { const known = [...windows] .filter(window => window.group !== 'other') .sort(compareQuotaWindows) - return known.length > 0 ? known : [...windows].sort(compareQuotaWindows) + return known.length > 0 + ? known + : [...windows].sort(compareQuotaWindows) } export function orderedPanelQuotaWindows(windows: AccountQuotaWindow[]) { @@ -117,7 +153,11 @@ export function groupedAccountQuotaWindows(windows: AccountQuotaWindow[]) { for (const window of windows) { const limitLabel = quotaLimitLabel(window) if (!window.limitId || !limitLabel) { - entries.push({ key: window.key, label: null, windows: [window] }) + entries.push({ + key: window.key, + label: null, + windows: [window], + }) continue } @@ -199,5 +239,7 @@ function quotaLimitLabel(window: AccountQuotaWindow) { } function optionalAccountCell(value: unknown) { - return value === '—' || value === '-' ? '' : value + return value === '—' || value === '-' + ? '' + : value } diff --git a/frontend/src/views/accounts/index.vue b/frontend/src/views/accounts/index.vue index affdcfce2..5e24fe361 100644 --- a/frontend/src/views/accounts/index.vue +++ b/frontend/src/views/accounts/index.vue @@ -70,7 +70,10 @@ const { } = useAccountGroupCatalog() const importTasks = useAccountImportTasks({ - reload: () => Promise.all([loadAccounts(), loadGroups()]), + reload: () => Promise.all([ + loadAccounts(), + loadGroups(), + ]), }) const { open: showImportTasks, @@ -123,7 +126,10 @@ const { onImportTaskCreated: importTasks.created, accounts, selectedIds, - reload: () => Promise.all([loadAccounts(), loadGroups()]), + reload: () => Promise.all([ + loadAccounts(), + loadGroups(), + ]), replaceAccount, }) diff --git a/frontend/src/views/accounts/utils/accountCreate.ts b/frontend/src/views/accounts/utils/accountCreate.ts index 4e2542912..1add9e9e4 100644 --- a/frontend/src/views/accounts/utils/accountCreate.ts +++ b/frontend/src/views/accounts/utils/accountCreate.ts @@ -43,11 +43,15 @@ export function emptyAccountCreateForm(): AccountCreateForm { } export function accountCreateProvider(form: AccountCreateForm) { - return form.source?.kind === 'provider' ? form.source.id : undefined + return form.source?.kind === 'provider' + ? form.source.id + : undefined } export function accountCreateSourceKey(form: AccountCreateForm) { - return form.source?.kind === 'provider' ? `provider:${form.source.id}` : form.source?.kind ?? '' + return form.source?.kind === 'provider' + ? `provider:${form.source.id}` + : form.source?.kind ?? '' } export function accountProxyError(form: AccountCreateForm): string | undefined { @@ -66,10 +70,17 @@ export function accountImportSettings(form: AccountCreateForm) { if (modelError) throw new Error(modelError) return { - modelAccess: form.modelAccess ? { ...form.modelAccess, models: [...form.modelAccess.models] } : undefined, + modelAccess: form.modelAccess + ? { + ...form.modelAccess, + models: [...form.modelAccess.models], + } + : undefined, enabled: form.enabled, ...scheduling.values, - groupIds: [...new Set(form.groupIds)], + groupIds: [ + ...new Set(form.groupIds), + ], notes: form.notes.trim() || undefined, } } @@ -78,13 +89,26 @@ export function accountImportModes(provider: string | undefined) { if (provider === 'openai') { return [ { label: 'OAuth', value: 'oauth' as const }, - { label: t('common.fields.apiKey'), value: 'api_key' as const }, + { + label: t('common.fields.apiKey'), + value: 'api_key' as const, + }, { label: 'AT', value: 'access_token' as const }, { label: 'RT', value: 'refresh_token' as const }, - { label: t('accounts.onboarding.accountFile'), value: 'json' as const }, + { + label: t('accounts.onboarding.accountFile'), + value: 'json' as const, + }, + ] + } + if (provider === 'xai') { + return [ + { label: 'OAuth', value: 'oauth' as const }, + { + label: t('accounts.onboarding.accountFile'), + value: 'json' as const, + }, ] } - if (provider === 'xai') - return [{ label: 'OAuth', value: 'oauth' as const }, { label: t('accounts.onboarding.accountFile'), value: 'json' as const }] return [] } diff --git a/frontend/src/views/accounts/utils/accountImport.ts b/frontend/src/views/accounts/utils/accountImport.ts index 671817bc4..cad8546b4 100644 --- a/frontend/src/views/accounts/utils/accountImport.ts +++ b/frontend/src/views/accounts/utils/accountImport.ts @@ -30,13 +30,23 @@ function splitAccountDocuments(entry: AccountImportDocument): AccountImportDocum if (!Array.isArray(data.accounts) || !data.accounts.length) return [entry] return data.accounts.map((account) => { - const item: Record = { ...data, accounts: [account] } + const item: Record = { + ...data, + accounts: [account], + } if (Array.isArray(data.proxies)) { // 保留同键的全部记录,让 Provider 继续拒绝重复引用,不复制其他账号的代理 - const key = isRecord(account) ? account.proxy_key : undefined + const key = isRecord(account) + ? account.proxy_key + : undefined item.proxies = data.proxies.filter(proxy => typeof key === 'string' && isRecord(proxy) && proxy.proxy_key === key) } - return { provider: entry.provider, document: data === document ? item : { ...document, data: item } } + return { + provider: entry.provider, + document: data === document + ? item + : { ...document, data: item }, + } }) } @@ -81,18 +91,32 @@ export function mixedImportDocuments(text: string): AccountImportDocument[] { export function accountImportDocuments(provider: string, mode: string, text: string): AccountImportDocument[] { if (provider === 'openai' && (mode === 'access_token' || mode === 'refresh_token')) { const tokens = text.split(/\r?\n/).map(token => token.trim()).filter(Boolean) - const label = mode === 'access_token' ? 'Access Token' : 'Refresh Token' + const label = mode === 'access_token' + ? 'Access Token' + : 'Refresh Token' if (!tokens.length) throw new Error(t('accounts.import.pasteAtLeastOne', { label })) - const key = mode === 'access_token' ? 'accessToken' : 'refreshToken' - return tokens.map(token => ({ provider, document: { accounts: [{ [key]: token }] } })) + const key = mode === 'access_token' + ? 'accessToken' + : 'refreshToken' + return tokens.map(token => ({ + provider, + document: { + accounts: [ + { [key]: token }, + ], + }, + })) } const value = parseJson(text) // 原生快捷入口保留 CPR 文件展开,插件文档的字段含义由插件自己解释。 if (isSupportedProvider(provider) && isRecord(value) && Array.isArray(value.documents)) { const documents = mixedImportDocuments(text).filter(entry => entry.provider === provider) - if (!documents.length) - throw new Error(t('accounts.import.missingProvider', { provider: formatProviderLabel(provider) })) + if (!documents.length) { + throw new Error(t('accounts.import.missingProvider', { + provider: formatProviderLabel(provider), + })) + } return documents } if (!isRecord(value)) diff --git a/frontend/src/views/accounts/utils/schedulingForm.ts b/frontend/src/views/accounts/utils/schedulingForm.ts index 5784236d7..a4ae31d37 100644 --- a/frontend/src/views/accounts/utils/schedulingForm.ts +++ b/frontend/src/views/accounts/utils/schedulingForm.ts @@ -16,19 +16,27 @@ export function parseAccountSchedulingForm( weight: string, ): AccountSchedulingParseResult { const limitText = concurrencyLimit.trim() - const parsedLimit = limitText === '' ? null : Number(limitText) + const parsedLimit = limitText === '' + ? null + : Number(limitText) if ( parsedLimit !== null && (!Number.isSafeInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > MAX_ACCOUNT_CONCURRENCY) ) { - return { valid: false, message: t('accounts.scheduling.invalidConcurrency', { value: MAX_ACCOUNT_CONCURRENCY }) } + return { + valid: false, + message: t('accounts.scheduling.invalidConcurrency', { value: MAX_ACCOUNT_CONCURRENCY }), + } } const parsedWeight = Number(weight.trim()) if (!Number.isSafeInteger(parsedWeight) || parsedWeight < 1 || parsedWeight > 100) { - return { valid: false, message: t('accounts.scheduling.weightMustBeIntegerFromTo') } + return { + valid: false, + message: t('accounts.scheduling.weightMustBeIntegerFromTo'), + } } return { @@ -41,5 +49,7 @@ export function parseAccountSchedulingForm( } export function concurrencyLimitInput(value: number | null) { - return value === null ? '' : String(value) + return value === null + ? '' + : String(value) } diff --git a/frontend/src/views/accounts/utils/upstreamApiKey.ts b/frontend/src/views/accounts/utils/upstreamApiKey.ts index ef210ccdd..7aab407e0 100644 --- a/frontend/src/views/accounts/utils/upstreamApiKey.ts +++ b/frontend/src/views/accounts/utils/upstreamApiKey.ts @@ -16,7 +16,12 @@ export interface ApiKeyAccountForm extends Omit | undefined): ApiKeyConfiguration | undefined { diff --git a/frontend/src/views/dashboard/components/AccountOverviewCard.vue b/frontend/src/views/dashboard/components/AccountOverviewCard.vue index 62592bd10..1b2b1b1b2 100644 --- a/frontend/src/views/dashboard/components/AccountOverviewCard.vue +++ b/frontend/src/views/dashboard/components/AccountOverviewCard.vue @@ -31,9 +31,24 @@ const scheduleStats = computed(() => { ? '—' : formatCompactNumber(value) return [ - { label: t('dashboard.scheduling.defaultConcurrency'), value: cap?.maxConcurrentPerAccount === 0 ? t('common.values.unlimited') : display(cap?.maxConcurrentPerAccount) }, - { label: t('dashboard.scheduling.totalSlots'), value: unlimitedCapacity.value ? t('common.values.unlimited') : display(cap?.totalSlots) }, - { label: t('dashboard.scheduling.freeSlots'), value: unlimitedCapacity.value ? t('common.values.unlimited') : display(cap?.availableSlots) }, + { + label: t('dashboard.scheduling.defaultConcurrency'), + value: cap?.maxConcurrentPerAccount === 0 + ? t('common.values.unlimited') + : display(cap?.maxConcurrentPerAccount), + }, + { + label: t('dashboard.scheduling.totalSlots'), + value: unlimitedCapacity.value + ? t('common.values.unlimited') + : display(cap?.totalSlots), + }, + { + label: t('dashboard.scheduling.freeSlots'), + value: unlimitedCapacity.value + ? t('common.values.unlimited') + : display(cap?.availableSlots), + }, ] }) @@ -44,14 +59,19 @@ const usedProgressStyle = computed(() => { return { width: `${(capacity.usedSlots / capacity.totalSlots) * 100}%`, - minWidth: capacity.usedSlots > 0 ? '3px' : undefined, + minWidth: capacity.usedSlots > 0 + ? '3px' + : undefined, } }) const usedRatio = computed(() => { const cap = props.capacity - if (unlimitedCapacity.value) - return `${cap?.usedSlots === null || cap?.usedSlots === undefined ? '—' : formatCompactNumber(cap.usedSlots)} / ∞` + if (unlimitedCapacity.value) { + return `${cap?.usedSlots === null || cap?.usedSlots === undefined + ? '—' + : formatCompactNumber(cap.usedSlots)} / ∞` + } if (!cap || cap.totalSlots === null || cap.totalSlots === undefined) return '— / —' if (cap.usedSlots === null || cap.usedSlots === undefined) @@ -105,10 +125,34 @@ const statusRows = computed(() => { const counts = accountStatusCounts.value if (!p || !counts) { return [ - { label: t('common.status.healthy'), description: t('dashboard.accountPool.empty'), value: '—', tone: 'success', icon: CircleCheck }, - { label: t('dashboard.accountPool.quotaExhausted'), description: t('dashboard.accountPool.empty'), value: '—', tone: 'warning', icon: TriangleAlert }, - { label: t('dashboard.accountPool.rateLimited'), description: t('dashboard.accountPool.empty'), value: '—', tone: 'normal', icon: RefreshCw }, - { label: t('dashboard.accountPool.pending'), description: t('dashboard.accountPool.issueCountsEmpty'), value: '—', tone: 'danger', icon: ShieldAlert }, + { + label: t('common.status.healthy'), + description: t('dashboard.accountPool.empty'), + value: '—', + tone: 'success', + icon: CircleCheck, + }, + { + label: t('dashboard.accountPool.quotaExhausted'), + description: t('dashboard.accountPool.empty'), + value: '—', + tone: 'warning', + icon: TriangleAlert, + }, + { + label: t('dashboard.accountPool.rateLimited'), + description: t('dashboard.accountPool.empty'), + value: '—', + tone: 'normal', + icon: RefreshCw, + }, + { + label: t('dashboard.accountPool.pending'), + description: t('dashboard.accountPool.issueCountsEmpty'), + value: '—', + tone: 'danger', + icon: ShieldAlert, + }, ] } diff --git a/frontend/src/views/dashboard/components/DashboardAccountIdentity.vue b/frontend/src/views/dashboard/components/DashboardAccountIdentity.vue index 175efc0db..1c8d62e5d 100644 --- a/frontend/src/views/dashboard/components/DashboardAccountIdentity.vue +++ b/frontend/src/views/dashboard/components/DashboardAccountIdentity.vue @@ -20,7 +20,9 @@ type DashboardAccount = DashboardSnapshot['accountUsage'][number] const email = computed(() => props.account.email?.trim() || String(props.account.id)) const displayTitle = computed(() => email.value.split('@')[0] || email.value) const normalizedProvider = computed(() => props.account.provider?.trim().toLowerCase() || '') -const providerIconComponent = computed(() => normalizedProvider.value ? providerIcon(normalizedProvider.value) : undefined) +const providerIconComponent = computed(() => normalizedProvider.value + ? providerIcon(normalizedProvider.value) + : undefined) const authenticationIconComponent = computed(() => authenticationIcon(props.account.authenticationKind)) const providerLabel = computed(() => formatProviderLabel(props.account.provider, t('common.providers.unknown'))) diff --git a/frontend/src/views/dashboard/components/DashboardHeartbeat.vue b/frontend/src/views/dashboard/components/DashboardHeartbeat.vue index 675f8ec7a..d5a9b20ba 100644 --- a/frontend/src/views/dashboard/components/DashboardHeartbeat.vue +++ b/frontend/src/views/dashboard/components/DashboardHeartbeat.vue @@ -20,7 +20,9 @@ const preferredMotion = usePreferredReducedMotion() const heartbeatDot = useTemplateRef('heartbeatDot') const heartbeatRing = useTemplateRef('heartbeatRing') const statusLabel = computed(() => - props.updatedAt ? t('dashboard.refresh.lastUpdated', { updatedAt: props.updatedAt }) : t('dashboard.refresh.waiting'), + props.updatedAt + ? t('dashboard.refresh.lastUpdated', { updatedAt: props.updatedAt }) + : t('dashboard.refresh.waiting'), ) function pulse() { diff --git a/frontend/src/views/dashboard/components/MetricCard.vue b/frontend/src/views/dashboard/components/MetricCard.vue index 80695bb6e..aed109d75 100644 --- a/frontend/src/views/dashboard/components/MetricCard.vue +++ b/frontend/src/views/dashboard/components/MetricCard.vue @@ -45,8 +45,17 @@ const sparklineOption = computed(() => { return { animation: false, grid: { left: 0, right: 0, top: 4, bottom: 4 }, - xAxis: { type: 'category', show: false, data: values.map((_, index) => index) }, - yAxis: { type: 'value', show: false, min: 'dataMin', max: 'dataMax' }, + xAxis: { + type: 'category', + show: false, + data: values.map((_, index) => index), + }, + yAxis: { + type: 'value', + show: false, + min: 'dataMin', + max: 'dataMax', + }, series: [ { type: 'line', diff --git a/frontend/src/views/dashboard/components/WireProfileCard.vue b/frontend/src/views/dashboard/components/WireProfileCard.vue index d24622e70..1c0e7bbcb 100644 --- a/frontend/src/views/dashboard/components/WireProfileCard.vue +++ b/frontend/src/views/dashboard/components/WireProfileCard.vue @@ -44,14 +44,18 @@ const releaseLabel = computed(() => { const release = profile.value?.release if (!release?.latestVersion) return t('dashboard.clientIdentity.notChecked') - return release.latestBuild ? `${release.latestVersion} · Build ${release.latestBuild}` : release.latestVersion + return release.latestBuild + ? `${release.latestVersion} · Build ${release.latestBuild}` + : release.latestVersion }) const releaseStatus = computed(() => { const current = profile.value if (!current?.release) { return { - label: current?.attributes.some(item => item.key === 'version_policy' && item.value === 'fixed') ? t('dashboard.clientIdentity.customVersion') : t('dashboard.clientIdentity.active'), + label: current?.attributes.some(item => item.key === 'version_policy' && item.value === 'fixed') + ? t('dashboard.clientIdentity.customVersion') + : t('dashboard.clientIdentity.active'), title: t('dashboard.clientIdentity.activeDescription'), tone: 'bg-cp-info-container text-cp-info-on-container', icon: ShieldCheck, @@ -93,12 +97,16 @@ const releaseStatus = computed(() => { const verifiedLabel = computed(() => { const verifiedAt = profile.value?.verifiedAtDisplay - return verifiedAt ? t('dashboard.clientIdentity.verifiedAt', { verifiedAt }) : undefined + return verifiedAt + ? t('dashboard.clientIdentity.verifiedAt', { verifiedAt }) + : undefined }) const checkedLabel = computed(() => { const checkedAt = profile.value?.release?.checkedAtDisplay - return checkedAt ? t('dashboard.clientIdentity.checkedAt', { checkedAt }) : undefined + return checkedAt + ? t('dashboard.clientIdentity.checkedAt', { checkedAt }) + : undefined }) function toPascalCase(value: string) { @@ -116,7 +124,9 @@ const clientIdentity = computed(() => { const value = current.attributes.find(attribute => attribute.key === 'client_identifier')?.value if (!value) return '—' - return current.provider === 'xai' ? toPascalCase(value) : value + return current.provider === 'xai' + ? toPascalCase(value) + : value }) const attributeLabels = computed>(() => ({ @@ -131,7 +141,12 @@ const identityAttributes = computed(() => { if (!current) return [] return isSupportedProvider(current.provider) - ? [{ label: t('dashboard.clientIdentity.clientId'), value: clientIdentity.value }] + ? [ + { + label: t('dashboard.clientIdentity.clientId'), + value: clientIdentity.value, + }, + ] : current.attributes.map(attribute => ({ label: attributeLabels.value[attribute.key] ?? attribute.key, value: attribute.value, diff --git a/frontend/src/views/dashboard/composables/useDashboard.ts b/frontend/src/views/dashboard/composables/useDashboard.ts index af8532751..1a27ad93f 100644 --- a/frontend/src/views/dashboard/composables/useDashboard.ts +++ b/frontend/src/views/dashboard/composables/useDashboard.ts @@ -33,7 +33,9 @@ export function useDashboard() { const capacityInfo = computed(() => snapshot.value.capacityInfo) const rotationStrategy = computed(() => snapshot.value.rotationStrategy) const trendView = computed(() => dashboardTrendView( - trend.value?.kind === activeTrendKind.value ? trend.value : null, + trend.value?.kind === activeTrendKind.value + ? trend.value + : null, )) const trendPoints = computed(() => trendView.value.points) const trendSummary = computed(() => trendView.value.summary) diff --git a/frontend/src/views/dashboard/composables/useRequestTrendChart.ts b/frontend/src/views/dashboard/composables/useRequestTrendChart.ts index 460b87590..73a6e888a 100644 --- a/frontend/src/views/dashboard/composables/useRequestTrendChart.ts +++ b/frontend/src/views/dashboard/composables/useRequestTrendChart.ts @@ -43,9 +43,18 @@ interface BarSeriesOptions { } const tabs = computed(() => ([ - { label: t('usage.usage'), value: 'usage' }, - { label: t('dashboard.trends.latency'), value: 'latency' }, - { label: t('dashboard.trends.errors'), value: 'errors' }, + { + label: t('usage.usage'), + value: 'usage', + }, + { + label: t('dashboard.trends.latency'), + value: 'latency', + }, + { + label: t('dashboard.trends.errors'), + value: 'errors', + }, ])) export function useRequestTrendChart(options: { @@ -84,8 +93,12 @@ export function useRequestTrendChart(options: { return { ...getCoordinateSystem(buckets), series: getSeries(), - animationDuration: preferredMotion.value === 'reduce' ? 0 : 420, - animationDurationUpdate: preferredMotion.value === 'reduce' ? 0 : 220, + animationDuration: preferredMotion.value === 'reduce' + ? 0 + : 420, + animationDurationUpdate: preferredMotion.value === 'reduce' + ? 0 + : 220, animationEasing: 'cubicOut', animationEasingUpdate: 'cubicOut', tooltip: { @@ -108,7 +121,9 @@ export function useRequestTrendChart(options: { return { grid: { left: 8, - right: activeKind.value === 'latency' ? 8 : 10, + right: activeKind.value === 'latency' + ? 8 + : 10, top: 10, bottom: 2, outerBoundsMode: 'same' as const, @@ -118,7 +133,10 @@ export function useRequestTrendChart(options: { type: 'category' as const, data: buckets, boundaryGap: activeKind.value === 'errors', - axisLine: { show: true, lineStyle: { color: axisLine } }, + axisLine: { + show: true, + lineStyle: { color: axisLine }, + }, axisTick: { show: false }, axisLabel: { color: muted, @@ -149,7 +167,9 @@ export function useRequestTrendChart(options: { { type: 'value' as const, min: 0, - max: activeKind.value === 'errors' ? 100 : undefined, + max: activeKind.value === 'errors' + ? 100 + : undefined, splitNumber: 3, axisLine: { show: false }, axisTick: { show: false }, @@ -170,7 +190,9 @@ export function useRequestTrendChart(options: { function formatPrimaryAxisValue(value: number) { if (activeKind.value === 'latency') { - return value >= 1000 ? `${formatAxisNumber(value / 1000)} s` : `${Math.round(value)} ms` + return value >= 1000 + ? `${formatAxisNumber(value / 1000)} s` + : `${Math.round(value)} ms` } return formatAxisCompact(value) } @@ -184,7 +206,9 @@ export function useRequestTrendChart(options: { } function formatTooltip(params: unknown) { - const rows = Array.isArray(params) ? params : [params] + const rows = Array.isArray(params) + ? params + : [params] const point = points.value[tooltipIndex(rows[0])] const title = point?.label ?? '' @@ -223,7 +247,9 @@ export function useRequestTrendChart(options: { case 'latencyP95Ms': return formatLatency(point?.latencyP95Ms, value) case 'outputThroughputP50': - return point?.outputThroughputP50 == null ? value : `${point.outputThroughputP50} tok/s` + return point?.outputThroughputP50 == null + ? value + : `${point.outputThroughputP50} tok/s` case 'errors': return point?.errors ?? value case 'successRate': @@ -275,19 +301,34 @@ export function useRequestTrendChart(options: { 'latencyP95Ms', activeSeriesValues('latencyP95Ms'), trendColor('latencyP95Ms'), - { lineType: 'dashed', smooth: 0.2, width: 1.8, z: 2 }, + { + lineType: 'dashed', + smooth: 0.2, + width: 1.8, + z: 2, + }, ), ...gapLineSeries( 'firstTokenP95Ms', activeSeriesValues('firstTokenP95Ms'), trendColor('firstTokenP95Ms'), - { area: true, smooth: 0.24, width: 2.5, z: 4 }, + { + area: true, + smooth: 0.24, + width: 2.5, + z: 4, + }, ), ...gapLineSeries( 'outputThroughputP50', activeSeriesValues('outputThroughputP50'), trendColor('outputThroughputP50'), - { smooth: 0.2, width: 2, yAxisIndex: 1, z: 3 }, + { + smooth: 0.2, + width: 2, + yAxisIndex: 1, + z: 3, + }, ), ] } @@ -319,7 +360,9 @@ export function useRequestTrendChart(options: { function activeSeriesValues(key: string) { return points.value.map(point => - Number(point.requestsValue) > 0 ? trendPointNumber(point, key) : 0, + Number(point.requestsValue) > 0 + ? trendPointNumber(point, key) + : 0, ) } @@ -351,7 +394,10 @@ export function useRequestTrendChart(options: { width: lineOptions.width ?? 2.2, opacity: muted ? 0.18 : 1, }, - itemStyle: { color, opacity: muted ? 0.18 : 1 }, + itemStyle: { + color, + opacity: muted ? 0.18 : 1, + }, emphasis: { disabled: true }, areaStyle: area ? { @@ -411,7 +457,9 @@ export function useRequestTrendChart(options: { z: barOptions.z ?? 2, itemStyle: { color, - opacity: muted ? 0.14 : (barOptions.opacity ?? 0.72), + opacity: muted + ? 0.14 + : (barOptions.opacity ?? 0.72), borderRadius: barOptions.borderRadius ?? [3, 3, 0, 0], }, emphasis: { disabled: true }, @@ -419,7 +467,9 @@ export function useRequestTrendChart(options: { } function summaryMarkerStyle(item: TrendSummaryItem) { - return item.colorVar ? { backgroundColor: `var(${item.colorVar})` } : undefined + return item.colorVar + ? { backgroundColor: `var(${item.colorVar})` } + : undefined } function trendColor(metric: DashboardTrendMetric) { @@ -431,7 +481,9 @@ export function useRequestTrendChart(options: { } function toggleSummarySeries(metric: DashboardTrendMetric) { - pinnedSummaryMetric.value = pinnedSummaryMetric.value === metric ? undefined : metric + pinnedSummaryMetric.value = pinnedSummaryMetric.value === metric + ? undefined + : metric } function isSeriesMuted(metric: DashboardTrendMetric) { @@ -470,7 +522,9 @@ function formatAxisCompact(value: number) { } function formatAxisNumber(value: number) { - return value >= 10 ? value.toFixed(0) : value.toFixed(1).replace(/\.0$/, '') + return value >= 10 + ? value.toFixed(0) + : value.toFixed(1).replace(/\.0$/, '') } function formatLatency(value: number | null | undefined, fallback: string) { @@ -494,7 +548,9 @@ function tooltipValue(source: unknown, key: string) { if (typeof source !== 'object' || source === null || !(key in source)) return '' const value = (source as Record)[key] - return typeof value === 'number' || typeof value === 'string' ? String(value) : '' + return typeof value === 'number' || typeof value === 'string' + ? String(value) + : '' } function trendPointProperty(point: TrendPoint | undefined, key: string) { @@ -505,7 +561,9 @@ function trendPointProperty(point: TrendPoint | undefined, key: string) { function trendPointText(point: TrendPoint | undefined, key: string) { const value = trendPointProperty(point, key) - return typeof value === 'string' ? value : undefined + return typeof value === 'string' + ? value + : undefined } function zeroAwareRateText( @@ -521,5 +579,7 @@ function zeroAwareRateText( function trendPointNumber(point: TrendPoint, key: string) { const value = trendPointProperty(point, key) - return typeof value === 'number' && Number.isFinite(value) ? value : null + return typeof value === 'number' && Number.isFinite(value) + ? value + : null } diff --git a/frontend/src/views/dashboard/presenter.ts b/frontend/src/views/dashboard/presenter.ts index d1f89e7b6..ec643ed70 100644 --- a/frontend/src/views/dashboard/presenter.ts +++ b/frontend/src/views/dashboard/presenter.ts @@ -107,12 +107,17 @@ export function dashboardTrendView(trend: DashboardTrendResponse | null) { if (trend.kind === 'usage') { const points = aggregateUsageTrend(trend.points) - return { points, summary: usageTrendSummary(points) } + return { + points, + summary: usageTrendSummary(points), + } } const summary = trend.summary.map(item => ({ ...trendMetricView(item.metric), - value: trend.kind === 'errors' && item.ratio !== null ? item.ratio : item.value, + value: trend.kind === 'errors' && item.ratio !== null + ? item.ratio + : item.value, })) return { points: trend.points, summary } } @@ -162,10 +167,20 @@ function metricCards( recentPoints.map(point => point.requestsValue), 'info', ), - trend: hasTodayRequests ? trendState(traffic.todayRequestsValue, traffic.yesterdayRequestsValue, 'info') : undefined, + trend: hasTodayRequests + ? trendState(traffic.todayRequestsValue, traffic.yesterdayRequestsValue, 'info') + : undefined, details: [ - { label: t('dashboard.metrics.totalRequests'), value: traffic.totalRequests, tone: 'info' }, - { label: t('usage.averageTtft'), value: cache.averageFirstTokenLatencyMs, tone: 'info' }, + { + label: t('dashboard.metrics.totalRequests'), + value: traffic.totalRequests, + tone: 'info', + }, + { + label: t('usage.averageTtft'), + value: cache.averageFirstTokenLatencyMs, + tone: 'info', + }, ], }, { @@ -179,10 +194,20 @@ function metricCards( recentPoints.map(point => point.tokensValue), 'success', ), - trend: hasTodayRequests ? trendState(tokens.todayTokensValue, tokens.yesterdayTokensValue, 'success') : undefined, + trend: hasTodayRequests + ? trendState(tokens.todayTokensValue, tokens.yesterdayTokensValue, 'success') + : undefined, details: [ - { label: t('dashboard.metrics.totalTokens'), value: tokens.totalTokens, tone: 'success' }, - { label: t('dashboard.metrics.totalCharges'), value: formatDashboardUsd(tokens.totalBillingAmountUsd), tone: 'success' }, + { + label: t('dashboard.metrics.totalTokens'), + value: tokens.totalTokens, + tone: 'success', + }, + { + label: t('dashboard.metrics.totalCharges'), + value: formatDashboardUsd(tokens.totalBillingAmountUsd), + tone: 'success', + }, ], }, { @@ -191,7 +216,9 @@ function metricCards( valueRaw: cache.todayHitRateValue, valueFormatter: formatDashboardRate, icon: Timer, - tone: cache.todayHitRateValue && cache.todayHitRateValue > 0 ? 'warning' : 'normal', + tone: cache.todayHitRateValue && cache.todayHitRateValue > 0 + ? 'warning' + : 'normal', sparkline: sparkline( recentPoints.map(point => point.cacheHitRateValue), 'warning', @@ -200,8 +227,16 @@ function metricCards( ? trendState(cache.todayHitRateValue, cache.yesterdayHitRateValue, 'warning') : undefined, details: [ - { label: t('dashboard.metrics.totalCacheHits'), value: cache.totalHitRate, tone: 'warning' }, - { label: t('dashboard.metrics.totalCache'), value: cache.totalCachedInputTokens, tone: 'warning' }, + { + label: t('dashboard.metrics.totalCacheHits'), + value: cache.totalHitRate, + tone: 'warning', + }, + { + label: t('dashboard.metrics.totalCache'), + value: cache.totalCachedInputTokens, + tone: 'warning', + }, ], }, ] @@ -212,7 +247,9 @@ function formatAccountCount(value: number) { } function sparkline(values: number[], tone: MetricTone) { - return values.some(value => value > 0) ? { values, tone } : undefined + return values.some(value => value > 0) + ? { values, tone } + : undefined } function recentTrendWindow(points: DashboardTrendPoint[]) { @@ -227,11 +264,15 @@ function recentTrendWindow(points: DashboardTrendPoint[]) { } function formatDashboardRate(value: number) { - return Number.isFinite(value) ? `${(value * 100).toFixed(1)}%` : '—' + return Number.isFinite(value) + ? `${(value * 100).toFixed(1)}%` + : '—' } function aggregateUsageTrend(points: DashboardTrendPoint[]) { - return Array.from({ length: Math.ceil(points.length / 2) }, (_, groupIndex) => { + return Array.from({ + length: Math.ceil(points.length / 2), + }, (_, groupIndex) => { const group = points.slice(groupIndex * 2, groupIndex * 2 + 2) const first = group[0] const requestsValue = sum(group, point => point.requestsValue) @@ -244,7 +285,9 @@ function aggregateUsageTrend(points: DashboardTrendPoint[]) { ) const uncachedInputTokensValue = inputTokensValue - cachedInputTokensValue const effectiveTokensValue = uncachedInputTokensValue + outputTokensValue - const cacheHitRateValue = inputTokensValue ? cachedInputTokensValue / inputTokensValue : 0 + const cacheHitRateValue = inputTokensValue + ? cachedInputTokensValue / inputTokensValue + : 0 const successRateValue = requestsValue ? ((requestsValue - errorsValue) / requestsValue) * 100 : 0 @@ -262,7 +305,9 @@ function aggregateUsageTrend(points: DashboardTrendPoint[]) { uncachedInputTokens: formatCompactNumber(uncachedInputTokensValue), uncachedInputTokensValue, effectiveTokens: formatCompactNumber(effectiveTokensValue), - effectiveTokensValue: effectiveTokensValue > 0 ? effectiveTokensValue : null, + effectiveTokensValue: effectiveTokensValue > 0 + ? effectiveTokensValue + : null, cacheHitRate: formatDashboardRate(cacheHitRateValue), cacheHitRateValue, tokensValue: inputTokensValue + outputTokensValue, @@ -325,20 +370,58 @@ function trendState( return { direction: 'up', tone: 'success' } if (current < previous) return { direction: 'down', tone: 'danger' } - return previous > 0 || current > 0 ? { direction: 'flat', tone: fallbackTone } : undefined + return previous > 0 || current > 0 + ? { direction: 'flat', tone: fallbackTone } + : undefined } // 展示与交互共享指标身份,切换语言不改变高亮或颜色 const trendMetrics = computed(() => ({ - inputTokens: { label: t('dashboard.metrics.inputTokens'), tone: 'info', colorVar: '--cp-color-blue-solid' }, - outputTokens: { label: t('dashboard.metrics.outputTokens'), tone: 'success', colorVar: '--cp-color-green-solid' }, - cachedInputTokens: { label: t('dashboard.metrics.cachedInputTokens'), tone: 'normal', colorVar: '--cp-color-text-tertiary' }, - firstTokenP95Ms: { label: t('dashboard.metrics.firstTokenP95Ms'), tone: 'normal', colorVar: '--cp-color-cyan-solid' }, - latencyP95Ms: { label: t('dashboard.metrics.latencyP95Ms'), tone: 'warning', colorVar: '--cp-color-orange-solid' }, - outputThroughputP50: { label: t('dashboard.metrics.outputThroughputP50'), tone: 'success', colorVar: '--cp-color-green-solid' }, - errors: { label: t('dashboard.metrics.errors'), tone: 'danger', colorVar: '--cp-color-red-solid' }, - successRate: { label: t('dashboard.metrics.successRate'), tone: 'success', colorVar: '--cp-color-green-solid' }, - requests: { label: t('dashboard.metrics.totalRequests'), tone: 'info', colorVar: '--cp-color-blue-solid' }, + inputTokens: { + label: t('dashboard.metrics.inputTokens'), + tone: 'info', + colorVar: '--cp-color-blue-solid', + }, + outputTokens: { + label: t('dashboard.metrics.outputTokens'), + tone: 'success', + colorVar: '--cp-color-green-solid', + }, + cachedInputTokens: { + label: t('dashboard.metrics.cachedInputTokens'), + tone: 'normal', + colorVar: '--cp-color-text-tertiary', + }, + firstTokenP95Ms: { + label: t('dashboard.metrics.firstTokenP95Ms'), + tone: 'normal', + colorVar: '--cp-color-cyan-solid', + }, + latencyP95Ms: { + label: t('dashboard.metrics.latencyP95Ms'), + tone: 'warning', + colorVar: '--cp-color-orange-solid', + }, + outputThroughputP50: { + label: t('dashboard.metrics.outputThroughputP50'), + tone: 'success', + colorVar: '--cp-color-green-solid', + }, + errors: { + label: t('dashboard.metrics.errors'), + tone: 'danger', + colorVar: '--cp-color-red-solid', + }, + successRate: { + label: t('dashboard.metrics.successRate'), + tone: 'success', + colorVar: '--cp-color-green-solid', + }, + requests: { + label: t('dashboard.metrics.totalRequests'), + tone: 'info', + colorVar: '--cp-color-blue-solid', + }, } satisfies Record)) export function trendMetricView(metric: DashboardTrendMetric) { diff --git a/frontend/src/views/groups/components/AccountGroupFormModal.vue b/frontend/src/views/groups/components/AccountGroupFormModal.vue index f6135c700..9f55cc804 100644 --- a/frontend/src/views/groups/components/AccountGroupFormModal.vue +++ b/frontend/src/views/groups/components/AccountGroupFormModal.vue @@ -21,7 +21,9 @@ const { t } = useI18n({ useScope: 'global' }) const open = defineModel({ required: true }) const form = defineModel('form', { required: true }) -const title = computed(() => props.group ? t('groups.actions.editGroup') : t('groups.filters.createGroup')) +const title = computed(() => props.group + ? t('groups.actions.editGroup') + : t('groups.filters.createGroup')) const description = computed(() => props.group ? t('groups.editor.editDescription') : t('groups.editor.createDescription')) diff --git a/frontend/src/views/groups/components/AccountGroupMetricsCell.vue b/frontend/src/views/groups/components/AccountGroupMetricsCell.vue index fa9eafc26..aa64e588f 100644 --- a/frontend/src/views/groups/components/AccountGroupMetricsCell.vue +++ b/frontend/src/views/groups/components/AccountGroupMetricsCell.vue @@ -62,14 +62,20 @@ const metrics = computed(() => { { label: t('groups.metrics.concurrency'), value: `${props.group.capacity.usedSlots ?? '—'} / ${props.group.capacity.totalSlots ?? '∞'}`, - tone: (props.group.capacity.usedSlots ?? 0) > 0 ? 'active' : 'primary', + tone: (props.group.capacity.usedSlots ?? 0) > 0 + ? 'active' + : 'primary', icon: Grid2X2, }, ] } return [ - { label: t('groups.metrics.today'), value: formatUsd(props.group.usage.todayUsd), tone: 'primary' }, + { + label: t('groups.metrics.today'), + value: formatUsd(props.group.usage.todayUsd), + tone: 'primary', + }, { label: t('groups.metrics.cumulative'), value: formatUsd(props.group.usage.retainedTotalUsd), diff --git a/frontend/src/views/groups/composables/useAccountGroups.ts b/frontend/src/views/groups/composables/useAccountGroups.ts index 1e78003f1..3a3115abf 100644 --- a/frontend/src/views/groups/composables/useAccountGroups.ts +++ b/frontend/src/views/groups/composables/useAccountGroups.ts @@ -56,7 +56,9 @@ export function useAccountGroups() { page, pageSize, search: searchQuery.value.trim() || undefined, - enabled: statusQuery.value ? statusQuery.value === 'enabled' : undefined, + enabled: statusQuery.value + ? statusQuery.value === 'enabled' + : undefined, }, options), }) @@ -135,12 +137,16 @@ export function useAccountGroups() { return const name = form.value.name.trim() if (!name) { - ZNotification.warning({ message: t('groups.actions.enterGroupName') }) + ZNotification.warning({ + message: t('groups.actions.enterGroupName'), + }) return } const color = normalizeRgbaHexColor(form.value.color) if (!color) { - ZNotification.warning({ message: t('groups.actions.selectValidGroupingColor') }) + ZNotification.warning({ + message: t('groups.actions.selectValidGroupingColor'), + }) return } @@ -150,7 +156,14 @@ export function useAccountGroups() { const fastMode = form.value.fastMode const allowImageGeneration = form.value.allowImageGeneration if (editingGroup.value) { - await updateAccountGroup({ id: editingGroup.value.id, name, description, color, fastMode, allowImageGeneration }) + await updateAccountGroup({ + id: editingGroup.value.id, + name, + description, + color, + fastMode, + allowImageGeneration, + }) } else { await createAccountGroup({ @@ -162,8 +175,15 @@ export function useAccountGroups() { }) } showFormModal.value = false - await Promise.all([query.execute(), loadReferenceKeys()]) - ZNotification.success({ message: updating ? t('groups.actions.updated') : t('groups.actions.created') }) + await Promise.all([ + query.execute(), + loadReferenceKeys(), + ]) + ZNotification.success({ + message: updating + ? t('groups.actions.updated') + : t('groups.actions.created'), + }) }) } @@ -184,7 +204,9 @@ export function useAccountGroups() { await disableAccountGroup({ id: group.id }) showDisableModal.value = false await query.execute() - ZNotification.success({ message: t('groups.actions.disabled') }) + ZNotification.success({ + message: t('groups.actions.disabled'), + }) }) } @@ -194,7 +216,11 @@ export function useAccountGroups() { const mutation = group.enabled ? disableAccountGroup : enableAccountGroup await mutation({ id: group.id }) await query.execute() - ZNotification.success({ message: group.enabled ? t('groups.actions.disabled') : t('groups.actions.enabled') }) + ZNotification.success({ + message: group.enabled + ? t('groups.actions.disabled') + : t('groups.actions.enabled'), + }) } catch {} }) @@ -216,8 +242,12 @@ export function useAccountGroups() { selectedIds.value = remaining showDeleteModal.value = false await query.execute() - ZNotification.success({ message: t('groups.actions.groupDeleted') }) - }, { onError: () => void query.execute() }) + ZNotification.success({ + message: t('groups.actions.groupDeleted'), + }) + }, { + onError: () => void query.execute(), + }) } async function confirmBatchDelete() { @@ -245,14 +275,21 @@ export function useAccountGroups() { await query.execute() showBatchDeleteModal.value = false - ZNotification.success({ message: t('groups.actions.deletedGroupDeletedGroups', { count: deletedCount }) }) + ZNotification.success({ + message: t('groups.actions.deletedGroupDeletedGroups', { count: deletedCount }), + }) }, { errorText: false, onError: (error) => { void query.execute().catch(() => undefined) - ZNotification.error({ message: deletedCount > 0 - ? t('groups.actions.partialDeletion', { count: deletedCount, value: errorMessage(error, t('common.errors.operation')) }) - : errorMessage(error, t('groups.actions.batchDeletionFailed')) }) + ZNotification.error({ + message: deletedCount > 0 + ? t('groups.actions.partialDeletion', { + count: deletedCount, + value: errorMessage(error, t('common.errors.operation')), + }) + : errorMessage(error, t('groups.actions.batchDeletionFailed')), + }) }, }) } @@ -275,7 +312,10 @@ export function useAccountGroups() { void query.reloadFromStart() }) onMounted(() => { - void Promise.all([query.execute(), loadReferenceKeys()]) + void Promise.all([ + query.execute(), + loadReferenceKeys(), + ]) }) return { @@ -314,5 +354,11 @@ export function useAccountGroups() { } function emptyForm(): AccountGroupFormValue { - return { name: '', description: '', color: DEFAULT_ACCOUNT_GROUP_COLOR, fastMode: 'default', allowImageGeneration: true } + return { + name: '', + description: '', + color: DEFAULT_ACCOUNT_GROUP_COLOR, + fastMode: 'default', + allowImageGeneration: true, + } } diff --git a/frontend/src/views/groups/constants.ts b/frontend/src/views/groups/constants.ts index 170e19de1..2a9201fab 100644 --- a/frontend/src/views/groups/constants.ts +++ b/frontend/src/views/groups/constants.ts @@ -18,18 +18,62 @@ export const ACCOUNT_GROUP_COLOR_PRESETS = [ export const accountGroupColumns = computed(() => (defineTableColumns([ { key: 'selection', kind: 'selection' }, - { key: 'identity', label: t('groups.group'), kind: 'identity' }, - { key: 'color', label: t('common.fields.color'), kind: 'status', size: 'sm' }, - { key: 'enabled', label: t('common.fields.status'), kind: 'status' }, - { key: 'accountCount', label: t('accounts.fields.count'), kind: 'custom' }, - { key: 'capacity', label: t('groups.fields.capacity'), kind: 'custom', size: 'md' }, - { key: 'usage', label: t('usage.usage'), kind: 'custom' }, - { key: 'updatedAtDisplay', label: t('common.fields.updatedAt'), kind: 'datetime' }, - { key: 'actions', label: t('common.fields.actions'), kind: 'actions', size: 'lg' }, + { + key: 'identity', + label: t('groups.group'), + kind: 'identity', + }, + { + key: 'color', + label: t('common.fields.color'), + kind: 'status', + size: 'sm', + }, + { + key: 'enabled', + label: t('common.fields.status'), + kind: 'status', + }, + { + key: 'accountCount', + label: t('accounts.fields.count'), + kind: 'custom', + }, + { + key: 'capacity', + label: t('groups.fields.capacity'), + kind: 'custom', + size: 'md', + }, + { + key: 'usage', + label: t('usage.usage'), + kind: 'custom', + }, + { + key: 'updatedAtDisplay', + label: t('common.fields.updatedAt'), + kind: 'datetime', + }, + { + key: 'actions', + label: t('common.fields.actions'), + kind: 'actions', + size: 'lg', + }, ]))) export const accountGroupStatusOptions = computed(() => ([ - { label: t('common.status.all'), value: '' }, - { label: t('common.status.enabled'), value: 'enabled' }, - { label: t('common.status.disabled'), value: 'disabled' }, + { + label: t('common.status.all'), + value: '', + }, + { + label: t('common.status.enabled'), + value: 'enabled', + }, + { + label: t('common.status.disabled'), + value: 'disabled', + }, ])) diff --git a/frontend/src/views/key-usage/components/KeyUsageBudget.vue b/frontend/src/views/key-usage/components/KeyUsageBudget.vue index e42c38d23..735bb2f7f 100644 --- a/frontend/src/views/key-usage/components/KeyUsageBudget.vue +++ b/frontend/src/views/key-usage/components/KeyUsageBudget.vue @@ -12,13 +12,27 @@ const props = defineProps<{ budget: KeyUsageBudget }>() const { t } = useI18n({ useScope: 'global' }) const windows = computed(() => [ - { label: t('usage.budget.todaySQuota'), limit: props.budget.dailyLimitUsd, used: props.budget.dailyUsedUsd, reset: props.budget.dailyResetsAt, resetDisplay: props.budget.dailyResetsAtDisplay }, - { label: t('usage.budget.weeklyQuota'), limit: props.budget.weeklyLimitUsd, used: props.budget.weeklyUsedUsd, reset: props.budget.weeklyResetsAt, resetDisplay: props.budget.weeklyResetsAtDisplay }, + { + label: t('usage.budget.todaySQuota'), + limit: props.budget.dailyLimitUsd, + used: props.budget.dailyUsedUsd, + reset: props.budget.dailyResetsAt, + resetDisplay: props.budget.dailyResetsAtDisplay, + }, + { + label: t('usage.budget.weeklyQuota'), + limit: props.budget.weeklyLimitUsd, + used: props.budget.weeklyUsedUsd, + reset: props.budget.weeklyResetsAt, + resetDisplay: props.budget.weeklyResetsAtDisplay, + }, ].map(window => ({ ...window, limited: Number(window.limit) > 0, remaining: Math.max(0, Number(window.limit) - Number(window.used)), - percentage: Number(window.limit) > 0 ? Number(window.used) / Number(window.limit) * 100 : 0, + percentage: Number(window.limit) > 0 + ? Number(window.used) / Number(window.limit) * 100 + : 0, }))) diff --git a/frontend/src/views/key-usage/components/KeyUsageHeader.vue b/frontend/src/views/key-usage/components/KeyUsageHeader.vue index 7cfdd7706..68e74e17f 100644 --- a/frontend/src/views/key-usage/components/KeyUsageHeader.vue +++ b/frontend/src/views/key-usage/components/KeyUsageHeader.vue @@ -25,8 +25,12 @@ const loggingOut = shallowRef(false) async function logout() { loggingOut.value = true try { - if (await auth.logout()) - await router.replace({ name: 'login', state: { loginMode: 'key' } }) + if (await auth.logout()) { + await router.replace({ + name: 'login', + state: { loginMode: 'key' }, + }) + } } finally { loggingOut.value = false diff --git a/frontend/src/views/key-usage/components/KeyUsageSkeleton.vue b/frontend/src/views/key-usage/components/KeyUsageSkeleton.vue index d357ba2bc..45caa4ab2 100644 --- a/frontend/src/views/key-usage/components/KeyUsageSkeleton.vue +++ b/frontend/src/views/key-usage/components/KeyUsageSkeleton.vue @@ -11,7 +11,11 @@ const { t } = useI18n({ useScope: 'global' }) const metrics = computed(() => ([ ...keyUsageTokenMetrics.value, - { label: t('usage.page.cacheHitRate'), icon: Gauge, tone: 'text-cp-success-text' }, + { + label: t('usage.page.cacheHitRate'), + icon: Gauge, + tone: 'text-cp-success-text', + }, ])) diff --git a/frontend/src/views/key-usage/components/KeyUsageSummary.vue b/frontend/src/views/key-usage/components/KeyUsageSummary.vue index 0c0c15182..ef21b915c 100644 --- a/frontend/src/views/key-usage/components/KeyUsageSummary.vue +++ b/frontend/src/views/key-usage/components/KeyUsageSummary.vue @@ -14,14 +14,34 @@ const props = defineProps<{ summary: KeyUsageMetrics }>() const { t } = useI18n({ useScope: 'global' }) -const hitRate = computed(() => props.summary.inputTokens > 0 ? props.summary.cachedInputTokens / props.summary.inputTokens * 100 : null) +const hitRate = computed(() => props.summary.inputTokens > 0 + ? props.summary.cachedInputTokens / props.summary.inputTokens * 100 + : null) const metrics = computed(() => [ - ...keyUsageTokenMetrics.value.map(metric => ({ ...metric, value: keyUsageTokenValue(props.summary, metric.key), percentage: false })), - { label: t('usage.page.cacheHitRate'), value: hitRate.value, icon: Gauge, tone: 'text-cp-success-text', percentage: true }, + ...keyUsageTokenMetrics.value.map(metric => ({ + ...metric, + value: keyUsageTokenValue(props.summary, metric.key), + percentage: false, + })), + { + label: t('usage.page.cacheHitRate'), + value: hitRate.value, + icon: Gauge, + tone: 'text-cp-success-text', + percentage: true, + }, ].map(item => ({ ...item, - display: item.value === null ? '—' : item.percentage ? `${item.value.toFixed(1)}%` : formatCompactNumber(item.value), - exact: item.value === null ? '—' : item.percentage ? `${item.value.toFixed(1)}%` : formatInteger(item.value), + display: item.value === null + ? '—' + : item.percentage + ? `${item.value.toFixed(1)}%` + : formatCompactNumber(item.value), + exact: item.value === null + ? '—' + : item.percentage + ? `${item.value.toFixed(1)}%` + : formatInteger(item.value), }))) diff --git a/frontend/src/views/key-usage/components/KeyUsageTrend.vue b/frontend/src/views/key-usage/components/KeyUsageTrend.vue index d156e7a3f..ecdd61904 100644 --- a/frontend/src/views/key-usage/components/KeyUsageTrend.vue +++ b/frontend/src/views/key-usage/components/KeyUsageTrend.vue @@ -27,32 +27,87 @@ const option = computed(() => { color: color(metric.colorToken, metric.fallback), data: props.points.map(point => keyUsageTokenValue(point, metric.key)), })), - { key: 'costUsd', name: t('usage.trends.cost'), color: colors.danger, data: props.points.map(point => point.costUsd === null ? null : Number(point.costUsd)), cost: true }, + { + key: 'costUsd', + name: t('usage.trends.cost'), + color: colors.danger, + data: props.points.map(point => point.costUsd === null + ? null + : Number(point.costUsd)), + cost: true, + }, ] return { animation: false, - textStyle: { fontFamily: color('--font-sans', 'sans-serif') }, - grid: { left: 8, right: 12, top: 48, bottom: 0, containLabel: true }, + textStyle: { + fontFamily: color('--font-sans', 'sans-serif'), + }, + grid: { + left: 8, + right: 12, + top: 48, + bottom: 0, + containLabel: true, + }, tooltip: { trigger: 'axis', ...chartTooltipStyle(colors, { axisPointer: true, confine: true }), // 富文本渲染不把模型等外部内容插入 HTML。 renderMode: 'richText', - valueFormatter: value => typeof value === 'number' ? formatInteger(value) : '—', + valueFormatter: value => typeof value === 'number' + ? formatInteger(value) + : '—', + }, + legend: { + top: 0, + right: 0, + type: 'plain', + icon: 'circle', + itemWidth: 7, + itemHeight: 7, + textStyle: { color: colors.textSecondary, fontSize: 11 }, }, - legend: { top: 0, right: 0, type: 'plain', icon: 'circle', itemWidth: 7, itemHeight: 7, textStyle: { color: colors.textSecondary, fontSize: 11 } }, xAxis: { type: 'category', boundaryGap: false, data: props.points.map(point => point.time), axisLine: { show: false }, axisTick: { show: false }, - axisLabel: { color: colors.textMuted, fontSize: 10, hideOverlap: true, formatter: (_value: string, index: number) => props.points[index]?.label ?? '' }, - axisPointer: { label: { formatter: ({ value }) => props.points.find(point => point.time === value)?.label ?? '' } }, + axisLabel: { + color: colors.textMuted, + fontSize: 10, + hideOverlap: true, + formatter: (_value: string, index: number) => props.points[index]?.label ?? '', + }, + axisPointer: { + label: { + formatter: ({ value }) => props.points.find(point => point.time === value)?.label ?? '', + }, + }, }, yAxis: [ - { type: 'value', min: 0, axisLabel: { color: colors.textMuted, fontSize: 10, formatter: (value: number) => formatCompactNumber(value) }, splitLine: { lineStyle: { color: colors.grid, type: 'dashed' } } }, - { type: 'value', min: 0, axisLabel: { color: colors.textMuted, fontSize: 10, formatter: (value: number) => money(value) }, splitLine: { show: false } }, + { + type: 'value', + min: 0, + axisLabel: { + color: colors.textMuted, + fontSize: 10, + formatter: (value: number) => formatCompactNumber(value), + }, + splitLine: { + lineStyle: { color: colors.grid, type: 'dashed' }, + }, + }, + { + type: 'value', + min: 0, + axisLabel: { + color: colors.textMuted, + fontSize: 10, + formatter: (value: number) => money(value), + }, + splitLine: { show: false }, + }, ], series: lines.map((line): LineSeriesOption => ({ id: line.key, @@ -64,10 +119,22 @@ const option = computed(() => { showSymbol: props.points.length <= 1, connectNulls: false, symbolSize: 5, - lineStyle: { color: line.color, width: 2, type: line.cost ? 'dashed' : 'solid' }, + lineStyle: { + color: line.color, + width: 2, + type: line.cost ? 'dashed' : 'solid', + }, itemStyle: { color: line.color }, - areaStyle: line.key === 'cachedInputTokens' ? { color: line.color, opacity: 0.1 } : undefined, - tooltip: { valueFormatter: value => typeof value === 'number' ? (line.cost ? money(value) : formatInteger(value)) : '—' }, + areaStyle: line.key === 'cachedInputTokens' + ? { color: line.color, opacity: 0.1 } + : undefined, + tooltip: { + valueFormatter: value => typeof value === 'number' + ? (line.cost + ? money(value) + : formatInteger(value)) + : '—', + }, })), } }) diff --git a/frontend/src/views/key-usage/composables/useKeyConfig.ts b/frontend/src/views/key-usage/composables/useKeyConfig.ts index a57cdcda6..3754a1975 100644 --- a/frontend/src/views/key-usage/composables/useKeyConfig.ts +++ b/frontend/src/views/key-usage/composables/useKeyConfig.ts @@ -32,7 +32,10 @@ export function useKeyConfig() { } function copyConfig(text: string) { - return copyText(text, { successText: t('usage.configuration.copiedToClipboard'), emptyErrorText: t('common.errors.copy') }) + return copyText(text, { + successText: t('usage.configuration.copiedToClipboard'), + emptyErrorText: t('common.errors.copy'), + }) } // 明文只随弹窗保存在内存中,不进入登录状态或浏览器持久化存储。 @@ -40,5 +43,12 @@ export function useKeyConfig() { configKey.value = null }) - return { showConfig, configKey, configuring: request.loading, apiBaseUrl, openConfig, copyConfig } + return { + showConfig, + configKey, + configuring: request.loading, + apiBaseUrl, + openConfig, + copyConfig, + } } diff --git a/frontend/src/views/key-usage/composables/useKeyUsage.ts b/frontend/src/views/key-usage/composables/useKeyUsage.ts index 530dccc3f..a4e8727e0 100644 --- a/frontend/src/views/key-usage/composables/useKeyUsage.ts +++ b/frontend/src/views/key-usage/composables/useKeyUsage.ts @@ -26,7 +26,11 @@ export function useKeyUsage() { }) const records = useStablePagedQuery({ initialPageSize: 20, - load: (pagination, options) => getKeyUsageRecords({ ...query.value, ...pagination, kind: kind.value }, { ...options, silent: true }), + load: (pagination, options) => getKeyUsageRecords({ + ...query.value, + ...pagination, + kind: kind.value, + }, { ...options, silent: true }), onSuccess: () => { recordsStale.value = false records.error.value = '' @@ -55,7 +59,10 @@ export function useKeyUsage() { const generation = queryGeneration rangeEnd.value = Date.now() try { - const [, recordsOk] = await Promise.all([loadOverview(), records.execute(undefined, { silent: true })]) + const [, recordsOk] = await Promise.all([ + loadOverview(), + records.execute(undefined, { silent: true }), + ]) // 轮询被新筛选或翻页取代时,不把取消结果标成刷新失败。 if (generation === queryGeneration) recordsStale.value = !recordsOk diff --git a/frontend/src/views/key-usage/utils/metrics.ts b/frontend/src/views/key-usage/utils/metrics.ts index 530c7ae5e..900837650 100644 --- a/frontend/src/views/key-usage/utils/metrics.ts +++ b/frontend/src/views/key-usage/utils/metrics.ts @@ -6,11 +6,46 @@ import { t } from '@/i18n' // 卡片、曲线和图例共用语义与主题色,避免同一指标出现不同颜色。 export const keyUsageTokenMetrics = computed(() => ([ - { key: 'inputTokens', label: t('usage.metrics.newInput'), icon: ArrowDown, tone: 'text-cp-success-text', colorToken: '--cp-color-success-text', fallback: '#12B981' }, - { key: 'outputTokens', label: t('usage.metrics.output'), icon: ArrowUp, tone: 'text-cp-warning-text', colorToken: '--cp-color-warning-text', fallback: '#F59E0B' }, - { key: 'cacheWriteInputTokens', label: t('usage.metrics.cacheCreation'), icon: Database, tone: 'text-cp-info-text', colorToken: '--cp-color-info-text', fallback: '#5983F4' }, - { key: 'cachedInputTokens', label: t('usage.trends.cacheHit'), icon: Sparkles, tone: 'text-cp-purple-text', colorToken: '--cp-color-purple-text', fallback: '#722ED1' }, - { key: 'reasoningOutputTokens', label: t('usage.metrics.reasoning'), icon: Brain, tone: 'text-cp-cyan-text', colorToken: '--cp-color-cyan-text', fallback: '#13C2C2' }, + { + key: 'inputTokens', + label: t('usage.metrics.newInput'), + icon: ArrowDown, + tone: 'text-cp-success-text', + colorToken: '--cp-color-success-text', + fallback: '#12B981', + }, + { + key: 'outputTokens', + label: t('usage.metrics.output'), + icon: ArrowUp, + tone: 'text-cp-warning-text', + colorToken: '--cp-color-warning-text', + fallback: '#F59E0B', + }, + { + key: 'cacheWriteInputTokens', + label: t('usage.metrics.cacheCreation'), + icon: Database, + tone: 'text-cp-info-text', + colorToken: '--cp-color-info-text', + fallback: '#5983F4', + }, + { + key: 'cachedInputTokens', + label: t('usage.trends.cacheHit'), + icon: Sparkles, + tone: 'text-cp-purple-text', + colorToken: '--cp-color-purple-text', + fallback: '#722ED1', + }, + { + key: 'reasoningOutputTokens', + label: t('usage.metrics.reasoning'), + icon: Brain, + tone: 'text-cp-cyan-text', + colorToken: '--cp-color-cyan-text', + fallback: '#13C2C2', + }, ] as const)) export function keyUsageTokenValue(metrics: KeyUsageMetrics, key: typeof keyUsageTokenMetrics.value[number]['key']) { diff --git a/frontend/src/views/keys/components/ApiKeyBudgetCell.vue b/frontend/src/views/keys/components/ApiKeyBudgetCell.vue index 9c2a1c5fa..7ac2c6641 100644 --- a/frontend/src/views/keys/components/ApiKeyBudgetCell.vue +++ b/frontend/src/views/keys/components/ApiKeyBudgetCell.vue @@ -11,8 +11,22 @@ const props = defineProps<{ apiKey: ApiKey }>() const { t } = useI18n({ useScope: 'global' }) const windows = computed(() => [ - { label: t('keys.day'), heading: t('keys.dailyUsage'), used: props.apiKey.dailyUsedUsd, limit: props.apiKey.dailyLimitUsd, reset: props.apiKey.dailyResetsAt, resetDisplay: props.apiKey.dailyResetsAtDisplay }, - { label: t('keys.week'), heading: t('keys.weeklyUsage'), used: props.apiKey.weeklyUsedUsd, limit: props.apiKey.weeklyLimitUsd, reset: props.apiKey.weeklyResetsAt, resetDisplay: props.apiKey.weeklyResetsAtDisplay }, + { + label: t('keys.day'), + heading: t('keys.dailyUsage'), + used: props.apiKey.dailyUsedUsd, + limit: props.apiKey.dailyLimitUsd, + reset: props.apiKey.dailyResetsAt, + resetDisplay: props.apiKey.dailyResetsAtDisplay, + }, + { + label: t('keys.week'), + heading: t('keys.weeklyUsage'), + used: props.apiKey.weeklyUsedUsd, + limit: props.apiKey.weeklyLimitUsd, + reset: props.apiKey.weeklyResetsAt, + resetDisplay: props.apiKey.weeklyResetsAtDisplay, + }, ]) function amount(value: string) { // 列表最多显示两位小数,不补末尾零;明细保留原始金额的全部精度。 diff --git a/frontend/src/views/keys/components/ApiKeyBudgetResetModal.vue b/frontend/src/views/keys/components/ApiKeyBudgetResetModal.vue index 9e2bc4ab3..7c64ea1eb 100644 --- a/frontend/src/views/keys/components/ApiKeyBudgetResetModal.vue +++ b/frontend/src/views/keys/components/ApiKeyBudgetResetModal.vue @@ -19,14 +19,31 @@ const open = defineModel({ default: false }) const period = shallowRef('all') const { loading, run } = useAsyncAction() const periods = computed(() => ([ - { label: t('common.values.all'), value: 'all' }, - { label: t('keys.budgetReset.dailyQuota'), value: 'daily' }, - { label: t('keys.budgetReset.weeklyQuota'), value: 'weekly' }, + { + label: t('common.values.all'), + value: 'all', + }, + { + label: t('keys.budgetReset.dailyQuota'), + value: 'daily', + }, + { + label: t('keys.budgetReset.weeklyQuota'), + value: 'weekly', + }, ])) const windows = computed(() => props.apiKey ? [ - { value: 'daily', label: t('keys.budgetReset.dailyQuota'), used: props.apiKey.dailyUsedUsd }, - { value: 'weekly', label: t('keys.budgetReset.weeklyQuota'), used: props.apiKey.weeklyUsedUsd }, + { + value: 'daily', + label: t('keys.budgetReset.dailyQuota'), + used: props.apiKey.dailyUsedUsd, + }, + { + value: 'weekly', + label: t('keys.budgetReset.weeklyQuota'), + used: props.apiKey.weeklyUsedUsd, + }, ].map(window => ({ ...window, selected: period.value === 'all' || period.value === window.value })) : []) @@ -43,7 +60,9 @@ async function resetBudget() { await run(async () => { await resetApiKeyBudget({ id: key.id, period: selectedPeriod }) open.value = false - ZNotification.success({ message: t('keys.budgetReset.keyQuotaReset') }) + ZNotification.success({ + message: t('keys.budgetReset.keyQuotaReset'), + }) emit('reset') }) } diff --git a/frontend/src/views/keys/components/ApiKeyFormModal.vue b/frontend/src/views/keys/components/ApiKeyFormModal.vue index 359a53e02..3e219fd6c 100644 --- a/frontend/src/views/keys/components/ApiKeyFormModal.vue +++ b/frontend/src/views/keys/components/ApiKeyFormModal.vue @@ -27,7 +27,9 @@ const { t } = useI18n({ useScope: 'global' }) const open = defineModel({ default: false }) const form = defineModel('form', { required: true }) -const title = computed(() => props.editing ? t('keys.actions.editKey') : t('keys.filters.createApiKey')) +const title = computed(() => props.editing + ? t('keys.actions.editKey') + : t('keys.filters.createApiKey')) const formRef = useTemplateRef>('formElement') const rules = useApiKeyFormRules() diff --git a/frontend/src/views/keys/composables/useApiKeyEditor.ts b/frontend/src/views/keys/composables/useApiKeyEditor.ts index baf95d6fd..3a8a2cda9 100644 --- a/frontend/src/views/keys/composables/useApiKeyEditor.ts +++ b/frontend/src/views/keys/composables/useApiKeyEditor.ts @@ -76,7 +76,9 @@ export function useApiKeyEditor(options: { const payload = { name: form.value.name.trim(), label: form.value.label.trim() || null, - groupIds: [...new Set(form.value.groupIds)], + groupIds: [ + ...new Set(form.value.groupIds), + ], maxConcurrency: Number(form.value.maxConcurrency), requestsPerMinute: Number(form.value.requestsPerMinute), dailyLimitUsd: form.value.dailyLimitUsd.trim() || '0', @@ -106,14 +108,20 @@ export function useApiKeyEditor(options: { showFormModal.value = false await options.reload() if (current) { - ZNotification.success({ message: t('keys.editor.updated') }) + ZNotification.success({ + message: t('keys.editor.updated'), + }) } else if (created) { options.onCreated(created.key, created.name) - ZNotification.success({ message: t('keys.editor.created') }) + ZNotification.success({ + message: t('keys.editor.created'), + }) } }, - { onError: () => void options.reload() }, + { + onError: () => void options.reload(), + }, ) } @@ -121,7 +129,18 @@ export function useApiKeyEditor(options: { form.value.customKey = '' } - return { showFormModal, showAllAccountsConfirm, editingKey, savingKey, form, openCreate, openEdit, clearCustomKey, requestSave, confirmAllAccountsScope } + return { + showFormModal, + showAllAccountsConfirm, + editingKey, + savingKey, + form, + openCreate, + openEdit, + clearCustomKey, + requestSave, + confirmAllAccountsScope, + } } function emptyForm(): ApiKeyFormValue { @@ -151,5 +170,7 @@ function profileOverrideUpdates( } function limitInputValue(limit: string | number) { - return Number(limit) === 0 ? '' : String(limit) + return Number(limit) === 0 + ? '' + : String(limit) } diff --git a/frontend/src/views/keys/composables/useApiKeyMutations.ts b/frontend/src/views/keys/composables/useApiKeyMutations.ts index 2e14cf814..2ca8ae790 100644 --- a/frontend/src/views/keys/composables/useApiKeyMutations.ts +++ b/frontend/src/views/keys/composables/useApiKeyMutations.ts @@ -47,9 +47,13 @@ export function useApiKeyMutations(options: { options.selectedIds.value = remaining showSingleDeleteModal.value = false await options.reload() - ZNotification.success({ message: t('common.feedback.deleted') }) + ZNotification.success({ + message: t('common.feedback.deleted'), + }) + }, + { + onError: () => void options.reload(), }, - { onError: () => void options.reload() }, ) } @@ -68,9 +72,13 @@ export function useApiKeyMutations(options: { } showDeleteModal.value = false await options.reload() - ZNotification.success({ message: t('keys.actions.deletedApiKeyDeletedApiKeys', { count: deleteCount }) }) + ZNotification.success({ + message: t('keys.actions.deletedApiKeyDeletedApiKeys', { count: deleteCount }), + }) + }, + { + onError: () => void options.reload(), }, - { onError: () => void options.reload() }, ) } @@ -80,7 +88,11 @@ export function useApiKeyMutations(options: { const mutation = key.enabled ? disableApiKey : enableApiKey await mutation({ id: key.id }) await options.reload() - ZNotification.success({ message: key.enabled ? t('common.status.disabled') : t('common.status.enabled') }) + ZNotification.success({ + message: key.enabled + ? t('common.status.disabled') + : t('common.status.enabled'), + }) } catch { void options.reload() @@ -88,5 +100,17 @@ export function useApiKeyMutations(options: { }) } - return { showDeleteModal, showSingleDeleteModal, pendingDeleteKey, deleteCount, deletingKey, batchDeleting, updatingStatusKeyIds, requestDeleteKey, handleDelete, handleBatchDelete, handleToggleStatus } + return { + showDeleteModal, + showSingleDeleteModal, + pendingDeleteKey, + deleteCount, + deletingKey, + batchDeleting, + updatingStatusKeyIds, + requestDeleteKey, + handleDelete, + handleBatchDelete, + handleToggleStatus, + } } diff --git a/frontend/src/views/keys/composables/useApiKeyUse.ts b/frontend/src/views/keys/composables/useApiKeyUse.ts index 1c9705955..2eb1cbb60 100644 --- a/frontend/src/views/keys/composables/useApiKeyUse.ts +++ b/frontend/src/views/keys/composables/useApiKeyUse.ts @@ -67,7 +67,10 @@ export function useApiKeyUse() { } async function copyToClipboard(text: string) { - await copyText(text, { successText: t('keys.configuration.copiedToClipboard'), emptyErrorText: t('common.errors.copy') }) + await copyText(text, { + successText: t('keys.configuration.copiedToClipboard'), + emptyErrorText: t('common.errors.copy'), + }) } async function withPlaintextKey(apiKey: ApiKey, use: (key: string) => void | Promise) { @@ -80,7 +83,9 @@ export function useApiKeyUse() { if (!result || !revealRequest.isCurrent(requestId)) return if (!result.plaintextKey) { - ZNotification.error({ message: t('keys.configuration.fullApiKeyNotAvailable') }) + ZNotification.error({ + message: t('keys.configuration.fullApiKeyNotAvailable'), + }) return } await use(result.plaintextKey) diff --git a/frontend/src/views/keys/composables/useApiKeysQuery.ts b/frontend/src/views/keys/composables/useApiKeysQuery.ts index 74cfd6691..d2bb9c1cd 100644 --- a/frontend/src/views/keys/composables/useApiKeysQuery.ts +++ b/frontend/src/views/keys/composables/useApiKeysQuery.ts @@ -16,7 +16,9 @@ export function useApiKeysQuery() { const apiKeys = shallowRef([]) const request = useRequestState() const { loading } = request - const cursors = new Map([[1, undefined]]) + const cursors = new Map([ + [1, undefined], + ]) const apiKeyPagination = computed(() => ({ currentPage: page.value, @@ -31,8 +33,12 @@ export function useApiKeysQuery() { } function knownPageBefore(targetPage: number) { - return [...cursors.keys()].reduce( - (known, candidate) => (candidate <= targetPage && candidate > known ? candidate : known), + return [ + ...cursors.keys(), + ].reduce( + (known, candidate) => (candidate <= targetPage && candidate > known + ? candidate + : known), 1, ) } diff --git a/frontend/src/views/keys/constants.ts b/frontend/src/views/keys/constants.ts index cd8d7d76a..3edf27459 100644 --- a/frontend/src/views/keys/constants.ts +++ b/frontend/src/views/keys/constants.ts @@ -6,13 +6,49 @@ import { t } from '@/i18n' export const apiKeyColumns = computed(() => (defineTableColumns([ { key: 'selection', kind: 'selection' }, - { key: 'identity', label: t('common.fields.name'), kind: 'identity', size: 'xl', sortable: 'name' }, - { key: 'prefix', label: t('keys.fields.keyPrefix'), kind: 'mono', size: 'xl' }, - { key: 'enabled', label: t('common.fields.status'), kind: 'status', sortable: true }, - { key: 'scope', label: t('groups.group'), kind: 'status', size: 'lg' }, - { key: 'budget', label: t('keys.fields.quotaUsage'), kind: 'text', size: 'xl' }, - { key: 'limits', label: t('keys.fields.requestLimits'), kind: 'text', size: 'xl' }, - { key: 'createdAtDisplay', label: t('common.fields.createdAt'), kind: 'datetime', sortable: 'createdAt' }, + { + key: 'identity', + label: t('common.fields.name'), + kind: 'identity', + size: 'xl', + sortable: 'name', + }, + { + key: 'prefix', + label: t('keys.fields.keyPrefix'), + kind: 'mono', + size: 'xl', + }, + { + key: 'enabled', + label: t('common.fields.status'), + kind: 'status', + sortable: true, + }, + { + key: 'scope', + label: t('groups.group'), + kind: 'status', + size: 'lg', + }, + { + key: 'budget', + label: t('keys.fields.quotaUsage'), + kind: 'text', + size: 'xl', + }, + { + key: 'limits', + label: t('keys.fields.requestLimits'), + kind: 'text', + size: 'xl', + }, + { + key: 'createdAtDisplay', + label: t('common.fields.createdAt'), + kind: 'datetime', + sortable: 'createdAt', + }, { key: 'lastUsedAt', label: t('keys.fields.lastUsed'), @@ -21,5 +57,10 @@ export const apiKeyColumns = computed(() => (defineTableColumns([ sortable: true, emptyText: '', }, - { key: 'actions', label: t('common.fields.actions'), kind: 'actions', size: 'lg' }, + { + key: 'actions', + label: t('common.fields.actions'), + kind: 'actions', + size: 'lg', + }, ]))) diff --git a/frontend/src/views/login/components/LoginBackground.vue b/frontend/src/views/login/components/LoginBackground.vue index 5028598e9..f522ddf78 100644 --- a/frontend/src/views/login/components/LoginBackground.vue +++ b/frontend/src/views/login/components/LoginBackground.vue @@ -5,13 +5,39 @@ interface RouteCluster { className: string } -const ingressProtocols = ['HTTP JSON', 'HTTP SSE', 'WS JSON', 'WS SSE'] as const -const upstreamProtocols = ['RESPONSES', 'CHAT', 'REALTIME', 'TRACE'] as const +const ingressProtocols = [ + 'HTTP JSON', + 'HTTP SSE', + 'WS JSON', + 'WS SSE', +] as const +const upstreamProtocols = [ + 'RESPONSES', + 'CHAT', + 'REALTIME', + 'TRACE', +] as const const routeClusters: readonly RouteCluster[] = [ - { id: 'edge', labels: ['edge', 'cache', 'keys'], className: 'login-route-cluster--edge' }, - { id: 'retry', labels: ['retry', 'trace', 'sse'], className: 'login-route-cluster--retry' }, - { id: 'audit', labels: ['audit', 'events', 'logs'], className: 'login-route-cluster--audit' }, - { id: 'model', labels: ['model', 'tools', 'quota'], className: 'login-route-cluster--model' }, + { + id: 'edge', + labels: ['edge', 'cache', 'keys'], + className: 'login-route-cluster--edge', + }, + { + id: 'retry', + labels: ['retry', 'trace', 'sse'], + className: 'login-route-cluster--retry', + }, + { + id: 'audit', + labels: ['audit', 'events', 'logs'], + className: 'login-route-cluster--audit', + }, + { + id: 'model', + labels: ['model', 'tools', 'quota'], + className: 'login-route-cluster--model', + }, ] diff --git a/frontend/src/views/login/components/LoginPanel.vue b/frontend/src/views/login/components/LoginPanel.vue index a6bb21894..fe7d682b9 100644 --- a/frontend/src/views/login/components/LoginPanel.vue +++ b/frontend/src/views/login/components/LoginPanel.vue @@ -26,12 +26,22 @@ const password = defineModel('password', { required: true }) const apiKey = defineModel('apiKey', { required: true }) const realmOptions = computed(() => ([ - { label: t('auth.adminSignIn'), value: 'admin', icon: ShieldCheck }, - { label: t('auth.apiKeySignIn'), value: 'key', icon: KeyRound }, + { + label: t('auth.adminSignIn'), + value: 'admin', + icon: ShieldCheck, + }, + { + label: t('auth.apiKeySignIn'), + value: 'key', + icon: KeyRound, + }, ])) const isKeyRealm = computed(() => realm.value === 'key') -const realmCaption = computed(() => isKeyRealm.value ? t('auth.keyRealm') : t('auth.adminRealm')) +const realmCaption = computed(() => isKeyRealm.value + ? t('auth.keyRealm') + : t('auth.adminRealm')) const submitLabel = computed(() => { if (props.loading) return t('auth.signingIn') diff --git a/frontend/src/views/login/components/LoginThemeToggle.vue b/frontend/src/views/login/components/LoginThemeToggle.vue index 1b994b2db..ccb43e6bd 100644 --- a/frontend/src/views/login/components/LoginThemeToggle.vue +++ b/frontend/src/views/login/components/LoginThemeToggle.vue @@ -12,7 +12,9 @@ const { t } = useI18n({ useScope: 'global' }) const themeStore = useThemeStore() const { effectiveTheme } = storeToRefs(themeStore) -const label = computed(() => effectiveTheme.value === 'dark' ? t('appearance.switchToLight') : t('appearance.switchToDark')) +const label = computed(() => effectiveTheme.value === 'dark' + ? t('appearance.switchToLight') + : t('appearance.switchToDark'))