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%
2.8 KB · 73 lines typescript
Raw Blame History
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