├── src ├── vite-env.d.ts ├── components │ ├── mcp │ │ └── index.ts │ ├── extensions │ │ ├── index.ts │ │ └── ExtensionsPage.tsx │ ├── websocket │ │ └── index.ts │ ├── plugins │ │ └── index.ts │ ├── prompts │ │ └── index.ts │ ├── skills │ │ └── index.ts │ ├── resilience │ │ ├── index.ts │ │ └── ResiliencePage.tsx │ ├── monitoring │ │ └── index.ts │ ├── switch │ │ ├── index.ts │ │ ├── SwitchPage.tsx │ │ └── AppTabs.tsx │ ├── clients │ │ ├── index.ts │ │ ├── ClientsPage.tsx │ │ └── AppTabs.tsx │ ├── config │ │ ├── index.ts │ │ └── ConfigManagementPage.tsx │ ├── routing │ │ ├── index.ts │ │ └── RoutingManagementPage.tsx │ ├── provider-pool │ │ ├── credential-forms │ │ │ ├── index.ts │ │ │ ├── ModeSelector.tsx │ │ │ ├── types.ts │ │ │ ├── FileImportForm.tsx │ │ │ └── OAuthUrlDisplay.tsx │ │ └── index.ts │ ├── flow-monitor │ │ └── index.ts │ ├── settings │ │ ├── index.ts │ │ ├── SettingsPage.tsx │ │ └── GeneralSettings.test.ts │ ├── ui │ │ └── sonner.tsx │ ├── HelpTip.tsx │ ├── Sidebar.tsx │ └── ConfirmDialog.tsx ├── hooks │ ├── index.ts │ ├── useErrorHandler.ts │ ├── useFileMonitoring.ts │ ├── useSkills.ts │ ├── useSwitch.ts │ └── useProviderState.ts ├── pages │ └── index.ts ├── main.tsx ├── icons │ ├── providers │ │ ├── anthropic.svg │ │ ├── openai.svg │ │ ├── index.tsx │ │ └── gemini.svg │ ├── app-icon-compact.svg │ └── app-icon.svg ├── lib │ ├── utils.ts │ └── api │ │ ├── usage.ts │ │ ├── routes.ts │ │ ├── mcp.ts │ │ ├── resilience.ts │ │ ├── skills.ts │ │ ├── switch.ts │ │ ├── injection.ts │ │ ├── prompts.ts │ │ └── credentials.ts ├── index.css └── App.tsx ├── src-tauri ├── build.rs ├── icons │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 32x32.png │ ├── 64x64.png │ ├── icon.icns │ ├── StoreLogo.png │ ├── 128x128@2x.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── tray │ │ ├── tray-error.png │ │ ├── tray-running.png │ │ ├── tray-stopped.png │ │ ├── tray-warning.png │ │ ├── trayTemplate.png │ │ └── trayTemplate@2x.png │ ├── ios │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x-1.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ └── AppIcon-83.5x83.5@2x.png │ └── android │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── values │ │ └── ic_launcher_background.xml │ │ └── mipmap-anydpi-v26 │ │ └── ic_launcher.xml ├── src │ ├── database │ │ ├── dao │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── migration.rs │ ├── main.rs │ ├── proxy │ │ └── mod.rs │ ├── middleware │ │ └── mod.rs │ ├── services │ │ ├── mod.rs │ │ └── prompt_sync.rs │ ├── server │ │ └── handlers │ │ │ └── mod.rs │ ├── injection │ │ └── mod.rs │ ├── commands │ │ ├── mod.rs │ │ ├── mcp_cmd.rs │ │ ├── switch_cmd.rs │ │ └── prompt_cmd.rs │ ├── tray │ │ └── mod.rs │ ├── processor │ │ └── steps │ │ │ ├── mod.rs │ │ │ └── traits.rs │ ├── plugin │ │ └── mod.rs │ ├── converter │ │ └── mod.rs │ ├── telemetry │ │ └── mod.rs │ ├── resilience │ │ └── mod.rs │ ├── models │ │ ├── mod.rs │ │ ├── prompt_model.rs │ │ ├── mcp_model.rs │ │ ├── app_type.rs │ │ ├── provider_model.rs │ │ └── skill_model.rs │ ├── credential │ │ └── mod.rs │ ├── router │ │ └── mod.rs │ ├── config │ │ └── mod.rs │ ├── providers │ │ └── mod.rs │ ├── streaming │ │ └── mod.rs │ └── flow_monitor │ │ └── mod.rs ├── gen │ └── schemas │ │ └── capabilities.json ├── capabilities │ └── default.json ├── proptest-regressions │ ├── proxy │ │ └── tests.txt │ ├── flow_monitor │ │ ├── memory_store.txt │ │ ├── file_store.txt │ │ └── stream_rebuilder.txt │ ├── router │ │ └── tests.txt │ ├── providers │ │ └── tests.txt │ ├── websocket │ │ └── tests.txt │ └── config │ │ └── tests.txt ├── tauri.conf.json └── Cargo.toml ├── postcss.config.js ├── docs ├── images │ ├── 067c7d64-e116-4a30-b533-748873166f37.png │ ├── 151b4355-821c-4bda-a731-c4367b6b8716.png │ ├── 25eb018a-5be2-4f82-ba22-e68f39160cac.png │ ├── 943663ed-b17c-4b32-a74c-c0243ffb3dea.png │ ├── aee62eb5-3aeb-4454-b14d-24b1d5f9a0fe.png │ ├── c7d8236b-ea6c-4496-ada5-288cd0a01738.png │ └── ffc70018-aa5f-4738-883d-045614488608.png ├── nuxt.config.ts ├── package.json ├── app.config.ts └── content │ ├── 07.legal │ └── 1.disclaimer.md │ ├── 02.user-guide │ ├── 1.dashboard.md │ ├── 2.monitoring.md │ ├── 3.credential-pool.md │ ├── 10.prompts.md │ ├── 7.config-switch.md │ ├── 5.resilience.md │ ├── 12.settings.md │ ├── 6.config-management.md │ ├── 11.skills.md │ └── 4.smart-routing.md │ ├── 01.introduction │ ├── 2.installation.md │ ├── 1.overview.md │ └── 3.quickstart.md │ ├── 03.providers │ ├── 2.kiro-claude.md │ ├── 7.codex.md │ ├── 3.gemini-cli.md │ ├── 4.qwen.md │ └── 5.openai-custom.md │ ├── 05.troubleshooting │ ├── 1.common-issues.md │ ├── 2.credential-errors.md │ └── 3.connection-issues.md │ └── 04.api-reference │ └── 1.overview.md ├── .gitignore ├── tsconfig.node.json ├── index.html ├── vite.config.ts ├── .claude └── settings.local.json ├── tsconfig.json ├── scripts └── update-version.sh ├── .husky └── pre-commit ├── eslint.config.js ├── .github └── workflows │ ├── ci.yml │ └── deploy-docs.yml ├── public └── vite.svg ├── tailwind.config.js ├── AGENTS.md └── package.json /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/components/mcp/index.ts: -------------------------------------------------------------------------------- 1 | export { McpPage } from "./McpPage"; 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useFlowEvents } from "./useFlowEvents"; 2 | -------------------------------------------------------------------------------- /src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export { FlowMonitorPage } from "./FlowMonitorPage"; 2 | -------------------------------------------------------------------------------- /src/components/extensions/index.ts: -------------------------------------------------------------------------------- 1 | export { ExtensionsPage } from "./ExtensionsPage"; 2 | -------------------------------------------------------------------------------- /src/components/websocket/index.ts: -------------------------------------------------------------------------------- 1 | export { WebSocketStatus } from "./WebSocketStatus"; 2 | -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/64x64.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/tray/tray-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/tray/tray-error.png -------------------------------------------------------------------------------- /src-tauri/icons/tray/tray-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/tray/tray-running.png -------------------------------------------------------------------------------- /src-tauri/icons/tray/tray-stopped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/tray/tray-stopped.png -------------------------------------------------------------------------------- /src-tauri/icons/tray/tray-warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/tray/tray-warning.png -------------------------------------------------------------------------------- /src-tauri/icons/tray/trayTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/tray/trayTemplate.png -------------------------------------------------------------------------------- /src/components/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { PluginManager } from "./PluginManager"; 2 | export { default } from "./PluginManager"; 3 | -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/tray/trayTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/tray/trayTemplate@2x.png -------------------------------------------------------------------------------- /src-tauri/src/database/dao/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod mcp; 2 | pub mod prompts; 3 | pub mod provider_pool; 4 | pub mod providers; 5 | pub mod skills; 6 | -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 | 3 | fn main() { 4 | proxycast_lib::run() 5 | } 6 | -------------------------------------------------------------------------------- /docs/images/067c7d64-e116-4a30-b533-748873166f37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/067c7d64-e116-4a30-b533-748873166f37.png -------------------------------------------------------------------------------- /docs/images/151b4355-821c-4bda-a731-c4367b6b8716.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/151b4355-821c-4bda-a731-c4367b6b8716.png -------------------------------------------------------------------------------- /docs/images/25eb018a-5be2-4f82-ba22-e68f39160cac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/25eb018a-5be2-4f82-ba22-e68f39160cac.png -------------------------------------------------------------------------------- /docs/images/943663ed-b17c-4b32-a74c-c0243ffb3dea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/943663ed-b17c-4b32-a74c-c0243ffb3dea.png -------------------------------------------------------------------------------- /docs/images/aee62eb5-3aeb-4454-b14d-24b1d5f9a0fe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/aee62eb5-3aeb-4454-b14d-24b1d5f9a0fe.png -------------------------------------------------------------------------------- /docs/images/c7d8236b-ea6c-4496-ada5-288cd0a01738.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/c7d8236b-ea6c-4496-ada5-288cd0a01738.png -------------------------------------------------------------------------------- /docs/images/ffc70018-aa5f-4738-883d-045614488608.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/docs/images/ffc70018-aa5f-4738-883d-045614488608.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/components/prompts/index.ts: -------------------------------------------------------------------------------- 1 | export { PromptsPage } from "./PromptsPage"; 2 | export { PromptCard } from "./PromptCard"; 3 | export { PromptForm } from "./PromptForm"; 4 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src/components/skills/index.ts: -------------------------------------------------------------------------------- 1 | export { SkillsPage } from "./SkillsPage"; 2 | export { SkillCard } from "./SkillCard"; 3 | export { RepoManagerPanel } from "./RepoManagerPanel"; 4 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #fff 4 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiclientproxy/proxycast/HEAD/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src/components/resilience/index.ts: -------------------------------------------------------------------------------- 1 | export { RetrySettings } from "./RetrySettings"; 2 | export { FailoverSettings } from "./FailoverSettings"; 3 | export { ResiliencePage } from "./ResiliencePage"; 4 | -------------------------------------------------------------------------------- /src-tauri/gen/schemas/capabilities.json: -------------------------------------------------------------------------------- 1 | {"default":{"identifier":"default","description":"Default capabilities for ProxyCast","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","dialog:default"]}} -------------------------------------------------------------------------------- /src/components/monitoring/index.ts: -------------------------------------------------------------------------------- 1 | export { MonitoringPage } from "./MonitoringPage"; 2 | export { StatsOverview } from "./StatsOverview"; 3 | export { LogViewer } from "./LogViewer"; 4 | export { TokenStats } from "./TokenStats"; 5 | -------------------------------------------------------------------------------- /src-tauri/src/proxy/mod.rs: -------------------------------------------------------------------------------- 1 | //! 代理模块 2 | //! 3 | //! 提供 Per-Key 代理支持,允许为每个凭证配置独立的代理设置 4 | 5 | mod client_factory; 6 | #[cfg(test)] 7 | mod tests; 8 | 9 | pub use client_factory::{ProxyClientFactory, ProxyError, ProxyProtocol}; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build 5 | dist/ 6 | src-tauri/target/ 7 | 8 | # IDE 9 | .vscode/ 10 | .idea/ 11 | 12 | # OS 13 | .DS_Store 14 | Thumbs.db 15 | 16 | # Logs 17 | *.log 18 | 19 | # Kiro 20 | .kiro/ 21 | -------------------------------------------------------------------------------- /src-tauri/src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | //! Middleware 模块 2 | //! 3 | //! 提供 HTTP 请求处理的中间件组件 4 | 5 | pub mod management_auth; 6 | 7 | #[cfg(test)] 8 | mod tests; 9 | 10 | pub use management_auth::{ManagementAuthLayer, ManagementAuthService}; 11 | -------------------------------------------------------------------------------- /src/components/switch/index.ts: -------------------------------------------------------------------------------- 1 | export { SwitchPage } from "./SwitchPage"; 2 | export { AppTabs } from "./AppTabs"; 3 | export { ProviderList } from "./ProviderList"; 4 | export { ProviderCard } from "./ProviderCard"; 5 | export { ProviderForm } from "./ProviderForm"; 6 | -------------------------------------------------------------------------------- /src/components/clients/index.ts: -------------------------------------------------------------------------------- 1 | export { ClientsPage } from "./ClientsPage"; 2 | export { AppTabs } from "./AppTabs"; 3 | export { ProviderList } from "./ProviderList"; 4 | export { ProviderCard } from "./ProviderCard"; 5 | export { ProviderForm } from "./ProviderForm"; 6 | -------------------------------------------------------------------------------- /src/components/config/index.ts: -------------------------------------------------------------------------------- 1 | export { ConfigPage } from "./ConfigPage"; 2 | export { ConfigEditor } from "./ConfigEditor"; 3 | export { ImportExport } from "./ImportExport"; 4 | export { AuthDirSettings } from "./AuthDirSettings"; 5 | export { ConfigManagementPage } from "./ConfigManagementPage"; 6 | -------------------------------------------------------------------------------- /src-tauri/src/services/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod live_sync; 2 | pub mod mcp_service; 3 | pub mod mcp_sync; 4 | pub mod prompt_service; 5 | pub mod prompt_sync; 6 | pub mod provider_pool_service; 7 | pub mod skill_service; 8 | pub mod switch; 9 | pub mod token_cache_service; 10 | pub mod usage_service; 11 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ["docus"], 3 | app: { 4 | baseURL: "/proxycast/", 5 | }, 6 | image: { 7 | provider: "none", 8 | }, 9 | robots: { 10 | robotsTxt: false, 11 | }, 12 | llms: { 13 | domain: "https://proxycast.local", 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src-tauri/src/server/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP 请求处理器模块 2 | //! 3 | //! 将 server 中的各类处理器拆分到独立文件 4 | 5 | pub mod api; 6 | pub mod management; 7 | pub mod provider_calls; 8 | pub mod websocket; 9 | 10 | pub use api::*; 11 | pub use management::*; 12 | pub use provider_calls::*; 13 | pub use websocket::*; 14 | -------------------------------------------------------------------------------- /src-tauri/src/injection/mod.rs: -------------------------------------------------------------------------------- 1 | //! 参数注入模块 2 | //! 3 | //! 提供请求参数注入功能,支持: 4 | //! - 模型通配符匹配规则 5 | //! - merge 和 override 两种注入模式 6 | //! - 规则优先级排序 7 | 8 | mod types; 9 | 10 | pub use types::{InjectionConfig, InjectionMode, InjectionResult, InjectionRule, Injector}; 11 | 12 | #[cfg(test)] 13 | mod tests; 14 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schemas.tauri.app/config/2/capability", 3 | "identifier": "default", 4 | "description": "Default capabilities for ProxyCast", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:default", 8 | "shell:allow-open", 9 | "dialog:default" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import { Toaster } from "./components/ui/sonner"; 5 | import "./index.css"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")!).render( 8 | 9 | 10 | 11 | , 12 | ); 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxycast-docs", 3 | "scripts": { 4 | "dev": "nuxt dev --extends docus", 5 | "build": "nuxt build --extends docus", 6 | "generate": "nuxt generate --extends docus" 7 | }, 8 | "dependencies": { 9 | "docus": "latest", 10 | "better-sqlite3": "^12.2.0", 11 | "nuxt": "^4.2.1", 12 | "mermaid": "^11.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/icons/providers/anthropic.svg: -------------------------------------------------------------------------------- 1 | Anthropic -------------------------------------------------------------------------------- /src/components/routing/index.ts: -------------------------------------------------------------------------------- 1 | export { ModelMapping } from "./ModelMapping"; 2 | export { RoutingRules } from "./RoutingRules"; 3 | export { ExclusionList } from "./ExclusionList"; 4 | export { InjectionRules } from "./InjectionRules"; 5 | export { RoutingPage } from "./RoutingPage"; 6 | export type { RoutingPageRef } from "./RoutingPage"; 7 | export { RoutingManagementPage } from "./RoutingManagementPage"; 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ProxyCast 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/provider-pool/credential-forms/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 凭证表单组件导出 3 | */ 4 | 5 | export * from "./types"; 6 | export * from "./ModeSelector"; 7 | export * from "./OAuthUrlDisplay"; 8 | export * from "./FileImportForm"; 9 | export * from "./AntigravityForm"; 10 | export * from "./CodexForm"; 11 | export * from "./ClaudeOAuthForm"; 12 | export * from "./QwenForm"; 13 | export * from "./IFlowForm"; 14 | export * from "./GeminiForm"; 15 | -------------------------------------------------------------------------------- /src/components/flow-monitor/index.ts: -------------------------------------------------------------------------------- 1 | export { FlowList } from "./FlowList"; 2 | export { FlowFilters } from "./FlowFilters"; 3 | export { FlowDetail } from "./FlowDetail"; 4 | export { FlowTimeline, FlowTimelineCompact } from "./FlowTimeline"; 5 | export { FlowStats } from "./FlowStats"; 6 | export { ExportDialog } from "./ExportDialog"; 7 | export { useFlowEvents } from "@/hooks/useFlowEvents"; 8 | export { useFlowActions } from "@/hooks/useFlowActions"; 9 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/proxy/tests.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 99af5a6dad66f0b5a2650223417a2e83a7a3bfa8b6eab8ad57a88023367e739c # shrinks to url = "http://08:1024" 8 | -------------------------------------------------------------------------------- /src-tauri/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config_cmd; 2 | pub mod flow_monitor_cmd; 3 | pub mod injection_cmd; 4 | pub mod mcp_cmd; 5 | pub mod oauth_cmd; 6 | pub mod plugin_cmd; 7 | pub mod prompt_cmd; 8 | pub mod provider_pool_cmd; 9 | pub mod resilience_cmd; 10 | pub mod route_cmd; 11 | pub mod router_cmd; 12 | pub mod skill_cmd; 13 | pub mod switch_cmd; 14 | pub mod telemetry_cmd; 15 | pub mod tray_cmd; 16 | pub mod usage_cmd; 17 | pub mod websocket_cmd; 18 | -------------------------------------------------------------------------------- /src-tauri/src/tray/mod.rs: -------------------------------------------------------------------------------- 1 | //! 系统托盘模块 2 | //! 3 | //! 提供系统托盘功能,包括: 4 | //! - 托盘图标状态管理 5 | //! - 托盘菜单构建 6 | //! - 菜单事件处理 7 | //! - 托盘图标点击事件处理 8 | //! - 状态同步 9 | 10 | mod events; 11 | mod format; 12 | mod manager; 13 | mod menu; 14 | mod menu_handler; 15 | mod state; 16 | mod sync; 17 | 18 | pub use events::*; 19 | pub use format::*; 20 | pub use manager::*; 21 | pub use menu::*; 22 | pub use menu_handler::*; 23 | pub use state::*; 24 | pub use sync::*; 25 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/flow_monitor/memory_store.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 4ef289c82d7068ccd05f549e93999e0d88c53e9d4ad4ebbcac1b33d00993d259 # shrinks to prefix = "ot" 8 | -------------------------------------------------------------------------------- /src/components/settings/index.ts: -------------------------------------------------------------------------------- 1 | export { SettingsPage } from "./SettingsPage"; 2 | export { GeneralSettings } from "./GeneralSettings"; 3 | export { ProxySettings } from "./ProxySettings"; 4 | export { DirectorySettings } from "./DirectorySettings"; 5 | export { AboutSection } from "./AboutSection"; 6 | export { TlsSettings } from "./TlsSettings"; 7 | export { QuotaSettings } from "./QuotaSettings"; 8 | export { RemoteManagementSettings } from "./RemoteManagementSettings"; 9 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/router/tests.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 98a27aecaee1a9145362c2fa8a876f39010daa37133f7dd9f9713f6c2f8118f7 # shrinks to provider = "google", version = "v1" 8 | -------------------------------------------------------------------------------- /src-tauri/src/processor/steps/mod.rs: -------------------------------------------------------------------------------- 1 | //! 管道步骤模块 2 | //! 3 | //! 定义请求处理管道中的各个步骤 4 | 5 | mod auth; 6 | mod injection; 7 | mod plugin; 8 | mod provider; 9 | mod routing; 10 | mod telemetry; 11 | mod traits; 12 | 13 | pub use auth::AuthStep; 14 | pub use injection::InjectionStep; 15 | pub use plugin::{PluginPostStep, PluginPreStep}; 16 | pub use provider::ProviderStep; 17 | pub use routing::RoutingStep; 18 | pub use telemetry::TelemetryStep; 19 | pub use traits::PipelineStep; 20 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/providers/tests.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 10c52f014c7e9b4cd049a9802452d417b5774e6f42173c2c9329544d8ac4340c # shrinks to lead_time_mins = 21, time_offset_secs = 1260 8 | -------------------------------------------------------------------------------- /src-tauri/src/plugin/mod.rs: -------------------------------------------------------------------------------- 1 | //! 插件系统模块 2 | //! 3 | //! 提供插件扩展功能,支持: 4 | //! - 插件加载和初始化 5 | //! - 请求前/响应后钩子 6 | //! - 插件隔离和错误处理 7 | //! - 插件配置管理 8 | 9 | mod loader; 10 | mod manager; 11 | mod types; 12 | 13 | pub use loader::PluginLoader; 14 | pub use manager::PluginManager; 15 | pub use types::{ 16 | HookResult, Plugin, PluginConfig, PluginContext, PluginError, PluginInfo, PluginManifest, 17 | PluginState, PluginStatus, PluginType, 18 | }; 19 | 20 | #[cfg(test)] 21 | mod tests; 22 | -------------------------------------------------------------------------------- /src-tauri/src/converter/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod anthropic_to_openai; 2 | pub mod cw_to_openai; 3 | pub mod openai_to_antigravity; 4 | pub mod openai_to_cw; 5 | pub mod protocol_selector; 6 | 7 | #[allow(unused_imports)] 8 | pub use anthropic_to_openai::*; 9 | #[allow(unused_imports)] 10 | pub use cw_to_openai::*; 11 | #[allow(unused_imports)] 12 | pub use openai_to_antigravity::*; 13 | #[allow(unused_imports)] 14 | pub use openai_to_cw::*; 15 | #[allow(unused_imports)] 16 | pub use protocol_selector::*; 17 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/flow_monitor/file_store.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 93bc59c922237bf2c4ffc251c66c90f6bdcf7433a6a188b325fe65f38a8a6fd4 # shrinks to flow_count = 1 8 | cc 5f7ef8a17a79bad4599803df567d02ed7c7a29bae560e3e90dfb8b2f920cadf2 # shrinks to flow_count = 5 9 | -------------------------------------------------------------------------------- /src-tauri/src/telemetry/mod.rs: -------------------------------------------------------------------------------- 1 | //! 监控与日志模块 2 | //! 3 | //! 提供请求日志记录、统计聚合和 Token 追踪功能 4 | 5 | mod logger; 6 | mod stats; 7 | mod tokens; 8 | mod types; 9 | 10 | pub use logger::{LogRotationConfig, LoggerError, RequestLogger}; 11 | pub use stats::StatsAggregator; 12 | pub use tokens::{ 13 | ModelTokenStats, PeriodTokenStats, ProviderTokenStats, TokenSource, TokenStatsSummary, 14 | TokenTracker, TokenUsageRecord, 15 | }; 16 | pub use types::{ModelStats, ProviderStats, RequestLog, RequestStatus, StatsSummary, TimeRange}; 17 | 18 | #[cfg(test)] 19 | mod tests; 20 | -------------------------------------------------------------------------------- /src-tauri/src/resilience/mod.rs: -------------------------------------------------------------------------------- 1 | //! 容错机制模块 2 | //! 3 | //! 提供重试、故障转移和超时控制功能 4 | 5 | mod failover; 6 | mod retry; 7 | mod timeout; 8 | 9 | pub use failover::{ 10 | Failover, FailoverConfig, FailoverManager, FailoverResult, FailureType, SwitchEvent, 11 | QUOTA_EXCEEDED_KEYWORDS, QUOTA_EXCEEDED_STATUS_CODES, 12 | }; 13 | pub use retry::{Retrier, RetryConfig, RetryError}; 14 | pub use timeout::{ 15 | CancellationToken, StreamIdleDetector, StreamWithIdleTimeout, TimeoutConfig, TimeoutController, 16 | TimeoutError, 17 | }; 18 | 19 | #[cfg(test)] 20 | mod tests; 21 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | import { Toaster as SonnerToaster } from "sonner"; 2 | 3 | export function Toaster() { 4 | return ( 5 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite"; 3 | import react from "@vitejs/plugin-react"; 4 | import path from "path"; 5 | 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | clearScreen: false, 14 | server: { 15 | port: 1420, 16 | strictPort: true, 17 | watch: { 18 | ignored: ["**/src-tauri/**"], 19 | }, 20 | }, 21 | test: { 22 | globals: true, 23 | environment: "jsdom", 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/provider-pool/index.ts: -------------------------------------------------------------------------------- 1 | export { ProviderPoolPage } from "./ProviderPoolPage"; 2 | export { CredentialCard } from "./CredentialCard"; 3 | export { AddCredentialModal } from "./AddCredentialModal"; 4 | export { EditCredentialModal } from "./EditCredentialModal"; 5 | export { GeminiApiKeySection } from "./GeminiApiKeySection"; 6 | export { VertexAISection } from "./VertexAISection"; 7 | export { CodexSection } from "./CodexSection"; 8 | export { IFlowSection } from "./IFlowSection"; 9 | export { AmpConfigSection } from "./AmpConfigSection"; 10 | export { UsageDisplay } from "./UsageDisplay"; 11 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | /** 9 | * 验证代理 URL 格式 10 | * 支持的格式: 11 | * - http://host:port 12 | * - https://host:port 13 | * - socks5://host:port 14 | * - http://user:pass@host:port(带认证) 15 | */ 16 | export function validateProxyUrl(url: string): boolean { 17 | if (!url || url.trim() === "") return true; // 空值允许 18 | const pattern = /^(https?|socks5?):\/\/[^\s]+$/i; 19 | return pattern.test(url.trim()); 20 | } 21 | -------------------------------------------------------------------------------- /.claude/settings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "Bash(npm run format:*)", 5 | "Bash(npm run lint)", 6 | "Bash(npx tsc:*)", 7 | "Bash(cargo test:*)", 8 | "Bash(cargo build:*)", 9 | "Bash(npm run check:*)", 10 | "Bash(npm run:*)", 11 | "Bash(tree:*)", 12 | "Bash(find:*)", 13 | "Bash(cargo check:*)", 14 | "Bash(rm:*)", 15 | "Bash(cargo clippy:*)", 16 | "Bash(cargo fmt:*)", 17 | "Bash(lsof:*)", 18 | "Bash(xargs kill:*)", 19 | "Bash(cargo run:*)" 20 | ], 21 | "deny": [], 22 | "ask": [] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/websocket/tests.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc bba7155a7ed5c22d19990ebdd9935e761dbdc41bc1e11076fbbf4218b82c8002 # shrinks to request_id = "0-AAaA-a", (original_data, sse_body) = ([" "], "data: \n\n") 8 | cc 4f0d0f49ef961c396cdf1aa7ccbd2a01b525e96ba5a2048d18860dbe763abe37 # shrinks to request_id = "a00000aa", data = " ", index = 0 9 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/flow_monitor/stream_rebuilder.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc be956d5aa14123ea1af5d3e310121b3dec52581c16da860aa2622f1689edd520 # shrinks to tool_call = ("call_00aa00aa", "aa_", "{\"value\":\"aAaA_a\"}") 8 | cc f35fbd7673ecfb016ab0ee416718b5561162ae8ac1b04fbffe0fcebd8467a341 # shrinks to tool_call = ("call_a000a0a0", "__a", "{\"value\":\"aaAaaA\"}") 9 | -------------------------------------------------------------------------------- /src/hooks/useErrorHandler.ts: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react"; 2 | 3 | interface AppError { 4 | message: string; 5 | context?: string; 6 | timestamp: Date; 7 | } 8 | 9 | export function useErrorHandler() { 10 | const [error, setError] = useState(null); 11 | 12 | const handleError = useCallback((e: unknown, context?: string) => { 13 | console.error(`[${context || "Error"}]`, e); 14 | setError({ 15 | message: e instanceof Error ? e.message : String(e), 16 | context, 17 | timestamp: new Date(), 18 | }); 19 | }, []); 20 | 21 | const clearError = useCallback(() => setError(null), []); 22 | 23 | return { error, handleError, clearError }; 24 | } 25 | -------------------------------------------------------------------------------- /src/components/switch/SwitchPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { AppType } from "@/lib/api/switch"; 3 | import { AppTabs } from "./AppTabs"; 4 | import { ProviderList } from "./ProviderList"; 5 | 6 | export function SwitchPage() { 7 | const [activeApp, setActiveApp] = useState("claude"); 8 | 9 | return ( 10 |
11 |
12 |

