# Component Inventory -- LLMHosts.com

## META
- created: 2026-02-14
- references: design-system.md, wireframes/, PRD

---

## LAYOUT COMPONENTS

### AppShell
- category: layout
- desc: Root layout with sidebar + topbar + main content area
- props: `{ children: ReactNode, sidebarCollapsed?: boolean }`
- used: every page

### Sidebar
- category: navigation
- desc: Left nav with logo, links, backend status, tunnel status, version
- props: `{ collapsed: boolean, onToggle: () => void, backends: BackendStatus[], tunnelStatus: TunnelState }`
- data: GET /health, GET /internal/tunnel/status
- used: every page (inside AppShell)

### TopBar
- category: layout
- desc: Page title + search + settings gear icon
- props: `{ title: string, showSearch?: boolean }`
- used: every page

### PageContainer
- category: layout
- desc: Consistent padding (space-6) and max-width container for page content
- props: `{ children: ReactNode }`
- used: every page

---

## DATA DISPLAY COMPONENTS

### SavingsTicker
- category: data-display
- desc: Hero widget showing total $ saved with animated counter roll-up
- props: `{ totalSaved: number, percentReduction: number, period: "today" | "week" | "month" | "all" }`
- variants: compact (sidebar), full (dashboard card)
- data: GET /internal/metrics/savings
- color: color-savings (emerald-400)
- animation: counter 500ms ease-out
- a11y: aria-live="polite", announces dollar amount
- used: dashboard-main

### StatCard
- category: data-display
- desc: Generic stat card with number, label, and optional trend
- props: `{ value: string, label: string, trend?: { direction: "up" | "down" | "flat", percent: number }, icon?: LucideIcon }`
- used: dashboard-main (requests, cache hit rate, cost)

### RequestLogTable
- category: data-display
- desc: Real-time scrolling table of recent requests
- props: `{ maxRows?: number }`
- columns: Time, Query (truncated), Model Used, Routing Tier (Badge), Cost, Cache (hit/miss), Latency
- data: WS /ws/dashboard (streaming)
- features: virtual scroll >100 rows, click row to expand detail, tier badges colored per design-system
- used: dashboard-main

### BackendStatusBadge
- category: data-display
- desc: Colored dot + provider name showing backend health
- props: `{ provider: string, status: "ok" | "error" | "unconfigured" }`
- colors: ok=green-400, error=red-400, unconfigured=gray-500
- used: sidebar, dashboard-main

### ModelCard
- category: data-display
- desc: Card showing model info: name, provider, VRAM, capabilities, status
- props: `{ name: string, provider: "ollama" | "openai" | "anthropic" | "vllm", vramMb?: number, capabilities: string[], status: "ready" | "loading" | "offline" }`
- used: models-page

### CacheTierBreakdown
- category: data-display
- desc: Shows hit rates per cache tier (exact, entity, semantic)
- props: `{ exact: number, entity: number, semantic: number, fpRate: number }`
- data: GET /internal/cache/stats
- used: dashboard-main, cache-page

### RoutingTierBreakdown
- category: data-display
- desc: Shows routing distribution (rule, knn, modernbert, qwen)
- props: `{ rule: number, knn: number, modernbert: number, qwen: number }`
- data: GET /internal/metrics/routing
- used: dashboard-main

### FalsePositiveGauge
- category: data-display
- desc: Circular gauge showing FP rate vs target (1%)
- props: `{ current: number, target: number }`
- colors: below target=green, above=red
- used: cache-page

### TunnelStatusIndicator
- category: data-display
- desc: Shows tunnel connection state with URL
- props: `{ state: "connected" | "connecting" | "disconnected" | "error", url?: string }`
- used: sidebar, settings-page

### HardwareProfile
- category: data-display
- desc: GPU name, VRAM bar, CPU, RAM display
- props: `{ gpu?: string, vramMb?: number, vramUsedMb?: number, cpu: string, ramMb: number }`
- data: GET /internal/doctor (hardware_profile table)
- used: settings-page

### RoutingDecisionTooltip
- category: data-display
- desc: Tooltip showing why a routing decision was made
- props: `{ tier: string, confidence: number, reason: string, alternatives: string[] }`
- used: dashboard-main (hover on request row)

