mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 19:45:34 +08:00
feat(claude-desktop): add 3P provider switching with proxy gateway
Adds a new ClaudeDesktop AppType that writes Claude Desktop's third-party
inference profile under configLibrary/, sharing _meta.json with other
launchers (Ollama-compatible) so cc-switch can coexist with them.
Two switch modes:
- direct: provider already exposes claude-* / anthropic/claude-* model
ids on Anthropic Messages, Claude Desktop connects to it directly.
- proxy: cc-switch's local proxy acts as the inference gateway,
presenting only claude-* route names to Claude Desktop and mapping
them to real upstream models. Required after Anthropic restricted
Claude Desktop to claude-family ids.
Backend:
- New module claude_desktop_config with snapshot/rollback, official seed
bypass, /claude-desktop/v1/{models,messages} routes, and a single
source of truth for default proxy routes.
- Gateway token persisted in SQLite, validated on every proxied request.
- get_claude_desktop_status surfaces drift signals (stale models,
missing routes, proxy stopped, base URL mismatch, missing token).
Frontend:
- Slim ClaudeDesktopProviderForm independent from ProviderForm,
controlled by a top-level appId guard.
- ProviderList banner consumes the status query (5s polling) and
renders actionable diagnostics.
- ClaudeDesktopRouteToggle in the header to start/stop the local
gateway without touching takeover state.
- Three-locale i18n synchronised.
This commit is contained in:
+37
-24
@@ -65,6 +65,7 @@ import { SettingsPage } from "@/components/settings/SettingsPage";
|
||||
import { UpdateBadge } from "@/components/UpdateBadge";
|
||||
import { EnvWarningBanner } from "@/components/env/EnvWarningBanner";
|
||||
import { ProxyToggle } from "@/components/proxy/ProxyToggle";
|
||||
import { ClaudeDesktopRouteToggle } from "@/components/proxy/ClaudeDesktopRouteToggle";
|
||||
import { FailoverToggle } from "@/components/proxy/FailoverToggle";
|
||||
import UsageScriptModal from "@/components/UsageScriptModal";
|
||||
import UnifiedMcpPanel from "@/components/mcp/UnifiedMcpPanel";
|
||||
@@ -117,6 +118,7 @@ const HEADER_HEIGHT = 64; // px
|
||||
const STORAGE_KEY = "cc-switch-last-app";
|
||||
const VALID_APPS: AppId[] = [
|
||||
"claude",
|
||||
"claude-desktop",
|
||||
"codex",
|
||||
"gemini",
|
||||
"opencode",
|
||||
@@ -179,6 +181,7 @@ function App() {
|
||||
const contentTopOffset = dragBarHeight + HEADER_HEIGHT;
|
||||
const visibleApps: VisibleApps = settingsData?.visibleApps ?? {
|
||||
claude: true,
|
||||
"claude-desktop": true,
|
||||
codex: true,
|
||||
gemini: true,
|
||||
opencode: true,
|
||||
@@ -188,6 +191,7 @@ function App() {
|
||||
|
||||
const getFirstVisibleApp = (): AppId => {
|
||||
if (visibleApps.claude) return "claude";
|
||||
if (visibleApps["claude-desktop"]) return "claude-desktop";
|
||||
if (visibleApps.codex) return "codex";
|
||||
if (visibleApps.gemini) return "gemini";
|
||||
if (visibleApps.opencode) return "opencode";
|
||||
@@ -269,7 +273,7 @@ function App() {
|
||||
currentView === "openclawAgents");
|
||||
const { data: openclawHealthWarnings = [] } =
|
||||
useOpenClawHealth(isOpenClawView);
|
||||
const hasSkillsSupport = true;
|
||||
const hasSkillsSupport = activeApp !== "claude-desktop";
|
||||
const hasSessionSupport =
|
||||
activeApp === "claude" ||
|
||||
activeApp === "codex" ||
|
||||
@@ -1241,12 +1245,17 @@ function App() {
|
||||
className="flex shrink-0 items-center gap-1.5"
|
||||
style={{ WebkitAppRegion: "no-drag" } as any}
|
||||
>
|
||||
{settingsData?.enableLocalProxy && (
|
||||
<ProxyToggle activeApp={activeApp} />
|
||||
)}
|
||||
{settingsData?.enableFailoverToggle && (
|
||||
<FailoverToggle activeApp={activeApp} />
|
||||
{activeApp === "claude-desktop" ? (
|
||||
<ClaudeDesktopRouteToggle />
|
||||
) : (
|
||||
settingsData?.enableLocalProxy && (
|
||||
<ProxyToggle activeApp={activeApp} />
|
||||
)
|
||||
)}
|
||||
{activeApp !== "claude-desktop" &&
|
||||
settingsData?.enableFailoverToggle && (
|
||||
<FailoverToggle activeApp={activeApp} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
@@ -1487,15 +1496,17 @@ function App() {
|
||||
>
|
||||
<Wrench className="flex-shrink-0 w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("prompts")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5 w-8 px-2"
|
||||
title={t("prompts.manage")}
|
||||
>
|
||||
<Book className="w-4 h-4" />
|
||||
</Button>
|
||||
{activeApp !== "claude-desktop" && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("prompts")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5 w-8 px-2"
|
||||
title={t("prompts.manage")}
|
||||
>
|
||||
<Book className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -1511,15 +1522,17 @@ function App() {
|
||||
>
|
||||
<History className="flex-shrink-0 w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("mcp")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5 w-8 px-2"
|
||||
title={t("mcp.title")}
|
||||
>
|
||||
<McpIcon size={16} />
|
||||
</Button>
|
||||
{activeApp !== "claude-desktop" && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setCurrentView("mcp")}
|
||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5 w-8 px-2"
|
||||
title={t("mcp.title")}
|
||||
>
|
||||
<McpIcon size={16} />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user