Skip to content

Commit 6c8485b

Browse files
Development environment setup (#22)
* Add ⌘K keyboard shortcut to focus search input - Add Cmd+K / Ctrl+K keyboard shortcut that focuses the search input - Show a ⌘K badge inside the search input when not focused and empty - Badge hides when input is focused or has a query Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com> * Add 10 polish improvements for a more professional feel 1. Scroll to top on tab change — no more stranded mid-scroll 2. Sticky table header — stays visible when scrolling 2600+ models 3. Custom ::selection color — brand-consistent indigo highlight 4. Escape key blurs search — complements ⌘K shortcut 5. Logo is a clickable home link — returns to Models tab 6. Indigo left-border accent on row hover — visual affordance 7. Global focus-visible ring — keyboard accessibility polish 8. Fade transition on tab content — smooth tab switching 9. Title tooltip on truncated model names — full name on hover 10. Animated stat card numbers — count-up from 0 on load Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Ishaan Jaff <ishaan-jaff@users.noreply.github.com>
1 parent 976cf29 commit 6c8485b

2 files changed

Lines changed: 111 additions & 17 deletions

File tree

src/App.svelte

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@
3535
// Copy toast
3636
let copiedModel = "";
3737
38+
// Search input ref and focus tracking
39+
let searchInput: HTMLInputElement;
40+
let searchFocused = false;
41+
42+
function handleKeydown(e: KeyboardEvent) {
43+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
44+
e.preventDefault();
45+
searchInput?.focus();
46+
}
47+
if (e.key === "Escape" && document.activeElement === searchInput) {
48+
searchInput?.blur();
49+
}
50+
}
51+
3852
// Quick start tab state per model
3953
let codeTabStates: Record<string, "sdk" | "proxy"> = {};
4054
@@ -295,6 +309,8 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
295309
}
296310
</script>
297311

