Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@
// Copy toast
let copiedModel = "";

// Search input ref and focus tracking
let searchInput: HTMLInputElement;
let searchFocused = false;

function handleKeydown(e: KeyboardEvent) {
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault();
searchInput?.focus();
}
if (e.key === "Escape" && document.activeElement === searchInput) {
searchInput?.blur();
}
}

// Quick start tab state per model
let codeTabStates: Record<string, "sdk" | "proxy"> = {};

Expand Down Expand Up @@ -295,6 +309,8 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
}
</script>

<svelte:window on:keydown={handleKeydown} />

<main class="container">
<!-- Hero Section -->
<div class="hero">
Expand Down Expand Up @@ -370,7 +386,10 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
</svg>
<input
id="query"
bind:this={searchInput}
bind:value={query}
on:focus={() => { searchFocused = true; }}
on:blur={() => { searchFocused = false; }}
type="text"
autocomplete="off"
name="query"
Expand All @@ -383,6 +402,9 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
{/if}
{#if !query && !searchFocused}
<kbd class="search-shortcut">⌘K</kbd>
{/if}
</div>

<ProviderDropdown bind:selectedProvider {providers} />
Expand Down Expand Up @@ -502,7 +524,7 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
{/if}
</div>
<div class="model-name-group">
<span class="model-title">{getDisplayModelName(name, litellm_provider)}</span>
<span class="model-title" title={getDisplayModelName(name, litellm_provider)}>{getDisplayModelName(name, litellm_provider)}</span>
{#if mode}
<span class="mode-badge">{getModeLabel(mode)}</span>
{/if}
Expand Down Expand Up @@ -925,6 +947,26 @@ curl http://0.0.0.0:4000/v1/chat/completions \

.search-clear:hover { color: var(--text-color); background: var(--bg-tertiary); }

.search-shortcut {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.125rem 0.4375rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
font-size: 0.6875rem;
font-weight: 500;
color: var(--muted-color);
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 5px;
pointer-events: none;
line-height: 1.4;
}

/* Filters */
.filters-row {
display: grid;
Expand Down Expand Up @@ -1053,6 +1095,9 @@ curl http://0.0.0.0:4000/v1/chat/completions \
background-color: var(--bg-secondary);
white-space: nowrap;
user-select: none;
position: sticky;
top: 63px;
z-index: 10;
}

.th-model { padding-left: 1rem; }
Expand Down Expand Up @@ -1082,7 +1127,10 @@ curl http://0.0.0.0:4000/v1/chat/completions \
cursor: pointer;
}

tbody tr.model-row:hover { background-color: var(--hover-bg); }
tbody tr.model-row:hover {
background-color: var(--hover-bg);
box-shadow: inset 3px 0 0 var(--litellm-primary);
}
tbody tr.model-row.expanded { background-color: var(--bg-secondary); }
tbody tr.model-row:last-child { border-bottom: none; }

Expand Down
76 changes: 61 additions & 15 deletions src/Main.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import { fade } from "svelte/transition";
import App from "./App.svelte";
import Providers from "./Providers.svelte";
import Cookbook from "./Cookbook.svelte";
Expand Down Expand Up @@ -44,6 +45,7 @@
activeTab = tab;
closeMobileMenu();
trackTabChange(tab);
window.scrollTo({ top: 0, behavior: "smooth" });

if (updateUrl) {
const path = getPathFromTab(tab);
Expand All @@ -64,6 +66,23 @@
let modelCount = 0;
let statsLoading = true;

let displayModelCount = 0;
let displayProviderCount = 0;
let displayEndpointCount = 0;
let displayComboCount = 0;

function animateValue(start: number, end: number, duration: number, setter: (v: number) => void) {
const startTime = performance.now();
function step(now: number) {
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
setter(Math.round(start + (end - start) * eased));
if (progress < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}

onMount(async () => {
initAnalytics();
trackPageView('Home');
Expand Down Expand Up @@ -109,6 +128,11 @@
modelCount = Object.keys(modelsData).length;

statsLoading = false;

animateValue(0, modelCount, 800, (v) => displayModelCount = v);
animateValue(0, providerCount, 600, (v) => displayProviderCount = v);
animateValue(0, endpointCount, 600, (v) => displayEndpointCount = v);
animateValue(0, providerEndpointCount, 700, (v) => displayComboCount = v);
} catch (error) {
console.error("Failed to load statistics:", error);
statsLoading = false;
Expand All @@ -127,10 +151,10 @@
<!-- Header -->
<header class="header">
<div class="header-content">
<div class="logo-section-header">
<a href="/" class="logo-section-header" on:click|preventDefault={() => selectTab("models")}>
<span class="logo-emoji">🚅</span>
<span class="logo-text-header">LiteLLM</span>
</div>
</a>

<!-- Desktop Navigation -->
<div class="desktop-nav">
Expand Down Expand Up @@ -256,35 +280,39 @@
<div class="stats-section">
<div class="stats-container">
<button class="stat-card" on:click={() => selectTab("models")}>
<div class="stat-value">{modelCount.toLocaleString()}</div>
<div class="stat-value">{displayModelCount.toLocaleString()}</div>
<div class="stat-label">Models Supported</div>
</button>
<button class="stat-card" on:click={() => selectTab("providers")}>
<div class="stat-value">{providerCount}</div>
<div class="stat-value">{displayProviderCount.toLocaleString()}</div>
<div class="stat-label">Providers</div>
</button>
<button class="stat-card" on:click={() => selectTab("providers")}>
<div class="stat-value">{endpointCount}</div>
<div class="stat-value">{displayEndpointCount.toLocaleString()}</div>
<div class="stat-label">Unique Endpoints</div>
</button>
<button class="stat-card" on:click={() => selectTab("providers")}>
<div class="stat-value">{providerEndpointCount}</div>
<div class="stat-value">{displayComboCount.toLocaleString()}</div>
<div class="stat-label">Provider + Endpoint Combos</div>
</button>
</div>
</div>
{/if}

<!-- Content -->
{#if activeTab === "models"}
<App />
{:else if activeTab === "providers"}
<Providers />
{:else if activeTab === "cookbook"}
<Cookbook />
{:else if activeTab === "guardrails"}
<Guardrails />
{/if}
{#key activeTab}
<div in:fade={{ duration: 150, delay: 50 }}>
{#if activeTab === "models"}
<App />
{:else if activeTab === "providers"}
<Providers />
{:else if activeTab === "cookbook"}
<Cookbook />
{:else if activeTab === "guardrails"}
<Guardrails />
{/if}
</div>
{/key}

<!-- Request Form Modal -->
<RequestForm bind:this={requestForm} />
Expand All @@ -301,6 +329,22 @@
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

:global(::selection) {
background: rgba(99, 102, 241, 0.2);
color: inherit;
}

:global(*:focus-visible) {
outline: 2px solid var(--litellm-primary);
outline-offset: 2px;
}

:global(input:focus-visible),
:global(textarea:focus-visible),
:global(select:focus-visible) {
outline: none;
}

:global(*::-webkit-scrollbar) {
width: 8px;
height: 8px;
Expand Down Expand Up @@ -420,6 +464,8 @@
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: inherit;
}

.logo-emoji {
Expand Down