Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@ label. A management client can set or reset one label with
`{ "modelId": "grok-4.6", "displayName": "Grok 4.6" }`; send `displayName: null` to reset it.
Provider `PATCH` does not edit this map. Use this dedicated `PUT` endpoint to change or remove labels.

The dashboard exposes the same durable setting on **Models**. Expand the provider, find a
discovered model, and choose **Name**. The dialog keeps the exact `provider/model` selector visible
while you save a friendly label. Choose **Reset name** to return to provider metadata or the normal
selector fallback. **Name** changes presentation only; the separate alias pencil changes the
short routing alias and is not a display name editor. Native OpenAI and custom model rows keep their
existing controls.

## Codex catalog and root `config.toml` settings

These settings belong in the root of `$CODEX_HOME/config.toml`, alongside
Expand Down
158 changes: 158 additions & 0 deletions gui/src/components/ModelDisplayNameDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { useEffect, useId, useRef, useState } from "react";
import { useT, type TKey } from "../i18n/shared";
import {
modelDisplayNameValidationKey,
type ModelRow,
} from "../pages/models-shared";

interface ModelDisplayNameDialogProps {
model: ModelRow;
saving: boolean;
requestError: string | null;
onSave: (displayName: string) => void;
onReset: () => void;
onClose: () => void;
}

const SOURCE_LABEL_KEYS: Record<NonNullable<ModelRow["displayNameSource"]>, TKey> = {
operator: "models.displayNameSourceOperator",
provider: "models.displayNameSourceProvider",
fallback: "models.displayNameSourceFallback",
};

