import { useState } from 'react'; import { useAppStore } from '@/store/app-store'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { cn } from '@/lib/utils'; import { Cloud, Eye, EyeOff, ExternalLink, MoreVertical, Pencil, Plus, Server, Trash2, Zap, } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import type { ClaudeApiProfile, ApiKeySource } from '@automaker/types'; import { CLAUDE_API_PROFILE_TEMPLATES } from '@automaker/types'; // Generate unique ID for profiles function generateProfileId(): string { return `profile-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } // Mask API key for display (show first 4 + last 4 chars) function maskApiKey(key?: string): string { if (!key || key.length <= 8) return '••••••••'; return `${key.substring(0, 4)}••••${key.substring(key.length - 4)}`; } interface ProfileFormData { name: string; baseUrl: string; apiKeySource: ApiKeySource; apiKey: string; useAuthToken: boolean; timeoutMs: string; // String for input, convert to number modelMappings: { haiku: string; sonnet: string; opus: string; }; disableNonessentialTraffic: boolean; } const emptyFormData: ProfileFormData = { name: '', baseUrl: '', apiKeySource: 'inline', apiKey: '', useAuthToken: false, timeoutMs: '', modelMappings: { haiku: '', sonnet: '', opus: '', }, disableNonessentialTraffic: false, }; export function ApiProfilesSection() { const { claudeApiProfiles, activeClaudeApiProfileId, addClaudeApiProfile, updateClaudeApiProfile, deleteClaudeApiProfile, setActiveClaudeApiProfile, } = useAppStore(); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingProfileId, setEditingProfileId] = useState(null); const [formData, setFormData] = useState(emptyFormData); const [showApiKey, setShowApiKey] = useState(false); const [deleteConfirmId, setDeleteConfirmId] = useState(null); const [currentTemplate, setCurrentTemplate] = useState< (typeof CLAUDE_API_PROFILE_TEMPLATES)[0] | null >(null); const handleOpenAddDialog = (templateName?: string) => { const template = templateName ? CLAUDE_API_PROFILE_TEMPLATES.find((t) => t.name === templateName) : undefined; if (template) { setFormData({ name: template.name, baseUrl: template.baseUrl, apiKeySource: template.defaultApiKeySource ?? 'inline', apiKey: '', useAuthToken: template.useAuthToken, timeoutMs: template.timeoutMs?.toString() ?? '', modelMappings: { haiku: template.modelMappings?.haiku ?? '', sonnet: template.modelMappings?.sonnet ?? '', opus: template.modelMappings?.opus ?? '', }, disableNonessentialTraffic: template.disableNonessentialTraffic ?? false, }); setCurrentTemplate(template); } else { setFormData(emptyFormData); setCurrentTemplate(null); } setEditingProfileId(null); setShowApiKey(false); setIsDialogOpen(true); }; const handleOpenEditDialog = (profile: ClaudeApiProfile) => { // Find matching template by base URL const template = CLAUDE_API_PROFILE_TEMPLATES.find((t) => t.baseUrl === profile.baseUrl); setFormData({ name: profile.name, baseUrl: profile.baseUrl, apiKeySource: profile.apiKeySource ?? 'inline', apiKey: profile.apiKey ?? '', useAuthToken: profile.useAuthToken ?? false, timeoutMs: profile.timeoutMs?.toString() ?? '', modelMappings: { haiku: profile.modelMappings?.haiku ?? '', sonnet: profile.modelMappings?.sonnet ?? '', opus: profile.modelMappings?.opus ?? '', }, disableNonessentialTraffic: profile.disableNonessentialTraffic ?? false, }); setEditingProfileId(profile.id); setCurrentTemplate(template ?? null); setShowApiKey(false); setIsDialogOpen(true); }; const handleSave = () => { const profileData: ClaudeApiProfile = { id: editingProfileId ?? generateProfileId(), name: formData.name.trim(), baseUrl: formData.baseUrl.trim(), apiKeySource: formData.apiKeySource, // Only include apiKey when source is 'inline' apiKey: formData.apiKeySource === 'inline' ? formData.apiKey : undefined, useAuthToken: formData.useAuthToken, timeoutMs: (() => { const parsed = Number(formData.timeoutMs); return Number.isFinite(parsed) ? parsed : undefined; })(), modelMappings: formData.modelMappings.haiku || formData.modelMappings.sonnet || formData.modelMappings.opus ? { ...(formData.modelMappings.haiku && { haiku: formData.modelMappings.haiku }), ...(formData.modelMappings.sonnet && { sonnet: formData.modelMappings.sonnet }), ...(formData.modelMappings.opus && { opus: formData.modelMappings.opus }), } : undefined, disableNonessentialTraffic: formData.disableNonessentialTraffic || undefined, }; if (editingProfileId) { updateClaudeApiProfile(editingProfileId, profileData); } else { addClaudeApiProfile(profileData); } setIsDialogOpen(false); setFormData(emptyFormData); setEditingProfileId(null); }; const handleDelete = (id: string) => { deleteClaudeApiProfile(id); setDeleteConfirmId(null); }; // Check for duplicate profile name (case-insensitive, excluding current profile when editing) const isDuplicateName = claudeApiProfiles.some( (p) => p.name.toLowerCase() === formData.name.trim().toLowerCase() && p.id !== editingProfileId ); // API key is only required when source is 'inline' const isFormValid = formData.name.trim().length > 0 && formData.baseUrl.trim().length > 0 && (formData.apiKeySource !== 'inline' || formData.apiKey.length > 0) && !isDuplicateName; return (
{/* Header */}

