Files
pi-gui/ui/src/model-catalog.ts
T

113 lines
3.0 KiB
TypeScript

import type { Model } from "./workspace/types";
export type ModelIdentity = { provider: string; modelId: string };
export type ThinkingLevelOption = { value: string; label: string };
const DEFAULT_THINKING_LEVELS = [
"off",
"minimal",
"low",
"medium",
"high",
"xhigh",
] as const;
const OFF_THINKING_LEVEL: ThinkingLevelOption = { value: "off", label: "Off" };
function thinkingLevelLabel(level: string) {
if (level === "xhigh") return "XHigh";
return level[0].toUpperCase() + level.slice(1);
}
export function modelIdentity(model: Model): ModelIdentity | undefined {
if (!model.provider || !model.id) return undefined;
return { provider: model.provider, modelId: model.id };
}
export function modelIdentityValue(identity: ModelIdentity) {
return JSON.stringify([identity.provider, identity.modelId]);
}
export function modelIdentityFromValue(
value: string,
): ModelIdentity | undefined {
try {
const parsed: unknown = JSON.parse(value);
if (
!Array.isArray(parsed) ||
parsed.length !== 2 ||
!parsed.every((part) => typeof part === "string")
)
return undefined;
return { provider: parsed[0], modelId: parsed[1] };
} catch {
return undefined;
}
}
export function reconcileModelIdentity(
models: Model[],
preferred: ModelIdentity,
): ModelIdentity | undefined {
const identities = models
.map(modelIdentity)
.filter((identity): identity is ModelIdentity => !!identity);
const preferredValue = modelIdentityValue(preferred);
return (
identities.find(
(identity) => modelIdentityValue(identity) === preferredValue,
) ??
identities.find((identity) => identity.provider === preferred.provider) ??
identities[0]
);
}
export function thinkingLevelOptions(model?: Model): ThinkingLevelOption[] {
if (!model || model.reasoning === false) return [OFF_THINKING_LEVEL];
const map = model.thinkingLevelMap;
const levels = [
...DEFAULT_THINKING_LEVELS.filter((level) => {
if (level === "xhigh") return map?.xhigh !== undefined && map.xhigh !== null;
return map?.[level] !== null;
}),
...(map?.max == null ? [] : ["max"]),
];
return levels.length
? levels.map((value) => ({ value, label: thinkingLevelLabel(value) }))
: [OFF_THINKING_LEVEL];
}
export function reconcileThinkingLevel(
model: Model | undefined,
level: string,
) {
const options = thinkingLevelOptions(model);
return options.some((option) => option.value === level)
? level
: (options.find((option) => option.value === "off")?.value ??
options[0].value);
}
export function catalogModels(
models: Model[],
configured: ModelIdentity[],
): Model[] {
const available = models.filter((model) => model.provider && model.id);
const keys = new Set(
available
.map(modelIdentity)
.filter((identity): identity is ModelIdentity => !!identity)
.map(modelIdentityValue),
);
for (const identity of configured) {
if (!identity.provider || !identity.modelId) continue;
const key = modelIdentityValue(identity);
if (keys.has(key)) continue;
available.unshift({
provider: identity.provider,
id: identity.modelId,
});
keys.add(key);
}
return available;
}