312+
<svelte:window on:keydown={handleKeydown} />
313+
298314
<main class="container">
299315
<!-- Hero Section -->
300316
<div class="hero">
@@ -370,7 +386,10 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
370386
</svg>
371387
<input
372388
id="query"
389+
bind:this={searchInput}
373390
bind:value={query}
391+
on:focus={() => { searchFocused = true; }}
392+
on:blur={() => { searchFocused = false; }}
374393
type="text"
375394
autocomplete="off"
376395
name="query"
@@ -383,6 +402,9 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
383402
<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>
384403
</button>
385404
{/if}
405+
{#if !query && !searchFocused}
406+
<kbd class="search-shortcut">⌘K</kbd>
407+
{/if}
386408
</div>
387409

388410
<ProviderDropdown bind:selectedProvider {providers} />
@@ -502,7 +524,7 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_
502524
{/if}
503525
</div>
504526
<div class="model-name-group">
505-
<span class="model-title">{getDisplayModelName(name, litellm_provider)}</span>
527+
<span class="model-title" title={getDisplayModelName(name, litellm_provider)}>{getDisplayModelName(name, litellm_provider)}</span>
506528
{#if mode}
507529
<span class="mode-badge">{getModeLabel(mode)}</span>
508530
{/if}
@@ -925,6 +947,26 @@ curl http://0.0.0.0:4000/v1/chat/completions \
925947
926948
.search-clear:hover { color: var(--text-color); background: var(--bg-tertiary); }
927949
950+
.search-shortcut {
951+
position: absolute;
952+
right: 0.75rem;
953+
top: 50%;
954+
transform: translateY(-50%);
955+
display: inline-flex;
956+
align-items: center;
957+
justify-content: center;
958+
padding: 0.125rem 0.4375rem;
959+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
960+
font-size: 0.6875rem;
961+
font-weight: 500;
962+
color: var(--muted-color);
963+
background: var(--bg-secondary);
964+
border: 1px solid var(--border-color);
965+
border-radius: 5px;
966+
pointer-events: none;
967+
line-height: 1.4;
968+
}
969+
928970
/* Filters */
929971
.filters-row {
930972
display: grid;
@@ -1053,6 +1095,9 @@ curl http://0.0.0.0:4000/v1/chat/completions \
10531095
background-color: var(--bg-secondary);
10541096
white-space: nowrap;
10551097
user-select: none;
1098+
position: sticky;
1099+
top: 63px;
1100+
z-index: 10;
10561101
}
10571102
10581103
.th-model { padding-left: 1rem; }
@@ -1082,7 +1127,10 @@ curl http://0.0.0.0:4000/v1/chat/completions \
10821127
cursor: pointer;
10831128
}
10841129
1085-
tbody tr.model-row:hover { background-color: var(--hover-bg); }
1130+
tbody tr.model-row:hover {
1131+
background-color: var(--hover-bg);
1132+
box-shadow: inset 3px 0 0 var(--litellm-primary);
1133+
}
10861134
tbody tr.model-row.expanded { background-color: var(--bg-secondary); }
10871135
tbody tr.model-row:last-child { border-bottom: none; }
10881136

src/Main.svelte

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { onMount } from "svelte";
3+
import { fade } from "svelte/transition";
34
import App from "./App.svelte";
45
import Providers from "./Providers.svelte";
56
import Cookbook from "./Cookbook.svelte";
@@ -44,6 +45,7 @@
4445
activeTab = tab;
4546
closeMobileMenu();
4647
trackTabChange(tab);
48+
window.scrollTo({ top: 0, behavior: "smooth" });
4749
4850
if (updateUrl) {
4951
const path = getPathFromTab(tab);
@@ -64,6 +66,23 @@
6466
let modelCount = 0;
6567
let statsLoading = true;
6668
69+
let displayModelCount = 0;
70+
let displayProviderCount = 0;
71+
let displayEndpointCount = 0;
72+
let displayComboCount = 0;
73+
74+
function animateValue(start: number, end: number, duration: number, setter: (v: number) => void) {
75+
const startTime = performance.now();
76+
function step(now: number) {
77+
const elapsed = now - startTime;
78+
const progress = Math.min(elapsed / duration, 1);
79+
const eased = 1 - Math.pow(1 - progress, 3);
80+
setter(Math.round(start + (end - start) * eased));
81+
if (progress < 1) requestAnimationFrame(step);
82+
}
83+
requestAnimationFrame(step);
84+
}
85+
6786
onMount(async () => {
6887
initAnalytics();
6988
trackPageView('Home');
@@ -109,6 +128,11 @@
109128
modelCount = Object.keys(modelsData).length;
110129
111130
statsLoading = false;
131+
132+
animateValue(0, modelCount, 800, (v) => displayModelCount = v);
133+
animateValue(0, providerCount, 600, (v) => displayProviderCount = v);
134+
animateValue(0, endpointCount, 600, (v) => displayEndpointCount = v);
135+
animateValue(0, providerEndpointCount, 700, (v) => displayComboCount = v);
112136
} catch (error) {
113137
console.error("Failed to load statistics:", error);
114138
statsLoading = false;
@@ -127,10 +151,10 @@
127151
<!-- Header -->
128152
<header class="header">
129153
<div class="header-content">
130-
<div class="logo-section-header">
154+
<a href="/" class="logo-section-header" on:click|preventDefault={() => selectTab("models")}>
131155
<span class="logo-emoji">🚅</span>
132156
<span class="logo-text-header">LiteLLM</span>
133-
</div>
157+
</a>
134158

135159
<!-- Desktop Navigation -->
136160
<div class="desktop-nav">
@@ -256,35 +280,39 @@
256280
<div class="stats-section">
257281
<div class="stats-container">
258282
<button class="stat-card" on:click={() => selectTab("models")}>
259-
<div class="stat-value">{modelCount.toLocaleString()}</div>
283+
<div class="stat-value">{displayModelCount.toLocaleString()}</div>
260284
<div class="stat-label">Models Supported</div>
261285
</button>
262286
<button class="stat-card" on:click={() => selectTab("providers")}>
263-
<div class="stat-value">{providerCount}</div>
287+
<div class="stat-value">{displayProviderCount.toLocaleString()}</div>
264288
<div class="stat-label">Providers</div>
265289
</button>
266290
<button class="stat-card" on:click={() => selectTab("providers")}>
267-
<div class="stat-value">{endpointCount}</div>
291+
<div class="stat-value">{displayEndpointCount.toLocaleString()}</div>
268292
<div class="stat-label">Unique Endpoints</div>
269293
</button>
270294
<button class="stat-card" on:click={() => selectTab("providers")}>
271-
<div class="stat-value">{providerEndpointCount}</div>
295+
<div class="stat-value">{displayComboCount.toLocaleString()}</div>
272296
<div class="stat-label">Provider + Endpoint Combos</div>
273297
</button>
274298
</div>
275299
</div>
276300
{/if}
277301

278302
<!-- Content -->
279-
{#if activeTab === "models"}
280-
<App />
281-
{:else if activeTab === "providers"}
282-
<Providers />
283-
{:else if activeTab === "cookbook"}
284-
<Cookbook />
285-
{:else if activeTab === "guardrails"}
286-
<Guardrails />
287-
{/if}
303+
{#key activeTab}
304+
<div in:fade={{ duration: 150, delay: 50 }}>
305+
{#if activeTab === "models"}
306+
<App />
307+
{:else if activeTab === "providers"}
308+
<Providers />
309+
{:else if activeTab === "cookbook"}
310+
<Cookbook />
311+
{:else if activeTab === "guardrails"}
312+
<Guardrails />
313+
{/if}
314+
</div>
315+
{/key}
288316

289317
<!-- Request Form Modal -->
290318
<RequestForm bind:this={requestForm} />
@@ -301,6 +329,22 @@
301329
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
302330
}
303331
332+
:global(::selection) {
333+
background: rgba(99, 102, 241, 0.2);
334+
color: inherit;
335+
}
336+
337+
:global(*:focus-visible) {
338+
outline: 2px solid var(--litellm-primary);
339+
outline-offset: 2px;
340+
}
341+
342+
:global(input:focus-visible),
343+
:global(textarea:focus-visible),
344+
:global(select:focus-visible) {
345+
outline: none;
346+
}
347+
304348
:global(*::-webkit-scrollbar) {
305349
width: 8px;
306350
height: 8px;
@@ -420,6 +464,8 @@
420464
display: flex;
421465
align-items: center;
422466
gap: 0.5rem;
467+
text-decoration: none;
468+
color: inherit;
423469
}
424470
425471
.logo-emoji {

0 commit comments

Comments
 (0)