mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 10:57:07 +00:00
Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
221 lines
6.8 KiB
TypeScript
221 lines
6.8 KiB
TypeScript
import type { AgentFeature, AgentModelDefinition } from "@getpaseo/protocol/agent-types";
|
|
import { i18n } from "@/i18n/i18next";
|
|
|
|
export type ExplainedAgentControl = "mode" | "model" | "thinking";
|
|
export type FeatureHighlightColor = "blue" | "default" | "green" | "yellow";
|
|
export type AgentControlHintKey =
|
|
| "agentControls.hints.thinking"
|
|
| "agentControls.hints.model"
|
|
| "agentControls.hints.mode";
|
|
|
|
export function getAgentControlHintKey(selector: ExplainedAgentControl): AgentControlHintKey {
|
|
switch (selector) {
|
|
case "thinking":
|
|
return "agentControls.hints.thinking";
|
|
case "model":
|
|
return "agentControls.hints.model";
|
|
case "mode":
|
|
return "agentControls.hints.mode";
|
|
default:
|
|
throw new Error("unreachable");
|
|
}
|
|
}
|
|
|
|
export function normalizeModelId(modelId: string | null | undefined): string | null {
|
|
const normalized = typeof modelId === "string" ? modelId.trim() : "";
|
|
if (!normalized) {
|
|
return null;
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
export function getFeatureTooltip(feature: Pick<AgentFeature, "label" | "tooltip">): string {
|
|
return feature.tooltip ?? feature.label;
|
|
}
|
|
|
|
export function getFeatureHighlightColor(featureId: string): FeatureHighlightColor {
|
|
switch (featureId) {
|
|
case "fast_mode":
|
|
return "yellow";
|
|
case "auto_accept":
|
|
return "green";
|
|
case "plan_mode":
|
|
return "blue";
|
|
default:
|
|
return "default";
|
|
}
|
|
}
|
|
|
|
interface ControlLabelInput {
|
|
id: string;
|
|
label?: string | null;
|
|
}
|
|
|
|
function sentenceCase(value: string): string {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
|
|
}
|
|
|
|
function splitCompactLabel(value: string, splitHyphen: boolean): string {
|
|
const separatorPattern = splitHyphen ? /[_-]+/g : /_+/g;
|
|
|
|
return value
|
|
.replace(separatorPattern, " ")
|
|
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function formatControlLabel(option: ControlLabelInput, splitHyphen: boolean): string {
|
|
const rawLabel = (option.label ?? option.id).trim();
|
|
return sentenceCase(splitCompactLabel(rawLabel, splitHyphen));
|
|
}
|
|
|
|
export function formatAgentModeLabel(mode: ControlLabelInput): string {
|
|
return formatControlLabel(mode, mode.label == null);
|
|
}
|
|
|
|
export function formatThinkingOptionLabel(option: ControlLabelInput): string {
|
|
const rawLabel = (option.label ?? option.id).trim();
|
|
const compactId = option.id.replace(/[\s_-]+/g, "").toLowerCase();
|
|
const compactLabel = rawLabel.replace(/[\s_-]+/g, "").toLowerCase();
|
|
|
|
if (compactId === "xhigh" || compactLabel === "xhigh") {
|
|
return i18n.t("agentControls.thinking.extraHigh");
|
|
}
|
|
|
|
return formatControlLabel(option, true);
|
|
}
|
|
|
|
function findModelById(
|
|
models: AgentModelDefinition[] | null,
|
|
modelId: string | null,
|
|
): AgentModelDefinition | null {
|
|
if (!models || !modelId) {
|
|
return null;
|
|
}
|
|
return models.find((model) => model.id === modelId) ?? null;
|
|
}
|
|
|
|
function getFallbackModel(models: AgentModelDefinition[] | null): AgentModelDefinition | null {
|
|
return models?.find((model) => model.isDefault) ?? models?.[0] ?? null;
|
|
}
|
|
|
|
function resolvePreferredModelId(
|
|
runtimeSelectedModel: AgentModelDefinition | null,
|
|
normalizedConfiguredModelId: string | null,
|
|
normalizedRuntimeModelId: string | null,
|
|
): string | null {
|
|
return runtimeSelectedModel?.id ?? normalizedConfiguredModelId ?? normalizedRuntimeModelId;
|
|
}
|
|
|
|
function pickSelectedModel(
|
|
models: AgentModelDefinition[] | null,
|
|
preferredModelId: string | null,
|
|
fallbackModel: AgentModelDefinition | null,
|
|
): AgentModelDefinition | null {
|
|
if (!models || !preferredModelId) {
|
|
return fallbackModel;
|
|
}
|
|
return findModelById(models, preferredModelId) ?? fallbackModel;
|
|
}
|
|
|
|
function resolveThinkingId(
|
|
explicitThinkingOptionId: string | null | undefined,
|
|
selectedModel: AgentModelDefinition | null,
|
|
): string | null {
|
|
if (explicitThinkingOptionId && explicitThinkingOptionId !== "default") {
|
|
return explicitThinkingOptionId;
|
|
}
|
|
return selectedModel?.defaultThinkingOptionId ?? null;
|
|
}
|
|
|
|
type ThinkingOption = NonNullable<AgentModelDefinition["thinkingOptions"]>[number];
|
|
|
|
function resolveEffectiveThinking(
|
|
thinkingOptions: ThinkingOption[] | null,
|
|
resolvedThinkingId: string | null,
|
|
): ThinkingOption | null {
|
|
const selectedThinking =
|
|
thinkingOptions?.find((option) => option.id === resolvedThinkingId) ?? null;
|
|
return selectedThinking ?? thinkingOptions?.[0] ?? null;
|
|
}
|
|
|
|
function resolveModelDisplay(
|
|
selectedModel: AgentModelDefinition | null,
|
|
preferredModelId: string | null,
|
|
fallbackModel: AgentModelDefinition | null,
|
|
unknownModelLabel: string,
|
|
): { activeModelId: string | null; displayModel: string } {
|
|
return {
|
|
activeModelId: selectedModel?.id ?? preferredModelId ?? null,
|
|
displayModel:
|
|
selectedModel?.label ?? preferredModelId ?? fallbackModel?.label ?? unknownModelLabel,
|
|
};
|
|
}
|
|
|
|
function resolveThinkingDisplay(
|
|
effectiveThinking: ThinkingOption | null,
|
|
selectedThinkingId: string | null,
|
|
unknownThinkingLabel: string,
|
|
): string {
|
|
if (effectiveThinking) {
|
|
return formatThinkingOptionLabel(effectiveThinking);
|
|
}
|
|
|
|
if (selectedThinkingId) {
|
|
return formatThinkingOptionLabel({ id: selectedThinkingId });
|
|
}
|
|
|
|
return unknownThinkingLabel;
|
|
}
|
|
|
|
export function resolveAgentModelSelection(input: {
|
|
models: AgentModelDefinition[] | null;
|
|
runtimeModelId: string | null | undefined;
|
|
configuredModelId: string | null | undefined;
|
|
explicitThinkingOptionId: string | null | undefined;
|
|
}) {
|
|
const { models, runtimeModelId, configuredModelId, explicitThinkingOptionId } = input;
|
|
const normalizedRuntimeModelId = normalizeModelId(runtimeModelId);
|
|
const normalizedConfiguredModelId = normalizeModelId(configuredModelId);
|
|
|
|
const runtimeSelectedModel = findModelById(models, normalizedRuntimeModelId);
|
|
const preferredModelId = resolvePreferredModelId(
|
|
runtimeSelectedModel,
|
|
normalizedConfiguredModelId,
|
|
normalizedRuntimeModelId,
|
|
);
|
|
const fallbackModel = getFallbackModel(models);
|
|
const selectedModel = pickSelectedModel(models, preferredModelId, fallbackModel);
|
|
|
|
const { activeModelId, displayModel } = resolveModelDisplay(
|
|
selectedModel,
|
|
preferredModelId,
|
|
fallbackModel,
|
|
i18n.t("agentControls.model.unknown"),
|
|
);
|
|
|
|
const thinkingOptions = selectedModel?.thinkingOptions ?? null;
|
|
const resolvedThinkingId = resolveThinkingId(explicitThinkingOptionId, selectedModel);
|
|
const effectiveThinking = resolveEffectiveThinking(thinkingOptions, resolvedThinkingId);
|
|
const selectedThinkingId = effectiveThinking?.id ?? null;
|
|
const displayThinking = resolveThinkingDisplay(
|
|
effectiveThinking,
|
|
selectedThinkingId,
|
|
i18n.t("agentControls.thinking.unknown"),
|
|
);
|
|
|
|
return {
|
|
selectedModel,
|
|
activeModelId,
|
|
displayModel,
|
|
thinkingOptions,
|
|
selectedThinkingId,
|
|
displayThinking,
|
|
};
|
|
}
|