/** * How a configured provider instance presents itself in a client: its label, * its accent color, and whether its icon carries the account badge. Shared by * web and mobile so both clients name and badge the same instance identically. * * @module providerInstanceDisplay */ import { defaultInstanceIdForDriver, PROVIDER_DISPLAY_NAMES, type ProviderDriverKind, type ServerProvider, } from "@t3tools/contracts"; /** * Title-case a slug: splits on `_` / `-` and camelCase boundaries, so * `codex_personal` becomes "Codex Personal" and `myCustomInstance` becomes * "My Custom Instance". */ function humanizeSlug(slug: string): string { return slug .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_-]+/g, " ") .trim() .replace(/\b\w/g, (char) => char.toUpperCase()); } /** * Resolve an instance's label with a tiered priority: * * 1. A snapshot `displayName` that differs from the driver's brand label — * the server has explicitly named this instance, trust it. * 2. For non-default instances, a humanized `instanceId` — the server fell * back to the driver-level label (the same for every instance of that * kind), so the slug is what keeps "Codex" and "Codex Personal" apart. * 3. The snapshot's `displayName`, or the brand label from contracts. */ export function resolveProviderInstanceDisplayName( snapshot: Pick, ): string { const trimmedSnapshotName = snapshot.displayName?.trim(); const kindLabel = PROVIDER_DISPLAY_NAMES[snapshot.driver] ?? humanizeSlug(snapshot.driver); if (trimmedSnapshotName && trimmedSnapshotName !== kindLabel) return trimmedSnapshotName; if (snapshot.instanceId !== defaultInstanceIdForDriver(snapshot.driver)) { const humanized = humanizeSlug(snapshot.instanceId); if (humanized.length > 0) return humanized; } return trimmedSnapshotName || kindLabel; } /** * Turn a display name into up to two initials for the badge: the first two * characters of a single word, or the first character of each of the first * two words. Iterates by code point so an emoji never splits into surrogates. */ export function providerInstanceInitials(label: string): string { const words = label.replace(/[_-]+/g, " ").split(/\s+/u).filter(Boolean); if (words.length === 0) return ""; if (words.length === 1) return Array.from(words[0]!).slice(0, 2).join("").toUpperCase(); return words .slice(0, 2) .map((word) => Array.from(word)[0]?.toUpperCase() ?? "") .join(""); } /** Only `#rrggbb` accent colors render; anything else is treated as unset. */ export function normalizeProviderAccentColor(value: string | undefined): string | undefined { const trimmed = value?.trim(); if (!trimmed) return undefined; return /^#[0-9a-fA-F]{6}$/u.test(trimmed) ? trimmed : undefined; } /** * Whether an instance's icon carries the account badge: accent color set, or * several instances sharing a driver so the brand glyph alone is ambiguous. * Shared by the composer trigger, the picker rail, and sidebar/thread rows. */ export function shouldShowInstanceBadge( entry: { readonly driverKind: ProviderDriverKind; readonly accentColor?: string | undefined }, entries: Iterable<{ readonly driverKind: ProviderDriverKind }>, ): boolean { if (entry.accentColor) return true; let sharedDriverCount = 0; for (const candidate of entries) { if (candidate.driverKind === entry.driverKind && ++sharedDriverCount > 1) return true; } return false; }