API Profiles

Manage Claude-compatible API endpoints

handleOpenAddDialog()}> Custom Profile {CLAUDE_API_PROFILE_TEMPLATES.map((template) => ( handleOpenAddDialog(template.name)} > {template.name} ))}
{/* Content */}
{/* Active Profile Selector */}

{activeClaudeApiProfileId ? 'Using custom API endpoint' : 'Using direct Anthropic API (API key or Claude Max plan)'}

{/* Profile List */} {claudeApiProfiles.length === 0 ? (

No API profiles configured

Add a profile to use alternative Claude-compatible endpoints

) : (
{claudeApiProfiles.map((profile) => ( handleOpenEditDialog(profile)} onDelete={() => setDeleteConfirmId(profile.id)} onSetActive={() => setActiveClaudeApiProfile(profile.id)} /> ))}
)}
{/* Add/Edit Dialog */} {editingProfileId ? 'Edit API Profile' : 'Add API Profile'} Configure a Claude-compatible API endpoint. API keys are stored locally.
{/* Name */}
setFormData({ ...formData, name: e.target.value })} placeholder="e.g., z.AI GLM" className={isDuplicateName ? 'border-destructive' : ''} /> {isDuplicateName && (

A profile with this name already exists

)}
{/* Base URL */}
setFormData({ ...formData, baseUrl: e.target.value })} placeholder="https://api.example.com/v1" />
{/* API Key Source */}
{formData.apiKeySource === 'credentials' && (

Will use the Anthropic key from Settings → API Keys

)} {formData.apiKeySource === 'env' && (

Will use ANTHROPIC_API_KEY environment variable

)}
{/* API Key (only shown for inline source) */} {formData.apiKeySource === 'inline' && (
setFormData({ ...formData, apiKey: e.target.value })} placeholder="Enter API key" className="pr-10" />
{currentTemplate?.apiKeyUrl && ( Get API Key from {currentTemplate.name} )}
)} {/* Use Auth Token */}

Use ANTHROPIC_AUTH_TOKEN instead of ANTHROPIC_API_KEY

setFormData({ ...formData, useAuthToken: checked })} />
{/* Timeout */}
setFormData({ ...formData, timeoutMs: e.target.value })} placeholder="Optional, e.g., 3000000" />
{/* Model Mappings */}

Map Claude model aliases to provider-specific model names

setFormData({ ...formData, modelMappings: { ...formData.modelMappings, haiku: e.target.value }, }) } placeholder="e.g., GLM-4.5-Flash" className="text-xs" />
setFormData({ ...formData, modelMappings: { ...formData.modelMappings, sonnet: e.target.value }, }) } placeholder="e.g., glm-4.7" className="text-xs" />
setFormData({ ...formData, modelMappings: { ...formData.modelMappings, opus: e.target.value }, }) } placeholder="e.g., glm-4.7" className="text-xs" />
{/* Disable Non-essential Traffic */}

Sets CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

setFormData({ ...formData, disableNonessentialTraffic: checked }) } />
{/* Delete Confirmation Dialog */} !open && setDeleteConfirmId(null)}> Delete Profile? This will permanently delete the API profile. If this profile is currently active, you will be switched to direct Anthropic API.
); } interface ProfileCardProps { profile: ClaudeApiProfile; isActive: boolean; onEdit: () => void; onDelete: () => void; onSetActive: () => void; } function ProfileCard({ profile, isActive, onEdit, onDelete, onSetActive }: ProfileCardProps) { return (

{profile.name}

{isActive && ( Active )}

{profile.baseUrl}

Key: {maskApiKey(profile.apiKey)} {profile.useAuthToken && Auth Token} {profile.timeoutMs && Timeout: {(profile.timeoutMs / 1000).toFixed(0)}s}
{!isActive && ( Set Active )} Edit Delete
); }