generated from kevisual/vite-react-template
Compare commits
26 Commits
1884e87421
...
ee176fd80b
| Author | SHA1 | Date | |
|---|---|---|---|
| ee176fd80b | |||
| d85fc1154f | |||
| 3227c795a3 | |||
| 0d8d81b1fc | |||
| 9e8c658159 | |||
| 60a7328889 | |||
| 245bbb33b0 | |||
| a54597c65e | |||
| 5a769a6748 | |||
| bbb762db97 | |||
| 3ef9c47508 | |||
| 635e6a8a1b | |||
| 7ec6428643 | |||
| f4643464ba | |||
| f1c27a8726 | |||
| 6b37e34392 | |||
| de87263a5d | |||
| eb1adbc100 | |||
| 6516bacbf4 | |||
| 83fc1dca18 | |||
| d85a1cf84b | |||
|
|
fe3a3cfdc3 | ||
|
|
a9f42f14f0 | ||
| a934a7620d | |||
| 6750a8858d | |||
| f9fd2a67b4 |
@@ -46,3 +46,10 @@ pages/page-app/
|
||||
- **sonner**: Toast 通知
|
||||
- **zustand**: 状态管理
|
||||
- **tailwindcss v4**: 使用 @tailwindcss/vite 插件进行样式处理
|
||||
|
||||
## 主题系统
|
||||
|
||||
- **主题配色**: 采用黑白配色方案,提供简洁优雅的视觉体验
|
||||
- **主题模式**: 支持 light(浅色)和 dark(深色)模式切换
|
||||
- **主题实现**: 使用 `next-themes` 进行主题管理
|
||||
- **CSS 变量**: 主题相关的 CSS 变量定义在 `src/styles/theme.css` 中
|
||||
27
README.md
27
README.md
@@ -1 +1,26 @@
|
||||
# vite-react-template
|
||||
# cnb center
|
||||
|
||||
> cnb 仓库界面很不好用,所以自己写了一个纯调api的界面,方便管理仓库和查看同步状态
|
||||
|
||||
## 主要功能
|
||||
|
||||
### git仓库管理功能
|
||||
|
||||
- 创建仓库
|
||||
- 删除仓库(使用cookie)
|
||||
- 同步仓库
|
||||
|
||||
仓库列出用户所有仓库,主体显示仓库名,描述。每一个仓库具备更快捷的功能模块。
|
||||
|
||||
启动是启动云开发环境。
|
||||
|
||||

|
||||
|
||||
配置项
|
||||
|
||||