Switch

13 |

14 | 管理和切换不同应用的 Provider 配置 15 |

16 |
17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src-tauri/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod anthropic; 2 | pub mod app_type; 3 | pub mod codewhisperer; 4 | pub mod mcp_model; 5 | pub mod openai; 6 | pub mod prompt_model; 7 | pub mod provider_model; 8 | pub mod provider_pool_model; 9 | pub mod route_model; 10 | pub mod skill_model; 11 | 12 | #[allow(unused_imports)] 13 | pub use anthropic::*; 14 | pub use app_type::AppType; 15 | #[allow(unused_imports)] 16 | pub use codewhisperer::*; 17 | pub use mcp_model::McpServer; 18 | #[allow(unused_imports)] 19 | pub use openai::*; 20 | pub use prompt_model::Prompt; 21 | pub use provider_model::Provider; 22 | #[allow(unused_imports)] 23 | pub use provider_pool_model::*; 24 | pub use skill_model::{Skill, SkillMetadata, SkillRepo, SkillState, SkillStates}; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "moduleResolution": "bundler", 9 | "allowImportingTsExtensions": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "jsx": "react-jsx", 14 | "strict": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["./src/*"] 21 | } 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src-tauri/src/credential/mod.rs: -------------------------------------------------------------------------------- 1 | //! 凭证池管理模块 2 | //! 3 | //! 提供多凭证管理、负载均衡和健康检查功能 4 | 5 | mod balancer; 6 | mod health; 7 | mod pool; 8 | mod quota; 9 | mod sync; 10 | mod types; 11 | 12 | pub use balancer::{BalanceStrategy, CooldownInfo, CredentialSelection, LoadBalancer}; 13 | pub use health::{HealthCheckConfig, HealthCheckResult, HealthChecker, HealthStatus}; 14 | pub use pool::{CredentialPool, PoolError, PoolStatus}; 15 | pub use quota::{ 16 | create_shared_quota_manager, start_quota_cleanup_task, AllCredentialsExhaustedError, 17 | QuotaAutoSwitchResult, QuotaExceededRecord, QuotaManager, 18 | }; 19 | pub use sync::{CredentialSyncService, SyncError}; 20 | pub use types::{Credential, CredentialData, CredentialStats, CredentialStatus}; 21 | 22 | #[cfg(test)] 23 | mod tests; 24 | -------------------------------------------------------------------------------- /src/lib/api/usage.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | /** 4 | * 用量信息接口 5 | * 6 | * 与后端 UsageInfo 结构对应 7 | * _Requirements: 3.3_ 8 | */ 9 | export interface UsageInfo { 10 | /** 订阅类型名称 */ 11 | subscriptionTitle: string; 12 | /** 总额度 */ 13 | usageLimit: number; 14 | /** 已使用 */ 15 | currentUsage: number; 16 | /** 余额 = usageLimit - currentUsage */ 17 | balance: number; 18 | /** 余额低于 20% */ 19 | isLowBalance: boolean; 20 | } 21 | 22 | /** 23 | * Usage API 24 | */ 25 | export const usageApi = { 26 | /** 27 | * 获取 Kiro 凭证的用量信息 28 | * 29 | * @param credentialUuid - 凭证的 UUID 30 | * @returns 用量信息 31 | */ 32 | getKiroUsage: (credentialUuid: string): Promise => 33 | invoke("get_kiro_usage", { credentialUuid }), 34 | }; 35 | -------------------------------------------------------------------------------- /src-tauri/src/database/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dao; 2 | pub mod migration; 3 | pub mod schema; 4 | 5 | use rusqlite::Connection; 6 | use std::path::PathBuf; 7 | use std::sync::{Arc, Mutex}; 8 | 9 | pub type DbConnection = Arc>; 10 | 11 | /// 获取数据库文件路径 12 | pub fn get_db_path() -> PathBuf { 13 | let home = dirs::home_dir().expect("Cannot find home directory"); 14 | let db_dir = home.join(".proxycast"); 15 | std::fs::create_dir_all(&db_dir).expect("Cannot create .proxycast directory"); 16 | db_dir.join("proxycast.db") 17 | } 18 | 19 | /// 初始化数据库连接 20 | pub fn init_database() -> Result { 21 | let db_path = get_db_path(); 22 | let conn = Connection::open(&db_path)?; 23 | 24 | // 创建表结构 25 | schema::create_tables(&conn)?; 26 | 27 | Ok(Arc::new(Mutex::new(conn))) 28 | } 29 | -------------------------------------------------------------------------------- /src-tauri/src/router/mod.rs: -------------------------------------------------------------------------------- 1 | //! 路由系统模块 2 | //! 3 | //! 支持动态路由注册、命名空间路由解析、模型映射和路由规则。 4 | //! 5 | //! 路由格式: 6 | //! - `/{provider-name}/v1/messages` - Provider 命名空间路由 7 | //! - `/{selector}/v1/messages` - 凭证选择器路由(向后兼容) 8 | //! - `/v1/messages` - 默认路由 9 | //! - `/api/provider/{provider}/v1/*` - Amp CLI 路由 10 | //! 11 | //! 模型映射: 12 | //! - 支持模型别名映射(如 `gpt-4` -> `claude-sonnet-4-5-20250514`) 13 | //! 14 | //! 路由规则: 15 | //! - 支持通配符模式匹配(前缀、后缀、包含) 16 | //! - 支持规则优先级排序 17 | 18 | mod amp_router; 19 | mod mapper; 20 | mod provider_router; 21 | mod route_registry; 22 | mod rules; 23 | 24 | pub use amp_router::{AmpRouteMatch, AmpRouter}; 25 | pub use mapper::{ModelInfo, ModelMapper}; 26 | pub use provider_router::ProviderRouter; 27 | pub use route_registry::{RegisteredRoute, RouteRegistry, RouteType}; 28 | pub use rules::{RouteResult, Router, RoutingRule}; 29 | 30 | #[cfg(test)] 31 | mod tests; 32 | -------------------------------------------------------------------------------- /src/lib/api/routes.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export interface RouteEndpoint { 4 | path: string; 5 | protocol: string; 6 | url: string; 7 | } 8 | 9 | export interface RouteInfo { 10 | selector: string; 11 | provider_type: string; 12 | credential_count: number; 13 | endpoints: RouteEndpoint[]; 14 | tags: string[]; 15 | enabled: boolean; 16 | } 17 | 18 | export interface RouteListResponse { 19 | base_url: string; 20 | default_provider: string; 21 | routes: RouteInfo[]; 22 | } 23 | 24 | export interface CurlExample { 25 | description: string; 26 | command: string; 27 | } 28 | 29 | export const routesApi = { 30 | async getAvailableRoutes(): Promise { 31 | return invoke("get_available_routes"); 32 | }, 33 | 34 | async getCurlExamples(selector: string): Promise { 35 | return invoke("get_route_curl_examples", { selector }); 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | docus: { 3 | title: "ProxyCast", 4 | description: "把你的 AI 客户端额度用到任何地方", 5 | url: "https://aiclientproxy.github.io/proxycast", 6 | 7 | image: "/images/logo-banner.svg", 8 | 9 | socials: { 10 | github: "aiclientproxy/proxycast", 11 | }, 12 | 13 | header: { 14 | logo: true, 15 | title: "ProxyCast", 16 | showLinkIcon: true, 17 | }, 18 | 19 | aside: { 20 | level: 0, 21 | collapsed: false, 22 | exclude: [], 23 | }, 24 | 25 | footer: { 26 | credits: { 27 | icon: "", 28 | text: "Made with 💜 by ProxyCast Team", 29 | href: "https://github.com/aiclientproxy", 30 | }, 31 | textLinks: [ 32 | { 33 | text: "GitHub", 34 | href: "https://github.com/aiclientproxy/proxycast", 35 | target: "_blank", 36 | }, 37 | ], 38 | iconLinks: [], 39 | }, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/icons/app-icon-compact.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | P 24 | 25 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "ProxyCast", 4 | "version": "0.16.0", 5 | "identifier": "com.proxycast.app", 6 | "build": { 7 | "beforeDevCommand": "npm run dev", 8 | "devUrl": "http://localhost:1420", 9 | "beforeBuildCommand": "npm run build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "withGlobalTauri": true, 14 | "windows": [ 15 | { 16 | "title": "ProxyCast", 17 | "width": 1000, 18 | "height": 700, 19 | "resizable": true, 20 | "fullscreen": false 21 | } 22 | ], 23 | "security": { 24 | "csp": null 25 | } 26 | }, 27 | "bundle": { 28 | "active": true, 29 | "targets": "all", 30 | "icon": [ 31 | "icons/32x32.png", 32 | "icons/128x128.png", 33 | "icons/128x128@2x.png", 34 | "icons/icon.icns", 35 | "icons/icon.ico" 36 | ], 37 | "resources": [ 38 | "icons/tray/*" 39 | ] 40 | }, 41 | "plugins": { 42 | "shell": { 43 | "open": true 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src-tauri/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | //! 配置管理模块 2 | //! 3 | //! 提供 YAML 配置文件支持、热重载和配置导入导出功能 4 | //! 同时保持与旧版 JSON 配置的向后兼容性 5 | 6 | mod export; 7 | mod hot_reload; 8 | mod import; 9 | mod path_utils; 10 | mod types; 11 | mod yaml; 12 | 13 | pub use export::{ExportBundle, ExportOptions, ExportService, REDACTED_PLACEHOLDER}; 14 | pub use hot_reload::{ 15 | ConfigChangeEvent, ConfigChangeKind, FileWatcher, HotReloadManager, ReloadResult, 16 | }; 17 | pub use import::{ImportOptions, ImportService, ValidationResult}; 18 | pub use path_utils::{collapse_tilde, contains_tilde, expand_tilde}; 19 | pub use types::{ 20 | AmpConfig, AmpModelMapping, ApiKeyEntry, Config, CredentialEntry, CredentialPoolConfig, 21 | CustomProviderConfig, GeminiApiKeyEntry, IFlowCredentialEntry, InjectionRuleConfig, 22 | InjectionSettings, LoggingConfig, ProviderConfig, ProvidersConfig, QuotaExceededConfig, 23 | RemoteManagementConfig, RetrySettings, RoutingConfig, RoutingRuleConfig, ServerConfig, 24 | TlsConfig, VertexApiKeyEntry, VertexModelAlias, 25 | }; 26 | pub use yaml::{load_config, save_config, ConfigError, ConfigManager, YamlService}; 27 | 28 | #[cfg(test)] 29 | mod tests; 30 | -------------------------------------------------------------------------------- /scripts/update-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 统一版本管理脚本 3 | # 用法: ./scripts/update-version.sh 4 | # 示例: ./scripts/update-version.sh 0.5.0 5 | 6 | set -e 7 | 8 | if [ -z "$1" ]; then 9 | echo "用法: $0 " 10 | echo "示例: $0 0.5.0" 11 | exit 1 12 | fi 13 | 14 | NEW_VERSION="$1" 15 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 16 | PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" 17 | 18 | echo "更新版本到 $NEW_VERSION..." 19 | 20 | # 更新 package.json 21 | sed -i '' "s/\"version\": \"[^\"]*\"/\"version\": \"$NEW_VERSION\"/" "$PROJECT_ROOT/package.json" 22 | echo "✓ package.json" 23 | 24 | # 更新 Cargo.toml 25 | sed -i '' "s/^version = \"[^\"]*\"/version = \"$NEW_VERSION\"/" "$PROJECT_ROOT/src-tauri/Cargo.toml" 26 | echo "✓ src-tauri/Cargo.toml" 27 | 28 | # 更新 tauri.conf.json 29 | sed -i '' "s/\"version\": \"[^\"]*\"/\"version\": \"$NEW_VERSION\"/" "$PROJECT_ROOT/src-tauri/tauri.conf.json" 30 | echo "✓ src-tauri/tauri.conf.json" 31 | 32 | echo "" 33 | echo "版本已更新到 $NEW_VERSION" 34 | echo "" 35 | echo "下一步:" 36 | echo " git add -A" 37 | echo " git commit -m 'chore: bump version to $NEW_VERSION'" 38 | echo " git tag v$NEW_VERSION" 39 | echo " git push origin main --tags" 40 | -------------------------------------------------------------------------------- /docs/content/07.legal/1.disclaimer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 免责声明 3 | description: 使用条款和法律声明 4 | navigation: 5 | icon: i-heroicons-scale 6 | --- 7 | 8 | # 免责声明 9 | 10 | ## 使用目的 11 | 12 | ProxyCast 是一款开源工具,其设计初衷是帮助用户**充分利用已订阅的 AI 服务 Token**,在更多场景中发挥其价值。 13 | 14 | ::alert{type="warning"} 15 | **重要提示**: 本工具仅限于个人合法使用,严禁用于任何非法盈利目的。 16 | :: 17 | 18 | ## 合法使用范围 19 | 20 | 本工具的合法使用场景包括但不限于: 21 | 22 | - ✅ 个人学习和研究 23 | - ✅ 个人开发项目中使用已订阅的 AI 服务 24 | - ✅ 在不同开发工具间复用个人订阅额度 25 | - ✅ 提高个人工作效率 26 | 27 | ## 禁止行为 28 | 29 | 以下行为严格禁止: 30 | 31 | - ❌ 将本工具用于商业盈利目的 32 | - ❌ 转售或分享他人的 AI 服务凭证 33 | - ❌ 违反 AI 服务提供商的服务条款 34 | - ❌ 任何形式的非法活动 35 | 36 | ## 责任声明 37 | 38 | 1. **用户责任**: 用户应确保其使用行为符合所在地区的法律法规,以及相关 AI 服务提供商的服务条款。 39 | 40 | 2. **风险自担**: 使用本工具所产生的任何后果由用户自行承担,包括但不限于账户封禁、服务终止等。 41 | 42 | 3. **无担保**: 本工具按"现状"提供,不提供任何明示或暗示的担保。 43 | 44 | 4. **免责**: 开发者不对因使用本工具而导致的任何直接或间接损失承担责任。 45 | 46 | ## 服务条款遵守 47 | 48 | 使用本工具时,请务必遵守以下服务提供商的使用条款: 49 | 50 | - [Anthropic 使用政策](https://www.anthropic.com/policies) 51 | - [Google AI 服务条款](https://policies.google.com/terms) 52 | - [阿里云服务条款](https://terms.alibabacloud.com/) 53 | - [OpenAI 使用政策](https://openai.com/policies) 54 | 55 | ## 联系我们 56 | 57 | 如对本声明有任何疑问,请通过 GitHub Issues 联系我们。 58 | -------------------------------------------------------------------------------- /src/hooks/useFileMonitoring.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useCallback } from "react"; 2 | 3 | interface MonitorConfig { 4 | checkFn: () => Promise; 5 | interval?: number; 6 | enabled?: boolean; 7 | } 8 | 9 | export function useFileMonitoring(configs: Record) { 10 | const intervalRef = useRef | null>(null); 11 | 12 | const checkAll = useCallback(async () => { 13 | for (const [key, config] of Object.entries(configs)) { 14 | if (config.enabled !== false) { 15 | try { 16 | await config.checkFn(); 17 | } catch (e) { 18 | console.error(`[FileMonitoring:${key}]`, e); 19 | } 20 | } 21 | } 22 | }, [configs]); 23 | 24 | useEffect(() => { 25 | // 获取最小间隔 26 | const intervals = Object.values(configs).map((c) => c.interval || 10000); 27 | const minInterval = Math.min(...intervals); 28 | 29 | intervalRef.current = setInterval(checkAll, minInterval); 30 | 31 | return () => { 32 | if (intervalRef.current) { 33 | clearInterval(intervalRef.current); 34 | } 35 | }; 36 | }, [checkAll, configs]); 37 | 38 | return { checkAll }; 39 | } 40 | -------------------------------------------------------------------------------- /src-tauri/src/models/prompt_model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Serialize, Deserialize)] 4 | pub struct Prompt { 5 | pub id: String, 6 | pub app_type: String, 7 | pub name: String, 8 | pub content: String, 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub description: Option, 11 | /// Whether this prompt is currently enabled (synced to live file) 12 | #[serde(default)] 13 | pub enabled: bool, 14 | #[serde(rename = "createdAt", skip_serializing_if = "Option::is_none")] 15 | pub created_at: Option, 16 | #[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none")] 17 | pub updated_at: Option, 18 | } 19 | 20 | impl Prompt { 21 | #[allow(dead_code)] 22 | pub fn new(id: String, app_type: String, name: String, content: String) -> Self { 23 | let now = chrono::Utc::now().timestamp(); 24 | Self { 25 | id, 26 | app_type, 27 | name, 28 | content, 29 | description: None, 30 | enabled: false, 31 | created_at: Some(now), 32 | updated_at: Some(now), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src-tauri/src/models/mcp_model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct McpServer { 6 | pub id: String, 7 | pub name: String, 8 | pub server_config: Value, 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub description: Option, 11 | #[serde(default)] 12 | pub enabled_proxycast: bool, 13 | #[serde(default)] 14 | pub enabled_claude: bool, 15 | #[serde(default)] 16 | pub enabled_codex: bool, 17 | #[serde(default)] 18 | pub enabled_gemini: bool, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub created_at: Option, 21 | } 22 | 23 | impl McpServer { 24 | #[allow(dead_code)] 25 | pub fn new(id: String, name: String, server_config: Value) -> Self { 26 | Self { 27 | id, 28 | name, 29 | server_config, 30 | description: None, 31 | enabled_proxycast: false, 32 | enabled_claude: false, 33 | enabled_codex: false, 34 | enabled_gemini: false, 35 | created_at: Some(chrono::Utc::now().timestamp()), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "🔍 Running pre-commit checks..." 4 | 5 | # 前端检查 6 | echo "📦 Checking frontend..." 7 | 8 | # TypeScript 检查 9 | echo " → TypeScript check..." 10 | npx tsc --noEmit 11 | if [ $? -ne 0 ]; then 12 | echo "❌ TypeScript check failed!" 13 | exit 1 14 | fi 15 | 16 | # ESLint 检查 17 | echo " → ESLint check..." 18 | npm run lint 19 | if [ $? -ne 0 ]; then 20 | echo "❌ ESLint check failed!" 21 | exit 1 22 | fi 23 | 24 | # Prettier 检查 25 | echo " → Prettier check..." 26 | npx prettier --check "src/**/*.{ts,tsx,css}" 27 | if [ $? -ne 0 ]; then 28 | echo "❌ Prettier check failed! Run 'npm run format' to fix." 29 | exit 1 30 | fi 31 | 32 | # Rust 检查 33 | echo "🦀 Checking Rust..." 34 | 35 | # Rust 格式检查 36 | echo " → Rust format check..." 37 | cd src-tauri 38 | cargo fmt --all -- --check 39 | if [ $? -ne 0 ]; then 40 | echo "❌ Rust format check failed! Run 'cargo fmt' in src-tauri to fix." 41 | exit 1 42 | fi 43 | 44 | # Rust 编译检查 (不使用严格的 clippy) 45 | echo " → Rust build check..." 46 | cargo check --all-targets 47 | if [ $? -ne 0 ]; then 48 | echo "❌ Rust build check failed!" 49 | exit 1 50 | fi 51 | 52 | cd .. 53 | 54 | echo "✅ All pre-commit checks passed!" 55 | -------------------------------------------------------------------------------- /src-tauri/src/models/app_type.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 4 | #[serde(rename_all = "lowercase")] 5 | pub enum AppType { 6 | ProxyCast, 7 | Claude, 8 | Codex, 9 | Gemini, 10 | } 11 | 12 | impl AppType { 13 | pub fn as_str(&self) -> &'static str { 14 | match self { 15 | AppType::ProxyCast => "proxycast", 16 | AppType::Claude => "claude", 17 | AppType::Codex => "codex", 18 | AppType::Gemini => "gemini", 19 | } 20 | } 21 | } 22 | 23 | impl std::str::FromStr for AppType { 24 | type Err = String; 25 | 26 | fn from_str(s: &str) -> Result { 27 | match s.to_lowercase().as_str() { 28 | "proxycast" => Ok(AppType::ProxyCast), 29 | "claude" => Ok(AppType::Claude), 30 | "codex" => Ok(AppType::Codex), 31 | "gemini" => Ok(AppType::Gemini), 32 | _ => Err(format!("Invalid app type: {s}")), 33 | } 34 | } 35 | } 36 | 37 | impl std::fmt::Display for AppType { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | write!(f, "{}", self.as_str()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src-tauri/src/providers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod antigravity; 2 | pub mod claude_custom; 3 | pub mod claude_oauth; 4 | pub mod codex; 5 | pub mod error; 6 | pub mod gemini; 7 | pub mod iflow; 8 | pub mod kiro; 9 | pub mod openai_custom; 10 | pub mod qwen; 11 | pub mod traits; 12 | pub mod vertex; 13 | 14 | #[cfg(test)] 15 | mod tests; 16 | 17 | // Trait exports 18 | #[allow(unused_imports)] 19 | pub use traits::{CredentialProvider, ProviderResult, TokenManager}; 20 | 21 | #[allow(unused_imports)] 22 | pub use antigravity::AntigravityProvider; 23 | #[allow(unused_imports)] 24 | pub use claude_custom::ClaudeCustomProvider; 25 | #[allow(unused_imports)] 26 | pub use claude_oauth::ClaudeOAuthProvider; 27 | #[allow(unused_imports)] 28 | pub use codex::CodexProvider; 29 | #[allow(unused_imports)] 30 | pub use error::ProviderError; 31 | #[allow(unused_imports)] 32 | pub use gemini::{GeminiApiKeyCredential, GeminiApiKeyProvider, GeminiProvider}; 33 | #[allow(unused_imports)] 34 | pub use iflow::IFlowProvider; 35 | #[allow(unused_imports)] 36 | pub use kiro::KiroProvider; 37 | #[allow(unused_imports)] 38 | pub use openai_custom::OpenAICustomProvider; 39 | #[allow(unused_imports)] 40 | pub use qwen::QwenProvider; 41 | #[allow(unused_imports)] 42 | pub use vertex::VertexProvider; 43 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import tseslint from "@typescript-eslint/eslint-plugin"; 3 | import tsparser from "@typescript-eslint/parser"; 4 | import reactHooks from "eslint-plugin-react-hooks"; 5 | import reactRefresh from "eslint-plugin-react-refresh"; 6 | import globals from "globals"; 7 | 8 | export default [ 9 | { ignores: ["dist", "src-tauri", "node_modules"] }, 10 | { 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | parser: tsparser, 16 | parserOptions: { 17 | ecmaFeatures: { jsx: true }, 18 | }, 19 | }, 20 | plugins: { 21 | "@typescript-eslint": tseslint, 22 | "react-hooks": reactHooks, 23 | "react-refresh": reactRefresh, 24 | }, 25 | rules: { 26 | ...js.configs.recommended.rules, 27 | ...tseslint.configs.recommended.rules, 28 | ...reactHooks.configs.recommended.rules, 29 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 30 | "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" }], 31 | "@typescript-eslint/no-explicit-any": "off", 32 | }, 33 | }, 34 | ]; 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build-check: 14 | name: Build Check 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '20' 25 | cache: 'npm' 26 | 27 | - name: Setup Rust 28 | uses: dtolnay/rust-toolchain@stable 29 | 30 | - name: Setup Rust cache 31 | uses: Swatinem/rust-cache@v2 32 | with: 33 | workspaces: src-tauri 34 | shared-key: "rust-cache-build" 35 | 36 | - name: Install system dependencies (Linux) 37 | run: | 38 | sudo apt-get update 39 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev 40 | 41 | - name: Install frontend dependencies 42 | run: npm ci 43 | 44 | - name: Build frontend 45 | run: npm run build 46 | 47 | - name: Check Rust build 48 | working-directory: src-tauri 49 | run: cargo check --all-targets 50 | -------------------------------------------------------------------------------- /src/lib/api/mcp.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export interface McpServer { 4 | id: string; 5 | name: string; 6 | server_config: { 7 | command: string; 8 | args?: string[]; 9 | env?: Record; 10 | }; 11 | description?: string; 12 | enabled_proxycast: boolean; 13 | enabled_claude: boolean; 14 | enabled_codex: boolean; 15 | enabled_gemini: boolean; 16 | created_at?: number; 17 | } 18 | 19 | export const mcpApi = { 20 | getServers: (): Promise => invoke("get_mcp_servers"), 21 | 22 | addServer: (server: McpServer): Promise => 23 | invoke("add_mcp_server", { server }), 24 | 25 | updateServer: (server: McpServer): Promise => 26 | invoke("update_mcp_server", { server }), 27 | 28 | deleteServer: (id: string): Promise => 29 | invoke("delete_mcp_server", { id }), 30 | 31 | toggleServer: ( 32 | id: string, 33 | appType: string, 34 | enabled: boolean, 35 | ): Promise => invoke("toggle_mcp_server", { id, appType, enabled }), 36 | 37 | /** 从外部应用导入 MCP 配置 */ 38 | importFromApp: (appType: string): Promise => 39 | invoke("import_mcp_from_app", { appType }), 40 | 41 | /** 同步所有 MCP 配置到实际配置文件 */ 42 | syncAllToLive: (): Promise => invoke("sync_all_mcp_to_live"), 43 | }; 44 | -------------------------------------------------------------------------------- /src-tauri/src/streaming/mod.rs: -------------------------------------------------------------------------------- 1 | //! 流式传输核心模块 2 | //! 3 | //! 该模块提供真正的端到端流式传输支持,将当前的"伪流式"架构改造为 4 | //! 逐 chunk 处理和实时 Flow 监控的真正流式传输。 5 | //! 6 | //! # 主要组件 7 | //! 8 | //! - `error`: 流式错误类型定义 9 | //! - `metrics`: 流式指标类型定义 10 | //! - `aws_parser`: AWS Event Stream 解析器(用于 Kiro/CodeWhisperer) 11 | //! - `converter`: 流式格式转换器 12 | //! - `traits`: StreamingProvider trait 定义 13 | //! - `manager`: 流式管理器 14 | 15 | pub mod aws_parser; 16 | pub mod converter; 17 | pub mod error; 18 | pub mod manager; 19 | pub mod metrics; 20 | pub mod traits; 21 | 22 | // 重新导出核心类型 23 | pub use aws_parser::{ 24 | extract_content, extract_tool_calls, serialize_event, AwsEvent, AwsEventStreamParser, 25 | ParserState, 26 | }; 27 | pub use converter::{ 28 | extract_content_from_sse, extract_tool_calls_from_sse, ConverterState, PartialJsonAccumulator, 29 | StreamConverter, StreamFormat, 30 | }; 31 | pub use error::StreamError; 32 | pub use manager::{ 33 | collect_stream_content, create_flow_monitor_callback, with_timeout, FlowMonitorCallback, 34 | ManagedStream, ManagedStreamWithCallback, StreamConfig, StreamContext, StreamEvent, 35 | StreamManager, TimeoutStream, 36 | }; 37 | pub use metrics::StreamMetrics; 38 | pub use traits::{ 39 | reqwest_stream_to_stream_response, StreamFormat as TraitsStreamFormat, StreamResponse, 40 | StreamingProvider, 41 | }; 42 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/1.dashboard.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 仪表盘 3 | description: 监控和控制代理服务 4 | navigation: 5 | icon: i-heroicons-chart-bar 6 | --- 7 | 8 | # 仪表盘 9 | 10 | 仪表盘是 ProxyCast 的主界面,提供服务状态监控和快速操作入口。 11 | 12 | ## 服务状态 13 | 14 | 仪表盘顶部显示当前服务状态: 15 | 16 | | 状态 | 说明 | 17 | |------|------| 18 | | 🟢 运行中 | API Server 正在运行,可以接收请求 | 19 | | 🔴 已停止 | API Server 未启动 | 20 | | 🟡 启动中 | 服务正在初始化 | 21 | 22 | ## 控制按钮 23 | 24 | - **启动服务**: 启动 API Server 25 | - **停止服务**: 停止 API Server 26 | - **重启服务**: 重新启动服务 27 | 28 | ## API 信息 29 | 30 | 服务运行时显示: 31 | 32 | - **API 地址**: 本地 API 端点(如 `http://127.0.0.1:9090`) 33 | - **API Key**: 当前配置的访问密钥 34 | - **复制按钮**: 一键复制 API 地址或 Key 35 | 36 | ## API 测试面板 37 | 38 | 内置的 API 测试工具: 39 | 40 | 1. **消息输入**: 输入测试消息 41 | 2. **模型选择**: 选择要使用的模型 42 | 3. **发送请求**: 点击发送测试请求 43 | 4. **响应显示**: 查看 AI 响应结果 44 | 45 | ### 测试示例 46 | 47 | ``` 48 | 用户: Hello, how are you? 49 | 助手: I'm doing well, thank you for asking! How can I help you today? 50 | ``` 51 | 52 | ## 请求统计 53 | 54 | 实时统计信息: 55 | 56 | - **总请求数**: 累计处理的请求数量 57 | - **成功率**: 请求成功百分比 58 | - **平均延迟**: 请求平均响应时间 59 | - **Token 使用**: 累计 Token 消耗 60 | 61 | ## 凭证状态 62 | 63 | 显示当前可用的凭证: 64 | 65 | | 字段 | 说明 | 66 | |------|------| 67 | | Provider | 凭证类型 | 68 | | 状态 | 有效/过期/错误 | 69 | | 剩余额度 | 可用额度(如支持) | 70 | 71 | ## 快捷操作 72 | 73 | - **打开设置**: 进入设置页面 74 | - **查看日志**: 打开请求日志 75 | - **刷新凭证**: 重新加载凭证文件 76 | -------------------------------------------------------------------------------- /src/components/provider-pool/credential-forms/ModeSelector.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 登录/文件导入模式选择器 3 | */ 4 | 5 | import { LogIn, FolderOpen } from "lucide-react"; 6 | 7 | interface ModeSelectorProps { 8 | mode: "login" | "file"; 9 | setMode: (mode: "login" | "file") => void; 10 | loginLabel?: string; 11 | fileLabel?: string; 12 | } 13 | 14 | export function ModeSelector({ 15 | mode, 16 | setMode, 17 | loginLabel = "登录", 18 | fileLabel = "导入文件", 19 | }: ModeSelectorProps) { 20 | return ( 21 |
22 | 34 | 46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /docs/content/01.introduction/2.installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 安装指南 3 | description: 下载并安装 ProxyCast 4 | navigation: 5 | icon: i-heroicons-arrow-down-tray 6 | --- 7 | 8 | # 安装指南 9 | 10 | ## 系统要求 11 | 12 | | 平台 | 最低版本 | 架构 | 13 | |------|----------|------| 14 | | macOS | 11.0 (Big Sur) | Apple Silicon (arm64) | 15 | | Windows | 10 | x64 | 16 | 17 | ## 下载 18 | 19 | 从 GitHub Tags 下载最新版本: 20 | 21 | [下载 ProxyCast](https://github.com/aiclientproxy/proxycast/tags) 22 | 23 | ### 安装包 24 | 25 | | 平台 | 文件名 | 说明 | 26 | |------|--------|------| 27 | | macOS | `ProxyCast_x.x.x_aarch64.dmg` | Apple Silicon Mac | 28 | | Windows | `ProxyCast_x.x.x_x64-setup.exe` | Windows 64位 | 29 | 30 | ## macOS 安装 31 | 32 | 1. 下载 `.dmg` 文件 33 | 2. 双击打开 DMG 镜像 34 | 3. 将 ProxyCast 拖入 Applications 文件夹 35 | 4. 首次运行时,右键点击应用选择"打开"(绕过 Gatekeeper) 36 | 37 | ::alert{type="info"} 38 | 如果提示"无法验证开发者",请在系统偏好设置 > 安全性与隐私中点击"仍要打开"。 39 | :: 40 | 41 | ## Windows 安装 42 | 43 | 1. 下载 `.exe` 安装程序 44 | 2. 双击运行安装程序 45 | 3. 按照安装向导完成安装 46 | 4. 从开始菜单启动 ProxyCast 47 | 48 | ## 验证安装 49 | 50 | 启动 ProxyCast 后,你应该看到: 51 | 52 | 1. 系统托盘图标出现 53 | 2. 主窗口显示仪表盘 54 | 3. 服务状态显示"已停止"(首次启动) 55 | 56 | ## 常见安装问题 57 | 58 | ### macOS: "应用已损坏" 59 | 60 | ```bash 61 | # 移除隔离属性 62 | xattr -cr /Applications/ProxyCast.app 63 | ``` 64 | 65 | ### Windows: SmartScreen 警告 66 | 67 | 点击"更多信息" > "仍要运行"即可继续安装。 68 | 69 | ## 下一步 70 | 71 | 安装完成后,继续阅读 [快速开始](/introduction/quickstart) 配置你的第一个 Provider。 72 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src-tauri/src/database/migration.rs: -------------------------------------------------------------------------------- 1 | use rusqlite::Connection; 2 | use serde_json::Value; 3 | 4 | /// 从旧的 JSON 配置迁移数据到 SQLite 5 | #[allow(dead_code)] 6 | pub fn migrate_from_json( 7 | conn: &Connection, 8 | ) -> Result<(), Box> { 9 | // 检查是否已经迁移过 10 | let migrated: bool = conn 11 | .query_row( 12 | "SELECT value FROM settings WHERE key = 'migrated_from_json'", 13 | [], 14 | |row| row.get::<_, String>(0), 15 | ) 16 | .map(|v| v == "true") 17 | .unwrap_or(false); 18 | 19 | if migrated { 20 | return Ok(()); 21 | } 22 | 23 | // 读取旧配置文件 24 | let home = dirs::home_dir().ok_or("Cannot find home directory")?; 25 | let config_path = home.join(".proxycast").join("config.json"); 26 | 27 | if config_path.exists() { 28 | let content = std::fs::read_to_string(&config_path)?; 29 | let _config: Value = serde_json::from_str(&content)?; 30 | 31 | // TODO: 解析旧配置并插入到数据库 32 | // 这里需要根据实际的旧配置格式来实现 33 | 34 | // 备份旧配置 35 | let backup_path = home.join(".proxycast").join("config.json.backup"); 36 | std::fs::copy(&config_path, &backup_path)?; 37 | } 38 | 39 | // 标记迁移完成 40 | conn.execute( 41 | "INSERT OR REPLACE INTO settings (key, value) VALUES ('migrated_from_json', 'true')", 42 | [], 43 | )?; 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: { 6 | colors: { 7 | border: "hsl(var(--border))", 8 | background: "hsl(var(--background))", 9 | foreground: "hsl(var(--foreground))", 10 | primary: { 11 | DEFAULT: "hsl(var(--primary))", 12 | foreground: "hsl(var(--primary-foreground))", 13 | }, 14 | secondary: { 15 | DEFAULT: "hsl(var(--secondary))", 16 | foreground: "hsl(var(--secondary-foreground))", 17 | }, 18 | muted: { 19 | DEFAULT: "hsl(var(--muted))", 20 | foreground: "hsl(var(--muted-foreground))", 21 | }, 22 | accent: { 23 | DEFAULT: "hsl(var(--accent))", 24 | foreground: "hsl(var(--accent-foreground))", 25 | }, 26 | destructive: { 27 | DEFAULT: "hsl(var(--destructive))", 28 | foreground: "hsl(var(--destructive-foreground))", 29 | }, 30 | card: { 31 | DEFAULT: "hsl(var(--card))", 32 | foreground: "hsl(var(--card-foreground))", 33 | }, 34 | }, 35 | borderRadius: { 36 | lg: "var(--radius)", 37 | md: "calc(var(--radius) - 2px)", 38 | sm: "calc(var(--radius) - 4px)", 39 | }, 40 | }, 41 | }, 42 | plugins: [], 43 | }; 44 | -------------------------------------------------------------------------------- /src/lib/api/resilience.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | // Retry configuration 4 | export interface RetryConfig { 5 | max_retries: number; 6 | base_delay_ms: number; 7 | max_delay_ms: number; 8 | retryable_codes: number[]; 9 | } 10 | 11 | // Failover configuration 12 | export interface FailoverConfig { 13 | auto_switch: boolean; 14 | switch_on_quota: boolean; 15 | } 16 | 17 | // Switch log entry 18 | export interface SwitchLogEntry { 19 | from_provider: string; 20 | to_provider: string; 21 | failure_type: string; 22 | timestamp: string; 23 | } 24 | 25 | export const resilienceApi = { 26 | // Retry config 27 | async getRetryConfig(): Promise { 28 | return invoke("get_retry_config"); 29 | }, 30 | 31 | async updateRetryConfig(config: RetryConfig): Promise { 32 | return invoke("update_retry_config", { config }); 33 | }, 34 | 35 | // Failover config 36 | async getFailoverConfig(): Promise { 37 | return invoke("get_failover_config"); 38 | }, 39 | 40 | async updateFailoverConfig(config: FailoverConfig): Promise { 41 | return invoke("update_failover_config", { config }); 42 | }, 43 | 44 | // Switch log 45 | async getSwitchLog(): Promise { 46 | return invoke("get_switch_log"); 47 | }, 48 | 49 | async clearSwitchLog(): Promise { 50 | return invoke("clear_switch_log"); 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /src/lib/api/skills.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export interface Skill { 4 | key: string; 5 | name: string; 6 | description: string; 7 | directory: string; 8 | readmeUrl?: string; 9 | installed: boolean; 10 | repoOwner?: string; 11 | repoName?: string; 12 | repoBranch?: string; 13 | } 14 | 15 | export interface SkillRepo { 16 | owner: string; 17 | name: string; 18 | branch: string; 19 | enabled: boolean; 20 | } 21 | 22 | export type AppType = "claude" | "codex" | "gemini"; 23 | 24 | export const skillsApi = { 25 | async getAll(app: AppType = "claude"): Promise { 26 | return invoke("get_skills_for_app", { app }); 27 | }, 28 | 29 | async install(directory: string, app: AppType = "claude"): Promise { 30 | return invoke("install_skill_for_app", { app, directory }); 31 | }, 32 | 33 | async uninstall( 34 | directory: string, 35 | app: AppType = "claude", 36 | ): Promise { 37 | return invoke("uninstall_skill_for_app", { app, directory }); 38 | }, 39 | 40 | async getRepos(): Promise { 41 | return invoke("get_skill_repos"); 42 | }, 43 | 44 | async addRepo(repo: SkillRepo): Promise { 45 | return invoke("add_skill_repo", { repo }); 46 | }, 47 | 48 | async removeRepo(owner: string, name: string): Promise { 49 | return invoke("remove_skill_repo", { owner, name }); 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /src-tauri/src/commands/mcp_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::database::DbConnection; 2 | use crate::models::McpServer; 3 | use crate::services::mcp_service::McpService; 4 | use tauri::State; 5 | 6 | #[tauri::command] 7 | pub fn get_mcp_servers(db: State<'_, DbConnection>) -> Result, String> { 8 | McpService::get_all(&db) 9 | } 10 | 11 | #[tauri::command] 12 | pub fn add_mcp_server(db: State<'_, DbConnection>, server: McpServer) -> Result<(), String> { 13 | McpService::add(&db, server) 14 | } 15 | 16 | #[tauri::command] 17 | pub fn update_mcp_server(db: State<'_, DbConnection>, server: McpServer) -> Result<(), String> { 18 | McpService::update(&db, server) 19 | } 20 | 21 | #[tauri::command] 22 | pub fn delete_mcp_server(db: State<'_, DbConnection>, id: String) -> Result<(), String> { 23 | McpService::delete(&db, &id) 24 | } 25 | 26 | #[tauri::command] 27 | pub fn toggle_mcp_server( 28 | db: State<'_, DbConnection>, 29 | id: String, 30 | app_type: String, 31 | enabled: bool, 32 | ) -> Result<(), String> { 33 | McpService::toggle_enabled(&db, &id, &app_type, enabled) 34 | } 35 | 36 | #[tauri::command] 37 | pub fn import_mcp_from_app(db: State<'_, DbConnection>, app_type: String) -> Result { 38 | McpService::import_from_app(&db, &app_type) 39 | } 40 | 41 | #[tauri::command] 42 | pub fn sync_all_mcp_to_live(db: State<'_, DbConnection>) -> Result<(), String> { 43 | McpService::sync_all_to_live(&db) 44 | } 45 | -------------------------------------------------------------------------------- /src-tauri/src/models/provider_model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct Provider { 6 | pub id: String, 7 | pub app_type: String, 8 | pub name: String, 9 | pub settings_config: Value, 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub category: Option, 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub icon: Option, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub icon_color: Option, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub notes: Option, 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub created_at: Option, 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub sort_index: Option, 22 | #[serde(default)] 23 | pub is_current: bool, 24 | } 25 | 26 | impl Provider { 27 | #[allow(dead_code)] 28 | pub fn new(id: String, app_type: String, name: String, settings_config: Value) -> Self { 29 | Self { 30 | id, 31 | app_type, 32 | name, 33 | settings_config, 34 | category: None, 35 | icon: None, 36 | icon_color: None, 37 | notes: None, 38 | created_at: Some(chrono::Utc::now().timestamp()), 39 | sort_index: None, 40 | is_current: false, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/api/switch.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export interface Provider { 4 | id: string; 5 | app_type: string; 6 | name: string; 7 | settings_config: Record; 8 | category?: string; 9 | icon?: string; 10 | icon_color?: string; 11 | notes?: string; 12 | created_at?: number; 13 | sort_index?: number; 14 | is_current: boolean; 15 | } 16 | 17 | // proxycast 保留用于内部配置存储,但不在 UI 的 Tab 中显示 18 | export type AppType = "claude" | "codex" | "gemini" | "proxycast"; 19 | 20 | export const switchApi = { 21 | getProviders: (appType: AppType): Promise => 22 | invoke("get_switch_providers", { appType }), 23 | 24 | getCurrentProvider: (appType: AppType): Promise => 25 | invoke("get_current_switch_provider", { appType }), 26 | 27 | addProvider: (provider: Provider): Promise => 28 | invoke("add_switch_provider", { provider }), 29 | 30 | updateProvider: (provider: Provider): Promise => 31 | invoke("update_switch_provider", { provider }), 32 | 33 | deleteProvider: (appType: AppType, id: string): Promise => 34 | invoke("delete_switch_provider", { appType, id }), 35 | 36 | switchProvider: (appType: AppType, id: string): Promise => 37 | invoke("switch_provider", { appType, id }), 38 | 39 | /** 读取当前生效的配置(从实际配置文件读取) */ 40 | readLiveSettings: (appType: AppType): Promise> => 41 | invoke("read_live_provider_settings", { appType }), 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/clients/ClientsPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { AppType } from "@/lib/api/switch"; 3 | import { AppTabs } from "./AppTabs"; 4 | import { ProviderList } from "./ProviderList"; 5 | import { HelpTip } from "@/components/HelpTip"; 6 | 7 | interface ClientsPageProps { 8 | hideHeader?: boolean; 9 | } 10 | 11 | export function ClientsPage({ hideHeader = false }: ClientsPageProps) { 12 | const [activeApp, setActiveApp] = useState("claude"); 13 | 14 | return ( 15 |
16 | {!hideHeader && ( 17 |
18 |

配置切换

19 |

20 | 一键切换 Claude Code / Codex / Gemini CLI 的 API 配置,快速在不同 21 | Provider 间切换 22 |

23 |
24 | )} 25 | 26 | 27 |

