"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { HugeiconsIcon } from "@hugeicons/react"; import { PlusSignIcon, Loading02Icon, Delete02Icon, PencilEdit01Icon, Tick01Icon, ServerStack01Icon, Cancel01Icon, } from "@hugeicons/core-free-icons"; import { ProviderForm } from "./ProviderForm"; import type { ProviderFormData } from "./ProviderForm"; import type { ApiProvider } from "@/types"; const QUICK_PRESETS = [ { name: "Anthropic", provider_type: "anthropic", base_url: "https://api.anthropic.com" }, { name: "OpenRouter", provider_type: "openrouter", base_url: "https://openrouter.ai/api" }, { name: "GLM (CN)", provider_type: "custom", base_url: "https://open.bigmodel.cn/api/anthropic", extra_env: '{"API_TIMEOUT_MS":"3000000","ANTHROPIC_API_KEY":""}' }, { name: "GLM (Global)", provider_type: "custom", base_url: "https://api.z.ai/api/anthropic", extra_env: '{"API_TIMEOUT_MS":"3000000","ANTHROPIC_API_KEY":""}' }, { name: "Kimi Coding Plan", provider_type: "custom", base_url: "https://api.kimi.com/coding/", extra_env: '{"ANTHROPIC_AUTH_TOKEN":""}' }, { name: "Moonshot", provider_type: "custom", base_url: "https://api.moonshot.cn/anthropic", extra_env: '{"ANTHROPIC_API_KEY":""}' }, { name: "MiniMax (CN)", provider_type: "custom", base_url: "https://api.minimaxi.com/anthropic", extra_env: '{"API_TIMEOUT_MS":"3000000","CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":"1","ANTHROPIC_API_KEY":""}' }, { name: "MiniMax (Global)", provider_type: "custom", base_url: "https://api.minimax.io/anthropic", extra_env: '{"API_TIMEOUT_MS":"3000000","CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC":"1","ANTHROPIC_API_KEY":""}' }, { name: "AWS Bedrock", provider_type: "bedrock", base_url: "" }, { name: "Google Vertex", provider_type: "vertex", base_url: "" }, { name: "LiteLLM", provider_type: "custom", base_url: "http://localhost:4000" }, ]; export function ProviderManager() { const [providers, setProviders] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [envDetected, setEnvDetected] = useState>({}); // Form dialog state const [formOpen, setFormOpen] = useState(false); const [formMode, setFormMode] = useState<"create" | "edit">("create"); const [editingProvider, setEditingProvider] = useState(null); const [initialPreset, setInitialPreset] = useState<{ name: string; provider_type: string; base_url: string } | null>(null); // Delete confirmation state const [deleteTarget, setDeleteTarget] = useState(null); const [deleting, setDeleting] = useState(false); // Activating state const [activatingId, setActivatingId] = useState(null); const fetchProviders = useCallback(async () => { try { setError(null); const res = await fetch("/api/providers"); if (!res.ok) { throw new Error("Failed to load providers"); } const data = await res.json(); setProviders(data.providers || []); setEnvDetected(data.env_detected || {}); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load providers"); } finally { setLoading(false); } }, []); useEffect(() => { fetchProviders(); }, [fetchProviders]); const handleAdd = () => { setFormMode("create"); setEditingProvider(null); setInitialPreset(null); setFormOpen(true); }; const handlePresetAdd = (preset: typeof QUICK_PRESETS[number]) => { setFormMode("create"); setEditingProvider(null); setInitialPreset(preset); setFormOpen(true); }; const handleEdit = (provider: ApiProvider) => { setFormMode("edit"); setEditingProvider(provider); setInitialPreset(null); setFormOpen(true); }; const handleSave = async (data: ProviderFormData) => { if (formMode === "edit" && editingProvider) { const res = await fetch(`/api/providers/${editingProvider.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) { const errData = await res.json().catch(() => ({})); throw new Error(errData.error || "Failed to update provider"); } const result = await res.json(); setProviders((prev) => prev.map((p) => (p.id === editingProvider.id ? result.provider : p)) ); } else { const res = await fetch("/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) { const errData = await res.json().catch(() => ({})); throw new Error(errData.error || "Failed to create provider"); } const result = await res.json(); setProviders((prev) => [...prev, result.provider]); } }; const handleDelete = async () => { if (!deleteTarget) return; setDeleting(true); try { const res = await fetch(`/api/providers/${deleteTarget.id}`, { method: "DELETE", }); if (res.ok) { setProviders((prev) => prev.filter((p) => p.id !== deleteTarget.id)); } } catch { // ignore } finally { setDeleting(false); setDeleteTarget(null); } }; const handleActivate = async (provider: ApiProvider) => { setActivatingId(provider.id); try { const res = await fetch(`/api/providers/${provider.id}/activate`, { method: "POST", }); if (res.ok) { // Mark the activated provider as active, deactivate others setProviders((prev) => prev.map((p) => ({ ...p, is_active: p.id === provider.id ? 1 : 0, })) ); } } catch { // ignore } finally { setActivatingId(null); } }; const handleDeactivate = async (provider: ApiProvider) => { setActivatingId(provider.id); try { const res = await fetch(`/api/providers/${provider.id}/activate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ active: false }), }); if (res.ok) { setProviders((prev) => prev.map((p) => ({ ...p, is_active: 0 })) ); } } catch { // ignore } finally { setActivatingId(null); } }; const sorted = [...providers].sort((a, b) => a.sort_order - b.sort_order); return (
{/* Header */}

API Providers

{providers.length > 0 && ( ({providers.length}) )}

Manage API providers for Claude Code. The active provider will be used for all sessions.

{/* Error */} {error && (

{error}

)} {/* Environment variable detection banner */} {!loading && Object.keys(envDetected).length > 0 && (() => { const hasActiveProvider = providers.some(p => p.is_active === 1); return (

Environment variables detected

{hasActiveProvider ? ( Overridden ) : ( In use )}
{Object.entries(envDetected).map(([key, value]) => (

{key}={value}

))}
{hasActiveProvider && (

Active provider takes priority. Disable it to use environment variables.

)}
); })()} {/* Loading */} {loading && (

Loading providers...

)} {/* Empty state */} {!loading && providers.length === 0 && (

No providers configured

{Object.keys(envDetected).length > 0 ? "Using environment variables. Add a provider below to override." : "Add a provider to use a custom API endpoint with Claude Code."}

{/* Quick preset buttons */}
{QUICK_PRESETS.map((preset) => ( ))}
)} {/* Provider list */} {!loading && sorted.length > 0 && (
{sorted.map((provider) => { const isActive = provider.is_active === 1; const isActivating = activatingId === provider.id; return (
{provider.name} {provider.provider_type} {isActive && ( Active )}
{provider.base_url && (

{provider.base_url}

)}
{/* Actions */}
{isActive ? ( ) : ( )}
); })}
)} {/* Quick presets row (when providers exist) */} {!loading && providers.length > 0 && (
Quick add: {QUICK_PRESETS.map((preset) => ( ))}
)} {/* Form dialog */} {/* Delete confirmation */} !open && setDeleteTarget(null)}> Delete Provider Are you sure you want to delete "{deleteTarget?.name}"? This action cannot be undone. Cancel {deleting ? "Deleting..." : "Delete"}
); }