export default function ModelDisplayNameDialog({
model,
saving,
requestError,
onSave,
onReset,
onClose,
}: ModelDisplayNameDialogProps) {
const t = useT();
const dialogRef = useRef<HTMLDialogElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const wasSavingRef = useRef(saving);
const titleId = useId();
const helpId = useId();
const errorId = useId();
const [draft, setDraft] = useState(model.displayNameOverride ?? "");
const [validationKey, setValidationKey] = useState<TKey | null>(null);

useEffect(() => {
const dialog = dialogRef.current;
if (dialog && !dialog.open) dialog.showModal();
inputRef.current?.focus();
return () => { if (dialog?.open) dialog.close(); };
}, []);

useEffect(() => {
const saveFailed = wasSavingRef.current && !saving && Boolean(requestError);
wasSavingRef.current = saving;
if (saveFailed) inputRef.current?.focus();
}, [requestError, saving]);

const validationError = validationKey ? t(validationKey) : null;
const visibleError = validationError ?? requestError;
const sourceKey = model.displayNameSource
? SOURCE_LABEL_KEYS[model.displayNameSource]
: "models.displayNameSourceFallback";

const requestClose = () => {
if (!saving) onClose();
};

return (
<dialog
ref={dialogRef}
className="modal-overlay"
aria-labelledby={titleId}
onCancel={event => {
event.preventDefault();
requestClose();
}}
>
<button
type="button"
className="modal-backdrop-dismiss"
aria-label={t("common.close")}
tabIndex={-1}
disabled={saving}
onClick={requestClose}
/>
<form
className="modal-card model-display-name-dialog"
role="document"
onClick={event => event.stopPropagation()}
onSubmit={event => {
event.preventDefault();
if (saving) return;
const nextValidationKey = modelDisplayNameValidationKey(draft);
setValidationKey(nextValidationKey);
if (!nextValidationKey) onSave(draft.trim());
}}
>
<div className="modal-head">
<h3 id={titleId}>{t("models.displayNameTitle")}</h3>
<button type="button" className="btn btn-ghost btn-sm" disabled={saving} onClick={requestClose}>
{t("common.close")}
</button>
</div>

<div className="model-display-name-identity">
<span className="muted text-label">{t("models.displayNameModelId")}</span>
<code className="mono text-control">{model.namespaced}</code>
</div>

<div className="model-display-name-current">
<span className="muted text-label">{t("models.displayNameCurrent")}</span>
<strong>{model.displayName ?? model.namespaced}</strong>
<span className="models-chip muted text-caption">{t(sourceKey)}</span>
</div>

<label className="field-label" htmlFor={`${titleId}-input`}>
{t("models.displayNameField")}
</label>
<input
ref={inputRef}
id={`${titleId}-input`}
className="input"
value={draft}
maxLength={129}
placeholder={t("models.displayNamePlaceholder")}
aria-describedby={`${helpId}${visibleError ? ` ${errorId}` : ""}`}
aria-invalid={validationError ? true : undefined}
disabled={saving}
onChange={event => {
setDraft(event.target.value);
setValidationKey(null);
}}
/>
<p id={helpId} className="muted small">
{t("models.displayNameHelp", { model: model.namespaced })}
</p>
{visibleError && (
<p id={errorId} className="model-display-name-error" role="alert">
{visibleError}
</p>
)}

<div className="modal-actions">
<button
type="button"
className="btn btn-ghost btn-sm"
disabled={saving || !model.displayNameOverride}
onClick={onReset}
>
{t("models.displayNameReset")}
</button>
<button type="button" className="btn btn-sm" disabled={saving} onClick={requestClose}>
{t("common.cancel")}
</button>
<button type="submit" className="btn btn-primary btn-sm" disabled={saving}>
{saving ? t("common.saving") : t("common.save")}
</button>
</div>
</form>
</dialog>
);
}
19 changes: 19 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2548,4 +2548,23 @@ export const de: Record<TKey, string> = {
"integrations.cursor.colReasoning": "Reasoning-Aufwand",
"integrations.cursor.colContext": "Kontext",
"integrations.cursor.guide": "Anleitung zu Cursor Private Inference öffnen",
"models.displayNameAction": "Name",
"models.displayNameActionLabel": "Anzeigenamen für {model} bearbeiten",
"models.displayNameTitle": "Anzeigename",
"models.displayNameModelId": "Modell-ID",
"models.displayNameCurrent": "Aktueller Name",
"models.displayNameSourceOperator": "Ihr Name",
"models.displayNameSourceProvider": "Anbietername",
"models.displayNameSourceFallback": "Modell-ID als Ersatz",
"models.displayNameField": "Anzeigename",
"models.displayNamePlaceholder": "z. B. Grok 4.6",
"models.displayNameHelp": "Ändert nur die Anzeige. Das Routing bleibt {model}.",
"models.displayNameReset": "Name zurücksetzen",
"models.displayNameSaved": "Anzeigename gespeichert",
"models.displayNameResetDone": "Anzeigename zurückgesetzt",
"models.displayNameSaveFailed": "Anzeigename konnte nicht gespeichert werden",
"models.displayNameRequired": "Geben Sie einen Anzeigenamen ein oder verwenden Sie Name zurücksetzen.",
"models.displayNameTooLong": "Der Anzeigename darf höchstens 128 Zeichen lang sein.",
"models.displayNameNoSlash": "Der Anzeigename darf kein / enthalten.",
"models.displayNameNoControl": "Der Anzeigename darf keine Steuerzeichen enthalten.",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2582,6 +2582,25 @@ export const en = {
"usage.scope.machine": "This machine",
"usage.scope.hub": "Hub-wide",
"usage.hubOffline": "Hub usage is unavailable. Local usage was not substituted.",
"models.displayNameAction": "Name",
"models.displayNameActionLabel": "Edit friendly name for {model}",
"models.displayNameTitle": "Friendly name",
"models.displayNameModelId": "Model ID",
"models.displayNameCurrent": "Current name",
"models.displayNameSourceOperator": "Your name",
"models.displayNameSourceProvider": "Provider name",
"models.displayNameSourceFallback": "Model ID fallback",
"models.displayNameField": "Friendly name",
"models.displayNamePlaceholder": "e.g. Grok 4.6",
"models.displayNameHelp": "Changes presentation only. Routing remains {model}.",
"models.displayNameReset": "Reset name",
"models.displayNameSaved": "Display name saved",
"models.displayNameResetDone": "Display name reset",
"models.displayNameSaveFailed": "Failed to save display name",
"models.displayNameRequired": "Enter a friendly name, or use Reset name.",
"models.displayNameTooLong": "Friendly name must be 128 characters or fewer.",
"models.displayNameNoSlash": "Friendly name cannot contain /.",
"models.displayNameNoControl": "Friendly name cannot contain control characters.",
} as const;

export type TKey = keyof typeof en;
19 changes: 19 additions & 0 deletions gui/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2535,4 +2535,23 @@ export const fr: Record<TKey, string> = {
"integrations.cursor.colReasoning": "Raisonnement",
"integrations.cursor.colContext": "Contexte",
"integrations.cursor.guide": "Ouvrir le guide de Cursor Private Inference",
"models.displayNameAction": "Nom",
"models.displayNameActionLabel": "Modifier le nom d’affichage de {model}",
"models.displayNameTitle": "Nom d’affichage",
"models.displayNameModelId": "ID du modèle",
"models.displayNameCurrent": "Nom actuel",
"models.displayNameSourceOperator": "Votre nom d’affichage",
"models.displayNameSourceProvider": "Nom du fournisseur",
"models.displayNameSourceFallback": "ID du modèle par défaut",
"models.displayNameField": "Nom d’affichage",
"models.displayNamePlaceholder": "p. ex. Grok 4.6",
"models.displayNameHelp": "Modifie uniquement l’affichage. Le routage reste {model}.",
"models.displayNameReset": "Réinitialiser le nom",
"models.displayNameSaved": "Nom d’affichage enregistré",
"models.displayNameResetDone": "Nom d’affichage réinitialisé",
"models.displayNameSaveFailed": "Impossible d’enregistrer le nom d’affichage",
"models.displayNameRequired": "Saisissez un nom d’affichage ou utilisez Réinitialiser le nom.",
"models.displayNameTooLong": "Le nom d’affichage doit contenir au maximum 128 caractères.",
"models.displayNameNoSlash": "Le nom d’affichage ne peut pas contenir /.",
"models.displayNameNoControl": "Le nom d’affichage ne peut pas contenir de caractères de contrôle.",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2569,4 +2569,23 @@ export const ja: Record<TKey, string> = {
"integrations.cursor.colReasoning": "推論",
"integrations.cursor.colContext": "コンテキスト",
"integrations.cursor.guide": "Cursor Private Inference のガイドを開く",
"models.displayNameAction": "名前",
"models.displayNameActionLabel": "{model} の表示名を編集",
"models.displayNameTitle": "表示名",
"models.displayNameModelId": "モデル ID",
"models.displayNameCurrent": "現在の名前",
"models.displayNameSourceOperator": "設定した名前",
"models.displayNameSourceProvider": "プロバイダー名",
"models.displayNameSourceFallback": "モデル ID の既定値",
"models.displayNameField": "表示名",
"models.displayNamePlaceholder": "例: Grok 4.6",
"models.displayNameHelp": "表示だけを変更します。ルーティングは {model} のままです。",
"models.displayNameReset": "名前をリセット",
"models.displayNameSaved": "表示名を保存しました",
"models.displayNameResetDone": "表示名をリセットしました",
"models.displayNameSaveFailed": "表示名を保存できませんでした",
"models.displayNameRequired": "表示名を入力するか、名前をリセットしてください。",
"models.displayNameTooLong": "表示名は 128 文字以内にしてください。",
"models.displayNameNoSlash": "表示名に / は使用できません。",
"models.displayNameNoControl": "表示名に制御文字は使用できません。",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2570,4 +2570,23 @@ export const ko: Record<TKey, string> = {
"integrations.cursor.colReasoning": "추론",
"integrations.cursor.colContext": "컨텍스트",
"integrations.cursor.guide": "Cursor Private Inference 가이드 열기",
"models.displayNameAction": "이름",
"models.displayNameActionLabel": "{model}의 표시 이름 편집",
"models.displayNameTitle": "표시 이름",
"models.displayNameModelId": "모델 ID",
"models.displayNameCurrent": "현재 이름",
"models.displayNameSourceOperator": "운영자 지정 이름",
"models.displayNameSourceProvider": "프로바이더 제공 이름",
"models.displayNameSourceFallback": "모델 ID 기본값",
"models.displayNameField": "표시 이름",
"models.displayNamePlaceholder": "예: Grok 4.6",
"models.displayNameHelp": "표시 방식만 변경합니다. 라우팅은 {model}로 유지됩니다.",
"models.displayNameReset": "이름 초기화",
"models.displayNameSaved": "표시 이름이 저장되었습니다",
"models.displayNameResetDone": "표시 이름이 초기화되었습니다",
"models.displayNameSaveFailed": "표시 이름을 저장하지 못했습니다",
"models.displayNameRequired": "표시 이름을 입력하거나 이름 초기화를 사용하세요.",
"models.displayNameTooLong": "표시 이름은 128자 이하여야 합니다.",
"models.displayNameNoSlash": "표시 이름에 /를 사용할 수 없습니다.",
"models.displayNameNoControl": "표시 이름에 제어 문자를 사용할 수 없습니다.",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2571,4 +2571,23 @@ export const ru: Record<TKey, string> = {
"integrations.cursor.colReasoning": "Рассуждения",
"integrations.cursor.colContext": "Контекст",
"integrations.cursor.guide": "Открыть руководство по Cursor Private Inference",
"models.displayNameAction": "Имя",
"models.displayNameActionLabel": "Изменить понятное имя для {model}",
"models.displayNameTitle": "Понятное имя",
"models.displayNameModelId": "ID модели",
"models.displayNameCurrent": "Текущее имя",
"models.displayNameSourceOperator": "Ваше имя",
"models.displayNameSourceProvider": "Имя провайдера",
"models.displayNameSourceFallback": "ID модели по умолчанию",
"models.displayNameField": "Понятное имя",
"models.displayNamePlaceholder": "например, Grok 4.6",
"models.displayNameHelp": "Меняет только отображение. Маршрут остаётся {model}.",
"models.displayNameReset": "Сбросить имя",
"models.displayNameSaved": "Понятное имя сохранено",
"models.displayNameResetDone": "Понятное имя сброшено",
"models.displayNameSaveFailed": "Не удалось сохранить понятное имя",
"models.displayNameRequired": "Введите понятное имя или используйте Сбросить имя.",
"models.displayNameTooLong": "Понятное имя должно содержать не более 128 символов.",
"models.displayNameNoSlash": "Понятное имя не может содержать /.",
"models.displayNameNoControl": "Понятное имя не может содержать управляющие символы.",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2571,4 +2571,23 @@ export const tr: Record<TKey, string> = {
"integrations.cursor.colReasoning": "Akıl yürütme",
"integrations.cursor.colContext": "Bağlam",
"integrations.cursor.guide": "Cursor Private Inference kılavuzunu aç",
"models.displayNameAction": "Ad",
"models.displayNameActionLabel": "{model} için görünen adı düzenle",
"models.displayNameTitle": "Görünen ad",
"models.displayNameModelId": "Model kimliği",
"models.displayNameCurrent": "Geçerli ad",
"models.displayNameSourceOperator": "Sizin adınız",
"models.displayNameSourceProvider": "Sağlayıcı adı",
"models.displayNameSourceFallback": "Model kimliği varsayılanı",
"models.displayNameField": "Görünen ad",
"models.displayNamePlaceholder": "örn. Grok 4.6",
"models.displayNameHelp": "Yalnızca görünümü değiştirir. Yönlendirme {model} olarak kalır.",
"models.displayNameReset": "Adı sıfırla",
"models.displayNameSaved": "Görünen ad kaydedildi",
"models.displayNameResetDone": "Görünen ad sıfırlandı",
"models.displayNameSaveFailed": "Görünen ad kaydedilemedi",
"models.displayNameRequired": "Bir görünen ad girin veya Adı sıfırla seçeneğini kullanın.",
"models.displayNameTooLong": "Görünen ad en fazla 128 karakter olabilir.",
"models.displayNameNoSlash": "Görünen ad / içeremez.",
"models.displayNameNoControl": "Görünen ad denetim karakterleri içeremez.",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2533,4 +2533,23 @@ export const zhTW: Record<TKey, string> = {
"integrations.cursor.colReasoning": "推理",
"integrations.cursor.colContext": "上下文",
"integrations.cursor.guide": "開啟 Cursor Private Inference 指南",
"models.displayNameAction": "名稱",
"models.displayNameActionLabel": "編輯 {model} 的友善名稱",
"models.displayNameTitle": "友善名稱",
"models.displayNameModelId": "模型 ID",
"models.displayNameCurrent": "目前名稱",
"models.displayNameSourceOperator": "你的名稱",
"models.displayNameSourceProvider": "供應商名稱",
"models.displayNameSourceFallback": "模型 ID 預設值",
"models.displayNameField": "友善名稱",
"models.displayNamePlaceholder": "例如 Grok 4.6",
"models.displayNameHelp": "只變更顯示方式。路由仍為 {model}。",
"models.displayNameReset": "重設名稱",
"models.displayNameSaved": "友善名稱已儲存",
"models.displayNameResetDone": "友善名稱已重設",
"models.displayNameSaveFailed": "無法儲存友善名稱",
"models.displayNameRequired": "請輸入友善名稱,或使用重設名稱。",
"models.displayNameTooLong": "友善名稱不能超過 128 個字元。",
"models.displayNameNoSlash": "友善名稱不能包含 /。",
"models.displayNameNoControl": "友善名稱不能包含控制字元。",
};
19 changes: 19 additions & 0 deletions gui/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2569,4 +2569,23 @@ export const zh: Record<TKey, string> = {
"integrations.cursor.colReasoning": "推理",
"integrations.cursor.colContext": "上下文",
"integrations.cursor.guide": "打开 Cursor Private Inference 指南",
"models.displayNameAction": "名称",
"models.displayNameActionLabel": "编辑 {model} 的友好名称",
"models.displayNameTitle": "友好名称",
"models.displayNameModelId": "模型 ID",
"models.displayNameCurrent": "当前名称",
"models.displayNameSourceOperator": "你的名称",
"models.displayNameSourceProvider": "提供商名称",
"models.displayNameSourceFallback": "模型 ID 默认值",
"models.displayNameField": "友好名称",
"models.displayNamePlaceholder": "例如 Grok 4.6",
"models.displayNameHelp": "仅更改显示方式。路由仍为 {model}。",
"models.displayNameReset": "重置名称",
"models.displayNameSaved": "友好名称已保存",
"models.displayNameResetDone": "友好名称已重置",
"models.displayNameSaveFailed": "无法保存友好名称",
"models.displayNameRequired": "请输入友好名称,或使用重置名称。",
"models.displayNameTooLong": "友好名称不能超过 128 个字符。",
"models.displayNameNoSlash": "友好名称不能包含 /。",
"models.displayNameNoControl": "友好名称不能包含控制字符。",
};
Loading
Loading