28 | 添加名为 "ProxyCast" 的 Provider 29 | 后,可将凭证池中的凭证(Kiro/Gemini/Claude 等)转换为标准 30 | OpenAI/Anthropic API, 供 Claude Code、Codex、Cherry Studio 31 | 等工具使用。配置 API 地址为{" "} 32 | 33 | http://localhost:8999 34 | 35 |

36 |
37 | 38 | 39 | 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/api/injection.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | // Injection mode 4 | export type InjectionMode = "merge" | "override"; 5 | 6 | // Injection rule 7 | export interface InjectionRule { 8 | id: string; 9 | pattern: string; 10 | parameters: Record; 11 | mode: InjectionMode; 12 | priority: number; 13 | enabled: boolean; 14 | } 15 | 16 | // Injection configuration 17 | export interface InjectionConfig { 18 | enabled: boolean; 19 | rules: InjectionRule[]; 20 | } 21 | 22 | export const injectionApi = { 23 | // Get injection configuration 24 | async getInjectionConfig(): Promise { 25 | return invoke("get_injection_config"); 26 | }, 27 | 28 | // Set injection enabled 29 | async setInjectionEnabled(enabled: boolean): Promise { 30 | return invoke("set_injection_enabled", { enabled }); 31 | }, 32 | 33 | // Add injection rule 34 | async addInjectionRule(rule: InjectionRule): Promise { 35 | return invoke("add_injection_rule", { rule }); 36 | }, 37 | 38 | // Remove injection rule 39 | async removeInjectionRule(id: string): Promise { 40 | return invoke("remove_injection_rule", { id }); 41 | }, 42 | 43 | // Update injection rule 44 | async updateInjectionRule(id: string, rule: InjectionRule): Promise { 45 | return invoke("update_injection_rule", { id, rule }); 46 | }, 47 | 48 | // Get all injection rules 49 | async getInjectionRules(): Promise { 50 | return invoke("get_injection_rules"); 51 | }, 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/clients/AppTabs.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { AppType } from "@/lib/api/switch"; 3 | import { ProviderIcon } from "@/icons/providers"; 4 | 5 | interface AppTabsProps { 6 | activeApp: AppType; 7 | onAppChange: (app: AppType) => void; 8 | } 9 | 10 | const apps: { 11 | id: AppType; 12 | label: string; 13 | description: string; 14 | iconType: string; 15 | }[] = [ 16 | { 17 | id: "claude", 18 | label: "Claude Code", 19 | description: "Claude CLI 配置", 20 | iconType: "claude", 21 | }, 22 | { 23 | id: "codex", 24 | label: "Codex", 25 | description: "OpenAI Codex CLI", 26 | iconType: "openai", 27 | }, 28 | { 29 | id: "gemini", 30 | label: "Gemini", 31 | description: "Google Gemini CLI", 32 | iconType: "gemini", 33 | }, 34 | ]; 35 | 36 | export function AppTabs({ activeApp, onAppChange }: AppTabsProps) { 37 | return ( 38 |
39 | {apps.map((app) => ( 40 | 54 | ))} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/switch/AppTabs.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { AppType } from "@/lib/api/switch"; 3 | import { ProviderIcon } from "@/icons/providers"; 4 | 5 | interface AppTabsProps { 6 | activeApp: AppType; 7 | onAppChange: (app: AppType) => void; 8 | } 9 | 10 | const apps: { 11 | id: AppType; 12 | label: string; 13 | description: string; 14 | iconType: string; 15 | }[] = [ 16 | { 17 | id: "claude", 18 | label: "Claude Code", 19 | description: "Claude CLI 配置", 20 | iconType: "claude", 21 | }, 22 | { 23 | id: "codex", 24 | label: "Codex", 25 | description: "OpenAI Codex CLI", 26 | iconType: "openai", 27 | }, 28 | { 29 | id: "gemini", 30 | label: "Gemini", 31 | description: "Google Gemini CLI", 32 | iconType: "gemini", 33 | }, 34 | ]; 35 | 36 | export function AppTabs({ activeApp, onAppChange }: AppTabsProps) { 37 | return ( 38 |
39 | {apps.map((app) => ( 40 | 54 | ))} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/icons/providers/openai.svg: -------------------------------------------------------------------------------- 1 | OpenAI -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 222.2 84% 4.9%; 11 | --primary: 222.2 47.4% 11.2%; 12 | --primary-foreground: 210 40% 98%; 13 | --secondary: 210 40% 96.1%; 14 | --secondary-foreground: 222.2 47.4% 11.2%; 15 | --muted: 210 40% 96.1%; 16 | --muted-foreground: 215.4 16.3% 46.9%; 17 | --accent: 210 40% 96.1%; 18 | --accent-foreground: 222.2 47.4% 11.2%; 19 | --destructive: 0 84.2% 60.2%; 20 | --destructive-foreground: 210 40% 98%; 21 | --border: 214.3 31.8% 91.4%; 22 | --radius: 0.5rem; 23 | } 24 | 25 | .dark { 26 | --background: 222.2 84% 4.9%; 27 | --foreground: 210 40% 98%; 28 | --card: 222.2 84% 4.9%; 29 | --card-foreground: 210 40% 98%; 30 | --primary: 210 40% 98%; 31 | --primary-foreground: 222.2 47.4% 11.2%; 32 | --secondary: 217.2 32.6% 17.5%; 33 | --secondary-foreground: 210 40% 98%; 34 | --muted: 217.2 32.6% 17.5%; 35 | --muted-foreground: 215 20.2% 65.1%; 36 | --accent: 217.2 32.6% 17.5%; 37 | --accent-foreground: 210 40% 98%; 38 | --destructive: 0 62.8% 30.6%; 39 | --destructive-foreground: 210 40% 98%; 40 | --border: 217.2 32.6% 17.5%; 41 | } 42 | } 43 | 44 | @layer base { 45 | * { 46 | @apply border-border; 47 | } 48 | body { 49 | @apply bg-background text-foreground; 50 | font-family: 51 | -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, 52 | Cantarell, "Open Sans", "Helvetica Neue", sans-serif; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/2.monitoring.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 监控中心 3 | description: 请求统计和性能监控 4 | navigation: 5 | icon: i-heroicons-eye 6 | --- 7 | 8 | # 监控中心 9 | 10 | 监控中心提供详细的请求统计、性能指标和日志查看功能。 11 | 12 | ## 监控界面 13 | 14 | ### 概览面板 15 | 16 | 显示关键指标的实时数据: 17 | 18 | - **请求总数**: 今日/本周/本月请求量 19 | - **成功率**: 请求成功百分比 20 | - **平均延迟**: 响应时间统计 21 | - **活跃凭证**: 当前可用凭证数量 22 | 23 | ### 图表展示 24 | 25 | - **请求趋势图**: 按时间显示请求量变化 26 | - **延迟分布图**: 响应时间分布 27 | - **Provider 使用占比**: 各 Provider 请求比例 28 | 29 | ## 请求统计 30 | 31 | ### 按 Provider 统计 32 | 33 | | Provider | 请求数 | 成功率 | 平均延迟 | 34 | |----------|--------|--------|----------| 35 | | Kiro Claude | 1,234 | 99.2% | 1.2s | 36 | | Gemini CLI | 567 | 98.5% | 0.8s | 37 | | Qwen | 890 | 99.0% | 1.0s | 38 | 39 | ### 按模型统计 40 | 41 | 查看每个模型的使用情况: 42 | 43 | - 请求次数 44 | - Token 消耗 45 | - 平均响应时间 46 | 47 | ## Token 使用追踪 48 | 49 | ### 使用量统计 50 | 51 | | 指标 | 说明 | 52 | |------|------| 53 | | 输入 Token | 请求消息的 Token 数 | 54 | | 输出 Token | 响应消息的 Token 数 | 55 | | 总计 Token | 输入 + 输出 | 56 | 57 | ### 按时间段查看 58 | 59 | - 今日使用量 60 | - 本周使用量 61 | - 本月使用量 62 | - 自定义时间范围 63 | 64 | ## 请求日志 65 | 66 | ### 日志列表 67 | 68 | 每条日志包含: 69 | 70 | | 字段 | 说明 | 71 | |------|------| 72 | | 时间 | 请求时间戳 | 73 | | 模型 | 请求的模型名称 | 74 | | Provider | 实际使用的 Provider | 75 | | 状态 | 成功/失败 | 76 | | 延迟 | 响应时间 | 77 | | Token | Token 使用量 | 78 | 79 | ### 日志过滤 80 | 81 | 支持按以下条件过滤: 82 | 83 | - 时间范围 84 | - Provider 85 | - 模型 86 | - 状态(成功/失败) 87 | 88 | ### 日志详情 89 | 90 | 点击日志条目查看详细信息: 91 | 92 | - 完整请求内容 93 | - 完整响应内容 94 | - 错误信息(如有) 95 | - 请求头信息 96 | 97 | ## 导出数据 98 | 99 | 支持导出统计数据: 100 | 101 | - CSV 格式 102 | - JSON 格式 103 | - 自定义时间范围 104 | -------------------------------------------------------------------------------- /src/components/config/ConfigManagementPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Monitor, FileCode } from "lucide-react"; 3 | import { cn } from "@/lib/utils"; 4 | import { ClientsPage } from "../clients/ClientsPage"; 5 | import { ConfigPage } from "./ConfigPage"; 6 | 7 | type Tab = "switch" | "config"; 8 | 9 | const tabs = [ 10 | { id: "switch" as Tab, label: "配置切换", icon: Monitor }, 11 | { id: "config" as Tab, label: "配置文件", icon: FileCode }, 12 | ]; 13 | 14 | export function ConfigManagementPage() { 15 | const [activeTab, setActiveTab] = useState("switch"); 16 | 17 | return ( 18 |
19 |
20 |

配置管理

21 |

管理客户端配置和配置文件

22 |
23 | 24 | {/* Tab 切换 */} 25 |
26 | {tabs.map((tab) => ( 27 | 40 | ))} 41 |
42 | 43 | {/* Tab 内容 */} 44 |
45 | {activeTab === "switch" && } 46 | {activeTab === "config" && } 47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/components/provider-pool/credential-forms/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 凭证表单共享类型定义 3 | */ 4 | 5 | import { PoolProviderType } from "@/lib/api/providerPool"; 6 | 7 | /** OAuth 登录表单的通用 Props */ 8 | export interface OAuthLoginFormProps { 9 | name: string; 10 | loading: boolean; 11 | error: string | null; 12 | onSuccess: () => void; 13 | setLoading: (loading: boolean) => void; 14 | setError: (error: string | null) => void; 15 | } 16 | 17 | /** 文件导入表单的通用 Props */ 18 | export interface FileImportFormProps { 19 | credsFilePath: string; 20 | setCredsFilePath: (path: string) => void; 21 | projectId?: string; 22 | setProjectId?: (id: string) => void; 23 | onSelectFile: () => void; 24 | defaultPathHint?: string; 25 | fileHint?: string; 26 | } 27 | 28 | /** API Key 表单的通用 Props */ 29 | export interface ApiKeyFormProps { 30 | apiKey: string; 31 | setApiKey: (key: string) => void; 32 | baseUrl: string; 33 | setBaseUrl: (url: string) => void; 34 | providerType: PoolProviderType; 35 | } 36 | 37 | /** 默认凭证文件路径 */ 38 | export const defaultCredsPath: Record = { 39 | kiro: "~/.aws/sso/cache/kiro-auth-token.json", 40 | gemini: "~/.gemini/oauth_creds.json", 41 | qwen: "~/.qwen/oauth_creds.json", 42 | antigravity: "", 43 | codex: "~/.codex/auth.json", 44 | claude_oauth: "~/.claude/oauth.json", 45 | iflow: "~/.iflow/oauth_creds.json", 46 | }; 47 | 48 | /** Provider 显示名称 */ 49 | export const providerLabels: Record = { 50 | kiro: "Kiro (AWS)", 51 | gemini: "Gemini (Google)", 52 | qwen: "Qwen (阿里)", 53 | openai: "OpenAI", 54 | claude: "Claude (Anthropic)", 55 | antigravity: "Antigravity (Gemini 3 Pro)", 56 | codex: "Codex (OpenAI OAuth)", 57 | claude_oauth: "Claude OAuth", 58 | iflow: "iFlow", 59 | }; 60 | -------------------------------------------------------------------------------- /docs/content/01.introduction/1.overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 概述 3 | description: ProxyCast 项目介绍和核心价值 4 | navigation: 5 | icon: i-heroicons-home 6 | --- 7 | 8 | # ProxyCast 概述 9 | 10 | ProxyCast 是一款基于 Tauri 2.0 的跨平台桌面应用,让你可以**把 AI 客户端的订阅额度用到任何地方**。 11 | 12 | ::alert{type="warning"} 13 | **免责声明**: 本工具仅限于个人合法使用,严禁用于非法盈利目的。初衷是帮助用户充分利用已订阅的 AI 服务 Token。[查看完整声明](/legal/disclaimer) 14 | :: 15 | 16 | ## 核心价值 17 | 18 | 你是否有以下困扰? 19 | 20 | - 订阅了 Kiro、Claude Code 等 AI 编程助手,但只能在特定 IDE 中使用 21 | - 想在其他工具(如 Cursor、Continue、自定义脚本)中使用已有的 AI 额度 22 | - 需要管理多个 AI 服务的凭证,频繁切换很麻烦 23 | 24 | ProxyCast 解决这些问题:将你的 AI 客户端凭证转换为标准的 OpenAI/Claude 兼容 API,让任何支持 OpenAI 接口的工具都能使用你的订阅额度。 25 | 26 | ## 支持的 Provider 27 | 28 | | Provider | 类型 | 认证方式 | 说明 | 29 | |----------|------|----------|------| 30 | | Kiro Claude | OAuth | 自动刷新 | AWS Kiro IDE 的 Claude 凭证 | 31 | | Gemini CLI | OAuth | 自动刷新 | Google Gemini CLI 凭证 | 32 | | Qwen (通义千问) | OAuth | 自动刷新 | 阿里云通义千问凭证 | 33 | | OpenAI Custom | API Key | 手动配置 | 自定义 OpenAI 兼容服务 | 34 | | Claude Custom | API Key | 手动配置 | 自定义 Claude 兼容服务 | 35 | 36 | ## 核心特性 37 | 38 | ### 🔑 凭证池管理 39 | - 支持多个 Provider 凭证的统一管理 40 | - 自动检测和加载本地凭证文件 41 | - OAuth Token 自动刷新机制 42 | 43 | ### ⚖️ 智能路由 44 | - 基于模型名称的请求路由 45 | - 负载均衡和优先级配置 46 | - 健康检查和自动故障转移 47 | 48 | ### 🛡️ 容错机制 49 | - 可配置的重试策略 50 | - 超时控制和熔断器 51 | - 多 Provider 故障转移 52 | 53 | ### 🔄 协议转换 54 | - OpenAI Chat Completions API 兼容 55 | - Claude Messages API 兼容 56 | - 自动格式转换 57 | 58 | ### 📊 监控统计 59 | - 实时请求统计 60 | - Token 使用追踪 61 | - 详细的请求日志 62 | 63 | ## 使用场景 64 | 65 | 1. **IDE 集成**: 在 Cursor、Continue 等编辑器中使用 Kiro/Claude Code 额度 66 | 2. **脚本调用**: 在 Python/Node.js 脚本中调用 AI API 67 | 3. **多账户管理**: 统一管理多个 AI 服务账户 68 | 4. **团队共享**: 通过配置导出分享 Provider 设置 69 | 70 | ## 下一步 71 | 72 | - [安装指南](/introduction/installation) - 下载并安装 ProxyCast 73 | - [快速开始](/introduction/quickstart) - 5 分钟内完成首次 API 调用 74 | -------------------------------------------------------------------------------- /src-tauri/src/commands/switch_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::database::DbConnection; 2 | use crate::models::Provider; 3 | use crate::services::switch::SwitchService; 4 | use serde_json::Value; 5 | use tauri::State; 6 | 7 | #[tauri::command] 8 | pub fn get_switch_providers( 9 | db: State<'_, DbConnection>, 10 | app_type: String, 11 | ) -> Result, String> { 12 | SwitchService::get_providers(&db, &app_type) 13 | } 14 | 15 | #[tauri::command] 16 | pub fn get_current_switch_provider( 17 | db: State<'_, DbConnection>, 18 | app_type: String, 19 | ) -> Result, String> { 20 | SwitchService::get_current_provider(&db, &app_type) 21 | } 22 | 23 | #[tauri::command] 24 | pub fn add_switch_provider(db: State<'_, DbConnection>, provider: Provider) -> Result<(), String> { 25 | SwitchService::add_provider(&db, provider) 26 | } 27 | 28 | #[tauri::command] 29 | pub fn update_switch_provider( 30 | db: State<'_, DbConnection>, 31 | provider: Provider, 32 | ) -> Result<(), String> { 33 | SwitchService::update_provider(&db, provider) 34 | } 35 | 36 | #[tauri::command] 37 | pub fn delete_switch_provider( 38 | db: State<'_, DbConnection>, 39 | app_type: String, 40 | id: String, 41 | ) -> Result<(), String> { 42 | SwitchService::delete_provider(&db, &app_type, &id) 43 | } 44 | 45 | #[tauri::command] 46 | pub fn switch_provider( 47 | db: State<'_, DbConnection>, 48 | app_type: String, 49 | id: String, 50 | ) -> Result<(), String> { 51 | SwitchService::switch_provider(&db, &app_type, &id) 52 | } 53 | 54 | #[tauri::command] 55 | pub fn import_default_config( 56 | db: State<'_, DbConnection>, 57 | app_type: String, 58 | ) -> Result { 59 | SwitchService::import_default_config(&db, &app_type) 60 | } 61 | 62 | #[tauri::command] 63 | pub fn read_live_provider_settings(app_type: String) -> Result { 64 | SwitchService::read_live_settings(&app_type) 65 | } 66 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # AI Agent 指南 2 | 3 | 本文件为 AI Agent 在此代码库中工作时提供指导。 4 | 5 | ## 基本规则 6 | 7 | 1. **始终使用中文输出** - 所有回复、注释、文档都使用中文 8 | 9 | ## 构建命令 10 | 11 | ```bash 12 | # 构建 Tauri 应用 13 | cd src-tauri && cargo build 14 | 15 | # 构建前端 16 | npm run build 17 | 18 | # 开发模式 19 | npm run tauri dev 20 | ``` 21 | 22 | ## 测试命令 23 | 24 | ```bash 25 | # 运行 Rust 测试 26 | cd src-tauri && cargo test 27 | 28 | # 运行前端测试 29 | npm test 30 | ``` 31 | 32 | ## 代码检查 33 | 34 | ```bash 35 | # Rust 代码检查 36 | cd src-tauri && cargo clippy 37 | 38 | # 前端代码检查 39 | npm run lint 40 | ``` 41 | 42 | ## 项目架构 43 | 44 | ### 技术栈 45 | - 前端:React + TypeScript + Vite + TailwindCSS 46 | - 后端:Rust + Tauri 47 | - 数据库:SQLite (rusqlite) 48 | 49 | ### 核心模块 50 | 51 | 1. **Provider 系统** (`src-tauri/src/providers/`) 52 | - Kiro/CodeWhisperer OAuth 认证 53 | - Gemini OAuth 认证 54 | - Qwen OAuth 认证 55 | - Antigravity OAuth 认证 56 | - OpenAI/Claude API Key 认证 57 | 58 | 2. **凭证池管理** (`src-tauri/src/services/provider_pool_service.rs`) 59 | - 多凭证轮询负载均衡 60 | - 健康检查机制 61 | - Token 自动刷新 62 | 63 | 3. **API 服务器** (`src-tauri/src/server.rs`) 64 | - OpenAI 兼容 API 端点 65 | - Claude 兼容 API 端点 66 | - 流式响应支持 67 | 68 | 4. **协议转换** (`src-tauri/src/converter/`) 69 | - OpenAI ↔ CodeWhisperer 转换 70 | - OpenAI ↔ Claude 转换 71 | 72 | ### 凭证管理策略(方案 B) 73 | 74 | Kiro 凭证采用完全独立的副本策略: 75 | - 上传凭证时,自动合并 `clientIdHash` 文件中的 `client_id`/`client_secret` 到副本 76 | - 每个副本文件完全独立,支持多账号场景 77 | - 刷新 Token 时只使用副本文件中的凭证,不依赖原始文件 78 | 79 | ## 开发指南 80 | 81 | ### 添加新 Provider 82 | 83 | 1. 在 `src-tauri/src/providers/` 创建新的 provider 模块 84 | 2. 实现凭证加载、Token 刷新、API 调用方法 85 | 3. 在 `CredentialData` 枚举中添加新类型 86 | 4. 在 `ProviderPoolService` 中添加健康检查逻辑 87 | 88 | ### 修改凭证管理 89 | 90 | - 凭证文件存储在 `~/Library/Application Support/proxycast/credentials/` 91 | - 数据库存储凭证元数据和状态 92 | - Token 缓存在数据库中,避免频繁读取文件 93 | 94 | ### 调试技巧 95 | 96 | - 日志输出使用 `tracing` 宏 97 | - API 请求调试文件保存在 `~/.proxycast/logs/` 98 | - 使用 `debug_kiro_credentials` 命令调试凭证加载 99 | -------------------------------------------------------------------------------- /src/icons/app-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | P 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/icons/providers/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import { cn } from "@/lib/utils"; 3 | import { providerIcons, providerTypeToIcon } from "./utils"; 4 | 5 | interface ProviderIconProps { 6 | providerType: string; 7 | size?: number | string; 8 | className?: string; 9 | showFallback?: boolean; 10 | } 11 | 12 | export const ProviderIcon: React.FC = ({ 13 | providerType, 14 | size = 24, 15 | className, 16 | showFallback = true, 17 | }) => { 18 | const iconName = providerTypeToIcon[providerType] || providerType; 19 | const iconSvg = providerIcons[iconName]; 20 | 21 | const sizeStyle = useMemo(() => { 22 | const sizeValue = typeof size === "number" ? `${size}px` : size; 23 | return { 24 | width: sizeValue, 25 | height: sizeValue, 26 | fontSize: sizeValue, 27 | lineHeight: 1, 28 | }; 29 | }, [size]); 30 | 31 | if (iconSvg) { 32 | return ( 33 | 41 | ); 42 | } 43 | 44 | // Fallback:显示首字母 45 | if (showFallback) { 46 | const initials = providerType 47 | .split("_") 48 | .map((word) => word[0]) 49 | .join("") 50 | .toUpperCase() 51 | .slice(0, 2); 52 | const fallbackFontSize = 53 | typeof size === "number" ? `${Math.max(size * 0.5, 12)}px` : "0.5em"; 54 | return ( 55 | 63 | {initials} 64 | 65 | ); 66 | } 67 | 68 | return null; 69 | }; 70 | -------------------------------------------------------------------------------- /src/lib/api/prompts.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export interface Prompt { 4 | id: string; 5 | app_type: string; 6 | name: string; 7 | content: string; 8 | description?: string; 9 | enabled: boolean; 10 | createdAt?: number; 11 | updatedAt?: number; 12 | } 13 | 14 | export type AppType = "claude" | "codex" | "gemini"; 15 | 16 | export const promptsApi = { 17 | /** Get all prompts as a map (id -> Prompt) */ 18 | getPrompts: (app: AppType): Promise> => 19 | invoke("get_prompts", { app }), 20 | 21 | /** Upsert a prompt (insert or update) */ 22 | upsertPrompt: (app: AppType, id: string, prompt: Prompt): Promise => 23 | invoke("upsert_prompt", { app, id, prompt }), 24 | 25 | /** Add a new prompt */ 26 | addPrompt: (prompt: Prompt): Promise => 27 | invoke("add_prompt", { prompt }), 28 | 29 | /** Update an existing prompt */ 30 | updatePrompt: (prompt: Prompt): Promise => 31 | invoke("update_prompt", { prompt }), 32 | 33 | /** Delete a prompt */ 34 | deletePrompt: (app: AppType, id: string): Promise => 35 | invoke("delete_prompt", { app, id }), 36 | 37 | /** Enable a prompt and sync to live file */ 38 | enablePrompt: (app: AppType, id: string): Promise => 39 | invoke("enable_prompt", { app, id }), 40 | 41 | /** Import prompt from live file */ 42 | importFromFile: (app: AppType): Promise => 43 | invoke("import_prompt_from_file", { app }), 44 | 45 | /** Get current live prompt file content */ 46 | getCurrentFileContent: (app: AppType): Promise => 47 | invoke("get_current_prompt_file_content", { app }), 48 | 49 | /** Auto-import from live file if no prompts exist */ 50 | autoImport: (app: AppType): Promise => 51 | invoke("auto_import_prompt", { app }), 52 | 53 | // Legacy API for compatibility 54 | switchPrompt: (appType: AppType, id: string): Promise => 55 | invoke("switch_prompt", { appType, id }), 56 | }; 57 | -------------------------------------------------------------------------------- /src/hooks/useSkills.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from "react"; 2 | import { skillsApi, Skill, SkillRepo, AppType } from "@/lib/api/skills"; 3 | 4 | export function useSkills(app: AppType = "claude") { 5 | const [skills, setSkills] = useState([]); 6 | const [repos, setRepos] = useState([]); 7 | const [loading, setLoading] = useState(true); 8 | const [error, setError] = useState(null); 9 | 10 | const fetchSkills = useCallback(async () => { 11 | try { 12 | setLoading(true); 13 | setError(null); 14 | const data = await skillsApi.getAll(app); 15 | setSkills(data); 16 | } catch (e) { 17 | setError(e instanceof Error ? e.message : String(e)); 18 | } finally { 19 | setLoading(false); 20 | } 21 | }, [app]); 22 | 23 | const fetchRepos = useCallback(async () => { 24 | try { 25 | const data = await skillsApi.getRepos(); 26 | setRepos(data); 27 | } catch (e) { 28 | console.error("Failed to fetch repos:", e); 29 | } 30 | }, []); 31 | 32 | useEffect(() => { 33 | fetchSkills(); 34 | fetchRepos(); 35 | }, [fetchSkills, fetchRepos]); 36 | 37 | const install = async (directory: string) => { 38 | await skillsApi.install(directory, app); 39 | await fetchSkills(); 40 | }; 41 | 42 | const uninstall = async (directory: string) => { 43 | await skillsApi.uninstall(directory, app); 44 | await fetchSkills(); 45 | }; 46 | 47 | const addRepo = async (repo: SkillRepo) => { 48 | await skillsApi.addRepo(repo); 49 | await fetchRepos(); 50 | await fetchSkills(); 51 | }; 52 | 53 | const removeRepo = async (owner: string, name: string) => { 54 | await skillsApi.removeRepo(owner, name); 55 | await fetchRepos(); 56 | await fetchSkills(); 57 | }; 58 | 59 | return { 60 | skills, 61 | repos, 62 | loading, 63 | error, 64 | refresh: fetchSkills, 65 | install, 66 | uninstall, 67 | addRepo, 68 | removeRepo, 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proxycast" 3 | version = "0.16.0" 4 | description = "AI API Proxy Desktop App" 5 | authors = ["you"] 6 | edition = "2021" 7 | repository = "https://github.com/aiclientproxy/proxycast" 8 | homepage = "https://github.com/aiclientproxy/proxycast" 9 | 10 | [lib] 11 | name = "proxycast_lib" 12 | crate-type = ["lib", "cdylib", "staticlib"] 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "2", features = [] } 16 | 17 | [dependencies] 18 | tauri = { version = "2", features = ["tray-icon", "image-png"] } 19 | tauri-plugin-shell = "2" 20 | tauri-plugin-autostart = "2" 21 | tauri-plugin-dialog = "2" 22 | serde = { version = "1", features = ["derive"] } 23 | serde_json = "1" 24 | tokio = { version = "1", features = ["full"] } 25 | axum = { version = "0.7", features = ["ws"] } 26 | axum-server = { version = "0.7", features = ["tls-rustls"] } 27 | rustls-pemfile = "2" 28 | tower = "0.4" 29 | tower-http = { version = "0.5", features = ["limit"] } 30 | reqwest = { version = "0.12", features = ["json", "stream"] } 31 | uuid = { version = "1", features = ["v4"] } 32 | chrono = { version = "0.4", features = ["serde"] } 33 | dirs = "5" 34 | tracing = "0.1" 35 | tracing-subscriber = "0.3" 36 | futures = "0.3" 37 | async-stream = "0.3" 38 | regex = "1" 39 | md5 = "0.7" 40 | urlencoding = "2" 41 | rusqlite = { version = "0.31", features = ["bundled"] } 42 | serde_yaml = "0.9" 43 | indexmap = { version = "2", features = ["serde"] } 44 | zip = "0.6" 45 | anyhow = "1" 46 | dashmap = "5" 47 | notify = { version = "6", default-features = false, features = ["macos_fsevent"] } 48 | parking_lot = "0.12" 49 | tiktoken-rs = "0.6" 50 | async-trait = "0.1" 51 | thiserror = "1" 52 | base64 = "0.22" 53 | bytes = "1" 54 | rand = "0.8" 55 | sha2 = "0.10" 56 | serde_urlencoded = "0.7" 57 | open = "5" 58 | url = "2" 59 | once_cell = "1" 60 | tokio-util = "0.7" 61 | 62 | [dev-dependencies] 63 | proptest = "1" 64 | tempfile = "3" 65 | 66 | [features] 67 | default = ["custom-protocol"] 68 | custom-protocol = ["tauri/custom-protocol"] 69 | -------------------------------------------------------------------------------- /src/lib/api/credentials.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export type OAuthProvider = "kiro" | "gemini" | "qwen"; 4 | 5 | export interface OAuthCredentialStatus { 6 | provider: string; 7 | loaded: boolean; 8 | has_access_token: boolean; 9 | has_refresh_token: boolean; 10 | is_valid: boolean; 11 | expiry_info: string | null; 12 | creds_path: string; 13 | extra: Record; 14 | } 15 | 16 | export interface EnvVariable { 17 | key: string; 18 | value: string; 19 | masked: string; 20 | } 21 | 22 | export interface CheckResult { 23 | changed: boolean; 24 | new_hash: string; 25 | reloaded: boolean; 26 | } 27 | 28 | export const credentialsApi = { 29 | /** Get credentials status for a specific provider */ 30 | getCredentials: (provider: OAuthProvider): Promise => 31 | invoke("get_oauth_credentials", { provider }), 32 | 33 | /** Get all OAuth credentials at once */ 34 | getAllCredentials: (): Promise => 35 | invoke("get_all_oauth_credentials"), 36 | 37 | /** Reload credentials from file */ 38 | reloadCredentials: (provider: OAuthProvider): Promise => 39 | invoke("reload_oauth_credentials", { provider }), 40 | 41 | /** Refresh OAuth token */ 42 | refreshToken: (provider: OAuthProvider): Promise => 43 | invoke("refresh_oauth_token", { provider }), 44 | 45 | /** Get environment variables for a provider */ 46 | getEnvVariables: (provider: OAuthProvider): Promise => 47 | invoke("get_oauth_env_variables", { provider }), 48 | 49 | /** Get token file hash for change detection */ 50 | getTokenFileHash: (provider: OAuthProvider): Promise => 51 | invoke("get_oauth_token_file_hash", { provider }), 52 | 53 | /** Check and reload credentials if file changed */ 54 | checkAndReload: ( 55 | provider: OAuthProvider, 56 | lastHash: string, 57 | ): Promise => 58 | invoke("check_and_reload_oauth_credentials", { 59 | provider, 60 | lastHash, 61 | }), 62 | }; 63 | -------------------------------------------------------------------------------- /src/components/HelpTip.tsx: -------------------------------------------------------------------------------- 1 | import { useState, ReactNode } from "react"; 2 | import { ChevronDown, ChevronUp, HelpCircle } from "lucide-react"; 3 | 4 | interface HelpTipProps { 5 | title: string; 6 | children: ReactNode; 7 | defaultOpen?: boolean; 8 | variant?: "blue" | "amber" | "green"; 9 | } 10 | 11 | export function HelpTip({ 12 | title, 13 | children, 14 | defaultOpen = false, 15 | variant = "blue", 16 | }: HelpTipProps) { 17 | const [isOpen, setIsOpen] = useState(defaultOpen); 18 | 19 | const variantStyles = { 20 | blue: { 21 | border: "border-blue-200 dark:border-blue-900", 22 | bg: "bg-blue-50 dark:bg-blue-950/30", 23 | title: "text-blue-800 dark:text-blue-300", 24 | icon: "text-blue-600 dark:text-blue-400", 25 | }, 26 | amber: { 27 | border: "border-amber-200 dark:border-amber-900", 28 | bg: "bg-amber-50 dark:bg-amber-950/30", 29 | title: "text-amber-800 dark:text-amber-300", 30 | icon: "text-amber-600 dark:text-amber-400", 31 | }, 32 | green: { 33 | border: "border-green-200 dark:border-green-900", 34 | bg: "bg-green-50 dark:bg-green-950/30", 35 | title: "text-green-800 dark:text-green-300", 36 | icon: "text-green-600 dark:text-green-400", 37 | }, 38 | }; 39 | 40 | const styles = variantStyles[variant]; 41 | 42 | return ( 43 |
44 | 58 | {isOpen &&
{children}
} 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | LayoutDashboard, 3 | Settings, 4 | Globe, 5 | Database, 6 | Route, 7 | FileCode, 8 | Puzzle, 9 | Activity, 10 | } from "lucide-react"; 11 | import { cn } from "@/lib/utils"; 12 | 13 | type Page = 14 | | "dashboard" 15 | | "provider-pool" 16 | | "routing-management" 17 | | "config-management" 18 | | "extensions" 19 | | "api-server" 20 | | "flow-monitor" 21 | | "settings"; 22 | 23 | interface SidebarProps { 24 | currentPage: Page; 25 | onNavigate: (page: Page) => void; 26 | } 27 | 28 | const navItems = [ 29 | { id: "dashboard" as Page, label: "仪表盘", icon: LayoutDashboard }, 30 | { id: "provider-pool" as Page, label: "凭证池", icon: Database }, 31 | { id: "routing-management" as Page, label: "路由管理", icon: Route }, 32 | { id: "config-management" as Page, label: "配置管理", icon: FileCode }, 33 | { id: "extensions" as Page, label: "扩展", icon: Puzzle }, 34 | { id: "api-server" as Page, label: "API Server", icon: Globe }, 35 | { id: "flow-monitor" as Page, label: "Flow Monitor", icon: Activity }, 36 | { id: "settings" as Page, label: "设置", icon: Settings }, 37 | ]; 38 | 39 | export function Sidebar({ currentPage, onNavigate }: SidebarProps) { 40 | return ( 41 |
42 |
43 |

ProxyCast

44 |

AI API Proxy

45 |
46 | 63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { Sidebar } from "./components/Sidebar"; 3 | import { Dashboard } from "./components/Dashboard"; 4 | import { SettingsPage } from "./components/settings"; 5 | import { ApiServerPage } from "./components/api-server/ApiServerPage"; 6 | import { ProviderPoolPage } from "./components/provider-pool"; 7 | import { RoutingManagementPage } from "./components/routing/RoutingManagementPage"; 8 | import { ConfigManagementPage } from "./components/config/ConfigManagementPage"; 9 | import { ExtensionsPage } from "./components/extensions"; 10 | import { FlowMonitorPage } from "./pages"; 11 | import { flowEventManager } from "./lib/flowEventManager"; 12 | 13 | type Page = 14 | | "dashboard" 15 | | "provider-pool" 16 | | "routing-management" 17 | | "config-management" 18 | | "extensions" 19 | | "api-server" 20 | | "flow-monitor" 21 | | "settings"; 22 | 23 | function App() { 24 | const [currentPage, setCurrentPage] = useState("dashboard"); 25 | 26 | // 在应用启动时初始化 Flow 事件订阅 27 | useEffect(() => { 28 | flowEventManager.subscribe(); 29 | // 应用卸载时不取消订阅,因为这是全局订阅 30 | }, []); 31 | 32 | const renderPage = () => { 33 | switch (currentPage) { 34 | case "dashboard": 35 | return ; 36 | case "provider-pool": 37 | return ; 38 | case "routing-management": 39 | return ; 40 | case "config-management": 41 | return ; 42 | case "extensions": 43 | return ; 44 | case "api-server": 45 | return ; 46 | case "flow-monitor": 47 | return ; 48 | case "settings": 49 | return ; 50 | default: 51 | return ; 52 | } 53 | }; 54 | 55 | return ( 56 |
57 | 58 |
{renderPage()}
59 |
60 | ); 61 | } 62 | 63 | export default App; 64 | -------------------------------------------------------------------------------- /src/components/routing/RoutingManagementPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Route, Shield } from "lucide-react"; 3 | import { cn } from "@/lib/utils"; 4 | import { RoutingPage } from "./RoutingPage"; 5 | import { ResiliencePage } from "../resilience/ResiliencePage"; 6 | 7 | type Tab = "routing" | "resilience"; 8 | 9 | const tabs = [ 10 | { id: "routing" as Tab, label: "智能路由", icon: Route }, 11 | { id: "resilience" as Tab, label: "容错配置", icon: Shield }, 12 | ]; 13 | 14 | export function RoutingManagementPage() { 15 | const [activeTab, setActiveTab] = useState("routing"); 16 | 17 | return ( 18 |
19 |
20 |

路由管理

21 |

配置智能路由规则和容错策略

22 |
23 | 24 | {/* Tab 切换 */} 25 |
26 | {tabs.map((tab) => ( 27 | 40 | ))} 41 |
42 | 43 | {/* Tab 内容 */} 44 |
45 | {activeTab === "routing" && } 46 | {activeTab === "resilience" && } 47 |
48 |
49 | ); 50 | } 51 | 52 | // 路由页面内容(去掉标题) 53 | function RoutingPageContent() { 54 | return ( 55 |
56 | 57 |
58 | ); 59 | } 60 | 61 | // 容错页面内容(去掉标题) 62 | function ResiliencePageContent() { 63 | return ( 64 |
65 | 66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /docs/content/03.providers/2.kiro-claude.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Kiro Claude 3 | description: 配置 Kiro IDE 的 Claude 凭证 4 | navigation: 5 | icon: i-heroicons-cpu-chip 6 | --- 7 | 8 | # Kiro Claude 9 | 10 | Kiro Claude 是 AWS Kiro IDE 提供的 Claude AI 服务凭证。 11 | 12 | ## 凭证位置 13 | 14 | ### 默认路径 15 | 16 | Kiro 凭证文件位于: 17 | 18 | | 平台 | 路径 | 19 | |------|------| 20 | | macOS | `~/.kiro/credentials.json` | 21 | | Windows | `%USERPROFILE%\.kiro\credentials.json` | 22 | 23 | ### 凭证格式 24 | 25 | ```json 26 | { 27 | "accessToken": "eyJ...", 28 | "refreshToken": "eyJ...", 29 | "expiresAt": "2024-01-01T00:00:00Z" 30 | } 31 | ``` 32 | 33 | ## 自动刷新机制 34 | 35 | ### Token 生命周期 36 | 37 | - Access Token 有效期:约 1 小时 38 | - Refresh Token 有效期:约 30 天 39 | 40 | ### 自动刷新 41 | 42 | ProxyCast 会自动处理 Token 刷新: 43 | 44 | 1. 检测 Access Token 即将过期 45 | 2. 使用 Refresh Token 获取新 Token 46 | 3. 更新本地凭证文件 47 | 4. 继续处理请求 48 | 49 | ### 刷新失败处理 50 | 51 | 如果刷新失败: 52 | 53 | 1. 标记凭证为"已过期" 54 | 2. 尝试使用其他可用凭证 55 | 3. 通知用户重新登录 Kiro 56 | 57 | ## 配置步骤 58 | 59 | ### 自动检测 60 | 61 | 1. 确保已安装 Kiro IDE 62 | 2. 在 Kiro 中完成登录 63 | 3. 启动 ProxyCast 64 | 4. 凭证自动出现在凭证池中 65 | 66 | ### 手动添加 67 | 68 | 如果自动检测失败: 69 | 70 | 1. 进入 **凭证池** 页面 71 | 2. 点击 **添加凭证** 72 | 3. 选择 **Kiro Claude** 73 | 4. 选择凭证文件或手动输入 74 | 75 | ## 支持的模型 76 | 77 | | 模型 | 说明 | 78 | |------|------| 79 | | claude-sonnet-4-20250514 | Claude Sonnet 4 | 80 | | claude-3-5-sonnet-20241022 | Claude 3.5 Sonnet | 81 | | claude-3-5-haiku-20241022 | Claude 3.5 Haiku | 82 | 83 | ## 使用限制 84 | 85 | ### 额度限制 86 | 87 | Kiro 订阅有使用额度限制,具体取决于订阅计划。 88 | 89 | ### 并发限制 90 | 91 | 建议单个凭证的并发请求数不超过 5。 92 | 93 | ## 故障排除 94 | 95 | ### 凭证未检测到 96 | 97 | 1. 确认 Kiro 已安装并登录 98 | 2. 检查凭证文件是否存在 99 | 3. 点击 **刷新凭证** 重新扫描 100 | 101 | ### Token 过期 102 | 103 | 1. 打开 Kiro IDE 104 | 2. 确认登录状态 105 | 3. 如需要,重新登录 106 | 4. 在 ProxyCast 中刷新凭证 107 | 108 | ### 请求失败 109 | 110 | 常见错误: 111 | 112 | | 错误 | 原因 | 解决方案 | 113 | |------|------|----------| 114 | | 401 Unauthorized | Token 无效 | 刷新凭证或重新登录 | 115 | | 429 Too Many Requests | 超出限制 | 等待或使用其他凭证 | 116 | | 503 Service Unavailable | 服务不可用 | 稍后重试 | 117 | -------------------------------------------------------------------------------- /src/components/extensions/ExtensionsPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Plug, MessageSquare, Boxes, Puzzle } from "lucide-react"; 3 | import { cn } from "@/lib/utils"; 4 | import { McpPage } from "../mcp/McpPage"; 5 | import { PromptsPage } from "../prompts/PromptsPage"; 6 | import { SkillsPage } from "../skills/SkillsPage"; 7 | import { PluginManager } from "../plugins/PluginManager"; 8 | 9 | type Tab = "mcp" | "prompts" | "skills" | "plugins"; 10 | 11 | const tabs = [ 12 | { id: "mcp" as Tab, label: "MCP", icon: Plug }, 13 | { id: "prompts" as Tab, label: "Prompts", icon: MessageSquare }, 14 | { id: "skills" as Tab, label: "Skills", icon: Boxes }, 15 | { id: "plugins" as Tab, label: "Plugins", icon: Puzzle }, 16 | ]; 17 | 18 | export function ExtensionsPage() { 19 | const [activeTab, setActiveTab] = useState("mcp"); 20 | 21 | return ( 22 |
23 |
24 |