### CacheEntryRow
- category: data-display
- desc: Single row in cache entries table
- props: `{ queryPreview: string, namespace: string, hitCount: number, age: string, tier: "exact" | "entity" | "semantic" }`
- used: cache-page

### ModelGrid
- category: data-display
- desc: Grid layout of ModelCards with provider filter and status grouping
- props: `{ models: ModelInfo[], filter?: string, groupBy?: "provider" | "status" }`
- data: GET /v1/models
- used: models-page

### ProviderFilter
- category: data-display
- desc: Pill-style toggle filter for model providers (All, Ollama, OpenAI, Anthropic, vLLM)
- props: `{ providers: string[], selected: string, onChange: (provider: string) => void }`
- used: models-page (above ModelGrid)

---

## INPUT COMPONENTS

### ConfigInput
- category: input
- desc: Labeled config field with current value, default, and save
- props: `{ label: string, value: string, defaultValue: string, onChange: (v: string) => void, onSave: () => void }`
- used: settings-page

### KeyManager
- category: input
- desc: Add/remove BYOK API keys with provider select and validation
- props: `{ keys: { provider: string, validated: boolean }[], onAdd: (provider: string, key: string) => void, onRemove: (provider: string) => void }`
- data: GET /internal/keys/list, POST /internal/keys/add, POST /internal/keys/remove
- used: settings-page

### NamespaceBrowser
- category: input
- desc: Searchable tree of cache namespaces with entry counts
- props: `{ namespaces: { key: string, count: number }[], onSelect: (ns: string) => void, onClear: (ns: string) => void }`
- data: GET /internal/cache/stats
- used: cache-page

---

## FEEDBACK COMPONENTS

### BaselineCapture
- category: feedback
- desc: First-run modal asking monthly AI spend for honest savings comparison
- props: `{ onSubmit: (spend: number) => void, onSkip: () => void }`
- options: "$0-50", "$50-100", "$100-200", "$200+"
- used: dashboard-main (first visit only)

### UpgradePrompt
- category: feedback
- desc: Banner suggesting Pro upgrade with contextual reason
- props: `{ reason: "multi-device" | "cloud-dashboard" | "tunnel", onUpgrade: () => void, onDismiss: () => void }`
- used: dashboard-main, settings-page

### SetupInstructions
- category: feedback
- desc: Empty state card with copy-paste terminal commands
- props: `{ step: "install-ollama" | "pull-model" | "add-key" | "set-api-base", command: string }`
- used: dashboard-main (empty), models-page (no models)

### ClearCacheDialog
- category: feedback
- desc: Confirmation dialog for clearing cache (all or by namespace)
- props: `{ namespace?: string, onConfirm: () => void, onCancel: () => void }`
- data: POST /internal/cache/clear
- used: cache-page (clear button)

### SettingsSection
- category: layout
- desc: Collapsible settings group with title, description, and child config inputs
- props: `{ title: string, description?: string, children: ReactNode, defaultOpen?: boolean }`
- used: settings-page (General, Router, Cache, Keys, Tunnel, About sections)

---

## COMPOSITION PATTERNS

- **Standard Page**: AppShell > TopBar + PageContainer > [content]
- **Dashboard**: AppShell > TopBar + Row(4x StatCard) + RequestLogTable + Row(CacheTierBreakdown + RoutingTierBreakdown)
- **Settings**: AppShell > TopBar + PageContainer > SettingsSection(General) + SettingsSection(Router) + SettingsSection(Cache) + KeyManager + TunnelStatusIndicator + HardwareProfile

---

## SHARED UTILITIES

```typescript
formatCurrency(cents: number): string     // "$12.47"
formatDuration(ms: number): string        // "142ms" or "1.2s"
formatTokens(count: number): string       // "1.2K" or "3.4M"
formatPercent(ratio: number): string      // "34.2%"
cn(...classes: string[]): string          // tailwind-merge
useWebSocket(url: string): { data, status, reconnect }  // real-time dashboard hook
usePolling(url: string, intervalMs: number): { data, isLoading, error }  // fallback polling
```
