import { Trans, useLingui } from "@lingui/react/macro"; import type { ModelCatalogEntry, ModelCredential, ModelTier, ModelTierMapping, } from "@rakazo/contracts"; import { MODEL_TIERS, OPENAI_COMPATIBLE_PROVIDER_ID } from "@rakazo/contracts"; import { NativeSelect, NativeSelectOption } from "@rakazo/ui-web"; import { useId, useRef, useState } from "react"; import { connectedModelOptions } from "../lib/connected-models"; import { modelTierLabel } from "../lib/model-tier-label"; import { rpc } from "../lib/rpc"; function tierKey(model: { credentialId: string; modelId: string }) { return `${model.credentialId}::${model.modelId}`; } function parseTierKey(key: string) { const separator = key.indexOf("::"); if (separator <= 0) return null; return { credentialId: key.slice(0, separator), modelId: key.slice(separator + 2) }; } /** Maps each tier to one connected model; bots and helpers that ask for a tier run on it. */ export function ModelTierSettings({ credentials, catalog, }: { credentials: ModelCredential[]; catalog: ModelCatalogEntry[]; }) { const { t } = useLingui(); const ids = useId(); const [tiers, setTiers] = useState(null); const [serverModels, setServerModels] = useState>({}); const [error, setError] = useState(null); const saveQueueRef = useRef(Promise.resolve()); function loadTiers() { return rpc.models.tiers .list() .then(setTiers) .catch((err: unknown) => setError(err instanceof Error ? err.message : t`Could not load model settings`), ); } function load() { void loadTiers(); // A router endpoint serves several models; list them so each tier can pick one. for (const credential of credentials) { if (credential.provider !== OPENAI_COMPATIBLE_PROVIDER_ID) continue; void rpc.models .probeOpenAiCompatible({ credentialId: credential.id }) .then(({ models }) => setServerModels((current) => ({ ...current, [credential.id]: models })), ) // The saved model stays selectable when the server cannot be reached. .catch(() => undefined); } } function chooseModel(tier: ModelTier, key: string) { const model = parseTierKey(key); const provider = credentials.find((entry) => entry.id === model?.credentialId)?.provider; setError(null); setTiers((current) => [ ...(current ?? []).filter((entry) => entry.tier !== tier), ...(model && provider ? [{ tier, provider, ...model }] : []), ]); // Serialize saves so a slower earlier request cannot land after a newer choice. saveQueueRef.current = saveQueueRef.current.then(() => rpc.models.tiers.set({ tier, model }).then( () => undefined, (err: unknown) => { setError(err instanceof Error ? err.message : t`Could not save`); return loadTiers(); }, ), ); } if (!credentials.length) return null; const options = connectedModelOptions(credentials, catalog, serverModels); return (
{ if (event.currentTarget.open && !tiers) load(); }} > Tiers
{MODEL_TIERS.map((tier) => { const mapped = tiers?.find((entry) => entry.tier === tier); const value = mapped ? tierKey(mapped) : ""; return ( ); })}
{error ?

{error}

: null}
); }