fix: Codex model catalog WYSIWYG and config consolidation

- Remove mergeCodexDefaultCatalogModelForSave implicit injection (P1)
  The model mapping table is now the single source of truth; no hidden
  entries are prepended on save.

- Sync first catalog row model into config.toml on save
  Ensures Codex default request model matches the table's first entry
  instead of retaining a stale template value.

- Remove API Format selector from CodexFormFields (P3)
  wire_api is always 'responses'; the selector confused users into
  thinking they were changing the upstream protocol. Only the 'Needs
  Local Routing' toggle remains.

- Add restart hint to model mapping i18n text (P2)
  model_catalog_json is loaded at Codex startup; users are now informed
  that a restart is needed after changes.

- Unify write_codex_live_with_catalog helper (P4)
  Replaces three scattered prepare+write call sites in config.rs,
  provider/live.rs, and proxy.rs with a single entry point.

- Clean up useCodexConfigState dead state (P3 follow-up)
  Remove codexModelName, codexContextWindow, codexAutoCompactLimit and
  their handlers/effects since no component consumes them after the UI
  consolidation.
This commit is contained in:
Jason
2026-05-20 10:24:06 +08:00
parent 90b7f25111
commit 791ced0034
13 changed files with 959 additions and 207 deletions
@@ -2,10 +2,9 @@ import { useState, useCallback, useEffect, useRef } from "react";
import {
extractCodexBaseUrl,
setCodexBaseUrl as setCodexBaseUrlInConfig,
extractCodexModelName,
setCodexModelName as setCodexModelNameInConfig,
} from "@/utils/providerConfigUtils";
import { normalizeTomlText } from "@/utils/textNormalization";
import type { CodexCatalogModel } from "@/types";
interface UseCodexConfigStateProps {
initialData?: {
@@ -22,11 +21,12 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
const [codexConfig, setCodexConfigState] = useState("");
const [codexApiKey, setCodexApiKey] = useState("");
const [codexBaseUrl, setCodexBaseUrl] = useState("");
const [codexModelName, setCodexModelName] = useState("");
const [codexCatalogModels, setCodexCatalogModels] = useState<
CodexCatalogModel[]
>([]);
const [codexAuthError, setCodexAuthError] = useState("");
const isUpdatingCodexBaseUrlRef = useRef(false);
const isUpdatingCodexModelNameRef = useRef(false);
// 初始化 Codex 配置(编辑模式)
useEffect(() => {
@@ -45,18 +45,38 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
: "";
setCodexConfigState(configStr);
const modelCatalog = (config as any).modelCatalog;
const rawCatalogModels = Array.isArray(modelCatalog?.models)
? modelCatalog.models
: [];
setCodexCatalogModels(
rawCatalogModels
.map((item: any) => ({
model: typeof item?.model === "string" ? item.model : "",
displayName:
typeof item?.displayName === "string"
? item.displayName
: typeof item?.display_name === "string"
? item.display_name
: "",
contextWindow:
typeof item?.contextWindow === "string" ||
typeof item?.contextWindow === "number"
? item.contextWindow
: typeof item?.context_window === "string" ||
typeof item?.context_window === "number"
? item.context_window
: "",
}))
.filter((item: CodexCatalogModel) => item.model.trim()),
);
// 提取 Base URL
const initialBaseUrl = extractCodexBaseUrl(configStr);
if (initialBaseUrl) {
setCodexBaseUrl(initialBaseUrl);
}
// 提取 Model Name
const initialModelName = extractCodexModelName(configStr);
if (initialModelName) {
setCodexModelName(initialModelName);
}
// 提取 API Key
try {
if (auth && typeof auth.OPENAI_API_KEY === "string") {
@@ -77,15 +97,6 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
setCodexBaseUrl((prev) => (prev === extracted ? prev : extracted));
}, [codexConfig]);
// 与 TOML 配置保持模型名称同步
useEffect(() => {
if (isUpdatingCodexModelNameRef.current) {
return;
}
const extracted = extractCodexModelName(codexConfig) || "";
setCodexModelName((prev) => (prev === extracted ? prev : extracted));
}, [codexConfig]);
// 获取 API Key(从 auth JSON
const getCodexAuthApiKey = useCallback((authString: string): string => {
try {
@@ -170,22 +181,7 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
[setCodexConfig],
);
// 处理 Codex Model Name 变化
const handleCodexModelNameChange = useCallback(
(modelName: string) => {
const trimmed = modelName.trim();
setCodexModelName(trimmed);
isUpdatingCodexModelNameRef.current = true;
setCodexConfig((prev) => setCodexModelNameInConfig(prev, trimmed));
setTimeout(() => {
isUpdatingCodexModelNameRef.current = false;
}, 0);
},
[setCodexConfig],
);
// 处理 config 变化(同步 Base URL 和 Model Name
// 处理 config 变化(同步 Base URL
const handleCodexConfigChange = useCallback(
(value: string) => {
// 归一化中文/全角/弯引号,避免 TOML 解析报错
@@ -198,35 +194,24 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
setCodexBaseUrl(extracted);
}
}
if (!isUpdatingCodexModelNameRef.current) {
const extractedModel = extractCodexModelName(normalized) || "";
if (extractedModel !== codexModelName) {
setCodexModelName(extractedModel);
}
}
},
[setCodexConfig, codexBaseUrl, codexModelName],
[setCodexConfig, codexBaseUrl],
);
// 重置配置(用于预设切换)
const resetCodexConfig = useCallback(
(auth: Record<string, unknown>, config: string) => {
(
auth: Record<string, unknown>,
config: string,
modelCatalogModels: CodexCatalogModel[] = [],
) => {
const authString = JSON.stringify(auth, null, 2);
setCodexAuth(authString);
setCodexConfig(config);
setCodexCatalogModels(modelCatalogModels);
const baseUrl = extractCodexBaseUrl(config);
if (baseUrl) {
setCodexBaseUrl(baseUrl);
}
const modelName = extractCodexModelName(config);
if (modelName) {
setCodexModelName(modelName);
} else {
setCodexModelName("");
}
setCodexBaseUrl(baseUrl || "");
// 提取 API Key
try {
@@ -247,13 +232,13 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
codexConfig,
codexApiKey,
codexBaseUrl,
codexModelName,
codexCatalogModels,
codexAuthError,
setCodexAuth,
setCodexConfig,
setCodexCatalogModels,
handleCodexApiKeyChange,
handleCodexBaseUrlChange,
handleCodexModelNameChange,
handleCodexConfigChange,
resetCodexConfig,
getCodexAuthApiKey,