chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { ChipInput, Search } from '@sim/emcn'
|
||||
import Link from 'next/link'
|
||||
import { debounce, useQueryStates } from 'nuqs'
|
||||
import { ChevronArrow } from '@/app/(landing)/components/chevron-arrow'
|
||||
import { ProviderIcon } from '@/app/(landing)/models/components/model-primitives'
|
||||
import { modelsParsers, modelsUrlKeys } from '@/app/(landing)/models/search-params'
|
||||
import {
|
||||
type CatalogModel,
|
||||
type CatalogProvider,
|
||||
formatPrice,
|
||||
formatTokenCount,
|
||||
MODEL_PROVIDERS_WITH_CATALOGS,
|
||||
MODEL_PROVIDERS_WITH_DYNAMIC_CATALOGS,
|
||||
} from '@/app/(landing)/models/utils'
|
||||
|
||||
/** Debounce window for writing the search term to the URL (filtering is instant). */
|
||||
const SEARCH_DEBOUNCE_MS = 300
|
||||
|
||||
const PROVIDER_OPTIONS = MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({
|
||||
id: provider.id,
|
||||
name: provider.name,
|
||||
count: provider.modelCount,
|
||||
}))
|
||||
|
||||
export function ModelDirectory() {
|
||||
const [{ q: query, provider }, setParams] = useQueryStates(modelsParsers, modelsUrlKeys)
|
||||
const activeProviderId = provider || null
|
||||
|
||||
const normalizedQuery = query.trim().toLowerCase()
|
||||
|
||||
const { filteredProviders, filteredDynamicProviders } = useMemo(() => {
|
||||
const filteredProviders = MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => {
|
||||
const providerMatchesSearch =
|
||||
normalizedQuery.length > 0 && provider.searchText.includes(normalizedQuery)
|
||||
const providerMatchesFilter = !activeProviderId || provider.id === activeProviderId
|
||||
|
||||
if (!providerMatchesFilter) {
|
||||
return null
|
||||
}
|
||||
|
||||
const models =
|
||||
normalizedQuery.length === 0
|
||||
? provider.models
|
||||
: provider.models.filter(
|
||||
(model) =>
|
||||
model.searchText.includes(normalizedQuery) ||
|
||||
(providerMatchesSearch && normalizedQuery.length > 0)
|
||||
)
|
||||
|
||||
if (!providerMatchesSearch && models.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...provider,
|
||||
models: providerMatchesSearch && normalizedQuery.length > 0 ? provider.models : models,
|
||||
}
|
||||
}).filter((provider): provider is CatalogProvider => provider !== null)
|
||||
|
||||
const filteredDynamicProviders = MODEL_PROVIDERS_WITH_DYNAMIC_CATALOGS.filter((provider) => {
|
||||
const providerMatchesFilter = !activeProviderId || provider.id === activeProviderId
|
||||
if (!providerMatchesFilter) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!normalizedQuery) {
|
||||
return true
|
||||
}
|
||||
|
||||
return provider.searchText.includes(normalizedQuery)
|
||||
})
|
||||
|
||||
return {
|
||||
filteredProviders,
|
||||
filteredDynamicProviders,
|
||||
}
|
||||
}, [activeProviderId, normalizedQuery])
|
||||
|
||||
const hasResults = filteredProviders.length > 0 || filteredDynamicProviders.length > 0
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='mb-6 flex flex-col gap-4 px-6 sm:flex-row sm:items-center'>
|
||||
<div className='max-w-[480px] flex-1'>
|
||||
<ChipInput
|
||||
icon={Search}
|
||||
type='search'
|
||||
placeholder='Search models, providers, or capabilities…'
|
||||
value={query}
|
||||
onChange={(event) =>
|
||||
setParams(
|
||||
{ q: event.target.value },
|
||||
{ limitUrlUpdates: debounce(SEARCH_DEBOUNCE_MS) }
|
||||
)
|
||||
}
|
||||
aria-label='Search AI models'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-6 flex flex-wrap gap-2 px-6'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setParams({ provider: '' })}
|
||||
className={`rounded-[5px] border px-[9px] py-0.5 text-small transition-colors ${
|
||||
activeProviderId === null
|
||||
? 'border-[var(--border-1)] bg-[var(--surface-active)] text-[var(--text-primary)]'
|
||||
: 'border-[var(--border-1)] text-[var(--text-primary)] hover:bg-[var(--surface-hover)]'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
{PROVIDER_OPTIONS.map((provider) => (
|
||||
<button
|
||||
key={provider.id}
|
||||
type='button'
|
||||
onClick={() =>
|
||||
setParams({ provider: activeProviderId === provider.id ? '' : provider.id })
|
||||
}
|
||||
className={`rounded-[5px] border px-[9px] py-0.5 text-small transition-colors ${
|
||||
activeProviderId === provider.id
|
||||
? 'border-[var(--border-1)] bg-[var(--surface-active)] text-[var(--text-primary)]'
|
||||
: 'border-[var(--border-1)] text-[var(--text-primary)] hover:bg-[var(--surface-hover)]'
|
||||
}`}
|
||||
>
|
||||
{provider.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='h-px w-full bg-[var(--border)]' />
|
||||
|
||||
{!hasResults ? (
|
||||
<div className='px-6 py-12 text-center'>
|
||||
<h3 className='text-[18px] text-[var(--text-primary)]'>No matches found</h3>
|
||||
<p className='mt-2 text-[var(--text-muted)] text-sm leading-[150%]'>
|
||||
Try a provider name like OpenAI or Anthropic, or search for capabilities like
|
||||
structured outputs, reasoning, or deep research.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{filteredProviders.map((provider, index) => (
|
||||
<section key={provider.id} aria-labelledby={`${provider.id}-heading`}>
|
||||
{index > 0 && <div className='h-px w-full bg-[var(--border)]' />}
|
||||
|
||||
<Link
|
||||
href={provider.href}
|
||||
className='group/link flex items-center gap-3 px-6 py-4 transition-colors hover:bg-[var(--surface-hover)]'
|
||||
>
|
||||
<ProviderIcon
|
||||
provider={provider}
|
||||
className='size-8 rounded-xl'
|
||||
iconClassName='size-4'
|
||||
/>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<h3
|
||||
id={`${provider.id}-heading`}
|
||||
className='text-[14px] text-[var(--text-primary)] leading-snug tracking-[-0.02em]'
|
||||
>
|
||||
{provider.name}
|
||||
</h3>
|
||||
<p className='line-clamp-1 hidden text-[12px] text-[var(--text-muted)] leading-[150%] sm:block'>
|
||||
{provider.modelCount} models · {provider.description}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronArrow />
|
||||
</Link>
|
||||
|
||||
{provider.models.map((model) => (
|
||||
<ModelRow key={model.id} provider={provider} model={model} />
|
||||
))}
|
||||
</section>
|
||||
))}
|
||||
|
||||
{filteredDynamicProviders.length > 0 && (
|
||||
<section aria-labelledby='dynamic-catalogs-heading'>
|
||||
<div className='h-px w-full bg-[var(--border)]' />
|
||||
|
||||
<div className='px-6 pt-8 pb-6'>
|
||||
<h3
|
||||
id='dynamic-catalogs-heading'
|
||||
className='text-[18px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[20px]'
|
||||
>
|
||||
Dynamic model catalogs
|
||||
</h3>
|
||||
<p className='mt-2 text-[var(--text-muted)] text-sm leading-[150%]'>
|
||||
These providers load their model lists dynamically at runtime.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='h-px w-full bg-[var(--border)]' />
|
||||
|
||||
<nav aria-label='Dynamic catalog providers' className='flex flex-col lg:flex-row'>
|
||||
{filteredDynamicProviders.map((provider) => (
|
||||
<div
|
||||
key={provider.id}
|
||||
className='flex flex-1 items-center gap-3 border-[var(--border)] border-t px-6 py-4 first:border-t-0 lg:border-t-0 lg:border-l lg:first:border-l-0'
|
||||
>
|
||||
<ProviderIcon
|
||||
provider={provider}
|
||||
className='size-8 rounded-xl'
|
||||
iconClassName='size-4'
|
||||
/>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<h4 className='text-[14px] text-[var(--text-primary)] leading-snug'>
|
||||
{provider.name}
|
||||
</h4>
|
||||
<p className='line-clamp-1 text-[12px] text-[var(--text-muted)] leading-[150%]'>
|
||||
{provider.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelRow({ provider, model }: { provider: CatalogProvider; model: CatalogModel }) {
|
||||
return (
|
||||
<>
|
||||
<div className='h-px w-full bg-[var(--border)]' />
|
||||
<Link
|
||||
href={model.href}
|
||||
className='group/link flex items-center gap-4 px-6 py-4 transition-colors hover:bg-[var(--surface-hover)]'
|
||||
>
|
||||
<ProviderIcon
|
||||
provider={provider}
|
||||
className='size-8 shrink-0 rounded-xl'
|
||||
iconClassName='size-4'
|
||||
/>
|
||||
|
||||
<div className='flex min-w-0 flex-1 flex-col gap-0.5'>
|
||||
<h4 className='text-[14px] text-[var(--text-primary)] leading-snug tracking-[-0.02em]'>
|
||||
{model.displayName}
|
||||
</h4>
|
||||
<p className='line-clamp-1 hidden text-[12px] text-[var(--text-muted)] leading-[150%] sm:block'>
|
||||
{model.id} · Input {formatPrice(model.pricing.input)}/1M · Output{' '}
|
||||
{formatPrice(model.pricing.output)}/1M
|
||||
{model.contextWindow ? ` · ${formatTokenCount(model.contextWindow)} context` : ''}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ChevronArrow />
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user