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

129 lines
4.7 KiB
TypeScript

import Link from 'next/link'
import { getProviderColor } from '@/app/(landing)/models/components/constants'
import type { CatalogModel } from '@/app/(landing)/models/utils'
const SHORT_DATE_FORMAT = new Intl.DateTimeFormat('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
timeZone: 'UTC',
})
function formatShortDate(date: string): string {
try {
return SHORT_DATE_FORMAT.format(new Date(date))
} catch {
return date
}
}
interface ModelTimelineChartProps {
models: CatalogModel[]
providerId: string
}
const ITEM_WIDTH = 150
export function ModelTimelineChart({ models, providerId }: ModelTimelineChartProps) {
const entries = models
.filter((m) => m.releaseDate !== null)
.map((m) => ({
model: m,
date: new Date(m.releaseDate as string),
dateStr: m.releaseDate as string,
}))
.sort((a, b) => a.date.getTime() - b.date.getTime())
if (entries.length === 0) return null
const color = getProviderColor(providerId)
return (
<section aria-labelledby='timeline-heading'>
<div className='px-6 pt-10 pb-4'>
<h2
id='timeline-heading'
className='mb-2 text-[20px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] lg:text-[24px]'
>
Release timeline
</h2>
<p className='text-[var(--text-muted)] text-sm leading-[150%] tracking-[0.02em]'>
When each model was first publicly available.
</p>
</div>
<div className='overflow-x-auto px-6 pb-8'>
{/* Fixed height: top labels + line + bottom labels */}
<div
className='relative h-[140px]'
style={{ minWidth: `${entries.length * ITEM_WIDTH}px` }}
>
{/* Horizontal line - vertically centered */}
<div className='absolute top-[70px] right-0 left-0 h-px bg-[var(--border-1)]' />
{entries.map(({ model, dateStr }, i) => {
const left = i * ITEM_WIDTH + ITEM_WIDTH / 2
const isAbove = i % 2 === 0
return (
<Link
key={model.id}
href={model.href}
className='group absolute flex flex-col items-center'
style={{
left: `${left}px`,
width: `${ITEM_WIDTH}px`,
marginLeft: `${-ITEM_WIDTH / 2}px`,
top: 0,
height: '100%',
}}
>
<div
className='-translate-x-1/2 absolute top-[64px] left-1/2 size-[12px] rounded-full opacity-[0.85] transition-[opacity,transform] duration-150 group-hover:scale-110 group-hover:opacity-100'
style={{ backgroundColor: color }}
/>
{/* Stem + label above */}
{isAbove && (
<div className='-translate-x-1/2 absolute bottom-[74px] left-1/2 flex flex-col items-center'>
<div className='flex flex-col items-center gap-0.5 pb-1.5'>
<span className='whitespace-nowrap text-[12px] text-[var(--text-primary)] leading-none tracking-[-0.01em] transition-colors group-hover:text-[var(--text-primary)]'>
{model.displayName}
</span>
<span className='whitespace-nowrap text-[10px] text-[var(--text-muted)] leading-none'>
{formatShortDate(dateStr)}
</span>
</div>
<div
className='w-px'
style={{ height: '10px', backgroundColor: color, opacity: 0.2 }}
/>
</div>
)}
{/* Stem + label below */}
{!isAbove && (
<div className='-translate-x-1/2 absolute top-[75px] left-1/2 flex flex-col items-center'>
<div
className='w-px'
style={{ height: '10px', backgroundColor: color, opacity: 0.2 }}
/>
<div className='flex flex-col items-center gap-0.5 pt-1.5'>
<span className='whitespace-nowrap text-[12px] text-[var(--text-primary)] leading-none tracking-[-0.01em] transition-colors group-hover:text-[var(--text-primary)]'>
{model.displayName}
</span>
<span className='whitespace-nowrap text-[10px] text-[var(--text-muted)] leading-none'>
{formatShortDate(dateStr)}
</span>
</div>
</div>
)}
</Link>
)
})}
</div>
</div>
</section>
)
}