SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.6 KB · 99 lines typescript
Raw Blame History
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