diff --git a/ui/src/pages/api.tsx b/ui/src/pages/api.tsx index 64642395..53671c79 100644 --- a/ui/src/pages/api.tsx +++ b/ui/src/pages/api.tsx @@ -7,22 +7,21 @@ import { useState, useMemo } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { Badge } from '@/components/ui/badge'; -import { Separator } from '@/components/ui/separator'; import { Plus, Search, - Settings2, Trash2, CheckCircle2, AlertCircle, Server, - ExternalLink, FileJson, RefreshCw, } from 'lucide-react'; import { ProfileEditor } from '@/components/profile-editor'; import { ProfileCreateDialog } from '@/components/profiles/profile-create-dialog'; +import { OpenRouterBanner } from '@/components/profiles/openrouter-banner'; +import { OpenRouterQuickStart } from '@/components/profiles/openrouter-quick-start'; +import { OpenRouterPromoCard } from '@/components/profiles/openrouter-promo-card'; import { useProfiles, useDeleteProfile } from '@/hooks/use-profiles'; import { useOpenRouterModels } from '@/hooks/use-openrouter-models'; import { ConfirmDialog } from '@/components/shared/confirm-dialog'; @@ -36,6 +35,7 @@ export function ApiPage() { const [selectedProfile, setSelectedProfile] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [isCreateDialogOpen, setCreateDialogOpen] = useState(false); + const [createMode, setCreateMode] = useState<'normal' | 'openrouter'>('normal'); const [deleteConfirm, setDeleteConfirm] = useState(null); // Prefetch OpenRouter models when page loads (lazy - won't block render) @@ -50,13 +50,11 @@ export function ApiPage() { [profiles, searchQuery] ); - // Compute effective selected profile (auto-select first if none selected) - const effectiveSelectedProfile = useMemo(() => { - if (selectedProfile && profiles.some((p) => p.name === selectedProfile)) { - return selectedProfile; - } - return profiles.length > 0 ? profiles[0].name : null; - }, [selectedProfile, profiles]); + // selectedProfile is null by default - user must click to select + // This allows OpenRouterQuickStart to show as the default right panel + const selectedProfileData = selectedProfile + ? profiles.find((p) => p.name === selectedProfile) + : null; // Handle profile deletion const handleDelete = (name: string) => { @@ -76,137 +74,154 @@ export function ApiPage() { setSelectedProfile(name); }; - const selectedProfileData = profiles.find((p) => p.name === effectiveSelectedProfile); - return ( -
- {/* Left Panel - Profiles List */} -
- {/* Header */} -
-
-
- -

API Profiles

-
- -
+
+ {/* OpenRouter Announcement Banner */} + setCreateDialogOpen(true)} /> - {/* Search */} -
- - setSearchQuery(e.target.value)} - /> -
-
- - {/* Profile List */} - - {isLoading ? ( -
Loading profiles...
- ) : isError ? ( -
-
- -
-

Failed to load profiles

-

- Unable to fetch API profiles. Please try again. -

-
- + {/* Main Content */} +
+ {/* Left Panel - Profiles List */} +
+ {/* Header */} +
+
+
+ +

API Profiles

+
- ) : filteredProfiles.length === 0 ? ( -
- {profiles.length === 0 ? ( + + {/* Search */} +
+ + setSearchQuery(e.target.value)} + /> +
+
+ + {/* Profile List */} + + {isLoading ? ( +
Loading profiles...
+ ) : isError ? ( +
- +
-

No API profiles yet

+

Failed to load profiles

- Create your first profile to connect to custom API endpoints + Unable to fetch API profiles. Please try again.

-
- ) : ( -

- No profiles match "{searchQuery}" -

- )} -
- ) : ( -
- {filteredProfiles.map((profile) => ( - { - setSelectedProfile(profile.name); - }} - onDelete={() => setDeleteConfirm(profile.name)} - /> - ))} +
+ ) : filteredProfiles.length === 0 ? ( +
+ {profiles.length === 0 ? ( +
+ +
+

No API profiles yet

+

+ Create your first profile to connect to custom API endpoints +

+
+ +
+ ) : ( +

+ No profiles match "{searchQuery}" +

+ )} +
+ ) : ( +
+ {filteredProfiles.map((profile) => ( + { + setSelectedProfile(profile.name); + }} + onDelete={() => setDeleteConfirm(profile.name)} + /> + ))} +
+ )} +
+ + {/* Footer Stats */} + {profiles.length > 0 && ( +
+
+ + {profiles.length} profile{profiles.length !== 1 ? 's' : ''} + + + + {profiles.filter((p) => p.configured).length} configured + +
)} - - {/* Footer Stats */} - {profiles.length > 0 && ( -
-
- - {profiles.length} profile{profiles.length !== 1 ? 's' : ''} - - - - {profiles.filter((p) => p.configured).length} configured - -
-
- )} -
- - {/* Right Panel - Editor */} -
- {selectedProfileData ? ( - setDeleteConfirm(selectedProfileData.name)} - /> - ) : ( - { + setCreateMode('openrouter'); setCreateDialogOpen(true); }} /> - )} +
+ + {/* Right Panel - Editor or QuickStart */} +
+ {selectedProfileData ? ( + setDeleteConfirm(selectedProfileData.name)} + /> + ) : ( + { + setCreateMode('openrouter'); + setCreateDialogOpen(true); + }} + onCustomClick={() => { + setCreateMode('normal'); + setCreateDialogOpen(true); + }} + /> + )} +
{/* Create Dialog */} @@ -214,6 +229,7 @@ export function ApiPage() { open={isCreateDialogOpen} onOpenChange={setCreateDialogOpen} onSuccess={handleCreateSuccess} + initialMode={createMode} /> {/* Delete Confirmation */} @@ -289,66 +305,3 @@ function ProfileListItem({
); } - -/** Empty state when no profile is selected */ -function EmptyState({ onCreateClick }: { onCreateClick: () => void }) { - return ( -
-
- -

API Profile Manager

-

- Configure custom API endpoints for Claude CLI. Connect to proxy services like copilot-api, - OpenRouter, or your own API backend. -

- -
- - - - -
-

- What you can configure: -

-
    -
  • - - URL - - Custom API base URL endpoint -
  • -
  • - - Auth - - API key or authentication token -
  • -
  • - - Models - - Model mapping for Opus/Sonnet/Haiku -
  • -
-
- - -
-
-
- ); -}