TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import type { PolyModel } from "@/lib/ai/core/types";2import { sortWeightOf } from "./badges";34/**5 * URL slugs for public model pages: `/compare/gpt-5.5-vs-claude-sonnet-5`.6 * Dots are kept (they are the natural version separators in model ids); everything else7 * outside [a-z0-9] becomes a hyphen. `-vs-` is the pair delimiter.8 */9export const COMPARE_DELIMITER = "-vs-";10export const MAX_COMPARE = 4;1112export function slugify(input: string): string {13 return input14 .toLowerCase()15 .replace(/[^a-z0-9.]+/g, "-")16 .replace(/-{2,}/g, "-")17 .replace(/^[-.]+|[-.]+$/g, "");18}1920/** Canonical slug of a model: its id (e.g. `gpt-5.5`, `claude-sonnet-5`, `openai-gpt-5.5` for OpenRouter ids). */21export function modelSlug(m: Pick<PolyModel, "id">): string {22 return slugify(m.id);23}2425export function compareSlug(models: Pick<PolyModel, "id">[]): string {26 return models.map(modelSlug).join(COMPARE_DELIMITER);27}2829/** Split `a-vs-b[-vs-c]` into parts; null when malformed (fewer than 2 or more than MAX_COMPARE parts, empties). */30export function parseCompareSlug(slug: string): string[] | null {31 if (typeof slug !== "string") return null;32 const parts = slug.toLowerCase().split(COMPARE_DELIMITER).map((p) => p.trim());33 if (parts.length < 2 || parts.length > MAX_COMPARE) return null;34 if (parts.some((p) => !p || !/^[a-z0-9.-]+$/.test(p))) return null;35 return parts;36}3738function candidates(m: PolyModel): string[] {39 return [modelSlug(m), slugify(m.displayName), slugify(`${m.provider}-${m.id}`), slugify(`${m.provider} ${m.displayName}`)];40}4142/**43 * Resolve one slug part to a model. Matches id, display name, or provider-prefixed forms.44 * Prefers non-deprecated models on native providers (not OpenRouter), then the highest sort weight.45 */46export function findModelBySlug(models: PolyModel[], part: string): PolyModel | undefined {47 const p = part.toLowerCase();48 const hits = models.filter((m) => candidates(m).includes(p));49 if (!hits.length) return undefined;50 return hits.sort((a, b) => {51 const dep = Number(a.status === "deprecated") - Number(b.status === "deprecated");52 if (dep) return dep;53 const or = Number(a.provider === "openrouter") - Number(b.provider === "openrouter");54 if (or) return or;55 const exact = Number(modelSlug(b) === p) - Number(modelSlug(a) === p);56 if (exact) return exact;57 return sortWeightOf(b) - sortWeightOf(a);58 })[0];59}6061/** Resolve a full compare slug; null when any part is unknown or the same model appears twice. */62export function resolveCompareSlug(models: PolyModel[], slug: string): PolyModel[] | null {63 const parts = parseCompareSlug(slug);64 if (!parts) return null;65 const out: PolyModel[] = [];66 for (const part of parts) {67 const m = findModelBySlug(models, part);68 if (!m || out.some((x) => x.key === m.key)) return null;69 out.push(m);70 }71 return out;72}73