feat ui: add tooltip
This commit is contained in:
@@ -441,6 +441,8 @@ If you find this project helpful, please consider sponsoring its development. Yo
|
|||||||
|
|
||||||
[](https://ko-fi.com/F1F31GN2GM)
|
[](https://ko-fi.com/F1F31GN2GM)
|
||||||
|
|
||||||
|
[Paypal](https://paypal.me/musistudio1999)
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="/blog/images/alipay.jpg" width="200" alt="Alipay" /></td>
|
<td><img src="/blog/images/alipay.jpg" width="200" alt="Alipay" /></td>
|
||||||
|
|||||||
@@ -436,6 +436,8 @@ jobs:
|
|||||||
|
|
||||||
[](https://ko-fi.com/F1F31GN2GM)
|
[](https://ko-fi.com/F1F31GN2GM)
|
||||||
|
|
||||||
|
[Paypal](https://paypal.me/musistudio1999)
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="/blog/images/alipay.jpg" width="200" alt="Alipay" /></td>
|
<td><img src="/blog/images/alipay.jpg" width="200" alt="Alipay" /></td>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/static": "^8.2.0",
|
"@fastify/static": "^8.2.0",
|
||||||
"@musistudio/llms": "^1.0.18",
|
"@musistudio/llms": "^1.0.18",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"openurl": "^1.1.1",
|
"openurl": "^1.1.1",
|
||||||
|
|||||||
395
pnpm-lock.yaml
generated
395
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@musistudio/llms':
|
'@musistudio/llms':
|
||||||
specifier: ^1.0.18
|
specifier: ^1.0.18
|
||||||
version: 1.0.18(ws@8.18.3)(zod@3.25.67)
|
version: 1.0.18(ws@8.18.3)(zod@3.25.67)
|
||||||
|
'@radix-ui/react-tooltip':
|
||||||
|
specifier: ^1.2.7
|
||||||
|
version: 1.2.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^16.4.7
|
specifier: ^16.4.7
|
||||||
version: 16.6.1
|
version: 16.6.1
|
||||||
@@ -232,6 +235,21 @@ packages:
|
|||||||
'@fastify/static@8.2.0':
|
'@fastify/static@8.2.0':
|
||||||
resolution: {integrity: sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==}
|
resolution: {integrity: sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==}
|
||||||
|
|
||||||
|
'@floating-ui/core@1.7.3':
|
||||||
|
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
|
||||||
|
|
||||||
|
'@floating-ui/dom@1.7.3':
|
||||||
|
resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==}
|
||||||
|
|
||||||
|
'@floating-ui/react-dom@2.1.5':
|
||||||
|
resolution: {integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
|
'@floating-ui/utils@0.2.10':
|
||||||
|
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||||
|
|
||||||
'@google/genai@1.8.0':
|
'@google/genai@1.8.0':
|
||||||
resolution: {integrity: sha512-n3KiMFesQCy2R9iSdBIuJ0JWYQ1HZBJJkmt4PPZMGZKvlgHhBAGw1kUMyX+vsAIzprN3lK45DI755lm70wPOOg==}
|
resolution: {integrity: sha512-n3KiMFesQCy2R9iSdBIuJ0JWYQ1HZBJJkmt4PPZMGZKvlgHhBAGw1kUMyX+vsAIzprN3lK45DI755lm70wPOOg==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
@@ -272,6 +290,215 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.2':
|
||||||
|
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||||
|
|
||||||
|
'@radix-ui/react-arrow@1.1.7':
|
||||||
|
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2':
|
||||||
|
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2':
|
||||||
|
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.10':
|
||||||
|
resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1':
|
||||||
|
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.7':
|
||||||
|
resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9':
|
||||||
|
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.4':
|
||||||
|
resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3':
|
||||||
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3':
|
||||||
|
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-tooltip@1.2.7':
|
||||||
|
resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||||
|
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2':
|
||||||
|
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2':
|
||||||
|
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||||
|
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-rect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-size@1.1.1':
|
||||||
|
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-visually-hidden@1.2.3':
|
||||||
|
resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/rect@1.1.1':
|
||||||
|
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||||
|
|
||||||
'@types/node@24.0.15':
|
'@types/node@24.0.15':
|
||||||
resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==}
|
resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==}
|
||||||
|
|
||||||
@@ -729,6 +956,15 @@ packages:
|
|||||||
quick-format-unescaped@4.0.4:
|
quick-format-unescaped@4.0.4:
|
||||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||||
|
|
||||||
|
react-dom@19.1.1:
|
||||||
|
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^19.1.1
|
||||||
|
|
||||||
|
react@19.1.1:
|
||||||
|
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
real-require@0.2.0:
|
real-require@0.2.0:
|
||||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||||
engines: {node: '>= 12.13.0'}
|
engines: {node: '>= 12.13.0'}
|
||||||
@@ -770,6 +1006,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
scheduler@0.26.0:
|
||||||
|
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
||||||
|
|
||||||
secure-json-parse@4.0.0:
|
secure-json-parse@4.0.0:
|
||||||
resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==}
|
resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==}
|
||||||
|
|
||||||
@@ -1073,6 +1312,23 @@ snapshots:
|
|||||||
fastq: 1.19.1
|
fastq: 1.19.1
|
||||||
glob: 11.0.3
|
glob: 11.0.3
|
||||||
|
|
||||||
|
'@floating-ui/core@1.7.3':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/utils': 0.2.10
|
||||||
|
|
||||||
|
'@floating-ui/dom@1.7.3':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/core': 1.7.3
|
||||||
|
'@floating-ui/utils': 0.2.10
|
||||||
|
|
||||||
|
'@floating-ui/react-dom@2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/dom': 1.7.3
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@floating-ui/utils@0.2.10': {}
|
||||||
|
|
||||||
'@google/genai@1.8.0':
|
'@google/genai@1.8.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
google-auth-library: 9.15.1
|
google-auth-library: 9.15.1
|
||||||
@@ -1136,6 +1392,136 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.19.1
|
fastq: 1.19.1
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.2': {}
|
||||||
|
|
||||||
|
'@radix-ui/react-arrow@1.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.2(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.2(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.2
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-escape-keydown': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.1(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-popper@1.2.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@floating-ui/react-dom': 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-arrow': 1.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-rect': 1.1.1(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(react@19.1.1)
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.2.3(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.2.3(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-tooltip@1.2.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.2
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-context': 1.1.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-id': 1.1.1(react@19.1.1)
|
||||||
|
'@radix-ui/react-popper': 1.2.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-presence': 1.1.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-visually-hidden': 1.2.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.1(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.2(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.2(react@19.1.1)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.2(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-use-escape-keydown@1.1.1(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.1(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-use-rect@1.1.1(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/rect': 1.1.1
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-use-size@1.1.1(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
|
||||||
|
'@radix-ui/react-visually-hidden@1.2.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
|
react: 19.1.1
|
||||||
|
react-dom: 19.1.1(react@19.1.1)
|
||||||
|
|
||||||
|
'@radix-ui/rect@1.1.1': {}
|
||||||
|
|
||||||
'@types/node@24.0.15':
|
'@types/node@24.0.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.8.0
|
undici-types: 7.8.0
|
||||||
@@ -1633,6 +2019,13 @@ snapshots:
|
|||||||
|
|
||||||
quick-format-unescaped@4.0.4: {}
|
quick-format-unescaped@4.0.4: {}
|
||||||
|
|
||||||
|
react-dom@19.1.1(react@19.1.1):
|
||||||
|
dependencies:
|
||||||
|
react: 19.1.1
|
||||||
|
scheduler: 0.26.0
|
||||||
|
|
||||||
|
react@19.1.1: {}
|
||||||
|
|
||||||
real-require@0.2.0: {}
|
real-require@0.2.0: {}
|
||||||
|
|
||||||
rechoir@0.6.2:
|
rechoir@0.6.2:
|
||||||
@@ -1665,6 +2058,8 @@ snapshots:
|
|||||||
|
|
||||||
safe-stable-stringify@2.5.0: {}
|
safe-stable-stringify@2.5.0: {}
|
||||||
|
|
||||||
|
scheduler@0.26.0: {}
|
||||||
|
|
||||||
secure-json-parse@4.0.0: {}
|
secure-json-parse@4.0.0: {}
|
||||||
|
|
||||||
semver@5.7.2: {}
|
semver@5.7.2: {}
|
||||||
|
|||||||
77
src/cli.ts
77
src/cli.ts
@@ -2,12 +2,16 @@
|
|||||||
import { run } from "./index";
|
import { run } from "./index";
|
||||||
import { showStatus } from "./utils/status";
|
import { showStatus } from "./utils/status";
|
||||||
import { executeCodeCommand } from "./utils/codeCommand";
|
import { executeCodeCommand } from "./utils/codeCommand";
|
||||||
import { cleanupPidFile, isServiceRunning, getServiceInfo } from "./utils/processCheck";
|
import {
|
||||||
|
cleanupPidFile,
|
||||||
|
isServiceRunning,
|
||||||
|
getServiceInfo,
|
||||||
|
} from "./utils/processCheck";
|
||||||
import { version } from "../package.json";
|
import { version } from "../package.json";
|
||||||
import { spawn, exec } from "child_process";
|
import { spawn, exec } from "child_process";
|
||||||
import { PID_FILE, REFERENCE_COUNT_FILE } from "./constants";
|
import { PID_FILE, REFERENCE_COUNT_FILE } from "./constants";
|
||||||
import fs, { existsSync, readFileSync } from "fs";
|
import fs, { existsSync, readFileSync } from "fs";
|
||||||
import {join} from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
const command = process.argv[2];
|
const command = process.argv[2];
|
||||||
|
|
||||||
@@ -108,7 +112,9 @@ async function main() {
|
|||||||
startProcess.unref();
|
startProcess.unref();
|
||||||
|
|
||||||
if (await waitForService()) {
|
if (await waitForService()) {
|
||||||
executeCodeCommand(process.argv.slice(3));
|
// Join all code arguments into a single string to preserve spaces within quotes
|
||||||
|
const codeArgs = process.argv.slice(3);
|
||||||
|
executeCodeCommand(codeArgs);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Service startup timeout, please manually run `ccr start` to start the service"
|
"Service startup timeout, please manually run `ccr start` to start the service"
|
||||||
@@ -116,7 +122,9 @@ async function main() {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
executeCodeCommand(process.argv.slice(3));
|
// Join all code arguments into a single string to preserve spaces within quotes
|
||||||
|
const codeArgs = process.argv.slice(3);
|
||||||
|
executeCodeCommand(codeArgs);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "ui":
|
case "ui":
|
||||||
@@ -138,49 +146,68 @@ async function main() {
|
|||||||
|
|
||||||
if (!(await waitForService())) {
|
if (!(await waitForService())) {
|
||||||
// If service startup fails, try to start with default config
|
// If service startup fails, try to start with default config
|
||||||
console.log("Service startup timeout, trying to start with default configuration...");
|
console.log(
|
||||||
const { initDir, writeConfigFile, backupConfigFile } = require("./utils");
|
"Service startup timeout, trying to start with default configuration..."
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
initDir,
|
||||||
|
writeConfigFile,
|
||||||
|
backupConfigFile,
|
||||||
|
} = require("./utils");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize directories
|
// Initialize directories
|
||||||
await initDir();
|
await initDir();
|
||||||
|
|
||||||
// Backup existing config file if it exists
|
// Backup existing config file if it exists
|
||||||
const backupPath = await backupConfigFile();
|
const backupPath = await backupConfigFile();
|
||||||
if (backupPath) {
|
if (backupPath) {
|
||||||
console.log(`Backed up existing configuration file to ${backupPath}`);
|
console.log(
|
||||||
|
`Backed up existing configuration file to ${backupPath}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a minimal default config file
|
// Create a minimal default config file
|
||||||
await writeConfigFile({
|
await writeConfigFile({
|
||||||
"PORT": 3456,
|
PORT: 3456,
|
||||||
"Providers": [],
|
Providers: [],
|
||||||
"Router": {}
|
Router: {},
|
||||||
});
|
});
|
||||||
console.log("Created minimal default configuration file at ~/.claude-code-router/config.json");
|
console.log(
|
||||||
console.log("Please edit this file with your actual configuration.");
|
"Created minimal default configuration file at ~/.claude-code-router/config.json"
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"Please edit this file with your actual configuration."
|
||||||
|
);
|
||||||
|
|
||||||
// Try starting the service again
|
// Try starting the service again
|
||||||
const restartProcess = spawn("node", [cliPath, "start"], {
|
const restartProcess = spawn("node", [cliPath, "start"], {
|
||||||
detached: true,
|
detached: true,
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
});
|
});
|
||||||
|
|
||||||
restartProcess.on("error", (error) => {
|
restartProcess.on("error", (error) => {
|
||||||
console.error("Failed to start service with default config:", error.message);
|
console.error(
|
||||||
|
"Failed to start service with default config:",
|
||||||
|
error.message
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
restartProcess.unref();
|
restartProcess.unref();
|
||||||
|
|
||||||
if (!(await waitForService(15000))) { // Wait a bit longer for the first start
|
if (!(await waitForService(15000))) {
|
||||||
|
// Wait a bit longer for the first start
|
||||||
console.error(
|
console.error(
|
||||||
"Service startup still failing. Please manually run `ccr start` to start the service and check the logs."
|
"Service startup still failing. Please manually run `ccr start` to start the service and check the logs."
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Failed to create default configuration:", error.message);
|
console.error(
|
||||||
|
"Failed to create default configuration:",
|
||||||
|
error.message
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,11 +217,11 @@ async function main() {
|
|||||||
const serviceInfo = await getServiceInfo();
|
const serviceInfo = await getServiceInfo();
|
||||||
const uiUrl = `${serviceInfo.endpoint}/ui/`;
|
const uiUrl = `${serviceInfo.endpoint}/ui/`;
|
||||||
console.log(`Opening UI at ${uiUrl}`);
|
console.log(`Opening UI at ${uiUrl}`);
|
||||||
|
|
||||||
// Open URL in browser based on platform
|
// Open URL in browser based on platform
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
let openCommand = "";
|
let openCommand = "";
|
||||||
|
|
||||||
if (platform === "win32") {
|
if (platform === "win32") {
|
||||||
// Windows
|
// Windows
|
||||||
openCommand = `start ${uiUrl}`;
|
openCommand = `start ${uiUrl}`;
|
||||||
@@ -208,7 +235,7 @@ async function main() {
|
|||||||
console.error("Unsupported platform for opening browser");
|
console.error("Unsupported platform for opening browser");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
exec(openCommand, (error) => {
|
exec(openCommand, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Failed to open browser:", error.message);
|
console.error("Failed to open browser:", error.message);
|
||||||
|
|||||||
212
ui/src/App.tsx
212
ui/src/App.tsx
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { SettingsDialog } from "@/components/SettingsDialog";
|
import { SettingsDialog } from "@/components/SettingsDialog";
|
||||||
@@ -9,30 +9,23 @@ import { JsonEditor } from "@/components/JsonEditor";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useConfig } from "@/components/ConfigProvider";
|
import { useConfig } from "@/components/ConfigProvider";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import { Settings, Languages, Save, RefreshCw, FileJson, Upload, Download } from "lucide-react";
|
import { Settings, Languages, Save, RefreshCw, FileJson } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
import { Toast } from "@/components/ui/toast";
|
import { Toast } from "@/components/ui/toast";
|
||||||
import "@/styles/animations.css";
|
import "@/styles/animations.css";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { config, setConfig, error } = useConfig();
|
const { config, error } = useConfig();
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
const [isJsonEditorOpen, setIsJsonEditorOpen] = useState(false);
|
const [isJsonEditorOpen, setIsJsonEditorOpen] = useState(false);
|
||||||
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
||||||
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null);
|
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' | 'warning' } | null>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
@@ -160,43 +153,6 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportConfig = () => {
|
|
||||||
if (!config) return;
|
|
||||||
|
|
||||||
const configString = JSON.stringify(config, null, 2);
|
|
||||||
const blob = new Blob([configString], { type: "application/json" });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = url;
|
|
||||||
a.download = "claude-code-router-config.json";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
const importConfig = () => {
|
|
||||||
fileInputRef.current?.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (event) => {
|
|
||||||
try {
|
|
||||||
const configString = event.target?.result as string;
|
|
||||||
const importedConfig = JSON.parse(configString);
|
|
||||||
setConfig(importedConfig);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to parse config file:", error);
|
|
||||||
setToast({ message: t('settings.import_error'), type: 'error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (isCheckingAuth) {
|
if (isCheckingAuth) {
|
||||||
return (
|
return (
|
||||||
@@ -224,93 +180,51 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<div className="h-screen bg-gray-50 font-sans">
|
||||||
<div className="h-screen bg-gray-50 font-sans">
|
<header className="flex h-16 items-center justify-between border-b bg-white px-6">
|
||||||
<header className="flex h-16 items-center justify-between border-b bg-white px-6">
|
<h1 className="text-xl font-semibold text-gray-800">{t('app.title')}</h1>
|
||||||
<h1 className="text-xl font-semibold text-gray-800">{t('app.title')}</h1>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<Button variant="ghost" size="icon" onClick={() => setIsSettingsOpen(true)} className="transition-all-ease hover:scale-110">
|
||||||
<Tooltip>
|
<Settings className="h-5 w-5" />
|
||||||
<TooltipTrigger asChild>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" onClick={importConfig} className="transition-all-ease hover:scale-110">
|
<Button variant="ghost" size="icon" onClick={() => setIsJsonEditorOpen(true)} className="transition-all-ease hover:scale-110">
|
||||||
<Download className="h-5 w-5" />
|
<FileJson className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="ghost" size="icon" className="transition-all-ease hover:scale-110">
|
||||||
|
<Languages className="h-5 w-5" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-32 p-2">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Button
|
||||||
|
variant={i18n.language.startsWith('en') ? 'default' : 'ghost'}
|
||||||
|
className="w-full justify-start transition-all-ease hover:scale-[1.02]"
|
||||||
|
onClick={() => i18n.changeLanguage('en')}
|
||||||
|
>
|
||||||
|
English
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
<Button
|
||||||
<TooltipContent>
|
variant={i18n.language.startsWith('zh') ? 'default' : 'ghost'}
|
||||||
<p>{t('app.import_config')}</p>
|
className="w-full justify-start transition-all-ease hover:scale-[1.02]"
|
||||||
</TooltipContent>
|
onClick={() => i18n.changeLanguage('zh')}
|
||||||
</Tooltip>
|
>
|
||||||
<Tooltip>
|
中文
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon" onClick={exportConfig} className="transition-all-ease hover:scale-110">
|
|
||||||
<Upload className="h-5 w-5" />
|
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</div>
|
||||||
<TooltipContent>
|
</PopoverContent>
|
||||||
<p>{t('app.export_config')}</p>
|
</Popover>
|
||||||
</TooltipContent>
|
<Button onClick={saveConfig} variant="outline" className="transition-all-ease hover:scale-[1.02] active:scale-[0.98]">
|
||||||
</Tooltip>
|
<Save className="mr-2 h-4 w-4" />
|
||||||
<Tooltip>
|
{t('app.save')}
|
||||||
<TooltipTrigger asChild>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" onClick={() => setIsSettingsOpen(true)} className="transition-all-ease hover:scale-110">
|
<Button onClick={saveConfigAndRestart} className="transition-all-ease hover:scale-[1.02] active:scale-[0.98]">
|
||||||
<Settings className="h-5 w-5" />
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{t('app.save_and_restart')}
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent>
|
</div>
|
||||||
<p>{t('app.settings')}</p>
|
</header>
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon" onClick={() => setIsJsonEditorOpen(true)} className="transition-all-ease hover:scale-110">
|
|
||||||
<FileJson className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t('json_editor.title')}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Popover>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon" className="transition-all-ease hover:scale-110">
|
|
||||||
<Languages className="h-5 w-5" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t('app.change_language')}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<PopoverContent className="w-32 p-2">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Button
|
|
||||||
variant={i18n.language.startsWith('en') ? 'default' : 'ghost'}
|
|
||||||
className="w-full justify-start transition-all-ease hover:scale-[1.02]"
|
|
||||||
onClick={() => i18n.changeLanguage('en')}
|
|
||||||
>
|
|
||||||
English
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant={i18n.language.startsWith('zh') ? 'default' : 'ghost'}
|
|
||||||
className="w-full justify-start transition-all-ease hover:scale-[1.02]"
|
|
||||||
onClick={() => i18n.changeLanguage('zh')}
|
|
||||||
>
|
|
||||||
中文
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<Button onClick={saveConfig} variant="outline" className="transition-all-ease hover:scale-[1.02] active:scale-[0.98]">
|
|
||||||
<Save className="mr-2 h-4 w-4" />
|
|
||||||
{t('app.save')}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={saveConfigAndRestart} className="transition-all-ease hover:scale-[1.02] active:scale-[0.98]">
|
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
|
||||||
{t('app.save_and_restart')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex h-[calc(100vh-4rem)] gap-4 p-4">
|
<main className="flex h-[calc(100vh-4rem)] gap-4 p-4">
|
||||||
<div className="w-3/5">
|
<div className="w-3/5">
|
||||||
<Providers />
|
<Providers />
|
||||||
@@ -324,28 +238,20 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<SettingsDialog isOpen={isSettingsOpen} onOpenChange={setIsSettingsOpen} />
|
<SettingsDialog isOpen={isSettingsOpen} onOpenChange={setIsSettingsOpen} />
|
||||||
<JsonEditor
|
<JsonEditor
|
||||||
open={isJsonEditorOpen}
|
open={isJsonEditorOpen}
|
||||||
onOpenChange={setIsJsonEditorOpen}
|
onOpenChange={setIsJsonEditorOpen}
|
||||||
showToast={(message, type) => setToast({ message, type })}
|
showToast={(message, type) => setToast({ message, type })}
|
||||||
|
/>
|
||||||
|
{toast && (
|
||||||
|
<Toast
|
||||||
|
message={toast.message}
|
||||||
|
type={toast.type}
|
||||||
|
onClose={() => setToast(null)}
|
||||||
/>
|
/>
|
||||||
{toast && (
|
)}
|
||||||
<Toast
|
</div>
|
||||||
message={toast.message}
|
|
||||||
type={toast.type}
|
|
||||||
onClose={() => setToast(null)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
ref={fileInputRef}
|
|
||||||
onChange={handleFileChange}
|
|
||||||
accept=".json"
|
|
||||||
className="hidden"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
|
||||||
|
|
||||||
const Tooltip = TooltipPrimitive.Root
|
|
||||||
|
|
||||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
||||||
|
|
||||||
const TooltipContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
||||||
<TooltipPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
||||||
@@ -12,10 +12,7 @@
|
|||||||
"config_saved_success": "Config saved successfully",
|
"config_saved_success": "Config saved successfully",
|
||||||
"config_saved_failed": "Failed to save config",
|
"config_saved_failed": "Failed to save config",
|
||||||
"config_saved_restart_success": "Config saved and service restarted successfully",
|
"config_saved_restart_success": "Config saved and service restarted successfully",
|
||||||
"config_saved_restart_failed": "Failed to save config and restart service",
|
"config_saved_restart_failed": "Failed to save config and restart service"
|
||||||
"import_config": "Import Config",
|
|
||||||
"export_config": "Export Config",
|
|
||||||
"change_language": "Change Language"
|
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"title": "Sign in to your account",
|
"title": "Sign in to your account",
|
||||||
|
|||||||
@@ -12,10 +12,7 @@
|
|||||||
"config_saved_success": "配置保存成功",
|
"config_saved_success": "配置保存成功",
|
||||||
"config_saved_failed": "配置保存失败",
|
"config_saved_failed": "配置保存失败",
|
||||||
"config_saved_restart_success": "配置保存并服务重启成功",
|
"config_saved_restart_success": "配置保存并服务重启成功",
|
||||||
"config_saved_restart_failed": "配置保存并服务重启失败",
|
"config_saved_restart_failed": "配置保存并服务重启失败"
|
||||||
"import_config": "导入配置",
|
|
||||||
"export_config": "导出配置",
|
|
||||||
"change_language": "切换语言"
|
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"title": "登录到您的账户",
|
"title": "登录到您的账户",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/components/ui/tooltip.tsx","./src/lib/api.ts","./src/lib/utils.ts"],"version":"5.8.3"}
|
{"root":["./src/app.tsx","./src/i18n.ts","./src/main.tsx","./src/routes.tsx","./src/types.ts","./src/vite-env.d.ts","./src/components/configprovider.tsx","./src/components/jsoneditor.tsx","./src/components/login.tsx","./src/components/protectedroute.tsx","./src/components/providerlist.tsx","./src/components/providers.tsx","./src/components/publicroute.tsx","./src/components/router.tsx","./src/components/settingsdialog.tsx","./src/components/transformerlist.tsx","./src/components/transformers.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/combo-input.tsx","./src/components/ui/combobox.tsx","./src/components/ui/command.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/multi-combobox.tsx","./src/components/ui/popover.tsx","./src/components/ui/switch.tsx","./src/components/ui/toast.tsx","./src/lib/api.ts","./src/lib/utils.ts"],"version":"5.8.3"}
|
||||||
Reference in New Issue
Block a user