mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-26 01:27:03 +08:00
refactor(codex): move the model list out of the advanced options
The default model and the sub-agent model are picked from the model list, but the list sat collapsed inside the advanced options below them. Show it right after the connection fields, before the default model, and call it "Model list" as the Aggregation layout does. The advanced section no longer opens on its own for native Responses providers or cards with a list: that only existed to reveal the mapping. The default model drops its own fetch button when the list above already has one; Grok Build has no list and keeps it.
This commit is contained in:
1 parent
4f08989a66
commit
0dae89e25c
9 files changed
+137
-119
No files matched your search
@@ -616,7 +616,7 @@ export function CodexFormFields({
|
||||
const supportsEffort = codexChatReasoning.supportsEffort === true;
|
||||
|
||||
// 高级区在有任何可见配置时自动展开(仅折叠→展开,不会自动折叠):自定义 UA /
|
||||
// 请求覆盖 / 已填模型映射 / 原生 Responses(需维护 catalog)/ 已配置思考能力。
|
||||
// 请求覆盖 / 非默认的上游格式 / 已配置思考能力。模型列表在高级区外面,不算。
|
||||
const hasRequestOverrides = Boolean(
|
||||
localProxyHeadersOverride.trim() || localProxyBodyOverride.trim(),
|
||||
);
|
||||
@@ -624,8 +624,6 @@ export function CodexFormFields({
|
||||
isCopilotPreset ||
|
||||
!!customUserAgent ||
|
||||
hasRequestOverrides ||
|
||||
catalogModels.length > 0 ||
|
||||
effectiveApiFormat === "openai_responses" ||
|
||||
isAnthropicFormat ||
|
||||
supportsThinking ||
|
||||
supportsEffort ||
|
||||
@@ -1027,8 +1025,8 @@ export function CodexFormFields({
|
||||
seen.add(id);
|
||||
suggestions.push({
|
||||
id,
|
||||
ownedBy: t("codexConfig.modelMappingTitle", {
|
||||
defaultValue: "模型映射",
|
||||
ownedBy: t("codexConfig.modelListTitle", {
|
||||
defaultValue: "模型列表",
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -1827,8 +1825,45 @@ export function CodexFormFields({
|
||||
{apiKeySection}
|
||||
{endpointSection}
|
||||
|
||||
{/* 模型列表 —— 生成 Codex 的模型目录(model_catalog_json):/model 菜单列哪些模型,
|
||||
各自的窗口和思考等级。留空不生成。默认模型、子代理从这里选,所以排在它们前面。 */}
|
||||
{category !== "official" && canEditCatalog && (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FormLabel>
|
||||
{t("codexConfig.modelListTitle", {
|
||||
defaultValue: "模型列表",
|
||||
})}
|
||||
</FormLabel>
|
||||
{renderCatalogActionButtons(
|
||||
handleAddCatalogRow,
|
||||
t("codexConfig.addCatalogModel", {
|
||||
defaultValue: "手动添加",
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs leading-relaxed text-fg-2">
|
||||
{t("codexConfig.modelListHint", {
|
||||
defaultValue:
|
||||
"这些模型会出现在 Codex 的 /model 里,带上各自的上下文窗口和思考等级。修改后需要重启 Codex。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{fetchedModels.length > 0 && (
|
||||
<FetchedModelPicker
|
||||
models={fetchedModels}
|
||||
configuredModelIds={catalogRows.map((row) => row.model.trim())}
|
||||
onAdd={handleAddFetchedCatalogRows}
|
||||
/>
|
||||
)}
|
||||
{catalogRows.length > 0 && renderCatalogRows()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 默认模型 —— config.toml 顶层 model,Codex 启动时默认请求的模型。
|
||||
实时写回 TOML;留空则删行(有映射时保存回退为映射第一行)。 */}
|
||||
实时写回 TOML;留空则删行(有模型列表时保存回退为列表第一行)。 */}
|
||||
{category !== "official" && onModelChange && (
|
||||
<div className="space-y-1.5">
|
||||
<FormLabel htmlFor="codexDefaultModel">
|
||||
@@ -1850,23 +1885,26 @@ export function CodexFormFields({
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
<HoverTip content={t("providerForm.fetchModels")}>
|
||||
<Button
|
||||
aria-label={t("providerForm.fetchModels")}
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleFetchModels}
|
||||
disabled={isFetchingModels}
|
||||
className="shrink-0"
|
||||
>
|
||||
{isFetchingModels ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</HoverTip>
|
||||
{/* 有模型列表时用列表上的「获取模型列表」,这里不再放一个。 */}
|
||||
{!canEditCatalog && (
|
||||
<HoverTip content={t("providerForm.fetchModels")}>
|
||||
<Button
|
||||
aria-label={t("providerForm.fetchModels")}
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleFetchModels}
|
||||
disabled={isFetchingModels}
|
||||
className="shrink-0"
|
||||
>
|
||||
{isFetchingModels ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</HoverTip>
|
||||
)}
|
||||
{defaultModelSuggestions.length > 0 && (
|
||||
<ModelDropdown
|
||||
models={defaultModelSuggestions}
|
||||
@@ -1882,14 +1920,14 @@ export function CodexFormFields({
|
||||
})
|
||||
: t("codexConfig.defaultModelHint", {
|
||||
defaultValue:
|
||||
"Codex 默认请求的模型,随时可改,无需等待预设更新。留空且配置了模型映射时,默认使用映射第一行。",
|
||||
"Codex 默认请求的模型,随时可改,无需等待预设更新。留空时用模型列表第一行。",
|
||||
})}
|
||||
</p>
|
||||
{isDefaultModelOutsideCatalog && (
|
||||
<p className="flex flex-wrap items-center gap-x-2 text-xs leading-relaxed text-fg-2">
|
||||
{t("codexConfig.defaultModelNotInCatalog", {
|
||||
defaultValue:
|
||||
"该模型不在模型映射中,Codex 的 /model 菜单不会列出它(直接请求仍然有效)。",
|
||||
"该模型不在模型列表中,Codex 的 /model 菜单不会列出它(直接请求仍然有效)。",
|
||||
})}
|
||||
<Button
|
||||
type="button"
|
||||
@@ -1898,8 +1936,8 @@ export function CodexFormFields({
|
||||
className="h-auto p-0 text-xs"
|
||||
onClick={handleAddDefaultModelToCatalog}
|
||||
>
|
||||
{t("codexConfig.addToModelMapping", {
|
||||
defaultValue: "加入映射",
|
||||
{t("codexConfig.addToModelList", {
|
||||
defaultValue: "加入列表",
|
||||
})}
|
||||
</Button>
|
||||
</p>
|
||||
@@ -1982,7 +2020,7 @@ export function CodexFormFields({
|
||||
<p className="flex flex-wrap items-center gap-x-2 text-xs leading-relaxed text-fg-2">
|
||||
{t("codexConfig.subagentModelNotInCatalog", {
|
||||
defaultValue:
|
||||
"该模型不在模型映射中:Codex 拿不到它的上下文窗口;经路由转成 Chat / Anthropic 协议时会被换成默认模型。",
|
||||
"该模型不在模型列表中:Codex 拿不到它的上下文窗口;经路由转成 Chat / Anthropic 协议时会被换成默认模型。",
|
||||
})}
|
||||
{onCatalogModelsChange && (
|
||||
<Button
|
||||
@@ -1992,8 +2030,8 @@ export function CodexFormFields({
|
||||
className="h-auto p-0 text-xs"
|
||||
onClick={handleAddSubagentModelToCatalog}
|
||||
>
|
||||
{t("codexConfig.addToModelMapping", {
|
||||
defaultValue: "加入映射",
|
||||
{t("codexConfig.addToModelList", {
|
||||
defaultValue: "加入列表",
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
@@ -2002,7 +2040,7 @@ export function CodexFormFields({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 高级选项 —— 上游格式/模型映射/思考能力/自定义 UA;预设供应商通常无需展开 */}
|
||||
{/* 高级选项 —— 上游格式/思考能力/自定义 UA;预设供应商通常无需展开 */}
|
||||
{category !== "official" && (
|
||||
<Collapsible
|
||||
open={advancedExpanded}
|
||||
@@ -2035,7 +2073,7 @@ export function CodexFormFields({
|
||||
})
|
||||
: t("codexConfig.advancedSectionHint", {
|
||||
defaultValue:
|
||||
"包含上游格式、模型映射、思考能力与自定义 User-Agent。使用 Chat Completions 协议的供应商需开启路由接管才能使用。",
|
||||
"包含上游格式、思考能力与自定义 User-Agent。使用 Chat Completions / Anthropic Messages 协议的供应商需开启路由才能使用。",
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
@@ -2062,58 +2100,10 @@ export function CodexFormFields({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 模型映射 / 模型目录 —— 与「路由接管」解耦,常驻显示(可编辑即渲染)。
|
||||
填了才生成 catalog:Chat 模式生成兼容路由、原生 Responses 生成
|
||||
model-catalogs.json;留空则不生成。排在自定义 UA 之前。 */}
|
||||
{canEditCatalog && (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-4",
|
||||
(shouldShowSpeedTest || (isChatFormat && canEditReasoning)) &&
|
||||
"border-t border-border pt-3",
|
||||
)}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FormLabel>
|
||||
{t("codexConfig.modelMappingTitle", {
|
||||
defaultValue: "模型映射",
|
||||
})}
|
||||
</FormLabel>
|
||||
{renderCatalogActionButtons(
|
||||
handleAddCatalogRow,
|
||||
t("codexConfig.addCatalogModel", {
|
||||
defaultValue: "手动添加",
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs leading-relaxed text-fg-2">
|
||||
{t("codexConfig.modelMappingHint", {
|
||||
defaultValue:
|
||||
"选择模型角色后,CC Switch 会自动生成 Codex 兼容路由;菜单显示名可以填 DeepSeek、Kimi 等品牌模型,实际请求模型按右侧填写内容发送。",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{fetchedModels.length > 0 && (
|
||||
<FetchedModelPicker
|
||||
models={fetchedModels}
|
||||
configuredModelIds={catalogRows.map((row) =>
|
||||
row.model.trim(),
|
||||
)}
|
||||
onAdd={handleAddFetchedCatalogRows}
|
||||
/>
|
||||
)}
|
||||
{catalogRows.length > 0 && renderCatalogRows()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-3",
|
||||
(shouldShowSpeedTest ||
|
||||
(isChatFormat && canEditReasoning) ||
|
||||
canEditCatalog) &&
|
||||
(shouldShowSpeedTest || (isChatFormat && canEditReasoning)) &&
|
||||
"border-t border-border pt-3",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1912,17 +1912,15 @@
|
||||
"modelMetadataAdvancedHint": "Overrides Codex metadata for unknown models; clearing a field removes the matching config.toml entry.",
|
||||
"defaultModelLabel": "Default Model",
|
||||
"defaultModelPlaceholder": "e.g. gpt-5.6-sol",
|
||||
"defaultModelHint": "The model Codex requests by default; change it anytime without waiting for a preset update. If left empty while model mapping is configured, the first mapping row is used.",
|
||||
"defaultModelNotInCatalog": "This model is not in the model mapping, so the Codex /model menu won't list it (direct requests still work).",
|
||||
"defaultModelHint": "The model Codex requests by default; change it anytime without waiting for a preset update. If left empty, the first row of the model list is used.",
|
||||
"defaultModelNotInCatalog": "This model is not in the model list, so the Codex /model menu won't list it (direct requests still work).",
|
||||
"subagentModelLabel": "Sub-agent Model",
|
||||
"subagentModelPlaceholder": "Leave empty to follow the main model",
|
||||
"subagentReasoningEffortLabel": "Sub-agent reasoning effort",
|
||||
"subagentReasoningEffortDefault": "Default effort",
|
||||
"subagentModelHint": "The model and reasoning effort Codex sub-agents use by default. When empty, sub-agents use the main agent's; with only a model set, they use that model's default effort. Not applied in Aggregation mode, where sub-agents follow the main model.",
|
||||
"subagentModelNotInCatalog": "This model is not in the model mapping: Codex can't get its context window, and when routing converts requests to Chat / Anthropic it is replaced by the default model.",
|
||||
"addToModelMapping": "Add to mapping",
|
||||
"modelMappingTitle": "Model Mapping",
|
||||
"modelMappingHint": "Generates Codex model_catalog_json so /model can show these third-party model names; entries are saved exactly as listed. Codex must be restarted to refresh the model list after changes.",
|
||||
"subagentModelNotInCatalog": "This model is not in the model list: Codex can't get its context window, and when routing converts requests to Chat / Anthropic it is replaced by the default model.",
|
||||
"modelListHint": "These models appear in Codex's /model menu with their own context window and reasoning levels. Restart Codex after changes.",
|
||||
"addCatalogModel": "Add manually",
|
||||
"modelListTitle": "Models",
|
||||
"stackModelListHint": "These models appear in Codex's /model menu; picking one sends requests straight to this provider. Restart Codex after changes.",
|
||||
@@ -1951,7 +1949,7 @@
|
||||
"reasoningEffortHint": "Enable when the upstream supports thinking-depth control such as low/high/max. Enabling this also turns on thinking mode and converts Codex's reasoning.effort into the upstream Chat parameter.",
|
||||
"reasoningGroupTitle": "Reasoning Capability",
|
||||
"reasoningSectionHint": "Preset providers are configured automatically; custom providers are inferred from name/URL. Override manually only when auto-detection is wrong.",
|
||||
"advancedSectionHint": "Includes upstream format, model mapping, reasoning overrides and custom User-Agent. Providers using the Chat Completions protocol require routing takeover to be enabled.",
|
||||
"advancedSectionHint": "Includes upstream format, reasoning overrides and custom User-Agent. Providers using the Chat Completions / Anthropic Messages protocol require routing to be enabled.",
|
||||
"keyFieldsHint": "The endpoint, key, model, reasoning effort, context window and compatibility switches follow the provider; everything else is a global Codex setting that applies to every provider once saved.",
|
||||
"inactiveFieldsHint": "This provider still stores {{count}} settings that are not applied when switching. Click one to copy its TOML and paste it above if you need it; the stored values are kept.",
|
||||
"inactiveFieldCopied": "Copied"
|
||||
|
||||
@@ -1912,17 +1912,15 @@
|
||||
"modelMetadataAdvancedHint": "未知のモデル向けに Codex メタデータを上書きします。空にすると対応する config.toml 設定を削除します。",
|
||||
"defaultModelLabel": "デフォルトモデル",
|
||||
"defaultModelPlaceholder": "例: gpt-5.6-sol",
|
||||
"defaultModelHint": "Codex がデフォルトでリクエストするモデルです。プリセットの更新を待たずにいつでも変更できます。空欄のままモデルマッピングを設定している場合は、マッピングの先頭行が使われます。",
|
||||
"defaultModelNotInCatalog": "このモデルはモデルマッピングにないため、Codex の /model メニューには表示されません(直接リクエストは有効です)。",
|
||||
"defaultModelHint": "Codex がデフォルトでリクエストするモデルです。プリセットの更新を待たずにいつでも変更できます。空欄のときはモデル一覧の先頭行が使われます。",
|
||||
"defaultModelNotInCatalog": "このモデルはモデル一覧にないため、Codex の /model メニューには表示されません(直接リクエストは有効です)。",
|
||||
"subagentModelLabel": "サブエージェントモデル",
|
||||
"subagentModelPlaceholder": "空欄ならメインモデルに従う",
|
||||
"subagentReasoningEffortLabel": "サブエージェントの推論強度",
|
||||
"subagentReasoningEffortDefault": "デフォルト強度",
|
||||
"subagentModelHint": "Codex が起動するサブエージェントのデフォルトのモデルと推論強度です。空欄ならメインエージェントと同じものを使い、モデルだけ指定するとそのモデルのデフォルト強度を使います。集約モードでは適用されず、サブエージェントはメインモデルに従います。",
|
||||
"subagentModelNotInCatalog": "このモデルはモデルマッピングにありません。Codex はコンテキストウィンドウを取得できず、ルーティングで Chat / Anthropic 形式に変換する際はデフォルトモデルに置き換えられます。",
|
||||
"addToModelMapping": "マッピングに追加",
|
||||
"modelMappingTitle": "モデルマッピング",
|
||||
"modelMappingHint": "Codex model_catalog_json を生成し、/model コマンドでこれらの第三者モデル名を表示できるようにします。リストの内容はそのまま保存されます。変更後、モデルリストを更新するには Codex の再起動が必要です。",
|
||||
"subagentModelNotInCatalog": "このモデルはモデル一覧にありません。Codex はコンテキストウィンドウを取得できず、ルーティングで Chat / Anthropic 形式に変換する際はデフォルトモデルに置き換えられます。",
|
||||
"modelListHint": "これらのモデルは、それぞれのコンテキストウィンドウと思考レベル付きで Codex の /model に表示されます。変更後は Codex を再起動してください。",
|
||||
"addCatalogModel": "手動で追加",
|
||||
"modelListTitle": "モデル一覧",
|
||||
"stackModelListHint": "これらのモデルは Codex の /model に表示され、選ぶとリクエストがこのプロバイダーへ直接送られます。変更後は Codex を再起動してください。",
|
||||
@@ -1951,7 +1949,7 @@
|
||||
"reasoningEffortHint": "上流が low/high/max などの思考の深さの制御に対応している場合に有効化します。有効にすると思考モードも自動的にオンになり、Codex の reasoning.effort を上流の Chat パラメータに変換します。",
|
||||
"reasoningGroupTitle": "思考能力",
|
||||
"reasoningSectionHint": "プリセットの供給元は自動的に設定され、カスタム供給元は名前/URL から自動推論されます。自動識別が正しくない場合のみ手動で上書きしてください。",
|
||||
"advancedSectionHint": "上流フォーマット、モデルマッピング、思考能力、カスタム User-Agent の設定を含みます。Chat Completions プロトコルを使用するプロバイダーはルーティング引き継ぎの有効化が必要です。",
|
||||
"advancedSectionHint": "上流フォーマット、思考能力、カスタム User-Agent の設定を含みます。Chat Completions / Anthropic Messages プロトコルを使用するプロバイダーはルーティングの有効化が必要です。",
|
||||
"keyFieldsHint": "エンドポイント、キー、モデル、推論レベル、コンテキストウィンドウ、互換スイッチはプロバイダーに合わせて切り替わります。それ以外は Codex のグローバル設定で、保存するとすべてのプロバイダーに適用されます。",
|
||||
"inactiveFieldsHint": "このプロバイダーには切り替え時に適用されない設定が {{count}} 件保存されています。クリックすると TOML をコピーできるので、必要なら上に貼り付けてください。保存済みの値は削除されません。",
|
||||
"inactiveFieldCopied": "コピーしました"
|
||||
|
||||
@@ -1907,17 +1907,15 @@
|
||||
"promptCacheRoutingHint": "自動模式僅對已確認相容的上游傳送 prompt_cache_key;開啟可用於其他相容閘道,關閉可避免嚴格閘道因未知欄位回傳 400。只使用客戶端提供的穩定工作階段 ID。",
|
||||
"defaultModelLabel": "預設模型",
|
||||
"defaultModelPlaceholder": "例如: gpt-5.6-sol",
|
||||
"defaultModelHint": "Codex 預設請求的模型,隨時可改,無需等待軟體更新供應商範本。留空且設定了模型映射時,預設使用映射第一行。",
|
||||
"defaultModelNotInCatalog": "該模型不在模型映射中,Codex 的 /model 選單不會列出它(直接請求仍然有效)。",
|
||||
"defaultModelHint": "Codex 預設請求的模型,隨時可改,無需等待軟體更新供應商範本。留空時使用模型列表第一行。",
|
||||
"defaultModelNotInCatalog": "該模型不在模型列表中,Codex 的 /model 選單不會列出它(直接請求仍然有效)。",
|
||||
"subagentModelLabel": "子代理模型",
|
||||
"subagentModelPlaceholder": "留空則跟隨主模型",
|
||||
"subagentReasoningEffortLabel": "子代理推理強度",
|
||||
"subagentReasoningEffortDefault": "預設強度",
|
||||
"subagentModelHint": "Codex 派出的子代理預設使用的模型和推理強度。留空時子代理沿用主代理的;只填模型時使用該模型自己的預設強度。聚合模式下不生效,子代理跟隨主模型。",
|
||||
"subagentModelNotInCatalog": "該模型不在模型映射中:Codex 取得不到它的上下文視窗;經路由轉成 Chat / Anthropic 協定時會被換成預設模型。",
|
||||
"addToModelMapping": "加入映射",
|
||||
"modelMappingTitle": "模型映射",
|
||||
"modelMappingHint": "產生 Codex model_catalog_json,讓 /model 指令顯示這些第三方模型名;表中條目按填寫內容原樣儲存。修改後需要重新啟動 Codex 才能重新整理模型清單。",
|
||||
"subagentModelNotInCatalog": "該模型不在模型列表中:Codex 取得不到它的上下文視窗;經路由轉成 Chat / Anthropic 協定時會被換成預設模型。",
|
||||
"modelListHint": "這些模型會出現在 Codex 的 /model 裡,帶上各自的上下文視窗和思考等級。修改後需要重新啟動 Codex。",
|
||||
"addCatalogModel": "手動新增",
|
||||
"modelListTitle": "模型列表",
|
||||
"stackModelListHint": "這些模型會出現在 Codex 的 /model 裡,選中後請求直達這家。修改後需要重新啟動 Codex。",
|
||||
@@ -1952,7 +1950,7 @@
|
||||
"reasoningEffortHint": "上游支援 low/high/max 等思考深度控制時啟用。啟用後會自動啟用思考模式,並把 Codex 的 reasoning.effort 轉成上游 Chat 參數。",
|
||||
"reasoningGroupTitle": "思考能力",
|
||||
"reasoningSectionHint": "預設供應商已自動設定;自訂供應商會按名稱/位址自動推斷。僅當自動識別不準時才需手動覆寫。",
|
||||
"advancedSectionHint": "包含上游格式、模型映射、思考能力與自訂 User-Agent。使用 Chat Completions 協定的供應商需開啟路由接管才能使用。",
|
||||
"advancedSectionHint": "包含上游格式、思考能力與自訂 User-Agent。使用 Chat Completions / Anthropic Messages 協定的供應商需開啟路由才能使用。",
|
||||
"keyFieldsHint": "位址、Key、模型、推理檔位、上下文視窗和相容開關隨供應商切換;其餘是 Codex 全域設定,儲存後對所有供應商生效。",
|
||||
"inactiveFieldsHint": "這個供應商還保存著 {{count}} 個不隨切換生效的設定。點擊可複製它的 TOML,依需要貼到上方;供應商裡保存的原值不會刪除。",
|
||||
"inactiveFieldCopied": "已複製"
|
||||
|
||||
@@ -1912,17 +1912,15 @@
|
||||
"modelMetadataAdvancedHint": "用于未知模型的 Codex 元数据覆盖;清空字段会从 config.toml 删除对应配置。",
|
||||
"defaultModelLabel": "默认模型",
|
||||
"defaultModelPlaceholder": "例如: gpt-5.6-sol",
|
||||
"defaultModelHint": "Codex 默认请求的模型,随时可改,无需等待预设更新。留空且配置了模型映射时,默认使用映射第一行。",
|
||||
"defaultModelNotInCatalog": "该模型不在模型映射中,Codex 的 /model 菜单不会列出它(直接请求仍然有效)。",
|
||||
"defaultModelHint": "Codex 默认请求的模型,随时可改,无需等待预设更新。留空时用模型列表第一行。",
|
||||
"defaultModelNotInCatalog": "该模型不在模型列表中,Codex 的 /model 菜单不会列出它(直接请求仍然有效)。",
|
||||
"subagentModelLabel": "子代理模型",
|
||||
"subagentModelPlaceholder": "留空则跟随主模型",
|
||||
"subagentReasoningEffortLabel": "子代理推理强度",
|
||||
"subagentReasoningEffortDefault": "默认强度",
|
||||
"subagentModelHint": "Codex 派出的子代理默认用的模型和推理强度。留空时子代理沿用主代理的;只填模型时用该模型自己的默认强度。聚合模式下不生效,子代理跟随主模型。",
|
||||
"subagentModelNotInCatalog": "该模型不在模型映射中:Codex 拿不到它的上下文窗口;经路由转成 Chat / Anthropic 协议时会被换成默认模型。",
|
||||
"addToModelMapping": "加入映射",
|
||||
"modelMappingTitle": "模型映射",
|
||||
"modelMappingHint": "生成 Codex model_catalog_json,让 /model 命令显示这些第三方模型名;表中条目按填写内容原样保存。修改后需要重启 Codex 才能刷新模型列表。",
|
||||
"subagentModelNotInCatalog": "该模型不在模型列表中:Codex 拿不到它的上下文窗口;经路由转成 Chat / Anthropic 协议时会被换成默认模型。",
|
||||
"modelListHint": "这些模型会出现在 Codex 的 /model 里,带上各自的上下文窗口和思考等级。修改后需要重启 Codex。",
|
||||
"addCatalogModel": "手动添加",
|
||||
"modelListTitle": "模型列表",
|
||||
"stackModelListHint": "这些模型会出现在 Codex 的 /model 里,选中后请求直达这家。修改后需要重启 Codex。",
|
||||
@@ -1951,7 +1949,7 @@
|
||||
"reasoningEffortHint": "上游支持 low/high/max 等思考深度控制时启用。启用后会自动启用思考模式,并把 Codex 的 reasoning.effort 转成上游 Chat 参数。",
|
||||
"reasoningGroupTitle": "思考能力",
|
||||
"reasoningSectionHint": "预设供应商已自动配置;自定义供应商会按名称/地址自动推断。仅当自动识别不准时才需手动覆盖。",
|
||||
"advancedSectionHint": "包含上游格式、模型映射、思考能力与自定义 User-Agent。使用 Chat Completions 协议的供应商需开启路由接管才能使用。",
|
||||
"advancedSectionHint": "包含上游格式、思考能力与自定义 User-Agent。使用 Chat Completions / Anthropic Messages 协议的供应商需开启路由才能使用。",
|
||||
"keyFieldsHint": "地址、Key、模型、推理档位、上下文窗口和兼容开关随供应商切换;其余是 Codex 全局设置,保存后对所有供应商生效。",
|
||||
"inactiveFieldsHint": "这个供应商还保存着 {{count}} 个不随切换生效的设置。点击复制它的 TOML,按需粘贴到上方;供应商里保存的原值不会删除。",
|
||||
"inactiveFieldCopied": "已复制"
|
||||
|
||||
@@ -16,7 +16,7 @@ const FormShell = ({ children }: PropsWithChildren) => {
|
||||
return <Form {...form}>{children}</Form>;
|
||||
};
|
||||
|
||||
const NOT_IN_CATALOG = /该模型不在模型映射中:Codex 拿不到它的上下文窗口/;
|
||||
const NOT_IN_CATALOG = /该模型不在模型列表中:Codex 拿不到它的上下文窗口/;
|
||||
|
||||
function renderCodexForm(overrides: Partial<CodexFormFieldsProps> = {}) {
|
||||
const props: CodexFormFieldsProps = {
|
||||
@@ -82,7 +82,7 @@ describe("CodexFormFields sub-agent defaults", () => {
|
||||
expect(subagentInput()).toBeNull();
|
||||
});
|
||||
|
||||
it("offers to add a model outside the mapping, with the default model when the mapping is empty", () => {
|
||||
it("offers to add a model outside the list, with the default model when the list is empty", () => {
|
||||
const onCatalogModelsChange = vi.fn();
|
||||
renderCodexForm({
|
||||
apiFormat: "openai_chat",
|
||||
@@ -93,7 +93,7 @@ describe("CodexFormFields sub-agent defaults", () => {
|
||||
expect(screen.getByText(NOT_IN_CATALOG)).toBeTruthy();
|
||||
|
||||
onCatalogModelsChange.mockClear();
|
||||
fireEvent.click(screen.getByRole("button", { name: "加入映射" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "加入列表" }));
|
||||
const rows = onCatalogModelsChange.mock.calls.at(-1)?.[0] ?? [];
|
||||
expect(rows.map((row: { model: string }) => row.model)).toEqual([
|
||||
"gpt-a",
|
||||
@@ -101,8 +101,38 @@ describe("CodexFormFields sub-agent defaults", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("stays quiet for a native Responses provider without a mapping", () => {
|
||||
it("stays quiet for a native Responses provider without a list", () => {
|
||||
renderCodexForm({ subagentModel: "k3-mini", catalogModels: [] });
|
||||
expect(screen.queryByText(NOT_IN_CATALOG)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("CodexFormFields direct layout", () => {
|
||||
it("shows the model list outside the advanced section, before the default model", () => {
|
||||
renderCodexForm({
|
||||
catalogModels: [{ model: "gpt-a" }],
|
||||
onCatalogModelsChange: vi.fn(),
|
||||
});
|
||||
|
||||
const listTitle = screen.getByText("模型列表");
|
||||
expect(listTitle).toBeVisible();
|
||||
expect(screen.queryByText("上游格式")).toBeNull();
|
||||
const defaultModel = document.getElementById("codexDefaultModel")!;
|
||||
expect(
|
||||
listTitle.compareDocumentPosition(defaultModel) &
|
||||
Node.DOCUMENT_POSITION_FOLLOWING,
|
||||
).toBeTruthy();
|
||||
// 列表上已有「获取模型列表」,默认模型旁不再放第二个。
|
||||
expect(
|
||||
screen.getAllByRole("button", { name: /providerForm\.fetchModels/ }),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps the fetch button on the default model when there is no list (Grok Build)", () => {
|
||||
renderCodexForm();
|
||||
expect(screen.queryByText("模型列表")).toBeNull();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "providerForm.fetchModels" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -127,6 +127,9 @@ describe("GrokBuildProviderForm", () => {
|
||||
expect(container.querySelector("#grokbuild-context-window")).toHaveValue(
|
||||
500000,
|
||||
);
|
||||
// 默认的 Responses 格式不再把高级区自动展开。
|
||||
expect(screen.queryByText("上游格式")).toBeNull();
|
||||
fireEvent.click(screen.getByRole("button", { name: /高级选项/ }));
|
||||
expect(screen.getByText("上游格式")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ describe("Codex Copilot provider form", () => {
|
||||
expect(screen.getAllByDisplayValue("272000")).toHaveLength(4);
|
||||
expect(screen.getByDisplayValue("200000")).toBeVisible();
|
||||
expect(formatControl()).toHaveTextContent(formatLabels.auto);
|
||||
expect(screen.getByText("模型映射")).toBeVisible();
|
||||
expect(screen.getByText("模型列表")).toBeVisible();
|
||||
});
|
||||
|
||||
it.each<CodexCopilotApiFormat>(["auto", "openai_chat", "openai_responses"])(
|
||||
@@ -232,7 +232,7 @@ describe("Codex Copilot provider form", () => {
|
||||
async (format) => {
|
||||
const onSubmit = renderForm();
|
||||
await selectFormat(format);
|
||||
expect(screen.getByText("模型映射")).toBeVisible();
|
||||
expect(screen.getByText("模型列表")).toBeVisible();
|
||||
fireEvent.click(screen.getByRole("button", { name: "save" }));
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
||||
const saved = onSubmit.mock.calls[0][0];
|
||||
@@ -490,7 +490,7 @@ describe("Codex Copilot provider form", () => {
|
||||
it("keeps legacy cards automatic and shows mapping even with an empty catalog", () => {
|
||||
renderForm({ providerType: "github_copilot", apiFormat: "openai_chat" });
|
||||
expect(formatControl()).toHaveTextContent(formatLabels.auto);
|
||||
expect(screen.getByText("模型映射")).toBeVisible();
|
||||
expect(screen.getByText("模型列表")).toBeVisible();
|
||||
});
|
||||
|
||||
it("does not offer preset switching while editing an existing Copilot card", () => {
|
||||
@@ -543,6 +543,8 @@ requires_openai_auth = true`;
|
||||
screen.queryByTestId("selected-copilot-account"),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// 普通 Responses 卡的高级区默认折叠,上游格式在里面。
|
||||
fireEvent.click(screen.getByRole("button", { name: /高级选项/ }));
|
||||
await selectFormat("openai_chat");
|
||||
fireEvent.click(screen.getByRole("button", { name: "save" }));
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledTimes(1));
|
||||
@@ -584,7 +586,7 @@ requires_openai_auth = true`;
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /DeepSeek/ }));
|
||||
expect(formatControl()).toHaveTextContent("Responses(原生)");
|
||||
expect(screen.getByText("模型映射")).toBeVisible();
|
||||
expect(screen.getByText("模型列表")).toBeVisible();
|
||||
fireEvent.keyDown(formatControl(), { key: "ArrowDown" });
|
||||
expect(
|
||||
await screen.findByRole("option", { name: /Anthropic Messages/ }),
|
||||
@@ -592,6 +594,6 @@ requires_openai_auth = true`;
|
||||
fireEvent.click(screen.getByRole("option", { name: "Responses(原生)" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: /GitHub Copilot/ }));
|
||||
expect(formatControl()).toHaveTextContent(formatLabels.auto);
|
||||
expect(screen.getByText("模型映射")).toBeVisible();
|
||||
expect(screen.getByText("模型列表")).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -360,7 +360,8 @@ describe("ProviderForm Stack layout (Codex)", () => {
|
||||
it("没开 Stack 设置时仍是完整表单", () => {
|
||||
settingsState.enableStackMode = false;
|
||||
renderForm("codex", vi.fn());
|
||||
expect(screen.queryByText("模型列表")).toBeNull();
|
||||
// 完整表单也有模型列表,但说明是直连版的。
|
||||
expect(screen.queryByText(/选中后请求直达这家/)).toBeNull();
|
||||
expect(document.getElementById("codexDefaultModel")).not.toBeNull();
|
||||
expect(screen.getByTestId("codex-config-editor")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user