|
||||
|
||||
示例cookie配置
|
||||
```sh
|
||||
CNBSESSION=1770622649.1935321989751226368.0bc7fc786f7052cb2b077c00ded651a5945d46d1e466f4fafa14ede554da14a0;csrfkey=158893308
|
||||
```
|
||||
615
bun.lock
Normal file
615
bun.lock
Normal file
@@ -0,0 +1,615 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@kevisual/cnb-center",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.45",
|
||||
"@ai-sdk/openai": "^3.0.30",
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@base-ui/react": "^1.2.0",
|
||||
"@kevisual/cnb": "^0.0.26",
|
||||
"@kevisual/cnb-ai": "^0.0.2",
|
||||
"@kevisual/context": "^0.0.8",
|
||||
"@kevisual/router": "0.0.80",
|
||||
"@tanstack/react-router": "^1.161.1",
|
||||
"ai": "^6.0.91",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"lucide-react": "^0.575.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"sonner": "^2.0.7",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/gitea": "^0.0.6",
|
||||
"@kevisual/query": "0.0.49",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@tailwindcss/vite": "^4.2.0",
|
||||
"@tanstack/react-router-devtools": "^1.161.1",
|
||||
"@tanstack/router-plugin": "^1.161.1",
|
||||
"@types/node": "^25.3.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"dotenv": "^17.3.1",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.2.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.0-beta.14",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.45", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bpIS3RakSsaUhCRTIvL9bcVNeeUMDXWbndpYdXNeMJIIPcElTcvwktvla+JxIfbeK1AdQjB8ggYVChepeXPGwQ=="],
|
||||
|
||||
"@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.50", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Jdd1a8VgbD7l7r+COj0h5SuaYRfPvOJ/AO6l0OrmTPEcI2MUQPr3C4JttfpNkcheEN+gOdy0CtZWuG17bW2fjw=="],
|
||||
|
||||
"@ai-sdk/openai": ["@ai-sdk/openai@3.0.30", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-YDht3t7TDyWKP+JYZp20VuYqSjyF2brHYh47GGFDUPf2wZiqNQ263ecL+quar2bP3GZ3BeQA8f0m2B7UwLPR+g=="],
|
||||
|
||||
"@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.30", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iTjumHf1/u4NhjXYFn/aONM2GId3/o7J1Lp5ql8FCbgIMyRwrmanR5xy1S3aaVkfTscuDvLTzWiy1mAbGzK3nQ=="],
|
||||
|
||||
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.15", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@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-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
|
||||
|
||||
"@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.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@base-ui/react": ["@base-ui/react@1.2.0", "", { "dependencies": { "@babel/runtime": "^7.28.6", "@base-ui/utils": "0.2.5", "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.4.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-O6aEQHcm+QyGTFY28xuwRD3SEJGZOBDpyjN2WvpfWYFVhg+3zfXPysAILqtM0C1kWC82MccOE/v1j+GHXE4qIw=="],
|
||||
|
||||
"@base-ui/utils": ["@base-ui/utils@0.2.5", "", { "dependencies": { "@babel/runtime": "^7.28.6", "@floating-ui/utils": "^0.2.10", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-oYC7w0gp76RI5MxprlGLV0wze0SErZaRl3AAkeP3OnNB/UBMb6RqNf6ZSIlxOc9Qp68Ab3C2VOcJQyRs7Xc7Vw=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.4", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.5", "", { "dependencies": { "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg=="],
|
||||
|
||||
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.7", "", { "dependencies": { "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg=="],
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@kevisual/cnb": ["@kevisual/cnb@0.0.26", "", { "dependencies": { "@kevisual/query": "^0.0.40", "@kevisual/router": "^0.0.70", "@kevisual/use-config": "^1.0.30", "es-toolkit": "^1.44.0", "nanoid": "^5.1.6", "unstorage": "^1.17.4", "ws": "npm:@kevisual/ws", "zod": "^4.3.6" } }, "sha512-IpyhCkC/Szls1hYfkvvj0kJRY86rdJVPXT95+/QWl7HI9mV6W+kiZE8Q1zJqXjhLQ5d6Szfi1zI+Wh0Re/ao2Q=="],
|
||||
|
||||
"@kevisual/cnb-ai": ["@kevisual/cnb-ai@0.0.2", "", { "dependencies": { "@kevisual/cnb": "^0.0.24", "@kevisual/context": "^0.0.4", "@kevisual/query": "^0.0.40", "@kevisual/router": "^0.0.70", "ws": "npm:@kevisual/ws" } }, "sha512-GlyiYapf/3x4mPglhddJgZsS/LGY6/MOtYdmsPjKZVOdEEiRUrO0HRhIm1Xg0xuUGerj5B+orjIud8SvKbciQg=="],
|
||||
|
||||
"@kevisual/context": ["@kevisual/context@0.0.8", "", {}, "sha512-DTJpyHI34NE76B7g6f+QlIqiCCyqI2qkBMQE736dzeRDGxOjnbe2iQY9W+Rt2PE6kmymM3qyOmSfNovyWyWrkA=="],
|
||||
|
||||
"@kevisual/gitea": ["@kevisual/gitea@0.0.6", "", {}, "sha512-/qJha6IQ5VO+8WOGkLIMROmP0CvAaID0rPPyd5gtzl6yATqQLJS13Fm6Lfp5U5ImjTNmsq08khZqrj93Mz60cw=="],
|
||||
|
||||
"@kevisual/load": ["@kevisual/load@0.0.6", "", { "dependencies": { "eventemitter3": "^5.0.1" } }, "sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA=="],
|
||||
|
||||
"@kevisual/query": ["@kevisual/query@0.0.49", "", {}, "sha512-GrWW+QlBO5lkiqvb7PjOstNtpTQVSR74EHHWjm7YoL9UdT1wuPQXGUApZHmMBSh3NIWCf0AL2G1hPWZMC7YeOQ=="],
|
||||
|
||||
"@kevisual/router": ["@kevisual/router@0.0.80", "", { "dependencies": { "es-toolkit": "^1.44.0" } }, "sha512-rVwi6Yf411bnNm2x94lMm+s4Csw0Yb7u/aj+VJJ59iouAYhjLuL7Rs1EcARhnQf47cegBJi6zozfGHgLsLHN2w=="],
|
||||
|
||||
"@kevisual/types": ["@kevisual/types@0.0.12", "", {}, "sha512-zJXH2dosir3jVrQ6QG4i0+iLQeT9gJ3H+cKXs8ReWboxBSYzUZO78XssVeVrFPsJ33iaAqo4q3DWbSS1dWGn7Q=="],
|
||||
|
||||
"@kevisual/use-config": ["@kevisual/use-config@1.0.30", "", { "dependencies": { "@kevisual/load": "^0.0.6" }, "peerDependencies": { "dotenv": "^17" } }, "sha512-kPdna0FW/X7D600aMdiZ5UTjbCo6d8d4jjauSc8RMmBwUU6WliFDSPUNKVpzm2BsDX5Nth1IXFPYMqH+wxqAmw=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@oxc-project/runtime": ["@oxc-project/runtime@0.114.0", "", {}, "sha512-mVGQvr/uFJGQ3hsvgQ1sJfh79t5owyZZZtw+VaH+WhtvsmtgjT6imznB9sz2Q67Q0/4obM9mOOtQscU4aJteSg=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.114.0", "", {}, "sha512-//nBfbzHQHvJs8oFIjv6coZ6uxQ4alLfiPe6D5vit6c4pmxATHHlVwgB1k+Hv4yoAMyncdxgRBF5K4BYWUCzvA=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.5", "", { "os": "android", "cpu": "arm64" }, "sha512-zCEmUrt1bggwgBgeKLxNj217J1OrChrp3jJt24VK9jAharSTeVaHODNL+LpcQVhRz+FktYWfT9cjo5oZ99ZLpg=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZP9xb9lPAex36pvkNWCjSEJW/Gfdm9I3ssiqOFLmpZ/vosPXgpoGxCmh+dX1Qs+/bWQE6toNFXWWL8vYoKoK9Q=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-7IdrPunf6dp9mywMgTOKMMGDnMHQ6+h5gRl6LW8rhD8WK2kXX0IwzcM5Zc0B5J7xQs8QWOlKjv8BJsU/1CD3pg=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-o/JCk+dL0IN68EBhZ4DqfsfvxPfMeoM6cJtxORC1YYoxGHZyth2Kb2maXDb4oddw2wu8iIbnYXYPEzBtAF5CAg=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.5", "", { "os": "linux", "cpu": "arm" }, "sha512-IIBwTtA6VwxQLcEgq2mfrUgam7VvPZjhd/jxmeS1npM+edWsrrpRLHUdze+sk4rhb8/xpP3flemgcZXXUW6ukw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-KSol1De1spMZL+Xg7K5IBWXIvRWv7+pveaxFWXpezezAG7CS6ojzRjtCGCiLxQricutTAi/LkNWKMsd2wNhMKQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-WFljyDkxtXRlWxMjxeegf7xMYXxUr8u7JdXlOEWKYgDqEgxUnSEsVDxBiNWQ1D5kQKwf8Wo4sVKEYPRhCdsjwA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.5", "", { "os": "linux", "cpu": "x64" }, "sha512-CUlplTujmbDWp2gamvrqVKi2Or8lmngXT1WxsizJfts7JrvfGhZObciaY/+CbdbS9qNnskvwMZNEhTPrn7b+WA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.5", "", { "os": "linux", "cpu": "x64" }, "sha512-wdf7g9NbVZCeAo2iGhsjJb7I8ZFfs6X8bumfrWg82VK+8P6AlLXwk48a1ASiJQDTS7Svq2xVzZg3sGO2aXpHRA=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.5", "", { "os": "none", "cpu": "arm64" }, "sha512-0CWY7ubu12nhzz+tkpHjoG3IRSTlWYe0wrfJRf4qqjqQSGtAYgoL9kwzdvlhaFdZ5ffVeyYw9qLsChcjUMEloQ=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.5", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-LztXnGzv6t2u830mnZrFLRVqT/DPJ9DL4ZTz/y93rqUVkeHjMMYIYaFj+BUthiYxbVH9dH0SZYufETspKY/NhA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-jUct1XVeGtyjqJXEAfvdFa8xoigYZ2rge7nYEm70ppQxpfH9ze2fbIrpHmP2tNM2vL/F6Dd0CpXhpjPbC6bSxQ=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.5", "", { "os": "win32", "cpu": "x64" }, "sha512-VQ8F9ld5gw29epjnVGdrx8ugiLTe8BMqmhDYy7nGbdeDo4HAt4bgdZvLbViEhg7DZyHLpiEUlO5/jPSUrIuxRQ=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.0", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.0" } }, "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.0", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.0", "@tailwindcss/oxide-darwin-arm64": "4.2.0", "@tailwindcss/oxide-darwin-x64": "4.2.0", "@tailwindcss/oxide-freebsd-x64": "4.2.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", "@tailwindcss/oxide-linux-x64-musl": "4.2.0", "@tailwindcss/oxide-wasm32-wasi": "4.2.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" } }, "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.0", "", { "os": "android", "cpu": "arm64" }, "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.0", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ=="],
|
||||
|
||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.0", "", { "dependencies": { "@tailwindcss/node": "4.2.0", "@tailwindcss/oxide": "4.2.0", "tailwindcss": "4.2.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA=="],
|
||||
|
||||
"@tanstack/history": ["@tanstack/history@1.154.14", "", {}, "sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA=="],
|
||||
|
||||
"@tanstack/react-router": ["@tanstack/react-router@1.161.1", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.161.1", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-RQlCaunj+sleC8/JLxd22sWNpwqTHftcRdwGwNF27tjEzTnj06C6azWmA5sGclTdxGVclEOc/eaW7bUv5klsNw=="],
|
||||
|
||||
"@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.161.1", "", { "dependencies": { "@tanstack/router-devtools-core": "1.161.1" }, "peerDependencies": { "@tanstack/react-router": "^1.161.1", "@tanstack/router-core": "^1.161.1", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-fl+o760gCHbd4Nb64SpVJQjpe77xDh2Mx6NqZy0aKACXvWRd8CDcFPzSvDZu4s7tHqFKMfzXqhNzL/jT+A8Prg=="],
|
||||
|
||||
"@tanstack/react-store": ["@tanstack/react-store@0.8.1", "", { "dependencies": { "@tanstack/store": "0.8.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig=="],
|
||||
|
||||
"@tanstack/router-core": ["@tanstack/router-core@1.161.1", "", { "dependencies": { "@tanstack/history": "1.154.14", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.4.2", "seroval-plugins": "^1.4.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-Ika9RBvxB5cE+ziLxq90rqwhl9sb+j6mlGkRDwuDaGSDODenFeCDzjE0YQlgQ/kBUdSK2K1fFBiQPy5cnl54Og=="],
|
||||
|
||||
"@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.161.1", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3" }, "peerDependencies": { "@tanstack/router-core": "^1.161.1", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-I3BcTUD2D8l1sKkab4JJM5LHwwWX5sDCbbhD+MGWplycIujzaW7xADbOnwLpeDjtJarc8kY20cUQ2NJ2eaX9kw=="],
|
||||
|
||||
"@tanstack/router-generator": ["@tanstack/router-generator@1.161.1", "", { "dependencies": { "@tanstack/router-core": "1.161.1", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-IvkjrSaqr3WzYDUjdXOug1x5MhJT5Pw+hKkAi+GDA4isaBjyXS71QmY3jhsZZ2Rz08Xjw2JkAoIJCxfqw6AQKw=="],
|
||||
|
||||
"@tanstack/router-plugin": ["@tanstack/router-plugin@1.161.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "1.161.1", "@tanstack/router-generator": "1.161.1", "@tanstack/router-utils": "1.158.0", "@tanstack/virtual-file-routes": "1.154.7", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.161.1", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-1veqinPZRJMWJSgKljk3XF6l9PaDRRqnc2FMEGBRJ5ycmDqvzCP4RaKbA5pfE/DbXHkKF5Z7BiAeateZHgm4jA=="],
|
||||
|
||||
"@tanstack/router-utils": ["@tanstack/router-utils@1.158.0", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/generator": "^7.28.5", "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "ansis": "^4.1.0", "babel-dead-code-elimination": "^1.0.12", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg=="],
|
||||
|
||||
"@tanstack/store": ["@tanstack/store@0.8.1", "", {}, "sha512-PtOisLjUZPz5VyPRSCGjNOlwTvabdTBQ2K80DpVL1chGVr35WRxfeavAPdNq6pm/t7F8GhoR2qtmkkqtCEtHYw=="],
|
||||
|
||||
"@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.154.7", "", {}, "sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
|
||||
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
|
||||
"@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
"ai": ["ai@6.0.91", "", { "dependencies": { "@ai-sdk/gateway": "3.0.50", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.15", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-k1/8BusZMhYVxxLZt0BUZzm9HVDCCh117nyWfWUx5xjR2+tWisJbXgysL7EBMq2lgyHwgpA1jDR3tVjWSdWZXw=="],
|
||||
|
||||
"ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="],
|
||||
|
||||
"babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"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": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="],
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="],
|
||||
|
||||
"crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
||||
|
||||
"es-toolkit": ["es-toolkit@1.44.0", "", {}, "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"h3": ["h3@1.15.5", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg=="],
|
||||
|
||||
"idb-keyval": ["idb-keyval@6.2.2", "", {}, "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg=="],
|
||||
|
||||
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
|
||||
|
||||
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"isbot": ["isbot@5.1.35", "", {}, "sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
|
||||
|
||||
"lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||
|
||||
"lucide-react": ["lucide-react@0.575.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
|
||||
|
||||
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
||||
|
||||
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
|
||||
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
|
||||
|
||||
"react-hook-form": ["react-hook-form@7.71.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="],
|
||||
|
||||
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.5", "", { "dependencies": { "@oxc-project/types": "=0.114.0", "@rolldown/pluginutils": "1.0.0-rc.5" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.5", "@rolldown/binding-darwin-arm64": "1.0.0-rc.5", "@rolldown/binding-darwin-x64": "1.0.0-rc.5", "@rolldown/binding-freebsd-x64": "1.0.0-rc.5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.5", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.5", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.5", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.5", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.5", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.5", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-0AdalTs6hNTioaCYIkAa7+xsmHBfU5hCNclZnM/lp7lGGDuUOb6N4BVNtwiomybbencDjq/waKjTImqiGCs5sw=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"seroval": ["seroval@1.5.0", "", {}, "sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw=="],
|
||||
|
||||
"seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="],
|
||||
|
||||
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
|
||||
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.2.0", "", {}, "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
|
||||
|
||||
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
|
||||
|
||||
"unstorage": ["unstorage@1.17.4", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"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.0-beta.15", "", { "dependencies": { "@oxc-project/runtime": "0.114.0", "lightningcss": "^1.31.1", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-rc.5", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.0.0-alpha.31", "esbuild": "^0.27.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", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-RHX7IvsJlEfjyA1rS7MY0UsmF91etdLAamslHR5lfuO3W/BXRdXm2tRE64ztpSPZbKqB4wAAZ0AwtF6QzfKZLA=="],
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
"ws": ["@kevisual/ws@8.19.0", "", {}, "sha512-jLsL80wBBKkrJZrfk3SQpJ9JA/zREdlUROj7eCkmzqduAWKSI0wVcXuCKf+mLFCHB0Q0Tkh2rgzjSlurt3JQgw=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"zustand": ["zustand@5.0.11", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"@kevisual/cnb/@kevisual/query": ["@kevisual/query@0.0.40", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-7m5BgDzd01m51hCHUId6ugQHdwgrLTb6fI7DSuMY17VjWb0+zGnkYmvRBqkTXzoIjjYbP5iwtRnrooEoToQfhg=="],
|
||||
|
||||
"@kevisual/cnb/@kevisual/router": ["@kevisual/router@0.0.70", "", { "dependencies": { "es-toolkit": "^1.44.0" } }, "sha512-vXlIj9jRufhcIfeuPWemjSI+dxdzSmIBq5eRxQzqEfAJ7k+mBPhoI4KxH8vHnwyL30bqm8EdODL/p6Wg8uBw3g=="],
|
||||
|
||||
"@kevisual/cnb-ai/@kevisual/cnb": ["@kevisual/cnb@0.0.24", "", { "dependencies": { "@kevisual/query": "^0.0.40", "@kevisual/router": "^0.0.70", "@kevisual/use-config": "^1.0.30", "es-toolkit": "^1.44.0", "nanoid": "^5.1.6", "unstorage": "^1.17.4", "ws": "npm:@kevisual/ws", "zod": "^4.3.6" } }, "sha512-LxFhnf7hAyXlLn+CJihKeNud9wwo2QBj5QQY1eQCeDFlujnSGyc9WkqKG4dHTe9wdTDLbxanlb5/BXzFHudTbw=="],
|
||||
|
||||
"@kevisual/cnb-ai/@kevisual/context": ["@kevisual/context@0.0.4", "", {}, "sha512-HJeLeZQLU+7tCluSfOyvkgKLs0HjCZrdJlZgEgKRSa8XTwZfMAUt6J7qZTbrZAHBlPtX68EPu/PI8JMCeu3WAQ=="],
|
||||
|
||||
"@kevisual/cnb-ai/@kevisual/query": ["@kevisual/query@0.0.40", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-7m5BgDzd01m51hCHUId6ugQHdwgrLTb6fI7DSuMY17VjWb0+zGnkYmvRBqkTXzoIjjYbP5iwtRnrooEoToQfhg=="],
|
||||
|
||||
"@kevisual/cnb-ai/@kevisual/router": ["@kevisual/router@0.0.70", "", { "dependencies": { "es-toolkit": "^1.44.0" } }, "sha512-vXlIj9jRufhcIfeuPWemjSI+dxdzSmIBq5eRxQzqEfAJ7k+mBPhoI4KxH8vHnwyL30bqm8EdODL/p6Wg8uBw3g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"h3/cookie-es": ["cookie-es@1.2.2", "", {}, "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg=="],
|
||||
|
||||
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.5", "", {}, "sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw=="],
|
||||
|
||||
"unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/jpg" href="https://kevisual.xiongxiao.me/root/center/panda.jpg" />
|
||||
<link rel="icon" type="image/jpg" href="https://kevisual.cn/root/logo/assets/logos/cnb-gateway-logo-980x980.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CNB Center</title>
|
||||
<style>
|
||||
|
||||
42
package.json
42
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@kevisual/cnb-center",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.6",
|
||||
"type": "module",
|
||||
"basename": "/root/cnb-center",
|
||||
"scripts": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"ui": "pnpm dlx shadcn@latest add ",
|
||||
"pub": "envision deploy ./dist -k cnb-center -v 0.0.2 -y y -u"
|
||||
"pub": "envision deploy ./dist -k cnb-center -v 0.0.6 -y y -u"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
@@ -17,23 +17,26 @@
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.44",
|
||||
"@ai-sdk/openai": "^3.0.29",
|
||||
"@ai-sdk/anthropic": "^3.0.45",
|
||||
"@ai-sdk/openai": "^3.0.30",
|
||||
"@ai-sdk/openai-compatible": "^2.0.30",
|
||||
"@base-ui/react": "^1.2.0",
|
||||
"@kevisual/api": "^0.0.60",
|
||||
"@kevisual/cnb": "^0.0.26",
|
||||
"@kevisual/cnb-ai": "^0.0.2",
|
||||
"@kevisual/context": "^0.0.6",
|
||||
"@kevisual/router": "0.0.70",
|
||||
"@tanstack/react-router": "^1.160.0",
|
||||
"ai": "^6.0.86",
|
||||
"@kevisual/context": "^0.0.8",
|
||||
"@kevisual/kv-login": "^0.1.15",
|
||||
"@kevisual/router": "0.0.80",
|
||||
"@tanstack/react-router": "^1.161.1",
|
||||
"@uiw/react-codemirror": "^4.25.5",
|
||||
"ai": "^6.0.91",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"lucide-react": "^0.564.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
@@ -47,21 +50,22 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/query": "0.0.40",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@kevisual/gitea": "^0.0.6",
|
||||
"@kevisual/query": "0.0.49",
|
||||
"@kevisual/types": "^0.0.12",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-router-devtools": "^1.160.0",
|
||||
"@tanstack/router-plugin": "^1.160.0",
|
||||
"@types/node": "^25.2.3",
|
||||
"@tailwindcss/vite": "^4.2.0",
|
||||
"@tanstack/react-router-devtools": "^1.161.1",
|
||||
"@tanstack/router-plugin": "^1.161.1",
|
||||
"@types/node": "^25.3.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"dotenv": "^17.3.1",
|
||||
"tailwind-merge": "^3.4.1",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.2.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.0-beta.13"
|
||||
},
|
||||
"packageManager": "pnpm@10.29.3"
|
||||
"vite": "^8.0.0-beta.14"
|
||||
}
|
||||
}
|
||||
2598
pnpm-lock.yaml
generated
2598
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
25
public/comments.html
Normal file
25
public/comments.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" type="image/png" href="https://kevisual.xiongxiao.me/root/center/panda.jpg" />
|
||||
<title>Comments</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script
|
||||
src="https://commit.cool/client.js"
|
||||
repo="kevisual/kevisual"
|
||||
issue-term="custom"
|
||||
issue-id="comments"
|
||||
theme="light"
|
||||
layout="classic"
|
||||
crossorigin="anonymous"
|
||||
async
|
||||
></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
BIN
public/images/config-info.png
Normal file
BIN
public/images/config-info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
BIN
public/images/repo-info.png
Normal file
BIN
public/images/repo-info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -1,8 +1,10 @@
|
||||
import { QueryRouterServer } from '@kevisual/router/browser'
|
||||
|
||||
import { useContextKey } from '@kevisual/context'
|
||||
import { useConfigStore } from '@/app/config/store'
|
||||
import { useConfigStore } from '@/pages/config/store'
|
||||
import { useGiteaConfigStore } from '@/pages/config/gitea/store'
|
||||
import { CNB } from '@kevisual/cnb'
|
||||
import { Gitea } from '@kevisual/gitea';
|
||||
export const app = useContextKey('app', new QueryRouterServer())
|
||||
|
||||
export const cnb: CNB = useContextKey('cnb', () => {
|
||||
@@ -20,7 +22,6 @@ export const cnb: CNB = useContextKey('cnb', () => {
|
||||
cors
|
||||
})
|
||||
})
|
||||
//
|
||||
|
||||
// import '@kevisual/cnb-ai'
|
||||
|
||||
@@ -28,3 +29,15 @@ const url = 'https://kevisual.cn/root/cnb-ai/dist/app.js'
|
||||
setTimeout(() => {
|
||||
import(/* @vite-ignore */url)
|
||||
}, 2000)
|
||||
|
||||
export const gitea = useContextKey('gitea', () => {
|
||||
const state = useGiteaConfigStore.getState()
|
||||
const config = state.config || {}
|
||||
return new Gitea({
|
||||
token: config.GITEA_TOKEN,
|
||||
baseURL: config.GITEA_URL,
|
||||
cors: {
|
||||
baseUrl: 'https://cors.kevisual.cn'
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,131 +0,0 @@
|
||||
import { useConfigStore } from './store';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { configSchema } from './store/schema';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export const ConfigPage = () => {
|
||||
const { config, setConfig, resetConfig } = useConfigStore();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const result = configSchema.safeParse(config);
|
||||
if (result.success) {
|
||||
toast.success('配置已保存')
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 400)
|
||||
} else {
|
||||
console.error('验证错误:', result.error.format());
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field: keyof typeof config, value: string | boolean) => {
|
||||
setConfig({ [field]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-2xl py-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>CNB 配置</CardTitle>
|
||||
<CardDescription>
|
||||
配置您的 CNB API 设置。这些设置会保存在浏览器的本地存储中。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="api-key">API 密钥</Label>
|
||||
<Input
|
||||
id="api-key"
|
||||
type="text"
|
||||
value={config.CNB_API_KEY}
|
||||
onChange={(e) => handleChange('CNB_API_KEY', e.target.value)}
|
||||
placeholder="请输入您的 CNB API 密钥"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cookie">Cookie</Label>
|
||||
<Input
|
||||
id="cookie"
|
||||
type="text"
|
||||
value={config.CNB_COOKIE}
|
||||
onChange={(e) => handleChange('CNB_COOKIE', e.target.value)}
|
||||
placeholder="请输入您的 CNB Cookie"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cors-url">跨域地址</Label>
|
||||
<Input
|
||||
id="cors-url"
|
||||
type="url"
|
||||
value={config.CNB_CORS_URL}
|
||||
onChange={(e) => handleChange('CNB_CORS_URL', e.target.value)}
|
||||
placeholder="https://cors.example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="enable-cors"
|
||||
checked={config.ENABLE_CORS}
|
||||
onCheckedChange={(checked) => handleChange('ENABLE_CORS', checked === true)}
|
||||
/>
|
||||
<Label htmlFor="enable-cors" className="cursor-pointer">
|
||||
启用跨域
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-base-url">AI 基础地址</Label>
|
||||
<Input
|
||||
id="ai-base-url"
|
||||
type="url"
|
||||
value={config.AI_BASE_URL}
|
||||
onChange={(e) => handleChange('AI_BASE_URL', e.target.value)}
|
||||
placeholder="请输入 AI 基础地址"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-model">AI 模型</Label>
|
||||
<Input
|
||||
id="ai-model"
|
||||
type="text"
|
||||
value={config.AI_MODEL}
|
||||
onChange={(e) => handleChange('AI_MODEL', e.target.value)}
|
||||
placeholder="请输入 AI 模型名称"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-api-key">AI 密钥</Label>
|
||||
<Input
|
||||
id="ai-api-key"
|
||||
type="password"
|
||||
value={config.AI_API_KEY}
|
||||
onChange={(e) => handleChange('AI_API_KEY', e.target.value)}
|
||||
placeholder="请输入您的 AI API 密钥"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button type="submit">保存配置</Button>
|
||||
<Button type="button" variant="outline" onClick={resetConfig}>
|
||||
重置为默认值
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigPage;
|
||||
@@ -1,51 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { Config, defaultConfig } from './schema';
|
||||
|
||||
type ConfigState = {
|
||||
config: Config;
|
||||
setConfig: (config: Partial<Config>) => void;
|
||||
resetConfig: () => void;
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'cnb-config';
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
CNB_API_KEY: '',
|
||||
CNB_COOKIE: '',
|
||||
CNB_CORS_URL: 'https://cors.kevisual.cn',
|
||||
ENABLE_CORS: true,
|
||||
AI_BASE_URL: 'https://api.cnb.cool/kevisual/cnb-ai/-/ai/',
|
||||
AI_MODEL: 'CNB-Models',
|
||||
AI_API_KEY: ''
|
||||
}
|
||||
const loadInitialConfig = (): Config => {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
return DEFAULT_CONFIG;
|
||||
};
|
||||
|
||||
export const useConfigStore = create<ConfigState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
config: loadInitialConfig(),
|
||||
setConfig: (newConfig) =>
|
||||
set((state) => ({
|
||||
config: { ...state.config, ...newConfig },
|
||||
})),
|
||||
resetConfig: () =>
|
||||
set({
|
||||
config: DEFAULT_CONFIG,
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -1,3 +0,0 @@
|
||||
import App from './repo/page'
|
||||
|
||||
export default App;
|
||||
@@ -1,109 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRepoStore } from './store/index'
|
||||
import { RepoCard } from './components/RepoCard'
|
||||
import { EditRepoDialog } from './modules/EditRepoDialog'
|
||||
import { CreateRepoDialog } from './modules/CreateRepoDialog'
|
||||
import { WorkspaceDetailDialog } from './modules/WorkspaceDetailDialog'
|
||||
import { SyncRepoDialog } from './modules/SyncRepoDialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Plus } from 'lucide-react'
|
||||
|
||||
export const App = () => {
|
||||
const { list, getList, loading, editRepo, setEditRepo, showEditDialog, setShowEditDialog, showCreateDialog, setShowCreateDialog, startWorkspace, getWorkspaceList, deleteItem, setSelectedSyncRepo, setSyncDialogOpen } = useRepoStore()
|
||||
|
||||
useEffect(() => {
|
||||
getList()
|
||||
getWorkspaceList()
|
||||
}, [])
|
||||
|
||||
const handleEdit = (repo: any) => {
|
||||
setEditRepo(repo)
|
||||
setShowEditDialog(true)
|
||||
}
|
||||
|
||||
const handleIssue = (repo: any) => {
|
||||
window.open(`https://cnb.cool/${repo.path}/-/issues`)
|
||||
}
|
||||
|
||||
const handleSettings = (repo: any) => {
|
||||
window.open(`https://cnb.cool/${repo.path}/-/settings`)
|
||||
}
|
||||
|
||||
const handleDelete = (repo: any) => {
|
||||
if (repo.path)
|
||||
deleteItem(repo.path)
|
||||
}
|
||||
|
||||
const handleSync = (repo: any) => {
|
||||
setSelectedSyncRepo(repo)
|
||||
setSyncDialogOpen(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-neutral-50 flex flex-col">
|
||||
<div className="container mx-auto p-6 max-w-7xl flex-1">
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-neutral-900 mb-2">仓库列表</h1>
|
||||
<p className="text-neutral-600">共 {list.length} 个仓库</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => setShowCreateDialog(true)}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建仓库
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{list.map((repo) => (
|
||||
<RepoCard
|
||||
key={repo.id}
|
||||
repo={repo}
|
||||
onStartWorkspace={startWorkspace}
|
||||
onEdit={handleEdit}
|
||||
onIssue={handleIssue}
|
||||
onSettings={handleSettings}
|
||||
onDelete={handleDelete}
|
||||
onSync={handleSync}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{list.length === 0 && !loading && (
|
||||
<div className="text-center py-20">
|
||||
<div className="text-neutral-400 text-lg">暂无仓库数据</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer className="border-t border-neutral-200 bg-white py-6 mt-auto">
|
||||
<div className="container mx-auto px-6 max-w-7xl">
|
||||
<div className="flex items-center justify-between text-sm text-neutral-500">
|
||||
<div>© 2026 仓库管理系统</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<a href="#" className="hover:text-neutral-900 transition-colors">关于</a>
|
||||
<a href="#" className="hover:text-neutral-900 transition-colors">帮助</a>
|
||||
<a href="#" className="hover:text-neutral-900 transition-colors">联系我们</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<EditRepoDialog
|
||||
open={showEditDialog}
|
||||
onOpenChange={setShowEditDialog}
|
||||
repo={editRepo}
|
||||
/>
|
||||
<CreateRepoDialog
|
||||
open={showCreateDialog}
|
||||
onOpenChange={setShowCreateDialog}
|
||||
/>
|
||||
<WorkspaceDetailDialog />
|
||||
<SyncRepoDialog />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,33 +0,0 @@
|
||||
export const myOrgs = ['kevisual', 'kevision', 'skillpod', 'zxj.im', 'abearxiong']
|
||||
|
||||
export const createBuildConfig = (params: { repo: string }) => {
|
||||
const toRepo = params.repo!;
|
||||
return `
|
||||
# .cnb.yml
|
||||
include:
|
||||
- https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml
|
||||
|
||||
.common_env: &common_env
|
||||
env:
|
||||
TO_REPO: ${toRepo}
|
||||
TO_URL: git.xiongxiao.me
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||
|
||||
.common_sync_to_gitea: &common_sync_to_gitea
|
||||
- <<: *common_env
|
||||
services: !reference [.common_sync_to_gitea_template, services]
|
||||
stages: !reference [.common_sync_to_gitea_template, stages]
|
||||
|
||||
.common_sync_from_gitea: &common_sync_from_gitea
|
||||
- <<: *common_env
|
||||
services: !reference [.common_sync_from_gitea_template, services]
|
||||
stages: !reference [.common_sync_from_gitea_template, stages]
|
||||
|
||||
main:
|
||||
api_trigger_sync_to_gitea:
|
||||
- <<: *common_sync_to_gitea
|
||||
api_trigger_sync_from_gitea:
|
||||
- <<: *common_sync_from_gitea
|
||||
`
|
||||
};
|
||||
@@ -1,3 +1,14 @@
|
||||
import { QueryClient } from '@kevisual/query';
|
||||
import { Query, DataOpts } from '@kevisual/query';
|
||||
import { QueryLoginBrowser } from '@kevisual/api/query-login'
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
export const query = useContextKey('query', new Query({
|
||||
url: '/api/router',
|
||||
}));
|
||||
|
||||
export const query = new QueryClient();
|
||||
export const queryClient = useContextKey('queryClient', new Query({
|
||||
url: '/client/router',
|
||||
}));
|
||||
|
||||
export const queryLogin = useContextKey('queryLogin', new QueryLoginBrowser({
|
||||
query: query
|
||||
}));
|
||||
58
src/pages/auth/index.tsx
Normal file
58
src/pages/auth/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useEffect } from "react"
|
||||
import { useLayoutStore } from "./store"
|
||||
import { useShallow } from "zustand/shallow"
|
||||
import { LogIn, LockKeyhole } from "lucide-react"
|
||||
export { BaseHeader } from './modules/BaseHeader'
|
||||
import { useMemo } from 'react';
|
||||
import { useLocation, useNavigate } from '@tanstack/react-router';
|
||||
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode,
|
||||
mustLogin?: boolean,
|
||||
}
|
||||
export const AuthProvider = ({ children, mustLogin }: Props) => {
|
||||
const store = useLayoutStore(useShallow(state => ({
|
||||
init: state.init,
|
||||
me: state.me,
|
||||
openLinkList: state.openLinkList,
|
||||
})));
|
||||
useEffect(() => {
|
||||
store.init()
|
||||
}, [])
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate();
|
||||
const isOpen = useMemo(() => {
|
||||
return store.openLinkList.some(item => location.pathname.startsWith(item))
|
||||
}, [location.pathname])
|
||||
const loginUrl = '/root/login/?redirect=' + encodeURIComponent(window.location.href);
|
||||
if (mustLogin && !store.me && !isOpen) {
|
||||
return (
|
||||
<div className="w-full h-full min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="flex flex-col items-center gap-6 p-10 rounded-2xl border border-border bg-card shadow-lg max-w-sm w-full mx-4">
|
||||
<div className="flex items-center justify-center w-16 h-16 rounded-full bg-muted">
|
||||
<LockKeyhole className="w-8 h-8 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2 text-center">
|
||||
<h2 className="text-xl font-semibold text-foreground">需要登录</h2>
|
||||
<p className="text-sm text-muted-foreground">请先登录以继续访问此页面</p>
|
||||
</div>
|
||||
<div
|
||||
className="inline-flex items-center justify-center gap-2 w-full px-6 py-2.5 rounded-lg bg-foreground text-background text-sm font-medium transition-opacity hover:opacity-80 active:opacity-70"
|
||||
onClick={() => {
|
||||
// window.open(loginUrl, '_blank');
|
||||
navigate({ to: '/login' });
|
||||
}}
|
||||
>
|
||||
<LogIn className="w-4 h-4" />
|
||||
立即登录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <>
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
89
src/pages/auth/modules/BaseHeader.tsx
Normal file
89
src/pages/auth/modules/BaseHeader.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Home, User, LogIn, LogOut } from 'lucide-react';
|
||||
import { Link, useNavigate } from '@tanstack/react-router'
|
||||
import { useLayoutStore } from '../store';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const BaseHeader = (props: { main?: React.ComponentType | null }) => {
|
||||
const store = useLayoutStore(useShallow(state => ({
|
||||
me: state.me,
|
||||
clearMe: state.clearMe,
|
||||
links: state.links,
|
||||
})));
|
||||
const navigate = useNavigate();
|
||||
const meInfo = useMemo(() => {
|
||||
if (!store.me) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => navigate({ to: '/login' })}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
|
||||
>
|
||||
<LogIn className="w-4 h-4" />
|
||||
<span>去登录</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{store.me.avatar && (
|
||||
<img
|
||||
src={store.me.avatar}
|
||||
alt="Avatar"
|
||||
className="w-8 h-8 rounded-full object-cover"
|
||||
/>
|
||||
)}
|
||||
{!store.me.avatar && (
|
||||
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
|
||||
<User className="w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
)}
|
||||
<span className="font-medium text-gray-700">{store.me?.username}</span>
|
||||
<button
|
||||
onClick={() => store.clearMe?.()}
|
||||
className="flex items-center gap-1 px-2 py-1 text-sm text-gray-500 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors cursor-pointer"
|
||||
title="退出登录"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}, [store.me, store.clearMe])
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-2 text-lg w-full h-12 items-center justify-between bg-gray-200">
|
||||
<div className='px-2 flex items-center gap-1'>
|
||||
{
|
||||
store.links.map(link => (
|
||||
<div key={link.key || link.title}
|
||||
className="cursor-pointer flex items-center justify-center gap-1 p-2 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
onClick={() => {
|
||||
if (!link.href) return;
|
||||
if (link.href.startsWith('http') || link.isRoot) {
|
||||
window.open(link.href, '_blank');
|
||||
return;
|
||||
}
|
||||
navigate({
|
||||
to: link.href
|
||||
})
|
||||
}}
|
||||
>
|
||||
{link.key === 'home' && <Home className="w-4 h-4" />}
|
||||
{link.icon && <>{link.icon}</>}
|
||||
{!link.icon && link.title}
|
||||
</div>
|
||||
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className='mr-4'>
|
||||
{meInfo}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const LayoutMain = () => {
|
||||
return <BaseHeader />
|
||||
}
|
||||
81
src/pages/auth/page.tsx
Normal file
81
src/pages/auth/page.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import '@kevisual/kv-login';
|
||||
import { checkPluginLogin } from '@kevisual/kv-login'
|
||||
import { useEffect } from 'react';
|
||||
import { useLayoutStore } from './store';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
|
||||
export const LoginComponent = ({ onLoginSuccess }: { onLoginSuccess: () => void }) => {
|
||||
useEffect(() => {
|
||||
// 监听登录成功事件
|
||||
const handleLoginSuccess = () => {
|
||||
console.log('监听到登录成功事件,关闭弹窗');
|
||||
onLoginSuccess();
|
||||
};
|
||||
const loginEmitter = useContextKey('login-emitter')
|
||||
console.log('KvLogin Types:', loginEmitter);
|
||||
|
||||
loginEmitter.on('login-success', handleLoginSuccess);
|
||||
|
||||
// 清理监听器
|
||||
return () => {
|
||||
loginEmitter.off('login-success', handleLoginSuccess);
|
||||
};
|
||||
}, [onLoginSuccess]);
|
||||
|
||||
// @ts-ignore
|
||||
return (<kv-login></kv-login>)
|
||||
}
|
||||
export const App = () => {
|
||||
const store = useLayoutStore(useShallow((state) => ({
|
||||
init: state.init,
|
||||
loginPageConfig: state.loginPageConfig,
|
||||
})));
|
||||
useEffect(() => {
|
||||
checkPluginLogin();
|
||||
}, []);
|
||||
const navigate = useNavigate();
|
||||
const handleLoginSuccess = async () => {
|
||||
await store.init()
|
||||
navigate({ to: '/' })
|
||||
};
|
||||
const { title, subtitle, footer } = store.loginPageConfig;
|
||||
return (
|
||||
<div className='w-full h-full relative overflow-hidden bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900'>
|
||||
{/* 背景装饰 - 圆形光晕 */}
|
||||
<div className='absolute top-1/4 -left-32 w-96 h-96 bg-purple-500/30 rounded-full blur-3xl'></div>
|
||||
<div className='absolute bottom-1/4 -right-32 w-96 h-96 bg-blue-500/30 rounded-full blur-3xl'></div>
|
||||
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-indigo-500/20 rounded-full blur-3xl'></div>
|
||||
|
||||
{/* 背景装饰 - 网格图案 */}
|
||||
<div className='absolute inset-0 opacity-[0.03] bg-[linear-gradient(rgba(255,255,255,0.1)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.1)_1px,transparent_1px)] bg-[size:50px_50px]'></div>
|
||||
|
||||
{/* 顶部装饰文字 */}
|
||||
<div className='absolute top-10 left-0 right-0 text-center'>
|
||||
<h1 className='text-4xl font-bold text-white/90 tracking-wider'>{title}</h1>
|
||||
<p className='mt-2 text-white/50 text-sm tracking-widest'>{subtitle}</p>
|
||||
</div>
|
||||
|
||||
{/* 登录卡片容器 */}
|
||||
<div className='w-full h-full flex items-center justify-center p-8'>
|
||||
<div className='relative'>
|
||||
{/* 卡片外圈光效 */}
|
||||
<div className='absolute -inset-1 bg-gradient-to-r from-purple-500 via-blue-500 to-indigo-500 rounded-2xl blur opacity-30'></div>
|
||||
|
||||
{/* 登录组件容器 */}
|
||||
<div className='relative bg-slate-900/80 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl overflow-hidden'>
|
||||
<LoginComponent onLoginSuccess={handleLoginSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部装饰 */}
|
||||
<div className='absolute bottom-6 left-0 right-0 text-center'>
|
||||
<p className='text-white/30 text-xs'>{footer}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
106
src/pages/auth/store.ts
Normal file
106
src/pages/auth/store.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
import { queryLogin } from '@/modules/query';
|
||||
import { create } from 'zustand';
|
||||
import { toast } from 'sonner';
|
||||
type UserInfo = {
|
||||
id?: string;
|
||||
username?: string;
|
||||
nickname?: string | null;
|
||||
needChangePassword?: boolean;
|
||||
description?: string | null;
|
||||
type?: 'user' | 'org';
|
||||
orgs?: string[];
|
||||
avatar?: string;
|
||||
};
|
||||
export type LayoutStore = {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
openUser: boolean;
|
||||
setOpenUser: (openUser: boolean) => void;
|
||||
me?: UserInfo;
|
||||
setMe: (me: UserInfo) => void;
|
||||
clearMe: () => void;
|
||||
getMe: () => Promise<void>;
|
||||
switchOrg: (username?: string) => Promise<void>;
|
||||
isAdmin: boolean;
|
||||
setIsAdmin: (isAdmin: boolean) => void
|
||||
init: () => Promise<void>;
|
||||
openLinkList: string[];
|
||||
setOpenLinkList: (openLinkList: string[]) => void;
|
||||
loginPageConfig: {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
footer: string;
|
||||
};
|
||||
setLoginPageConfig: (config: Partial<LayoutStore['loginPageConfig']>) => void;
|
||||
links: HeaderLink[];
|
||||
setLinks: (links: HeaderLink[]) => void;
|
||||
};
|
||||
type HeaderLink = {
|
||||
title?: string;
|
||||
href: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
key?: string;
|
||||
isRoot?: boolean;
|
||||
};
|
||||
|
||||
export const useLayoutStore = create<LayoutStore>((set, get) => ({
|
||||
open: false,
|
||||
setOpen: (open) => set({ open }),
|
||||
openUser: false,
|
||||
setOpenUser: (openUser) => set({ openUser }),
|
||||
me: undefined,
|
||||
setMe: (me) => set({ me }),
|
||||
clearMe: () => {
|
||||
set({ me: undefined, isAdmin: false });
|
||||
window.location.href = '/root/login/?redirect=' + encodeURIComponent(window.location.href);
|
||||
},
|
||||
getMe: async () => {
|
||||
const res = await queryLogin.getMe();
|
||||
if (res.code === 200) {
|
||||
set({ me: res.data });
|
||||
set({ isAdmin: res.data.orgs?.includes?.('admin') || false });
|
||||
}
|
||||
},
|
||||
switchOrg: async (username?: string) => {
|
||||
const res = await queryLogin.switchUser(username || '');
|
||||
if (res.code === 200) {
|
||||
toast.success('切换成功');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
toast.error(res.message || '请求失败');
|
||||
}
|
||||
},
|
||||
isAdmin: false,
|
||||
setIsAdmin: (isAdmin) => set({ isAdmin }),
|
||||
init: async () => {
|
||||
const token = await queryLogin.getToken();
|
||||
if (token) {
|
||||
set({ me: {} })
|
||||
const me = await queryLogin.getMe();
|
||||
// const user = await queryLogin.checkLocalUser() as UserInfo;
|
||||
const user = me.code === 200 ? me.data : undefined;
|
||||
if (user) {
|
||||
set({ me: user });
|
||||
set({ isAdmin: user.orgs?.includes?.('admin') || false });
|
||||
} else {
|
||||
set({ me: undefined, isAdmin: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
openLinkList: ['/login'],
|
||||
setOpenLinkList: (openLinkList) => set({ openLinkList }),
|
||||
loginPageConfig: {
|
||||
title: '可视化管理平台',
|
||||
subtitle: '让工具和智能化触手可及',
|
||||
footer: '欢迎使用可视化管理平台 · 连接您的工具',
|
||||
},
|
||||
setLoginPageConfig: (config) => set((state) => ({
|
||||
loginPageConfig: { ...state.loginPageConfig, ...config },
|
||||
})),
|
||||
links: [{ title: '', href: '/', key: 'home' }],
|
||||
setLinks: (links) => set({ links }),
|
||||
}));
|
||||
77
src/pages/config/gitea/page.tsx
Normal file
77
src/pages/config/gitea/page.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useGiteaConfigStore } from './store';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { giteaConfigSchema } from './store/schema';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
|
||||
export const GiteaConfigPage = () => {
|
||||
const { config, setConfig, resetConfig } = useGiteaConfigStore();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const result = giteaConfigSchema.safeParse(config);
|
||||
if (result.success) {
|
||||
toast.success('Gitea 配置已保存');
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 400);
|
||||
} else {
|
||||
console.error('验证错误:', result.error.format());
|
||||
toast.error('配置验证失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field: keyof typeof config, value: string) => {
|
||||
setConfig({ [field]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-2xl py-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Gitea 配置</CardTitle>
|
||||
<CardDescription>
|
||||
配置您的 Gitea API 设置。这些设置会保存在浏览器的本地存储中。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gitea-url">Gitea 地址</Label>
|
||||
<Input
|
||||
id="gitea-url"
|
||||
type="url"
|
||||
value={config.GITEA_URL}
|
||||
onChange={(e) => handleChange('GITEA_URL', e.target.value)}
|
||||
placeholder="https://git.xiongxiao.me"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gitea-token">Gitea Token</Label>
|
||||
<Input
|
||||
id="gitea-token"
|
||||
type="password"
|
||||
value={config.GITEA_TOKEN}
|
||||
onChange={(e) => handleChange('GITEA_TOKEN', e.target.value)}
|
||||
placeholder="请输入您的 Gitea Access Token"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button type="submit">保存配置</Button>
|
||||
<Button type="button" variant="outline" onClick={resetConfig}>
|
||||
重置为默认值
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GiteaConfigPage;
|
||||
50
src/pages/config/gitea/store/index.ts
Normal file
50
src/pages/config/gitea/store/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { create } from 'zustand';
|
||||
import type { GiteaConfig } from './schema';
|
||||
|
||||
type GiteaConfigState = {
|
||||
config: GiteaConfig;
|
||||
setConfig: (config: Partial<GiteaConfig>) => void;
|
||||
resetConfig: () => void;
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: GiteaConfig = {
|
||||
GITEA_TOKEN: '',
|
||||
GITEA_URL: 'https://git.xiongxiao.me',
|
||||
};
|
||||
|
||||
const loadInitialConfig = (): GiteaConfig => {
|
||||
try {
|
||||
const token = localStorage.getItem('GITEA_TOKEN') || '';
|
||||
const url = localStorage.getItem('GITEA_URL') || DEFAULT_CONFIG.GITEA_URL;
|
||||
return {
|
||||
GITEA_TOKEN: token,
|
||||
GITEA_URL: url,
|
||||
};
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
return DEFAULT_CONFIG;
|
||||
};
|
||||
|
||||
const saveConfig = (config: GiteaConfig) => {
|
||||
try {
|
||||
localStorage.setItem('GITEA_TOKEN', config.GITEA_TOKEN);
|
||||
localStorage.setItem('GITEA_URL', config.GITEA_URL);
|
||||
} catch (error) {
|
||||
console.error('Failed to save config:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const useGiteaConfigStore = create<GiteaConfigState>()((set) => ({
|
||||
config: loadInitialConfig(),
|
||||
setConfig: (newConfig) =>
|
||||
set((state) => {
|
||||
const updatedConfig = { ...state.config, ...newConfig };
|
||||
saveConfig(updatedConfig);
|
||||
return { config: updatedConfig };
|
||||
}),
|
||||
resetConfig: () => {
|
||||
saveConfig(DEFAULT_CONFIG);
|
||||
return set({ config: DEFAULT_CONFIG });
|
||||
},
|
||||
}));
|
||||
13
src/pages/config/gitea/store/schema.ts
Normal file
13
src/pages/config/gitea/store/schema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const giteaConfigSchema = z.object({
|
||||
GITEA_TOKEN: z.string().min(1, 'Gitea Token is required'),
|
||||
GITEA_URL: z.url('Must be a valid URL'),
|
||||
});
|
||||
|
||||
export type GiteaConfig = z.infer<typeof giteaConfigSchema>;
|
||||
|
||||
export const defaultGiteaConfig: GiteaConfig = {
|
||||
GITEA_TOKEN: '',
|
||||
GITEA_URL: 'https://git.xiongxiao.me',
|
||||
};
|
||||
16
src/pages/config/hooks/check.ts
Normal file
16
src/pages/config/hooks/check.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useEffect } from "react";
|
||||
import { useConfigStore } from "../store";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const useCheckConfig = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const config = useConfigStore.getState().config;
|
||||
if (!config.CNB_API_KEY) {
|
||||
navigate({
|
||||
to: '/config'
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
198
src/pages/config/page.tsx
Normal file
198
src/pages/config/page.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import { useConfigStore } from './store';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Info } from 'lucide-react';
|
||||
import { configSchema } from './store/schema';
|
||||
import { toast } from 'sonner';
|
||||
import { useLayoutStore } from '../auth/store';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
|
||||
export const ConfigPage = () => {
|
||||
const { config, setConfig, resetConfig, saveToRemote, loadFromRemote } = useConfigStore();
|
||||
const layoutStore = useLayoutStore(useShallow(state => ({ me: state.me })))
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const result = configSchema.safeParse(config);
|
||||
if (result.success) {
|
||||
toast.success('配置已保存')
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 400)
|
||||
} else {
|
||||
console.error('验证错误:', result.error.format());
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field: keyof typeof config, value: string | boolean) => {
|
||||
setConfig({ [field]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="container mx-auto max-w-2xl py-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>CNB 配置</CardTitle>
|
||||
<CardDescription>
|
||||
配置您的 CNB API 设置。这些设置会保存在浏览器的本地存储中。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="api-key">API 密钥</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
用于访问 CNB API 的密钥。
|
||||
<a
|
||||
href="https://cnb.cool/profile/token"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline ml-1 hover:text-blue-400"
|
||||
>
|
||||
点击获取
|
||||
</a>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input
|
||||
id="api-key"
|
||||
type="text"
|
||||
value={config.CNB_API_KEY}
|
||||
onChange={(e) => handleChange('CNB_API_KEY', e.target.value)}
|
||||
placeholder="请输入您的 CNB API 密钥"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="cookie">Cookie</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
用于身份验证的 Cookie 信息。
|
||||
<a
|
||||
href="https://cnb.cool/kevisual/cnb-live-extension"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline ml-1 hover:text-blue-400"
|
||||
>
|
||||
前往获取
|
||||
</a>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input
|
||||
id="cookie"
|
||||
type="text"
|
||||
value={config.CNB_COOKIE}
|
||||
onChange={(e) => handleChange('CNB_COOKIE', e.target.value)}
|
||||
placeholder="请输入您的 CNB Cookie"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cors-url">跨域地址</Label>
|
||||
<Input
|
||||
id="cors-url"
|
||||
type="url"
|
||||
value={config.CNB_CORS_URL}
|
||||
onChange={(e) => handleChange('CNB_CORS_URL', e.target.value)}
|
||||
placeholder="https://cors.example.com"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="enable-cors"
|
||||
checked={config.ENABLE_CORS}
|
||||
onCheckedChange={(checked) => handleChange('ENABLE_CORS', checked === true)}
|
||||
/>
|
||||
<Label htmlFor="enable-cors" className="cursor-pointer">
|
||||
启用跨域
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-base-url">AI 基础地址</Label>
|
||||
<Input
|
||||
id="ai-base-url"
|
||||
type="url"
|
||||
value={config.AI_BASE_URL}
|
||||
onChange={(e) => handleChange('AI_BASE_URL', e.target.value)}
|
||||
placeholder="请输入 AI 基础地址"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ai-model">AI 模型</Label>
|
||||
<Input
|
||||
id="ai-model"
|
||||
type="text"
|
||||
value={config.AI_MODEL}
|
||||
onChange={(e) => handleChange('AI_MODEL', e.target.value)}
|
||||
placeholder="请输入 AI 模型名称"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="ai-api-key">AI 密钥</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
如果使用 CNB 的 AI,密钥和 API 密钥一样即可
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input
|
||||
id="ai-api-key"
|
||||
type="password"
|
||||
value={config.AI_API_KEY}
|
||||
onChange={(e) => handleChange('AI_API_KEY', e.target.value)}
|
||||
placeholder="请输入您的 AI API 密钥"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<Button type="submit">保存配置</Button>
|
||||
<Button type="button" variant="outline" onClick={resetConfig}>
|
||||
重置为默认值
|
||||
</Button>
|
||||
{layoutStore.me && <>
|
||||
<Button type="button" variant="outline" onClick={loadFromRemote}>
|
||||
获取远端配置
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onClick={saveToRemote}>
|
||||
保存到远端
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigPage;
|
||||
102
src/pages/config/store/index.ts
Normal file
102
src/pages/config/store/index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { Config, defaultConfig } from './schema';
|
||||
import { queryLogin } from '@/modules/query';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
type ConfigState = {
|
||||
config: Config;
|
||||
setConfig: (config: Partial<Config>) => void;
|
||||
resetConfig: () => void;
|
||||
saveToRemote: () => Promise<void>;
|
||||
loadFromRemote: () => Promise<void>;
|
||||
checkConfig: (opts?: { isUser?: boolean, reload?: boolean }) => Promise<boolean>;
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'cnb-config';
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
CNB_API_KEY: '',
|
||||
CNB_COOKIE: '',
|
||||
CNB_CORS_URL: 'https://cors.kevisual.cn',
|
||||
ENABLE_CORS: true,
|
||||
AI_BASE_URL: 'https://api.cnb.cool/kevisual/cnb-ai/-/ai/',
|
||||
AI_MODEL: 'CNB-Models',
|
||||
AI_API_KEY: ''
|
||||
}
|
||||
const loadInitialConfig = (): Config => {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
return DEFAULT_CONFIG;
|
||||
};
|
||||
|
||||
export const useConfigStore = create<ConfigState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
config: loadInitialConfig(),
|
||||
setConfig: (newConfig) =>
|
||||
set((state) => ({
|
||||
config: { ...state.config, ...newConfig },
|
||||
})),
|
||||
resetConfig: () =>
|
||||
set({
|
||||
config: DEFAULT_CONFIG,
|
||||
}),
|
||||
saveToRemote: async () => {
|
||||
const _config = get().config;
|
||||
const res = await queryLogin.post({
|
||||
path: 'config',
|
||||
key: 'update',
|
||||
data: {
|
||||
key: 'cnb_center_config.json',
|
||||
data: _config,
|
||||
}
|
||||
});
|
||||
if (res.code === 200) {
|
||||
toast.success('保存到远端成功')
|
||||
} else {
|
||||
toast.error('保存到远端失败')
|
||||
}
|
||||
},
|
||||
loadFromRemote: async () => {
|
||||
const setConfig = (config: Config) => set({ config });
|
||||
const res = await queryLogin.post({
|
||||
path: 'config',
|
||||
key: 'get',
|
||||
data: {
|
||||
key: 'cnb_center_config.json',
|
||||
}
|
||||
})
|
||||
if (res.code === 404) {
|
||||
toast.error('远端配置不存在')
|
||||
return;
|
||||
}
|
||||
if (res.code === 200) {
|
||||
const config = res.data?.data as typeof config;
|
||||
setConfig(config);
|
||||
toast.success('获取远端配置成功')
|
||||
}
|
||||
},
|
||||
checkConfig: async (opts?: { isUser?: boolean, reload?: boolean }) => {
|
||||
const { CNB_API_KEY } = get().config;
|
||||
if (!CNB_API_KEY && opts?.isUser) {
|
||||
await get().loadFromRemote();
|
||||
if (opts?.reload) {
|
||||
location.reload();
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
1
src/pages/gitea/page.tsx
Normal file
1
src/pages/gitea/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
// gitea.tsx
|
||||
3
src/pages/page.tsx
Normal file
3
src/pages/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import App from './repos/page'
|
||||
|
||||
export default App;
|
||||
136
src/pages/repos/components/BuildConfig.tsx
Normal file
136
src/pages/repos/components/BuildConfig.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRepoStore } from "../store";
|
||||
import { useShallow } from "zustand/shallow";
|
||||
import CodeMirror from "@uiw/react-codemirror";
|
||||
import { yaml } from "@codemirror/lang-yaml";
|
||||
import { useLayoutStore } from "@/pages/auth/store";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ArrowLeft, Workflow } from "lucide-react";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
export const BuildConfig = () => {
|
||||
const repoStore = useRepoStore(useShallow((state) => ({
|
||||
getItem: state.getItem,
|
||||
editRepo: state.editRepo,
|
||||
buildConfig: state.buildConfig,
|
||||
setBuildConfig: state.setBuildConfig,
|
||||
initBuildConfig: state.initBuildConfig,
|
||||
deleteBuildConfig: state.deleteBuildConfig,
|
||||
loading: state.loading,
|
||||
buildWorkspace: state.buildWorkspace,
|
||||
})));
|
||||
const repo = repoStore.editRepo!;
|
||||
const navigate = useNavigate();
|
||||
const me = useLayoutStore((state) => state.me);
|
||||
const [localConfig, setLocalConfig] = useState(repoStore.buildConfig?.config || "");
|
||||
|
||||
// 同步 buildConfig 变化时的状态
|
||||
useEffect(() => {
|
||||
setLocalConfig(repoStore.buildConfig?.config || "");
|
||||
}, [repoStore.buildConfig]);
|
||||
useEffect(() => {
|
||||
if (repo) {
|
||||
repoStore.initBuildConfig({ repo: repo, user: me });
|
||||
}
|
||||
}, [repo, me])
|
||||
|
||||
const handleSave = () => {
|
||||
if (repoStore.buildConfig) {
|
||||
repoStore.setBuildConfig({
|
||||
...repoStore.buildConfig,
|
||||
config: localConfig,
|
||||
}, true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFieldChange = (field: string, value: string | null) => {
|
||||
if (repoStore.buildConfig && value !== null) {
|
||||
repoStore.setBuildConfig({
|
||||
...repoStore.buildConfig,
|
||||
[field]: value,
|
||||
}, false);
|
||||
}
|
||||
};
|
||||
if (repoStore.loading) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
return (
|
||||
<div className="flex gap-4 h-full overflow-hidden">
|
||||
{/* 左侧边栏 - 配置信息 */}
|
||||
<div className="w-64 shrink-0 space-y-4">
|
||||
<div className="text-xl font-bold border-b pb-2 mb-4 flex">
|
||||
<button
|
||||
onClick={() => navigate({ to: '/' })}
|
||||
className="cursor-pointer flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 text-neutral-600" />
|
||||
</button>
|
||||
<span className="text-lg font-semibold">构建配置</span>
|
||||
<button
|
||||
onClick={repoStore.buildWorkspace}
|
||||
className="ml-auto p-2 text-sm cursor-pointer bg-gray-500 text-white rounded hover:bg-gray-600 flex items-center"
|
||||
title="构建工作空间"
|
||||
>
|
||||
<Workflow className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-neutral-500">仓库</label>
|
||||
<Input
|
||||
value={repoStore.buildConfig?.repo || ""}
|
||||
onChange={(e) => handleFieldChange("repo", e.target.value)}
|
||||
placeholder="仓库名称"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-neutral-500">分支</label>
|
||||
<Input
|
||||
value={repoStore.buildConfig?.branch || ""}
|
||||
onChange={(e) => handleFieldChange("branch", e.target.value)}
|
||||
placeholder="分支名称"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-neutral-500">事件</label>
|
||||
<Input
|
||||
value={repoStore.buildConfig?.event || ""}
|
||||
onChange={(e) => handleFieldChange("event", e.target.value)}
|
||||
placeholder="事件名称"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧 - 编辑器 */}
|
||||
<div className="flex-1 flex flex-col h-full ">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">配置文件</span>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="px-3 cursor-pointer py-1 text-sm bg-primary text-white rounded hover:bg-primary/90"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
<button
|
||||
onClick={() => repoStore.deleteBuildConfig({ repo: repo, user: me })}
|
||||
className="px-3 cursor-pointer py-1 text-sm bg-red-500 text-white rounded hover:bg-red-600"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border rounded-md flex-1 h-[calc(100%-40px)] overflow-auto scrollbar">
|
||||
<CodeMirror
|
||||
value={localConfig}
|
||||
height="100%"
|
||||
extensions={[yaml()]}
|
||||
onChange={(value) => setLocalConfig(value)}
|
||||
theme="light"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BuildConfig;
|
||||
@@ -13,47 +13,95 @@ import {
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
import { Star, GitFork, FileText, Edit, FolderGit2, MoreVertical, FileText as IssueIcon, Settings, Play, Trash2, RefreshCw, BookOpen } from 'lucide-react'
|
||||
import { Star, GitFork, FileText, Edit, FolderGit2, MoreVertical, FileText as IssueIcon, Settings, Play, Trash2, RefreshCw, BookOpen, Copy, Clock, Info, Eye, Square, LinkIcon, ExternalLink, ArrowLeft } from 'lucide-react'
|
||||
import { useRepoStore } from '../store'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useShallow } from 'zustand/shallow'
|
||||
import { myOrgs } from '../store/build'
|
||||
import { app, cnb } from '@/agents/app'
|
||||
import { toast } from 'sonner'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
|
||||
interface RepoCardProps {
|
||||
repo: any
|
||||
onStartWorkspace: (repo: any) => void
|
||||
onEdit: (repo: any) => void
|
||||
onIssue: (repo: any) => void
|
||||
onSettings: (repo: any) => void
|
||||
onDelete: (repo: any) => void
|
||||
onSync?: (repo: any) => void
|
||||
showReturn?: boolean
|
||||
}
|
||||
|
||||
export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings, onDelete, onSync }: RepoCardProps) {
|
||||
export function RepoCard({ showReturn = false, repo }: RepoCardProps) {
|
||||
const [deletePopoverOpen, setDeletePopoverOpen] = useState(false)
|
||||
const { workspaceList, getWorkspaceDetail, getList } = useRepoStore();
|
||||
const store = useRepoStore(useShallow((state) => ({
|
||||
workspaceList: state.workspaceList,
|
||||
getWorkspaceDetail: state.getWorkspaceDetail,
|
||||
getList: state.getList,
|
||||
buildUpdate: state.buildUpdate,
|
||||
stopWorkspace: state.stopWorkspace,
|
||||
setSelectedSyncRepo: state.setSelectedSyncRepo,
|
||||
setSyncDialogOpen: state.setSyncDialogOpen,
|
||||
startWorkspace: state.startWorkspace,
|
||||
setEditRepo: state.setEditRepo,
|
||||
setShowEditDialog: state.setShowEditDialog,
|
||||
deleteItem: state.deleteItem,
|
||||
})));
|
||||
const workspace = useMemo(() => {
|
||||
return workspaceList.find(ws => ws.slug === repo.path)
|
||||
}, [workspaceList, repo.path])
|
||||
return store.workspaceList.find(ws => ws.slug === repo.path)
|
||||
}, [store.workspaceList, repo.path])
|
||||
const isWorkspaceActive = !!workspace
|
||||
const owner = repo.path.split('/')[0]
|
||||
const isMine = myOrgs.includes(owner)
|
||||
|
||||
const navigate = useNavigate();
|
||||
const isKnowledge = repo?.flags === "KnowledgeBase"
|
||||
const createKnow = async () => {
|
||||
const res = await app.run({ path: 'cnb', key: 'build-knowledge-base', payload: { repo: repo.path } })
|
||||
if (res.code === 200) {
|
||||
toast.success("知识库创建中")
|
||||
getList(true)
|
||||
store.getList({}, true)
|
||||
}
|
||||
}
|
||||
const onClone = async () => {
|
||||
const url = `git clone https://cnb.cool/${repo.path}`
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
toast.success('克隆地址已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
toast.error('复制失败')
|
||||
})
|
||||
}
|
||||
const onUpdate = async () => {
|
||||
await store.buildUpdate({ path: repo.path });
|
||||
}
|
||||
const handleIssue = (repo: any) => {
|
||||
window.open(`https://cnb.cool/${repo.path}/-/issues`)
|
||||
}
|
||||
|
||||
const handleSettings = (repo: any) => {
|
||||
window.open(`https://cnb.cool/${repo.path}/-/settings`)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="relative p-0 overflow-hidden border border-neutral-200 bg-white hover:shadow-xl hover:border-neutral-300 transition-all duration-300 group pb-14">
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
{showReturn && (
|
||||
<button
|
||||
onClick={() => navigate({ to: '/' })}
|
||||
className="cursor-pointer flex items-center justify-center w-8 h-8 rounded-md hover:bg-neutral-100 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 text-neutral-600" />
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
className="text-lg font-bold text-neutral-900 hover:text-neutral-600 transition-colors line-clamp-1 group-hover:underline"
|
||||
onClick={() => {
|
||||
if (!showReturn) {
|
||||
navigate({ to: `/repo?repo=${repo.path}` })
|
||||
} else {
|
||||
window.open(`https://cnb.cool/${repo.path}`, '_blank')
|
||||
}
|
||||
}}
|
||||
>
|
||||
{repo.path}
|
||||
</div>
|
||||
{isKnowledge && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -70,14 +118,6 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<a
|
||||
href={repo.web_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-lg font-bold text-neutral-900 hover:text-neutral-600 transition-colors line-clamp-1 group-hover:underline"
|
||||
>
|
||||
{repo.path}
|
||||
</a>
|
||||
{isWorkspaceActive && (
|
||||
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
@@ -86,6 +126,29 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{isWorkspaceActive && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
store.stopWorkspace(workspace)
|
||||
}}
|
||||
className="h-8 w-8 p-0 border-neutral-200 hover:border-red-600 hover:bg-red-600 hover:text-white transition-all cursor-pointer"
|
||||
>
|
||||
<Square className="w-4 h-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<p>停止工作区</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
@@ -93,15 +156,22 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onStartWorkspace(repo)}
|
||||
onClick={() => {
|
||||
if (!isWorkspaceActive) {
|
||||
store.startWorkspace(repo)
|
||||
} else {
|
||||
store.getWorkspaceDetail(workspace)
|
||||
}
|
||||
|
||||
}}
|
||||
className="h-8 w-8 p-0 border-neutral-200 hover:border-neutral-900 hover:bg-neutral-900 hover:text-white transition-all cursor-pointer"
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
{isWorkspaceActive ? <Eye className="w-4 h-4" /> : <Play className="w-4 h-4" />}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<p>启动工作区</p>
|
||||
<p>{isWorkspaceActive ? '查看工作区' : '启动工作区'}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
@@ -118,24 +188,52 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent align="end" className="w-40">
|
||||
<DropdownMenuItem onClick={() => onEdit(repo)} className="cursor-pointer">
|
||||
<DropdownMenuItem onClick={() => {
|
||||
store.setEditRepo(repo)
|
||||
store.setShowEditDialog(true)
|
||||
}} className="cursor-pointer">
|
||||
<Edit className="w-4 h-4 mr-2" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onIssue(repo)} className="cursor-pointer">
|
||||
<IssueIcon className="w-4 h-4 mr-2" />
|
||||
Issue
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onSettings(repo)} className="cursor-pointer">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
设置
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
createKnow()
|
||||
}} className="cursor-pointer">
|
||||
<BookOpen className="w-4 h-4 mr-2" />
|
||||
知识库创建
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
onUpdate()
|
||||
}} className="cursor-pointer">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className={'flex gap-1 items-center'}>
|
||||
<Clock className="w-4 h-4 mr-2" />
|
||||
更新时间
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" className="max-w-xs">
|
||||
<p>给仓库添加一个空白 commit 的提交,保证在仓库列表中顺序活跃在前面</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onClone} className="cursor-pointer">
|
||||
<Copy className="w-4 h-4 mr-2" />
|
||||
Clone URL
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
window.open(repo.web_url, '_blank')
|
||||
}} className="cursor-pointer">
|
||||
<ExternalLink className="w-4 h-4 mr-2" />
|
||||
访问仓库
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleIssue(repo)} className="cursor-pointer">
|
||||
<IssueIcon className="w-4 h-4 mr-2" />
|
||||
访问问题
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleSettings(repo)} className="cursor-pointer">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
访问设置
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
@@ -173,7 +271,8 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
variant="outline"
|
||||
className="bg-red-600 text-white border-red-600 hover:bg-red-700 hover:border-red-700"
|
||||
onClick={() => {
|
||||
onDelete(repo)
|
||||
if (repo.path)
|
||||
store.deleteItem(repo.path)
|
||||
setDeletePopoverOpen(false)
|
||||
}}
|
||||
>
|
||||
@@ -186,33 +285,40 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{repo.topics && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{repo.topics.split(',').map((topic: string, idx: number) => (
|
||||
<Badge key={idx} variant="outline" className="text-xs border-neutral-300 text-neutral-700 hover:bg-neutral-100 transition-colors">
|
||||
{topic.trim()}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{repo.topics && (<>
|
||||
{
|
||||
repo.topics.split(',').map((topic: string, idx: number) => (
|
||||
<Badge key={idx} variant="outline" className="text-xs border-neutral-300 text-neutral-700 hover:bg-neutral-100 transition-colors">
|
||||
{topic.trim()}
|
||||
</Badge>
|
||||
))
|
||||
}
|
||||
</>
|
||||
)}
|
||||
<Badge variant="outline" className="text-xs border-neutral-300 text-neutral-700 hover:bg-neutral-100 transition-colors">{repo.visibility_level}</Badge>
|
||||
</div>
|
||||
|
||||
{repo.site && (
|
||||
<a
|
||||
href={repo.site}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-neutral-500 hover:text-neutral-900 hover:underline block truncate transition-colors"
|
||||
<div
|
||||
className="text-xs text-neutral-500 hover:text-neutral-900 hover:underline flex transition-colors"
|
||||
onClick={() => {
|
||||
window.open(repo.site, '_blank')
|
||||
}}
|
||||
>
|
||||
🔗 {repo.site}
|
||||
</a>
|
||||
<LinkIcon className="w-4 h-4 shrink-0 mr-2" />
|
||||
<div className='truncate grow'>
|
||||
{repo.site}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{repo.description && (
|
||||
<p className="text-sm text-neutral-600 line-clamp-2 min-h-[2.5rem]">
|
||||
<p className="ml-2 text-sm text-neutral-600 line-clamp-2 min-h-10 grow">
|
||||
{repo.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 flex items-center gap-4 text-xs text-neutral-500 px-6 py-3 border-t border-neutral-100 bg-neutral-50">
|
||||
<span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors">
|
||||
<Star className="w-3.5 h-3.5" />
|
||||
@@ -228,7 +334,7 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
</span>
|
||||
{isWorkspaceActive && <span className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
||||
onClick={() => {
|
||||
getWorkspaceDetail(workspace)
|
||||
store.getWorkspaceDetail(workspace)
|
||||
}}>
|
||||
<Play className="w-3.5 h-3.5" />
|
||||
<span className="font-medium">运行中</span>
|
||||
@@ -236,7 +342,10 @@ export function RepoCard({ repo, onStartWorkspace, onEdit, onIssue, onSettings,
|
||||
{isMine && (
|
||||
<span
|
||||
className="flex items-center gap-1.5 hover:text-neutral-900 transition-colors cursor-pointer"
|
||||
onClick={() => onSync?.(repo)}
|
||||
onClick={() => {
|
||||
store.setSelectedSyncRepo(repo)
|
||||
store.setSyncDialogOpen(true)
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5" />
|
||||
<span className="font-medium">同步</span>
|
||||
@@ -13,6 +13,7 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useRepoStore } from '../store'
|
||||
import { useShallow } from 'zustand/shallow'
|
||||
|
||||
interface CreateRepoDialogProps {
|
||||
open: boolean
|
||||
@@ -27,7 +28,10 @@ interface FormData {
|
||||
}
|
||||
|
||||
export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps) {
|
||||
const { createRepo, getList } = useRepoStore()
|
||||
const { createRepo, refresh } = useRepoStore(useShallow((state) => ({
|
||||
createRepo: state.createRepo,
|
||||
refresh: state.refresh,
|
||||
})))
|
||||
const { register, handleSubmit, reset } = useForm<FormData>()
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
@@ -50,11 +54,9 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps)
|
||||
...data,
|
||||
}
|
||||
|
||||
const res = await createRepo(submitData)
|
||||
if (res?.code === 200) {
|
||||
onOpenChange(false)
|
||||
await getList(true)
|
||||
}
|
||||
await createRepo(submitData)
|
||||
onOpenChange(false)
|
||||
refresh()
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
@@ -62,7 +64,7 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogContent className="sm:max-w-150">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新建仓库</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -14,6 +14,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { TagsInput } from '@/components/tags-input'
|
||||
import { useRepoStore } from '../store'
|
||||
import { useShallow } from 'zustand/shallow'
|
||||
|
||||
interface EditRepoDialogProps {
|
||||
open: boolean
|
||||
@@ -36,7 +37,10 @@ interface FormData {
|
||||
}
|
||||
|
||||
export function EditRepoDialog({ open, onOpenChange, repo }: EditRepoDialogProps) {
|
||||
const { updateRepoInfo, getList } = useRepoStore()
|
||||
const { updateRepoInfo, getList } = useRepoStore(useShallow((state) => ({
|
||||
updateRepoInfo: state.updateRepoInfo,
|
||||
getList: state.getList,
|
||||
})))
|
||||
const { register, handleSubmit, reset, setValue } = useForm<FormData>()
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
|
||||
@@ -64,7 +68,7 @@ export function EditRepoDialog({ open, onOpenChange, repo }: EditRepoDialogProps
|
||||
license: data.license?.trim() || '',
|
||||
})
|
||||
|
||||
await getList(true)
|
||||
await getList({}, true)
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,20 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { useRepoStore } from '../store'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useShallow } from 'zustand/shallow'
|
||||
import { get, set } from 'idb-keyval'
|
||||
import { gitea } from '@/agents/app';
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const SYNC_REPO_STORAGE_KEY = 'sync-repo-mapping'
|
||||
|
||||
export function SyncRepoDialog() {
|
||||
const { syncDialogOpen, setSyncDialogOpen, selectedSyncRepo, buildSync } = useRepoStore()
|
||||
const { syncDialogOpen, setSyncDialogOpen, selectedSyncRepo, buildSync } = useRepoStore(useShallow((state) => ({
|
||||
syncDialogOpen: state.syncDialogOpen,
|
||||
setSyncDialogOpen: state.setSyncDialogOpen,
|
||||
selectedSyncRepo: state.selectedSyncRepo,
|
||||
buildSync: state.buildSync,
|
||||
})))
|
||||
const [toRepo, setToRepo] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
@@ -39,10 +47,29 @@ export function SyncRepoDialog() {
|
||||
await buildSync(selectedSyncRepo, { toRepo })
|
||||
setSyncDialogOpen(false)
|
||||
}
|
||||
|
||||
const onCreateRepo = async () => {
|
||||
if (!toRepo.trim()) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await gitea.repo.createRepo({ name: toRepo })
|
||||
if (res.code !== 200 && res.code !== 409) {
|
||||
// 409 表示仓库已存在,可以继续同步
|
||||
throw new Error(`${res.message}`)
|
||||
}
|
||||
if (res.code === 200) {
|
||||
toast.success('仓库创建成功,正在同步...')
|
||||
} else {
|
||||
toast.warning('仓库已存在,正在同步...')
|
||||
}
|
||||
handleSync()
|
||||
} catch (error) {
|
||||
console.error('创建仓库失败:', error)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Dialog open={syncDialogOpen} onOpenChange={setSyncDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogContent className="sm:max-w-125">
|
||||
<DialogHeader>
|
||||
<DialogTitle>同步仓库到 Gitea</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -72,6 +99,9 @@ export function SyncRepoDialog() {
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={onCreateRepo} disabled={!toRepo.trim()}>
|
||||
先创建仓库再同步
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSync}
|
||||
disabled={!toRepo.trim()}
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { useShallow } from 'zustand/shallow'
|
||||
|
||||
type LinkItemKey = keyof WorkspaceOpen;
|
||||
interface LinkItem {
|
||||
@@ -94,9 +95,83 @@ const LinkItem = ({ label, icon, url }: { label: string; icon: React.ReactNode;
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkspaceDetailDialog() {
|
||||
const { showWorkspaceDialog, setShowWorkspaceDialog, workspaceLink, stopWorkspace } = useRepoStore()
|
||||
// Dev tab 内容
|
||||
const DevTabContent = ({ linkItems, workspaceLink, stopWorkspace }: {
|
||||
linkItems: LinkItem[]
|
||||
workspaceLink: Partial<WorkspaceOpen>
|
||||
stopWorkspace: () => void
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => stopWorkspace()}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg bg-red-500 hover:bg-red-600 text-white font-medium transition-colors"
|
||||
>
|
||||
<Square className="w-4 h-4" />
|
||||
停止工作区
|
||||
</button>
|
||||
<div className="grid grid-cols-2 gap-3 mt-2">
|
||||
{linkItems.map((item) => (
|
||||
<LinkItem
|
||||
key={item.key}
|
||||
label={item.label}
|
||||
icon={item.icon}
|
||||
url={item.getUrl(workspaceLink)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Work tab 内容(暂留,需要根据 business_id 做事情)
|
||||
const WorkTabContent = () => {
|
||||
const store = useRepoStore(useShallow((state) => ({ selectWorkspace: state.selectWorkspace })))
|
||||
const businessId = store.selectWorkspace?.business_id;
|
||||
|
||||
const appList = [
|
||||
{
|
||||
title: 'Kevisual Assistant Client', key: 'Assistant Client', port: 51515, end: '/root/cli-center/'
|
||||
},
|
||||
{
|
||||
title: 'OpenCode', key: 'OpenCode', port: 100, end: ''
|
||||
},
|
||||
{
|
||||
title: 'OpenClaw', key: 'OpenClaw', port: 80, end: '/openclaw'
|
||||
},
|
||||
{
|
||||
title: 'OpenWebUI', key: 'OpenWebUI', port: 200, end: ''
|
||||
},
|
||||
]
|
||||
const links = appList.map(app => {
|
||||
const url = `https://${businessId}-${app.port}.cnb.run${app.end}`
|
||||
return {
|
||||
label: app.title,
|
||||
icon: <Terminal className="w-5 h-5" />,
|
||||
url
|
||||
}
|
||||
})
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-2 text-neutral-400">
|
||||
<div className="grid grid-cols-1 gap-3 w-full max-w-sm">
|
||||
{links.map(link => (
|
||||
<LinkItem key={link.label} label={link.label} icon={link.icon} url={link.url} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function WorkspaceDetailDialog() {
|
||||
const { showWorkspaceDialog, setShowWorkspaceDialog, workspaceLink, stopWorkspace, workspaceTab, setWorkspaceTab, selectWorkspace } = useRepoStore(useShallow((state) => ({
|
||||
showWorkspaceDialog: state.showWorkspaceDialog,
|
||||
setShowWorkspaceDialog: state.setShowWorkspaceDialog,
|
||||
workspaceLink: state.workspaceLink,
|
||||
stopWorkspace: state.stopWorkspace,
|
||||
workspaceTab: state.workspaceTab,
|
||||
setWorkspaceTab: state.setWorkspaceTab,
|
||||
selectWorkspace: state.selectWorkspace,
|
||||
})))
|
||||
const linkItems: LinkItem[] = [
|
||||
{
|
||||
key: 'webide' as LinkItemKey,
|
||||
@@ -162,30 +237,46 @@ export function WorkspaceDetailDialog() {
|
||||
getUrl: (data) => data.codebuddycn
|
||||
},
|
||||
].sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
|
||||
return (
|
||||
<Dialog open={showWorkspaceDialog} onOpenChange={setShowWorkspaceDialog}>
|
||||
<DialogContent className="max-w-md! bg-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-neutral-900">打开工作区</DialogTitle>
|
||||
<DialogDescription className="text-neutral-500">选择一个编辑器或方式来打开工作区</DialogDescription>
|
||||
<DialogTitle className="text-neutral-900">工作区</DialogTitle>
|
||||
</DialogHeader>
|
||||
<button
|
||||
onClick={() => stopWorkspace()}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg bg-red-500 hover:bg-red-600 text-white font-medium transition-colors"
|
||||
>
|
||||
<Square className="w-4 h-4" />
|
||||
停止工作区
|
||||
</button>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{linkItems.map((item) => (
|
||||
<LinkItem
|
||||
key={item.key}
|
||||
label={item.label}
|
||||
icon={item.icon}
|
||||
url={item.getUrl(workspaceLink)}
|
||||
/>
|
||||
))}
|
||||
{/* Tab 导航 */}
|
||||
<div className="flex border-b border-neutral-200">
|
||||
<button
|
||||
onClick={() => setWorkspaceTab('dev')}
|
||||
className={`cursor-pointer flex-1 px-4 py-3 text-sm font-medium transition-colors relative ${workspaceTab === 'dev'
|
||||
? 'text-neutral-900'
|
||||
: 'text-neutral-500 hover:text-neutral-700'
|
||||
}`}
|
||||
>
|
||||
Dev
|
||||
{workspaceTab === 'dev' && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-neutral-900" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setWorkspaceTab('work')}
|
||||
className={`cursor-pointer flex-1 px-4 py-3 text-sm font-medium transition-colors relative ${workspaceTab === 'work'
|
||||
? 'text-neutral-900'
|
||||
: 'text-neutral-500 hover:text-neutral-700'
|
||||
}`}
|
||||
>
|
||||
Work
|
||||
{workspaceTab === 'work' && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-neutral-900" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{/* Tab 内容 */}
|
||||
<div className="py-2">
|
||||
{workspaceTab === 'dev' ? (
|
||||
<DevTabContent linkItems={linkItems} workspaceLink={workspaceLink} stopWorkspace={stopWorkspace} />
|
||||
) : (
|
||||
<WorkTabContent />
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
180
src/pages/repos/page.tsx
Normal file
180
src/pages/repos/page.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import { use, useEffect, useMemo, useState } from 'react'
|
||||
import { useRepoStore } from './store/index'
|
||||
import { useShallow } from 'zustand/shallow'
|
||||
import { RepoCard } from './components/RepoCard'
|
||||
import { EditRepoDialog } from './modules/EditRepoDialog'
|
||||
import { CreateRepoDialog } from './modules/CreateRepoDialog'
|
||||
import { WorkspaceDetailDialog } from './modules/WorkspaceDetailDialog'
|
||||
import { SyncRepoDialog } from './modules/SyncRepoDialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ExternalLinkIcon, Plus, RefreshCw, Search, Settings } from 'lucide-react'
|
||||
import Fuse from 'fuse.js'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { useLayoutStore } from '../auth/store'
|
||||
import { useConfigStore } from '../config/store'
|
||||
|
||||
export const App = () => {
|
||||
const { list, refresh, loading, workspaceList, setShowCreateDialog } = useRepoStore(useShallow((state) => ({
|
||||
list: state.list,
|
||||
refresh: state.refresh,
|
||||
loading: state.loading,
|
||||
workspaceList: state.workspaceList,
|
||||
setShowCreateDialog: state.setShowCreateDialog,
|
||||
})))
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const navigate = useNavigate();
|
||||
const me = useLayoutStore(state => state.me)
|
||||
const configStore = useConfigStore(useShallow(state => ({ checkConfig: state.checkConfig })))
|
||||
useEffect(() => {
|
||||
refresh({ showTips: false })
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (me && me.id) {
|
||||
configStore.checkConfig({ isUser: true, reload: true })
|
||||
}
|
||||
}, [me])
|
||||
|
||||
|
||||
const appList = useMemo(() => {
|
||||
// 首先按活动状态排序
|
||||
const sortedList = [...list].sort((a, b) => {
|
||||
const aActive = workspaceList.some(ws => ws.slug === a.path)
|
||||
const bActive = workspaceList.some(ws => ws.slug === b.path)
|
||||
|
||||
if (aActive && !bActive) return -1
|
||||
if (!aActive && bActive) return 1
|
||||
return 0
|
||||
})
|
||||
|
||||
// 如果没有搜索词,返回排序后的列表
|
||||
if (!searchQuery.trim()) {
|
||||
return sortedList
|
||||
}
|
||||
|
||||
// 使用 Fuse.js 进行模糊搜索
|
||||
const fuse = new Fuse(sortedList, {
|
||||
keys: ['name', 'path', 'description'],
|
||||
threshold: 0.3,
|
||||
includeScore: true
|
||||
})
|
||||
|
||||
const results = fuse.search(searchQuery)
|
||||
return results.map(result => result.item)
|
||||
}, [list, workspaceList, searchQuery])
|
||||
|
||||
const isCNB = location.hostname.includes('cnb.run')
|
||||
return (
|
||||
<div className="min-h-screen bg-neutral-50 flex flex-col">
|
||||
<div className="container mx-auto p-6 max-w-7xl flex-1">
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div >
|
||||
<h1 className="text-4xl font-bold text-neutral-900 mb-2 flex gap-2 items-center">
|
||||
仓库列表
|
||||
<Settings className="inline-block h-5 w-5 text-neutral-400 hover:text-neutral-600 cursor-pointer" onClick={() => navigate({ to: '/config' })} />
|
||||
</h1>
|
||||
<p className="text-neutral-600">共 {list.length} 个仓库</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => refresh()}
|
||||
variant="outline"
|
||||
className="gap-2"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
刷新
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setShowCreateDialog(true)}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建仓库
|
||||
</Button>
|
||||
|
||||
{isCNB && <Button
|
||||
onClick={() => {
|
||||
window.open('/root/cli-center', '_blank')
|
||||
}}
|
||||
className="gap-2"
|
||||
>
|
||||
<ExternalLinkIcon className="h-4 w-4" />
|
||||
CLI
|
||||
</Button>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-neutral-400" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="搜索仓库名称、路径或描述..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{appList.map((repo) => (
|
||||
<RepoCard
|
||||
key={repo.id}
|
||||
repo={repo}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{appList.length === 0 && !loading && (
|
||||
<div className="text-center py-20">
|
||||
<div className="text-neutral-400 text-lg">
|
||||
{searchQuery ? '未找到匹配的仓库' : '暂无仓库数据'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<footer className="border-t border-neutral-200 bg-white py-6 mt-auto">
|
||||
<div className="container mx-auto px-6 max-w-7xl">
|
||||
<div className="flex items-center justify-between text-sm text-neutral-500">
|
||||
<div>© 2026 仓库管理系统</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<a href="#" className="hover:text-neutral-900 transition-colors">关于</a>
|
||||
<a href="#" className="hover:text-neutral-900 transition-colors">帮助</a>
|
||||
<a href="#" className="hover:text-neutral-900 transition-colors">联系我们</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<CommonRepoDialog />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CommonRepoDialog = () => {
|
||||
const { editRepo, showEditDialog, setShowEditDialog, showCreateDialog, setShowCreateDialog, } = useRepoStore(useShallow((state) => ({
|
||||
editRepo: state.editRepo,
|
||||
showEditDialog: state.showEditDialog,
|
||||
setShowEditDialog: state.setShowEditDialog,
|
||||
showCreateDialog: state.showCreateDialog,
|
||||
setShowCreateDialog: state.setShowCreateDialog,
|
||||
})))
|
||||
return (
|
||||
<>
|
||||
<EditRepoDialog
|
||||
open={showEditDialog}
|
||||
onOpenChange={setShowEditDialog}
|
||||
repo={editRepo}
|
||||
/>
|
||||
<CreateRepoDialog
|
||||
open={showCreateDialog}
|
||||
onOpenChange={setShowCreateDialog}
|
||||
/>
|
||||
<WorkspaceDetailDialog />
|
||||
<SyncRepoDialog />
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default App;
|
||||
64
src/pages/repos/repo/page.tsx
Normal file
64
src/pages/repos/repo/page.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
import { useRepoStore } from "../store";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useShallow } from "zustand/shallow";
|
||||
import BuildConfig from "../components/BuildConfig";
|
||||
import { CommonRepoDialog } from "../page";
|
||||
import { RepoCard } from "../components/RepoCard";
|
||||
|
||||
export const App = () => {
|
||||
const params = useSearch({ strict: false }) as { repo?: string, tab?: string };
|
||||
const repoStore = useRepoStore(useShallow((state) => ({
|
||||
getItem: state.getItem,
|
||||
editRepo: state.editRepo,
|
||||
refresH: state.refresh,
|
||||
})));
|
||||
const [activeTab, setActiveTab] = useState(params.tab || "build");
|
||||
const tabs = [
|
||||
{ key: "build", label: "构建配置" },
|
||||
{ key: "info", label: "基本信息" },
|
||||
]
|
||||
useEffect(() => {
|
||||
if (params.repo) {
|
||||
repoStore.getItem(params.repo);
|
||||
repoStore.refresH({ search: params.repo, showTips: false });
|
||||
} else {
|
||||
console.log('no repo param')
|
||||
}
|
||||
}, [params])
|
||||
if (!repoStore.editRepo) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
return (
|
||||
<div className="p-2 flex-col flex gap-2 h-full">
|
||||
<div className="px-4 h-full scrollbar flex-col flex gap-4 overflow-hidden">
|
||||
<div className="flex border-b mb-4">
|
||||
{tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`px-4 py-2 cursor-pointer ${activeTab === tab.key ? 'border-b-2 border-gray-500' : ''}`}
|
||||
onClick={() => {
|
||||
setActiveTab(tab.key)
|
||||
history.replaceState(null, '', `?repo=${params.repo}&tab=${tab.key}`)
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{activeTab === 'build' && <BuildConfig />}
|
||||
{activeTab === 'info' && (
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<RepoCard repo={repoStore.editRepo} showReturn />
|
||||
<div className="p-4 border rounded bg-white h-full overflow-auto scrollbar">
|
||||
<pre className="whitespace-pre-wrap break-all">{JSON.stringify(repoStore.editRepo, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CommonRepoDialog />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
109
src/pages/repos/store/build.ts
Normal file
109
src/pages/repos/store/build.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
export const myOrgs = ['kevisual', 'kevision', 'skillpod', 'zxj.im', 'abearxiong']
|
||||
import dayjs from 'dayjs'
|
||||
export const createBuildConfig = (params: { repo: string }) => {
|
||||
const toRepo = params.repo!;
|
||||
return `
|
||||
# .cnb.yml
|
||||
include:
|
||||
- https://cnb.cool/kevisual/cnb/-/blob/main/.cnb/template.yml
|
||||
|
||||
.common_env: &common_env
|
||||
env:
|
||||
TO_REPO: ${toRepo}
|
||||
TO_URL: git.xiongxiao.me
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||
|
||||
.common_sync_to_gitea: &common_sync_to_gitea
|
||||
- <<: *common_env
|
||||
services: !reference [.common_sync_to_gitea_template, services]
|
||||
stages: !reference [.common_sync_to_gitea_template, stages]
|
||||
|
||||
.common_sync_from_gitea: &common_sync_from_gitea
|
||||
- <<: *common_env
|
||||
services: !reference [.common_sync_from_gitea_template, services]
|
||||
stages: !reference [.common_sync_from_gitea_template, stages]
|
||||
|
||||
main:
|
||||
api_trigger_sync_to_gitea:
|
||||
- <<: *common_sync_to_gitea
|
||||
api_trigger_sync_from_gitea:
|
||||
- <<: *common_sync_from_gitea
|
||||
`
|
||||
};
|
||||
|
||||
export const createCommitBlankConfig = (params: { repo?: string, event: 'api_trigger_event' }) => {
|
||||
const now = dayjs().format('YYYY-MM-DD HH:mm')
|
||||
const event = params?.event || 'api_trigger_event'
|
||||
return `main:
|
||||
${event}:
|
||||
-
|
||||
services:
|
||||
- docker
|
||||
stages:
|
||||
- name: 显示 git remote
|
||||
script: git remote -v
|
||||
- name: commit_blank
|
||||
script: |
|
||||
echo "这是一个空白提交 时间: ${now}"
|
||||
git commit --allow-empty -m "up: ${now}"
|
||||
git push
|
||||
`
|
||||
}
|
||||
|
||||
export const createDevConfig = (params: { repo?: string, event?: string, branch?: string }) => {
|
||||
const event = params?.event || 'api_trigger_event';
|
||||
const branch = params?.branch || 'main';
|
||||
return `##### 配置开始,保留注释 #####
|
||||
.common_env: &common_env
|
||||
env:
|
||||
# 使用环境变量管理密钥,推荐使用密钥仓库管理密钥, 详情见 readme.md
|
||||
# 使用仓库密钥时,注释
|
||||
## 可选 API-Key 配置(按需取消注释)
|
||||
# MINIMAX_API_KEY: '' # Minimax 模型
|
||||
# ZHIPU_API_KEY: '' # 智谱 AI
|
||||
# BAILIAN_CODE_API_KEY: '' # 阿里云百炼
|
||||
# VOLCENGINE_API_KEY: '' # 火山引擎
|
||||
# CNB_API_KEY: '' # CNB API
|
||||
# CNB_COOKIE: '' # 可选配置,用cnb.cool的cookie
|
||||
|
||||
# 可选应用配置
|
||||
# FEISHU_APP_ID: '' # 飞书应用 ID
|
||||
# FEISHU_APP_SECRET: '' # 飞书应用密钥
|
||||
|
||||
USERNAME: root
|
||||
ASSISTANT_CONFIG_DIR: /workspace/kevisual # ASSISTANT_CONFIG_DIR 环境变量指定了配置文件所在的目录
|
||||
# CNB_KEVISUAL_ORG: kevisual # 私密仓库使用环境配置(默认即可,默认为当前用户组CNB_GROUP_SLUG)
|
||||
# CNB_KEVISUAL_APP: assistant-app # 可选配置(默认即可)
|
||||
# CNB_OPENCLAW: openclaw # 仓库名(默认即可)
|
||||
# CNB_OPENWEBUI: open-webui # 仓库名(默认即可)
|
||||
imports:
|
||||
- https://cnb.cool/kevisual/env/-/blob/main/.env.development
|
||||
|
||||
##### 配置结束 #####
|
||||
|
||||
${branch}:
|
||||
${event}:
|
||||
- docker:
|
||||
image: docker.cnb.cool/kevisual/dev-env:latest
|
||||
services:
|
||||
- vscode
|
||||
- docker
|
||||
runner:
|
||||
cpus: 16
|
||||
#tags: cnb:arch:amd64:gpu
|
||||
imports: !reference [.common_env, imports]
|
||||
env: !reference [.common_env, env]
|
||||
stages:
|
||||
- name: 环境变量
|
||||
script: printenv > ~/.env.development
|
||||
- name: 启动nginx
|
||||
script: nginx
|
||||
- name: 初始化开发机
|
||||
script: zsh /workspace/scripts/init.sh
|
||||
# endStages:
|
||||
# - name: 结束阶段
|
||||
# script: bun /workspace/scripts/end.ts
|
||||
|
||||
`
|
||||
}
|
||||
@@ -3,7 +3,9 @@ import { query } from '@/modules/query';
|
||||
import { toast } from 'sonner';
|
||||
import { cnb } from '@/agents/app'
|
||||
import { WorkspaceInfo } from '@kevisual/cnb'
|
||||
import { createBuildConfig } from './build';
|
||||
import { createBuildConfig, createCommitBlankConfig, createDevConfig } from './build';
|
||||
import { useLayoutStore } from '@/pages/auth/store';
|
||||
import { useConfigStore } from '@/pages/config/store';
|
||||
interface DisplayModule {
|
||||
activity: boolean;
|
||||
contributors: boolean;
|
||||
@@ -20,6 +22,7 @@ interface Data {
|
||||
name: string;
|
||||
freeze: boolean;
|
||||
status: number;
|
||||
// Public, Private
|
||||
visibility_level: string;
|
||||
flags: string;
|
||||
created_at: string;
|
||||
@@ -49,6 +52,14 @@ interface Data {
|
||||
pinned_time: string;
|
||||
}
|
||||
|
||||
type WorkspaceTabType = 'dev' | 'work'
|
||||
|
||||
type BuildConfig = {
|
||||
repo: string;
|
||||
branch: string;
|
||||
event: string;
|
||||
config: string;
|
||||
}
|
||||
type State = {
|
||||
formData: Record<string, any>;
|
||||
setFormData: (data: Record<string, any>) => void;
|
||||
@@ -56,6 +67,8 @@ type State = {
|
||||
setShowEdit: (showEdit: boolean) => void;
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
workspaceTab: WorkspaceTabType;
|
||||
setWorkspaceTab: (tab: WorkspaceTabType) => void;
|
||||
list: Data[];
|
||||
editRepo: Data | null;
|
||||
setEditRepo: (repo: Data | null) => void;
|
||||
@@ -63,14 +76,15 @@ type State = {
|
||||
setShowEditDialog: (show: boolean) => void;
|
||||
showCreateDialog: boolean;
|
||||
setShowCreateDialog: (show: boolean) => void;
|
||||
getList: (silent?: boolean) => Promise<any>;
|
||||
getList: (params?: { search?: string }, silent?: boolean) => Promise<any>;
|
||||
updateRepoInfo: (data: Partial<Data>) => Promise<any>;
|
||||
createRepo: (data: { visibility: any, path: string, description: string, license: string }) => Promise<any>;
|
||||
deleteItem: (repo: string) => Promise<any>;
|
||||
workspaceList: WorkspaceInfo[];
|
||||
getWorkspaceList: () => Promise<any>;
|
||||
refresh: (opts?: { message?: string, showTips?: boolean, search?: string }) => Promise<any>;
|
||||
startWorkspace: (data: Partial<Data>, params?: { open?: boolean, branch?: string }) => Promise<any>;
|
||||
stopWorkspace: () => Promise<any>;
|
||||
stopWorkspace: (workspace?: WorkspaceInfo) => Promise<any>;
|
||||
getWorkspaceDetail: (data: WorkspaceInfo) => Promise<any>;
|
||||
workspaceLink: Partial<WorkspaceOpen>;
|
||||
selectWorkspace?: WorkspaceInfo,
|
||||
@@ -81,6 +95,13 @@ type State = {
|
||||
selectedSyncRepo: Data | null;
|
||||
setSelectedSyncRepo: (repo: Data | null) => void;
|
||||
buildSync: (data: Partial<Data>, params: { toRepo?: string, fromRepo?: string }) => Promise<any>;
|
||||
buildUpdate: (data: Partial<Data>, params?: any) => Promise<any>;
|
||||
getItem: (repo: string) => Promise<any>;
|
||||
buildConfig: BuildConfig | null;
|
||||
setBuildConfig: (config: BuildConfig | null, save?: boolean) => Promise<any>;
|
||||
deleteBuildConfig: (params: { repo: Data, user?: any }) => Promise<any>;
|
||||
initBuildConfig: (params: { repo: Data, user?: any }) => Promise<any>;
|
||||
buildWorkspace: () => Promise<any>;
|
||||
}
|
||||
|
||||
export const useRepoStore = create<State>((set, get) => {
|
||||
@@ -100,21 +121,141 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
setShowCreateDialog: (show) => set({ showCreateDialog: show }),
|
||||
showWorkspaceDialog: false,
|
||||
setShowWorkspaceDialog: (show) => set({ showWorkspaceDialog: show }),
|
||||
workspaceTab: 'dev',
|
||||
setWorkspaceTab: (tab) => set({ workspaceTab: tab }),
|
||||
syncDialogOpen: false,
|
||||
setSyncDialogOpen: (open) => set({ syncDialogOpen: open }),
|
||||
selectedSyncRepo: null,
|
||||
setSelectedSyncRepo: (repo) => set({ selectedSyncRepo: repo }),
|
||||
getItem: async (id) => {
|
||||
buildConfig: null,
|
||||
setBuildConfig: async (config, save = true) => {
|
||||
const me = useLayoutStore.getState().me;
|
||||
if (config && config!.repo && save) {
|
||||
let path: string = config.repo || '';
|
||||
path = path.replace(/\//g, '__');
|
||||
const key = `buildConfig_${path}.json`;
|
||||
if (config && me) {
|
||||
const res = await query.post({
|
||||
path: 'config',
|
||||
key: 'update',
|
||||
data: {
|
||||
key: key,
|
||||
data: config,
|
||||
}
|
||||
})
|
||||
if (res.code === 200) {
|
||||
toast.success('配置已保存')
|
||||
} else {
|
||||
toast.error(res.message || '配置保存失败')
|
||||
}
|
||||
} else if (config) {
|
||||
localStorage.setItem(key, JSON.stringify(config));
|
||||
}
|
||||
}
|
||||
|
||||
set({ buildConfig: config })
|
||||
},
|
||||
deleteBuildConfig: async (params: { repo: Data, user?: any }) => {
|
||||
const repo = params.repo;
|
||||
let path: string = repo.path || '';
|
||||
path = path.replace(/\//g, '__');
|
||||
const key = `buildConfig_${path}.json`;
|
||||
if (params?.user) {
|
||||
const res = await query.post({
|
||||
path: 'config',
|
||||
key: 'delete',
|
||||
data: {
|
||||
key: key,
|
||||
}
|
||||
})
|
||||
if (res.code === 200) {
|
||||
toast.success('配置已删除')
|
||||
} else {
|
||||
toast.error(res.message || '配置删除失败')
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem(key);
|
||||
toast.success('配置已删除')
|
||||
}
|
||||
},
|
||||
initBuildConfig: async (params) => {
|
||||
const repo = params.repo;
|
||||
if (!repo) {
|
||||
toast.error('仓库数据异常');
|
||||
return;
|
||||
}
|
||||
set({ loading: true })
|
||||
try {
|
||||
console.log('初始化构建配置', params)
|
||||
let path: string = repo.path || '';
|
||||
path = path.replace(/\//g, '__');
|
||||
const key = `buildConfig_${path}.json`;
|
||||
if (params?.user) {
|
||||
const res = await query.post({
|
||||
path: 'config',
|
||||
key: 'get',
|
||||
data: {
|
||||
key: key,
|
||||
}
|
||||
})
|
||||
if (res.code === 200 && res.data?.data) {
|
||||
set({ buildConfig: res.data.data })
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const localConfig = localStorage.getItem(key);
|
||||
if (localConfig) {
|
||||
try {
|
||||
const config = JSON.parse(localConfig);
|
||||
set({ buildConfig: config })
|
||||
return;
|
||||
} catch (e) {
|
||||
console.error('本地配置解析失败', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const config: BuildConfig = {
|
||||
repo: repo.path,
|
||||
branch: 'main',
|
||||
event: 'api_trigger_event',
|
||||
config: createDevConfig({ repo: repo.path, event: 'api_trigger_event', branch: 'main' }),
|
||||
}
|
||||
set({ buildConfig: config })
|
||||
} catch (e) {
|
||||
toast.error('配置加载失败');
|
||||
console.error('配置加载失败', e);
|
||||
}
|
||||
finally {
|
||||
set({ loading: false })
|
||||
}
|
||||
|
||||
},
|
||||
buildWorkspace: async () => {
|
||||
const config = get().buildConfig;
|
||||
if (!config) {
|
||||
toast.error('请先保存构建配置');
|
||||
return;
|
||||
}
|
||||
const res = await cnb.build.startBuild(config.repo, {
|
||||
branch: config.branch,
|
||||
env: {},
|
||||
event: config.event,
|
||||
config: config.config,
|
||||
})
|
||||
if (res.code === 200) {
|
||||
toast.success('构建已触发')
|
||||
} else {
|
||||
toast.error(res.message || '构建触发失败')
|
||||
}
|
||||
},
|
||||
getItem: async (repo: string) => {
|
||||
const { setLoading } = get();
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await query.post({
|
||||
path: 'demo',
|
||||
key: 'item',
|
||||
data: { id }
|
||||
})
|
||||
const res = await cnb.repo.getRepo(repo)
|
||||
if (res.code === 200) {
|
||||
return res;
|
||||
const data = res.data!;
|
||||
set({ editRepo: data })
|
||||
} else {
|
||||
toast.error(res.message || '请求失败');
|
||||
}
|
||||
@@ -122,13 +263,19 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
getList: async (silent = false) => {
|
||||
getList: async (params?: { search?: string }, silent = false) => {
|
||||
const { setLoading } = get();
|
||||
if (!silent) {
|
||||
setLoading(true);
|
||||
}
|
||||
try {
|
||||
const res = await cnb.repo.getRepoList({})
|
||||
let opts = {}
|
||||
if (params?.search) {
|
||||
opts = {
|
||||
search: params.search
|
||||
}
|
||||
}
|
||||
const res = await cnb.repo.getRepoList(opts)
|
||||
if (res.code === 200) {
|
||||
const list = res.data! || []
|
||||
set({ list });
|
||||
@@ -144,15 +291,18 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
},
|
||||
updateRepoInfo: async (data) => {
|
||||
const repo = data.path!;
|
||||
const topics = data.topics?.split?.(',');
|
||||
let topics = data.topics?.split?.(',');
|
||||
if (Array.isArray(topics)) {
|
||||
topics = topics.map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
if (topics?.length === 0) {
|
||||
topics.push('')
|
||||
topics.push('cnb-center')
|
||||
}
|
||||
const updateData = {
|
||||
description: data.description,
|
||||
license: data?.license as any,
|
||||
site: data.site,
|
||||
topics: topics,
|
||||
topics: topics
|
||||
}
|
||||
const res = await cnb.repo.updateRepoInfo(repo, updateData)
|
||||
if (res.code === 200) {
|
||||
@@ -161,6 +311,16 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
toast.error(res.message || '更新失败');
|
||||
}
|
||||
},
|
||||
refresh: async (opts?: { message?: string, showTips?: boolean, search?: string }) => {
|
||||
const getList = get().getList({ search: opts?.search }, true);
|
||||
|
||||
|
||||
const getWorkspaceList = get().getWorkspaceList();
|
||||
await Promise.all([getList, getWorkspaceList]);
|
||||
if (opts?.showTips !== false) {
|
||||
toast.success(opts?.message || '刷新成功');
|
||||
}
|
||||
},
|
||||
createRepo: async (data) => {
|
||||
try {
|
||||
const createData = {
|
||||
@@ -189,7 +349,7 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
if (res.code === 200) {
|
||||
toast.success('删除成功');
|
||||
// 刷新列表
|
||||
await get().getList(true);
|
||||
await get().getList({}, true);
|
||||
} else {
|
||||
toast.error(res.message || '删除失败');
|
||||
}
|
||||
@@ -198,7 +358,7 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
if (e.message?.includes('JSON') || e.message?.includes('json')) {
|
||||
toast.success('删除成功');
|
||||
// 刷新列表
|
||||
await get().getList(true);
|
||||
await get().getList({}, true);
|
||||
} else {
|
||||
toast.error('删除失败');
|
||||
console.error('删除错误:', e);
|
||||
@@ -253,7 +413,6 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
}
|
||||
const res = await checkOpen()
|
||||
if (res.code === 300) {
|
||||
toast.success(`新创建了一个工作区,sn: ${res.data?.sn}`)
|
||||
if (params.open) {
|
||||
const loadingToastId = toast.loading('正在启动工作区...')
|
||||
const interval = setInterval(async () => {
|
||||
@@ -262,6 +421,7 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
clearInterval(interval)
|
||||
toast.dismiss(loadingToastId)
|
||||
toast.success(`工作区已启动,正在打开: ${check.data.url}`)
|
||||
get().refresh({ showTips: false })
|
||||
setTimeout(() => {
|
||||
window.open(check.data?.url, '_blank')
|
||||
}, 200)
|
||||
@@ -290,8 +450,8 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
}
|
||||
return res;
|
||||
},
|
||||
stopWorkspace: async () => {
|
||||
const sn = get().selectWorkspace?.sn;
|
||||
stopWorkspace: async (workspace?: WorkspaceInfo) => {
|
||||
const sn = workspace?.sn || get().selectWorkspace?.sn;
|
||||
if (!sn) {
|
||||
toast.error('未选择工作区');
|
||||
return;
|
||||
@@ -302,7 +462,7 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
toast.success('工作区已停止');
|
||||
// 停止成功后关闭弹窗
|
||||
set({ showWorkspaceDialog: false });
|
||||
get().getList(true)
|
||||
get().refresh({ showTips: false });
|
||||
} else {
|
||||
toast.error(res.message || '停止失败');
|
||||
}
|
||||
@@ -339,7 +499,24 @@ export const useRepoStore = create<State>((set, get) => {
|
||||
} else {
|
||||
toast.error(res.message || '同步提交失败')
|
||||
}
|
||||
},
|
||||
buildUpdate: async (data) => {
|
||||
const res = await cnb.build.startBuild(data.path!, {
|
||||
branch: 'main',
|
||||
env: {},
|
||||
event: 'api_trigger_event',
|
||||
config: createCommitBlankConfig({ repo: data.path!, event: 'api_trigger_event' }),
|
||||
})
|
||||
if (res.code === 200) {
|
||||
toast.success('更新成功')
|
||||
setTimeout(() => {
|
||||
get().refresh({ showTips: false })
|
||||
}, 5000)
|
||||
} else {
|
||||
toast.error(res.message || '更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as ConfigRouteImport } from './routes/config'
|
||||
import { Route as LoginRouteImport } from './routes/login'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as RepoIndexRouteImport } from './routes/repo/index'
|
||||
import { Route as ConfigIndexRouteImport } from './routes/config/index'
|
||||
import { Route as ConfigGiteaRouteImport } from './routes/config/gitea'
|
||||
|
||||
const ConfigRoute = ConfigRouteImport.update({
|
||||
id: '/config',
|
||||
path: '/config',
|
||||
const LoginRoute = LoginRouteImport.update({
|
||||
id: '/login',
|
||||
path: '/login',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
@@ -22,40 +25,67 @@ const IndexRoute = IndexRouteImport.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const RepoIndexRoute = RepoIndexRouteImport.update({
|
||||
id: '/repo/',
|
||||
path: '/repo/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ConfigIndexRoute = ConfigIndexRouteImport.update({
|
||||
id: '/config/',
|
||||
path: '/config/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const ConfigGiteaRoute = ConfigGiteaRouteImport.update({
|
||||
id: '/config/gitea',
|
||||
path: '/config/gitea',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/config': typeof ConfigRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/config/gitea': typeof ConfigGiteaRoute
|
||||
'/config/': typeof ConfigIndexRoute
|
||||
'/repo/': typeof RepoIndexRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/config': typeof ConfigRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/config/gitea': typeof ConfigGiteaRoute
|
||||
'/config': typeof ConfigIndexRoute
|
||||
'/repo': typeof RepoIndexRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/config': typeof ConfigRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/config/gitea': typeof ConfigGiteaRoute
|
||||
'/config/': typeof ConfigIndexRoute
|
||||
'/repo/': typeof RepoIndexRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/config'
|
||||
fullPaths: '/' | '/login' | '/config/gitea' | '/config/' | '/repo/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/config'
|
||||
id: '__root__' | '/' | '/config'
|
||||
to: '/' | '/login' | '/config/gitea' | '/config' | '/repo'
|
||||
id: '__root__' | '/' | '/login' | '/config/gitea' | '/config/' | '/repo/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
ConfigRoute: typeof ConfigRoute
|
||||
LoginRoute: typeof LoginRoute
|
||||
ConfigGiteaRoute: typeof ConfigGiteaRoute
|
||||
ConfigIndexRoute: typeof ConfigIndexRoute
|
||||
RepoIndexRoute: typeof RepoIndexRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/config': {
|
||||
id: '/config'
|
||||
path: '/config'
|
||||
fullPath: '/config'
|
||||
preLoaderRoute: typeof ConfigRouteImport
|
||||
'/login': {
|
||||
id: '/login'
|
||||
path: '/login'
|
||||
fullPath: '/login'
|
||||
preLoaderRoute: typeof LoginRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
@@ -65,12 +95,36 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/repo/': {
|
||||
id: '/repo/'
|
||||
path: '/repo'
|
||||
fullPath: '/repo/'
|
||||
preLoaderRoute: typeof RepoIndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/config/': {
|
||||
id: '/config/'
|
||||
path: '/config'
|
||||
fullPath: '/config/'
|
||||
preLoaderRoute: typeof ConfigIndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/config/gitea': {
|
||||
id: '/config/gitea'
|
||||
path: '/config/gitea'
|
||||
fullPath: '/config/gitea'
|
||||
preLoaderRoute: typeof ConfigGiteaRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
ConfigRoute: ConfigRoute,
|
||||
LoginRoute: LoginRoute,
|
||||
ConfigGiteaRoute: ConfigGiteaRoute,
|
||||
ConfigIndexRoute: ConfigIndexRoute,
|
||||
RepoIndexRoute: RepoIndexRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
|
||||
@@ -1,47 +1,27 @@
|
||||
import { Link, Outlet, createRootRoute, useNavigate } from '@tanstack/react-router'
|
||||
import { LayoutMain } from '@/pages/auth/modules/BaseHeader';
|
||||
import { Outlet, createRootRoute } from '@tanstack/react-router'
|
||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
import { useConfigStore } from '@/app/config/store'
|
||||
import { useEffect } from 'react'
|
||||
import { AuthProvider } from '@/pages/auth'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
export const Route = createRootRoute({
|
||||
component: RootComponent,
|
||||
})
|
||||
|
||||
|
||||
function RootComponent() {
|
||||
const navigate = useNavigate()
|
||||
useEffect(() => {
|
||||
const config = useConfigStore.getState().config;
|
||||
if (!config.CNB_API_KEY) {
|
||||
navigate({
|
||||
to: '/config'
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className='h-full overflow-hidden'>
|
||||
|
||||
<div className="p-2 flex gap-2 text-lg">
|
||||
<Link
|
||||
to="/"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
activeOptions={{ exact: true }}
|
||||
>
|
||||
仓库列表
|
||||
</Link>
|
||||
<Link to='/config'>
|
||||
配置项
|
||||
</Link>
|
||||
</div>
|
||||
<hr />
|
||||
<main className='h-[calc(100%-4rem)] overflow-auto scrollbar'>
|
||||
|
||||
<Outlet />
|
||||
</main>
|
||||
<LayoutMain />
|
||||
<AuthProvider mustLogin={false}>
|
||||
<TooltipProvider>
|
||||
<main className='h-[calc(100%-3rem)] overflow-auto scrollbar'>
|
||||
<Outlet />
|
||||
</main>
|
||||
</TooltipProvider>
|
||||
</AuthProvider>
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
<Toaster />
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
9
src/routes/config/gitea.tsx
Normal file
9
src/routes/config/gitea.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import App from '@/pages/config/gitea/page'
|
||||
export const Route = createFileRoute('/config/gitea')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <App />
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import Home from '@/app/config/page'
|
||||
export const Route = createFileRoute('/config')({
|
||||
import App from '@/pages/config/page'
|
||||
export const Route = createFileRoute('/config/')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <Home />
|
||||
return <App />
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import App from '@/app/page'
|
||||
import App from '@/pages/page'
|
||||
export const Route = createFileRoute('/')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
9
src/routes/login.tsx
Normal file
9
src/routes/login.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import App from '@/pages/auth/page'
|
||||
export const Route = createFileRoute('/login')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <App />
|
||||
}
|
||||
9
src/routes/repo/index.tsx
Normal file
9
src/routes/repo/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import App from '@/pages/repos/repo/page'
|
||||
export const Route = createFileRoute('/repo/')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <App />
|
||||
}
|
||||
Reference in New Issue
Block a user