"use client";
import * as React from "react";
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { ProviderIcon } from "@/components/brand/provider-icon";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/misc";
import { useApi } from "@/lib/client/api";
import { usePrefersReducedMotion } from "@/lib/client/hooks";
import { formatContext, formatPricePair, NEW_WINDOW_DAYS, type PublicModelSummary } from "@/lib/marketing/public-models";
import { cn } from "@/lib/utils";
import { Container } from "./section";
interface Resp {
models: PublicModelSummary[];
total: number;
generatedAt: string;
}
function isNew(iso: string, now: number) {
return now - new Date(iso).getTime() < NEW_WINDOW_DAYS * 86_400_000;
}
function ModelChip({ m, now }: { m: PublicModelSummary; now: number }) {
const price = formatPricePair(m.inputPerMillion, m.outputPerMillion);
return (
{m.displayName}
{isNew(m.firstSeenAt, now) ? New : m.status === "preview" ? Preview : null}
{formatContext(m.contextTokens)}
{price ? {price} : null}
);
}
/**
* Live model strip. Fetches the public registry summary (cached 10 min server-side), renders a
* skeleton first, then a seamless marquee (content duplicated, paused on hover). Reduced motion
* or no data → a static wrapped grid / nothing.
*/
export function ModelStrip({ className }: { className?: string }) {
const { data, error, isLoading } = useApi("/api/public/models", { revalidateOnFocus: false, dedupingInterval: 600_000 });
const reduce = usePrefersReducedMotion();
const [now, setNow] = React.useState(0);
React.useEffect(() => {
// Wall clock read once on the client (never in render) so "New" badges are stable.
// eslint-disable-next-line react-hooks/set-state-in-effect
setNow(Date.now());
}, []);
const models = data?.models ?? [];
if (!isLoading && (error || models.length === 0)) return null;
return (
{data ? (
<>
{data.total} models in the registry · live from the catalog
>
) : (
"Loading the live model catalog…"
)}
Browse all
{isLoading ? (
{Array.from({ length: 10 }).map((_, i) => (
-
))}
) : reduce ? (
{models.slice(0, 16).map((m) => (
))}
) : (
)}
);
}