扩展

25 |

26 | 管理 MCP 服务器、Prompts 和 Skills 27 |

28 |
29 | 30 | {/* Tab 切换 */} 31 |
32 | {tabs.map((tab) => ( 33 | 46 | ))} 47 |
48 | 49 | {/* Tab 内容 */} 50 |
51 | {activeTab === "mcp" && } 52 | {activeTab === "prompts" && } 53 | {activeTab === "skills" && } 54 | {activeTab === "plugins" && } 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /src-tauri/src/processor/steps/traits.rs: -------------------------------------------------------------------------------- 1 | //! 管道步骤 trait 定义 2 | //! 3 | //! 定义所有管道步骤必须实现的接口 4 | 5 | use crate::processor::RequestContext; 6 | use async_trait::async_trait; 7 | use thiserror::Error; 8 | 9 | /// 步骤错误 10 | #[derive(Error, Debug, Clone)] 11 | pub enum StepError { 12 | /// 认证错误 13 | #[error("认证错误: {0}")] 14 | Auth(String), 15 | 16 | /// 路由错误 17 | #[error("路由错误: {0}")] 18 | Routing(String), 19 | 20 | /// 注入错误 21 | #[error("注入错误: {0}")] 22 | Injection(String), 23 | 24 | /// Provider 错误 25 | #[error("Provider 错误: {0}")] 26 | Provider(String), 27 | 28 | /// 插件错误 29 | #[error("插件错误: {plugin_name} - {message}")] 30 | Plugin { 31 | plugin_name: String, 32 | message: String, 33 | }, 34 | 35 | /// 遥测错误 36 | #[error("遥测错误: {0}")] 37 | Telemetry(String), 38 | 39 | /// 超时错误 40 | #[error("超时: {timeout_ms}ms")] 41 | Timeout { timeout_ms: u64 }, 42 | 43 | /// 内部错误 44 | #[error("内部错误: {0}")] 45 | Internal(String), 46 | } 47 | 48 | impl StepError { 49 | /// 获取对应的 HTTP 状态码 50 | pub fn status_code(&self) -> u16 { 51 | match self { 52 | StepError::Auth(_) => 401, 53 | StepError::Routing(_) => 404, 54 | StepError::Injection(_) => 400, 55 | StepError::Provider(_) => 502, 56 | StepError::Plugin { .. } => 500, 57 | StepError::Telemetry(_) => 500, 58 | StepError::Timeout { .. } => 408, 59 | StepError::Internal(_) => 500, 60 | } 61 | } 62 | } 63 | 64 | /// 管道步骤 trait 65 | /// 66 | /// 所有管道步骤必须实现此 trait 67 | #[async_trait] 68 | pub trait PipelineStep: Send + Sync { 69 | /// 执行步骤 70 | /// 71 | /// # Arguments 72 | /// * `ctx` - 请求上下文 73 | /// * `payload` - 请求/响应负载 74 | /// 75 | /// # Returns 76 | /// 成功返回 `Ok(())`,失败返回 `Err(StepError)` 77 | async fn execute( 78 | &self, 79 | ctx: &mut RequestContext, 80 | payload: &mut serde_json::Value, 81 | ) -> Result<(), StepError>; 82 | 83 | /// 获取步骤名称 84 | fn name(&self) -> &str; 85 | 86 | /// 检查步骤是否启用 87 | fn is_enabled(&self) -> bool { 88 | true 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/components/provider-pool/credential-forms/FileImportForm.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 文件导入表单组件 3 | */ 4 | 5 | import { FolderOpen } from "lucide-react"; 6 | 7 | interface FileImportFormProps { 8 | credsFilePath: string; 9 | setCredsFilePath: (path: string) => void; 10 | onSelectFile: () => void; 11 | placeholder?: string; 12 | hint?: string; 13 | projectId?: string; 14 | setProjectId?: (id: string) => void; 15 | showProjectId?: boolean; 16 | } 17 | 18 | export function FileImportForm({ 19 | credsFilePath, 20 | setCredsFilePath, 21 | onSelectFile, 22 | placeholder = "选择凭证文件...", 23 | hint, 24 | projectId, 25 | setProjectId, 26 | showProjectId = false, 27 | }: FileImportFormProps) { 28 | return ( 29 | <> 30 |
31 | 34 |
35 | setCredsFilePath(e.target.value)} 39 | placeholder={placeholder} 40 | className="flex-1 rounded-lg border bg-background px-3 py-2 text-sm" 41 | /> 42 | 50 |
51 | {hint &&

{hint}

} 52 |
53 | 54 | {showProjectId && setProjectId && ( 55 |
56 | 59 | setProjectId(e.target.value)} 63 | placeholder="Google Cloud Project ID..." 64 | className="w-full rounded-lg border bg-background px-3 py-2 text-sm" 65 | /> 66 |
67 | )} 68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxycast", 3 | "private": true, 4 | "version": "0.16.0", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/aiclientproxy/proxycast.git" 9 | }, 10 | "homepage": "https://github.com/aiclientproxy/proxycast", 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "tsc && vite build", 14 | "preview": "vite preview", 15 | "tauri": "tauri", 16 | "lint": "eslint src --max-warnings 0", 17 | "format": "prettier --write \"src/**/*.{ts,tsx,css}\"", 18 | "prepare": "husky", 19 | "test": "vitest --run", 20 | "test:watch": "vitest" 21 | }, 22 | "dependencies": { 23 | "@radix-ui/react-dialog": "^1.1.2", 24 | "@radix-ui/react-dropdown-menu": "^2.1.2", 25 | "@radix-ui/react-label": "^2.1.0", 26 | "@radix-ui/react-select": "^2.1.2", 27 | "@radix-ui/react-slot": "^1.1.0", 28 | "@radix-ui/react-switch": "^1.1.1", 29 | "@radix-ui/react-tabs": "^1.1.1", 30 | "@radix-ui/react-toast": "^1.2.2", 31 | "@radix-ui/react-tooltip": "^1.1.3", 32 | "@tauri-apps/api": "^2.0.0", 33 | "@tauri-apps/plugin-dialog": "^2.4.2", 34 | "@tauri-apps/plugin-shell": "^2.0.0", 35 | "class-variance-authority": "^0.7.0", 36 | "clsx": "^2.1.1", 37 | "lucide-react": "^0.460.0", 38 | "react": "^18.3.1", 39 | "react-dom": "^18.3.1", 40 | "sonner": "^2.0.7", 41 | "tailwind-merge": "^2.5.4" 42 | }, 43 | "devDependencies": { 44 | "@eslint/js": "^9.15.0", 45 | "@fast-check/vitest": "^0.2.4", 46 | "@tauri-apps/cli": "^2.0.0", 47 | "@types/node": "^22.9.0", 48 | "@types/react": "^18.3.12", 49 | "@types/react-dom": "^18.3.1", 50 | "@typescript-eslint/eslint-plugin": "^8.15.0", 51 | "@typescript-eslint/parser": "^8.15.0", 52 | "@vitejs/plugin-react": "^4.3.3", 53 | "autoprefixer": "^10.4.20", 54 | "eslint": "^9.15.0", 55 | "eslint-plugin-react-hooks": "^5.0.0", 56 | "eslint-plugin-react-refresh": "^0.4.14", 57 | "fast-check": "^4.4.0", 58 | "globals": "^15.12.0", 59 | "husky": "^9.1.7", 60 | "postcss": "^8.4.47", 61 | "prettier": "^3.3.3", 62 | "tailwindcss": "^3.4.14", 63 | "typescript": "^5.6.3", 64 | "vite": "^5.4.10", 65 | "vitest": "^4.0.16" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/content/05.troubleshooting/1.common-issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 常见问题 3 | description: 常见问题汇总和解决方案 4 | navigation: 5 | icon: i-heroicons-question-mark-circle 6 | --- 7 | 8 | # 常见问题 9 | 10 | 本页汇总了 ProxyCast 使用中的常见问题和解决方案。 11 | 12 | ## 启动问题 13 | 14 | ### 应用无法启动 15 | 16 | **症状**: 双击应用图标后无反应 17 | 18 | **解决方案**: 19 | 20 | 1. **macOS**: 右键点击应用,选择"打开" 21 | 2. **Windows**: 以管理员身份运行 22 | 3. 检查系统日志查看错误信息 23 | 24 | ### 端口被占用 25 | 26 | **症状**: 服务启动失败,提示端口已被使用 27 | 28 | **解决方案**: 29 | 30 | ```bash 31 | # 查找占用端口的进程 32 | # macOS/Linux 33 | lsof -i :9090 34 | 35 | # Windows 36 | netstat -ano | findstr :9090 37 | ``` 38 | 39 | 或在设置中更改端口号。 40 | 41 | ## 凭证问题 42 | 43 | ### 凭证未检测到 44 | 45 | **症状**: 凭证池为空,未显示任何凭证 46 | 47 | **解决方案**: 48 | 49 | 1. 确认 AI 客户端已安装并登录 50 | 2. 检查凭证文件是否存在 51 | 3. 点击"刷新凭证"重新扫描 52 | 4. 手动添加凭证 53 | 54 | 详见 [凭证错误](/troubleshooting/credential-errors) 55 | 56 | ### Token 过期 57 | 58 | **症状**: 请求返回 401 错误 59 | 60 | **解决方案**: 61 | 62 | 1. 打开对应的 AI 客户端 63 | 2. 确认登录状态 64 | 3. 在 ProxyCast 中刷新凭证 65 | 66 | ## 连接问题 67 | 68 | ### 无法连接到 Provider 69 | 70 | **症状**: 请求超时或连接失败 71 | 72 | **解决方案**: 73 | 74 | 1. 检查网络连接 75 | 2. 确认 Provider 服务正常 76 | 3. 检查代理设置 77 | 78 | 详见 [连接问题](/troubleshooting/connection-issues) 79 | 80 | ### SSL 证书错误 81 | 82 | **症状**: 提示证书验证失败 83 | 84 | **解决方案**: 85 | 86 | 1. 检查系统时间是否正确 87 | 2. 更新系统根证书 88 | 3. 检查代理是否拦截 HTTPS 89 | 90 | ## API 问题 91 | 92 | ### 请求返回错误 93 | 94 | **常见错误码**: 95 | 96 | | 错误码 | 原因 | 解决方案 | 97 | |--------|------|----------| 98 | | 400 | 请求格式错误 | 检查请求参数 | 99 | | 401 | 认证失败 | 检查 API Key | 100 | | 404 | 端点不存在 | 检查 URL | 101 | | 429 | 速率限制 | 降低请求频率 | 102 | | 500 | 服务器错误 | 查看日志 | 103 | 104 | ### 流式响应中断 105 | 106 | **症状**: 流式响应突然停止 107 | 108 | **解决方案**: 109 | 110 | 1. 检查网络稳定性 111 | 2. 增加超时时间 112 | 3. 检查 Provider 状态 113 | 114 | ## 性能问题 115 | 116 | ### 响应缓慢 117 | 118 | **可能原因**: 119 | 120 | 1. Provider 响应慢 121 | 2. 网络延迟高 122 | 3. 请求内容过长 123 | 124 | **解决方案**: 125 | 126 | 1. 切换到更快的 Provider 127 | 2. 使用更快的模型 128 | 3. 减少请求内容长度 129 | 130 | ### 内存占用高 131 | 132 | **解决方案**: 133 | 134 | 1. 清除请求日志 135 | 2. 减少日志保留天数 136 | 3. 重启应用 137 | 138 | ## 获取帮助 139 | 140 | 如果以上方案无法解决问题: 141 | 142 | 1. 查看应用日志 143 | 2. 在 GitHub 提交 Issue 144 | 3. 提供详细的错误信息和复现步骤 145 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/3.credential-pool.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 凭证池 3 | description: 管理多个 AI 服务凭证 4 | navigation: 5 | icon: i-heroicons-key 6 | --- 7 | 8 | # 凭证池 9 | 10 | 凭证池用于管理多个 AI 服务凭证,支持负载均衡和故障转移。 11 | 12 | ## 池管理界面 13 | 14 | ### 凭证列表 15 | 16 | 显示所有已添加的凭证: 17 | 18 | | 字段 | 说明 | 19 | |------|------| 20 | | 名称 | 凭证标识名称 | 21 | | Provider | 凭证类型 | 22 | | 状态 | 有效/过期/错误 | 23 | | 优先级 | 负载均衡优先级 | 24 | | 操作 | 编辑/删除/测试 | 25 | 26 | ### 状态指示 27 | 28 | - 🟢 **有效**: 凭证可正常使用 29 | - 🟡 **即将过期**: Token 即将过期,需要刷新 30 | - 🔴 **已过期**: Token 已过期 31 | - ⚪ **未验证**: 尚未验证凭证有效性 32 | 33 | ## 添加凭证 34 | 35 | ### 自动检测 36 | 37 | ProxyCast 会自动检测以下位置的凭证: 38 | 39 | ``` 40 | ~/.kiro/credentials.json # Kiro Claude 41 | ~/.config/gemini-cli/oauth_creds.json # Gemini CLI 42 | ~/.config/qwen/credentials.json # Qwen 43 | ``` 44 | 45 | 点击 **刷新凭证** 重新扫描。 46 | 47 | ### 从文件加载 48 | 49 | 1. 点击 **添加凭证** 50 | 2. 选择 **从文件加载** 51 | 3. 选择凭证文件 52 | 4. 确认 Provider 类型 53 | 54 | ### 手动输入 55 | 56 | 1. 点击 **添加凭证** 57 | 2. 选择 **手动输入** 58 | 3. 选择 Provider 类型 59 | 4. 填写凭证信息: 60 | 61 | **OAuth 类型 (Kiro/Gemini/Qwen):** 62 | - Access Token 63 | - Refresh Token 64 | - 过期时间 65 | 66 | **API Key 类型 (OpenAI/Claude Custom):** 67 | - API Key 68 | - Base URL(可选) 69 | 70 | ## 负载均衡配置 71 | 72 | ### 策略选择 73 | 74 | | 策略 | 说明 | 75 | |------|------| 76 | | 轮询 (Round Robin) | 依次使用每个凭证 | 77 | | 优先级 (Priority) | 按优先级顺序使用 | 78 | | 随机 (Random) | 随机选择凭证 | 79 | | 最少使用 (Least Used) | 优先使用请求数最少的凭证 | 80 | 81 | ### 优先级设置 82 | 83 | 为每个凭证设置优先级(1-100): 84 | 85 | - 数值越小优先级越高 86 | - 相同优先级按策略选择 87 | - 优先级为 0 表示禁用 88 | 89 | ### 健康检查 90 | 91 | 启用健康检查后: 92 | 93 | - 定期验证凭证有效性 94 | - 自动跳过失效凭证 95 | - 凭证恢复后自动重新启用 96 | 97 | 配置选项: 98 | 99 | | 选项 | 默认值 | 说明 | 100 | |------|--------|------| 101 | | 检查间隔 | 60s | 健康检查频率 | 102 | | 失败阈值 | 3 | 连续失败次数后标记为不健康 | 103 | | 恢复阈值 | 1 | 成功次数后恢复健康状态 | 104 | 105 | ## 凭证操作 106 | 107 | ### 测试凭证 108 | 109 | 点击 **测试** 按钮验证凭证: 110 | 111 | 1. 发送测试请求 112 | 2. 显示测试结果 113 | 3. 更新凭证状态 114 | 115 | ### 刷新 Token 116 | 117 | 对于 OAuth 凭证: 118 | 119 | - 自动刷新:Token 过期前自动刷新 120 | - 手动刷新:点击 **刷新** 按钮 121 | 122 | ### 删除凭证 123 | 124 | 1. 点击 **删除** 按钮 125 | 2. 确认删除操作 126 | 3. 凭证从池中移除 127 | 128 | ::alert{type="warning"} 129 | 删除凭证不会删除本地凭证文件,只是从 ProxyCast 中移除。 130 | :: 131 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | # 部署文档站点到 GitHub Pages 2 | name: Deploy Docs to Pages 3 | 4 | # 触发条件 5 | on: 6 | # 推送到 main 分支且 docs 目录有变更时触发 7 | push: 8 | branches: [main] 9 | paths: 10 | - "docs/**" 11 | 12 | # PR 到 main 分支且 docs 目录有变更时触发(仅构建,不部署) 13 | pull_request: 14 | branches: [main] 15 | paths: 16 | - "docs/**" 17 | 18 | # 允许手动触发 19 | workflow_dispatch: 20 | 21 | # 设置 GitHub Pages 部署所需的权限 22 | permissions: 23 | contents: read 24 | pages: write 25 | id-token: write 26 | 27 | # 只允许一个并发部署 28 | concurrency: 29 | group: pages 30 | cancel-in-progress: false 31 | 32 | jobs: 33 | # 构建任务 34 | build: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | with: 40 | fetch-depth: 0 41 | 42 | - name: Setup Node.js 43 | uses: actions/setup-node@v4 44 | with: 45 | node-version: "20" 46 | 47 | - name: Setup pnpm 48 | uses: pnpm/action-setup@v4 49 | with: 50 | version: 9 51 | 52 | - name: Get pnpm store directory 53 | shell: bash 54 | run: | 55 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 56 | 57 | - name: Setup pnpm cache 58 | uses: actions/cache@v4 59 | with: 60 | path: ${{ env.STORE_PATH }} 61 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('docs/pnpm-lock.yaml') }} 62 | restore-keys: | 63 | ${{ runner.os }}-pnpm-store- 64 | 65 | - name: Setup Pages 66 | uses: actions/configure-pages@v5 67 | 68 | - name: Install dependencies 69 | run: pnpm install 70 | working-directory: docs 71 | 72 | - name: Build 73 | run: pnpm run generate 74 | working-directory: docs 75 | 76 | - name: Upload artifact 77 | uses: actions/upload-pages-artifact@v3 78 | with: 79 | path: docs/.output/public 80 | 81 | # 部署任务 82 | deploy: 83 | environment: 84 | name: github-pages 85 | url: ${{ steps.deployment.outputs.page_url }} 86 | needs: build 87 | if: github.event_name != 'pull_request' 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: Deploy to GitHub Pages 91 | id: deployment 92 | uses: actions/deploy-pages@v4 93 | -------------------------------------------------------------------------------- /src/hooks/useSwitch.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from "react"; 2 | import { toast } from "sonner"; 3 | import { switchApi, Provider, AppType } from "@/lib/api/switch"; 4 | 5 | export function useSwitch(appType: AppType) { 6 | const [providers, setProviders] = useState([]); 7 | const [currentProvider, setCurrentProvider] = useState(null); 8 | const [loading, setLoading] = useState(true); 9 | const [error, setError] = useState(null); 10 | 11 | const fetchProviders = useCallback(async () => { 12 | try { 13 | setLoading(true); 14 | setError(null); 15 | const [list, current] = await Promise.all([ 16 | switchApi.getProviders(appType), 17 | switchApi.getCurrentProvider(appType), 18 | ]); 19 | setProviders(list); 20 | setCurrentProvider(current); 21 | } catch (e) { 22 | setError(e instanceof Error ? e.message : String(e)); 23 | } finally { 24 | setLoading(false); 25 | } 26 | }, [appType]); 27 | 28 | useEffect(() => { 29 | fetchProviders(); 30 | }, [fetchProviders]); 31 | 32 | const addProvider = async ( 33 | provider: Omit, 34 | ) => { 35 | const newProvider: Provider = { 36 | ...provider, 37 | id: crypto.randomUUID(), 38 | app_type: appType, 39 | is_current: false, 40 | created_at: Date.now(), 41 | }; 42 | await switchApi.addProvider(newProvider); 43 | await fetchProviders(); 44 | toast.success("配置已添加"); 45 | }; 46 | 47 | const updateProvider = async (provider: Provider) => { 48 | await switchApi.updateProvider(provider); 49 | await fetchProviders(); 50 | toast.success("配置已更新"); 51 | }; 52 | 53 | const deleteProvider = async (id: string) => { 54 | await switchApi.deleteProvider(appType, id); 55 | await fetchProviders(); 56 | toast.success("配置已删除"); 57 | }; 58 | 59 | const switchToProvider = async (id: string) => { 60 | await switchApi.switchProvider(appType, id); 61 | await fetchProviders(); 62 | toast.success("切换成功"); 63 | }; 64 | 65 | return { 66 | providers, 67 | currentProvider, 68 | loading, 69 | error, 70 | addProvider, 71 | updateProvider, 72 | deleteProvider, 73 | switchToProvider, 74 | refresh: fetchProviders, 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/10.prompts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prompts 管理 3 | description: 提示词存储和管理 4 | navigation: 5 | icon: i-heroicons-document-text 6 | --- 7 | 8 | # Prompts 管理 9 | 10 | Prompts 功能帮助你存储、组织和复用常用的提示词模板。 11 | 12 | ## 提示词存储 13 | 14 | ### 创建提示词 15 | 16 | 1. 进入 **Prompts** 页面 17 | 2. 点击 **新建提示词** 18 | 3. 填写提示词信息: 19 | 20 | | 字段 | 说明 | 21 | |------|------| 22 | | 名称 | 提示词标识名称 | 23 | | 描述 | 提示词用途说明 | 24 | | 内容 | 提示词正文 | 25 | | 标签 | 分类标签 | 26 | 27 | ### 提示词示例 28 | 29 | ```yaml 30 | name: "代码审查" 31 | description: "审查代码质量和最佳实践" 32 | content: | 33 | 请审查以下代码,关注: 34 | 1. 代码质量和可读性 35 | 2. 潜在的 bug 和安全问题 36 | 3. 性能优化建议 37 | 4. 最佳实践遵循情况 38 | 39 | 请提供具体的改进建议。 40 | tags: 41 | - 代码 42 | - 审查 43 | ``` 44 | 45 | ## 组织方式 46 | 47 | ### 文件夹分类 48 | 49 | 创建文件夹组织提示词: 50 | 51 | - 📁 代码相关 52 | - 代码审查 53 | - 代码重构 54 | - 单元测试 55 | - 📁 写作相关 56 | - 文档撰写 57 | - 邮件回复 58 | - 📁 翻译相关 59 | - 中英翻译 60 | - 技术翻译 61 | 62 | ### 标签系统 63 | 64 | 使用标签快速筛选: 65 | 66 | - `#代码` - 代码相关提示词 67 | - `#写作` - 写作相关提示词 68 | - `#常用` - 常用提示词 69 | 70 | ### 搜索功能 71 | 72 | 支持按以下条件搜索: 73 | 74 | - 名称 75 | - 描述 76 | - 内容 77 | - 标签 78 | 79 | ## 注入请求 80 | 81 | ### 系统提示词 82 | 83 | 将提示词作为系统消息注入: 84 | 85 | ```json 86 | { 87 | "model": "claude-sonnet-4-20250514", 88 | "messages": [ 89 | {"role": "system", "content": "你是一个代码审查专家..."}, 90 | {"role": "user", "content": "请审查这段代码..."} 91 | ] 92 | } 93 | ``` 94 | 95 | ### 使用方式 96 | 97 | 1. **手动复制**: 复制提示词内容到请求 98 | 2. **快捷插入**: 在 API 测试面板选择提示词 99 | 3. **自动注入**: 配置默认系统提示词 100 | 101 | ### 配置默认提示词 102 | 103 | 1. 进入 **设置** > **API Server** 104 | 2. 选择 **默认系统提示词** 105 | 3. 所有请求自动注入该提示词 106 | 107 | ## 变量支持 108 | 109 | ### 定义变量 110 | 111 | 在提示词中使用变量: 112 | 113 | ``` 114 | 请将以下 {{source_lang}} 文本翻译成 {{target_lang}}: 115 | 116 | {{content}} 117 | ``` 118 | 119 | ### 使用变量 120 | 121 | 调用时替换变量值: 122 | 123 | ```json 124 | { 125 | "prompt": "翻译模板", 126 | "variables": { 127 | "source_lang": "英文", 128 | "target_lang": "中文", 129 | "content": "Hello, world!" 130 | } 131 | } 132 | ``` 133 | 134 | ## 导入导出 135 | 136 | ### 导出提示词 137 | 138 | 1. 选择要导出的提示词 139 | 2. 点击 **导出** 140 | 3. 保存为 `.json` 或 `.yaml` 文件 141 | 142 | ### 导入提示词 143 | 144 | 1. 点击 **导入** 145 | 2. 选择提示词文件 146 | 3. 确认导入 147 | 148 | ### 分享提示词 149 | 150 | 导出的提示词文件可以分享给他人使用。 151 | -------------------------------------------------------------------------------- /src-tauri/src/flow_monitor/mod.rs: -------------------------------------------------------------------------------- 1 | //! LLM Flow Monitor 模块 2 | //! 3 | //! 该模块提供完整的 LLM API 流量监控功能,参考 mitmproxy 的 Flow 模型设计。 4 | //! 用于捕获、存储、分析和回放 AI Agent 与大模型之间的完整交互数据。 5 | //! 6 | //! # 主要组件 7 | //! 8 | //! - `models`: 核心数据模型,包括 LLMFlow、LLMRequest、LLMResponse 等 9 | //! - `stream_rebuilder`: SSE 流式响应重建器 10 | //! - `memory_store`: 内存存储,支持 LRU 驱逐策略 11 | //! - `file_store`: 文件存储,支持 JSONL 格式和 SQLite 索引 12 | //! - `query_service`: 查询服务,支持多维度过滤、排序、分页和全文搜索 13 | //! - `exporter`: 导出服务,支持 HAR、JSON、JSONL、Markdown、CSV 格式 14 | //! - `monitor`: 核心监控服务 15 | 16 | pub mod exporter; 17 | pub mod file_store; 18 | pub mod memory_store; 19 | pub mod models; 20 | pub mod monitor; 21 | pub mod query_service; 22 | pub mod stream_rebuilder; 23 | 24 | // 重新导出核心类型 25 | pub use models::{ 26 | ClientInfo, 27 | ContentPart, 28 | FlowAnnotations, 29 | // 错误 30 | FlowError, 31 | FlowErrorType, 32 | // 元数据 33 | FlowMetadata, 34 | FlowState, 35 | FlowTimestamps, 36 | FlowType, 37 | // 核心 Flow 结构 38 | LLMFlow, 39 | // 请求相关 40 | LLMRequest, 41 | // 响应相关 42 | LLMResponse, 43 | Message, 44 | MessageContent, 45 | MessageRole, 46 | RequestParameters, 47 | RoutingInfo, 48 | StopReason, 49 | StreamChunk, 50 | StreamInfo, 51 | ThinkingContent, 52 | TokenUsage, 53 | ToolCall, 54 | ToolCallDelta, 55 | ToolDefinition, 56 | ToolResult, 57 | }; 58 | 59 | // 重新导出流重建器 60 | pub use stream_rebuilder::{StreamFormat, StreamRebuilder, StreamRebuilderError}; 61 | 62 | // 重新导出内存存储 63 | pub use memory_store::{FlowFilter, FlowMemoryStore, LatencyRange, TimeRange, TokenRange}; 64 | 65 | // 重新导出文件存储 66 | pub use file_store::{ 67 | CleanupResult, FileStoreError, FlowFileStore, FlowIndexRecord, FtsSearchResult, RotationConfig, 68 | }; 69 | 70 | // 重新导出查询服务 71 | pub use query_service::{ 72 | FlowQueryResult, FlowQueryService, FlowSearchResult, FlowSortBy, FlowStats, ModelStats, 73 | ProviderStats, StateStats, 74 | }; 75 | 76 | // 重新导出导出服务 77 | pub use exporter::{ 78 | default_redaction_rules, ExportFormat, ExportOptions, ExportResult, FlowExporter, HarArchive, 79 | HarEntry, HarLlmExtension, HarLog, RedactionRule, Redactor, 80 | }; 81 | 82 | // 重新导出监控服务 83 | pub use monitor::{FlowEvent, FlowMonitor, FlowMonitorConfig, FlowSummary, FlowUpdate}; 84 | 85 | // 重新导出 ProviderType(从 lib.rs) 86 | pub use crate::ProviderType; 87 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/7.config-switch.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 配置切换 3 | description: 快速切换 AI 客户端配置 4 | navigation: 5 | icon: i-heroicons-arrows-up-down 6 | --- 7 | 8 | # 配置切换 9 | 10 | 配置切换功能让你可以一键在不同的 AI 客户端配置之间切换。 11 | 12 | ## 功能目的 13 | 14 | 当你需要在不同场景使用不同的 AI 服务时: 15 | 16 | - 开发时使用 Kiro Claude 17 | - 测试时使用 Gemini CLI 18 | - 生产时使用自定义 OpenAI 19 | 20 | 配置切换让你无需手动修改配置,一键完成切换。 21 | 22 | ## 预设配置 23 | 24 | ### 内置配置档案 25 | 26 | | 档案 | 说明 | 27 | |------|------| 28 | | Claude Code | 适用于 Claude Code 客户端 | 29 | | Codex | 适用于 OpenAI Codex | 30 | | Gemini CLI | 适用于 Gemini CLI | 31 | 32 | ### 配置内容 33 | 34 | 每个档案包含: 35 | 36 | - 默认 Provider 37 | - 路由规则 38 | - 模型映射 39 | - API 端点配置 40 | 41 | ## 创建配置档案 42 | 43 | ### 新建档案 44 | 45 | 1. 进入 **配置切换** 页面 46 | 2. 点击 **新建档案** 47 | 3. 输入档案名称 48 | 4. 配置以下内容: 49 | 50 | ```yaml 51 | name: "我的配置" 52 | description: "自定义配置档案" 53 | provider: kiro-claude 54 | routes: 55 | - pattern: "*" 56 | provider: kiro-claude 57 | settings: 58 | timeout: 120s 59 | retry: 3 60 | ``` 61 | 62 | ### 从当前配置创建 63 | 64 | 1. 配置好当前设置 65 | 2. 点击 **保存为档案** 66 | 3. 输入档案名称 67 | 4. 档案保存成功 68 | 69 | ## 切换配置 70 | 71 | ### 一键切换 72 | 73 | 1. 进入 **配置切换** 页面 74 | 2. 查看可用档案列表 75 | 3. 点击目标档案的 **应用** 按钮 76 | 4. 配置立即生效 77 | 78 | ### 快捷切换 79 | 80 | 使用标签页快速切换: 81 | 82 | - **Claude Code** 标签 83 | - **Codex** 标签 84 | - **Gemini CLI** 标签 85 | 86 | 点击标签即可切换到对应配置。 87 | 88 | ## 设置活动配置 89 | 90 | ### 标记当前配置 91 | 92 | 当前使用的配置会显示 **当前使用中** 标记。 93 | 94 | ### 切换活动配置 95 | 96 | 1. 选择目标档案 97 | 2. 点击 **设为活动** 98 | 3. 该档案成为当前活动配置 99 | 100 | ## 档案管理 101 | 102 | ### 编辑档案 103 | 104 | 1. 点击档案的 **编辑** 按钮 105 | 2. 修改配置内容 106 | 3. 点击 **保存** 107 | 108 | ### 删除档案 109 | 110 | 1. 点击档案的 **删除** 按钮 111 | 2. 确认删除操作 112 | 113 | ::alert{type="warning"} 114 | 内置档案无法删除,但可以修改。 115 | :: 116 | 117 | ### 导出档案 118 | 119 | 单独导出某个档案: 120 | 121 | 1. 点击档案的 **导出** 按钮 122 | 2. 选择保存位置 123 | 3. 档案保存为 `.yaml` 文件 124 | 125 | ### 导入档案 126 | 127 | 1. 点击 **导入档案** 128 | 2. 选择 `.yaml` 文件 129 | 3. 档案添加到列表 130 | 131 | ## 使用场景 132 | 133 | ### 场景 1: 开发/测试切换 134 | 135 | ``` 136 | 开发环境: 使用 Kiro Claude(免费额度) 137 | 测试环境: 使用 Gemini CLI(快速响应) 138 | ``` 139 | 140 | ### 场景 2: 模型切换 141 | 142 | ``` 143 | 代码生成: Claude Sonnet(高质量) 144 | 快速问答: Gemini Flash(低延迟) 145 | ``` 146 | 147 | ### 场景 3: 团队协作 148 | 149 | ``` 150 | 团队成员 A: 使用个人 Kiro 账户 151 | 团队成员 B: 使用共享 API Key 152 | ``` 153 | -------------------------------------------------------------------------------- /src/components/provider-pool/credential-forms/OAuthUrlDisplay.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * OAuth 授权 URL 显示组件 3 | * 用于显示授权 URL 和等待回调状态 4 | */ 5 | 6 | import { useState } from "react"; 7 | import { Copy, Check, Loader2 } from "lucide-react"; 8 | 9 | interface OAuthUrlDisplayProps { 10 | authUrl: string | null; 11 | waitingForCallback: boolean; 12 | colorScheme?: "blue" | "green" | "purple" | "amber"; 13 | } 14 | 15 | export function OAuthUrlDisplay({ 16 | authUrl, 17 | waitingForCallback, 18 | colorScheme: _colorScheme = "blue", 19 | }: OAuthUrlDisplayProps) { 20 | const [urlCopied, setUrlCopied] = useState(false); 21 | 22 | const handleCopyUrl = () => { 23 | if (authUrl) { 24 | navigator.clipboard.writeText(authUrl); 25 | setUrlCopied(true); 26 | setTimeout(() => setUrlCopied(false), 2000); 27 | } 28 | }; 29 | 30 | if (!authUrl) return null; 31 | 32 | return ( 33 |
34 |
35 |
36 | 授权 URL 37 | 54 |
55 |

56 | {authUrl.slice(0, 100)}... 57 |

58 |
59 | 60 | {waitingForCallback && ( 61 |
62 |
63 | 64 |

65 | 请复制上方 URL 到浏览器完成登录,正在等待授权回调... 66 |

67 |
68 |
69 | )} 70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /docs/content/05.troubleshooting/2.credential-errors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 凭证错误 3 | description: OAuth Token 问题诊断 4 | navigation: 5 | icon: i-heroicons-key 6 | --- 7 | 8 | # 凭证错误 9 | 10 | 本页帮助你诊断和解决凭证相关的问题。 11 | 12 | ## 诊断步骤 13 | 14 | ### 1. 检查凭证文件 15 | 16 | 确认凭证文件存在: 17 | 18 | ```bash 19 | # Kiro 20 | ls -la ~/.kiro/credentials.json 21 | 22 | # Gemini CLI 23 | ls -la ~/.config/gemini-cli/oauth_creds.json 24 | 25 | # Qwen 26 | ls -la ~/.config/qwen/credentials.json 27 | ``` 28 | 29 | ### 2. 验证文件格式 30 | 31 | 检查 JSON 格式是否正确: 32 | 33 | ```bash 34 | # 验证 JSON 格式 35 | cat ~/.kiro/credentials.json | python -m json.tool 36 | ``` 37 | 38 | ### 3. 检查 Token 有效性 39 | 40 | 在 ProxyCast 中: 41 | 42 | 1. 进入凭证池 43 | 2. 点击凭证的"测试"按钮 44 | 3. 查看测试结果 45 | 46 | ## 常见错误 47 | 48 | ### Token 已过期 49 | 50 | **症状**: 凭证状态显示"已过期" 51 | 52 | **原因**: 53 | - Access Token 超过有效期 54 | - Refresh Token 也已过期 55 | 56 | **解决方案**: 57 | 58 | 1. 打开对应的 AI 客户端 59 | 2. 重新登录 60 | 3. 在 ProxyCast 中刷新凭证 61 | 62 | ### Token 无效 63 | 64 | **症状**: 测试凭证返回 401 错误 65 | 66 | **原因**: 67 | - Token 被撤销 68 | - 账户状态异常 69 | 70 | **解决方案**: 71 | 72 | 1. 检查 AI 客户端账户状态 73 | 2. 重新登录获取新 Token 74 | 3. 删除旧凭证,重新添加 75 | 76 | ### 刷新失败 77 | 78 | **症状**: 自动刷新 Token 失败 79 | 80 | **原因**: 81 | - Refresh Token 过期 82 | - 网络问题 83 | - 服务端问题 84 | 85 | **解决方案**: 86 | 87 | 1. 检查网络连接 88 | 2. 手动刷新凭证 89 | 3. 如仍失败,重新登录客户端 90 | 91 | ## Provider 特定问题 92 | 93 | ### Kiro Claude 94 | 95 | **凭证位置**: `~/.kiro/credentials.json` 96 | 97 | **常见问题**: 98 | 99 | 1. **未安装 Kiro**: 安装 Kiro IDE 100 | 2. **未登录**: 在 Kiro 中完成登录 101 | 3. **订阅过期**: 检查 Kiro 订阅状态 102 | 103 | ### Gemini CLI 104 | 105 | **凭证位置**: `~/.config/gemini-cli/oauth_creds.json` 106 | 107 | **常见问题**: 108 | 109 | 1. **未安装 CLI**: 安装 Gemini CLI 110 | 2. **未认证**: 运行 `gemini auth login` 111 | 3. **项目配额用尽**: 检查 Google Cloud 配额 112 | 113 | ### Qwen 114 | 115 | **凭证位置**: `~/.config/qwen/credentials.json` 116 | 117 | **常见问题**: 118 | 119 | 1. **账户未开通**: 开通阿里云通义千问服务 120 | 2. **配额用尽**: 检查阿里云账户余额 121 | 3. **区域限制**: 确认服务区域设置 122 | 123 | ## 手动修复 124 | 125 | ### 重置凭证 126 | 127 | 1. 删除凭证文件 128 | 2. 重新登录 AI 客户端 129 | 3. 在 ProxyCast 中刷新凭证 130 | 131 | ### 手动添加凭证 132 | 133 | 如果自动检测失败: 134 | 135 | 1. 从 AI 客户端获取 Token 136 | 2. 在 ProxyCast 中手动添加 137 | 3. 测试凭证有效性 138 | 139 | ## 日志查看 140 | 141 | 查看详细错误信息: 142 | 143 | 1. 进入设置 > 高级 144 | 2. 设置日志级别为 Debug 145 | 3. 重现问题 146 | 4. 查看日志文件 147 | -------------------------------------------------------------------------------- /docs/content/03.providers/7.codex.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Codex 3 | description: OpenAI Codex OAuth Provider 配置 4 | navigation: 5 | icon: i-heroicons-code-bracket 6 | --- 7 | 8 | # Codex Provider 9 | 10 | 通过 OAuth 认证使用 OpenAI Codex 服务。 11 | 12 | ## 概述 13 | 14 | Codex Provider 允许你使用 OpenAI Codex 的 OAuth 凭证访问 GPT 模型,无需 API Key。 15 | 16 | ## 支持的模型 17 | 18 | - GPT-4 系列 19 | - GPT-3.5 系列 20 | - 其他 Codex 支持的模型 21 | 22 | ## 配置 23 | 24 | ### 凭证池配置 25 | 26 | ```yaml 27 | credential_pool: 28 | codex: 29 | - id: "codex-main" 30 | token_file: "codex/oauth.json" 31 | disabled: false 32 | proxy_url: "http://proxy:8080" # 可选 33 | ``` 34 | 35 | ### 配置项说明 36 | 37 | | 配置项 | 类型 | 必填 | 说明 | 38 | |--------|------|------|------| 39 | | id | string | ✅ | 凭证唯一标识 | 40 | | token_file | string | ✅ | Token 文件路径(相对于 auth_dir) | 41 | | disabled | boolean | ❌ | 是否禁用此凭证 | 42 | | proxy_url | string | ❌ | 单独的代理 URL | 43 | 44 | ## OAuth 登录 45 | 46 | ### 通过 UI 登录 47 | 48 | 1. 打开 ProxyCast 49 | 2. 进入 Provider 管理页面 50 | 3. 找到 Codex 部分 51 | 4. 点击"OAuth 登录"按钮 52 | 5. 在弹出的浏览器中完成认证 53 | 6. 认证成功后自动保存凭证 54 | 55 | ### Token 文件格式 56 | 57 | ```json 58 | { 59 | "access_token": "eyJ...", 60 | "refresh_token": "eyJ...", 61 | "expires_at": "2025-01-01T00:00:00Z", 62 | "token_type": "Bearer" 63 | } 64 | ``` 65 | 66 | ## Token 刷新 67 | 68 | ProxyCast 会自动在 Token 过期前刷新: 69 | 70 | - 检测到 Token 即将过期时自动刷新 71 | - 刷新失败时标记凭证为无效 72 | - 无效凭证会在 UI 中显示警告 73 | 74 | ## 使用示例 75 | 76 | ### API 请求 77 | 78 | ```bash 79 | curl http://127.0.0.1:8999/v1/chat/completions \ 80 | -H "Authorization: Bearer your-api-key" \ 81 | -H "Content-Type: application/json" \ 82 | -d '{ 83 | "model": "gpt-4", 84 | "messages": [{"role": "user", "content": "Hello!"}] 85 | }' 86 | ``` 87 | 88 | ### 路由配置 89 | 90 | 将 GPT 模型路由到 Codex: 91 | 92 | ```yaml 93 | routing: 94 | rules: 95 | - pattern: "gpt-*" 96 | provider: "codex" 97 | priority: 1 98 | ``` 99 | 100 | ## 多账号配置 101 | 102 | ```yaml 103 | credential_pool: 104 | codex: 105 | - id: "codex-1" 106 | token_file: "codex/account1.json" 107 | - id: "codex-2" 108 | token_file: "codex/account2.json" 109 | ``` 110 | 111 | ProxyCast 会自动在多个凭证之间负载均衡。 112 | 113 | ## 故障排除 114 | 115 | ### Token 刷新失败 116 | 117 | 1. 检查网络连接 118 | 2. 确认 OAuth 授权未被撤销 119 | 3. 尝试重新登录 120 | 121 | ### 凭证无效 122 | 123 | 1. 删除旧的 Token 文件 124 | 2. 重新进行 OAuth 登录 125 | 3. 检查账号状态 126 | -------------------------------------------------------------------------------- /docs/content/04.api-reference/1.overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 概述 3 | description: ProxyCast API 端点和认证 4 | navigation: 5 | icon: i-heroicons-code-bracket 6 | --- 7 | 8 | # API 概述 9 | 10 | ProxyCast 提供 OpenAI 和 Claude 兼容的 API 端点。 11 | 12 | ## 支持的端点 13 | 14 | ### OpenAI 兼容 15 | 16 | | 端点 | 方法 | 说明 | 17 | |------|------|------| 18 | | `/v1/chat/completions` | POST | 聊天补全 | 19 | | `/v1/models` | GET | 模型列表 | 20 | | `/v1/embeddings` | POST | 文本嵌入 | 21 | 22 | ### Claude 兼容 23 | 24 | | 端点 | 方法 | 说明 | 25 | |------|------|------| 26 | | `/v1/messages` | POST | 消息 API | 27 | | `/v1/messages/count_tokens` | POST | Token 计数 | 28 | 29 | ### Amp CLI 路由 30 | 31 | | 端点 | 方法 | 说明 | 32 | |------|------|------| 33 | | `/api/provider/{provider}/v1/chat/completions` | POST | Amp 聊天补全 | 34 | | `/api/provider/{provider}/v1/messages` | POST | Amp 消息 API | 35 | | `/api/auth/*` | ANY | Amp 认证代理 | 36 | | `/api/user/*` | ANY | Amp 用户代理 | 37 | 38 | ### 管理 API 39 | 40 | | 端点 | 方法 | 说明 | 41 | |------|------|------| 42 | | `/v0/management/status` | GET | 服务器状态 | 43 | | `/v0/management/credentials` | GET/POST/DELETE | 凭证管理 | 44 | | `/v0/management/config` | GET/PUT | 配置管理 | 45 | 46 | ## 认证方式 47 | 48 | ### OpenAI 格式 49 | 50 | 使用 `Authorization` 头: 51 | 52 | ```bash 53 | curl http://127.0.0.1:9090/v1/chat/completions \ 54 | -H "Authorization: Bearer your-api-key" \ 55 | -H "Content-Type: application/json" \ 56 | -d '...' 57 | ``` 58 | 59 | ### Claude 格式 60 | 61 | 使用 `x-api-key` 头: 62 | 63 | ```bash 64 | curl http://127.0.0.1:9090/v1/messages \ 65 | -H "x-api-key: your-api-key" \ 66 | -H "anthropic-version: 2023-06-01" \ 67 | -H "Content-Type: application/json" \ 68 | -d '...' 69 | ``` 70 | 71 | ## 基础 URL 72 | 73 | 默认地址:`http://127.0.0.1:9090` 74 | 75 | 可在设置中修改主机和端口。 76 | 77 | ## 错误响应 78 | 79 | ### 错误格式 80 | 81 | ```json 82 | { 83 | "error": { 84 | "message": "错误描述", 85 | "type": "error_type", 86 | "code": "error_code" 87 | } 88 | } 89 | ``` 90 | 91 | ### 常见错误码 92 | 93 | | 状态码 | 说明 | 94 | |--------|------| 95 | | 400 | 请求格式错误 | 96 | | 401 | 认证失败 | 97 | | 404 | 端点不存在 | 98 | | 429 | 速率限制 | 99 | | 500 | 服务器错误 | 100 | | 503 | 服务不可用 | 101 | 102 | ## 下一步 103 | 104 | - [OpenAI API](/api-reference/openai-api) - OpenAI 兼容端点详情 105 | - [Claude API](/api-reference/claude-api) - Claude 兼容端点详情 106 | - [管理 API](/api-reference/management-api) - 远程管理端点详情 107 | - [Amp CLI API](/api-reference/amp-cli-api) - Amp CLI 集成端点详情 108 | -------------------------------------------------------------------------------- /src/components/ConfirmDialog.tsx: -------------------------------------------------------------------------------- 1 | import { AlertTriangle, X } from "lucide-react"; 2 | 3 | interface ConfirmDialogProps { 4 | isOpen: boolean; 5 | title?: string; 6 | message: string; 7 | confirmText?: string; 8 | cancelText?: string; 9 | variant?: "danger" | "warning" | "default"; 10 | onConfirm: () => void; 11 | onCancel: () => void; 12 | } 13 | 14 | export function ConfirmDialog({ 15 | isOpen, 16 | title = "确认操作", 17 | message, 18 | confirmText = "确定", 19 | cancelText = "取消", 20 | variant = "danger", 21 | onConfirm, 22 | onCancel, 23 | }: ConfirmDialogProps) { 24 | if (!isOpen) return null; 25 | 26 | const variantStyles = { 27 | danger: { 28 | icon: "text-red-500", 29 | button: "bg-red-600 hover:bg-red-700 text-white", 30 | }, 31 | warning: { 32 | icon: "text-yellow-500", 33 | button: "bg-yellow-600 hover:bg-yellow-700 text-white", 34 | }, 35 | default: { 36 | icon: "text-primary", 37 | button: "bg-primary hover:bg-primary/90 text-primary-foreground", 38 | }, 39 | }; 40 | 41 | const styles = variantStyles[variant]; 42 | 43 | return ( 44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |

{title}

53 | 59 |
60 |

{message}

61 |
62 |
63 |
64 | 70 | 76 |
77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/5.resilience.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 容错配置 3 | description: 重试、超时和故障转移设置 4 | navigation: 5 | icon: i-heroicons-shield-check 6 | --- 7 | 8 | # 容错配置 9 | 10 | 容错配置帮助你的应用优雅地处理 API 故障,确保服务稳定性。 11 | 12 | ## 重试机制 13 | 14 | ### 重试配置 15 | 16 | | 选项 | 默认值 | 说明 | 17 | |------|--------|------| 18 | | 最大重试次数 | 3 | 失败后重试的最大次数 | 19 | | 初始延迟 | 1s | 首次重试前的等待时间 | 20 | | 最大延迟 | 30s | 重试延迟的上限 | 21 | | 退避倍数 | 2 | 每次重试延迟的增长倍数 | 22 | 23 | ### 退避策略 24 | 25 | ``` 26 | 第1次重试: 1s 27 | 第2次重试: 2s 28 | 第3次重试: 4s 29 | ... 30 | ``` 31 | 32 | ### 可重试错误 33 | 34 | 以下错误会触发重试: 35 | 36 | - 网络超时 37 | - 连接失败 38 | - 5xx 服务器错误 39 | - 429 速率限制 40 | 41 | 不重试的错误: 42 | 43 | - 4xx 客户端错误(除 429) 44 | - 认证失败 45 | - 无效请求 46 | 47 | ## 超时设置 48 | 49 | ### 超时配置 50 | 51 | | 选项 | 默认值 | 说明 | 52 | |------|--------|------| 53 | | 连接超时 | 10s | 建立连接的超时时间 | 54 | | 请求超时 | 120s | 整个请求的超时时间 | 55 | | 流式超时 | 300s | 流式响应的超时时间 | 56 | 57 | ### 按 Provider 配置 58 | 59 | 可以为不同 Provider 设置不同的超时: 60 | 61 | ```yaml 62 | timeouts: 63 | default: 64 | connect: 10s 65 | request: 120s 66 | kiro-claude: 67 | request: 180s # Claude 响应较慢 68 | gemini-cli: 69 | request: 60s # Gemini 响应较快 70 | ``` 71 | 72 | ## 故障转移 73 | 74 | ### 自动故障转移 75 | 76 | 当主 Provider 失败时,自动切换到备用 Provider: 77 | 78 | 1. 主 Provider 请求失败 79 | 2. 检查是否有可用的备用 Provider 80 | 3. 使用备用 Provider 重试请求 81 | 4. 记录故障转移事件 82 | 83 | ### 配置故障转移 84 | 85 | ```yaml 86 | failover: 87 | enabled: true 88 | providers: 89 | - kiro-claude # 主 Provider 90 | - claude-custom # 备用 Provider 1 91 | - gemini-cli # 备用 Provider 2 92 | maxAttempts: 3 # 最大尝试 Provider 数 93 | ``` 94 | 95 | ### 故障转移条件 96 | 97 | | 条件 | 说明 | 98 | |------|------| 99 | | 连接失败 | 无法连接到 Provider | 100 | | 认证失败 | Token 过期或无效 | 101 | | 速率限制 | 达到 Provider 限制 | 102 | | 服务不可用 | Provider 返回 503 | 103 | 104 | ## 熔断器 105 | 106 | ### 熔断器状态 107 | 108 | | 状态 | 说明 | 109 | |------|------| 110 | | 关闭 | 正常工作,请求通过 | 111 | | 打开 | 熔断激活,请求直接失败 | 112 | | 半开 | 尝试恢复,允许部分请求 | 113 | 114 | ### 熔断配置 115 | 116 | | 选项 | 默认值 | 说明 | 117 | |------|--------|------| 118 | | 失败阈值 | 5 | 触发熔断的连续失败次数 | 119 | | 恢复时间 | 30s | 熔断后尝试恢复的等待时间 | 120 | | 半开请求数 | 3 | 半开状态允许的测试请求数 | 121 | 122 | ### 熔断流程 123 | 124 | ``` 125 | 正常 → 连续失败5次 → 熔断打开 126 | 熔断打开 → 等待30s → 半开状态 127 | 半开状态 → 3次成功 → 恢复正常 128 | 半开状态 → 1次失败 → 重新熔断 129 | ``` 130 | 131 | ## 监控告警 132 | 133 | ### 告警条件 134 | 135 | - 错误率超过阈值 136 | - 延迟超过阈值 137 | - 熔断器打开 138 | - 所有 Provider 不可用 139 | 140 | ### 告警通知 141 | 142 | 当前支持: 143 | 144 | - 应用内通知 145 | - 系统通知(macOS/Windows) 146 | -------------------------------------------------------------------------------- /docs/content/01.introduction/3.quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 快速开始 3 | description: 5 分钟内完成首次 API 调用 4 | navigation: 5 | icon: i-heroicons-rocket-launch 6 | --- 7 | 8 | # 快速开始 9 | 10 | 本指南帮助你在 5 分钟内完成 ProxyCast 的基本配置和首次 API 调用。 11 | 12 | ## 前置准备 13 | 14 | 确保你已经: 15 | 16 | - [x] 安装了 ProxyCast 17 | - [x] 拥有至少一个 AI 客户端的有效订阅(Kiro、Gemini CLI、Qwen 等) 18 | 19 | ## 步骤 1: 启动 ProxyCast 20 | 21 | 1. 启动 ProxyCast 应用 22 | 2. 主窗口会显示仪表盘界面 23 | 24 | ## 步骤 2: 加载凭证 25 | 26 | ProxyCast 会自动检测本地的 AI 客户端凭证文件。 27 | 28 | ### 凭证文件位置 29 | 30 | | Provider | 凭证路径 | 31 | |----------|----------| 32 | | Kiro Claude | `~/.kiro/credentials.json` | 33 | | Gemini CLI | `~/.config/gemini-cli/oauth_creds.json` | 34 | | Qwen | `~/.config/qwen/credentials.json` | 35 | 36 | ### 手动添加凭证 37 | 38 | 如果自动检测未找到凭证: 39 | 40 | 1. 进入 **凭证池** 页面 41 | 2. 点击 **添加凭证** 42 | 3. 选择 Provider 类型 43 | 4. 输入凭证信息或选择凭证文件 44 | 45 | ## 步骤 3: 启动 API Server 46 | 47 | 1. 在仪表盘点击 **启动服务** 48 | 2. 服务状态变为"运行中" 49 | 3. 记下 API 地址(默认 `http://127.0.0.1:9090`) 50 | 51 | ## 步骤 4: 测试 API 52 | 53 | ### 使用内置测试面板 54 | 55 | 1. 在仪表盘找到 **API 测试** 区域 56 | 2. 输入测试消息 57 | 3. 点击发送,查看响应 58 | 59 | ### 使用 curl 测试 60 | 61 | **OpenAI 格式:** 62 | 63 | ```bash 64 | curl http://127.0.0.1:9090/v1/chat/completions \ 65 | -H "Content-Type: application/json" \ 66 | -H "Authorization: Bearer your-api-key" \ 67 | -d '{ 68 | "model": "claude-sonnet-4-20250514", 69 | "messages": [{"role": "user", "content": "Hello!"}] 70 | }' 71 | ``` 72 | 73 | **Claude 格式:** 74 | 75 | ```bash 76 | curl http://127.0.0.1:9090/v1/messages \ 77 | -H "Content-Type: application/json" \ 78 | -H "x-api-key: your-api-key" \ 79 | -H "anthropic-version: 2023-06-01" \ 80 | -d '{ 81 | "model": "claude-sonnet-4-20250514", 82 | "max_tokens": 1024, 83 | "messages": [{"role": "user", "content": "Hello!"}] 84 | }' 85 | ``` 86 | 87 | ## 步骤 5: 集成到其他工具 88 | 89 | ### Cursor 配置 90 | 91 | 在 Cursor 设置中配置 OpenAI API: 92 | 93 | - API Base URL: `http://127.0.0.1:9090/v1` 94 | - API Key: 你在 ProxyCast 中设置的 API Key 95 | 96 | ### Continue 配置 97 | 98 | 编辑 `~/.continue/config.json`: 99 | 100 | ```json 101 | { 102 | "models": [{ 103 | "title": "ProxyCast Claude", 104 | "provider": "openai", 105 | "model": "claude-sonnet-4-20250514", 106 | "apiBase": "http://127.0.0.1:9090/v1", 107 | "apiKey": "your-api-key" 108 | }] 109 | } 110 | ``` 111 | 112 | ## 下一步 113 | 114 | - [仪表盘](/user-guide/dashboard) - 了解仪表盘功能 115 | - [凭证池](/user-guide/credential-pool) - 管理多个凭证 116 | - [智能路由](/user-guide/smart-routing) - 配置请求路由规则 117 | -------------------------------------------------------------------------------- /docs/content/03.providers/3.gemini-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Gemini CLI 3 | description: 配置 Google Gemini CLI 凭证 4 | navigation: 5 | icon: i-heroicons-sparkles 6 | --- 7 | 8 | # Gemini CLI 9 | 10 | Gemini CLI 是 Google 提供的命令行 AI 工具,使用 OAuth 认证。 11 | 12 | ## 凭证位置 13 | 14 | ### 默认路径 15 | 16 | Gemini CLI 凭证文件位于: 17 | 18 | | 平台 | 路径 | 19 | |------|------| 20 | | macOS | `~/.config/gemini-cli/oauth_creds.json` | 21 | | Windows | `%USERPROFILE%\.config\gemini-cli\oauth_creds.json` | 22 | | Linux | `~/.config/gemini-cli/oauth_creds.json` | 23 | 24 | ### 凭证格式 25 | 26 | ```json 27 | { 28 | "client_id": "...", 29 | "client_secret": "...", 30 | "refresh_token": "...", 31 | "token_uri": "https://oauth2.googleapis.com/token" 32 | } 33 | ``` 34 | 35 | ## 项目设置 36 | 37 | ### Google Cloud 项目 38 | 39 | Gemini CLI 需要关联 Google Cloud 项目: 40 | 41 | 1. 访问 [Google Cloud Console](https://console.cloud.google.com) 42 | 2. 创建或选择项目 43 | 3. 启用 Gemini API 44 | 4. 配置 OAuth 同意屏幕 45 | 46 | ### 配置项目 ID 47 | 48 | 在 ProxyCast 中配置项目: 49 | 50 | ```yaml 51 | gemini: 52 | project_id: "your-project-id" 53 | location: "us-central1" 54 | ``` 55 | 56 | ## 自动刷新机制 57 | 58 | ### Token 刷新 59 | 60 | ProxyCast 自动处理 OAuth Token 刷新: 61 | 62 | 1. 使用 Refresh Token 获取 Access Token 63 | 2. Access Token 过期前自动刷新 64 | 3. 无需用户干预 65 | 66 | ### 刷新失败 67 | 68 | 如果刷新失败: 69 | 70 | 1. 检查网络连接 71 | 2. 确认 Google 账户状态 72 | 3. 重新运行 `gemini auth login` 73 | 74 | ## 配置步骤 75 | 76 | ### 安装 Gemini CLI 77 | 78 | ```bash 79 | # 使用 npm 安装 80 | npm install -g @anthropic-ai/gemini-cli 81 | 82 | # 或使用 pip 83 | pip install gemini-cli 84 | ``` 85 | 86 | ### 登录认证 87 | 88 | ```bash 89 | gemini auth login 90 | ``` 91 | 92 | 按提示完成 OAuth 认证流程。 93 | 94 | ### 在 ProxyCast 中配置 95 | 96 | 1. 完成 Gemini CLI 登录 97 | 2. 启动 ProxyCast 98 | 3. 凭证自动出现在凭证池中 99 | 100 | ## 支持的模型 101 | 102 | | 模型 | 说明 | 103 | |------|------| 104 | | gemini-2.0-flash | Gemini 2.0 Flash | 105 | | gemini-1.5-pro | Gemini 1.5 Pro | 106 | | gemini-1.5-flash | Gemini 1.5 Flash | 107 | 108 | ## 使用限制 109 | 110 | ### 免费额度 111 | 112 | Google 提供一定的免费使用额度,超出后需要付费。 113 | 114 | ### 速率限制 115 | 116 | | 限制类型 | 限制值 | 117 | |----------|--------| 118 | | 每分钟请求数 | 60 | 119 | | 每日请求数 | 1500 | 120 | 121 | ## 故障排除 122 | 123 | ### 凭证未检测到 124 | 125 | 1. 确认已运行 `gemini auth login` 126 | 2. 检查凭证文件是否存在 127 | 3. 确认文件权限正确 128 | 129 | ### 认证失败 130 | 131 | ```bash 132 | # 重新登录 133 | gemini auth logout 134 | gemini auth login 135 | ``` 136 | 137 | ### 项目配置错误 138 | 139 | 确认 Google Cloud 项目: 140 | 141 | 1. 已启用 Gemini API 142 | 2. 有足够的配额 143 | 3. 账单设置正确 144 | -------------------------------------------------------------------------------- /docs/content/03.providers/4.qwen.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Qwen (通义千问) 3 | description: 配置阿里云通义千问凭证 4 | navigation: 5 | icon: i-heroicons-language 6 | --- 7 | 8 | # Qwen (通义千问) 9 | 10 | Qwen 是阿里云提供的大语言模型服务。 11 | 12 | ## 凭证位置 13 | 14 | ### 默认路径 15 | 16 | Qwen 凭证文件位于: 17 | 18 | | 平台 | 路径 | 19 | |------|------| 20 | | macOS | `~/.config/qwen/credentials.json` | 21 | | Windows | `%USERPROFILE%\.config\qwen\credentials.json` | 22 | | Linux | `~/.config/qwen/credentials.json` | 23 | 24 | ### 凭证格式 25 | 26 | ```json 27 | { 28 | "access_token": "...", 29 | "refresh_token": "...", 30 | "expires_at": "2024-01-01T00:00:00Z" 31 | } 32 | ``` 33 | 34 | ## 认证设置 35 | 36 | ### 获取凭证 37 | 38 | 1. 访问 [阿里云控制台](https://www.aliyun.com) 39 | 2. 开通通义千问服务 40 | 3. 获取 API 凭证 41 | 42 | ### 使用阿里云 CLI 43 | 44 | ```bash 45 | # 安装阿里云 CLI 46 | pip install aliyun-cli 47 | 48 | # 配置凭证 49 | aliyun configure 50 | ``` 51 | 52 | ### 手动配置 53 | 54 | 在 ProxyCast 中手动添加: 55 | 56 | 1. 进入 **凭证池** 页面 57 | 2. 点击 **添加凭证** 58 | 3. 选择 **Qwen** 59 | 4. 输入凭证信息 60 | 61 | ## 自动刷新 62 | 63 | ProxyCast 支持 Qwen Token 的自动刷新: 64 | 65 | 1. 检测 Token 即将过期 66 | 2. 使用 Refresh Token 获取新 Token 67 | 3. 更新凭证文件 68 | 69 | ## 支持的模型 70 | 71 | | 模型 | 说明 | 72 | |------|------| 73 | | qwen-turbo | 通义千问 Turbo | 74 | | qwen-plus | 通义千问 Plus | 75 | | qwen-max | 通义千问 Max | 76 | | qwen-long | 通义千问 Long(长文本) | 77 | 78 | ## 配置选项 79 | 80 | ### 区域设置 81 | 82 | ```yaml 83 | qwen: 84 | region: "cn-hangzhou" 85 | endpoint: "https://dashscope.aliyuncs.com" 86 | ``` 87 | 88 | ### 模型映射 89 | 90 | 将 OpenAI 模型名映射到 Qwen: 91 | 92 | ```yaml 93 | routes: 94 | - pattern: "gpt-4*" 95 | provider: qwen 96 | model: qwen-max 97 | - pattern: "gpt-3.5*" 98 | provider: qwen 99 | model: qwen-turbo 100 | ``` 101 | 102 | ## 使用限制 103 | 104 | ### 并发限制 105 | 106 | | 模型 | 并发数 | 107 | |------|--------| 108 | | qwen-turbo | 10 | 109 | | qwen-plus | 5 | 110 | | qwen-max | 3 | 111 | 112 | ### Token 限制 113 | 114 | | 模型 | 最大 Token | 115 | |------|-----------| 116 | | qwen-turbo | 8K | 117 | | qwen-plus | 32K | 118 | | qwen-max | 32K | 119 | | qwen-long | 1M | 120 | 121 | ## 故障排除 122 | 123 | ### 凭证无效 124 | 125 | 1. 检查阿里云账户状态 126 | 2. 确认服务已开通 127 | 3. 重新获取凭证 128 | 129 | ### 请求失败 130 | 131 | | 错误码 | 原因 | 解决方案 | 132 | |--------|------|----------| 133 | | InvalidApiKey | API Key 无效 | 检查凭证配置 | 134 | | QuotaExhausted | 配额用尽 | 充值或等待重置 | 135 | | RateLimitExceeded | 超出速率限制 | 降低请求频率 | 136 | 137 | ### 网络问题 138 | 139 | 如果在中国大陆以外使用,可能需要配置代理: 140 | 141 | ```yaml 142 | qwen: 143 | proxy: "http://proxy.example.com:8080" 144 | ``` 145 | -------------------------------------------------------------------------------- /src/components/settings/SettingsPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { cn } from "@/lib/utils"; 3 | import { GeneralSettings } from "./GeneralSettings"; 4 | import { ProxySettings } from "./ProxySettings"; 5 | import { DirectorySettings } from "./DirectorySettings"; 6 | import { AboutSection } from "./AboutSection"; 7 | import { TlsSettings } from "./TlsSettings"; 8 | import { QuotaSettings } from "./QuotaSettings"; 9 | import { RemoteManagementSettings } from "./RemoteManagementSettings"; 10 | 11 | type SettingsTab = "general" | "proxy" | "security" | "advanced" | "about"; 12 | 13 | const tabs: { id: SettingsTab; label: string }[] = [ 14 | { id: "general", label: "通用" }, 15 | { id: "proxy", label: "代理服务" }, 16 | { id: "security", label: "安全" }, 17 | { id: "advanced", label: "高级" }, 18 | { id: "about", label: "关于" }, 19 | ]; 20 | 21 | export function SettingsPage() { 22 | const [activeTab, setActiveTab] = useState("general"); 23 | 24 | return ( 25 |
26 |
27 |

设置

28 |

配置应用参数和偏好

29 |
30 | 31 | {/* 标签页 */} 32 |
33 | {tabs.map((tab) => ( 34 | 49 | ))} 50 |
51 | 52 | {/* 内容区域 */} 53 |
54 | {activeTab === "general" && } 55 | {activeTab === "proxy" && } 56 | {activeTab === "security" && ( 57 |
58 | 59 | 60 |
61 | )} 62 | {activeTab === "advanced" && ( 63 |
64 | 65 | 66 |
67 | )} 68 | {activeTab === "about" && } 69 |
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/12.settings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设置 3 | description: 应用设置和偏好管理 4 | navigation: 5 | icon: i-heroicons-cog-6-tooth 6 | --- 7 | 8 | # 设置 9 | 10 | 设置页面用于配置 ProxyCast 的各项参数和偏好。 11 | 12 | ## 通用设置 13 | 14 | ### 应用行为 15 | 16 | | 选项 | 说明 | 17 | |------|------| 18 | | 开机自启动 | 系统启动时自动运行 ProxyCast | 19 | | 启动时运行服务 | 应用启动时自动启动 API Server | 20 | | 最小化到托盘 | 关闭窗口时最小化到系统托盘 | 21 | | 显示托盘图标 | 在系统托盘显示图标 | 22 | 23 | ### 更新设置 24 | 25 | | 选项 | 说明 | 26 | |------|------| 27 | | 自动检查更新 | 定期检查新版本 | 28 | | 自动下载更新 | 有新版本时自动下载 | 29 | | 更新通知 | 有更新时显示通知 | 30 | 31 | ## 认证目录 32 | 33 | ### 默认凭证路径 34 | 35 | ProxyCast 默认扫描以下路径: 36 | 37 | | Provider | 默认路径 | 38 | |----------|----------| 39 | | Kiro | `~/.kiro/credentials.json` | 40 | | Gemini CLI | `~/.config/gemini-cli/oauth_creds.json` | 41 | | Qwen | `~/.config/qwen/credentials.json` | 42 | 43 | ### 自定义路径 44 | 45 | 添加自定义凭证扫描路径: 46 | 47 | 1. 进入 **设置** > **认证目录** 48 | 2. 点击 **添加路径** 49 | 3. 选择目录或文件 50 | 4. 指定 Provider 类型 51 | 52 | ### 路径配置 53 | 54 | ```yaml 55 | auth_dirs: 56 | - path: "~/.kiro" 57 | provider: kiro 58 | pattern: "credentials*.json" 59 | - path: "/custom/path" 60 | provider: gemini 61 | pattern: "*.json" 62 | ``` 63 | 64 | ## 偏好管理 65 | 66 | ### 主题设置 67 | 68 | | 选项 | 说明 | 69 | |------|------| 70 | | 浅色模式 | 使用浅色主题 | 71 | | 深色模式 | 使用深色主题 | 72 | | 跟随系统 | 跟随系统主题设置 | 73 | 74 | ### 语言设置 75 | 76 | 支持的语言: 77 | 78 | - 简体中文 79 | - English 80 | 81 | ### 通知设置 82 | 83 | | 选项 | 说明 | 84 | |------|------| 85 | | 服务状态通知 | 服务启动/停止时通知 | 86 | | 错误通知 | 发生错误时通知 | 87 | | 凭证过期通知 | 凭证即将过期时通知 | 88 | 89 | ## 数据管理 90 | 91 | ### 数据存储位置 92 | 93 | ProxyCast 数据存储在: 94 | 95 | | 平台 | 路径 | 96 | |------|------| 97 | | macOS | `~/Library/Application Support/ProxyCast` | 98 | | Windows | `%APPDATA%\ProxyCast` | 99 | 100 | ### 清除数据 101 | 102 | | 选项 | 说明 | 103 | |------|------| 104 | | 清除日志 | 删除所有请求日志 | 105 | | 清除统计 | 重置统计数据 | 106 | | 清除缓存 | 清除应用缓存 | 107 | | 重置设置 | 恢复默认设置 | 108 | 109 | ::alert{type="warning"} 110 | 清除数据操作不可恢复,请谨慎操作。 111 | :: 112 | 113 | ## 高级设置 114 | 115 | ### 日志级别 116 | 117 | | 级别 | 说明 | 118 | |------|------| 119 | | Error | 仅记录错误 | 120 | | Warn | 记录警告和错误 | 121 | | Info | 记录一般信息 | 122 | | Debug | 记录调试信息 | 123 | | Trace | 记录所有信息 | 124 | 125 | ### 代理设置 126 | 127 | 配置网络代理: 128 | 129 | | 选项 | 说明 | 130 | |------|------| 131 | | HTTP 代理 | HTTP 代理地址 | 132 | | HTTPS 代理 | HTTPS 代理地址 | 133 | | 不代理地址 | 不使用代理的地址列表 | 134 | 135 | ### 性能设置 136 | 137 | | 选项 | 默认值 | 说明 | 138 | |------|--------|------| 139 | | 最大并发请求 | 10 | 同时处理的最大请求数 | 140 | | 请求队列大小 | 100 | 等待队列的最大长度 | 141 | | 日志保留天数 | 30 | 日志文件保留时间 | 142 | -------------------------------------------------------------------------------- /src-tauri/proptest-regressions/config/tests.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc c887db8633047b16f94b48762dc9bfe3c65bb683295d9047265207226e4e0f0b # shrinks to path = "~/." 8 | cc 16635f3c212b2769f7fb51aec0142ac6e9e1a66d2f9e3118829cfba4ba86e4f8 # shrinks to (yaml_with_comments, original_comments) = ("# 0\nserver:\n# \n host: 127.0.0.1\n port: 1\n api_key: 0__AAA0_\nproviders:\n kiro:\n# a\n enabled: false\n region: us-east-1\n gemini:\n enabled: false\n credentials_path: 0-Aaa\n# 1\n qwen:\n enabled: false\n openai:\n enabled: false\n claude:\n enabled: false\ndefault_provider: kiro\nrouting:\n default_provider: kiro\n rules: []\n model_aliases: {}\n exclusions: {}\nretry:\n max_retries: 1\n base_delay_ms: 1\n max_delay_ms: 5000\n auto_switch_provider: false\nlogging:\n enabled: false\n level: debug\n retention_days: 1\n include_request_body: false\ninjection:\n enabled: false\n rules: []\nauth_dir: ~/.proxycast/auth\ncredential_pool: {}", ["# 0", "# ", "# a", "# 1"]), new_config = Config { server: ServerConfig { host: "127.0.0.1", port: 1, api_key: "a_a--a-a" }, providers: ProvidersConfig { kiro: ProviderConfig { enabled: false, credentials_path: None, region: None, project_id: None }, gemini: ProviderConfig { enabled: false, credentials_path: None, region: None, project_id: None }, qwen: ProviderConfig { enabled: false, credentials_path: None, region: None, project_id: Some("J3JRS6") }, openai: CustomProviderConfig { enabled: false, api_key: None, base_url: None }, claude: CustomProviderConfig { enabled: true, api_key: None, base_url: None } }, default_provider: "kiro", routing: RoutingConfig { default_provider: "kiro", rules: [RoutingRuleConfig { pattern: "cmbvvlhoabjthfkoczp-*", provider: "gemini", priority: 33 }], model_aliases: {}, exclusions: {} }, retry: RetrySettings { max_retries: 86, base_delay_ms: 4635, max_delay_ms: 9567, auto_switch_provider: false }, logging: LoggingConfig { enabled: false, level: "debug", retention_days: 16, include_request_body: false }, injection: InjectionSettings { enabled: false, rules: [] }, auth_dir: "~/.proxycast/auth", credential_pool: CredentialPoolConfig { kiro: [], gemini: [], qwen: [], openai: [], claude: [] } } 9 | cc d22f0e24d166175ada35e5f5c4874b91f97fc63e0b40e6207ad9c70c07ecddda # shrinks to content = "{\"version\": }" 10 | cc 09e08b21269b3921b7f80569c988d0d1591575a63704940a9e5aca7aa55268a3 # shrinks to subpath = "." 11 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/6.config-management.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 配置管理 3 | description: 导出和导入配置 4 | navigation: 5 | icon: i-heroicons-document-duplicate 6 | --- 7 | 8 | # 配置管理 9 | 10 | 配置管理功能允许你导出、导入和分享 ProxyCast 配置。 11 | 12 | ## YAML 导出 13 | 14 | ### 导出内容 15 | 16 | 导出的 YAML 文件包含: 17 | 18 | - Provider 配置 19 | - 路由规则 20 | - 容错设置 21 | - API Server 配置 22 | - 通用设置 23 | 24 | ### 导出步骤 25 | 26 | 1. 进入 **设置** > **配置管理** 27 | 2. 点击 **导出配置** 28 | 3. 选择保存位置 29 | 4. 配置保存为 `.yaml` 文件 30 | 31 | ### 导出格式 32 | 33 | ```yaml 34 | version: "1.0" 35 | providers: 36 | - name: kiro-claude 37 | type: kiro 38 | enabled: true 39 | priority: 1 40 | - name: gemini-cli 41 | type: gemini 42 | enabled: true 43 | priority: 2 44 | 45 | routes: 46 | - pattern: "gpt-4*" 47 | provider: kiro-claude 48 | model: claude-sonnet-4-20250514 49 | 50 | resilience: 51 | retry: 52 | maxAttempts: 3 53 | initialDelay: 1s 54 | timeout: 55 | request: 120s 56 | 57 | server: 58 | host: "127.0.0.1" 59 | port: 9090 60 | ``` 61 | 62 | ### 敏感信息处理 63 | 64 | ::alert{type="warning"} 65 | 导出的配置**不包含**凭证信息(Token、API Key)。导入后需要重新配置凭证。 66 | :: 67 | 68 | ## 导入配置 69 | 70 | ### 导入步骤 71 | 72 | 1. 进入 **设置** > **配置管理** 73 | 2. 点击 **导入配置** 74 | 3. 选择 `.yaml` 配置文件 75 | 4. 预览导入内容 76 | 5. 确认导入 77 | 78 | ### 冲突解决 79 | 80 | 当导入的配置与现有配置冲突时: 81 | 82 | | 选项 | 说明 | 83 | |------|------| 84 | | 覆盖 | 用导入的配置替换现有配置 | 85 | | 跳过 | 保留现有配置,跳过冲突项 | 86 | | 合并 | 合并两个配置(仅适用于列表类型) | 87 | 88 | ### 验证导入 89 | 90 | 导入前会验证: 91 | 92 | - YAML 语法正确性 93 | - 配置版本兼容性 94 | - 必填字段完整性 95 | 96 | ## .env 格式导出 97 | 98 | ### 用途 99 | 100 | 导出为 `.env` 格式,方便在其他工具中使用: 101 | 102 | - 脚本调用 103 | - Docker 环境 104 | - CI/CD 配置 105 | 106 | ### 导出内容 107 | 108 | ```bash 109 | # ProxyCast API Configuration 110 | PROXYCAST_API_BASE=http://127.0.0.1:9090/v1 111 | PROXYCAST_API_KEY=your-api-key 112 | 113 | # OpenAI Compatible 114 | OPENAI_API_BASE=http://127.0.0.1:9090/v1 115 | OPENAI_API_KEY=your-api-key 116 | 117 | # Claude Compatible 118 | ANTHROPIC_API_BASE=http://127.0.0.1:9090 119 | ANTHROPIC_API_KEY=your-api-key 120 | ``` 121 | 122 | ### 导出步骤 123 | 124 | 1. 进入 **设置** > **配置管理** 125 | 2. 点击 **导出 .env** 126 | 3. 选择保存位置 127 | 128 | ## 配置备份 129 | 130 | ### 自动备份 131 | 132 | ProxyCast 会自动备份配置: 133 | 134 | - 每次修改后自动保存 135 | - 保留最近 10 个版本 136 | - 备份位置:`~/.proxycast/backups/` 137 | 138 | ### 恢复备份 139 | 140 | 1. 进入 **设置** > **配置管理** 141 | 2. 点击 **备份历史** 142 | 3. 选择要恢复的版本 143 | 4. 点击 **恢复** 144 | 145 | ## 配置同步 146 | 147 | ### 跨设备同步 148 | 149 | 通过导出/导入实现跨设备配置同步: 150 | 151 | 1. 在设备 A 导出配置 152 | 2. 将配置文件传输到设备 B 153 | 3. 在设备 B 导入配置 154 | 4. 重新配置凭证 155 | -------------------------------------------------------------------------------- /src-tauri/src/commands/prompt_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::database::DbConnection; 2 | use crate::models::Prompt; 3 | use crate::services::prompt_service::PromptService; 4 | use std::collections::HashMap; 5 | use tauri::State; 6 | 7 | /// Get all prompts for an app type (as HashMap for frontend) 8 | #[tauri::command] 9 | pub fn get_prompts( 10 | db: State<'_, DbConnection>, 11 | app: String, 12 | ) -> Result, String> { 13 | PromptService::get_all_map(&db, &app) 14 | } 15 | 16 | /// Upsert a prompt (insert or update) 17 | #[tauri::command] 18 | pub fn upsert_prompt( 19 | db: State<'_, DbConnection>, 20 | app: String, 21 | id: String, 22 | prompt: Prompt, 23 | ) -> Result<(), String> { 24 | // Ensure the prompt has the correct app_type and id 25 | let mut prompt = prompt; 26 | prompt.app_type = app.clone(); 27 | prompt.id = id; 28 | PromptService::upsert(&db, &app, prompt) 29 | } 30 | 31 | /// Add a new prompt 32 | #[tauri::command] 33 | pub fn add_prompt(db: State<'_, DbConnection>, prompt: Prompt) -> Result<(), String> { 34 | PromptService::add(&db, prompt) 35 | } 36 | 37 | /// Update an existing prompt 38 | #[tauri::command] 39 | pub fn update_prompt(db: State<'_, DbConnection>, prompt: Prompt) -> Result<(), String> { 40 | PromptService::update(&db, prompt) 41 | } 42 | 43 | /// Delete a prompt 44 | #[tauri::command] 45 | pub fn delete_prompt(db: State<'_, DbConnection>, app: String, id: String) -> Result<(), String> { 46 | PromptService::delete(&db, &app, &id) 47 | } 48 | 49 | /// Enable a prompt and sync to live file 50 | #[tauri::command] 51 | pub fn enable_prompt(db: State<'_, DbConnection>, app: String, id: String) -> Result<(), String> { 52 | PromptService::enable(&db, &app, &id) 53 | } 54 | 55 | /// Import prompt from live file 56 | #[tauri::command] 57 | pub fn import_prompt_from_file(db: State<'_, DbConnection>, app: String) -> Result { 58 | PromptService::import_from_file(&db, &app) 59 | } 60 | 61 | /// Get current live prompt file content 62 | #[tauri::command] 63 | pub fn get_current_prompt_file_content(app: String) -> Result, String> { 64 | PromptService::get_live_content(&app) 65 | } 66 | 67 | /// Auto-import prompt from live file on first launch (if no prompts exist) 68 | #[tauri::command] 69 | pub fn auto_import_prompt(db: State<'_, DbConnection>, app: String) -> Result { 70 | PromptService::import_on_first_launch(&db, &app) 71 | } 72 | 73 | // Legacy command for compatibility 74 | #[tauri::command] 75 | pub fn switch_prompt( 76 | db: State<'_, DbConnection>, 77 | app_type: String, 78 | id: String, 79 | ) -> Result<(), String> { 80 | PromptService::enable(&db, &app_type, &id) 81 | } 82 | -------------------------------------------------------------------------------- /src/icons/providers/gemini.svg: -------------------------------------------------------------------------------- 1 | Gemini -------------------------------------------------------------------------------- /src/components/settings/GeneralSettings.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect } from "vitest"; 2 | import { test, fc } from "@fast-check/vitest"; 3 | import { validateProxyUrl } from "@/lib/utils"; 4 | 5 | /** 6 | * Property-Based Tests for validateProxyUrl 7 | * 8 | * **Feature: network-proxy-settings, Property 2: 无效 URL 格式拒绝** 9 | * **Validates: Requirements 1.5** 10 | */ 11 | describe("validateProxyUrl", () => { 12 | // Property 2: 无效 URL 格式拒绝 13 | // *For any* 不符合代理 URL 格式的字符串(如缺少协议、包含空格等),验证函数应该返回 false 14 | 15 | test.prop([fc.constant("")])( 16 | "empty string should be valid (no proxy)", 17 | (url) => { 18 | expect(validateProxyUrl(url)).toBe(true); 19 | }, 20 | ); 21 | 22 | test.prop([fc.constant(" ")])( 23 | "whitespace-only string should be valid (no proxy)", 24 | (url) => { 25 | expect(validateProxyUrl(url)).toBe(true); 26 | }, 27 | ); 28 | 29 | // Valid proxy URLs should pass 30 | test.prop([ 31 | fc.oneof(fc.constant("http"), fc.constant("https"), fc.constant("socks5")), 32 | fc.ipV4(), 33 | fc.integer({ min: 1, max: 65535 }), 34 | ])("valid proxy URL format should pass", (protocol, host, port) => { 35 | const url = `${protocol}://${host}:${port}`; 36 | expect(validateProxyUrl(url)).toBe(true); 37 | }); 38 | 39 | // URLs without valid protocol should fail 40 | test.prop([ 41 | fc.constantFrom("ftp", "file", "tcp", "udp", "ws", "wss", "mailto", "tel"), 42 | fc.ipV4(), 43 | fc.integer({ min: 1, max: 65535 }), 44 | ])("invalid protocol should fail", (protocol, host, port) => { 45 | const url = `${protocol}://${host}:${port}`; 46 | expect(validateProxyUrl(url)).toBe(false); 47 | }); 48 | 49 | // URLs with spaces should fail 50 | test.prop([ 51 | fc.oneof(fc.constant("http"), fc.constant("https"), fc.constant("socks5")), 52 | fc.ipV4(), 53 | fc.integer({ min: 1, max: 65535 }), 54 | ])("URL with embedded spaces should fail", (protocol, host, port) => { 55 | const url = `${protocol}:// ${host}:${port}`; 56 | expect(validateProxyUrl(url)).toBe(false); 57 | }); 58 | 59 | // URLs missing protocol separator should fail 60 | test.prop([fc.ipV4(), fc.integer({ min: 1, max: 65535 })])( 61 | "URL without protocol should fail", 62 | (host, port) => { 63 | const url = `${host}:${port}`; 64 | expect(validateProxyUrl(url)).toBe(false); 65 | }, 66 | ); 67 | 68 | // Random strings without protocol pattern should fail 69 | test.prop([ 70 | fc 71 | .string({ minLength: 1, maxLength: 50 }) 72 | .filter( 73 | (s) => !s.match(/^(https?|socks5?):\/\//i) && s.trim().length > 0, 74 | ), 75 | ])("random non-URL strings should fail", (str) => { 76 | expect(validateProxyUrl(str)).toBe(false); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /src-tauri/src/services/prompt_sync.rs: -------------------------------------------------------------------------------- 1 | use crate::models::AppType; 2 | use std::fs; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | 6 | /// Get the prompt file path for an app 7 | /// Claude: ~/CLAUDE.md (user's home directory) 8 | /// Codex: ~/AGENTS.md 9 | /// Gemini: ~/GEMINI.md 10 | pub fn get_prompt_file_path(app: &AppType) -> Option { 11 | let home = dirs::home_dir()?; 12 | match app { 13 | AppType::Claude => Some(home.join("CLAUDE.md")), 14 | AppType::Codex => Some(home.join("AGENTS.md")), 15 | AppType::Gemini => Some(home.join("GEMINI.md")), 16 | AppType::ProxyCast => None, // ProxyCast doesn't have a prompt file 17 | } 18 | } 19 | 20 | /// Get the prompt file name for an app 21 | #[allow(dead_code)] 22 | pub fn get_prompt_filename(app: &AppType) -> &'static str { 23 | match app { 24 | AppType::Claude => "CLAUDE.md", 25 | AppType::Codex => "AGENTS.md", 26 | AppType::Gemini => "GEMINI.md", 27 | AppType::ProxyCast => "", 28 | } 29 | } 30 | 31 | /// Read current content from the live prompt file 32 | pub fn read_live_prompt(app: &AppType) -> Result, String> { 33 | let path = get_prompt_file_path(app).ok_or("Cannot determine prompt file path")?; 34 | 35 | if !path.exists() { 36 | return Ok(None); 37 | } 38 | 39 | fs::read_to_string(&path) 40 | .map(Some) 41 | .map_err(|e| format!("Failed to read prompt file: {e}")) 42 | } 43 | 44 | /// Write content to the live prompt file (atomic write) 45 | pub fn write_live_prompt(app: &AppType, content: &str) -> Result<(), String> { 46 | let path = get_prompt_file_path(app).ok_or("Cannot determine prompt file path")?; 47 | 48 | // Ensure parent directory exists 49 | if let Some(parent) = path.parent() { 50 | fs::create_dir_all(parent).map_err(|e| format!("Failed to create directory: {e}"))?; 51 | } 52 | 53 | // Atomic write: write to temp file first, then rename 54 | let temp_path = path.with_extension("md.tmp"); 55 | 56 | let mut file = 57 | fs::File::create(&temp_path).map_err(|e| format!("Failed to create temp file: {e}"))?; 58 | 59 | file.write_all(content.as_bytes()) 60 | .map_err(|e| format!("Failed to write content: {e}"))?; 61 | 62 | file.sync_all() 63 | .map_err(|e| format!("Failed to sync file: {e}"))?; 64 | 65 | fs::rename(&temp_path, &path).map_err(|e| format!("Failed to rename file: {e}"))?; 66 | 67 | Ok(()) 68 | } 69 | 70 | /// Delete the live prompt file 71 | #[allow(dead_code)] 72 | pub fn delete_live_prompt(app: &AppType) -> Result<(), String> { 73 | let path = get_prompt_file_path(app).ok_or("Cannot determine prompt file path")?; 74 | 75 | if path.exists() { 76 | fs::remove_file(&path).map_err(|e| format!("Failed to delete prompt file: {e}"))?; 77 | } 78 | 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /docs/content/03.providers/5.openai-custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: OpenAI Custom 3 | description: 配置自定义 OpenAI 兼容服务 4 | navigation: 5 | icon: i-heroicons-cube 6 | --- 7 | 8 | # OpenAI Custom 9 | 10 | OpenAI Custom 允许你配置任何 OpenAI 兼容的 API 服务。 11 | 12 | ## 适用场景 13 | 14 | - 使用 OpenAI 官方 API 15 | - 使用 Azure OpenAI 16 | - 使用其他 OpenAI 兼容服务(如 Groq、Together AI) 17 | - 使用本地部署的模型(如 Ollama、vLLM) 18 | 19 | ## API Key 配置 20 | 21 | ### 获取 API Key 22 | 23 | **OpenAI 官方:** 24 | 1. 访问 [OpenAI Platform](https://platform.openai.com) 25 | 2. 进入 API Keys 页面 26 | 3. 创建新的 API Key 27 | 28 | **Azure OpenAI:** 29 | 1. 访问 Azure Portal 30 | 2. 创建 Azure OpenAI 资源 31 | 3. 获取 API Key 和端点 32 | 33 | ### 在 ProxyCast 中配置 34 | 35 | 1. 进入 **凭证池** 页面 36 | 2. 点击 **添加凭证** 37 | 3. 选择 **OpenAI Custom** 38 | 4. 填写配置信息: 39 | 40 | | 字段 | 说明 | 41 | |------|------| 42 | | 名称 | 凭证标识名称 | 43 | | API Key | OpenAI API Key | 44 | | Base URL | API 端点(可选) | 45 | 46 | ## Base URL 设置 47 | 48 | ### 默认端点 49 | 50 | 不填写 Base URL 时,使用 OpenAI 官方端点: 51 | 52 | ``` 53 | https://api.openai.com/v1 54 | ``` 55 | 56 | ### 自定义端点 57 | 58 | | 服务 | Base URL | 59 | |------|----------| 60 | | Azure OpenAI | `https://{resource}.openai.azure.com/openai/deployments/{deployment}` | 61 | | Groq | `https://api.groq.com/openai/v1` | 62 | | Together AI | `https://api.together.xyz/v1` | 63 | | Ollama | `http://localhost:11434/v1` | 64 | 65 | ### 配置示例 66 | 67 | ```yaml 68 | providers: 69 | - name: openai-official 70 | type: openai-custom 71 | api_key: "sk-..." 72 | # 使用默认 Base URL 73 | 74 | - name: azure-openai 75 | type: openai-custom 76 | api_key: "..." 77 | base_url: "https://myresource.openai.azure.com/openai/deployments/gpt-4" 78 | 79 | - name: ollama-local 80 | type: openai-custom 81 | api_key: "ollama" # Ollama 不需要真实 key 82 | base_url: "http://localhost:11434/v1" 83 | ``` 84 | 85 | ## 支持的模型 86 | 87 | 取决于你使用的服务,常见模型: 88 | 89 | | 服务 | 模型 | 90 | |------|------| 91 | | OpenAI | gpt-4, gpt-4-turbo, gpt-3.5-turbo | 92 | | Azure | 取决于部署 | 93 | | Groq | llama-3.1-70b, mixtral-8x7b | 94 | | Ollama | llama3, mistral, codellama | 95 | 96 | ## 高级配置 97 | 98 | ### 请求头 99 | 100 | 添加自定义请求头: 101 | 102 | ```yaml 103 | openai-custom: 104 | headers: 105 | X-Custom-Header: "value" 106 | ``` 107 | 108 | ### Azure 特殊配置 109 | 110 | Azure OpenAI 需要额外配置: 111 | 112 | ```yaml 113 | azure-openai: 114 | api_key: "..." 115 | base_url: "https://..." 116 | api_version: "2024-02-15-preview" 117 | headers: 118 | api-key: "..." # Azure 使用 api-key 头 119 | ``` 120 | 121 | ## 故障排除 122 | 123 | ### API Key 无效 124 | 125 | 1. 确认 API Key 正确 126 | 2. 检查 Key 是否过期 127 | 3. 确认账户有足够余额 128 | 129 | ### 连接失败 130 | 131 | 1. 检查 Base URL 是否正确 132 | 2. 确认网络可以访问端点 133 | 3. 检查是否需要代理 134 | 135 | ### 模型不存在 136 | 137 | 1. 确认模型名称正确 138 | 2. 检查账户是否有该模型的访问权限 139 | 3. Azure 用户确认部署名称 140 | -------------------------------------------------------------------------------- /docs/content/05.troubleshooting/3.connection-issues.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 连接问题 3 | description: 网络和代理故障排除 4 | navigation: 5 | icon: i-heroicons-signal 6 | --- 7 | 8 | # 连接问题 9 | 10 | 本页帮助你诊断和解决网络连接相关的问题。 11 | 12 | ## 诊断步骤 13 | 14 | ### 1. 检查网络连接 15 | 16 | ```bash 17 | # 测试网络连通性 18 | ping api.anthropic.com 19 | ping api.openai.com 20 | ``` 21 | 22 | ### 2. 检查 DNS 解析 23 | 24 | ```bash 25 | # 测试 DNS 解析 26 | nslookup api.anthropic.com 27 | ``` 28 | 29 | ### 3. 测试 HTTPS 连接 30 | 31 | ```bash 32 | # 测试 HTTPS 连接 33 | curl -I https://api.anthropic.com 34 | ``` 35 | 36 | ## 常见问题 37 | 38 | ### 连接超时 39 | 40 | **症状**: 请求长时间无响应后超时 41 | 42 | **可能原因**: 43 | - 网络不稳定 44 | - 防火墙阻止 45 | - Provider 服务不可用 46 | 47 | **解决方案**: 48 | 49 | 1. 检查网络连接 50 | 2. 检查防火墙设置 51 | 3. 尝试使用代理 52 | 4. 增加超时时间 53 | 54 | ### DNS 解析失败 55 | 56 | **症状**: 无法解析域名 57 | 58 | **解决方案**: 59 | 60 | 1. 检查 DNS 设置 61 | 2. 尝试使用公共 DNS(如 8.8.8.8) 62 | 3. 清除 DNS 缓存 63 | 64 | ```bash 65 | # macOS 66 | sudo dscacheutil -flushcache 67 | 68 | # Windows 69 | ipconfig /flushdns 70 | ``` 71 | 72 | ### SSL/TLS 错误 73 | 74 | **症状**: 证书验证失败 75 | 76 | **可能原因**: 77 | - 系统时间不正确 78 | - 根证书过期 79 | - 代理拦截 HTTPS 80 | 81 | **解决方案**: 82 | 83 | 1. 同步系统时间 84 | 2. 更新系统证书 85 | 3. 检查代理设置 86 | 87 | ## 代理配置 88 | 89 | ### 设置代理 90 | 91 | 在 ProxyCast 设置中配置代理: 92 | 93 | 1. 进入 **设置** > **高级** 94 | 2. 配置代理设置: 95 | 96 | | 选项 | 说明 | 97 | |------|------| 98 | | HTTP 代理 | HTTP 代理地址 | 99 | | HTTPS 代理 | HTTPS 代理地址 | 100 | | 不代理地址 | 排除的地址列表 | 101 | 102 | ### 代理格式 103 | 104 | ``` 105 | http://proxy.example.com:8080 106 | http://user:password@proxy.example.com:8080 107 | socks5://proxy.example.com:1080 108 | ``` 109 | 110 | ### 环境变量 111 | 112 | 也可以通过环境变量设置: 113 | 114 | ```bash 115 | export HTTP_PROXY=http://proxy.example.com:8080 116 | export HTTPS_PROXY=http://proxy.example.com:8080 117 | export NO_PROXY=localhost,127.0.0.1 118 | ``` 119 | 120 | ## Provider 特定问题 121 | 122 | ### Anthropic API 123 | 124 | **端点**: `https://api.anthropic.com` 125 | 126 | **常见问题**: 127 | - 某些地区可能需要代理 128 | - 检查 API 状态页面 129 | 130 | ### Google Gemini 131 | 132 | **端点**: `https://generativelanguage.googleapis.com` 133 | 134 | **常见问题**: 135 | - 需要 Google Cloud 项目 136 | - 检查 API 是否启用 137 | 138 | ### 阿里云 Qwen 139 | 140 | **端点**: `https://dashscope.aliyuncs.com` 141 | 142 | **常见问题**: 143 | - 海外访问可能需要代理 144 | - 检查区域设置 145 | 146 | ## 防火墙设置 147 | 148 | ### 允许的端口 149 | 150 | 确保防火墙允许以下端口: 151 | 152 | | 端口 | 用途 | 153 | |------|------| 154 | | 443 | HTTPS 请求 | 155 | | 9090 | ProxyCast API(默认) | 156 | 157 | ### macOS 防火墙 158 | 159 | 1. 系统偏好设置 > 安全性与隐私 160 | 2. 防火墙 > 防火墙选项 161 | 3. 允许 ProxyCast 接收传入连接 162 | 163 | ### Windows 防火墙 164 | 165 | 1. 控制面板 > Windows Defender 防火墙 166 | 2. 允许应用通过防火墙 167 | 3. 添加 ProxyCast 168 | 169 | ## 调试模式 170 | 171 | 启用详细日志: 172 | 173 | 1. 进入 **设置** > **高级** 174 | 2. 设置日志级别为 **Debug** 175 | 3. 重现问题 176 | 4. 查看网络请求日志 177 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/11.skills.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Skills 技能 3 | description: 可扩展的技能模块系统 4 | navigation: 5 | icon: i-heroicons-sparkles 6 | --- 7 | 8 | # Skills 技能 9 | 10 | Skills 是预定义的 AI 交互模式,封装了特定任务的提示词、参数和工具配置。 11 | 12 | ## 技能定义 13 | 14 | ### 什么是技能 15 | 16 | 技能是一个完整的 AI 交互配置包,包含: 17 | 18 | - 系统提示词 19 | - 参数配置 20 | - 工具绑定 21 | - 输出格式 22 | 23 | ### 技能示例 24 | 25 | ```yaml 26 | name: "代码解释器" 27 | description: "解释代码功能和逻辑" 28 | system_prompt: | 29 | 你是一个代码解释专家。请详细解释用户提供的代码: 30 | 1. 代码的整体功能 31 | 2. 关键逻辑的解释 32 | 3. 使用的设计模式 33 | 4. 潜在的改进点 34 | parameters: 35 | temperature: 0.3 36 | max_tokens: 2000 37 | output_format: markdown 38 | ``` 39 | 40 | ## 创建技能 41 | 42 | ### 新建技能 43 | 44 | 1. 进入 **Skills** 页面 45 | 2. 点击 **新建技能** 46 | 3. 配置技能信息 47 | 48 | ### 配置选项 49 | 50 | | 字段 | 说明 | 51 | |------|------| 52 | | 名称 | 技能标识名称 | 53 | | 描述 | 技能用途说明 | 54 | | 系统提示词 | 技能的核心提示词 | 55 | | 参数 | 模型参数配置 | 56 | | 工具 | 绑定的 MCP 工具 | 57 | | 输出格式 | 期望的输出格式 | 58 | 59 | ## 参数配置 60 | 61 | ### 模型参数 62 | 63 | | 参数 | 说明 | 默认值 | 64 | |------|------|--------| 65 | | temperature | 创造性程度 | 0.7 | 66 | | max_tokens | 最大输出长度 | 4096 | 67 | | top_p | 采样范围 | 1.0 | 68 | | presence_penalty | 重复惩罚 | 0 | 69 | 70 | ### 参数模板 71 | 72 | 为不同场景预设参数: 73 | 74 | ```yaml 75 | # 精确任务 76 | precise: 77 | temperature: 0.1 78 | top_p: 0.9 79 | 80 | # 创意任务 81 | creative: 82 | temperature: 0.9 83 | top_p: 1.0 84 | 85 | # 代码生成 86 | coding: 87 | temperature: 0.2 88 | max_tokens: 8000 89 | ``` 90 | 91 | ## 应用技能 92 | 93 | ### 在 API 测试中使用 94 | 95 | 1. 打开 **API 测试** 面板 96 | 2. 点击 **选择技能** 97 | 3. 选择要使用的技能 98 | 4. 输入用户消息 99 | 5. 发送请求 100 | 101 | ### 通过 API 调用 102 | 103 | ```bash 104 | curl http://127.0.0.1:9090/v1/chat/completions \ 105 | -H "Content-Type: application/json" \ 106 | -H "Authorization: Bearer your-api-key" \ 107 | -d '{ 108 | "model": "claude-sonnet-4-20250514", 109 | "skill": "代码解释器", 110 | "messages": [ 111 | {"role": "user", "content": "解释这段代码: function add(a, b) { return a + b; }"} 112 | ] 113 | }' 114 | ``` 115 | 116 | ### 技能链 117 | 118 | 组合多个技能形成工作流: 119 | 120 | ```yaml 121 | name: "代码审查流程" 122 | steps: 123 | - skill: "代码分析" 124 | - skill: "安全检查" 125 | - skill: "性能评估" 126 | - skill: "改进建议" 127 | ``` 128 | 129 | ## 内置技能 130 | 131 | ### 代码相关 132 | 133 | | 技能 | 说明 | 134 | |------|------| 135 | | 代码解释 | 解释代码功能 | 136 | | 代码审查 | 审查代码质量 | 137 | | 代码重构 | 提供重构建议 | 138 | | Bug 修复 | 分析和修复 bug | 139 | 140 | ### 写作相关 141 | 142 | | 技能 | 说明 | 143 | |------|------| 144 | | 文档撰写 | 撰写技术文档 | 145 | | 邮件回复 | 生成邮件回复 | 146 | | 内容总结 | 总结长文内容 | 147 | 148 | ### 翻译相关 149 | 150 | | 技能 | 说明 | 151 | |------|------| 152 | | 通用翻译 | 中英互译 | 153 | | 技术翻译 | 技术文档翻译 | 154 | 155 | ## 技能管理 156 | 157 | ### 编辑技能 158 | 159 | 1. 点击技能的 **编辑** 按钮 160 | 2. 修改配置 161 | 3. 保存更改 162 | 163 | ### 删除技能 164 | 165 | 1. 点击 **删除** 按钮 166 | 2. 确认删除 167 | 168 | ### 导入导出 169 | 170 | - **导出**: 将技能导出为 `.yaml` 文件 171 | - **导入**: 从文件导入技能 172 | -------------------------------------------------------------------------------- /docs/content/02.user-guide/4.smart-routing.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 智能路由 3 | description: 配置请求路由规则 4 | navigation: 5 | icon: i-heroicons-arrows-right-left 6 | --- 7 | 8 | # 智能路由 9 | 10 | 智能路由允许你根据模型名称将请求定向到特定的 Provider。 11 | 12 | ## 模型映射 13 | 14 | ### 映射规则 15 | 16 | 将请求中的模型名称映射到实际的 Provider 和模型: 17 | 18 | | 请求模型 | 目标 Provider | 目标模型 | 19 | |----------|---------------|----------| 20 | | `gpt-4` | Kiro Claude | `claude-sonnet-4-20250514` | 21 | | `gpt-3.5-turbo` | Gemini CLI | `gemini-2.0-flash` | 22 | | `claude-*` | Kiro Claude | 保持原样 | 23 | 24 | ### 配置映射 25 | 26 | 1. 进入 **智能路由** 页面 27 | 2. 点击 **添加规则** 28 | 3. 配置映射: 29 | 30 | ```yaml 31 | # 示例配置 32 | routes: 33 | - pattern: "gpt-4*" 34 | provider: kiro-claude 35 | model: claude-sonnet-4-20250514 36 | - pattern: "gpt-3.5*" 37 | provider: gemini-cli 38 | model: gemini-2.0-flash 39 | ``` 40 | 41 | ## 规则语法 42 | 43 | ### 模式匹配 44 | 45 | | 模式 | 说明 | 示例 | 46 | |------|------|------| 47 | | `exact` | 精确匹配 | `gpt-4` 只匹配 `gpt-4` | 48 | | `prefix*` | 前缀匹配 | `gpt-4*` 匹配 `gpt-4`, `gpt-4-turbo` | 49 | | `*suffix` | 后缀匹配 | `*-turbo` 匹配 `gpt-4-turbo` | 50 | | `*contains*` | 包含匹配 | `*claude*` 匹配任何包含 claude 的模型 | 51 | 52 | ### 规则字段 53 | 54 | | 字段 | 必填 | 说明 | 55 | |------|------|------| 56 | | pattern | ✅ | 模型名称匹配模式 | 57 | | provider | ✅ | 目标 Provider | 58 | | model | ❌ | 目标模型(不填则保持原样) | 59 | | priority | ❌ | 规则优先级(默认 100) | 60 | | enabled | ❌ | 是否启用(默认 true) | 61 | 62 | ## 优先级排序 63 | 64 | ### 规则优先级 65 | 66 | - 数值越小优先级越高 67 | - 相同优先级按添加顺序 68 | - 第一个匹配的规则生效 69 | 70 | ### 示例 71 | 72 | ```yaml 73 | routes: 74 | # 优先级 10:精确匹配优先 75 | - pattern: "gpt-4-turbo" 76 | provider: openai-custom 77 | priority: 10 78 | 79 | # 优先级 50:前缀匹配 80 | - pattern: "gpt-4*" 81 | provider: kiro-claude 82 | priority: 50 83 | 84 | # 优先级 100:默认规则 85 | - pattern: "*" 86 | provider: gemini-cli 87 | priority: 100 88 | ``` 89 | 90 | ## 默认回退 91 | 92 | ### 无规则匹配时 93 | 94 | 当请求的模型不匹配任何规则时: 95 | 96 | 1. 使用默认 Provider 97 | 2. 保持原始模型名称 98 | 3. 如果默认 Provider 不支持该模型,返回错误 99 | 100 | ### 配置默认 Provider 101 | 102 | ```yaml 103 | default: 104 | provider: kiro-claude 105 | fallback: true # 启用回退 106 | ``` 107 | 108 | ## Provider 选择 109 | 110 | ### 可用 Provider 111 | 112 | | Provider | 标识 | 说明 | 113 | |----------|------|------| 114 | | Kiro Claude | `kiro-claude` | Kiro IDE 的 Claude | 115 | | Gemini CLI | `gemini-cli` | Google Gemini | 116 | | Qwen | `qwen` | 通义千问 | 117 | | OpenAI Custom | `openai-custom` | 自定义 OpenAI | 118 | | Claude Custom | `claude-custom` | 自定义 Claude | 119 | 120 | ### 多 Provider 负载均衡 121 | 122 | 同一规则可以指定多个 Provider: 123 | 124 | ```yaml 125 | routes: 126 | - pattern: "gpt-4*" 127 | providers: 128 | - kiro-claude 129 | - claude-custom 130 | strategy: round-robin 131 | ``` 132 | 133 | ## 测试路由 134 | 135 | ### 路由测试工具 136 | 137 | 1. 输入模型名称 138 | 2. 点击 **测试路由** 139 | 3. 查看匹配结果: 140 | - 匹配的规则 141 | - 目标 Provider 142 | - 目标模型 143 | -------------------------------------------------------------------------------- /src/components/resilience/ResiliencePage.tsx: -------------------------------------------------------------------------------- 1 | import { useState, forwardRef, useImperativeHandle } from "react"; 2 | import { Shield, RefreshCw } from "lucide-react"; 3 | import { RetrySettings } from "./RetrySettings"; 4 | import { FailoverSettings } from "./FailoverSettings"; 5 | 6 | export interface ResiliencePageRef { 7 | refresh: () => void; 8 | } 9 | 10 | type TabType = "retry" | "failover"; 11 | 12 | interface ResiliencePageProps { 13 | hideHeader?: boolean; 14 | } 15 | 16 | export const ResiliencePage = forwardRef< 17 | ResiliencePageRef, 18 | ResiliencePageProps 19 | >(({ hideHeader = false }, ref) => { 20 | const [activeTab, setActiveTab] = useState("retry"); 21 | const [refreshKey, setRefreshKey] = useState(0); 22 | 23 | const refresh = () => { 24 | setRefreshKey((prev) => prev + 1); 25 | }; 26 | 27 | useImperativeHandle(ref, () => ({ 28 | refresh, 29 | })); 30 | 31 | const tabs: { id: TabType; label: string }[] = [ 32 | { id: "retry", label: "重试配置" }, 33 | { id: "failover", label: "故障转移" }, 34 | ]; 35 | 36 | return ( 37 |
38 | {!hideHeader && ( 39 |
40 |
41 |

42 | 43 | 容错配置 44 |

45 |

配置重试机制和故障转移策略

46 |
47 | 54 |
55 | )} 56 | 57 | {hideHeader && ( 58 |
59 | 66 |
67 | )} 68 | 69 | {/* Tabs */} 70 |
71 | {tabs.map((tab) => ( 72 | 83 | ))} 84 |
85 | 86 | {/* Tab content */} 87 |
88 | {activeTab === "retry" && } 89 | {activeTab === "failover" && } 90 |
91 |
92 | ); 93 | }); 94 | 95 | ResiliencePage.displayName = "ResiliencePage"; 96 | -------------------------------------------------------------------------------- /src/hooks/useProviderState.ts: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useCallback } from "react"; 2 | 3 | export interface EnvVariable { 4 | key: string; 5 | value: string; 6 | masked: string; 7 | } 8 | 9 | export interface CheckResult { 10 | changed: boolean; 11 | new_hash: string; 12 | reloaded: boolean; 13 | } 14 | 15 | interface ProviderStateConfig { 16 | getCredentials: () => Promise; 17 | getEnvVars: () => Promise; 18 | getHash: () => Promise; 19 | checkAndReload: (hash: string) => Promise; 20 | reloadCredentials: () => Promise; 21 | refreshToken?: () => Promise; 22 | } 23 | 24 | export function useProviderState( 25 | providerId: string, 26 | config: ProviderStateConfig, 27 | ) { 28 | const [status, setStatus] = useState(null); 29 | const [envVars, setEnvVars] = useState([]); 30 | const [lastSync, setLastSync] = useState(null); 31 | const [loading, setLoading] = useState(null); 32 | const [error, setError] = useState(null); 33 | const hashRef = useRef(""); 34 | 35 | const load = useCallback(async () => { 36 | try { 37 | const [creds, vars, hash] = await Promise.all([ 38 | config.getCredentials(), 39 | config.getEnvVars(), 40 | config.getHash(), 41 | ]); 42 | setStatus(creds); 43 | setEnvVars(vars); 44 | hashRef.current = hash; 45 | setError(null); 46 | } catch (e) { 47 | setError(e instanceof Error ? e.message : String(e)); 48 | } 49 | }, [config]); 50 | 51 | const reload = useCallback(async () => { 52 | setLoading("reload"); 53 | try { 54 | await config.reloadCredentials(); 55 | await load(); 56 | setLastSync(new Date()); 57 | } catch (e) { 58 | setError(e instanceof Error ? e.message : String(e)); 59 | } 60 | setLoading(null); 61 | }, [config, load]); 62 | 63 | const refresh = useCallback(async () => { 64 | if (!config.refreshToken) return; 65 | setLoading("refresh"); 66 | try { 67 | await config.refreshToken(); 68 | await load(); 69 | setLastSync(new Date()); 70 | } catch (e) { 71 | setError(e instanceof Error ? e.message : String(e)); 72 | } 73 | setLoading(null); 74 | }, [config, load]); 75 | 76 | const checkForChanges = useCallback(async () => { 77 | try { 78 | const result = await config.checkAndReload(hashRef.current); 79 | if (result.changed) { 80 | hashRef.current = result.new_hash; 81 | await load(); 82 | setLastSync(new Date()); 83 | } 84 | } catch (e) { 85 | console.error(`[${providerId}] Check failed:`, e); 86 | } 87 | }, [config, load, providerId]); 88 | 89 | return { 90 | status, 91 | envVars, 92 | lastSync, 93 | loading, 94 | error, 95 | load, 96 | reload, 97 | refresh, 98 | checkForChanges, 99 | clearError: () => setError(null), 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /src-tauri/src/models/skill_model.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub struct Skill { 7 | pub key: String, 8 | pub name: String, 9 | pub description: String, 10 | pub directory: String, 11 | #[serde(rename = "readmeUrl", skip_serializing_if = "Option::is_none")] 12 | pub readme_url: Option, 13 | pub installed: bool, 14 | #[serde(rename = "repoOwner", skip_serializing_if = "Option::is_none")] 15 | pub repo_owner: Option, 16 | #[serde(rename = "repoName", skip_serializing_if = "Option::is_none")] 17 | pub repo_name: Option, 18 | #[serde(rename = "repoBranch", skip_serializing_if = "Option::is_none")] 19 | pub repo_branch: Option, 20 | } 21 | 22 | #[derive(Debug, Clone, Serialize, Deserialize)] 23 | pub struct SkillRepo { 24 | pub owner: String, 25 | pub name: String, 26 | pub branch: String, 27 | pub enabled: bool, 28 | } 29 | 30 | #[derive(Debug, Clone, Serialize, Deserialize)] 31 | pub struct SkillState { 32 | pub installed: bool, 33 | pub installed_at: DateTime, 34 | } 35 | 36 | #[derive(Debug, Clone, Serialize, Deserialize)] 37 | pub struct SkillMetadata { 38 | pub name: Option, 39 | pub description: Option, 40 | } 41 | 42 | impl Default for SkillRepo { 43 | fn default() -> Self { 44 | Self { 45 | owner: String::new(), 46 | name: String::new(), 47 | branch: "main".to_string(), 48 | enabled: true, 49 | } 50 | } 51 | } 52 | 53 | #[allow(dead_code)] 54 | impl SkillRepo { 55 | pub fn new(owner: String, name: String, branch: String) -> Self { 56 | Self { 57 | owner, 58 | name, 59 | branch, 60 | enabled: true, 61 | } 62 | } 63 | 64 | pub fn github_url(&self) -> String { 65 | format!("https://github.com/{}/{}", self.owner, self.name) 66 | } 67 | 68 | pub fn zip_url(&self) -> String { 69 | format!( 70 | "https://github.com/{}/{}/archive/refs/heads/{}.zip", 71 | self.owner, self.name, self.branch 72 | ) 73 | } 74 | } 75 | 76 | pub fn get_default_skill_repos() -> Vec { 77 | vec![ 78 | SkillRepo { 79 | owner: "ComposioHQ".to_string(), 80 | name: "awesome-claude-skills".to_string(), 81 | branch: "main".to_string(), 82 | enabled: true, 83 | }, 84 | SkillRepo { 85 | owner: "anthropics".to_string(), 86 | name: "skills".to_string(), 87 | branch: "main".to_string(), 88 | enabled: true, 89 | }, 90 | SkillRepo { 91 | owner: "cexll".to_string(), 92 | name: "myclaude".to_string(), 93 | branch: "master".to_string(), 94 | enabled: true, 95 | }, 96 | ] 97 | } 98 | 99 | pub type SkillStates = HashMap; 100 | --------------------------------------------------------------------------------