TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import type { ModelStatus, ProviderId } from "@/lib/ai/core/types";23/**4 * Compact, public-safe model summary for the marketing site (`GET /api/public/models`).5 * Nothing user-specific, nothing secret: just the registry facts a visitor can see anyway6 * on /models.7 */8export interface PublicModelSummary {9 key: string;10 displayName: string;11 provider: ProviderId;12 contextTokens: number | null;13 inputPerMillion: number | null;14 outputPerMillion: number | null;15 status: ModelStatus;16 /** ISO date the registry first saw the model. */17 firstSeenAt: string;18}1920export interface CurationInput {21 key: string;22 displayName: string;23 provider: ProviderId;24 contextTokens?: number | null;25 inputPerMillion?: number | null;26 outputPerMillion?: number | null;27 status: ModelStatus;28 sortWeight: number;29 firstSeenAt: Date;30 hidden?: boolean;31}3233export const PUBLIC_MODELS_MAX = 40;34/** A model is "new" for the strip when the registry first saw it within this window. */35export const NEW_WINDOW_DAYS = 30;3637/**38 * Curated ordering for the live strip: newest releases first, then flagships (registry39 * `sortWeight`), interleaved per provider so nine providers show up in the first screen40 * instead of one provider's whole catalog. Pure and deterministic — unit tested.41 */42export function curatePublicModels(rows: CurationInput[], now: Date, max = PUBLIC_MODELS_MAX): PublicModelSummary[] {43 const cutoff = now.getTime() - NEW_WINDOW_DAYS * 86_400_000;44 const eligible = rows.filter((r) => !r.hidden && (r.status === "active" || r.status === "preview"));4546 const ranked = [...eligible].sort((a, b) => {47 const aNew = a.firstSeenAt.getTime() >= cutoff ? 1 : 0;48 const bNew = b.firstSeenAt.getTime() >= cutoff ? 1 : 0;49 if (aNew !== bNew) return bNew - aNew;50 if (a.sortWeight !== b.sortWeight) return b.sortWeight - a.sortWeight;51 const t = b.firstSeenAt.getTime() - a.firstSeenAt.getTime();52 if (t !== 0) return t;53 return a.displayName.localeCompare(b.displayName);54 });5556 // Round-robin across providers, preserving each provider's own ranking.57 const queues = new Map<ProviderId, CurationInput[]>();58 for (const r of ranked) {59 const q = queues.get(r.provider) ?? [];60 q.push(r);61 queues.set(r.provider, q);62 }63 const out: CurationInput[] = [];64 while (out.length < max && queues.size) {65 for (const [provider, q] of [...queues.entries()]) {66 const next = q.shift();67 if (next) out.push(next);68 if (!q.length) queues.delete(provider);69 if (out.length >= max) break;70 }71 }7273 return out.map((r) => ({74 key: r.key,75 displayName: r.displayName,76 provider: r.provider,77 contextTokens: r.contextTokens ?? null,78 inputPerMillion: r.inputPerMillion ?? null,79 outputPerMillion: r.outputPerMillion ?? null,80 status: r.status,81 firstSeenAt: r.firstSeenAt.toISOString(),82 }));83}8485/** `1M`, `400K`, `128K` — compact context window. */86export function formatContext(tokens: number | null | undefined): string {87 if (!tokens) return "—";88 if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(tokens % 1_000_000 === 0 ? 0 : 1)}M`;89 if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}K`;90 return String(tokens);91}9293/** `$1.25 / $10` — per-million input / output price, compact. */94export function formatPricePair(input: number | null | undefined, output: number | null | undefined): string | null {95 if (input == null && output == null) return null;96 const f = (n: number | null | undefined) => (n == null ? "—" : n === 0 ? "free" : Number.isInteger(n) ? `$${n}` : `$${n.toFixed(2)}`);97 return `${f(input)} / ${f(output)}`;98}99