feat: add Zhipu icon, custom scrollbar styles, and SVGR support
- Added a new SVG icon for Zhipu in the icons provider directory. - Implemented custom scrollbar styles for better UI experience. - Updated Vite configuration to include SVGR plugin for importing SVGs as React components. - Added SVGR type definitions to Vite environment declarations.
@@ -50,6 +50,7 @@
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
"vitest": "^4.0.16"
|
||||
}
|
||||
},
|
||||
@@ -97,6 +98,7 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -2137,6 +2139,49 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/pluginutils/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.53.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
|
||||
@@ -2452,6 +2497,232 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
||||
"integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz",
|
||||
"integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz",
|
||||
"integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz",
|
||||
"integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-svg-dynamic-title": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz",
|
||||
"integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-svg-em-dimensions": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz",
|
||||
"integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-transform-react-native-svg": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz",
|
||||
"integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-plugin-transform-svg-component": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz",
|
||||
"integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/babel-preset": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz",
|
||||
"integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgr/babel-plugin-add-jsx-attribute": "8.0.0",
|
||||
"@svgr/babel-plugin-remove-jsx-attribute": "8.0.0",
|
||||
"@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0",
|
||||
"@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0",
|
||||
"@svgr/babel-plugin-svg-dynamic-title": "8.0.0",
|
||||
"@svgr/babel-plugin-svg-em-dimensions": "8.0.0",
|
||||
"@svgr/babel-plugin-transform-react-native-svg": "8.1.0",
|
||||
"@svgr/babel-plugin-transform-svg-component": "8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/core": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
|
||||
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@svgr/babel-preset": "8.1.0",
|
||||
"camelcase": "^6.2.0",
|
||||
"cosmiconfig": "^8.1.3",
|
||||
"snake-case": "^3.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/hast-util-to-babel-ast": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
|
||||
"integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.21.3",
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
}
|
||||
},
|
||||
"node_modules/@svgr/plugin-jsx": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
|
||||
"integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.21.3",
|
||||
"@svgr/babel-preset": "8.1.0",
|
||||
"@svgr/hast-util-to-babel-ast": "8.0.0",
|
||||
"svg-parser": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/gregberge"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@svgr/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz",
|
||||
@@ -2780,6 +3051,7 @@
|
||||
"integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -2797,6 +3069,7 @@
|
||||
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -2808,6 +3081,7 @@
|
||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
@@ -2847,6 +3121,7 @@
|
||||
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.49.0",
|
||||
"@typescript-eslint/types": "8.49.0",
|
||||
@@ -3169,6 +3444,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -3387,6 +3663,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -3411,6 +3688,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
@@ -3572,6 +3862,33 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "8.3.6",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.2.0",
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/d-fischer"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.9.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -3652,6 +3969,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dot-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
|
||||
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.267",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||
@@ -3659,6 +3987,29 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
@@ -3734,6 +4085,7 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -4293,6 +4645,13 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -4368,6 +4727,7 @@
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
@@ -4411,6 +4771,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -4517,6 +4884,16 @@
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lower-case": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
@@ -4631,6 +5008,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lower-case": "^2.0.2",
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
@@ -4752,6 +5140,25 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"lines-and-columns": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -4779,6 +5186,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
@@ -4846,6 +5263,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -5068,6 +5486,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -5080,6 +5499,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -5347,6 +5767,17 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/snake-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
"integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dot-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/sonner": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
||||
@@ -5443,6 +5874,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-parser": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
|
||||
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
|
||||
@@ -5572,6 +6010,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -5647,6 +6086,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -5759,6 +6199,7 @@
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
@@ -5813,12 +6254,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-svgr": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz",
|
||||
"integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.2.0",
|
||||
"@svgr/core": "^8.1.0",
|
||||
"@svgr/plugin-jsx": "^8.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": ">=2.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz",
|
||||
"integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.0.16",
|
||||
"@vitest/mocker": "4.0.16",
|
||||
@@ -6375,6 +6832,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6388,6 +6846,7 @@
|
||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
"vitest": "^4.0.16"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AlertTriangle, X } from "lucide-react";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
import { Modal } from "./Modal";
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
isOpen: boolean;
|
||||
@@ -21,8 +22,6 @@ export function ConfirmDialog({
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: ConfirmDialogProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const variantStyles = {
|
||||
danger: {
|
||||
icon: "text-red-500",
|
||||
@@ -41,22 +40,19 @@ export function ConfirmDialog({
|
||||
const styles = variantStyles[variant];
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="w-full max-w-sm rounded-lg bg-background p-6 shadow-xl">
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onCancel}
|
||||
maxWidth="max-w-sm"
|
||||
showCloseButton={false}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`mt-0.5 ${styles.icon}`}>
|
||||
<AlertTriangle className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="rounded-lg p-1 hover:bg-muted"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold">{title}</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,6 +71,6 @@ export function ConfirmDialog({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { useEffect, type ReactNode, type MouseEvent } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
/** Modal 内容区的额外 className */
|
||||
className?: string;
|
||||
/** 是否显示关闭按钮,默认 true */
|
||||
showCloseButton?: boolean;
|
||||
/** 点击遮罩是否关闭,默认 true */
|
||||
closeOnOverlayClick?: boolean;
|
||||
/** 内容区最大宽度,默认 max-w-lg */
|
||||
maxWidth?: string;
|
||||
}
|
||||
|
||||
export function Modal({
|
||||
isOpen,
|
||||
onClose,
|
||||
children,
|
||||
className = "",
|
||||
showCloseButton = true,
|
||||
closeOnOverlayClick = true,
|
||||
maxWidth = "max-w-lg",
|
||||
}: ModalProps) {
|
||||
// ESC 键关闭
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const handleEsc = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleEsc);
|
||||
return () => document.removeEventListener("keydown", handleEsc);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
// 阻止背景滚动
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleOverlayClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
<div
|
||||
className={`relative w-full ${maxWidth} rounded-lg bg-background shadow-xl ${className}`}
|
||||
>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute right-4 top-4 rounded-lg p-1 hover:bg-muted"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
/** Modal 标题区 */
|
||||
export function ModalHeader({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`border-b px-6 py-4 ${className}`}>
|
||||
<h2 className="text-lg font-semibold">{children}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Modal 内容区 */
|
||||
export function ModalBody({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={`p-6 ${className}`}>{children}</div>;
|
||||
}
|
||||
|
||||
/** Modal 底部操作区 */
|
||||
export function ModalFooter({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`flex justify-end gap-2 border-t px-6 py-4 ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
FileText,
|
||||
Package,
|
||||
} from "lucide-react";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import { Config, configApi, ImportResult } from "@/lib/api/config";
|
||||
|
||||
interface ImportExportProps {
|
||||
@@ -314,220 +315,226 @@ export function ImportExport({ config, onConfigImported }: ImportExportProps) {
|
||||
</div>
|
||||
|
||||
{/* Security Warning Dialog */}
|
||||
{showSecurityWarning && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="w-full max-w-md rounded-lg bg-background p-6 shadow-lg">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-yellow-100 dark:bg-yellow-900">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium">安全警告</h3>
|
||||
<Modal
|
||||
isOpen={showSecurityWarning}
|
||||
onClose={() => setShowSecurityWarning(false)}
|
||||
maxWidth="max-w-md"
|
||||
showCloseButton={false}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-yellow-100 dark:bg-yellow-900">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium">安全警告</h3>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
您即将导出未脱敏的凭证数据,导出文件将包含明文 API 密钥和 OAuth
|
||||
Token。 请确保:
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-sm text-muted-foreground mb-4 space-y-1">
|
||||
<li>不要将此文件分享给他人</li>
|
||||
<li>不要上传到公共代码仓库</li>
|
||||
<li>妥善保管导出文件</li>
|
||||
</ul>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
您即将导出未脱敏的凭证数据,导出文件将包含明文 API 密钥和 OAuth
|
||||
Token。 请确保:
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-sm text-muted-foreground mb-4 space-y-1">
|
||||
<li>不要将此文件分享给他人</li>
|
||||
<li>不要上传到公共代码仓库</li>
|
||||
<li>妥善保管导出文件</li>
|
||||
</ul>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setShowSecurityWarning(false)}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={performExport}
|
||||
className="rounded-lg bg-yellow-600 px-4 py-2 text-sm text-white hover:bg-yellow-700"
|
||||
>
|
||||
我已了解,继续导出
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setShowSecurityWarning(false)}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={performExport}
|
||||
className="rounded-lg bg-yellow-600 px-4 py-2 text-sm text-white hover:bg-yellow-700"
|
||||
>
|
||||
我已了解,继续导出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* Import Dialog */}
|
||||
{showImportDialog && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="w-full max-w-2xl rounded-lg bg-background p-6 shadow-lg max-h-[90vh] overflow-y-auto">
|
||||
<h3 className="text-lg font-medium mb-4 flex items-center gap-2">
|
||||
{getFileTypeIcon()}
|
||||
导入配置 - {importFileName}
|
||||
</h3>
|
||||
<Modal
|
||||
isOpen={showImportDialog}
|
||||
onClose={closeImportDialog}
|
||||
maxWidth="max-w-2xl"
|
||||
className="max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium mb-4 flex items-center gap-2">
|
||||
{getFileTypeIcon()}
|
||||
导入配置 - {importFileName}
|
||||
</h3>
|
||||
|
||||
{/* Validation Result */}
|
||||
{validationResult && (
|
||||
<div className="mb-4 space-y-2">
|
||||
{validationResult.valid ? (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-green-800 dark:bg-green-950 dark:text-green-400">
|
||||
<Check className="h-5 w-5" />
|
||||
<span className="text-sm">文件格式有效</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-400">
|
||||
<AlertCircle className="h-5 w-5 flex-shrink-0" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium">文件格式无效</p>
|
||||
<ul className="mt-1 list-disc list-inside">
|
||||
{validationResult.errors.map((err, i) => (
|
||||
<li key={i}>{err}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content info */}
|
||||
{validationResult.valid && (
|
||||
<div className="flex flex-wrap gap-2 text-sm">
|
||||
{validationResult.version && (
|
||||
<span className="rounded bg-muted px-2 py-1">
|
||||
版本: {validationResult.version}
|
||||
</span>
|
||||
)}
|
||||
{validationResult.has_config && (
|
||||
<span className="rounded bg-blue-100 px-2 py-1 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
包含配置
|
||||
</span>
|
||||
)}
|
||||
{validationResult.has_credentials && (
|
||||
<span className="rounded bg-purple-100 px-2 py-1 text-purple-700 dark:bg-purple-900 dark:text-purple-300">
|
||||
包含凭证
|
||||
</span>
|
||||
)}
|
||||
{validationResult.redacted && (
|
||||
<span className="rounded bg-yellow-100 px-2 py-1 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300">
|
||||
已脱敏
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Redaction warning */}
|
||||
{validationResult.redacted && (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-yellow-200 bg-yellow-50 p-3 text-yellow-700 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-400">
|
||||
<AlertTriangle className="h-5 w-5 flex-shrink-0" />
|
||||
<span className="text-sm">
|
||||
此导出包已脱敏,凭证数据(API 密钥、Token)无法恢复。
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Validation warnings */}
|
||||
{validationResult.warnings.length > 0 && (
|
||||
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-950">
|
||||
<p className="text-sm font-medium text-yellow-700 dark:text-yellow-400">
|
||||
警告
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-inside text-sm text-yellow-600 dark:text-yellow-500">
|
||||
{validationResult.warnings.map((warning, i) => (
|
||||
<li key={i}>{warning}</li>
|
||||
{/* Validation Result */}
|
||||
{validationResult && (
|
||||
<div className="mb-4 space-y-2">
|
||||
{validationResult.valid ? (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-green-800 dark:bg-green-950 dark:text-green-400">
|
||||
<Check className="h-5 w-5" />
|
||||
<span className="text-sm">文件格式有效</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-400">
|
||||
<AlertCircle className="h-5 w-5 flex-shrink-0" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium">文件格式无效</p>
|
||||
<ul className="mt-1 list-disc list-inside">
|
||||
{validationResult.errors.map((err, i) => (
|
||||
<li key={i}>{err}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Preview */}
|
||||
<div className="mb-4">
|
||||
<label className="text-sm font-medium">内容预览</label>
|
||||
<pre className="mt-2 max-h-48 overflow-auto rounded-lg bg-muted p-4 text-xs font-mono">
|
||||
{importContent.slice(0, 2000)}
|
||||
{importContent.length > 2000 && "\n..."}
|
||||
</pre>
|
||||
</div>
|
||||
{/* Content info */}
|
||||
{validationResult.valid && (
|
||||
<div className="flex flex-wrap gap-2 text-sm">
|
||||
{validationResult.version && (
|
||||
<span className="rounded bg-muted px-2 py-1">
|
||||
版本: {validationResult.version}
|
||||
</span>
|
||||
)}
|
||||
{validationResult.has_config && (
|
||||
<span className="rounded bg-blue-100 px-2 py-1 text-blue-700 dark:bg-blue-900 dark:text-blue-300">
|
||||
包含配置
|
||||
</span>
|
||||
)}
|
||||
{validationResult.has_credentials && (
|
||||
<span className="rounded bg-purple-100 px-2 py-1 text-purple-700 dark:bg-purple-900 dark:text-purple-300">
|
||||
包含凭证
|
||||
</span>
|
||||
)}
|
||||
{validationResult.redacted && (
|
||||
<span className="rounded bg-yellow-100 px-2 py-1 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300">
|
||||
已脱敏
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Import Options */}
|
||||
<div className="mb-4 space-y-2">
|
||||
<label className="text-sm font-medium">导入模式</label>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="importMode"
|
||||
checked={mergeConfig}
|
||||
onChange={() => setMergeConfig(true)}
|
||||
className="rounded-full border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">合并到现有配置</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(保留现有数据,添加新数据)
|
||||
{/* Redaction warning */}
|
||||
{validationResult.redacted && (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-yellow-200 bg-yellow-50 p-3 text-yellow-700 dark:border-yellow-800 dark:bg-yellow-950 dark:text-yellow-400">
|
||||
<AlertTriangle className="h-5 w-5 flex-shrink-0" />
|
||||
<span className="text-sm">
|
||||
此导出包已脱敏,凭证数据(API 密钥、Token)无法恢复。
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="importMode"
|
||||
checked={!mergeConfig}
|
||||
onChange={() => setMergeConfig(false)}
|
||||
className="rounded-full border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">替换现有配置</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(完全覆盖现有数据)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Import Result Warnings */}
|
||||
{importResult?.warnings && importResult.warnings.length > 0 && (
|
||||
<div className="mb-4 rounded-lg border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-950">
|
||||
<p className="text-sm font-medium text-yellow-700 dark:text-yellow-400">
|
||||
导入警告
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-inside text-sm text-yellow-600 dark:text-yellow-500">
|
||||
{importResult.warnings.map((warning, i) => (
|
||||
<li key={i}>{warning}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Success */}
|
||||
{importResult?.success && (
|
||||
<div className="mb-4 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-green-800 dark:bg-green-950 dark:text-green-400">
|
||||
<Check className="h-5 w-5" />
|
||||
<span className="text-sm">配置导入成功</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="mb-4 flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-400">
|
||||
<AlertCircle className="h-5 w-5 flex-shrink-0" />
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={closeImportDialog}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
{importResult?.success ? "关闭" : "取消"}
|
||||
</button>
|
||||
{!importResult?.success && validationResult?.valid && (
|
||||
<button
|
||||
onClick={handleImport}
|
||||
disabled={isImporting}
|
||||
className="rounded-lg bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{isImporting ? "导入中..." : "导入"}
|
||||
</button>
|
||||
{/* Validation warnings */}
|
||||
{validationResult.warnings.length > 0 && (
|
||||
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-950">
|
||||
<p className="text-sm font-medium text-yellow-700 dark:text-yellow-400">
|
||||
警告
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-inside text-sm text-yellow-600 dark:text-yellow-500">
|
||||
{validationResult.warnings.map((warning, i) => (
|
||||
<li key={i}>{warning}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Preview */}
|
||||
<div className="mb-4">
|
||||
<label className="text-sm font-medium">内容预览</label>
|
||||
<pre className="mt-2 max-h-48 overflow-auto rounded-lg bg-muted p-4 text-xs font-mono">
|
||||
{importContent.slice(0, 2000)}
|
||||
{importContent.length > 2000 && "\n..."}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Import Options */}
|
||||
<div className="mb-4 space-y-2">
|
||||
<label className="text-sm font-medium">导入模式</label>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="importMode"
|
||||
checked={mergeConfig}
|
||||
onChange={() => setMergeConfig(true)}
|
||||
className="rounded-full border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">合并到现有配置</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(保留现有数据,添加新数据)
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="importMode"
|
||||
checked={!mergeConfig}
|
||||
onChange={() => setMergeConfig(false)}
|
||||
className="rounded-full border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">替换现有配置</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(完全覆盖现有数据)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Import Result Warnings */}
|
||||
{importResult?.warnings && importResult.warnings.length > 0 && (
|
||||
<div className="mb-4 rounded-lg border border-yellow-200 bg-yellow-50 p-3 dark:border-yellow-800 dark:bg-yellow-950">
|
||||
<p className="text-sm font-medium text-yellow-700 dark:text-yellow-400">
|
||||
导入警告
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-inside text-sm text-yellow-600 dark:text-yellow-500">
|
||||
{importResult.warnings.map((warning, i) => (
|
||||
<li key={i}>{warning}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Success */}
|
||||
{importResult?.success && (
|
||||
<div className="mb-4 flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 p-3 text-green-700 dark:border-green-800 dark:bg-green-950 dark:text-green-400">
|
||||
<Check className="h-5 w-5" />
|
||||
<span className="text-sm">配置导入成功</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="mb-4 flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-400">
|
||||
<AlertCircle className="h-5 w-5 flex-shrink-0" />
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={closeImportDialog}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
{importResult?.success ? "关闭" : "取消"}
|
||||
</button>
|
||||
{!importResult?.success && validationResult?.valid && (
|
||||
<button
|
||||
onClick={handleImport}
|
||||
disabled={isImporting}
|
||||
className="rounded-lg bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{isImporting ? "导入中..." : "导入"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* Global Error */}
|
||||
{error && !showImportDialog && (
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { Bell, BellOff, Settings } from "lucide-react";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import {
|
||||
notificationService,
|
||||
type NotificationPermission,
|
||||
@@ -114,22 +115,18 @@ export function NotificationSettings({
|
||||
});
|
||||
};
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background border rounded-lg shadow-lg p-8">
|
||||
<div className="text-center">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<Modal isOpen={open} onClose={onClose ?? (() => {})} maxWidth="max-w-sm">
|
||||
<div className="p-8 text-center">加载中...</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !config) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background border rounded-lg shadow-lg p-8">
|
||||
<Modal isOpen={open} onClose={onClose ?? (() => {})} maxWidth="max-w-sm">
|
||||
<div className="p-8">
|
||||
<div className="text-center text-red-500">
|
||||
{error || "配置加载失败"}
|
||||
</div>
|
||||
@@ -140,412 +137,403 @@ export function NotificationSettings({
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="bg-background border rounded-lg shadow-lg w-96 max-h-[80vh] overflow-y-auto">
|
||||
{/* 标题栏 */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
<h3 className="font-semibold">通知设置</h3>
|
||||
<Modal
|
||||
isOpen={open}
|
||||
onClose={onClose ?? (() => {})}
|
||||
maxWidth="max-w-sm"
|
||||
className="max-h-[80vh] overflow-y-auto"
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<div className="flex items-center justify-between p-4 border-b">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
<h3 className="font-semibold">通知设置</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="p-4 space-y-4">
|
||||
{/* 权限状态 */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">通知权限</label>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{permissionStatus === "granted" && "已授权"}
|
||||
{permissionStatus === "denied" && "已拒绝"}
|
||||
{permissionStatus === "default" && "未设置"}
|
||||
</span>
|
||||
{permissionStatus !== "granted" && (
|
||||
<button
|
||||
onClick={onRequestPermission}
|
||||
className="text-sm px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
>
|
||||
{permissionStatus === "denied" ? "重新授权" : "请求权限"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="p-4 space-y-4">
|
||||
{/* 权限状态 */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">通知权限</label>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{permissionStatus === "granted" && "已授权"}
|
||||
{permissionStatus === "denied" && "已拒绝"}
|
||||
{permissionStatus === "default" && "未设置"}
|
||||
</span>
|
||||
{permissionStatus !== "granted" && (
|
||||
<button
|
||||
onClick={onRequestPermission}
|
||||
className="text-sm px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
>
|
||||
{permissionStatus === "denied" ? "重新授权" : "请求权限"}
|
||||
</button>
|
||||
{/* 基本设置 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">基本设置</label>
|
||||
|
||||
{/* 启用通知 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{config.enabled ? (
|
||||
<Bell className="h-4 w-4 text-blue-500" />
|
||||
) : (
|
||||
<BellOff className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span className="text-sm">启用通知</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 基本设置 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">基本设置</label>
|
||||
|
||||
{/* 启用通知 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{config.enabled ? (
|
||||
<Bell className="h-4 w-4 text-blue-500" />
|
||||
) : (
|
||||
<BellOff className="h-4 w-4 text-muted-foreground" />
|
||||
<button
|
||||
onClick={() => updateConfig({ enabled: !config.enabled })}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.enabled ? "translate-x-5" : "translate-x-1",
|
||||
)}
|
||||
<span className="text-sm">启用通知</span>
|
||||
</div>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 通知类型 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">通知类型</label>
|
||||
|
||||
{/* 新 Flow 通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">新 LLM 请求</span>
|
||||
<button
|
||||
onClick={() => updateConfig({ enabled: !config.enabled })}
|
||||
onClick={() =>
|
||||
updateNotificationSettings("new_flow", {
|
||||
enabled: !config.new_flow.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.enabled ? "bg-blue-500" : "bg-muted",
|
||||
config.new_flow.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.enabled ? "translate-x-5" : "translate-x-1",
|
||||
config.new_flow.enabled ? "translate-x-5" : "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.new_flow.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("new_flow", {
|
||||
desktop: !config.new_flow.desktop,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.new_flow.desktop ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.new_flow.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("new_flow", {
|
||||
sound: !config.new_flow.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.new_flow.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.new_flow.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 通知类型 */}
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium">通知类型</label>
|
||||
|
||||
{/* 新 Flow 通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">新 LLM 请求</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("new_flow", {
|
||||
enabled: !config.new_flow.enabled,
|
||||
})
|
||||
}
|
||||
{/* 错误通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">请求失败</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("error_flow", {
|
||||
enabled: !config.error_flow.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.error_flow.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.new_flow.enabled ? "bg-blue-500" : "bg-muted",
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.error_flow.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.new_flow.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.new_flow.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("new_flow", {
|
||||
desktop: !config.new_flow.desktop,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.new_flow.desktop ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.new_flow.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("new_flow", {
|
||||
sound: !config.new_flow.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.new_flow.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.new_flow.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 错误通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">请求失败</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("error_flow", {
|
||||
enabled: !config.error_flow.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.error_flow.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
{config.error_flow.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("error_flow", {
|
||||
desktop: !config.error_flow.desktop,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.error_flow.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.error_flow.desktop ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.error_flow.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("error_flow", {
|
||||
desktop: !config.error_flow.desktop,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.error_flow.desktop ? "bg-blue-500" : "bg-muted",
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.error_flow.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.error_flow.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("error_flow", {
|
||||
sound: !config.error_flow.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.error_flow.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.error_flow.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 阈值警告通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">阈值警告</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("latency_warning", {
|
||||
enabled: !config.latency_warning.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.latency_warning.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("error_flow", {
|
||||
sound: !config.error_flow.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.latency_warning.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.error_flow.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.latency_warning.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("latency_warning", {
|
||||
desktop: !config.latency_warning.desktop,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.error_flow.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 阈值警告通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">阈值警告</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("latency_warning", {
|
||||
enabled: !config.latency_warning.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.latency_warning.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.latency_warning.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.latency_warning.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("latency_warning", {
|
||||
desktop: !config.latency_warning.desktop,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.latency_warning.desktop
|
||||
? "bg-blue-500"
|
||||
: "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.latency_warning.desktop
|
||||
? "bg-blue-500"
|
||||
: "bg-muted",
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.latency_warning.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("latency_warning", {
|
||||
sound: !config.latency_warning.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.latency_warning.sound
|
||||
? "bg-blue-500"
|
||||
: "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.latency_warning.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Token 警告通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Token 警告</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("token_warning", {
|
||||
enabled: !config.token_warning.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.token_warning.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("latency_warning", {
|
||||
sound: !config.latency_warning.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.token_warning.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.latency_warning.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.token_warning.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("token_warning", {
|
||||
desktop: !config.token_warning.desktop,
|
||||
})
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.token_warning.desktop
|
||||
? "bg-blue-500"
|
||||
: "bg-muted",
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.latency_warning.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.token_warning.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("token_warning", {
|
||||
sound: !config.token_warning.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.token_warning.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.token_warning.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 测试按钮 */}
|
||||
<div className="pt-2">
|
||||
<button
|
||||
onClick={testNotification}
|
||||
disabled={!config.enabled || permissionStatus !== "granted"}
|
||||
className="w-full px-3 py-2 text-sm bg-muted hover:bg-muted/80 rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
发送测试通知
|
||||
</button>
|
||||
{/* Token 警告通知 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Token 警告</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("token_warning", {
|
||||
enabled: !config.token_warning.enabled,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
|
||||
config.token_warning.enabled ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-3 w-3 transform rounded-full bg-white transition-transform",
|
||||
config.token_warning.enabled
|
||||
? "translate-x-5"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{config.token_warning.enabled && (
|
||||
<div className="ml-4 space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>桌面通知</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("token_warning", {
|
||||
desktop: !config.token_warning.desktop,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.token_warning.desktop ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.token_warning.desktop
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span>声音提示</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
updateNotificationSettings("token_warning", {
|
||||
sound: !config.token_warning.sound,
|
||||
})
|
||||
}
|
||||
className={cn(
|
||||
"relative inline-flex h-4 w-7 items-center rounded-full transition-colors",
|
||||
config.token_warning.sound ? "bg-blue-500" : "bg-muted",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block h-2 w-2 transform rounded-full bg-white transition-transform",
|
||||
config.token_warning.sound
|
||||
? "translate-x-4"
|
||||
: "translate-x-1",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 测试按钮 */}
|
||||
<div className="pt-2">
|
||||
<button
|
||||
onClick={testNotification}
|
||||
disabled={!config.enabled || permissionStatus !== "granted"}
|
||||
className="w-full px-3 py-2 text-sm bg-muted hover:bg-muted/80 rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
发送测试通知
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { X, Key, FolderOpen } from "lucide-react";
|
||||
import { Key, FolderOpen } from "lucide-react";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import { providerPoolApi, PoolProviderType } from "@/lib/api/providerPool";
|
||||
import { AntigravityForm } from "./credential-forms/AntigravityForm";
|
||||
import { CodexForm } from "./credential-forms/CodexForm";
|
||||
@@ -514,64 +515,57 @@ export function AddCredentialModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="w-full max-w-md rounded-lg bg-background p-6 shadow-xl">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b pb-4">
|
||||
<h3 className="text-lg font-semibold">
|
||||
添加 {providerLabels[providerType]} 凭证
|
||||
</h3>
|
||||
<button onClick={onClose} className="rounded-lg p-1 hover:bg-muted">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="mt-4 space-y-4">
|
||||
{/* 名称字段 */}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
名称 (可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="给这个凭证起个名字..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 根据类型渲染不同表单 */}
|
||||
{providerType === "antigravity" && antigravityForm.render()}
|
||||
{providerType === "codex" && codexForm.render()}
|
||||
{providerType === "claude_oauth" && claudeOAuthForm.render()}
|
||||
{providerType === "qwen" && qwenForm.render()}
|
||||
{providerType === "iflow" && iflowForm.render()}
|
||||
{providerType === "gemini" && geminiForm.render()}
|
||||
{providerType === "kiro" && kiroForm.render()}
|
||||
{isSimpleOAuth.includes(providerType) && renderSimpleOAuthForm()}
|
||||
{isApiKey && renderApiKeyForm()}
|
||||
|
||||
{/* 错误提示 */}
|
||||
{error && (
|
||||
<div className="rounded-lg border border-red-500 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
{renderFooterButton()}
|
||||
</div>
|
||||
<Modal isOpen={true} onClose={onClose} maxWidth="max-w-md">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b px-6 py-4">
|
||||
<h3 className="text-lg font-semibold">
|
||||
添加 {providerLabels[providerType]} 凭证
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-4 px-6 py-4">
|
||||
{/* 名称字段 */}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">名称 (可选)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="给这个凭证起个名字..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 根据类型渲染不同表单 */}
|
||||
{providerType === "antigravity" && antigravityForm.render()}
|
||||
{providerType === "codex" && codexForm.render()}
|
||||
{providerType === "claude_oauth" && claudeOAuthForm.render()}
|
||||
{providerType === "qwen" && qwenForm.render()}
|
||||
{providerType === "iflow" && iflowForm.render()}
|
||||
{providerType === "gemini" && geminiForm.render()}
|
||||
{providerType === "kiro" && kiroForm.render()}
|
||||
{isSimpleOAuth.includes(providerType) && renderSimpleOAuthForm()}
|
||||
{isApiKey && renderApiKeyForm()}
|
||||
|
||||
{/* 错误提示 */}
|
||||
{error && (
|
||||
<div className="rounded-lg border border-red-500 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-end gap-2 border-t px-6 py-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
{renderFooterButton()}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
X,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Settings,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
Globe,
|
||||
} from "lucide-react";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import {
|
||||
CredentialDisplay,
|
||||
UpdateCredentialRequest,
|
||||
@@ -227,318 +227,311 @@ export function EditCredentialModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
|
||||
<div className="w-full max-w-2xl max-h-[85vh] rounded-lg bg-background shadow-xl flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b pb-4 px-6 pt-6 shrink-0">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
编辑凭证
|
||||
</h3>
|
||||
<button onClick={onClose} className="rounded-lg p-1 hover:bg-muted">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
maxWidth="max-w-2xl"
|
||||
className="max-h-[85vh] flex flex-col"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b pb-4 px-6 pt-6 shrink-0">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
编辑凭证
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Content - Scrollable */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
<div className="space-y-5">
|
||||
{/* 名称 + 健康检查 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
名称 (选填)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="给这个凭证起个名字..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
健康检查
|
||||
</label>
|
||||
<select
|
||||
value={checkHealth ? "enabled" : "disabled"}
|
||||
onChange={(e) => setCheckHealth(e.target.value === "enabled")}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="enabled">启用</option>
|
||||
<option value="disabled">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 检查模型名称 */}
|
||||
{/* Content - Scrollable */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||
<div className="space-y-5">
|
||||
{/* 名称 + 健康检查 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
检查模型名称 (选填)
|
||||
名称 (选填)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={checkModelName}
|
||||
onChange={(e) => setCheckModelName(e.target.value)}
|
||||
placeholder="用于健康检查的模型名称..."
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="给这个凭证起个名字..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">健康检查</label>
|
||||
<select
|
||||
value={checkHealth ? "enabled" : "disabled"}
|
||||
onChange={(e) => setCheckHealth(e.target.value === "enabled")}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="enabled">启用</option>
|
||||
<option value="disabled">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OAuth凭据文件路径 */}
|
||||
{isOAuth && (
|
||||
{/* 检查模型名称 */}
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
检查模型名称 (选填)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={checkModelName}
|
||||
onChange={(e) => setCheckModelName(e.target.value)}
|
||||
placeholder="用于健康检查的模型名称..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* OAuth凭据文件路径 */}
|
||||
{isOAuth && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
OAuth凭据文件路径
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={
|
||||
showCredentialDetails
|
||||
? credential.display_credential
|
||||
: getMaskedCredentialInfo()
|
||||
}
|
||||
readOnly
|
||||
className="flex-1 rounded-lg border bg-muted/50 px-3 py-2 text-sm text-muted-foreground"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setShowCredentialDetails(!showCredentialDetails)
|
||||
}
|
||||
className="rounded-lg border p-2 hover:bg-muted"
|
||||
title={showCredentialDetails ? "隐藏" : "显示"}
|
||||
>
|
||||
{showCredentialDetails ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSelectNewFile}
|
||||
className="rounded-lg border p-2 hover:bg-muted"
|
||||
title="上传新文件"
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{newCredFilePath && (
|
||||
<div className="mt-2 text-xs text-green-600 dark:text-green-400 flex items-center gap-1">
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
新文件已选择: {newCredFilePath.split("/").pop()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Gemini Project ID */}
|
||||
{credential.credential_type === "gemini_oauth" && newCredFilePath && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
项目ID(可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newProjectId}
|
||||
onChange={(e) => setNewProjectId(e.target.value)}
|
||||
placeholder="留空保持当前项目ID..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* API Key 编辑 */}
|
||||
{isApiKey && (
|
||||
<>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
OAuth凭据文件路径
|
||||
API Key
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={
|
||||
showCredentialDetails
|
||||
? credential.display_credential
|
||||
: getMaskedCredentialInfo()
|
||||
}
|
||||
readOnly
|
||||
className="flex-1 rounded-lg border bg-muted/50 px-3 py-2 text-sm text-muted-foreground"
|
||||
type={showApiKey ? "text" : "password"}
|
||||
value={newApiKey}
|
||||
onChange={(e) => setNewApiKey(e.target.value)}
|
||||
placeholder="留空保持当前 Key,或输入新的 API Key..."
|
||||
className="flex-1 rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setShowCredentialDetails(!showCredentialDetails)
|
||||
}
|
||||
onClick={() => setShowApiKey(!showApiKey)}
|
||||
className="rounded-lg border p-2 hover:bg-muted"
|
||||
title={showCredentialDetails ? "隐藏" : "显示"}
|
||||
title={showApiKey ? "隐藏" : "显示"}
|
||||
>
|
||||
{showCredentialDetails ? (
|
||||
{showApiKey ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSelectNewFile}
|
||||
className="rounded-lg border p-2 hover:bg-muted"
|
||||
title="上传新文件"
|
||||
>
|
||||
<Upload className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{newCredFilePath && (
|
||||
<div className="mt-2 text-xs text-green-600 dark:text-green-400 flex items-center gap-1">
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
新文件已选择: {newCredFilePath.split("/").pop()}
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
当前: {credential.display_credential}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
Base URL(可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newBaseUrl}
|
||||
onChange={(e) => setNewBaseUrl(e.target.value)}
|
||||
placeholder={
|
||||
credential.credential_type === "openai_key"
|
||||
? "https://api.openai.com"
|
||||
: "https://api.anthropic.com"
|
||||
}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
留空使用默认 URL,或输入自定义代理地址(不要包含 /v1 后缀)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 不支持的模型 - Checkbox Grid */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Ban className="h-4 w-4 text-muted-foreground" />
|
||||
<label className="text-sm font-medium">不支持的模型</label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
选择此提供商不支持的模型,系统会自动排除这些模型
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{currentProviderModels.map((model) => (
|
||||
<label
|
||||
key={model}
|
||||
className={`flex items-center gap-2 rounded-lg border px-3 py-2 cursor-pointer transition-colors ${
|
||||
notSupportedModels.includes(model)
|
||||
? "border-red-300 bg-red-50 dark:border-red-800 dark:bg-red-950/30"
|
||||
: "border-border hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notSupportedModels.includes(model)}
|
||||
onChange={() => toggleModelSupport(model)}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm truncate">{model}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 高级选项:代理设置 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4 text-blue-500" />
|
||||
<label className="text-sm font-medium">凭证代理设置</label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(高级选项)
|
||||
</span>
|
||||
</div>
|
||||
<div className="rounded-lg border p-4 space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">
|
||||
代理 URL(可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={proxyUrl}
|
||||
onChange={(e) => handleProxyUrlChange(e.target.value)}
|
||||
placeholder="例如: http://127.0.0.1:7890 或 socks5://127.0.0.1:1080"
|
||||
className={`w-full rounded-lg border bg-background px-3 py-2 text-sm ${
|
||||
proxyError ? "border-red-500" : ""
|
||||
}`}
|
||||
/>
|
||||
{proxyError ? (
|
||||
<p className="text-xs text-red-500 mt-1">{proxyError}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
留空则使用全局代理设置。支持 http://、https://、socks5://
|
||||
协议
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Gemini Project ID */}
|
||||
{credential.credential_type === "gemini_oauth" &&
|
||||
newCredFilePath && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
项目ID(可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newProjectId}
|
||||
onChange={(e) => setNewProjectId(e.target.value)}
|
||||
placeholder="留空保持当前项目ID..."
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* API Key 编辑 */}
|
||||
{isApiKey && (
|
||||
<>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
API Key
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type={showApiKey ? "text" : "password"}
|
||||
value={newApiKey}
|
||||
onChange={(e) => setNewApiKey(e.target.value)}
|
||||
placeholder="留空保持当前 Key,或输入新的 API Key..."
|
||||
className="flex-1 rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowApiKey(!showApiKey)}
|
||||
className="rounded-lg border p-2 hover:bg-muted"
|
||||
title={showApiKey ? "隐藏" : "显示"}
|
||||
>
|
||||
{showApiKey ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
当前: {credential.display_credential}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium">
|
||||
Base URL(可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newBaseUrl}
|
||||
onChange={(e) => setNewBaseUrl(e.target.value)}
|
||||
placeholder={
|
||||
credential.credential_type === "openai_key"
|
||||
? "https://api.openai.com"
|
||||
: "https://api.anthropic.com"
|
||||
}
|
||||
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
留空使用默认 URL,或输入自定义代理地址(不要包含 /v1 后缀)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 不支持的模型 - Checkbox Grid */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Ban className="h-4 w-4 text-muted-foreground" />
|
||||
<label className="text-sm font-medium">不支持的模型</label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
选择此提供商不支持的模型,系统会自动排除这些模型
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{currentProviderModels.map((model) => (
|
||||
<label
|
||||
key={model}
|
||||
className={`flex items-center gap-2 rounded-lg border px-3 py-2 cursor-pointer transition-colors ${
|
||||
notSupportedModels.includes(model)
|
||||
? "border-red-300 bg-red-50 dark:border-red-800 dark:bg-red-950/30"
|
||||
: "border-border hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={notSupportedModels.includes(model)}
|
||||
onChange={() => toggleModelSupport(model)}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm truncate">{model}</span>
|
||||
</label>
|
||||
))}
|
||||
<div className="rounded-lg bg-blue-50 dark:bg-blue-900/20 p-3 text-xs">
|
||||
<p className="font-medium text-blue-700 dark:text-blue-300">
|
||||
代理优先级说明:
|
||||
</p>
|
||||
<ul className="mt-1 list-inside list-disc text-blue-600 dark:text-blue-400">
|
||||
<li>此凭证代理优先于全局代理</li>
|
||||
<li>留空时使用全局代理设置</li>
|
||||
<li>全局代理可在「设置 → 通用」中配置</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 高级选项:代理设置 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="h-4 w-4 text-blue-500" />
|
||||
<label className="text-sm font-medium">凭证代理设置</label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
(高级选项)
|
||||
</span>
|
||||
</div>
|
||||
<div className="rounded-lg border p-4 space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">
|
||||
代理 URL(可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={proxyUrl}
|
||||
onChange={(e) => handleProxyUrlChange(e.target.value)}
|
||||
placeholder="例如: http://127.0.0.1:7890 或 socks5://127.0.0.1:1080"
|
||||
className={`w-full rounded-lg border bg-background px-3 py-2 text-sm ${
|
||||
proxyError ? "border-red-500" : ""
|
||||
}`}
|
||||
/>
|
||||
{proxyError ? (
|
||||
<p className="text-xs text-red-500 mt-1">{proxyError}</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
留空则使用全局代理设置。支持 http://、https://、socks5://
|
||||
协议
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-lg bg-blue-50 dark:bg-blue-900/20 p-3 text-xs">
|
||||
<p className="font-medium text-blue-700 dark:text-blue-300">
|
||||
代理优先级说明:
|
||||
</p>
|
||||
<ul className="mt-1 list-inside list-disc text-blue-600 dark:text-blue-400">
|
||||
<li>此凭证代理优先于全局代理</li>
|
||||
<li>留空时使用全局代理设置</li>
|
||||
<li>全局代理可在「设置 → 通用」中配置</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 使用统计(只读) */}
|
||||
<div className="rounded-lg bg-muted/50 p-4">
|
||||
<label className="mb-3 block text-sm font-medium">使用统计</label>
|
||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
使用次数
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{credential.usage_count}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
错误次数
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{credential.error_count}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
最后使用
|
||||
</span>
|
||||
<span className="text-xs">
|
||||
{credential.last_used || "从未"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="rounded-lg border border-red-500 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t px-6 py-4 flex justify-end gap-2 shrink-0">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || !!proxyError}
|
||||
className="rounded-lg bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{loading ? "保存中..." : "保存更改"}
|
||||
</button>
|
||||
{/* 使用统计(只读) */}
|
||||
<div className="rounded-lg bg-muted/50 p-4">
|
||||
<label className="mb-3 block text-sm font-medium">使用统计</label>
|
||||
<div className="grid grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
使用次数
|
||||
</span>
|
||||
<span className="font-semibold">{credential.usage_count}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
错误次数
|
||||
</span>
|
||||
<span className="font-semibold">{credential.error_count}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground block text-xs">
|
||||
最后使用
|
||||
</span>
|
||||
<span className="text-xs">
|
||||
{credential.last_used || "从未"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<div className="rounded-lg border border-red-500 bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/30">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t px-6 py-4 flex justify-end gap-2 shrink-0">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg border px-4 py-2 text-sm hover:bg-muted"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || !!proxyError}
|
||||
className="rounded-lg bg-primary px-4 py-2 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{loading ? "保存中..." : "保存更改"}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,20 +30,21 @@ export interface ProviderPoolPageRef {
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
// All provider types (OAuth/API Key 凭证)
|
||||
const allProviderTypes: PoolProviderType[] = [
|
||||
// OAuth 类型凭证(需要上传凭证文件或登录授权)
|
||||
const oauthProviderTypes: PoolProviderType[] = [
|
||||
"kiro",
|
||||
"gemini",
|
||||
"qwen",
|
||||
"antigravity",
|
||||
"openai",
|
||||
"claude",
|
||||
"codex",
|
||||
"claude_oauth",
|
||||
"iflow",
|
||||
];
|
||||
|
||||
// 配置类型 tab(非凭证池)
|
||||
// API Key 类型凭证(直接填入 API Key)
|
||||
const apiKeyProviderTypes: PoolProviderType[] = ["openai", "claude"];
|
||||
|
||||
// 配置类型 tab(非凭证池)- gemini_api 移到 API Key 分类下
|
||||
type ConfigTabType = "gemini_api" | "vertex" | "amp";
|
||||
|
||||
// 所有 tab 类型
|
||||
@@ -62,7 +63,7 @@ const providerLabels: Record<PoolProviderType, string> = {
|
||||
};
|
||||
|
||||
const configTabLabels: Record<ConfigTabType, string> = {
|
||||
gemini_api: "Gemini API Key",
|
||||
gemini_api: "Gemini",
|
||||
vertex: "Vertex AI",
|
||||
amp: "Amp CLI",
|
||||
};
|
||||
@@ -72,12 +73,16 @@ const isConfigTab = (tab: TabType): tab is ConfigTabType => {
|
||||
return ["gemini_api", "vertex", "amp"].includes(tab);
|
||||
};
|
||||
|
||||
// 分类类型
|
||||
type CategoryType = "oauth" | "apikey" | "config";
|
||||
|
||||
export const ProviderPoolPage = forwardRef<ProviderPoolPageRef>(
|
||||
(_props, ref) => {
|
||||
const [addModalOpen, setAddModalOpen] = useState(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editingCredential, setEditingCredential] =
|
||||
useState<CredentialDisplay | null>(null);
|
||||
const [activeCategory, setActiveCategory] = useState<CategoryType>("oauth");
|
||||
const [activeTab, setActiveTab] = useState<TabType>("kiro");
|
||||
const [deletingCredentials, setDeletingCredentials] = useState<Set<string>>(
|
||||
new Set(),
|
||||
@@ -136,13 +141,13 @@ export const ProviderPoolPage = forwardRef<ProviderPoolPageRef>(
|
||||
setConfigSaving(false);
|
||||
};
|
||||
|
||||
// 切换到配置 tab 时加载配置
|
||||
// 切换到配置 tab 或 apikey 分类时加载配置(apikey 分类包含 gemini_api)
|
||||
useEffect(() => {
|
||||
if (isConfigTab(activeTab)) {
|
||||
if (isConfigTab(activeTab) || activeCategory === "apikey") {
|
||||
loadConfig();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab]);
|
||||
}, [activeTab, activeCategory]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
refresh,
|
||||
@@ -367,48 +372,166 @@ export const ProviderPoolPage = forwardRef<ProviderPoolPageRef>(
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-2 border-b overflow-x-auto">
|
||||
{/* 凭证池 tabs */}
|
||||
{allProviderTypes.map((providerType) => {
|
||||
const count = getCredentialCount(providerType);
|
||||
return (
|
||||
<button
|
||||
key={providerType}
|
||||
onClick={() => setActiveTab(providerType)}
|
||||
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px whitespace-nowrap flex items-center gap-2 ${
|
||||
activeTab === providerType
|
||||
? "border-primary text-primary"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
<ProviderIcon providerType={providerType} size={16} />
|
||||
{providerLabels[providerType]}
|
||||
{count > 0 && (
|
||||
<span className="rounded-full bg-muted px-1.5 py-0.5 text-xs">
|
||||
{count}
|
||||
{/* Category Tabs - 第一行:分类选择 */}
|
||||
<div className="flex gap-1 mb-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveCategory("oauth");
|
||||
setActiveTab(oauthProviderTypes[0]);
|
||||
}}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
|
||||
activeCategory === "oauth"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground hover:text-foreground hover:bg-muted/80"
|
||||
}`}
|
||||
>
|
||||
OAuth 凭证
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveCategory("apikey");
|
||||
setActiveTab(apiKeyProviderTypes[0]);
|
||||
}}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
|
||||
activeCategory === "apikey"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground hover:text-foreground hover:bg-muted/80"
|
||||
}`}
|
||||
>
|
||||
API Key
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveCategory("config");
|
||||
setActiveTab("vertex");
|
||||
}}
|
||||
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
|
||||
activeCategory === "config"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground hover:text-foreground hover:bg-muted/80"
|
||||
}`}
|
||||
>
|
||||
其他配置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Provider Selection - 第二行:图标网格选择 */}
|
||||
{activeCategory === "oauth" && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{oauthProviderTypes.map((providerType) => {
|
||||
const count = getCredentialCount(providerType);
|
||||
const isActive = activeTab === providerType;
|
||||
return (
|
||||
<button
|
||||
key={providerType}
|
||||
onClick={() => setActiveTab(providerType)}
|
||||
title={providerLabels[providerType]}
|
||||
className={`group relative flex items-center gap-2 px-3 py-2 rounded-lg border transition-all ${
|
||||
isActive
|
||||
? "border-primary bg-primary/10 text-primary shadow-sm"
|
||||
: "border-border bg-card hover:border-primary/50 hover:bg-muted text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
<ProviderIcon providerType={providerType} size={20} />
|
||||
<span className="text-sm font-medium">
|
||||
{providerLabels[providerType].split(" ")[0]}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{/* 分隔符 */}
|
||||
<div className="border-l mx-2" />
|
||||
{/* 配置 tabs */}
|
||||
{(Object.keys(configTabLabels) as ConfigTabType[]).map((tabId) => (
|
||||
{count > 0 && (
|
||||
<span
|
||||
className={`min-w-[1.25rem] h-5 flex items-center justify-center rounded-full text-xs font-medium ${
|
||||
isActive
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted-foreground/20 text-muted-foreground group-hover:bg-primary/20 group-hover:text-primary"
|
||||
}`}
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{activeCategory === "apikey" && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{apiKeyProviderTypes.map((providerType) => {
|
||||
const count = getCredentialCount(providerType);
|
||||
const isActive = activeTab === providerType;
|
||||
return (
|
||||
<button
|
||||
key={providerType}
|
||||
onClick={() => setActiveTab(providerType)}
|
||||
title={providerLabels[providerType]}
|
||||
className={`group relative flex items-center gap-2 px-3 py-2 rounded-lg border transition-all ${
|
||||
isActive
|
||||
? "border-primary bg-primary/10 text-primary shadow-sm"
|
||||
: "border-border bg-card hover:border-primary/50 hover:bg-muted text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
<ProviderIcon providerType={providerType} size={20} />
|
||||
<span className="text-sm font-medium">
|
||||
{providerLabels[providerType].split(" ")[0]}
|
||||
</span>
|
||||
{count > 0 && (
|
||||
<span
|
||||
className={`min-w-[1.25rem] h-5 flex items-center justify-center rounded-full text-xs font-medium ${
|
||||
isActive
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted-foreground/20 text-muted-foreground group-hover:bg-primary/20 group-hover:text-primary"
|
||||
}`}
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{/* Gemini API Key - 配置文件存储但归类到 API Key */}
|
||||
<button
|
||||
key={tabId}
|
||||
onClick={() => setActiveTab(tabId)}
|
||||
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px whitespace-nowrap ${
|
||||
activeTab === tabId
|
||||
? "border-primary text-primary"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setActiveTab("gemini_api")}
|
||||
title="Gemini API Key"
|
||||
className={`group relative flex items-center gap-2 px-3 py-2 rounded-lg border transition-all ${
|
||||
activeTab === "gemini_api"
|
||||
? "border-primary bg-primary/10 text-primary shadow-sm"
|
||||
: "border-border bg-card hover:border-primary/50 hover:bg-muted text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{configTabLabels[tabId]}
|
||||
<ProviderIcon providerType="gemini" size={20} />
|
||||
<span className="text-sm font-medium">Gemini</span>
|
||||
{(config?.credential_pool?.gemini_api_keys?.length ?? 0) > 0 && (
|
||||
<span
|
||||
className={`min-w-[1.25rem] h-5 flex items-center justify-center rounded-full text-xs font-medium ${
|
||||
activeTab === "gemini_api"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted-foreground/20 text-muted-foreground group-hover:bg-primary/20 group-hover:text-primary"
|
||||
}`}
|
||||
>
|
||||
{config?.credential_pool?.gemini_api_keys?.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeCategory === "config" && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(["vertex", "amp"] as const).map((tabId) => {
|
||||
const isActive = activeTab === tabId;
|
||||
return (
|
||||
<button
|
||||
key={tabId}
|
||||
onClick={() => setActiveTab(tabId)}
|
||||
className={`px-3 py-2 rounded-lg border text-sm font-medium transition-all ${
|
||||
isActive
|
||||
? "border-primary bg-primary/10 text-primary shadow-sm"
|
||||
: "border-border bg-card hover:border-primary/50 hover:bg-muted text-muted-foreground hover:text-foreground"
|
||||
}`}
|
||||
>
|
||||
{configTabLabels[tabId]}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 配置 Tab 内容 */}
|
||||
{isConfigTab(activeTab) ? (
|
||||
@@ -432,13 +555,16 @@ export const ProviderPoolPage = forwardRef<ProviderPoolPageRef>(
|
||||
})
|
||||
}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={configSaving}
|
||||
className="w-full px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{configSaving ? "保存中..." : "保存配置"}
|
||||
</button>
|
||||
{(config.credential_pool?.gemini_api_keys?.length ?? 0) >
|
||||
0 && (
|
||||
<button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={configSaving}
|
||||
className="w-full px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{configSaving ? "保存中..." : "保存配置"}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -456,13 +582,16 @@ export const ProviderPoolPage = forwardRef<ProviderPoolPageRef>(
|
||||
})
|
||||
}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={configSaving}
|
||||
className="w-full px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{configSaving ? "保存中..." : "保存配置"}
|
||||
</button>
|
||||
{(config.credential_pool?.vertex_api_keys?.length ?? 0) >
|
||||
0 && (
|
||||
<button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={configSaving}
|
||||
className="w-full px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{configSaving ? "保存中..." : "保存配置"}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -537,15 +666,15 @@ export const ProviderPoolPage = forwardRef<ProviderPoolPageRef>(
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
重置状态
|
||||
</button>
|
||||
<button
|
||||
onClick={openAddModal}
|
||||
className="flex items-center gap-1 rounded-lg bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
添加凭证
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={openAddModal}
|
||||
className="flex items-center gap-1 rounded-lg bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
添加凭证
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
|
||||
import { RefreshCw, Route, Sparkles, Check, Trash2 } from "lucide-react";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import { ModelMapping } from "./ModelMapping";
|
||||
import { RoutingRules } from "./RoutingRules";
|
||||
import { ExclusionList } from "./ExclusionList";
|
||||
@@ -293,84 +294,81 @@ export const RoutingPage = forwardRef<RoutingPageRef, RoutingPageProps>(
|
||||
)}
|
||||
|
||||
{/* Presets Modal */}
|
||||
{showPresets && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div className="w-full max-w-2xl rounded-lg bg-background p-6 shadow-xl max-h-[80vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-primary" />
|
||||
推荐配置
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowPresets(false)}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
<Modal
|
||||
isOpen={showPresets}
|
||||
onClose={() => setShowPresets(false)}
|
||||
maxWidth="max-w-2xl"
|
||||
className="max-h-[80vh] overflow-y-auto"
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-primary" />
|
||||
推荐配置
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
选择一个预设配置快速设置路由规则和模型别名
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
{presets.map((preset) => (
|
||||
<div
|
||||
key={preset.id}
|
||||
className="rounded-lg border p-4 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
选择一个预设配置快速设置路由规则和模型别名
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
{presets.map((preset) => (
|
||||
<div
|
||||
key={preset.id}
|
||||
className="rounded-lg border p-4 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium">{preset.name}</h4>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{preset.description}
|
||||
</p>
|
||||
<div className="flex gap-4 mt-2 text-xs text-muted-foreground">
|
||||
<span>{preset.aliases.length} 个别名</span>
|
||||
<span>{preset.rules.length} 条规则</span>
|
||||
{preset.endpoint_providers && (
|
||||
<span>
|
||||
{
|
||||
Object.values(preset.endpoint_providers).filter(
|
||||
(v) => v,
|
||||
).length
|
||||
}{" "}
|
||||
个客户端路由
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<button
|
||||
onClick={() => handleApplyPreset(preset.id, true)}
|
||||
disabled={applyingPreset !== null}
|
||||
className="flex items-center gap-1 rounded px-3 py-1.5 text-sm border hover:bg-muted disabled:opacity-50"
|
||||
>
|
||||
{applyingPreset === preset.id ? (
|
||||
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Check className="h-3 w-3" />
|
||||
)}
|
||||
合并
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleApplyPreset(preset.id, false)}
|
||||
disabled={applyingPreset !== null}
|
||||
className="flex items-center gap-1 rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{applyingPreset === preset.id ? (
|
||||
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Check className="h-3 w-3" />
|
||||
)}
|
||||
应用
|
||||
</button>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium">{preset.name}</h4>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{preset.description}
|
||||
</p>
|
||||
<div className="flex gap-4 mt-2 text-xs text-muted-foreground">
|
||||
<span>{preset.aliases.length} 个别名</span>
|
||||
<span>{preset.rules.length} 条规则</span>
|
||||
{preset.endpoint_providers && (
|
||||
<span>
|
||||
{
|
||||
Object.values(preset.endpoint_providers).filter(
|
||||
(v) => v,
|
||||
).length
|
||||
}{" "}
|
||||
个客户端路由
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 ml-4">
|
||||
<button
|
||||
onClick={() => handleApplyPreset(preset.id, true)}
|
||||
disabled={applyingPreset !== null}
|
||||
className="flex items-center gap-1 rounded px-3 py-1.5 text-sm border hover:bg-muted disabled:opacity-50"
|
||||
>
|
||||
{applyingPreset === preset.id ? (
|
||||
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Check className="h-3 w-3" />
|
||||
)}
|
||||
合并
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleApplyPreset(preset.id, false)}
|
||||
disabled={applyingPreset !== null}
|
||||
className="flex items-center gap-1 rounded bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
|
||||
>
|
||||
{applyingPreset === preset.id ? (
|
||||
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Check className="h-3 w-3" />
|
||||
)}
|
||||
应用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<HelpTip title="智能路由说明" variant="blue">
|
||||
<ul className="list-disc list-inside space-y-1 text-sm text-blue-700 dark:text-blue-400">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M24 14.014c-2.8 1.512-5.62 2.896-8.759 3.524-.7.139-1.476.139-2.187.043-.678-.085-1.017-.682-.776-1.31.23-.585.536-1.181.93-1.671.852-1.065 1.814-2.034 2.678-3.088a15.75 15.75 0 001.422-2.054c.306-.511.164-1.129-.372-1.384-.897-.437-1.859-.745-2.81-1.075-.11-.043-.274.074-.492.149.273.244.47.425.743.67-2.821.48-5.49 1.16-8.08 2.098-.012.053-.033.095-.023.117.383.585.208 1.032-.35 1.394a2.365 2.365 0 00-.568.522c1.706.5 3.226.213 4.68-.735-.087-.127-.175-.244-.262-.372.546.096.874.394.918.862.011.107-.054.213-.087.32-.077-.086-.175-.17-.24-.267-.045-.064-.056-.138-.088-.245-1.728 1.15-3.587 1.438-5.632.842 0 .404-.022.745.011 1.075.022.287-.098.415-.36.564-.591.362-1.204.735-1.696 1.214-.59.585-.371 1.299.427 1.597.907.34 1.859.35 2.81.234 1.126-.139 2.23-.32 3.456-.49-1.433.67-2.844 1.14-4.33 1.33-1.04.14-2.078.214-3.106-.084-1.476-.415-2.133-1.501-1.75-2.96.361-1.363 1.236-2.449 2.176-3.45 3.139-3.332 7.108-5.024 11.7-5.365 1.072-.074 2.155.064 3.16.511 1.411.639 2.002 1.99 1.313 3.354-.448.905-1.072 1.735-1.695 2.555-.612.809-1.301 1.554-1.946 2.331-.186.234-.361.48-.503.745-.274.5-.088.83.492.778 1.213-.118 2.45-.213 3.62-.511 1.716-.437 3.389-1.054 5.084-1.597.175-.043.339-.107.492-.17z" fill="#FF6003" fill-rule="evenodd"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" fill="#6366F1"></path></svg>
|
||||
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 935 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M6.763 11.212c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 01-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 01-.287-.375 6.18 6.18 0 01-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.39-.384-.59-.894-.59-1.533 0-.678.24-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.4 2.4 0 01-.28.104.488.488 0 01-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 01.224-.167 4.577 4.577 0 011.005-.36 4.84 4.84 0 011.246-.151c.95 0 1.644.216 2.091.647.44.43.662 1.085.662 1.963v2.586h.016zm-3.24 1.214c.263 0 .534-.048.822-.144a1.78 1.78 0 00.758-.51 1.27 1.27 0 00.272-.512c.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 00-.735-.136 6.02 6.02 0 00-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 6.726a1.398 1.398 0 01-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 01.32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 01.311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 01-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 01-.303.08h-.687c-.15 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32L12.32 7.747l-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08l-.686.001zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 01-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.32.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 00.415-.758.777.777 0 00-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 01-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .36.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 01.24.2.43.43 0 01.071.263v.375c0 .168-.064.256-.184.256a.83.83 0 01-.303-.096 3.652 3.652 0 00-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.16.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926a2.157 2.157 0 01-.583.703c-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167z"></path><path d="M.378 15.475c3.384 1.963 7.56 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.44-.2.814.287.383.607-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351zm23.531-.2c.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151l.175-.439c.343-.88.802-2.198.52-2.555-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399z" fill="#F90"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M7.242 1.613A1.11 1.11 0 018.295.857h6.977L8.03 22.316a1.11 1.11 0 01-1.052.755h-5.43a1.11 1.11 0 01-1.053-1.466L7.242 1.613z" fill="url(#azure-fill-0)"></path><path d="M18.397 15.296H7.4a.51.51 0 00-.347.882l7.066 6.595c.206.192.477.298.758.298h6.226l-2.706-7.775z" fill="#0078D4"></path><path d="M15.272.857H7.497L0 23.071h7.775l1.596-4.73 5.068 4.73h6.665l-2.707-7.775h-7.998L15.272.857z" fill="url(#azure-fill-1)"></path><path d="M17.193 1.613a1.11 1.11 0 00-1.052-.756h-7.81.035c.477 0 .9.304 1.052.756l6.748 19.992a1.11 1.11 0 01-1.052 1.466h-.12 7.895a1.11 1.11 0 001.052-1.466L17.193 1.613z" fill="url(#azure-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="azure-fill-0" x1="8.247" x2="1.002" y1="1.626" y2="23.03"><stop stop-color="#114A8B"></stop><stop offset="1" stop-color="#0669BC"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="azure-fill-1" x1="14.042" x2="12.324" y1="15.302" y2="15.888"><stop stop-opacity=".3"></stop><stop offset=".071" stop-opacity=".2"></stop><stop offset=".321" stop-opacity=".1"></stop><stop offset=".623" stop-opacity=".05"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="azure-fill-2" x1="12.841" x2="20.793" y1="1.626" y2="22.814"><stop stop-color="#3CCBF4"></stop><stop offset="1" stop-color="#2892DF"></stop></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em"><title>Copilot</title><path d="M17.533 1.829A2.528 2.528 0 0015.11 0h-.737a2.531 2.531 0 00-2.484 2.087l-1.263 6.937.314-1.08a2.528 2.528 0 012.424-1.833h4.284l1.797.706 1.731-.706h-.505a2.528 2.528 0 01-2.423-1.829l-.715-2.453z" fill="url(#lobe-icons-copilot-fill-0)" transform="translate(0 1)"/><path d="M6.726 20.16A2.528 2.528 0 009.152 22h1.566c1.37 0 2.49-1.1 2.525-2.48l.17-6.69-.357 1.228a2.528 2.528 0 01-2.423 1.83h-4.32l-1.54-.842-1.667.843h.497c1.124 0 2.113.75 2.426 1.84l.697 2.432z" fill="url(#lobe-icons-copilot-fill-1)" transform="translate(0 1)"/><path d="M15 0H6.252c-2.5 0-4 3.331-5 6.662-1.184 3.947-2.734 9.225 1.75 9.225H6.78c1.13 0 2.12-.753 2.43-1.847.657-2.317 1.809-6.359 2.713-9.436.46-1.563.842-2.906 1.43-3.742A1.97 1.97 0 0115 0" fill="url(#lobe-icons-copilot-fill-2)" transform="translate(0 1)"/><path d="M15 0H6.252c-2.5 0-4 3.331-5 6.662-1.184 3.947-2.734 9.225 1.75 9.225H6.78c1.13 0 2.12-.753 2.43-1.847.657-2.317 1.809-6.359 2.713-9.436.46-1.563.842-2.906 1.43-3.742A1.97 1.97 0 0115 0" fill="url(#lobe-icons-copilot-fill-3)" transform="translate(0 1)"/><path d="M9 22h8.749c2.5 0 4-3.332 5-6.663 1.184-3.948 2.734-9.227-1.75-9.227H17.22c-1.129 0-2.12.754-2.43 1.848a1149.2 1149.2 0 01-2.713 9.437c-.46 1.564-.842 2.907-1.43 3.743A1.97 1.97 0 019 22" fill="url(#lobe-icons-copilot-fill-4)" transform="translate(0 1)"/><path d="M9 22h8.749c2.5 0 4-3.332 5-6.663 1.184-3.948 2.734-9.227-1.75-9.227H17.22c-1.129 0-2.12.754-2.43 1.848a1149.2 1149.2 0 01-2.713 9.437c-.46 1.564-.842 2.907-1.43 3.743A1.97 1.97 0 019 22" fill="url(#lobe-icons-copilot-fill-5)" transform="translate(0 1)"/><defs><radialGradient cx="85.44%" cy="100.653%" fx="85.44%" fy="100.653%" gradientTransform="scale(-.8553 -1) rotate(50.927 2.041 -1.946)" id="lobe-icons-copilot-fill-0" r="105.116%"><stop offset="9.6%" stop-color="#00AEFF"/><stop offset="77.3%" stop-color="#2253CE"/><stop offset="100%" stop-color="#0736C4"/></radialGradient><radialGradient cx="18.143%" cy="32.928%" fx="18.143%" fy="32.928%" gradientTransform="scale(.8897 1) rotate(52.069 .193 .352)" id="lobe-icons-copilot-fill-1" r="95.612%"><stop offset="0%" stop-color="#FFB657"/><stop offset="63.4%" stop-color="#FF5F3D"/><stop offset="92.3%" stop-color="#C02B3C"/></radialGradient><radialGradient cx="82.987%" cy="-9.792%" fx="82.987%" fy="-9.792%" gradientTransform="scale(-1 -.9441) rotate(-70.872 .142 1.17)" id="lobe-icons-copilot-fill-4" r="140.622%"><stop offset="6.6%" stop-color="#8C48FF"/><stop offset="50%" stop-color="#F2598A"/><stop offset="89.6%" stop-color="#FFB152"/></radialGradient><linearGradient id="lobe-icons-copilot-fill-2" x1="39.465%" x2="46.884%" y1="12.117%" y2="103.774%"><stop offset="15.6%" stop-color="#0D91E1"/><stop offset="48.7%" stop-color="#52B471"/><stop offset="65.2%" stop-color="#98BD42"/><stop offset="93.7%" stop-color="#FFC800"/></linearGradient><linearGradient id="lobe-icons-copilot-fill-3" x1="45.949%" x2="50%" y1="0%" y2="100%"><stop offset="0%" stop-color="#3DCBFF"/><stop offset="24.7%" stop-color="#0588F7" stop-opacity="0"/></linearGradient><linearGradient id="lobe-icons-copilot-fill-5" x1="83.507%" x2="83.453%" y1="-6.106%" y2="21.131%"><stop offset="5.8%" stop-color="#F8ADFA"/><stop offset="70.8%" stop-color="#A86EDD" stop-opacity="0"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M5.31 15.756c.172-3.75 1.883-5.999 2.549-6.739-3.26 2.058-5.425 5.658-6.358 8.308v1.12C1.501 21.513 4.226 24 7.59 24a6.59 6.59 0 002.2-.375c.353-.12.7-.248 1.039-.378.913-.899 1.65-1.91 2.243-2.992-4.877 2.431-7.974.072-7.763-4.5l.002.001z" fill="#1E37FC"></path><path d="M22.57 10.283c-1.212-.901-4.109-2.404-7.397-2.8.295 3.792.093 8.766-2.1 12.773a12.782 12.782 0 01-2.244 2.992c3.764-1.448 6.746-3.457 8.596-5.219 2.82-2.683 3.353-5.178 3.361-6.66a2.737 2.737 0 00-.216-1.084v-.002z" fill="#37E1BE"></path><path d="M14.303 1.867C12.955.7 11.248 0 9.39 0 7.532 0 5.883.677 4.545 1.807 2.791 3.29 1.627 5.557 1.5 8.125v9.201c.932-2.65 3.097-6.25 6.357-8.307.5-.318 1.025-.595 1.569-.829 1.883-.801 3.878-.932 5.746-.706-.222-2.83-.718-5.002-.87-5.617h.001z" fill="#A569FF"></path><path d="M17.305 4.961a199.47 199.47 0 01-1.08-1.094c-.202-.213-.398-.419-.586-.622l-1.333-1.378c.151.615.648 2.786.869 5.617 3.288.395 6.185 1.898 7.396 2.8-1.306-1.275-3.475-3.487-5.266-5.323z" fill="#1E37FC"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M23 12.245c0-.905-.075-1.565-.236-2.25h-10.54v4.083h6.186c-.124 1.014-.797 2.542-2.294 3.569l-.021.136 3.332 2.53.23.022C21.779 18.417 23 15.593 23 12.245z" fill="#4285F4"></path><path d="M12.225 23c3.03 0 5.574-.978 7.433-2.665l-3.542-2.688c-.948.648-2.22 1.1-3.891 1.1a6.745 6.745 0 01-6.386-4.572l-.132.011-3.465 2.628-.045.124C4.043 20.531 7.835 23 12.225 23z" fill="#34A853"></path><path d="M5.84 14.175A6.65 6.65 0 015.463 12c0-.758.138-1.491.361-2.175l-.006-.147-3.508-2.67-.115.054A10.831 10.831 0 001 12c0 1.772.436 3.447 1.197 4.938l3.642-2.763z" fill="#FBBC05"></path><path d="M12.225 5.253c2.108 0 3.529.892 4.34 1.638l3.167-3.031C17.787 2.088 15.255 1 12.225 1 7.834 1 4.043 3.469 2.197 7.062l3.63 2.763a6.77 6.77 0 016.398-4.572z" fill="#EB4335"></path></svg>
|
||||
|
After Width: | Height: | Size: 867 B |
@@ -1,6 +1,48 @@
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useMemo, ComponentType, SVGProps } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { providerIcons, providerTypeToIcon } from "./utils";
|
||||
import { providerTypeToIcon } from "./utils";
|
||||
|
||||
// 使用 SVGR 导入 SVG 组件
|
||||
import AwsIcon from "./aws.svg?react";
|
||||
import GeminiIcon from "./gemini.svg?react";
|
||||
import AnthropicIcon from "./anthropic.svg?react";
|
||||
import ClaudeIcon from "./claude.svg?react";
|
||||
import QwenIcon from "./qwen.svg?react";
|
||||
import GoogleIcon from "./google.svg?react";
|
||||
import OpenaiIcon from "./openai.svg?react";
|
||||
import AlibabaIcon from "./alibaba.svg?react";
|
||||
import CopilotIcon from "./copilot.svg?react";
|
||||
import AmpIcon from "./amp.svg?react";
|
||||
import KiroIcon from "./kiro.svg?react";
|
||||
import DeepseekIcon from "./deepseek.svg?react";
|
||||
import ZhipuIcon from "./zhipu.svg?react";
|
||||
import KimiIcon from "./kimi.svg?react";
|
||||
import MinimaxIcon from "./minimax.svg?react";
|
||||
import DoubaoIcon from "./doubao.svg?react";
|
||||
import AzureIcon from "./azure.svg?react";
|
||||
import AntigravityIcon from "./antigravity.svg?react";
|
||||
|
||||
// 图标组件映射
|
||||
const iconComponents: Record<string, ComponentType<SVGProps<SVGSVGElement>>> = {
|
||||
aws: AwsIcon,
|
||||
gemini: GeminiIcon,
|
||||
anthropic: AnthropicIcon,
|
||||
claude: ClaudeIcon,
|
||||
qwen: QwenIcon,
|
||||
google: GoogleIcon,
|
||||
openai: OpenaiIcon,
|
||||
alibaba: AlibabaIcon,
|
||||
copilot: CopilotIcon,
|
||||
amp: AmpIcon,
|
||||
kiro: KiroIcon,
|
||||
deepseek: DeepseekIcon,
|
||||
zhipu: ZhipuIcon,
|
||||
kimi: KimiIcon,
|
||||
minimax: MinimaxIcon,
|
||||
doubao: DoubaoIcon,
|
||||
azure: AzureIcon,
|
||||
antigravity: AntigravityIcon,
|
||||
};
|
||||
|
||||
interface ProviderIconProps {
|
||||
providerType: string;
|
||||
@@ -16,7 +58,7 @@ export const ProviderIcon: React.FC<ProviderIconProps> = ({
|
||||
showFallback = true,
|
||||
}) => {
|
||||
const iconName = providerTypeToIcon[providerType] || providerType;
|
||||
const iconSvg = providerIcons[iconName];
|
||||
const IconComponent = iconComponents[iconName];
|
||||
|
||||
const sizeStyle = useMemo(() => {
|
||||
const sizeValue = typeof size === "number" ? `${size}px` : size;
|
||||
@@ -28,7 +70,7 @@ export const ProviderIcon: React.FC<ProviderIconProps> = ({
|
||||
};
|
||||
}, [size]);
|
||||
|
||||
if (iconSvg) {
|
||||
if (IconComponent) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
@@ -36,8 +78,9 @@ export const ProviderIcon: React.FC<ProviderIconProps> = ({
|
||||
className,
|
||||
)}
|
||||
style={sizeStyle}
|
||||
dangerouslySetInnerHTML={{ __html: iconSvg }}
|
||||
/>
|
||||
>
|
||||
<IconComponent width="1em" height="1em" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M19.738 5.776c.163-.209.306-.4.457-.585.07-.087.064-.153-.004-.244-.655-.861-.717-1.817-.34-2.787.283-.73.909-1.072 1.674-1.145.477-.045.945.004 1.379.236.57.305.902.77 1.01 1.412.086.512.07 1.012-.075 1.508-.257.878-.888 1.333-1.753 1.448-.718.096-1.446.108-2.17.157-.056.004-.113 0-.178 0z" fill="#027AFF"></path><path d="M17.962 1.844h-4.326l-3.425 7.81H5.369V1.878H1.5V22h3.87v-8.477h6.824a3.025 3.025 0 002.743-1.75V22h3.87v-8.477a3.87 3.87 0 00-3.588-3.86v-.01h-2.125a3.94 3.94 0 002.323-2.12l2.545-5.689z"></path></svg>
|
||||
|
After Width: | Height: | Size: 660 B |
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200" fill="none">
|
||||
<rect width="1200" height="1200" rx="260" fill="#9046FF" />
|
||||
<mask id="mask0_1106_4856" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="272" y="202" width="655"
|
||||
height="796">
|
||||
<path d="M926.578 202.793H272.637V997.857H926.578V202.793Z" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_1106_4856)">
|
||||
<path
|
||||
d="M398.554 818.914C316.315 1001.03 491.477 1046.74 620.672 940.156C658.687 1059.66 801.052 970.473 852.234 877.795C964.787 673.567 919.318 465.357 907.64 422.374C827.637 129.443 427.623 128.946 358.8 423.865C342.651 475.544 342.402 534.18 333.458 595.051C328.986 625.86 325.507 645.488 313.83 677.785C306.873 696.424 297.68 712.819 282.773 740.645C259.915 783.881 269.604 867.113 387.87 823.883L399.051 818.914H398.554Z"
|
||||
fill="white" />
|
||||
<path
|
||||
d="M636.123 549.353C603.328 549.353 598.359 510.097 598.359 486.742C598.359 465.623 602.086 448.977 609.293 438.293C615.504 428.852 624.697 424.131 636.123 424.131C647.555 424.131 657.492 428.852 664.447 438.541C672.398 449.474 676.623 466.12 676.623 486.742C676.623 525.998 661.471 549.353 636.375 549.353H636.123Z"
|
||||
fill="black" />
|
||||
<path
|
||||
d="M771.24 549.353C738.445 549.353 733.477 510.097 733.477 486.742C733.477 465.623 737.203 448.977 744.41 438.293C750.621 428.852 759.814 424.131 771.24 424.131C782.672 424.131 792.609 428.852 799.564 438.541C807.516 449.474 811.74 466.12 811.74 486.742C811.74 525.998 796.588 549.353 771.492 549.353H771.24Z"
|
||||
fill="black" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="minimax-gradient" x1="0%" y1="50%" x2="100%" y2="50%"><stop offset="0%" style="stop-color:#E2167E;stop-opacity:1" /><stop offset="100%" style="stop-color:#FE603C;stop-opacity:1" /></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#minimax-gradient)" fill-rule="nonzero"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#qwen-fill)" fill-rule="nonzero"></path><defs><linearGradient id="qwen-fill" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,11 +1,33 @@
|
||||
// Provider 图标辅助函数
|
||||
|
||||
// 可用的图标名称列表
|
||||
export const availableIcons = [
|
||||
"aws",
|
||||
"gemini",
|
||||
"anthropic",
|
||||
"claude",
|
||||
"qwen",
|
||||
"google",
|
||||
"openai",
|
||||
"alibaba",
|
||||
"copilot",
|
||||
"amp",
|
||||
"kiro",
|
||||
"deepseek",
|
||||
"zhipu",
|
||||
"kimi",
|
||||
"minimax",
|
||||
"doubao",
|
||||
"azure",
|
||||
"antigravity",
|
||||
] as const;
|
||||
|
||||
// Provider 类型到图标名称的映射
|
||||
const providerTypeToIcon: Record<string, string> = {
|
||||
export const providerTypeToIcon: Record<string, string> = {
|
||||
kiro: "kiro",
|
||||
gemini: "gemini",
|
||||
qwen: "qwen",
|
||||
antigravity: "gemini",
|
||||
antigravity: "antigravity",
|
||||
openai: "openai",
|
||||
claude: "claude",
|
||||
anthropic: "anthropic",
|
||||
@@ -22,40 +44,16 @@ const providerTypeToIcon: Record<string, string> = {
|
||||
minimax: "minimax",
|
||||
doubao: "doubao",
|
||||
azure: "azure",
|
||||
aws: "aws",
|
||||
};
|
||||
|
||||
// Provider 图标 SVG 内联定义
|
||||
export const providerIcons: Record<string, string> = {
|
||||
aws: `<svg fill="currentColor" fill-rule="evenodd" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M6.763 11.212c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 01-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 01-.287-.375 6.18 6.18 0 01-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.39-.384-.59-.894-.59-1.533 0-.678.24-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.4 2.4 0 01-.28.104.488.488 0 01-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 01.224-.167 4.577 4.577 0 011.005-.36 4.84 4.84 0 011.246-.151c.95 0 1.644.216 2.091.647.44.43.662 1.085.662 1.963v2.586h.016zm-3.24 1.214c.263 0 .534-.048.822-.144a1.78 1.78 0 00.758-.51 1.27 1.27 0 00.272-.512c.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 00-.735-.136 6.02 6.02 0 00-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 6.726a1.398 1.398 0 01-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 01.32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 01.311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 01-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 01-.303.08h-.687c-.15 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32L12.32 7.747l-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08l-.686.001zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 01-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.32.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 00.415-.758.777.777 0 00-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 01-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .36.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 01.24.2.43.43 0 01.071.263v.375c0 .168-.064.256-.184.256a.83.83 0 01-.303-.096 3.652 3.652 0 00-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.16.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926a2.157 2.157 0 01-.583.703c-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167z"></path><path d="M.378 15.475c3.384 1.963 7.56 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.44-.2.814.287.383.607-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351zm23.531-.2c.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151l.175-.439c.343-.88.802-2.198.52-2.555-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399z" fill="#F90"></path></svg>`,
|
||||
gemini: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path></svg>`,
|
||||
anthropic: `<svg fill="currentColor" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z"></path></svg>`,
|
||||
claude: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>`,
|
||||
qwen: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z" fill="url(#qwen-fill)" fill-rule="nonzero"></path><defs><linearGradient id="qwen-fill" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#6336E7" stop-opacity=".84"></stop><stop offset="100%" stop-color="#6F69F7" stop-opacity=".84"></stop></linearGradient></defs></svg>`,
|
||||
google: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M23 12.245c0-.905-.075-1.565-.236-2.25h-10.54v4.083h6.186c-.124 1.014-.797 2.542-2.294 3.569l-.021.136 3.332 2.53.23.022C21.779 18.417 23 15.593 23 12.245z" fill="#4285F4"></path><path d="M12.225 23c3.03 0 5.574-.978 7.433-2.665l-3.542-2.688c-.948.648-2.22 1.1-3.891 1.1a6.745 6.745 0 01-6.386-4.572l-.132.011-3.465 2.628-.045.124C4.043 20.531 7.835 23 12.225 23z" fill="#34A853"></path><path d="M5.84 14.175A6.65 6.65 0 015.463 12c0-.758.138-1.491.361-2.175l-.006-.147-3.508-2.67-.115.054A10.831 10.831 0 001 12c0 1.772.436 3.447 1.197 4.938l3.642-2.763z" fill="#FBBC05"></path><path d="M12.225 5.253c2.108 0 3.529.892 4.34 1.638l3.167-3.031C17.787 2.088 15.255 1 12.225 1 7.834 1 4.043 3.469 2.197 7.062l3.63 2.763a6.77 6.77 0 016.398-4.572z" fill="#EB4335"></path></svg>`,
|
||||
openai: `<svg fill="currentColor" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.002zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z"></path></svg>`,
|
||||
alibaba: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M24 14.014c-2.8 1.512-5.62 2.896-8.759 3.524-.7.139-1.476.139-2.187.043-.678-.085-1.017-.682-.776-1.31.23-.585.536-1.181.93-1.671.852-1.065 1.814-2.034 2.678-3.088a15.75 15.75 0 001.422-2.054c.306-.511.164-1.129-.372-1.384-.897-.437-1.859-.745-2.81-1.075-.11-.043-.274.074-.492.149.273.244.47.425.743.67-2.821.48-5.49 1.16-8.08 2.098-.012.053-.033.095-.023.117.383.585.208 1.032-.35 1.394a2.365 2.365 0 00-.568.522c1.706.5 3.226.213 4.68-.735-.087-.127-.175-.244-.262-.372.546.096.874.394.918.862.011.107-.054.213-.087.32-.077-.086-.175-.17-.24-.267-.045-.064-.056-.138-.088-.245-1.728 1.15-3.587 1.438-5.632.842 0 .404-.022.745.011 1.075.022.287-.098.415-.36.564-.591.362-1.204.735-1.696 1.214-.59.585-.371 1.299.427 1.597.907.34 1.859.35 2.81.234 1.126-.139 2.23-.32 3.456-.49-1.433.67-2.844 1.14-4.33 1.33-1.04.14-2.078.214-3.106-.084-1.476-.415-2.133-1.501-1.75-2.96.361-1.363 1.236-2.449 2.176-3.45 3.139-3.332 7.108-5.024 11.7-5.365 1.072-.074 2.155.064 3.16.511 1.411.639 2.002 1.99 1.313 3.354-.448.905-1.072 1.735-1.695 2.555-.612.809-1.301 1.554-1.946 2.331-.186.234-.361.48-.503.745-.274.5-.088.83.492.778 1.213-.118 2.45-.213 3.62-.511 1.716-.437 3.389-1.054 5.084-1.597.175-.043.339-.107.492-.17z" fill="#FF6003" fill-rule="evenodd"></path></svg>`,
|
||||
copilot: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M17.533 1.829A2.528 2.528 0 0015.11 0h-.737a2.531 2.531 0 00-2.484 2.087l-1.263 6.937.314-1.08a2.528 2.528 0 012.424-1.833h4.284l1.797.706 1.731-.706h-.505a2.528 2.528 0 01-2.423-1.829l-.715-2.453z" fill="#00AEFF" transform="translate(0 1)"></path><path d="M6.726 20.16A2.528 2.528 0 009.152 22h1.566c1.37 0 2.49-1.1 2.525-2.48l.17-6.69-.357 1.228a2.528 2.528 0 01-2.423 1.83h-4.32l-1.54-.842-1.667.843h.497c1.124 0 2.113.75 2.426 1.84l.697 2.432z" fill="#FFB657" transform="translate(0 1)"></path><path d="M15 0H6.252c-2.5 0-4 3.331-5 6.662-1.184 3.947-2.734 9.225 1.75 9.225H6.78c1.13 0 2.12-.753 2.43-1.847.657-2.317 1.809-6.359 2.713-9.436.46-1.563.842-2.906 1.43-3.742A1.97 1.97 0 0115 0" fill="#0D91E1" transform="translate(0 1)"></path><path d="M9 22h8.749c2.5 0 4-3.332 5-6.663 1.184-3.948 2.734-9.227-1.75-9.227H17.22c-1.129 0-2.12.754-2.43 1.848a1149.2 1149.2 0 01-2.713 9.437c-.46 1.564-.842 2.907-1.43 3.743A1.97 1.97 0 019 22" fill="#8C48FF" transform="translate(0 1)"></path></svg>`,
|
||||
amp: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" fill="#6366F1"></path></svg>`,
|
||||
kiro: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="kiro-gradient" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#7C3AED;stop-opacity:1" /><stop offset="100%" style="stop-color:#6D28D9;stop-opacity:1" /></linearGradient></defs><rect width="24" height="24" rx="6" fill="url(#kiro-gradient)"/><path d="M8 10c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm6 0c0-1.1.9-2 2-2s2 .9 2 2-.9 2-2 2-2-.9-2-2zm-7 8c0 2.2 1.8 4 4 4s4-1.8 4-4h-2c0 1.1-.9 2-2 2s-2-.9-2-2h-2z" fill="white"/></svg>`,
|
||||
deepseek: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>`,
|
||||
zhipu: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M11.991 23.503a.24.24 0 00-.244.248.24.24 0 00.244.249.24.24 0 00.245-.249.24.24 0 00-.22-.247l-.025-.001zM9.671 5.365a1.697 1.697 0 011.099 2.132l-.071.172-.016.04-.018.054c-.07.16-.104.32-.104.498-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368 0 .124.018.23.053.337.209.373.54.658.96.8.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711.854.284 1.378 1.226 1.099 2.167a1.661 1.661 0 01-2.077 1.102 1.711 1.711 0 01-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 00-1.954.746 1.66 1.66 0 01-1.065.764 1.677 1.677 0 01-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.51 1.51 0 01.296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.307 1.307 0 00-.227-.693 2.515 2.515 0 01-.366-1.403 2.39 2.39 0 01.366-1.208c.14-.195.21-.444.227-.693.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.43 1.43 0 01-.299-.07l-.05-.019a1.7 1.7 0 01-1.047-2.114 1.68 1.68 0 012.094-1.101zm-5.575 10.11c.26-.264.639-.367.994-.27.355.096.633.379.728.74.095.362-.007.748-.267 1.013-.402.41-1.053.41-1.455 0a1.062 1.062 0 010-1.482zm14.845-.294c.359-.09.738.024.992.297.254.274.344.665.237 1.025-.107.36-.396.634-.756.718-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 01.757-1.26zm-.064-4.39c.314.32.49.753.49 1.206 0 .452-.176.886-.49 1.206-.315.32-.74.5-1.185.5-.444 0-.87-.18-1.184-.5a1.727 1.727 0 010-2.412 1.654 1.654 0 012.369 0zm-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 01-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 011.33-1.038c.593-.08 1.183.169 1.547.652zm11.545-4.221c.368 0 .708.2.892.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.892.524c-.568 0-1.03-.47-1.03-1.048 0-.579.462-1.048 1.03-1.048zm-14.358 0c.368 0 .707.2.891.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.891.524c-.569 0-1.03-.47-1.03-1.048 0-.579.461-1.048 1.03-1.048zm10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705-1.674-.763-1.674-1.705c0-.942.75-1.706 1.674-1.706zm-2.626-.684c.362-.082.653-.356.761-.718a1.062 1.062 0 00-.238-1.028 1.017 1.017 0 00-.996-.294c-.547.14-.881.7-.752 1.257.13.558.675.907 1.225.783zm0 16.876c.359-.087.644-.36.75-.72a1.062 1.062 0 00-.237-1.019 1.018 1.018 0 00-.985-.301 1.037 1.037 0 00-.762.717c-.108.361-.017.754.239 1.028.245.263.606.377.953.305l.043-.01zM17.19 3.5a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64a.631.631 0 00-.628.64c0 .355.28.64.628.64zm-10.38 0a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64a.631.631 0 00-.628.64c0 .355.279.64.628.64zm-5.182 7.852a.631.631 0 00-.628.64c0 .354.28.639.628.639a.63.63 0 00.627-.606l.001-.034a.62.62 0 00-.628-.64zm5.182 9.13a.631.631 0 00-.628.64c0 .355.279.64.628.64a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm10.38.018a.631.631 0 00-.628.64c0 .355.28.64.628.64a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64zm5.182-9.148a.631.631 0 00-.628.64c0 .354.279.639.628.639a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 00.244-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249c0 .142.122.249.244.249zM11.991.497a.24.24 0 00.245-.248A.24.24 0 0011.99 0a.24.24 0 00-.244.249c0 .133.108.236.223.247l.021.001zM2.011 6.36a.24.24 0 00.245-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249.24.24 0 00.244.249zm0 11.263a.24.24 0 00-.243.248.24.24 0 00.244.249.24.24 0 00.244-.249.252.252 0 00-.244-.248zm19.995-.018a.24.24 0 00-.245.248.24.24 0 00.245.25.24.24 0 00.244-.25.252.252 0 00-.244-.248z" fill="#3859FF" fill-rule="nonzero"></path></svg>`,
|
||||
kimi: `<svg fill="currentColor" fill-rule="evenodd" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M19.738 5.776c.163-.209.306-.4.457-.585.07-.087.064-.153-.004-.244-.655-.861-.717-1.817-.34-2.787.283-.73.909-1.072 1.674-1.145.477-.045.945.004 1.379.236.57.305.902.77 1.01 1.412.086.512.07 1.012-.075 1.508-.257.878-.888 1.333-1.753 1.448-.718.096-1.446.108-2.17.157-.056.004-.113 0-.178 0z" fill="#027AFF"></path><path d="M17.962 1.844h-4.326l-3.425 7.81H5.369V1.878H1.5V22h3.87v-8.477h6.824a3.025 3.025 0 002.743-1.75V22h3.87v-8.477a3.87 3.87 0 00-3.588-3.86v-.01h-2.125a3.94 3.94 0 002.323-2.12l2.545-5.689z"></path></svg>`,
|
||||
minimax: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="minimax-gradient" x1="0%" y1="50%" x2="100%" y2="50%"><stop offset="0%" style="stop-color:#E2167E;stop-opacity:1" /><stop offset="100%" style="stop-color:#FE603C;stop-opacity:1" /></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#minimax-gradient)" fill-rule="nonzero"></path></svg>`,
|
||||
doubao: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M5.31 15.756c.172-3.75 1.883-5.999 2.549-6.739-3.26 2.058-5.425 5.658-6.358 8.308v1.12C1.501 21.513 4.226 24 7.59 24a6.59 6.59 0 002.2-.375c.353-.12.7-.248 1.039-.378.913-.899 1.65-1.91 2.243-2.992-4.877 2.431-7.974.072-7.763-4.5l.002.001z" fill="#1E37FC"></path><path d="M22.57 10.283c-1.212-.901-4.109-2.404-7.397-2.8.295 3.792.093 8.766-2.1 12.773a12.782 12.782 0 01-2.244 2.992c3.764-1.448 6.746-3.457 8.596-5.219 2.82-2.683 3.353-5.178 3.361-6.66a2.737 2.737 0 00-.216-1.084v-.002z" fill="#37E1BE"></path><path d="M14.303 1.867C12.955.7 11.248 0 9.39 0 7.532 0 5.883.677 4.545 1.807 2.791 3.29 1.627 5.557 1.5 8.125v9.201c.932-2.65 3.097-6.25 6.357-8.307.5-.318 1.025-.595 1.569-.829 1.883-.801 3.878-.932 5.746-.706-.222-2.83-.718-5.002-.87-5.617h.001z" fill="#A569FF"></path><path d="M17.305 4.961a199.47 199.47 0 01-1.08-1.094c-.202-.213-.398-.419-.586-.622l-1.333-1.378c.151.615.648 2.786.869 5.617 3.288.395 6.185 1.898 7.396 2.8-1.306-1.275-3.475-3.487-5.266-5.323z" fill="#1E37FC"></path></svg>`,
|
||||
azure: `<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M7.242 1.613A1.11 1.11 0 018.295.857h6.977L8.03 22.316a1.11 1.11 0 01-1.052.755h-5.43a1.11 1.11 0 01-1.053-1.466L7.242 1.613z" fill="url(#azure-fill-0)"></path><path d="M18.397 15.296H7.4a.51.51 0 00-.347.882l7.066 6.595c.206.192.477.298.758.298h6.226l-2.706-7.775z" fill="#0078D4"></path><path d="M15.272.857H7.497L0 23.071h7.775l1.596-4.73 5.068 4.73h6.665l-2.707-7.775h-7.998L15.272.857z" fill="url(#azure-fill-1)"></path><path d="M17.193 1.613a1.11 1.11 0 00-1.052-.756h-7.81.035c.477 0 .9.304 1.052.756l6.748 19.992a1.11 1.11 0 01-1.052 1.466h-.12 7.895a1.11 1.11 0 001.052-1.466L17.193 1.613z" fill="url(#azure-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="azure-fill-0" x1="8.247" x2="1.002" y1="1.626" y2="23.03"><stop stop-color="#114A8B"></stop><stop offset="1" stop-color="#0669BC"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="azure-fill-1" x1="14.042" x2="12.324" y1="15.302" y2="15.888"><stop stop-opacity=".3"></stop><stop offset=".071" stop-opacity=".2"></stop><stop offset=".321" stop-opacity=".1"></stop><stop offset=".623" stop-opacity=".05"></stop><stop offset="1" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="azure-fill-2" x1="12.841" x2="20.793" y1="1.626" y2="22.814"><stop stop-color="#3CCBF4"></stop><stop offset="1" stop-color="#2892DF"></stop></linearGradient></defs></svg>`,
|
||||
// 获取规范化的图标名称
|
||||
export const getIconName = (providerType: string): string => {
|
||||
return providerTypeToIcon[providerType] || providerType;
|
||||
};
|
||||
|
||||
// 导出图标名称检查函数
|
||||
// 检查是否有对应的图标
|
||||
export const hasProviderIcon = (providerType: string): boolean => {
|
||||
const iconName = providerTypeToIcon[providerType] || providerType;
|
||||
return iconName in providerIcons;
|
||||
const iconName = getIconName(providerType);
|
||||
return (availableIcons as readonly string[]).includes(iconName);
|
||||
};
|
||||
|
||||
// 导出获取图标 SVG 函数
|
||||
export const getProviderIcon = (providerType: string): string | undefined => {
|
||||
const iconName = providerTypeToIcon[providerType] || providerType;
|
||||
return providerIcons[iconName];
|
||||
};
|
||||
|
||||
// 导出 Provider 类型映射
|
||||
export { providerTypeToIcon };
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M11.991 23.503a.24.24 0 00-.244.248.24.24 0 00.244.249.24.24 0 00.245-.249.24.24 0 00-.22-.247l-.025-.001zM9.671 5.365a1.697 1.697 0 011.099 2.132l-.071.172-.016.04-.018.054c-.07.16-.104.32-.104.498-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368 0 .124.018.23.053.337.209.373.54.658.96.8.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711.854.284 1.378 1.226 1.099 2.167a1.661 1.661 0 01-2.077 1.102 1.711 1.711 0 01-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 00-1.954.746 1.66 1.66 0 01-1.065.764 1.677 1.677 0 01-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.51 1.51 0 01.296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.307 1.307 0 00-.227-.693 2.515 2.515 0 01-.366-1.403 2.39 2.39 0 01.366-1.208c.14-.195.21-.444.227-.693.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.43 1.43 0 01-.299-.07l-.05-.019a1.7 1.7 0 01-1.047-2.114 1.68 1.68 0 012.094-1.101zm-5.575 10.11c.26-.264.639-.367.994-.27.355.096.633.379.728.74.095.362-.007.748-.267 1.013-.402.41-1.053.41-1.455 0a1.062 1.062 0 010-1.482zm14.845-.294c.359-.09.738.024.992.297.254.274.344.665.237 1.025-.107.36-.396.634-.756.718-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 01.757-1.26zm-.064-4.39c.314.32.49.753.49 1.206 0 .452-.176.886-.49 1.206-.315.32-.74.5-1.185.5-.444 0-.87-.18-1.184-.5a1.727 1.727 0 010-2.412 1.654 1.654 0 012.369 0zm-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 01-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 011.33-1.038c.593-.08 1.183.169 1.547.652zm11.545-4.221c.368 0 .708.2.892.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.892.524c-.568 0-1.03-.47-1.03-1.048 0-.579.462-1.048 1.03-1.048zm-14.358 0c.368 0 .707.2.891.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.891.524c-.569 0-1.03-.47-1.03-1.048 0-.579.461-1.048 1.03-1.048zm10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705-1.674-.763-1.674-1.705c0-.942.75-1.706 1.674-1.706zm-2.626-.684c.362-.082.653-.356.761-.718a1.062 1.062 0 00-.238-1.028 1.017 1.017 0 00-.996-.294c-.547.14-.881.7-.752 1.257.13.558.675.907 1.225.783zm0 16.876c.359-.087.644-.36.75-.72a1.062 1.062 0 00-.237-1.019 1.018 1.018 0 00-.985-.301 1.037 1.037 0 00-.762.717c-.108.361-.017.754.239 1.028.245.263.606.377.953.305l.043-.01zM17.19 3.5a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64a.631.631 0 00-.628.64c0 .355.28.64.628.64zm-10.38 0a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64a.631.631 0 00-.628.64c0 .355.279.64.628.64zm-5.182 7.852a.631.631 0 00-.628.64c0 .354.28.639.628.639a.63.63 0 00.627-.606l.001-.034a.62.62 0 00-.628-.64zm5.182 9.13a.631.631 0 00-.628.64c0 .355.279.64.628.64a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm10.38.018a.631.631 0 00-.628.64c0 .355.28.64.628.64a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64zm5.182-9.148a.631.631 0 00-.628.64c0 .354.279.639.628.639a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 00.244-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249c0 .142.122.249.244.249zM11.991.497a.24.24 0 00.245-.248A.24.24 0 0011.99 0a.24.24 0 00-.244.249c0 .133.108.236.223.247l.021.001zM2.011 6.36a.24.24 0 00.245-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249.24.24 0 00.244.249zm0 11.263a.24.24 0 00-.243.248.24.24 0 00.244.249.24.24 0 00.244-.249.252.252 0 00-.244-.248zm19.995-.018a.24.24 0 00-.245.248.24.24 0 00.245.25.24.24 0 00.244-.25.252.252 0 00-.244-.248z" fill="#3859FF" fill-rule="nonzero"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -51,4 +51,29 @@
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
/* Custom scrollbar styles */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--muted-foreground) / 0.3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--muted-foreground) / 0.5);
|
||||
}
|
||||
|
||||
/* Firefox scrollbar */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
|
||||
// 全局类型声明
|
||||
declare global {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [react(), svgr()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
|
||||