Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

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>
)
}