diff --git a/src/App.svelte b/src/App.svelte index 3b1c2f4..a1dc4a3 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -75,11 +75,14 @@ .then((res) => res.text()) .then((text) => { lines = text.split("\n"); - const items: Item[] = Object.entries(JSON.parse(text)).map( - ([k, v]: any) => ({ name: k, ...v }), - ); - - providers = [...new Set(items.map((i) => i.litellm_provider))]; + // Upstream JSON ships a "sample_spec" pseudo-entry that documents + // the schema (each field's value is a description string, not data). + // It is not a real model — drop it from the table. + const items: Item[] = Object.entries(JSON.parse(text)) + .filter(([k]) => k !== "sample_spec") + .map(([k, v]: any) => ({ name: k, ...v })); + + providers = [...new Set(items.map((i) => i.litellm_provider).filter(Boolean))]; providers.sort(); index = new Fuse(items, { @@ -354,25 +357,25 @@ We also need to update [${RESOURCE_BACKUP_NAME}](https://github.com/${REPO_FULL_ Google ADK Greptile OpenHands - Netflix + Netflix OpenAI Agents SDK Adobe - + Twilio - + Z Zurich Zapier - Rocket Money - Lemonade - + Rocket Money + Lemonade + The Weather Company - samsara + samsara @@ -817,12 +820,24 @@ curl http://0.0.0.0:4000/v1/chat/completions \ flex-wrap: wrap; } + /* Unified hover transition for all trust logos (img + text + svg-icon). + Same duration/easing across the board so each one fades smoothly + into its colored state. */ + .trust-logo-img, + .trust-logo-text, + .trust-logo-icon svg, + .trust-logo-svg { + transition: color 0.3s ease-out, + fill 0.3s ease-out, + opacity 0.3s ease-out, + filter 0.3s ease-out; + } + .trust-logo-img { height: 28px; object-fit: contain; opacity: 0.55; filter: grayscale(100%); - transition: all 0.2s ease; } .trust-logo-img:hover { @@ -841,11 +856,47 @@ curl http://0.0.0.0:4000/v1/chat/completions \ } } + /* Text logos use the same grayscale->color treatment as image logos. + The brand color is the BASE color; the grayscale filter desaturates + it by default, and removing the filter on hover reveals it. */ .trust-logo-text { font-size: 1.125rem; font-weight: 700; - color: var(--border-color-strong); + color: var(--text-color); letter-spacing: 0.04em; + opacity: 0.55; + filter: grayscale(100%); + cursor: default; + /* Promote to its own compositing layer so filter transitions don't + cause subpixel jitter in the inline SVG + text combo (Zurich, + Weather Company, Twilio). */ + transform: translateZ(0); + will-change: filter, opacity; + backface-visibility: hidden; + } + + .trust-logo-text:hover { + opacity: 0.9; + filter: grayscale(0%); + } + + .trust-logo-text[data-brand="netflix"] { color: #E50914; } + .trust-logo-text[data-brand="lemonade"] { color: #FF0083; } + .trust-logo-text[data-brand="rocketmoney"] { color: #00D4AA; } + .trust-logo-text[data-brand="twilio"] { color: #F22F46; } + .trust-logo-text[data-brand="zurich"] { color: #2167AE; } + .trust-logo-text[data-brand="weather"] { color: #00ABEB; } + .trust-logo-text[data-brand="samsara"] { color: #1F6FB2; } + + @media (prefers-color-scheme: dark) { + .trust-logo-text { + filter: grayscale(100%) brightness(2); + opacity: 0.5; + } + .trust-logo-text:hover { + filter: grayscale(0%) brightness(1.2); + opacity: 0.9; + } } .trust-logo-icon { @@ -856,26 +907,27 @@ curl http://0.0.0.0:4000/v1/chat/completions \ .trust-logo-icon svg { flex-shrink: 0; + fill: currentColor; } .trust-logo-svg { filter: brightness(0); - opacity: 0.45; + opacity: 0.55; } .trust-logo-svg:hover { filter: brightness(0); - opacity: 0.8; + opacity: 1; } @media (prefers-color-scheme: dark) { .trust-logo-svg { filter: brightness(0) invert(1); - opacity: 0.5; + opacity: 0.6; } .trust-logo-svg:hover { filter: brightness(0) invert(1); - opacity: 0.9; + opacity: 1; } } @@ -1095,9 +1147,6 @@ 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; } @@ -1125,6 +1174,7 @@ curl http://0.0.0.0:4000/v1/chat/completions \ border-bottom: 1px solid var(--border-color); transition: background-color 0.1s ease; cursor: pointer; + height: 52px; } tbody tr.model-row:hover { @@ -1153,6 +1203,9 @@ curl http://0.0.0.0:4000/v1/chat/completions \ display: flex; align-items: center; gap: 0.625rem; + flex-wrap: nowrap; + min-width: 0; + min-height: 32px; } .expand-icon { @@ -1224,6 +1277,8 @@ curl http://0.0.0.0:4000/v1/chat/completions \ align-items: center; gap: 0.5rem; min-width: 0; + flex: 1 1 auto; + flex-wrap: nowrap; } .model-title { @@ -1233,6 +1288,8 @@ curl http://0.0.0.0:4000/v1/chat/completions \ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + flex: 1 1 auto; + min-width: 0; } .mode-badge { @@ -1246,6 +1303,9 @@ curl http://0.0.0.0:4000/v1/chat/completions \ text-transform: uppercase; letter-spacing: 0.03em; flex-shrink: 0; + max-width: 110px; + overflow: hidden; + text-overflow: ellipsis; } .context-cell { @@ -1334,6 +1394,8 @@ curl http://0.0.0.0:4000/v1/chat/completions \ align-items: center; padding: 0.375rem 0; border-bottom: 1px solid var(--border-color); + gap: 0.75rem; + min-width: 0; } .info-row:last-child { border-bottom: none; } @@ -1341,6 +1403,7 @@ curl http://0.0.0.0:4000/v1/chat/completions \ .info-label { font-size: 0.8125rem; color: var(--muted-color); + flex-shrink: 0; } .info-value { @@ -1348,6 +1411,9 @@ curl http://0.0.0.0:4000/v1/chat/completions \ font-weight: 600; color: var(--text-color); font-family: 'JetBrains Mono', monospace; + text-align: right; + overflow-wrap: anywhere; + min-width: 0; } .feature-list { @@ -1449,9 +1515,13 @@ curl http://0.0.0.0:4000/v1/chat/completions \ overflow-x: auto; background: var(--code-bg); color: var(--code-text); + max-width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; } - .code-snippet code { display: block; } + .code-snippet code { display: block; white-space: pre; } + .detail-code-section { max-width: 100%; min-width: 0; } .code-kw { color: #8b5cf6; } .code-str { color: #10b981; } @@ -1518,6 +1588,12 @@ curl http://0.0.0.0:4000/v1/chat/completions \ .th-hide-mobile, .td-hide-mobile { display: none; } } + /* Tablet — intermediate code-snippet size between desktop and phone */ + @media (max-width: 1024px) { + .code-snippet { font-size: 0.6875rem; line-height: 1.5; padding: 0.75rem; } + .code-snippet code { white-space: pre-wrap; word-break: break-word; } + } + @media (max-width: 768px) { .hero { padding: 2.5rem 1rem 1.5rem; } .hero-title { font-size: 2rem; } @@ -1532,6 +1608,36 @@ curl http://0.0.0.0:4000/v1/chat/completions \ .model-name { min-width: 180px; } .trust-logos { gap: 1.25rem; } .trust-logo-img { height: 22px; } - .detail-grid { grid-template-columns: 1fr; } + .trust-logo-text { font-size: 0.9375rem; } + .detail-grid { grid-template-columns: 1fr; gap: 1rem; } + + /* Detail panel: shrink section headings and pricing values so they + no longer dominate the body text on phones. Section headings + (Pricing, Model Info, Features) sit one tier above pricing-card + labels (Input, Cache Read, ...) for clear hierarchy. */ + .detail-panel { padding: 1rem; } + .detail-heading { font-size: 0.5625rem; letter-spacing: 0.06em; margin-bottom: 0.5rem; } + .pricing-cards { gap: 0.3125rem; } + .pricing-card { padding: 0.375rem 0.4375rem; gap: 0.125rem; } + .pricing-label { font-size: 0.375rem; letter-spacing: 0.04em; font-weight: 500; } + .pricing-value { font-size: 0.5625rem; font-weight: 600; } + + /* Stack info-rows so "Max Input" label can't collide with long values */ + .info-row { flex-direction: column; align-items: flex-start; gap: 0.125rem; padding: 0.375rem 0; } + .info-label { font-size: 0.75rem; } + .info-value { text-align: left; font-size: 0.75rem; word-break: break-word; } + + /* Code blocks: smaller font and tighter padding for mobile readability. + Wrap lines (pre-wrap) so very long model names break instead of + forcing the block to be visually huge. */ + .code-snippet { font-size: 0.4375rem; line-height: 1.4; padding: 0.5rem; } + .code-snippet code { white-space: pre-wrap; word-break: break-word; } + .code-header-row { padding: 0.5rem 0.75rem; flex-wrap: wrap; gap: 0.5rem; } + .code-tab { padding: 0.3125rem 0.625rem; font-size: 0.6875rem; } + .copy-code-btn { font-size: 0.6875rem; padding: 0.25rem 0.5rem; } + + /* Lock row height so Bedrock / first entries don't render taller than others */ + .model-row td { height: 48px; } + .provider-avatar { width: 24px; height: 24px; } } diff --git a/src/Providers.svelte b/src/Providers.svelte index fb90b93..75d604d 100644 --- a/src/Providers.svelte +++ b/src/Providers.svelte @@ -32,20 +32,26 @@ const PROVIDERS_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/provider_endpoints_support.json"; const DOCS_URL = "https://docs.litellm.ai/docs/providers"; + // Upstream JSON ships a few broken /docs/providers/* URLs that 404. + // Map provider key -> correct doc URL. + const PROVIDER_URL_OVERRIDES: Record = { + a2a: "https://docs.litellm.ai/docs/a2a", + }; + onMount(async () => { try { const response = await fetch(PROVIDERS_URL); const data = await response.json(); - + if (data.endpoints) { endpointsMetadata = data.endpoints; } - + if (data.providers) { providers = Object.entries(data.providers).map(([provider, info]: [string, any]) => ({ provider, display_name: info.display_name || provider, - url: info.url || DOCS_URL, + url: PROVIDER_URL_OVERRIDES[provider] || info.url || DOCS_URL, endpoints: info.endpoints || {} })); }