mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
Compare commits
8 Commits
@@ -23,13 +23,17 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.3.14'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build all-in-one HTML
|
||||
run: npm run build
|
||||
run: bun run build
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ The address is auto-detected from the current page URL; manual override is suppo
|
||||
### Option B: Run the dev server
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run dev
|
||||
bun install --frozen-lockfile
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Open `http://localhost:5173`, then connect to your CLI Proxy API backend instance.
|
||||
@@ -37,13 +37,13 @@ Open `http://localhost:5173`, then connect to your CLI Proxy API backend instanc
|
||||
### Option C: Build a single HTML file
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
```
|
||||
|
||||
- Output: `dist/index.html` (all assets are inlined).
|
||||
- For CLI Proxy API bundling, the release workflow renames it to `management.html`.
|
||||
- To preview locally: `npm run preview`
|
||||
- To preview locally: `bun run preview`
|
||||
|
||||
Tip: opening `dist/index.html` via `file://` may be blocked by browser CORS; serving it (preview/static server) is more reliable.
|
||||
|
||||
@@ -136,12 +136,12 @@ The UI language is automatically detected from browser settings and can be manua
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm run dev # Vite dev server
|
||||
npm run build # tsc + Vite build
|
||||
npm run preview # serve dist locally
|
||||
npm run lint # ESLint (fails on warnings)
|
||||
npm run format # Prettier
|
||||
npm run type-check # tsc --noEmit
|
||||
bun run dev # Vite dev server
|
||||
bun run build # tsc + Vite build
|
||||
bun run preview # serve dist locally
|
||||
bun run lint # ESLint (fails on warnings)
|
||||
bun run format # Prettier
|
||||
bun run type-check # tsc --noEmit
|
||||
```
|
||||
|
||||
## Contributing
|
||||
@@ -150,7 +150,7 @@ Issues and PRs are welcome. Please include:
|
||||
|
||||
- Reproduction steps (server version + UI version)
|
||||
- Screenshots for UI changes
|
||||
- Verification notes (`npm run lint`, `npm run type-check`, `npm run build`)
|
||||
- Verification notes (`bun run lint`, `bun run type-check`, `bun run build`)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+12
-12
@@ -28,8 +28,8 @@
|
||||
### 方式 B:开发调试
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run dev
|
||||
bun install --frozen-lockfile
|
||||
bun run dev
|
||||
```
|
||||
|
||||
打开 `http://localhost:5173`,然后连接到你的 CLI Proxy API 后端实例。
|
||||
@@ -37,13 +37,13 @@ npm run dev
|
||||
### 方式 C:构建单文件 HTML
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npm run build
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
```
|
||||
|
||||
- 构建产物:`dist/index.html`(资源已全部内联)。
|
||||
- 在 CLI Proxy API 的发布流程里会重命名为 `management.html`。
|
||||
- 本地预览:`npm run preview`
|
||||
- 本地预览:`bun run preview`
|
||||
|
||||
提示:直接用 `file://` 打开 `dist/index.html` 可能遇到浏览器 CORS 限制;更稳妥的方式是用预览/静态服务器打开。
|
||||
|
||||
@@ -136,12 +136,12 @@ npm run build
|
||||
## 开发命令
|
||||
|
||||
```bash
|
||||
npm run dev # 启动开发服务器
|
||||
npm run build # tsc + Vite 构建
|
||||
npm run preview # 本地预览 dist
|
||||
npm run lint # ESLint(warnings 视为失败)
|
||||
npm run format # Prettier
|
||||
npm run type-check # tsc --noEmit
|
||||
bun run dev # 启动开发服务器
|
||||
bun run build # tsc + Vite 构建
|
||||
bun run preview # 本地预览 dist
|
||||
bun run lint # ESLint(warnings 视为失败)
|
||||
bun run format # Prettier
|
||||
bun run type-check # tsc --noEmit
|
||||
```
|
||||
|
||||
## 贡献
|
||||
@@ -150,7 +150,7 @@ npm run type-check # tsc --noEmit
|
||||
|
||||
- 复现步骤(服务端版本 + UI 版本)
|
||||
- UI 改动截图
|
||||
- 验证记录(`npm run lint`、`npm run type-check`、`npm run build`)
|
||||
- 验证记录(`bun run lint`、`bun run type-check`、`bun run build`)
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@@ -0,0 +1,626 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "cli-proxy-webui-react",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/merge": "^6.12.0",
|
||||
"@uiw/react-codemirror": "^4.25.3",
|
||||
"axios": "1.15.2",
|
||||
"i18next": "^26.2.0",
|
||||
"motion": "^12.34.3",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react-i18next": "^17.0.8",
|
||||
"react-router-dom": "^7.10.1",
|
||||
"yaml": "^2.8.2",
|
||||
"zustand": "^5.0.9",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
||||
"@typescript-eslint/parser": "^8.59.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"prettier": "^3.7.4",
|
||||
"sass": "^1.94.2",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.3",
|
||||
"vite": "^8.0.10",
|
||||
"vite-plugin-singlefile": "^2.3.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg=="],
|
||||
|
||||
"@codemirror/commands": ["@codemirror/commands@6.10.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w=="],
|
||||
|
||||
"@codemirror/lang-yaml": ["@codemirror/lang-yaml@6.1.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.0.0", "@lezer/yaml": "^1.0.0" } }, "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw=="],
|
||||
|
||||
"@codemirror/language": ["@codemirror/language@6.11.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA=="],
|
||||
|
||||
"@codemirror/lint": ["@codemirror/lint@6.9.2", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ=="],
|
||||
|
||||
"@codemirror/merge": ["@codemirror/merge@6.12.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/highlight": "^1.0.0", "style-mod": "^4.1.0" } }, "sha512-o+36bbapcEHf4Ux75pZ4CKjMBUd14parA0uozvWVlacaT+uxaA3DDefEvWYjngsKU+qsrDe/HOOfsw0Q72pLjA=="],
|
||||
|
||||
"@codemirror/search": ["@codemirror/search@6.5.11", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "crelt": "^1.0.5" } }, "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA=="],
|
||||
|
||||
"@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="],
|
||||
|
||||
"@codemirror/theme-one-dark": ["@codemirror/theme-one-dark@6.1.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/highlight": "^1.0.0" } }, "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA=="],
|
||||
|
||||
"@codemirror/view": ["@codemirror/view@6.38.8", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@lezer/common": ["@lezer/common@1.4.0", "", {}, "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg=="],
|
||||
|
||||
"@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
|
||||
|
||||
"@lezer/lr": ["@lezer/lr@1.4.4", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A=="],
|
||||
|
||||
"@lezer/yaml": ["@lezer/yaml@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.4.0" } }, "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA=="],
|
||||
|
||||
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
|
||||
|
||||
"@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
|
||||
|
||||
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="],
|
||||
|
||||
"@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="],
|
||||
|
||||
"@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="],
|
||||
|
||||
"@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="],
|
||||
|
||||
"@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="],
|
||||
|
||||
"@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="],
|
||||
|
||||
"@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="],
|
||||
|
||||
"@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="],
|
||||
|
||||
"@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="],
|
||||
|
||||
"@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="],
|
||||
|
||||
"@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="],
|
||||
|
||||
"@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="],
|
||||
|
||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.3", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/type-utils": "8.59.3", "@typescript-eslint/utils": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.3", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.3", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/types": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.3", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.3", "@typescript-eslint/types": "^8.59.3", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.3", "", { "dependencies": { "@typescript-eslint/types": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3" } }, "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.3", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.3", "", { "dependencies": { "@typescript-eslint/types": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/utils": "8.59.3", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.59.3", "", {}, "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.3", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.3", "@typescript-eslint/tsconfig-utils": "8.59.3", "@typescript-eslint/types": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/types": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.3", "", { "dependencies": { "@typescript-eslint/types": "8.59.3", "eslint-visitor-keys": "^5.0.0" } }, "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg=="],
|
||||
|
||||
"@uiw/codemirror-extensions-basic-setup": ["@uiw/codemirror-extensions-basic-setup@4.25.3", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" }, "peerDependencies": { "@codemirror/autocomplete": ">=6.0.0", "@codemirror/commands": ">=6.0.0", "@codemirror/language": ">=6.0.0", "@codemirror/lint": ">=6.0.0", "@codemirror/search": ">=6.0.0", "@codemirror/state": ">=6.0.0", "@codemirror/view": ">=6.0.0" } }, "sha512-F1doRyD50CWScwGHG2bBUtUpwnOv/zqSnzkZqJcX5YAHQx6Z1CuX8jdnFMH6qktRrPU1tfpNYftTWu3QIoHiMA=="],
|
||||
|
||||
"@uiw/react-codemirror": ["@uiw/react-codemirror@4.25.3", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@codemirror/commands": "^6.1.0", "@codemirror/state": "^6.1.1", "@codemirror/theme-one-dark": "^6.0.0", "@uiw/codemirror-extensions-basic-setup": "4.25.3", "codemirror": "^6.0.0" }, "peerDependencies": { "@babel/runtime": ">=7.11.0", "@codemirror/state": ">=6.0.0", "@codemirror/theme-one-dark": ">=6.0.0", "@codemirror/view": ">=6.0.0", "codemirror": ">=6.0.0", "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-1wtBZTXPIp8u6F/xjHvsUAYlEeF5Dic4xZBnqJyLzv7o7GjGYEUfSz9Z7bo9aK9GAx2uojG/AuBMfhA4uhvIVQ=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": "bin/acorn" }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, ""],
|
||||
|
||||
"axios": ["axios@1.15.2", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, ""],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.4", "", { "bin": "dist/cli.js" }, "sha512-ZCQ9GEWl73BVm8bu5Fts8nt7MHdbt5vY9bP6WGnUh+r3l8M7CgfyTlwsgCbMC66BNxPr6Xoce3j66Ms5YUQTNA=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": "cli.js" }, ""],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, ""],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, ""],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, ""],
|
||||
|
||||
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
|
||||
|
||||
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, ""],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, ""],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, ""],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, ""],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"crelt": ["crelt@1.0.6", "", {}, ""],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, ""],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, ""],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, ""],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, ""],
|
||||
|
||||
"detect-libc": ["detect-libc@1.0.3", "", { "bin": "bin/detect-libc.js" }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, ""],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.266", "", {}, "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, ""],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, ""],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, ""],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, ""],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, ""],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, ""],
|
||||
|
||||
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": "bin/eslint.js" }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
||||
|
||||
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, ""],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, ""],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, ""],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, ""],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, ""],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, ""],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, ""],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, ""],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, ""],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, ""],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, ""],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
|
||||
|
||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, ""],
|
||||
|
||||
"framer-motion": ["framer-motion@12.34.3", "", { "dependencies": { "motion-dom": "^12.34.3", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, ""],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, ""],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, ""],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, ""],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, ""],
|
||||
|
||||
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, ""],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, ""],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, ""],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, ""],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, ""],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, ""],
|
||||
|
||||
"i18next": ["i18next@26.2.0", "", { "peerDependencies": { "typescript": "^5 || ^6" } }, "sha512-zwBHldHdTmwN7r6UNc7lC6GWNN+YYg3DrRSeHR5PRRBf5QnJZcYHrQc0uaU26qZeYxR7iFZD+Y315dPnKP47wA=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"immutable": ["immutable@5.1.5", "", {}, "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, ""],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, ""],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, ""],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, ""],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, ""],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, ""],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, ""],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, ""],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, ""],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, ""],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, ""],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, ""],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, ""],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, ""],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, ""],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, ""],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, ""],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""],
|
||||
|
||||
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"motion": ["motion@12.34.3", "", { "dependencies": { "framer-motion": "^12.34.3", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-xZIkBGO7v/Uvm+EyaqYd+9IpXu0sZqLywVlGdCFrrMiaO9JI4Kx51mO9KlHSWwll+gZUVY5OJsWgYI5FywJ/tw=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.34.3", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ=="],
|
||||
|
||||
"motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, ""],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, ""],
|
||||
|
||||
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, ""],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, ""],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, ""],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, ""],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, ""],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, ""],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, ""],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, ""],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"postcss": ["postcss@8.5.13", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, ""],
|
||||
|
||||
"prettier": ["prettier@3.7.4", "", { "bin": "bin/prettier.cjs" }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, ""],
|
||||
|
||||
"react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="],
|
||||
|
||||
"react-i18next": ["react-i18next@17.0.8", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.2.0", "react": ">= 16.8.0", "typescript": "^5 || ^6" } }, "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw=="],
|
||||
|
||||
"react-router": ["react-router@7.12.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw=="],
|
||||
|
||||
"react-router-dom": ["react-router-dom@7.12.0", "", { "dependencies": { "react-router": "7.12.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA=="],
|
||||
|
||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, ""],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": "bin/cli.mjs" }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="],
|
||||
|
||||
"sass": ["sass@1.94.2", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": "sass.js" }, "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@7.8.0", "", { "bin": "bin/semver.js" }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, ""],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, ""],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, ""],
|
||||
|
||||
"style-mod": ["style-mod@4.1.3", "", {}, ""],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, ""],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, ""],
|
||||
|
||||
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.59.3", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.3", "@typescript-eslint/parser": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/utils": "8.59.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, ""],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass-embedded", "stylus", "sugarss", "terser", "tsx"], "bin": "bin/vite.js" }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="],
|
||||
|
||||
"vite-plugin-singlefile": ["vite-plugin-singlefile@2.3.3", "", { "dependencies": { "micromatch": "^4.0.8" }, "peerDependencies": { "rollup": "^4.59.0", "vite": "^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["rollup"] }, "sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg=="],
|
||||
|
||||
"void-elements": ["void-elements@3.1.0", "", {}, ""],
|
||||
|
||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, ""],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, ""],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, ""],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, ""],
|
||||
|
||||
"yaml": ["yaml@2.8.3", "", { "bin": "bin.mjs" }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, ""],
|
||||
|
||||
"zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"zustand": ["zustand@5.0.9", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["immer"] }, "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, ""],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, ""],
|
||||
|
||||
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"postcss/source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
}
|
||||
}
|
||||
Generated
-4095
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -3,6 +3,7 @@
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.14",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
@@ -15,7 +16,7 @@
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/merge": "^6.12.0",
|
||||
"@uiw/react-codemirror": "^4.25.3",
|
||||
"axios": "^1.13.2",
|
||||
"axios": "1.15.2",
|
||||
"i18next": "^26.2.0",
|
||||
"motion": "^12.34.3",
|
||||
"react": "^19.2.1",
|
||||
|
||||
@@ -5,5 +5,12 @@
|
||||
export { QuotaSection } from './QuotaSection';
|
||||
export { QuotaCard } from './QuotaCard';
|
||||
export { useQuotaLoader } from './useQuotaLoader';
|
||||
export { ANTIGRAVITY_CONFIG, CLAUDE_CONFIG, CODEX_CONFIG, GEMINI_CLI_CONFIG, KIMI_CONFIG } from './quotaConfigs';
|
||||
export {
|
||||
ANTIGRAVITY_CONFIG,
|
||||
CLAUDE_CONFIG,
|
||||
CODEX_CONFIG,
|
||||
GEMINI_CLI_CONFIG,
|
||||
KIMI_CONFIG,
|
||||
XAI_CONFIG,
|
||||
} from './quotaConfigs';
|
||||
export type { QuotaConfig } from './quotaConfigs';
|
||||
|
||||
@@ -28,6 +28,9 @@ import type {
|
||||
GeminiCliUserTier,
|
||||
KimiQuotaRow,
|
||||
KimiQuotaState,
|
||||
XaiBillingConfig,
|
||||
XaiBillingSummary,
|
||||
XaiQuotaState,
|
||||
} from '@/types';
|
||||
import { apiCallApi, authFilesApi, getApiCallErrorMessage } from '@/services/api';
|
||||
import { useQuotaStore } from '@/stores';
|
||||
@@ -45,6 +48,8 @@ import {
|
||||
GEMINI_CLI_REQUEST_HEADERS,
|
||||
KIMI_USAGE_URL,
|
||||
KIMI_REQUEST_HEADERS,
|
||||
XAI_BILLING_URL,
|
||||
XAI_REQUEST_HEADERS,
|
||||
normalizeGeminiCliModelId,
|
||||
normalizeNumberValue,
|
||||
normalizePlanType,
|
||||
@@ -56,6 +61,7 @@ import {
|
||||
parseGeminiCliQuotaPayload,
|
||||
parseGeminiCliCodeAssistPayload,
|
||||
parseKimiUsagePayload,
|
||||
parseXaiBillingPayload,
|
||||
resolveCodexChatgptAccountId,
|
||||
resolveCodexPlanType,
|
||||
resolveGeminiCliProjectId,
|
||||
@@ -74,6 +80,7 @@ import {
|
||||
isGeminiCliFile,
|
||||
isKimiFile,
|
||||
isRuntimeOnlyAuthFile,
|
||||
isXaiFile,
|
||||
} from '@/utils/quota';
|
||||
import { normalizeAuthIndex } from '@/utils/authIndex';
|
||||
import type { QuotaRenderHelpers } from './QuotaCard';
|
||||
@@ -81,7 +88,7 @@ import styles from '@/pages/QuotaPage.module.scss';
|
||||
|
||||
type QuotaUpdater<T> = T | ((prev: T) => T);
|
||||
|
||||
type QuotaType = 'antigravity' | 'claude' | 'codex' | 'gemini-cli' | 'kimi';
|
||||
type QuotaType = 'antigravity' | 'claude' | 'codex' | 'gemini-cli' | 'kimi' | 'xai';
|
||||
|
||||
const DEFAULT_ANTIGRAVITY_PROJECT_ID = 'bamboo-precept-lgxtn';
|
||||
const QUOTA_PROGRESS_HIGH_THRESHOLD = 70;
|
||||
@@ -89,7 +96,12 @@ const QUOTA_PROGRESS_MEDIUM_THRESHOLD = 30;
|
||||
const geminiCliSupplementaryRequestIds = new Map<string, number>();
|
||||
const geminiCliSupplementaryCache = new Map<
|
||||
string,
|
||||
{ requestId: number; tierLabel: string | null; tierId: string | null; creditBalance: number | null }
|
||||
{
|
||||
requestId: number;
|
||||
tierLabel: string | null;
|
||||
tierId: string | null;
|
||||
creditBalance: number | null;
|
||||
}
|
||||
>();
|
||||
|
||||
export interface QuotaStore {
|
||||
@@ -98,11 +110,13 @@ export interface QuotaStore {
|
||||
codexQuota: Record<string, CodexQuotaState>;
|
||||
geminiCliQuota: Record<string, GeminiCliQuotaState>;
|
||||
kimiQuota: Record<string, KimiQuotaState>;
|
||||
xaiQuota: Record<string, XaiQuotaState>;
|
||||
setAntigravityQuota: (updater: QuotaUpdater<Record<string, AntigravityQuotaState>>) => void;
|
||||
setClaudeQuota: (updater: QuotaUpdater<Record<string, ClaudeQuotaState>>) => void;
|
||||
setCodexQuota: (updater: QuotaUpdater<Record<string, CodexQuotaState>>) => void;
|
||||
setGeminiCliQuota: (updater: QuotaUpdater<Record<string, GeminiCliQuotaState>>) => void;
|
||||
setKimiQuota: (updater: QuotaUpdater<Record<string, KimiQuotaState>>) => void;
|
||||
setXaiQuota: (updater: QuotaUpdater<Record<string, XaiQuotaState>>) => void;
|
||||
clearQuotaCache: () => void;
|
||||
}
|
||||
|
||||
@@ -233,12 +247,19 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
const WINDOW_META = {
|
||||
codeFiveHour: { id: 'five-hour', labelKey: 'codex_quota.primary_window' },
|
||||
codeWeekly: { id: 'weekly', labelKey: 'codex_quota.secondary_window' },
|
||||
codeReviewFiveHour: { id: 'code-review-five-hour', labelKey: 'codex_quota.code_review_primary_window' },
|
||||
codeReviewWeekly: { id: 'code-review-weekly', labelKey: 'codex_quota.code_review_secondary_window' },
|
||||
codeReviewFiveHour: {
|
||||
id: 'code-review-five-hour',
|
||||
labelKey: 'codex_quota.code_review_primary_window',
|
||||
},
|
||||
codeReviewWeekly: {
|
||||
id: 'code-review-weekly',
|
||||
labelKey: 'codex_quota.code_review_secondary_window',
|
||||
},
|
||||
} as const;
|
||||
|
||||
const rateLimit = payload.rate_limit ?? payload.rateLimit ?? undefined;
|
||||
const codeReviewLimit = payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
|
||||
const codeReviewLimit =
|
||||
payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
|
||||
const additionalRateLimits = payload.additional_rate_limits ?? payload.additionalRateLimits ?? [];
|
||||
const windows: CodexQuotaWindow[] = [];
|
||||
|
||||
@@ -302,7 +323,8 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
fiveHourWindow = primaryWindow && primaryWindow !== weeklyWindow ? primaryWindow : null;
|
||||
}
|
||||
if (!weeklyWindow) {
|
||||
weeklyWindow = secondaryWindow && secondaryWindow !== fiveHourWindow ? secondaryWindow : null;
|
||||
weeklyWindow =
|
||||
secondaryWindow && secondaryWindow !== fiveHourWindow ? secondaryWindow : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +392,8 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
|
||||
|
||||
const idPrefix = normalizeWindowId(limitName) || `additional-${index + 1}`;
|
||||
const additionalPrimaryWindow = rateInfo.primary_window ?? rateInfo.primaryWindow ?? null;
|
||||
const additionalSecondaryWindow = rateInfo.secondary_window ?? rateInfo.secondaryWindow ?? null;
|
||||
const additionalSecondaryWindow =
|
||||
rateInfo.secondary_window ?? rateInfo.secondaryWindow ?? null;
|
||||
const additionalLimitReached = rateInfo.limit_reached ?? rateInfo.limitReached;
|
||||
const additionalAllowed = rateInfo.allowed;
|
||||
|
||||
@@ -456,8 +479,7 @@ const resolveGeminiCliTierLabel = (
|
||||
if (!payload) return null;
|
||||
const currentTier: GeminiCliUserTier | null | undefined =
|
||||
payload.currentTier ?? payload.current_tier;
|
||||
const paidTier: GeminiCliUserTier | null | undefined =
|
||||
payload.paidTier ?? payload.paid_tier;
|
||||
const paidTier: GeminiCliUserTier | null | undefined = payload.paidTier ?? payload.paid_tier;
|
||||
const rawId = normalizeStringValue(paidTier?.id) ?? normalizeStringValue(currentTier?.id);
|
||||
if (!rawId) return null;
|
||||
const tierId = rawId.toLowerCase();
|
||||
@@ -465,14 +487,11 @@ const resolveGeminiCliTierLabel = (
|
||||
return labelKey ? t(`gemini_cli_quota.${labelKey}`) : rawId;
|
||||
};
|
||||
|
||||
const resolveGeminiCliTierId = (
|
||||
payload: GeminiCliCodeAssistPayload | null
|
||||
): string | null => {
|
||||
const resolveGeminiCliTierId = (payload: GeminiCliCodeAssistPayload | null): string | null => {
|
||||
if (!payload) return null;
|
||||
const currentTier: GeminiCliUserTier | null | undefined =
|
||||
payload.currentTier ?? payload.current_tier;
|
||||
const paidTier: GeminiCliUserTier | null | undefined =
|
||||
payload.paidTier ?? payload.paid_tier;
|
||||
const paidTier: GeminiCliUserTier | null | undefined = payload.paidTier ?? payload.paid_tier;
|
||||
const rawId = normalizeStringValue(paidTier?.id) ?? normalizeStringValue(currentTier?.id);
|
||||
return rawId ? rawId.toLowerCase() : null;
|
||||
};
|
||||
@@ -481,14 +500,12 @@ const resolveGeminiCliCreditBalance = (
|
||||
payload: GeminiCliCodeAssistPayload | null
|
||||
): number | null => {
|
||||
if (!payload) return null;
|
||||
const paidTier: GeminiCliUserTier | null | undefined =
|
||||
payload.paidTier ?? payload.paid_tier;
|
||||
const paidTier: GeminiCliUserTier | null | undefined = payload.paidTier ?? payload.paid_tier;
|
||||
const currentTier: GeminiCliUserTier | null | undefined =
|
||||
payload.currentTier ?? payload.current_tier;
|
||||
const tier = paidTier ?? currentTier;
|
||||
if (!tier) return null;
|
||||
const credits: GeminiCliCredits[] =
|
||||
tier.availableCredits ?? tier.available_credits ?? [];
|
||||
const credits: GeminiCliCredits[] = tier.availableCredits ?? tier.available_credits ?? [];
|
||||
let total = 0;
|
||||
let found = false;
|
||||
for (const credit of credits) {
|
||||
@@ -859,7 +876,11 @@ const renderGeminiCliItems = (
|
||||
|
||||
if (buckets.length === 0) {
|
||||
nodes.push(
|
||||
h('div', { key: 'empty', className: styleMap.quotaMessage }, t('gemini_cli_quota.empty_buckets'))
|
||||
h(
|
||||
'div',
|
||||
{ key: 'empty', className: styleMap.quotaMessage },
|
||||
t('gemini_cli_quota.empty_buckets')
|
||||
)
|
||||
);
|
||||
return h(Fragment, null, ...nodes);
|
||||
}
|
||||
@@ -973,8 +994,12 @@ const resolveClaudePlanType = (profile: ClaudeProfileResponse | null): string |
|
||||
const hasClaudePro = normalizeFlagValue(profile.account?.has_claude_pro);
|
||||
if (hasClaudePro) return 'plan_pro';
|
||||
|
||||
const organizationType = normalizeStringValue(profile.organization?.organization_type)?.toLowerCase();
|
||||
const subscriptionStatus = normalizeStringValue(profile.organization?.subscription_status)?.toLowerCase();
|
||||
const organizationType = normalizeStringValue(
|
||||
profile.organization?.organization_type
|
||||
)?.toLowerCase();
|
||||
const subscriptionStatus = normalizeStringValue(
|
||||
profile.organization?.subscription_status
|
||||
)?.toLowerCase();
|
||||
|
||||
if (organizationType === 'claude_team' && subscriptionStatus === 'active') {
|
||||
return 'plan_team';
|
||||
@@ -988,7 +1013,11 @@ const resolveClaudePlanType = (profile: ClaudeProfileResponse | null): string |
|
||||
const fetchClaudeQuota = async (
|
||||
file: AuthFileItem,
|
||||
t: TFunction
|
||||
): Promise<{ windows: ClaudeQuotaWindow[]; extraUsage?: ClaudeExtraUsage | null; planType?: string | null }> => {
|
||||
): Promise<{
|
||||
windows: ClaudeQuotaWindow[];
|
||||
extraUsage?: ClaudeExtraUsage | null;
|
||||
planType?: string | null;
|
||||
}> => {
|
||||
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
||||
const authIndex = normalizeAuthIndex(rawAuthIndex);
|
||||
if (!authIndex) {
|
||||
@@ -1217,7 +1246,13 @@ export const GEMINI_CLI_CONFIG: QuotaConfig<
|
||||
fetchQuota: fetchGeminiCliQuota,
|
||||
storeSelector: (state) => state.geminiCliQuota,
|
||||
storeSetter: 'setGeminiCliQuota',
|
||||
buildLoadingState: () => ({ status: 'loading', buckets: [], tierLabel: null, tierId: null, creditBalance: null }),
|
||||
buildLoadingState: () => ({
|
||||
status: 'loading',
|
||||
buckets: [],
|
||||
tierLabel: null,
|
||||
tierId: null,
|
||||
creditBalance: null,
|
||||
}),
|
||||
buildSuccessState: (data) => {
|
||||
const supplementarySnapshot = readGeminiCliSupplementarySnapshot(
|
||||
data.fileName,
|
||||
@@ -1245,10 +1280,7 @@ export const GEMINI_CLI_CONFIG: QuotaConfig<
|
||||
renderQuotaItems: renderGeminiCliItems,
|
||||
};
|
||||
|
||||
const fetchKimiQuota = async (
|
||||
file: AuthFileItem,
|
||||
t: TFunction
|
||||
): Promise<KimiQuotaRow[]> => {
|
||||
const fetchKimiQuota = async (file: AuthFileItem, t: TFunction): Promise<KimiQuotaRow[]> => {
|
||||
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
||||
const authIndex = normalizeAuthIndex(rawAuthIndex);
|
||||
if (!authIndex) {
|
||||
@@ -1299,7 +1331,7 @@ const renderKimiItems = (
|
||||
const percentLabel = remaining === null ? '--' : `${remaining}%`;
|
||||
const rowLabel = row.labelKey
|
||||
? t(row.labelKey, (row.labelParams ?? {}) as Record<string, string | number>)
|
||||
: row.label ?? '';
|
||||
: (row.label ?? '');
|
||||
const resetLabel = formatKimiResetHint(t, row.resetHint);
|
||||
|
||||
return h(
|
||||
@@ -1313,12 +1345,8 @@ const renderKimiItems = (
|
||||
'div',
|
||||
{ className: styleMap.quotaMeta },
|
||||
h('span', { className: styleMap.quotaPercent }, percentLabel),
|
||||
limit > 0
|
||||
? h('span', { className: styleMap.quotaAmount }, `${used} / ${limit}`)
|
||||
: null,
|
||||
resetLabel
|
||||
? h('span', { className: styleMap.quotaReset }, resetLabel)
|
||||
: null
|
||||
limit > 0 ? h('span', { className: styleMap.quotaAmount }, `${used} / ${limit}`) : null,
|
||||
resetLabel ? h('span', { className: styleMap.quotaReset }, resetLabel) : null
|
||||
)
|
||||
),
|
||||
h(QuotaProgressBar, {
|
||||
@@ -1330,6 +1358,151 @@ const renderKimiItems = (
|
||||
});
|
||||
};
|
||||
|
||||
const normalizeXaiCentValue = (value: XaiBillingConfig['monthlyLimit']): number | null => {
|
||||
if (value === undefined || value === null) return null;
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return normalizeNumberValue((value as { val?: unknown }).val);
|
||||
}
|
||||
return normalizeNumberValue(value);
|
||||
};
|
||||
|
||||
const buildXaiBillingSummary = (
|
||||
config: XaiBillingConfig | null | undefined
|
||||
): XaiBillingSummary | null => {
|
||||
if (!config || typeof config !== 'object') return null;
|
||||
|
||||
const monthlyLimitCents = normalizeXaiCentValue(config.monthlyLimit ?? config.monthly_limit);
|
||||
const usedCents = normalizeXaiCentValue(config.used);
|
||||
const onDemandCapCents = normalizeXaiCentValue(config.onDemandCap ?? config.on_demand_cap);
|
||||
const billingPeriodStart =
|
||||
normalizeStringValue(config.billingPeriodStart ?? config.billing_period_start) ?? undefined;
|
||||
const billingPeriodEnd =
|
||||
normalizeStringValue(config.billingPeriodEnd ?? config.billing_period_end) ?? undefined;
|
||||
|
||||
if (
|
||||
monthlyLimitCents === null &&
|
||||
usedCents === null &&
|
||||
onDemandCapCents === null &&
|
||||
!billingPeriodEnd
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const usedPercent =
|
||||
monthlyLimitCents !== null && monthlyLimitCents > 0 && usedCents !== null
|
||||
? (usedCents / monthlyLimitCents) * 100
|
||||
: null;
|
||||
|
||||
return {
|
||||
monthlyLimitCents,
|
||||
usedCents,
|
||||
onDemandCapCents,
|
||||
billingPeriodStart,
|
||||
billingPeriodEnd,
|
||||
usedPercent,
|
||||
};
|
||||
};
|
||||
|
||||
const fetchXaiQuota = async (file: AuthFileItem, t: TFunction): Promise<XaiBillingSummary> => {
|
||||
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
||||
const authIndex = normalizeAuthIndex(rawAuthIndex);
|
||||
if (!authIndex) {
|
||||
throw new Error(t('xai_quota.missing_auth_index'));
|
||||
}
|
||||
|
||||
const result = await apiCallApi.request({
|
||||
authIndex,
|
||||
method: 'GET',
|
||||
url: XAI_BILLING_URL,
|
||||
header: { ...XAI_REQUEST_HEADERS },
|
||||
});
|
||||
|
||||
if (result.statusCode < 200 || result.statusCode >= 300) {
|
||||
throw createStatusError(getApiCallErrorMessage(result), result.statusCode);
|
||||
}
|
||||
|
||||
const payload = parseXaiBillingPayload(result.body ?? result.bodyText);
|
||||
const summary = buildXaiBillingSummary(payload?.config);
|
||||
if (!summary) {
|
||||
throw new Error(t('xai_quota.empty_data'));
|
||||
}
|
||||
|
||||
return summary;
|
||||
};
|
||||
|
||||
const formatUsdFromCents = (cents: number | null): string => {
|
||||
if (cents === null) return '--';
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(cents / 100);
|
||||
};
|
||||
|
||||
const formatXaiUsageAmount = (billing: XaiBillingSummary): string => {
|
||||
const used = formatUsdFromCents(billing.usedCents);
|
||||
const limit = formatUsdFromCents(billing.monthlyLimitCents);
|
||||
if (billing.monthlyLimitCents === null) return used;
|
||||
return `${used} / ${limit}`;
|
||||
};
|
||||
|
||||
const renderXaiItems = (
|
||||
quota: XaiQuotaState,
|
||||
t: TFunction,
|
||||
helpers: QuotaRenderHelpers
|
||||
): ReactNode => {
|
||||
const { styles: styleMap, QuotaProgressBar } = helpers;
|
||||
const { createElement: h, Fragment } = React;
|
||||
const billing = quota.billing;
|
||||
|
||||
if (!billing) {
|
||||
return h('div', { className: styleMap.quotaMessage }, t('xai_quota.empty_data'));
|
||||
}
|
||||
|
||||
const clampedUsed =
|
||||
billing.usedPercent === null ? null : Math.max(0, Math.min(100, billing.usedPercent));
|
||||
const remaining = clampedUsed === null ? null : Math.max(0, Math.min(100, 100 - clampedUsed));
|
||||
const percentLabel = remaining === null ? '--' : `${Math.round(remaining)}%`;
|
||||
const amountLabel = formatXaiUsageAmount(billing);
|
||||
const resetLabel = formatQuotaResetTime(billing.billingPeriodEnd);
|
||||
const onDemandCap = billing.onDemandCapCents ?? 0;
|
||||
const payAsYouGoLabel =
|
||||
onDemandCap > 0
|
||||
? t('xai_quota.pay_as_you_go_enabled', { cap: formatUsdFromCents(onDemandCap) })
|
||||
: t('xai_quota.pay_as_you_go_disabled');
|
||||
|
||||
return h(
|
||||
Fragment,
|
||||
null,
|
||||
h(
|
||||
'div',
|
||||
{ key: 'pay-as-you-go', className: styleMap.codexPlan },
|
||||
h('span', { className: styleMap.codexPlanLabel }, t('xai_quota.pay_as_you_go_label')),
|
||||
h('span', { className: styleMap.codexPlanValue }, payAsYouGoLabel)
|
||||
),
|
||||
h(
|
||||
'div',
|
||||
{ key: 'monthly-credits', className: styleMap.quotaRow },
|
||||
h(
|
||||
'div',
|
||||
{ className: styleMap.quotaRowHeader },
|
||||
h('span', { className: styleMap.quotaModel }, t('xai_quota.monthly_credits')),
|
||||
h(
|
||||
'div',
|
||||
{ className: styleMap.quotaMeta },
|
||||
h('span', { className: styleMap.quotaPercent }, percentLabel),
|
||||
h('span', { className: styleMap.quotaAmount }, amountLabel),
|
||||
h('span', { className: styleMap.quotaReset }, resetLabel)
|
||||
)
|
||||
),
|
||||
h(QuotaProgressBar, {
|
||||
percent: remaining,
|
||||
highThreshold: QUOTA_PROGRESS_HIGH_THRESHOLD,
|
||||
mediumThreshold: QUOTA_PROGRESS_MEDIUM_THRESHOLD,
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const KIMI_CONFIG: QuotaConfig<KimiQuotaState, KimiQuotaRow[]> = {
|
||||
type: 'kimi',
|
||||
i18nPrefix: 'kimi_quota',
|
||||
@@ -1352,3 +1525,26 @@ export const KIMI_CONFIG: QuotaConfig<KimiQuotaState, KimiQuotaRow[]> = {
|
||||
gridClassName: styles.kimiGrid,
|
||||
renderQuotaItems: renderKimiItems,
|
||||
};
|
||||
|
||||
export const XAI_CONFIG: QuotaConfig<XaiQuotaState, XaiBillingSummary> = {
|
||||
type: 'xai',
|
||||
i18nPrefix: 'xai_quota',
|
||||
cardIdleMessageKey: 'quota_management.card_idle_hint',
|
||||
filterFn: (file) => isXaiFile(file) && !isDisabledAuthFile(file),
|
||||
fetchQuota: fetchXaiQuota,
|
||||
storeSelector: (state) => state.xaiQuota,
|
||||
storeSetter: 'setXaiQuota',
|
||||
buildLoadingState: () => ({ status: 'loading', billing: null }),
|
||||
buildSuccessState: (billing) => ({ status: 'success', billing }),
|
||||
buildErrorState: (message, status) => ({
|
||||
status: 'error',
|
||||
billing: null,
|
||||
error: message,
|
||||
errorStatus: status,
|
||||
}),
|
||||
cardClassName: styles.xaiCard,
|
||||
controlsClassName: styles.xaiControls,
|
||||
controlClassName: styles.xaiControl,
|
||||
gridClassName: styles.xaiGrid,
|
||||
renderQuotaItems: renderXaiItems,
|
||||
};
|
||||
|
||||
@@ -112,7 +112,9 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
? styles.geminiCliCard
|
||||
: quotaType === 'kimi'
|
||||
? styles.kimiCard
|
||||
: '';
|
||||
: quotaType === 'xai'
|
||||
? styles.xaiCard
|
||||
: '';
|
||||
|
||||
const rawAuthIndex = file['auth_index'] ?? file.authIndex;
|
||||
const authIndexKey = normalizeRecentRequestAuthIndex(rawAuthIndex);
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
CLAUDE_CONFIG,
|
||||
CODEX_CONFIG,
|
||||
GEMINI_CLI_CONFIG,
|
||||
KIMI_CONFIG
|
||||
KIMI_CONFIG,
|
||||
XAI_CONFIG,
|
||||
} from '@/components/quota';
|
||||
import { useNotificationStore, useQuotaStore } from '@/stores';
|
||||
import type { AuthFileItem } from '@/types';
|
||||
@@ -14,7 +15,7 @@ import { getStatusFromError } from '@/utils/quota';
|
||||
import {
|
||||
isRuntimeOnlyAuthFile,
|
||||
resolveQuotaErrorMessage,
|
||||
type QuotaProviderType
|
||||
type QuotaProviderType,
|
||||
} from '@/features/authFiles/constants';
|
||||
import { QuotaProgressBar } from '@/features/authFiles/components/QuotaProgressBar';
|
||||
import styles from '@/pages/AuthFilesPage.module.scss';
|
||||
@@ -26,6 +27,7 @@ const getQuotaConfig = (type: QuotaProviderType) => {
|
||||
if (type === 'claude') return CLAUDE_CONFIG;
|
||||
if (type === 'codex') return CODEX_CONFIG;
|
||||
if (type === 'kimi') return KIMI_CONFIG;
|
||||
if (type === 'xai') return XAI_CONFIG;
|
||||
return GEMINI_CLI_CONFIG;
|
||||
};
|
||||
|
||||
@@ -45,14 +47,18 @@ export function AuthFileQuotaSection(props: AuthFileQuotaSectionProps) {
|
||||
if (quotaType === 'claude') return state.claudeQuota[file.name] as QuotaState;
|
||||
if (quotaType === 'codex') return state.codexQuota[file.name] as QuotaState;
|
||||
if (quotaType === 'kimi') return state.kimiQuota[file.name] as QuotaState;
|
||||
if (quotaType === 'xai') return state.xaiQuota[file.name] as QuotaState;
|
||||
return state.geminiCliQuota[file.name] as QuotaState;
|
||||
});
|
||||
|
||||
const updateQuotaState = useQuotaStore((state) => {
|
||||
if (quotaType === 'antigravity') return state.setAntigravityQuota as unknown as (updater: unknown) => void;
|
||||
if (quotaType === 'claude') return state.setClaudeQuota as unknown as (updater: unknown) => void;
|
||||
if (quotaType === 'antigravity')
|
||||
return state.setAntigravityQuota as unknown as (updater: unknown) => void;
|
||||
if (quotaType === 'claude')
|
||||
return state.setClaudeQuota as unknown as (updater: unknown) => void;
|
||||
if (quotaType === 'codex') return state.setCodexQuota as unknown as (updater: unknown) => void;
|
||||
if (quotaType === 'kimi') return state.setKimiQuota as unknown as (updater: unknown) => void;
|
||||
if (quotaType === 'xai') return state.setXaiQuota as unknown as (updater: unknown) => void;
|
||||
return state.setGeminiCliQuota as unknown as (updater: unknown) => void;
|
||||
});
|
||||
|
||||
@@ -73,14 +79,14 @@ export function AuthFileQuotaSection(props: AuthFileQuotaSectionProps) {
|
||||
|
||||
updateQuotaState((prev: Record<string, unknown>) => ({
|
||||
...prev,
|
||||
[file.name]: config.buildLoadingState()
|
||||
[file.name]: config.buildLoadingState(),
|
||||
}));
|
||||
|
||||
try {
|
||||
const data = await config.fetchQuota(file, t);
|
||||
updateQuotaState((prev: Record<string, unknown>) => ({
|
||||
...prev,
|
||||
[file.name]: config.buildSuccessState(data)
|
||||
[file.name]: config.buildSuccessState(data),
|
||||
}));
|
||||
showNotification(t('auth_files.quota_refresh_success', { name: file.name }), 'success');
|
||||
} catch (err: unknown) {
|
||||
@@ -88,7 +94,7 @@ export function AuthFileQuotaSection(props: AuthFileQuotaSectionProps) {
|
||||
const status = getStatusFromError(err);
|
||||
updateQuotaState((prev: Record<string, unknown>) => ({
|
||||
...prev,
|
||||
[file.name]: config.buildErrorState(message, status)
|
||||
[file.name]: config.buildErrorState(message, status),
|
||||
}));
|
||||
showNotification(t('auth_files.quota_refresh_failed', { name: file.name, message }), 'error');
|
||||
}
|
||||
@@ -123,7 +129,7 @@ export function AuthFileQuotaSection(props: AuthFileQuotaSectionProps) {
|
||||
) : quotaStatus === 'error' ? (
|
||||
<div className={styles.quotaError}>
|
||||
{t(`${config.i18nPrefix}.load_failed`, {
|
||||
message: quotaErrorMessage
|
||||
message: quotaErrorMessage,
|
||||
})}
|
||||
</div>
|
||||
) : quota ? (
|
||||
|
||||
@@ -24,7 +24,7 @@ export type AuthFileModelItem = {
|
||||
};
|
||||
export type AuthFileIconAsset = string | { light: string; dark: string };
|
||||
|
||||
export type QuotaProviderType = 'antigravity' | 'claude' | 'codex' | 'gemini-cli' | 'kimi';
|
||||
export type QuotaProviderType = 'antigravity' | 'claude' | 'codex' | 'gemini-cli' | 'kimi' | 'xai';
|
||||
|
||||
export const QUOTA_PROVIDER_TYPES = new Set<QuotaProviderType>([
|
||||
'antigravity',
|
||||
@@ -32,6 +32,7 @@ export const QUOTA_PROVIDER_TYPES = new Set<QuotaProviderType>([
|
||||
'codex',
|
||||
'gemini-cli',
|
||||
'kimi',
|
||||
'xai',
|
||||
]);
|
||||
|
||||
export const MIN_CARD_PAGE_SIZE = 3;
|
||||
@@ -247,7 +248,7 @@ export const formatModified = (item: AuthFileItem): string => {
|
||||
const date =
|
||||
Number.isFinite(asNumber) && !Number.isNaN(asNumber)
|
||||
? new Date(asNumber < 1e12 ? asNumber * 1000 : asNumber)
|
||||
: parseTimestamp(raw) ?? new Date(String(raw));
|
||||
: (parseTimestamp(raw) ?? new Date(String(raw)));
|
||||
return Number.isNaN(date.getTime()) ? '-' : date.toLocaleString();
|
||||
};
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
* Hooks 统一导出
|
||||
*/
|
||||
|
||||
export { useApi } from './useApi';
|
||||
export { useDebounce } from './useDebounce';
|
||||
export { useLocalStorage } from './useLocalStorage';
|
||||
export { useInterval } from './useInterval';
|
||||
export { useMediaQuery } from './useMediaQuery';
|
||||
export { usePagination } from './usePagination';
|
||||
export { useHeaderRefresh } from './useHeaderRefresh';
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 通用 API 调用 Hook
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
|
||||
interface UseApiOptions<T> {
|
||||
onSuccess?: (data: T) => void;
|
||||
onError?: (error: Error) => void;
|
||||
showSuccessNotification?: boolean;
|
||||
showErrorNotification?: boolean;
|
||||
successMessage?: string;
|
||||
}
|
||||
|
||||
export function useApi<T = unknown, Args extends unknown[] = unknown[]>(
|
||||
apiFunction: (...args: Args) => Promise<T>,
|
||||
options: UseApiOptions<T> = {}
|
||||
) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const { showNotification } = useNotificationStore();
|
||||
|
||||
const execute = useCallback(
|
||||
async (...args: Args) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await apiFunction(...args);
|
||||
setData(result);
|
||||
|
||||
if (options.showSuccessNotification && options.successMessage) {
|
||||
showNotification(options.successMessage, 'success');
|
||||
}
|
||||
|
||||
options.onSuccess?.(result);
|
||||
return result;
|
||||
} catch (err: unknown) {
|
||||
const errorObj =
|
||||
err instanceof Error ? err : new Error(typeof err === 'string' ? err : 'Unknown error');
|
||||
setError(errorObj);
|
||||
|
||||
if (options.showErrorNotification !== false) {
|
||||
showNotification(errorObj.message, 'error');
|
||||
}
|
||||
|
||||
options.onError?.(errorObj);
|
||||
throw errorObj;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[apiFunction, options, showNotification]
|
||||
);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setData(null);
|
||||
setError(null);
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
return { data, loading, error, execute, reset };
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 防抖 Hook
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useDebounce<T>(value: T, delay: number = 500): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* 分页 Hook
|
||||
*/
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import type { PaginationState } from '@/types';
|
||||
|
||||
export function usePagination<T>(
|
||||
items: T[],
|
||||
initialPageSize: number = 20
|
||||
): PaginationState & {
|
||||
currentItems: T[];
|
||||
goToPage: (page: number) => void;
|
||||
nextPage: () => void;
|
||||
prevPage: () => void;
|
||||
setPageSize: (size: number) => void;
|
||||
} {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(initialPageSize);
|
||||
|
||||
const totalItems = items.length;
|
||||
const totalPages = Math.ceil(totalItems / pageSize) || 1;
|
||||
|
||||
const currentItems = useMemo(() => {
|
||||
const start = (currentPage - 1) * pageSize;
|
||||
const end = start + pageSize;
|
||||
return items.slice(start, end);
|
||||
}, [items, currentPage, pageSize]);
|
||||
|
||||
const goToPage = (page: number) => {
|
||||
const validPage = Math.max(1, Math.min(page, totalPages));
|
||||
setCurrentPage(validPage);
|
||||
};
|
||||
|
||||
const nextPage = () => {
|
||||
goToPage(currentPage + 1);
|
||||
};
|
||||
|
||||
const prevPage = () => {
|
||||
goToPage(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleSetPageSize = (size: number) => {
|
||||
setPageSize(size);
|
||||
setCurrentPage(1); // 重置到第一页
|
||||
};
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
pageSize,
|
||||
totalPages,
|
||||
totalItems,
|
||||
currentItems,
|
||||
goToPage,
|
||||
nextPage,
|
||||
prevPage,
|
||||
setPageSize: handleSetPageSize
|
||||
};
|
||||
}
|
||||
@@ -85,14 +85,15 @@
|
||||
"submit_button": "Login",
|
||||
"submitting": "Connecting...",
|
||||
"error_title": "Login Failed",
|
||||
"error_backend_detail": "Backend response",
|
||||
"error_required": "Please fill in complete connection information",
|
||||
"error_invalid": "Connection failed, please check address and key",
|
||||
"error_invalid": "Connection failed, please check the server address, Management API, and management key",
|
||||
"error_network": "Network connection failed, please check your network or server address",
|
||||
"error_timeout": "Connection timed out, server not responding",
|
||||
"error_unauthorized": "Authentication failed, invalid management key",
|
||||
"error_forbidden": "Access denied, insufficient permissions",
|
||||
"error_not_found": "Server address invalid or management API not enabled",
|
||||
"error_server": "Internal server error, please try again later",
|
||||
"error_unauthorized": "Authentication failed, management key is missing or invalid",
|
||||
"error_forbidden": "Access denied, remote management is disabled, the management key is not configured, or this IP is temporarily banned",
|
||||
"error_not_found": "Management API not found, confirm the server address points to the CLIProxyAPI backend",
|
||||
"error_server": "Backend failed while handling the management request, check the server logs",
|
||||
"error_cors": "Cross-origin request blocked, please check server configuration",
|
||||
"error_ssl": "SSL/TLS certificate verification failed"
|
||||
},
|
||||
@@ -725,6 +726,22 @@
|
||||
"limit_index": "Limit #{{index}}",
|
||||
"reset_hint": "resets in {{hint}}"
|
||||
},
|
||||
"xai_quota": {
|
||||
"title": "Grok Quota",
|
||||
"empty_title": "No xAI/Grok Auth Files",
|
||||
"empty_desc": "Log in with xAI OAuth to view Grok quota.",
|
||||
"idle": "Click here to refresh quota",
|
||||
"loading": "Loading quota...",
|
||||
"load_failed": "Failed to load quota: {{message}}",
|
||||
"missing_auth_index": "Auth file missing auth_index",
|
||||
"empty_data": "No quota data available",
|
||||
"refresh_button": "Refresh Quota",
|
||||
"fetch_all": "Fetch All",
|
||||
"monthly_credits": "Monthly credits",
|
||||
"pay_as_you_go_label": "Pay as you go",
|
||||
"pay_as_you_go_enabled": "Enabled, cap {{cap}}",
|
||||
"pay_as_you_go_disabled": "Disabled"
|
||||
},
|
||||
"vertex_import": {
|
||||
"title": "Vertex JSON Login",
|
||||
"description": "Upload a Google service account JSON to store it as auth-dir/vertex-<project>.json using the same rules as the CLI vertex-import helper.",
|
||||
|
||||
@@ -85,14 +85,15 @@
|
||||
"submit_button": "Войти",
|
||||
"submitting": "Подключение...",
|
||||
"error_title": "Ошибка входа",
|
||||
"error_backend_detail": "Ответ сервера",
|
||||
"error_required": "Пожалуйста, заполните все данные подключения",
|
||||
"error_invalid": "Подключение не удалось, проверьте адрес и ключ",
|
||||
"error_invalid": "Подключение не удалось, проверьте адрес сервера, Management API и ключ управления",
|
||||
"error_network": "Сетевая ошибка, проверьте подключение или адрес сервера",
|
||||
"error_timeout": "Время ожидания истекло, сервер не отвечает",
|
||||
"error_unauthorized": "Аутентификация не удалась, неверный ключ управления",
|
||||
"error_forbidden": "Доступ запрещён, недостаточно прав",
|
||||
"error_not_found": "Неверный адрес сервера или интерфейс управления не включён",
|
||||
"error_server": "Внутренняя ошибка сервера, попробуйте позже",
|
||||
"error_unauthorized": "Аутентификация не удалась, ключ управления отсутствует или неверен",
|
||||
"error_forbidden": "Доступ запрещён: удалённое управление отключено, ключ управления не настроен или IP временно заблокирован",
|
||||
"error_not_found": "Management API не найден, проверьте, что адрес указывает на backend CLIProxyAPI",
|
||||
"error_server": "Backend не смог обработать запрос управления, проверьте журналы сервера",
|
||||
"error_cors": "Блокировка CORS, проверьте конфигурацию сервера",
|
||||
"error_ssl": "Ошибка проверки SSL/TLS сертификата"
|
||||
},
|
||||
@@ -722,6 +723,22 @@
|
||||
"limit_index": "Лимит #{{index}}",
|
||||
"reset_hint": "сброс через {{hint}}"
|
||||
},
|
||||
"xai_quota": {
|
||||
"title": "Квота Grok",
|
||||
"empty_title": "Файлы авторизации xAI/Grok отсутствуют",
|
||||
"empty_desc": "Войдите через xAI OAuth, чтобы увидеть квоту Grok.",
|
||||
"idle": "Не загружено. Нажмите \"Обновить квоту\".",
|
||||
"loading": "Загрузка квоты...",
|
||||
"load_failed": "Не удалось загрузить квоту: {{message}}",
|
||||
"missing_auth_index": "В файле авторизации отсутствует auth_index",
|
||||
"empty_data": "Данные по квоте отсутствуют",
|
||||
"refresh_button": "Обновить квоту",
|
||||
"fetch_all": "Получить все",
|
||||
"monthly_credits": "Месячные кредиты",
|
||||
"pay_as_you_go_label": "Оплата по факту",
|
||||
"pay_as_you_go_enabled": "Включено, лимит {{cap}}",
|
||||
"pay_as_you_go_disabled": "Отключено"
|
||||
},
|
||||
"vertex_import": {
|
||||
"title": "Вход с Vertex JSON",
|
||||
"description": "Загрузите JSON ключа сервисного аккаунта Google, чтобы сохранить его как auth-dir/vertex-<project>.json по тем же правилам, что и помощник CLI vertex-import.",
|
||||
|
||||
@@ -85,14 +85,15 @@
|
||||
"submit_button": "登录",
|
||||
"submitting": "连接中...",
|
||||
"error_title": "登录失败",
|
||||
"error_backend_detail": "后端返回",
|
||||
"error_required": "请填写完整的连接信息",
|
||||
"error_invalid": "连接失败,请检查地址和密钥",
|
||||
"error_invalid": "连接失败,请检查服务地址、管理接口和管理密钥",
|
||||
"error_network": "网络连接失败,请检查网络或服务器地址",
|
||||
"error_timeout": "连接超时,服务器无响应",
|
||||
"error_unauthorized": "认证失败,管理密钥无效",
|
||||
"error_forbidden": "访问被拒绝,权限不足",
|
||||
"error_not_found": "服务器地址无效或管理接口未启用",
|
||||
"error_server": "服务器内部错误,请稍后重试",
|
||||
"error_unauthorized": "认证失败,管理密钥缺失或无效",
|
||||
"error_forbidden": "访问被拒绝,远程管理未启用、管理密钥未配置或 IP 被临时封禁",
|
||||
"error_not_found": "未找到 Management API,请确认服务地址指向 CLIProxyAPI 后端",
|
||||
"error_server": "后端处理管理接口时发生错误,请查看服务日志",
|
||||
"error_cors": "跨域请求被阻止,请检查服务器配置",
|
||||
"error_ssl": "SSL/TLS 证书验证失败"
|
||||
},
|
||||
@@ -725,6 +726,22 @@
|
||||
"limit_index": "限额 #{{index}}",
|
||||
"reset_hint": "{{hint}} 后重置"
|
||||
},
|
||||
"xai_quota": {
|
||||
"title": "Grok 额度",
|
||||
"empty_title": "暂无 xAI/Grok 认证",
|
||||
"empty_desc": "使用 xAI OAuth 登录后即可查看 Grok 额度。",
|
||||
"idle": "点击此处刷新额度",
|
||||
"loading": "正在加载额度...",
|
||||
"load_failed": "额度获取失败:{{message}}",
|
||||
"missing_auth_index": "认证文件缺少 auth_index",
|
||||
"empty_data": "暂无额度数据",
|
||||
"refresh_button": "刷新额度",
|
||||
"fetch_all": "获取全部",
|
||||
"monthly_credits": "月度积分",
|
||||
"pay_as_you_go_label": "按量付费",
|
||||
"pay_as_you_go_enabled": "已启用,封顶 {{cap}}",
|
||||
"pay_as_you_go_disabled": "未启用"
|
||||
},
|
||||
"vertex_import": {
|
||||
"title": "Vertex JSON 登录",
|
||||
"description": "上传 Google 服务账号 JSON,使用 CLI vertex-import 同步规则写入 auth-dir/vertex-<project>.json。",
|
||||
|
||||
@@ -85,14 +85,15 @@
|
||||
"submit_button": "登入",
|
||||
"submitting": "連線中...",
|
||||
"error_title": "登入失敗",
|
||||
"error_backend_detail": "後端返回",
|
||||
"error_required": "請填寫完整的連線資訊",
|
||||
"error_invalid": "連線失敗,請檢查位址和金鑰",
|
||||
"error_invalid": "連線失敗,請檢查服務位址、管理介面和管理金鑰",
|
||||
"error_network": "網路連線失敗,請檢查網路或伺服器位址",
|
||||
"error_timeout": "連線逾時,伺服器無回應",
|
||||
"error_unauthorized": "驗證失敗,管理金鑰無效",
|
||||
"error_forbidden": "存取被拒絕,權限不足",
|
||||
"error_not_found": "伺服器位址無效或管理介面未啟用",
|
||||
"error_server": "伺服器內部錯誤,請稍後再試",
|
||||
"error_unauthorized": "驗證失敗,管理金鑰缺失或無效",
|
||||
"error_forbidden": "存取被拒絕,遠端管理未啟用、管理金鑰未設定或 IP 被暫時封鎖",
|
||||
"error_not_found": "找不到 Management API,請確認服務位址指向 CLIProxyAPI 後端",
|
||||
"error_server": "後端處理管理介面時發生錯誤,請查看服務日誌",
|
||||
"error_cors": "跨域請求被阻擋,請檢查伺服器設定",
|
||||
"error_ssl": "SSL/TLS 憑證驗證失敗"
|
||||
},
|
||||
@@ -725,6 +726,22 @@
|
||||
"limit_index": "限額 #{{index}}",
|
||||
"reset_hint": "{{hint}} 後重置"
|
||||
},
|
||||
"xai_quota": {
|
||||
"title": "Grok 配額",
|
||||
"empty_title": "暫無 xAI/Grok 驗證",
|
||||
"empty_desc": "使用 xAI OAuth 登入後即可查看 Grok 配額。",
|
||||
"idle": "點擊此處重新整理配額",
|
||||
"loading": "正在載入配額...",
|
||||
"load_failed": "配額取得失敗:{{message}}",
|
||||
"missing_auth_index": "驗證檔案缺少 auth_index",
|
||||
"empty_data": "暫無配額資料",
|
||||
"refresh_button": "重新整理配額",
|
||||
"fetch_all": "取得全部",
|
||||
"monthly_credits": "月度點數",
|
||||
"pay_as_you_go_label": "按量付費",
|
||||
"pay_as_you_go_enabled": "已啟用,封頂 {{cap}}",
|
||||
"pay_as_you_go_disabled": "未啟用"
|
||||
},
|
||||
"vertex_import": {
|
||||
"title": "Vertex JSON 登入",
|
||||
"description": "上傳 Google 服務帳號 JSON,使用 CLI vertex-import 同步規則寫入 auth-dir/vertex-<project>.json。",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } f
|
||||
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
|
||||
import { modelsToEntries } from '@/components/ui/modelInputListUtils';
|
||||
import type { ClaudeEditBaseline } from '@/stores/useClaudeEditDraftStore';
|
||||
import { parseRouteIndexParam } from '@/utils/routeParams';
|
||||
|
||||
type LocationState = { fromAiProviders?: boolean } | null;
|
||||
|
||||
@@ -53,12 +54,6 @@ const buildEmptyForm = (): ProviderFormState => ({
|
||||
excludedText: '',
|
||||
});
|
||||
|
||||
const parseIndexParam = (value: string | undefined) => {
|
||||
if (!value) return null;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const getErrorMessage = (err: unknown) => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
@@ -122,7 +117,7 @@ export function AiProvidersClaudeEditLayout() {
|
||||
|
||||
const params = useParams<{ index?: string }>();
|
||||
const hasIndexParam = typeof params.index === 'string';
|
||||
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
|
||||
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
|
||||
const invalidIndexParam = hasIndexParam && editIndex === null;
|
||||
|
||||
const connectionStatus = useAuthStore((state) => state.connectionStatus);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||
import type { ProviderKeyConfig } from '@/types';
|
||||
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
|
||||
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
|
||||
import { parseRouteIndexParam } from '@/utils/routeParams';
|
||||
import { entriesToModels, modelsToEntries } from '@/components/ui/modelInputListUtils';
|
||||
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
|
||||
import type { ProviderFormState } from '@/components/providers';
|
||||
@@ -40,12 +41,6 @@ const buildEmptyForm = (): ProviderFormState => ({
|
||||
excludedText: '',
|
||||
});
|
||||
|
||||
const parseIndexParam = (value: string | undefined) => {
|
||||
if (!value) return null;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const getErrorMessage = (err: unknown) => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
@@ -121,7 +116,7 @@ export function AiProvidersCodexEditPage() {
|
||||
const modelDiscoveryRequestIdRef = useRef(0);
|
||||
|
||||
const hasIndexParam = typeof params.index === 'string';
|
||||
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
|
||||
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
|
||||
const invalidIndexParam = hasIndexParam && editIndex === null;
|
||||
|
||||
const initialData = useMemo(() => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||
import type { GeminiKeyConfig } from '@/types';
|
||||
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
|
||||
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
|
||||
import { parseRouteIndexParam } from '@/utils/routeParams';
|
||||
import type { ModelInfo } from '@/utils/models';
|
||||
import { entriesToModels, modelsToEntries } from '@/components/ui/modelInputListUtils';
|
||||
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
|
||||
@@ -37,12 +38,6 @@ const buildEmptyForm = (): GeminiFormState => ({
|
||||
excludedText: '',
|
||||
});
|
||||
|
||||
const parseIndexParam = (value: string | undefined) => {
|
||||
if (!value) return null;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const stripGeminiModelResourceName = (value: string) => {
|
||||
return String(value ?? '')
|
||||
.trim()
|
||||
@@ -116,7 +111,7 @@ export function AiProvidersGeminiEditPage() {
|
||||
const modelDiscoveryRequestIdRef = useRef(0);
|
||||
|
||||
const hasIndexParam = typeof params.index === 'string';
|
||||
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
|
||||
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
|
||||
const invalidIndexParam = hasIndexParam && editIndex === null;
|
||||
|
||||
const initialData = useMemo(() => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { ApiKeyEntry, OpenAIProviderConfig } from '@/types';
|
||||
import type { ModelInfo } from '@/utils/models';
|
||||
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
|
||||
import { areKeyValueEntriesEqual, areModelEntriesEqual } from '@/utils/compare';
|
||||
import { parseRouteIndexParam } from '@/utils/routeParams';
|
||||
import { buildApiKeyEntry } from '@/components/providers/utils';
|
||||
import type { ModelEntry, OpenAIFormState } from '@/components/providers/types';
|
||||
import type { KeyTestStatus, OpenAIEditBaseline } from '@/stores/useOpenAIEditDraftStore';
|
||||
@@ -52,12 +53,6 @@ const buildEmptyForm = (): OpenAIFormState => ({
|
||||
testModel: undefined,
|
||||
});
|
||||
|
||||
const parseIndexParam = (value: string | undefined) => {
|
||||
if (!value) return null;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const getErrorMessage = (err: unknown) => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
@@ -140,7 +135,7 @@ export function AiProvidersOpenAIEditLayout() {
|
||||
|
||||
const params = useParams<{ index?: string }>();
|
||||
const hasIndexParam = typeof params.index === 'string';
|
||||
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
|
||||
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
|
||||
const invalidIndexParam = hasIndexParam && editIndex === null;
|
||||
|
||||
const connectionStatus = useAuthStore((state) => state.connectionStatus);
|
||||
|
||||
@@ -16,6 +16,7 @@ import type { ProviderKeyConfig } from '@/types';
|
||||
import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils';
|
||||
import { buildHeaderObject, headersToEntries, normalizeHeaderEntries } from '@/utils/headers';
|
||||
import { areKeyValueEntriesEqual, areModelEntriesEqual, areStringArraysEqual } from '@/utils/compare';
|
||||
import { parseRouteIndexParam } from '@/utils/routeParams';
|
||||
import type { VertexFormState } from '@/components/providers';
|
||||
import layoutStyles from './AiProvidersEditLayout.module.scss';
|
||||
|
||||
@@ -33,12 +34,6 @@ const buildEmptyForm = (): VertexFormState => ({
|
||||
excludedText: '',
|
||||
});
|
||||
|
||||
const parseIndexParam = (value: string | undefined) => {
|
||||
if (!value) return null;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
const normalizeModelEntries = (entries: Array<{ name: string; alias: string }>) =>
|
||||
(entries ?? []).reduce<Array<{ name: string; alias: string }>>((acc, entry) => {
|
||||
const name = String(entry?.name ?? '').trim();
|
||||
@@ -93,7 +88,7 @@ export function AiProvidersVertexEditPage() {
|
||||
const [baseline, setBaseline] = useState(() => buildVertexBaseline(buildEmptyForm()));
|
||||
|
||||
const hasIndexParam = typeof params.index === 'string';
|
||||
const editIndex = useMemo(() => parseIndexParam(params.index), [params.index]);
|
||||
const editIndex = useMemo(() => parseRouteIndexParam(params.index), [params.index]);
|
||||
const invalidIndexParam = hasIndexParam && editIndex === null;
|
||||
|
||||
const initialData = useMemo(() => {
|
||||
|
||||
@@ -517,6 +517,10 @@
|
||||
background-image: linear-gradient(180deg, rgba(220, 232, 255, 0.08), transparent);
|
||||
}
|
||||
|
||||
.xaiCard {
|
||||
background-image: linear-gradient(180deg, rgba(243, 244, 246, 0.08), transparent);
|
||||
}
|
||||
|
||||
.quotaSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -35,6 +35,15 @@ function readCommercialModeFromYaml(yamlContent: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeYamlForVisualDiff(yamlContent: string): string {
|
||||
try {
|
||||
const doc = parseDocument(yamlContent);
|
||||
return doc.toString({ indent: 2, lineWidth: 120, minContentWidth: 0 });
|
||||
} catch {
|
||||
return yamlContent;
|
||||
}
|
||||
}
|
||||
|
||||
export function ConfigPage() {
|
||||
const { t } = useTranslation();
|
||||
const pageTransitionLayer = usePageTransitionLayer();
|
||||
@@ -178,6 +187,8 @@ export function ConfigPage() {
|
||||
try {
|
||||
const latestServerYaml = await configFileApi.fetchConfigYaml();
|
||||
|
||||
const visualBaseYaml = dirty ? content : latestServerYaml;
|
||||
|
||||
if (activeTab !== 'source') {
|
||||
const latestDocument = parseDocument(latestServerYaml);
|
||||
if (latestDocument.errors.length > 0) {
|
||||
@@ -191,23 +202,34 @@ export function ConfigPage() {
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (visualBaseYaml !== latestServerYaml) {
|
||||
const visualBaseDocument = parseDocument(visualBaseYaml);
|
||||
if (visualBaseDocument.errors.length > 0) {
|
||||
showNotification(
|
||||
t('config_management.visual_mode_latest_yaml_invalid', {
|
||||
message:
|
||||
visualBaseDocument.errors[0]?.message ??
|
||||
t('config_management.visual_mode_save_blocked'),
|
||||
}),
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In source mode, save exactly what the user edited. In visual mode, materialize visual changes into the latest YAML.
|
||||
// In source mode, save exactly what the user edited. In visual mode, preserve the
|
||||
// local source draft when it has unsaved edits so source-only backend fields are not dropped.
|
||||
const nextMergedYaml =
|
||||
activeTab === 'source' ? content : applyVisualChangesToYaml(latestServerYaml);
|
||||
activeTab === 'source' ? content : applyVisualChangesToYaml(visualBaseYaml);
|
||||
|
||||
// In visual mode, applyVisualChangesToYaml re-serializes YAML via parseDocument → toString,
|
||||
// which may reformat comments/whitespace. Normalize the server YAML through the same pipeline
|
||||
// so the diff only shows actual value changes, not cosmetic reformatting.
|
||||
let diffOriginal = latestServerYaml;
|
||||
if (activeTab !== 'source') {
|
||||
try {
|
||||
const doc = parseDocument(latestServerYaml);
|
||||
diffOriginal = doc.toString({ indent: 2, lineWidth: 120, minContentWidth: 0 });
|
||||
} catch {
|
||||
/* keep raw on parse failure */
|
||||
}
|
||||
diffOriginal = normalizeYamlForVisualDiff(latestServerYaml);
|
||||
}
|
||||
|
||||
if (diffOriginal === nextMergedYaml) {
|
||||
|
||||
+20
-5
@@ -32,18 +32,33 @@ function getLocalizedErrorMessage(error: unknown, t: (key: string) => string): s
|
||||
? error
|
||||
: '';
|
||||
|
||||
const withHttpStatus = (summary: string) => {
|
||||
if (!status) {
|
||||
return summary;
|
||||
}
|
||||
|
||||
const genericAxiosMessage = `Request failed with status code ${status}`;
|
||||
const detail = message.trim();
|
||||
const backendDetail =
|
||||
detail && detail !== genericAxiosMessage
|
||||
? ` (${t('login.error_backend_detail')}: ${detail})`
|
||||
: '';
|
||||
|
||||
return `HTTP ${status}: ${summary}${backendDetail}`;
|
||||
};
|
||||
|
||||
// 根据 HTTP 状态码判断
|
||||
if (status === 401) {
|
||||
return t('login.error_unauthorized');
|
||||
return withHttpStatus(t('login.error_unauthorized'));
|
||||
}
|
||||
if (status === 403) {
|
||||
return t('login.error_forbidden');
|
||||
return withHttpStatus(t('login.error_forbidden'));
|
||||
}
|
||||
if (status === 404) {
|
||||
return t('login.error_not_found');
|
||||
return withHttpStatus(t('login.error_not_found'));
|
||||
}
|
||||
if (status && status >= 500) {
|
||||
return t('login.error_server');
|
||||
return withHttpStatus(t('login.error_server'));
|
||||
}
|
||||
|
||||
// 根据 axios 错误码判断
|
||||
@@ -63,7 +78,7 @@ function getLocalizedErrorMessage(error: unknown, t: (key: string) => string): s
|
||||
}
|
||||
|
||||
// 默认错误消息
|
||||
return t('login.error_invalid');
|
||||
return withHttpStatus(t('login.error_invalid'));
|
||||
}
|
||||
|
||||
export function LoginPage() {
|
||||
|
||||
@@ -114,7 +114,8 @@
|
||||
.claudeGrid,
|
||||
.codexGrid,
|
||||
.geminiCliGrid,
|
||||
.kimiGrid {
|
||||
.kimiGrid,
|
||||
.xaiGrid {
|
||||
display: grid;
|
||||
gap: $spacing-md;
|
||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||
@@ -128,7 +129,8 @@
|
||||
.claudeControls,
|
||||
.codexControls,
|
||||
.geminiCliControls,
|
||||
.kimiControls {
|
||||
.kimiControls,
|
||||
.xaiControls {
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
flex-wrap: wrap;
|
||||
@@ -140,7 +142,8 @@
|
||||
.claudeControl,
|
||||
.codexControl,
|
||||
.geminiCliControl,
|
||||
.kimiControl {
|
||||
.kimiControl,
|
||||
.xaiControl {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
@@ -247,33 +250,27 @@
|
||||
|
||||
// 卡片渐变背景 — 基于 TYPE_COLORS light.bg 转 rgba
|
||||
.claudeCard {
|
||||
background-image: linear-gradient(180deg,
|
||||
rgba(251, 236, 228, 0.18),
|
||||
rgba(251, 236, 228, 0));
|
||||
background-image: linear-gradient(180deg, rgba(251, 236, 228, 0.18), rgba(251, 236, 228, 0));
|
||||
}
|
||||
|
||||
.antigravityCard {
|
||||
background-image: linear-gradient(180deg,
|
||||
rgba(224, 247, 250, 0.12),
|
||||
rgba(224, 247, 250, 0));
|
||||
background-image: linear-gradient(180deg, rgba(224, 247, 250, 0.12), rgba(224, 247, 250, 0));
|
||||
}
|
||||
|
||||
.codexCard {
|
||||
background-image: linear-gradient(180deg,
|
||||
rgba(234, 231, 255, 0.18),
|
||||
rgba(234, 231, 255, 0));
|
||||
background-image: linear-gradient(180deg, rgba(234, 231, 255, 0.18), rgba(234, 231, 255, 0));
|
||||
}
|
||||
|
||||
.geminiCliCard {
|
||||
background-image: linear-gradient(180deg,
|
||||
rgba(224, 232, 255, 0.2),
|
||||
rgba(224, 232, 255, 0));
|
||||
background-image: linear-gradient(180deg, rgba(224, 232, 255, 0.2), rgba(224, 232, 255, 0));
|
||||
}
|
||||
|
||||
.kimiCard {
|
||||
background-image: linear-gradient(180deg,
|
||||
rgba(220, 232, 255, 0.2),
|
||||
rgba(220, 232, 255, 0));
|
||||
background-image: linear-gradient(180deg, rgba(220, 232, 255, 0.2), rgba(220, 232, 255, 0));
|
||||
}
|
||||
|
||||
.xaiCard {
|
||||
background-image: linear-gradient(180deg, rgba(243, 244, 246, 0.22), rgba(243, 244, 246, 0));
|
||||
}
|
||||
|
||||
.quotaSection {
|
||||
@@ -636,7 +633,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-sm;
|
||||
transition: transform $transition-fast, box-shadow $transition-fast, border-color $transition-fast;
|
||||
transition:
|
||||
transform $transition-fast,
|
||||
box-shadow $transition-fast,
|
||||
border-color $transition-fast;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
CLAUDE_CONFIG,
|
||||
CODEX_CONFIG,
|
||||
GEMINI_CLI_CONFIG,
|
||||
KIMI_CONFIG
|
||||
KIMI_CONFIG,
|
||||
XAI_CONFIG,
|
||||
} from '@/components/quota';
|
||||
import type { AuthFileItem } from '@/types';
|
||||
import styles from './QuotaPage.module.scss';
|
||||
@@ -89,6 +90,12 @@ export function QuotaPage() {
|
||||
loading={loading}
|
||||
disabled={disableControls}
|
||||
/>
|
||||
<QuotaSection
|
||||
config={XAI_CONFIG}
|
||||
files={files}
|
||||
loading={loading}
|
||||
disabled={disableControls}
|
||||
/>
|
||||
<QuotaSection
|
||||
config={GEMINI_CLI_CONFIG}
|
||||
files={files}
|
||||
|
||||
+339
-20
@@ -6,21 +6,286 @@ import { apiClient } from './client';
|
||||
import {
|
||||
normalizeGeminiKeyConfig,
|
||||
normalizeOpenAIProvider,
|
||||
normalizeProviderKeyConfig
|
||||
normalizeProviderKeyConfig,
|
||||
} from './transformers';
|
||||
import type {
|
||||
GeminiKeyConfig,
|
||||
OpenAIProviderConfig,
|
||||
ProviderKeyConfig,
|
||||
ApiKeyEntry,
|
||||
ModelAlias
|
||||
ModelAlias,
|
||||
} from '@/types';
|
||||
|
||||
const serializeHeaders = (headers?: Record<string, string>) => (headers && Object.keys(headers).length ? headers : undefined);
|
||||
const serializeHeaders = (headers?: Record<string, string>) =>
|
||||
headers && Object.keys(headers).length ? headers : undefined;
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const RESPONSE_ONLY_FIELDS = ['auth-index', 'authIndex', 'auth_index'] as const;
|
||||
|
||||
const PROVIDER_KEY_FIELDS = [
|
||||
'api-key',
|
||||
'apiKey',
|
||||
'priority',
|
||||
'prefix',
|
||||
'base-url',
|
||||
'baseUrl',
|
||||
'base_url',
|
||||
'websockets',
|
||||
'proxy-url',
|
||||
'proxyUrl',
|
||||
'proxy_url',
|
||||
'headers',
|
||||
'models',
|
||||
'excluded-models',
|
||||
'excludedModels',
|
||||
'excluded_models',
|
||||
'cloak',
|
||||
] as const;
|
||||
|
||||
const GEMINI_KEY_FIELDS = PROVIDER_KEY_FIELDS.filter(
|
||||
(field) => field !== 'websockets' && field !== 'cloak'
|
||||
);
|
||||
const VERTEX_KEY_FIELDS = GEMINI_KEY_FIELDS;
|
||||
|
||||
const OPENAI_PROVIDER_FIELDS = [
|
||||
'name',
|
||||
'priority',
|
||||
'disabled',
|
||||
'prefix',
|
||||
'base-url',
|
||||
'baseUrl',
|
||||
'base_url',
|
||||
'api-key-entries',
|
||||
'apiKeyEntries',
|
||||
'api_key_entries',
|
||||
'api-keys',
|
||||
'apiKeys',
|
||||
'api_keys',
|
||||
'headers',
|
||||
'models',
|
||||
'test-model',
|
||||
'testModel',
|
||||
'test_model',
|
||||
] as const;
|
||||
|
||||
const MODEL_ALIAS_FIELDS = [
|
||||
'name',
|
||||
'id',
|
||||
'model',
|
||||
'alias',
|
||||
'display_name',
|
||||
'displayName',
|
||||
'priority',
|
||||
'test-model',
|
||||
'testModel',
|
||||
'test_model',
|
||||
] as const;
|
||||
|
||||
const API_KEY_ENTRY_FIELDS = [
|
||||
'api-key',
|
||||
'apiKey',
|
||||
'key',
|
||||
'proxy-url',
|
||||
'proxyUrl',
|
||||
'proxy_url',
|
||||
] as const;
|
||||
|
||||
const CLOAK_FIELDS = [
|
||||
'mode',
|
||||
'strict-mode',
|
||||
'strictMode',
|
||||
'strict_mode',
|
||||
'sensitive-words',
|
||||
'sensitiveWords',
|
||||
'sensitive_words',
|
||||
] as const;
|
||||
|
||||
const RAW_SECTION_ALIASES: Record<string, readonly string[]> = {
|
||||
'gemini-api-key': ['gemini-api-key', 'geminiApiKey', 'geminiApiKeys'],
|
||||
'codex-api-key': ['codex-api-key', 'codexApiKey', 'codexApiKeys'],
|
||||
'claude-api-key': ['claude-api-key', 'claudeApiKey', 'claudeApiKeys'],
|
||||
'vertex-api-key': ['vertex-api-key', 'vertexApiKey', 'vertexApiKeys'],
|
||||
'openai-compatibility': ['openai-compatibility', 'openaiCompatibility', 'openAICompatibility'],
|
||||
};
|
||||
|
||||
const getStringField = (record: Record<string, unknown>, keys: readonly string[]) => {
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
if (value === undefined || value === null) continue;
|
||||
const text = String(value).trim();
|
||||
if (text) return text;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const providerKeyIdentity = (record: Record<string, unknown>) => {
|
||||
const apiKey = getStringField(record, ['api-key', 'apiKey']);
|
||||
if (!apiKey) return '';
|
||||
const baseUrl = getStringField(record, ['base-url', 'baseUrl', 'base_url']);
|
||||
return `${apiKey}\u0000${baseUrl}`;
|
||||
};
|
||||
|
||||
const openAIProviderIdentity = (record: Record<string, unknown>) =>
|
||||
getStringField(record, ['name', 'id']);
|
||||
|
||||
const modelIdentity = (record: Record<string, unknown>) =>
|
||||
getStringField(record, ['name', 'id', 'model']);
|
||||
|
||||
const apiKeyEntryIdentity = (record: Record<string, unknown>) =>
|
||||
getStringField(record, ['api-key', 'apiKey', 'key']);
|
||||
|
||||
const cloneWithoutKnownFields = (
|
||||
raw: unknown,
|
||||
knownFields: readonly string[]
|
||||
): Record<string, unknown> => {
|
||||
const next: Record<string, unknown> = isRecord(raw) ? { ...raw } : {};
|
||||
[...knownFields, ...RESPONSE_ONLY_FIELDS].forEach((field) => {
|
||||
delete next[field];
|
||||
});
|
||||
return next;
|
||||
};
|
||||
|
||||
const mergeKnownFields = (
|
||||
raw: unknown,
|
||||
payload: Record<string, unknown>,
|
||||
knownFields: readonly string[]
|
||||
) => {
|
||||
const next = cloneWithoutKnownFields(raw, knownFields);
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
next[key] = value;
|
||||
}
|
||||
});
|
||||
return next;
|
||||
};
|
||||
|
||||
const findRawRecord = (
|
||||
rawRecords: Array<Record<string, unknown> | undefined>,
|
||||
usedIndexes: Set<number>,
|
||||
payload: Record<string, unknown>,
|
||||
index: number,
|
||||
getIdentity: (record: Record<string, unknown>) => string
|
||||
) => {
|
||||
const identity = getIdentity(payload);
|
||||
if (identity) {
|
||||
for (let i = 0; i < rawRecords.length; i += 1) {
|
||||
const candidate = rawRecords[i];
|
||||
if (!candidate || usedIndexes.has(i)) continue;
|
||||
if (getIdentity(candidate) === identity) {
|
||||
usedIndexes.add(i);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fallback = rawRecords[index];
|
||||
if (fallback && !usedIndexes.has(index)) {
|
||||
usedIndexes.add(index);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const mergeKnownRecordList = (
|
||||
rawItems: unknown,
|
||||
payloadItems: Record<string, unknown>[],
|
||||
knownFields: readonly string[],
|
||||
getIdentity: (record: Record<string, unknown>) => string
|
||||
) => {
|
||||
const rawRecords = Array.isArray(rawItems)
|
||||
? rawItems.map((item) => (isRecord(item) ? item : undefined))
|
||||
: [];
|
||||
const usedIndexes = new Set<number>();
|
||||
|
||||
return payloadItems.map((payload, index) => {
|
||||
const raw = findRawRecord(rawRecords, usedIndexes, payload, index, getIdentity);
|
||||
return mergeKnownFields(raw, payload, knownFields);
|
||||
});
|
||||
};
|
||||
|
||||
const getRawSectionList = (rawConfig: unknown, section: string) => {
|
||||
if (!isRecord(rawConfig)) return [];
|
||||
const aliases = RAW_SECTION_ALIASES[section] ?? [section];
|
||||
for (const alias of aliases) {
|
||||
const value = rawConfig[alias];
|
||||
if (Array.isArray(value)) return value;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const mergeModelPayloads = (raw: unknown, models: unknown) =>
|
||||
Array.isArray(models)
|
||||
? mergeKnownRecordList(
|
||||
isRecord(raw) ? raw.models : undefined,
|
||||
models.filter(isRecord),
|
||||
MODEL_ALIAS_FIELDS,
|
||||
modelIdentity
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const mergeProviderKeyPayload = (
|
||||
raw: unknown,
|
||||
payload: Record<string, unknown>,
|
||||
knownFields: readonly string[]
|
||||
) => {
|
||||
const next = mergeKnownFields(raw, payload, knownFields);
|
||||
const models = mergeModelPayloads(raw, payload.models);
|
||||
if (models) next.models = models;
|
||||
if (isRecord(payload.cloak)) {
|
||||
next.cloak = mergeKnownFields(
|
||||
isRecord(raw) ? raw.cloak : undefined,
|
||||
payload.cloak,
|
||||
CLOAK_FIELDS
|
||||
);
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const mergeOpenAIProviderPayload = (raw: unknown, payload: Record<string, unknown>) => {
|
||||
const next = mergeKnownFields(raw, payload, OPENAI_PROVIDER_FIELDS);
|
||||
const rawApiKeyEntries = isRecord(raw)
|
||||
? (raw['api-key-entries'] ?? raw.apiKeyEntries)
|
||||
: undefined;
|
||||
const apiKeyEntries = payload['api-key-entries'];
|
||||
if (Array.isArray(apiKeyEntries)) {
|
||||
next['api-key-entries'] = mergeKnownRecordList(
|
||||
rawApiKeyEntries,
|
||||
apiKeyEntries.filter(isRecord),
|
||||
API_KEY_ENTRY_FIELDS,
|
||||
apiKeyEntryIdentity
|
||||
);
|
||||
}
|
||||
const models = mergeModelPayloads(raw, payload.models);
|
||||
if (models) next.models = models;
|
||||
return next;
|
||||
};
|
||||
|
||||
const buildPreservedList = async <T>(
|
||||
section: string,
|
||||
configs: T[],
|
||||
serialize: (item: T) => Record<string, unknown>,
|
||||
mergePayload: (raw: unknown, payload: Record<string, unknown>) => Record<string, unknown>,
|
||||
getIdentity: (record: Record<string, unknown>) => string
|
||||
) => {
|
||||
// These PUT endpoints replace entire backend slices. Merge over the current
|
||||
// raw config first so backend-only fields survive UI saves and toggles.
|
||||
const rawConfig = await apiClient.get('/config');
|
||||
const rawItems = getRawSectionList(rawConfig, section);
|
||||
const payloads = configs.map((item) => serialize(item));
|
||||
const rawRecords = Array.isArray(rawItems)
|
||||
? rawItems.map((item) => (isRecord(item) ? item : undefined))
|
||||
: [];
|
||||
const usedIndexes = new Set<number>();
|
||||
|
||||
return payloads.map((payload, index) => {
|
||||
const raw = findRawRecord(rawRecords, usedIndexes, payload, index, getIdentity);
|
||||
return mergePayload(raw, payload);
|
||||
});
|
||||
};
|
||||
|
||||
const extractArrayPayload = (data: unknown, key: string): unknown[] => {
|
||||
if (Array.isArray(data)) return data;
|
||||
if (!isRecord(data)) return [];
|
||||
@@ -81,7 +346,8 @@ const serializeProviderKey = (config: ProviderKeyConfig) => {
|
||||
const cloakPayload: Record<string, unknown> = {};
|
||||
const mode = config.cloak.mode?.trim();
|
||||
if (mode) cloakPayload.mode = mode;
|
||||
if (config.cloak.strictMode !== undefined) cloakPayload['strict-mode'] = config.cloak.strictMode;
|
||||
if (config.cloak.strictMode !== undefined)
|
||||
cloakPayload['strict-mode'] = config.cloak.strictMode;
|
||||
if (config.cloak.sensitiveWords && config.cloak.sensitiveWords.length) {
|
||||
cloakPayload['sensitive-words'] = config.cloak.sensitiveWords;
|
||||
}
|
||||
@@ -142,7 +408,7 @@ const serializeOpenAIProvider = (provider: OpenAIProviderConfig) => {
|
||||
'base-url': provider.baseUrl,
|
||||
'api-key-entries': Array.isArray(provider.apiKeyEntries)
|
||||
? provider.apiKeyEntries.map((entry) => serializeApiKeyEntry(entry))
|
||||
: []
|
||||
: [],
|
||||
};
|
||||
if (provider.prefix?.trim()) payload.prefix = provider.prefix.trim();
|
||||
if (provider.disabled !== undefined) payload.disabled = provider.disabled;
|
||||
@@ -162,8 +428,17 @@ export const providersApi = {
|
||||
return list.map((item) => normalizeGeminiKeyConfig(item)).filter(Boolean) as GeminiKeyConfig[];
|
||||
},
|
||||
|
||||
saveGeminiKeys: (configs: GeminiKeyConfig[]) =>
|
||||
apiClient.put('/gemini-api-key', configs.map((item) => serializeGeminiKey(item))),
|
||||
saveGeminiKeys: async (configs: GeminiKeyConfig[]) =>
|
||||
apiClient.put(
|
||||
'/gemini-api-key',
|
||||
await buildPreservedList(
|
||||
'gemini-api-key',
|
||||
configs,
|
||||
serializeGeminiKey,
|
||||
(raw, payload) => mergeProviderKeyPayload(raw, payload, GEMINI_KEY_FIELDS),
|
||||
providerKeyIdentity
|
||||
)
|
||||
),
|
||||
|
||||
updateGeminiKey: (index: number, value: GeminiKeyConfig) =>
|
||||
apiClient.patch('/gemini-api-key', { index, value: serializeGeminiKey(value) }),
|
||||
@@ -174,11 +449,22 @@ export const providersApi = {
|
||||
async getCodexConfigs(): Promise<ProviderKeyConfig[]> {
|
||||
const data = await apiClient.get('/codex-api-key');
|
||||
const list = extractArrayPayload(data, 'codex-api-key');
|
||||
return list.map((item) => normalizeProviderKeyConfig(item)).filter(Boolean) as ProviderKeyConfig[];
|
||||
return list
|
||||
.map((item) => normalizeProviderKeyConfig(item))
|
||||
.filter(Boolean) as ProviderKeyConfig[];
|
||||
},
|
||||
|
||||
saveCodexConfigs: (configs: ProviderKeyConfig[]) =>
|
||||
apiClient.put('/codex-api-key', configs.map((item) => serializeProviderKey(item))),
|
||||
saveCodexConfigs: async (configs: ProviderKeyConfig[]) =>
|
||||
apiClient.put(
|
||||
'/codex-api-key',
|
||||
await buildPreservedList(
|
||||
'codex-api-key',
|
||||
configs,
|
||||
serializeProviderKey,
|
||||
(raw, payload) => mergeProviderKeyPayload(raw, payload, PROVIDER_KEY_FIELDS),
|
||||
providerKeyIdentity
|
||||
)
|
||||
),
|
||||
|
||||
updateCodexConfig: (index: number, value: ProviderKeyConfig) =>
|
||||
apiClient.patch('/codex-api-key', { index, value: serializeProviderKey(value) }),
|
||||
@@ -189,11 +475,22 @@ export const providersApi = {
|
||||
async getClaudeConfigs(): Promise<ProviderKeyConfig[]> {
|
||||
const data = await apiClient.get('/claude-api-key');
|
||||
const list = extractArrayPayload(data, 'claude-api-key');
|
||||
return list.map((item) => normalizeProviderKeyConfig(item)).filter(Boolean) as ProviderKeyConfig[];
|
||||
return list
|
||||
.map((item) => normalizeProviderKeyConfig(item))
|
||||
.filter(Boolean) as ProviderKeyConfig[];
|
||||
},
|
||||
|
||||
saveClaudeConfigs: (configs: ProviderKeyConfig[]) =>
|
||||
apiClient.put('/claude-api-key', configs.map((item) => serializeProviderKey(item))),
|
||||
saveClaudeConfigs: async (configs: ProviderKeyConfig[]) =>
|
||||
apiClient.put(
|
||||
'/claude-api-key',
|
||||
await buildPreservedList(
|
||||
'claude-api-key',
|
||||
configs,
|
||||
serializeProviderKey,
|
||||
(raw, payload) => mergeProviderKeyPayload(raw, payload, PROVIDER_KEY_FIELDS),
|
||||
providerKeyIdentity
|
||||
)
|
||||
),
|
||||
|
||||
updateClaudeConfig: (index: number, value: ProviderKeyConfig) =>
|
||||
apiClient.patch('/claude-api-key', { index, value: serializeProviderKey(value) }),
|
||||
@@ -204,11 +501,22 @@ export const providersApi = {
|
||||
async getVertexConfigs(): Promise<ProviderKeyConfig[]> {
|
||||
const data = await apiClient.get('/vertex-api-key');
|
||||
const list = extractArrayPayload(data, 'vertex-api-key');
|
||||
return list.map((item) => normalizeProviderKeyConfig(item)).filter(Boolean) as ProviderKeyConfig[];
|
||||
return list
|
||||
.map((item) => normalizeProviderKeyConfig(item))
|
||||
.filter(Boolean) as ProviderKeyConfig[];
|
||||
},
|
||||
|
||||
saveVertexConfigs: (configs: ProviderKeyConfig[]) =>
|
||||
apiClient.put('/vertex-api-key', configs.map((item) => serializeVertexKey(item))),
|
||||
saveVertexConfigs: async (configs: ProviderKeyConfig[]) =>
|
||||
apiClient.put(
|
||||
'/vertex-api-key',
|
||||
await buildPreservedList(
|
||||
'vertex-api-key',
|
||||
configs,
|
||||
serializeVertexKey,
|
||||
(raw, payload) => mergeProviderKeyPayload(raw, payload, VERTEX_KEY_FIELDS),
|
||||
providerKeyIdentity
|
||||
)
|
||||
),
|
||||
|
||||
updateVertexConfig: (index: number, value: ProviderKeyConfig) =>
|
||||
apiClient.patch('/vertex-api-key', { index, value: serializeVertexKey(value) }),
|
||||
@@ -219,11 +527,22 @@ export const providersApi = {
|
||||
async getOpenAIProviders(): Promise<OpenAIProviderConfig[]> {
|
||||
const data = await apiClient.get('/openai-compatibility');
|
||||
const list = extractArrayPayload(data, 'openai-compatibility');
|
||||
return list.map((item) => normalizeOpenAIProvider(item)).filter(Boolean) as OpenAIProviderConfig[];
|
||||
return list
|
||||
.map((item) => normalizeOpenAIProvider(item))
|
||||
.filter(Boolean) as OpenAIProviderConfig[];
|
||||
},
|
||||
|
||||
saveOpenAIProviders: (providers: OpenAIProviderConfig[]) =>
|
||||
apiClient.put('/openai-compatibility', providers.map((item) => serializeOpenAIProvider(item))),
|
||||
saveOpenAIProviders: async (providers: OpenAIProviderConfig[]) =>
|
||||
apiClient.put(
|
||||
'/openai-compatibility',
|
||||
await buildPreservedList(
|
||||
'openai-compatibility',
|
||||
providers,
|
||||
serializeOpenAIProvider,
|
||||
mergeOpenAIProviderPayload,
|
||||
openAIProviderIdentity
|
||||
)
|
||||
),
|
||||
|
||||
updateOpenAIProvider: (index: number, value: OpenAIProviderConfig) =>
|
||||
apiClient.patch('/openai-compatibility', { index, value: serializeOpenAIProvider(value) }),
|
||||
@@ -232,5 +551,5 @@ export const providersApi = {
|
||||
apiClient.patch('/openai-compatibility', { index, value: { disabled } }),
|
||||
|
||||
deleteOpenAIProvider: (name: string) =>
|
||||
apiClient.delete(`/openai-compatibility?name=${encodeURIComponent(name)}`)
|
||||
apiClient.delete(`/openai-compatibility?name=${encodeURIComponent(name)}`),
|
||||
};
|
||||
|
||||
@@ -3,7 +3,14 @@
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import type { AntigravityQuotaState, ClaudeQuotaState, CodexQuotaState, GeminiCliQuotaState, KimiQuotaState } from '@/types';
|
||||
import type {
|
||||
AntigravityQuotaState,
|
||||
ClaudeQuotaState,
|
||||
CodexQuotaState,
|
||||
GeminiCliQuotaState,
|
||||
KimiQuotaState,
|
||||
XaiQuotaState,
|
||||
} from '@/types';
|
||||
|
||||
type QuotaUpdater<T> = T | ((prev: T) => T);
|
||||
|
||||
@@ -13,15 +20,17 @@ interface QuotaStoreState {
|
||||
codexQuota: Record<string, CodexQuotaState>;
|
||||
geminiCliQuota: Record<string, GeminiCliQuotaState>;
|
||||
kimiQuota: Record<string, KimiQuotaState>;
|
||||
xaiQuota: Record<string, XaiQuotaState>;
|
||||
setAntigravityQuota: (updater: QuotaUpdater<Record<string, AntigravityQuotaState>>) => void;
|
||||
setClaudeQuota: (updater: QuotaUpdater<Record<string, ClaudeQuotaState>>) => void;
|
||||
setCodexQuota: (updater: QuotaUpdater<Record<string, CodexQuotaState>>) => void;
|
||||
setGeminiCliQuota: (updater: QuotaUpdater<Record<string, GeminiCliQuotaState>>) => void;
|
||||
setKimiQuota: (updater: QuotaUpdater<Record<string, KimiQuotaState>>) => void;
|
||||
setXaiQuota: (updater: QuotaUpdater<Record<string, XaiQuotaState>>) => void;
|
||||
clearQuotaCache: () => void;
|
||||
}
|
||||
|
||||
const resolveUpdater = <T,>(updater: QuotaUpdater<T>, prev: T): T => {
|
||||
const resolveUpdater = <T>(updater: QuotaUpdater<T>, prev: T): T => {
|
||||
if (typeof updater === 'function') {
|
||||
return (updater as (value: T) => T)(prev);
|
||||
}
|
||||
@@ -34,25 +43,30 @@ export const useQuotaStore = create<QuotaStoreState>((set) => ({
|
||||
codexQuota: {},
|
||||
geminiCliQuota: {},
|
||||
kimiQuota: {},
|
||||
xaiQuota: {},
|
||||
setAntigravityQuota: (updater) =>
|
||||
set((state) => ({
|
||||
antigravityQuota: resolveUpdater(updater, state.antigravityQuota)
|
||||
antigravityQuota: resolveUpdater(updater, state.antigravityQuota),
|
||||
})),
|
||||
setClaudeQuota: (updater) =>
|
||||
set((state) => ({
|
||||
claudeQuota: resolveUpdater(updater, state.claudeQuota)
|
||||
claudeQuota: resolveUpdater(updater, state.claudeQuota),
|
||||
})),
|
||||
setCodexQuota: (updater) =>
|
||||
set((state) => ({
|
||||
codexQuota: resolveUpdater(updater, state.codexQuota)
|
||||
codexQuota: resolveUpdater(updater, state.codexQuota),
|
||||
})),
|
||||
setGeminiCliQuota: (updater) =>
|
||||
set((state) => ({
|
||||
geminiCliQuota: resolveUpdater(updater, state.geminiCliQuota)
|
||||
geminiCliQuota: resolveUpdater(updater, state.geminiCliQuota),
|
||||
})),
|
||||
setKimiQuota: (updater) =>
|
||||
set((state) => ({
|
||||
kimiQuota: resolveUpdater(updater, state.kimiQuota)
|
||||
kimiQuota: resolveUpdater(updater, state.kimiQuota),
|
||||
})),
|
||||
setXaiQuota: (updater) =>
|
||||
set((state) => ({
|
||||
xaiQuota: resolveUpdater(updater, state.xaiQuota),
|
||||
})),
|
||||
clearQuotaCache: () =>
|
||||
set({
|
||||
@@ -60,6 +74,7 @@ export const useQuotaStore = create<QuotaStoreState>((set) => ({
|
||||
claudeQuota: {},
|
||||
codexQuota: {},
|
||||
geminiCliQuota: {},
|
||||
kimiQuota: {}
|
||||
})
|
||||
kimiQuota: {},
|
||||
xaiQuota: {},
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -306,3 +306,40 @@ export interface KimiQuotaState {
|
||||
error?: string;
|
||||
errorStatus?: number;
|
||||
}
|
||||
|
||||
// xAI/Grok API payload types
|
||||
export interface XaiBillingCent {
|
||||
val?: number | string;
|
||||
}
|
||||
|
||||
export interface XaiBillingConfig {
|
||||
monthlyLimit?: XaiBillingCent | number | string | null;
|
||||
monthly_limit?: XaiBillingCent | number | string | null;
|
||||
used?: XaiBillingCent | number | string | null;
|
||||
onDemandCap?: XaiBillingCent | number | string | null;
|
||||
on_demand_cap?: XaiBillingCent | number | string | null;
|
||||
billingPeriodStart?: string;
|
||||
billing_period_start?: string;
|
||||
billingPeriodEnd?: string;
|
||||
billing_period_end?: string;
|
||||
}
|
||||
|
||||
export interface XaiBillingPayload {
|
||||
config?: XaiBillingConfig | null;
|
||||
}
|
||||
|
||||
export interface XaiBillingSummary {
|
||||
monthlyLimitCents: number | null;
|
||||
usedCents: number | null;
|
||||
onDemandCapCents: number | null;
|
||||
billingPeriodStart?: string;
|
||||
billingPeriodEnd?: string;
|
||||
usedPercent: number | null;
|
||||
}
|
||||
|
||||
export interface XaiQuotaState {
|
||||
status: 'idle' | 'loading' | 'success' | 'error';
|
||||
billing: XaiBillingSummary | null;
|
||||
error?: string;
|
||||
errorStatus?: number;
|
||||
}
|
||||
|
||||
@@ -18,26 +18,17 @@ export const MANAGEMENT_API_PREFIX = '/v0/management';
|
||||
export const REQUEST_TIMEOUT_MS = 30 * 1000;
|
||||
export const VERSION_HEADER_KEYS = ['x-cpa-version', 'x-server-version'];
|
||||
export const BUILD_DATE_HEADER_KEYS = ['x-cpa-build-date', 'x-server-build-date'];
|
||||
export const STATUS_UPDATE_INTERVAL_MS = 1000;
|
||||
export const LOG_REFRESH_DELAY_MS = 500;
|
||||
|
||||
// 日志相关
|
||||
export const MAX_LOG_LINES = 2000;
|
||||
export const LOG_FETCH_LIMIT = 2500;
|
||||
export const LOGS_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
// 认证文件分页
|
||||
export const DEFAULT_AUTH_FILES_PAGE_SIZE = 20;
|
||||
export const MIN_AUTH_FILES_PAGE_SIZE = 10;
|
||||
export const MAX_AUTH_FILES_PAGE_SIZE = 100;
|
||||
export const MAX_AUTH_FILE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
// 本地存储键名
|
||||
export const STORAGE_KEY_AUTH = 'cli-proxy-auth';
|
||||
export const STORAGE_KEY_THEME = 'cli-proxy-theme';
|
||||
export const STORAGE_KEY_LANGUAGE = 'cli-proxy-language';
|
||||
export const STORAGE_KEY_SIDEBAR = 'cli-proxy-sidebar-collapsed';
|
||||
export const STORAGE_KEY_AUTH_FILES_PAGE_SIZE = 'cli-proxy-auth-files-page-size';
|
||||
|
||||
// 语言配置
|
||||
export const LANGUAGE_ORDER = defineLanguageOrder(['zh-CN', 'zh-TW', 'en', 'ru'] as const);
|
||||
@@ -51,32 +42,3 @@ export const SUPPORTED_LANGUAGES = LANGUAGE_ORDER;
|
||||
|
||||
// 通知持续时间
|
||||
export const NOTIFICATION_DURATION_MS = 3000;
|
||||
|
||||
// OAuth 卡片 ID 列表
|
||||
export const OAUTH_CARD_IDS = [
|
||||
'codex-oauth-card',
|
||||
'anthropic-oauth-card',
|
||||
'antigravity-oauth-card',
|
||||
'gemini-cli-oauth-card',
|
||||
'kimi-oauth-card',
|
||||
'xai-oauth-card'
|
||||
];
|
||||
export const OAUTH_PROVIDERS = {
|
||||
CODEX: 'codex',
|
||||
ANTHROPIC: 'anthropic',
|
||||
ANTIGRAVITY: 'antigravity',
|
||||
GEMINI_CLI: 'gemini-cli',
|
||||
KIMI: 'kimi',
|
||||
XAI: 'xai'
|
||||
} as const;
|
||||
|
||||
// API 端点
|
||||
export const API_ENDPOINTS = {
|
||||
CONFIG: '/config',
|
||||
LOGIN: '/login',
|
||||
API_KEYS: '/api-keys',
|
||||
PROVIDERS: '/providers',
|
||||
AUTH_FILES: '/auth-files',
|
||||
OAUTH: '/oauth',
|
||||
LOGS: '/logs'
|
||||
} as const;
|
||||
|
||||
@@ -5,14 +5,6 @@ import { parseTimestamp } from './timestamp';
|
||||
* 从原项目 src/utils/string.js 迁移
|
||||
*/
|
||||
|
||||
const resolveDefaultLocale = (): string | undefined => {
|
||||
const fromDocument =
|
||||
typeof document !== 'undefined' ? document.documentElement?.lang?.trim() : '';
|
||||
if (fromDocument) return fromDocument;
|
||||
const fromNavigator = typeof navigator !== 'undefined' ? navigator.language?.trim() : '';
|
||||
return fromNavigator || undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 隐藏 API Key 中间部分,仅保留前后两位
|
||||
*/
|
||||
@@ -45,27 +37,6 @@ export function formatFileSize(bytes: number): string {
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
*/
|
||||
export function formatDateTime(date: string | Date, locale?: string): string {
|
||||
const d = typeof date === 'string' ? parseTimestamp(date) ?? new Date(date) : date;
|
||||
|
||||
if (isNaN(d.getTime())) {
|
||||
return 'Invalid Date';
|
||||
}
|
||||
|
||||
const resolvedLocale = locale?.trim() || resolveDefaultLocale();
|
||||
return d.toLocaleString(resolvedLocale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Unix 时间戳(秒/毫秒/微秒/纳秒)格式化为本地时间字符串
|
||||
*/
|
||||
@@ -96,21 +67,3 @@ export function formatUnixTimestamp(value: unknown, locale?: string): string {
|
||||
if (Number.isNaN(date.getTime())) return '';
|
||||
return locale ? date.toLocaleString(locale) : date.toLocaleString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(添加千位分隔符)
|
||||
*/
|
||||
export function formatNumber(num: number, locale?: string): string {
|
||||
const resolvedLocale = locale?.trim() || resolveDefaultLocale();
|
||||
return num.toLocaleString(resolvedLocale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截断长文本
|
||||
*/
|
||||
export function truncateText(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return text.slice(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
@@ -3,86 +3,9 @@
|
||||
* 从原项目 src/utils/array.js, dom.js, html.js 迁移
|
||||
*/
|
||||
|
||||
/**
|
||||
* 规范化数组响应(处理后端可能返回非数组的情况)
|
||||
*/
|
||||
export function normalizeArrayResponse<T>(data: T | T[] | null | undefined): T[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
return [data];
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
*/
|
||||
export function debounce<This, Args extends unknown[], Return>(
|
||||
func: (this: This, ...args: Args) => Return,
|
||||
delay: number
|
||||
): (this: This, ...args: Args) => void {
|
||||
let timeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
return function (this: This, ...args: Args) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 节流函数
|
||||
*/
|
||||
export function throttle<This, Args extends unknown[], Return>(
|
||||
func: (this: This, ...args: Args) => Return,
|
||||
limit: number
|
||||
): (this: This, ...args: Args) => void {
|
||||
let inThrottle: boolean;
|
||||
|
||||
return function (this: This, ...args: Args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => (inThrottle = false), limit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML 转义(防 XSS)
|
||||
*/
|
||||
export function escapeHtml(text: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一 ID
|
||||
*/
|
||||
export function generateId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 深拷贝对象
|
||||
*/
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== 'object') return obj;
|
||||
|
||||
if (obj instanceof Date) return new Date(obj.getTime()) as unknown as T;
|
||||
if (Array.isArray(obj)) return obj.map((item) => deepClone(item)) as unknown as T;
|
||||
|
||||
const source = obj as Record<string, unknown>;
|
||||
const cloned: Record<string, unknown> = {};
|
||||
for (const key in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
cloned[key] = deepClone(source[key]);
|
||||
}
|
||||
}
|
||||
return cloned as unknown as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟函数
|
||||
*/
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ export const TYPE_COLORS: Record<string, TypeColorSet> = {
|
||||
light: { bg: '#e0f7fa', text: '#006064' },
|
||||
dark: { bg: '#004d40', text: '#80deea' },
|
||||
},
|
||||
xai: {
|
||||
light: { bg: '#f3f4f6', text: '#111827', border: '1px solid #d1d5db' },
|
||||
dark: { bg: '#111827', text: '#f9fafb', border: '1px solid #374151' },
|
||||
},
|
||||
iflow: {
|
||||
light: { bg: '#f5e3fc', text: '#9025c8' },
|
||||
dark: { bg: '#521490', text: '#d49cf5' },
|
||||
@@ -176,7 +180,11 @@ export const CLAUDE_REQUEST_HEADERS = {
|
||||
export const CLAUDE_USAGE_WINDOW_KEYS = [
|
||||
{ key: 'five_hour', id: 'five-hour', labelKey: 'claude_quota.five_hour' },
|
||||
{ key: 'seven_day', id: 'seven-day', labelKey: 'claude_quota.seven_day' },
|
||||
{ key: 'seven_day_oauth_apps', id: 'seven-day-oauth-apps', labelKey: 'claude_quota.seven_day_oauth_apps' },
|
||||
{
|
||||
key: 'seven_day_oauth_apps',
|
||||
id: 'seven-day-oauth-apps',
|
||||
labelKey: 'claude_quota.seven_day_oauth_apps',
|
||||
},
|
||||
{ key: 'seven_day_opus', id: 'seven-day-opus', labelKey: 'claude_quota.seven_day_opus' },
|
||||
{ key: 'seven_day_sonnet', id: 'seven-day-sonnet', labelKey: 'claude_quota.seven_day_sonnet' },
|
||||
{ key: 'seven_day_cowork', id: 'seven-day-cowork', labelKey: 'claude_quota.seven_day_cowork' },
|
||||
@@ -198,3 +206,10 @@ export const KIMI_USAGE_URL = 'https://api.kimi.com/coding/v1/usages';
|
||||
export const KIMI_REQUEST_HEADERS = {
|
||||
Authorization: 'Bearer $TOKEN$',
|
||||
};
|
||||
|
||||
// xAI/Grok API configuration
|
||||
export const XAI_BILLING_URL = 'https://cli-chat-proxy.grok.com/v1/billing';
|
||||
|
||||
export const XAI_REQUEST_HEADERS = {
|
||||
Authorization: 'Bearer $TOKEN$',
|
||||
};
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
* Normalization and parsing functions for quota data.
|
||||
*/
|
||||
|
||||
import type { ClaudeUsagePayload, CodexUsagePayload, GeminiCliCodeAssistPayload, GeminiCliQuotaPayload, KimiUsagePayload } from '@/types';
|
||||
import type {
|
||||
ClaudeUsagePayload,
|
||||
CodexUsagePayload,
|
||||
GeminiCliCodeAssistPayload,
|
||||
GeminiCliQuotaPayload,
|
||||
KimiUsagePayload,
|
||||
XaiBillingPayload,
|
||||
} from '@/types';
|
||||
import { normalizeAuthIndex } from '@/utils/authIndex';
|
||||
|
||||
const GEMINI_CLI_MODEL_SUFFIX = '_vertex';
|
||||
@@ -191,7 +198,9 @@ export function parseGeminiCliQuotaPayload(payload: unknown): GeminiCliQuotaPayl
|
||||
return null;
|
||||
}
|
||||
|
||||
export function parseGeminiCliCodeAssistPayload(payload: unknown): GeminiCliCodeAssistPayload | null {
|
||||
export function parseGeminiCliCodeAssistPayload(
|
||||
payload: unknown
|
||||
): GeminiCliCodeAssistPayload | null {
|
||||
if (payload === undefined || payload === null) return null;
|
||||
if (typeof payload === 'string') {
|
||||
const trimmed = payload.trim();
|
||||
@@ -224,3 +233,20 @@ export function parseKimiUsagePayload(payload: unknown): KimiUsagePayload | null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function parseXaiBillingPayload(payload: unknown): XaiBillingPayload | null {
|
||||
if (payload === undefined || payload === null) return null;
|
||||
if (typeof payload === 'string') {
|
||||
const trimmed = payload.trim();
|
||||
if (!trimmed) return null;
|
||||
try {
|
||||
return JSON.parse(trimmed) as XaiBillingPayload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (typeof payload === 'object') {
|
||||
return payload as XaiBillingPayload;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import { GEMINI_CLI_IGNORED_MODEL_PREFIXES } from './constants';
|
||||
|
||||
export function resolveAuthProvider(file: AuthFileItem): string {
|
||||
const raw = file.provider ?? file.type ?? '';
|
||||
return String(raw).trim().toLowerCase();
|
||||
const key = String(raw).trim().toLowerCase().replace(/_/g, '-');
|
||||
if (key === 'x-ai' || key === 'grok') return 'xai';
|
||||
return key;
|
||||
}
|
||||
|
||||
export function isAntigravityFile(file: AuthFileItem): boolean {
|
||||
@@ -25,9 +27,7 @@ export function isClaudeOAuthFile(file: AuthFileItem): boolean {
|
||||
? (file.metadata as Record<string, unknown>)
|
||||
: null;
|
||||
const accessToken =
|
||||
metadata && typeof metadata.access_token === 'string'
|
||||
? metadata.access_token.trim()
|
||||
: '';
|
||||
metadata && typeof metadata.access_token === 'string' ? metadata.access_token.trim() : '';
|
||||
return accessToken.includes('sk-ant-oat');
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ export function isKimiFile(file: AuthFileItem): boolean {
|
||||
return resolveAuthProvider(file) === 'kimi';
|
||||
}
|
||||
|
||||
export function isXaiFile(file: AuthFileItem): boolean {
|
||||
return resolveAuthProvider(file) === 'xai';
|
||||
}
|
||||
|
||||
export function isRuntimeOnlyAuthFile(file: AuthFileItem): boolean {
|
||||
const raw = file['runtime_only'] ?? file.runtimeOnly;
|
||||
if (typeof raw === 'boolean') return raw;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const ROUTE_INDEX_PATTERN = /^(0|[1-9]\d*)$/;
|
||||
|
||||
export function parseRouteIndexParam(value: string | undefined): number | null {
|
||||
// Route indexes must be canonical digits only; parseInt would accept values like "1abc"
|
||||
// and could route a malformed URL to a real provider entry.
|
||||
if (!value || !ROUTE_INDEX_PATTERN.test(value)) return null;
|
||||
|
||||
const parsed = Number(value);
|
||||
return Number.isSafeInteger(parsed) ? parsed : null;
|
||||
}
|
||||
@@ -2,39 +2,6 @@
|
||||
* 验证工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 验证 URL 格式
|
||||
*/
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 API Base URL
|
||||
*/
|
||||
export function isValidApiBase(apiBase: string): boolean {
|
||||
if (!apiBase) return false;
|
||||
|
||||
// 允许 http/https 协议
|
||||
const urlPattern = /^https?:\/\/.+/i;
|
||||
return urlPattern.test(apiBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 API Key 格式
|
||||
*/
|
||||
export function isValidApiKey(key: string): boolean {
|
||||
if (!key || key.length < 8) return false;
|
||||
|
||||
// 基础验证:不包含空格
|
||||
return !/\s/.test(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 API Key 字符集(仅允许 ASCII 可见字符)
|
||||
*/
|
||||
@@ -42,23 +9,3 @@ export function isValidApiKeyCharset(key: string): boolean {
|
||||
if (!key) return false;
|
||||
return /^[\x21-\x7E]+$/.test(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 JSON 格式
|
||||
*/
|
||||
export function isValidJson(str: string): boolean {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Email 格式
|
||||
*/
|
||||
export function isValidEmail(email: string): boolean {
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailPattern.test(email);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user