'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 (
setParams( { q: event.target.value }, { limitUrlUpdates: debounce(SEARCH_DEBOUNCE_MS) } ) } aria-label='Search AI models' />
{PROVIDER_OPTIONS.map((provider) => ( ))}
{!hasResults ? (

No matches found

Try a provider name like OpenAI or Anthropic, or search for capabilities like  structured outputs, reasoning, or deep research.

) : (
{filteredProviders.map((provider, index) => (
{index > 0 &&
}

{provider.name}

{provider.modelCount} models · {provider.description}

{provider.models.map((model) => ( ))}
))} {filteredDynamicProviders.length > 0 && (

Dynamic model catalogs

These providers load their model lists dynamically at runtime.

)}
)}
) } function ModelRow({ provider, model }: { provider: CatalogProvider; model: CatalogModel }) { return ( <>

{model.displayName}

{model.id} · Input {formatPrice(model.pricing.input)}/1M · Output{' '} {formatPrice(model.pricing.output)}/1M {model.contextWindow ? ` · ${formatTokenCount(model.contextWindow)} context` : ''}

) }