d25d482dc2
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
243 lines
7.8 KiB
TypeScript
243 lines
7.8 KiB
TypeScript
import type { ComponentType } from 'react'
|
|
import Link from 'next/link'
|
|
import { getProviderColor } from '@/app/(landing)/models/components/constants'
|
|
import type { CatalogModel } from '@/app/(landing)/models/utils'
|
|
import {
|
|
formatPrice,
|
|
formatTokenCount,
|
|
MODEL_CATALOG_PROVIDERS,
|
|
} from '@/app/(landing)/models/utils'
|
|
|
|
/** Flagship providers featured in the landing-page comparison, in display order. */
|
|
const FEATURED_COMPARISON_PROVIDER_IDS = ['anthropic', 'openai', 'google']
|
|
|
|
/** Max latest models pulled from each featured provider. */
|
|
const MAX_MODELS_PER_PROVIDER = 4
|
|
|
|
const PROVIDER_ICON_MAP: Record<string, ComponentType<{ className?: string }>> = (() => {
|
|
const map: Record<string, ComponentType<{ className?: string }>> = {}
|
|
for (const provider of MODEL_CATALOG_PROVIDERS) {
|
|
if (provider.icon) {
|
|
map[provider.id] = provider.icon
|
|
}
|
|
}
|
|
return map
|
|
})()
|
|
|
|
function selectComparisonModels(models: CatalogModel[]): CatalogModel[] {
|
|
const seen = new Set<string>()
|
|
const result: CatalogModel[] = []
|
|
|
|
for (const providerId of FEATURED_COMPARISON_PROVIDER_IDS) {
|
|
const providerModels = models
|
|
.filter((model) => model.providerId === providerId && !model.deprecated)
|
|
.sort((a, b) => (b.releaseDate ?? '').localeCompare(a.releaseDate ?? ''))
|
|
|
|
let takenForProvider = 0
|
|
for (const model of providerModels) {
|
|
if (takenForProvider >= MAX_MODELS_PER_PROVIDER) break
|
|
|
|
const nameKey = model.displayName.toLowerCase()
|
|
if (seen.has(nameKey)) continue
|
|
|
|
seen.add(nameKey)
|
|
result.push(model)
|
|
takenForProvider += 1
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
interface ModelLabelProps {
|
|
model: CatalogModel
|
|
}
|
|
|
|
function ModelLabel({ model }: ModelLabelProps) {
|
|
const Icon = PROVIDER_ICON_MAP[model.providerId]
|
|
|
|
return (
|
|
<div className='flex w-[90px] shrink-0 items-center justify-end gap-1.5 sm:w-[140px] lg:w-[180px]'>
|
|
{Icon && <Icon className='size-3.5 shrink-0' />}
|
|
<span className='truncate text-[13px] text-[var(--text-primary)] leading-none tracking-[-0.01em]'>
|
|
{model.displayName}
|
|
</span>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface ChartProps {
|
|
models: CatalogModel[]
|
|
}
|
|
|
|
function StackedCostChart({ models }: ChartProps) {
|
|
const entries = models
|
|
.reduce<Array<{ model: CatalogModel; input: number; output: number; total: number }>>(
|
|
(acc, model) => {
|
|
const total = model.pricing.input + model.pricing.output
|
|
if (total > 0) {
|
|
acc.push({ model, input: model.pricing.input, output: model.pricing.output, total })
|
|
}
|
|
return acc
|
|
},
|
|
[]
|
|
)
|
|
.sort((a, b) => a.total - b.total)
|
|
|
|
const maxTotal = entries.length > 0 ? Math.max(...entries.map((e) => e.total)) : 0
|
|
const data = { entries, maxTotal }
|
|
|
|
if (data.entries.length === 0) return null
|
|
|
|
return (
|
|
<div className='flex flex-col gap-3'>
|
|
<div className='flex flex-col gap-1'>
|
|
<h3 className='text-[20px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[24px]'>
|
|
Cost
|
|
</h3>
|
|
<span className='text-[var(--text-muted)] text-sm leading-[150%] tracking-[0.02em]'>
|
|
Per 1M tokens
|
|
</span>
|
|
</div>
|
|
|
|
<div className='flex flex-col gap-1.5'>
|
|
{data.entries.map(({ model, input, output, total }) => {
|
|
const totalPct = data.maxTotal > 0 ? (total / data.maxTotal) * 100 : 0
|
|
const inputPct = total > 0 ? (input / total) * 100 : 0
|
|
const color = getProviderColor(model.providerId)
|
|
|
|
return (
|
|
<Link
|
|
key={model.id}
|
|
href={model.href}
|
|
className='-mx-2 flex items-center gap-3 rounded-md px-2 transition-colors hover:bg-[var(--surface-hover)]'
|
|
>
|
|
<ModelLabel model={model} />
|
|
<div className='relative flex h-7 min-w-0 flex-1 items-center'>
|
|
<div
|
|
className='hidden h-full overflow-hidden rounded-r-[3px] sm:flex'
|
|
style={{ width: `${Math.max(totalPct, 3)}%` }}
|
|
>
|
|
<div
|
|
className='h-full'
|
|
style={{
|
|
width: `${inputPct}%`,
|
|
backgroundColor: color,
|
|
opacity: 0.8,
|
|
}}
|
|
/>
|
|
<div
|
|
className='h-full'
|
|
style={{
|
|
width: `${100 - inputPct}%`,
|
|
backgroundColor: color,
|
|
opacity: 0.35,
|
|
}}
|
|
/>
|
|
</div>
|
|
<span className='shrink-0 text-[11px] text-[var(--text-muted)] sm:ml-2.5 sm:text-xs'>
|
|
{formatPrice(input)} input / {formatPrice(output)} output
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function ContextWindowChart({ models }: ChartProps) {
|
|
const entries = models
|
|
.map((model) => ({
|
|
model,
|
|
value: model.contextWindow,
|
|
}))
|
|
.filter((e): e is { model: CatalogModel; value: number } => e.value !== null && e.value > 0)
|
|
.sort((a, b) => a.value - b.value)
|
|
|
|
const maxValue = entries.length > 0 ? Math.max(...entries.map((e) => e.value)) : 0
|
|
const data = { entries, maxValue }
|
|
|
|
if (data.entries.length === 0) return null
|
|
|
|
return (
|
|
<div className='flex flex-col gap-3'>
|
|
<div className='flex flex-col gap-1'>
|
|
<h3 className='text-[20px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[24px]'>
|
|
Context window
|
|
</h3>
|
|
<span className='text-[var(--text-muted)] text-sm leading-[150%] tracking-[0.02em]'>
|
|
Max tokens
|
|
</span>
|
|
</div>
|
|
|
|
<div className='flex flex-col gap-1.5'>
|
|
{data.entries.map(({ model, value }) => {
|
|
const pct = data.maxValue > 0 ? (value / data.maxValue) * 100 : 0
|
|
const color = getProviderColor(model.providerId)
|
|
|
|
return (
|
|
<Link
|
|
key={model.id}
|
|
href={model.href}
|
|
className='-mx-2 flex items-center gap-3 rounded-md px-2 transition-colors hover:bg-[var(--surface-hover)]'
|
|
>
|
|
<ModelLabel model={model} />
|
|
<div className='relative flex h-7 min-w-0 flex-1 items-center'>
|
|
<div
|
|
className='h-full rounded-r-[3px]'
|
|
style={{
|
|
width: `${Math.max(pct, 3)}%`,
|
|
backgroundColor: color,
|
|
opacity: 0.8,
|
|
}}
|
|
/>
|
|
<span className='ml-2.5 shrink-0 text-[11px] text-[var(--text-muted)] sm:text-xs'>
|
|
{formatTokenCount(value)}
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface ModelComparisonChartsProps {
|
|
models: CatalogModel[]
|
|
}
|
|
|
|
export function ModelComparisonCharts({ models }: ModelComparisonChartsProps) {
|
|
const comparisonModels = selectComparisonModels(models)
|
|
|
|
return (
|
|
<section aria-labelledby='comparison-heading'>
|
|
<div className='px-6 pt-10 pb-4'>
|
|
<h2
|
|
id='comparison-heading'
|
|
className='mb-2 text-[20px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[24px]'
|
|
>
|
|
Compare models
|
|
</h2>
|
|
<p className='text-[var(--text-muted)] text-sm leading-[150%] tracking-[0.02em]'>
|
|
Side-by-side comparison of top models across key metrics.
|
|
</p>
|
|
</div>
|
|
|
|
<div className='h-px w-full bg-[var(--border)]' />
|
|
|
|
<div className='flex flex-col sm:flex-row'>
|
|
<div className='flex-1 p-6'>
|
|
<StackedCostChart models={comparisonModels} />
|
|
</div>
|
|
<div className='h-px w-full bg-[var(--border)] sm:h-auto sm:w-px' />
|
|
<div className='flex-1 p-6'>
|
|
<ContextWindowChart models={comparisonModels} />
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|