fix:Add a new vendor page, API endpoint, and model name. (#1155)

* fix:Add a new vendor page, API endpoint, and model name. Fix the bug where, after entering characters, line breaks cannot be fully deleted.

* fix: add missing i18n key codexConfig.modelNameHint for zh/en/ja

---------

Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
wugeer
2026-03-07 22:53:44 +08:00
committed by GitHub
parent 33d5f6985d
commit fb8996d19c
8 changed files with 101 additions and 34 deletions
@@ -117,9 +117,13 @@ export function CodexFormFields({
className="w-full px-3 py-2 border border-border-default bg-background text-foreground rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 transition-colors"
/>
<p className="text-xs text-muted-foreground">
{t("codexConfig.modelNameHint", {
defaultValue: "指定使用的模型,将自动更新到 config.toml 中",
})}
{modelName.trim()
? t("codexConfig.modelNameHint", {
defaultValue: "指定使用的模型,将自动更新到 config.toml 中",
})
: t("providerForm.modelHint", {
defaultValue: "💡 留空将使用供应商的默认模型",
})}
</p>
</div>
)}
@@ -60,10 +60,8 @@ export function useBaseUrlState({
if (!codexConfig) return;
const extracted = extractCodexBaseUrl(codexConfig) || "";
if (extracted !== codexBaseUrl) {
setCodexBaseUrl(extracted);
}
}, [appType, category, codexConfig, codexBaseUrl]);
setCodexBaseUrl((prev) => (prev === extracted ? prev : extracted));
}, [appType, category, codexConfig]);
// 从Claude配置同步到 stateGemini
useEffect(() => {
@@ -116,7 +114,7 @@ export function useBaseUrlState({
const sanitized = url.trim();
setCodexBaseUrl(sanitized);
if (!sanitized || !onCodexConfigChange) {
if (!onCodexConfigChange) {
return;
}
@@ -74,10 +74,8 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
return;
}
const extracted = extractCodexBaseUrl(codexConfig) || "";
if (extracted !== codexBaseUrl) {
setCodexBaseUrl(extracted);
}
}, [codexConfig, codexBaseUrl]);
setCodexBaseUrl((prev) => (prev === extracted ? prev : extracted));
}, [codexConfig]);
// 与 TOML 配置保持模型名称同步
useEffect(() => {
@@ -85,10 +83,8 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
return;
}
const extracted = extractCodexModelName(codexConfig) || "";
if (extracted !== codexModelName) {
setCodexModelName(extracted);
}
}, [codexConfig, codexModelName]);
setCodexModelName((prev) => (prev === extracted ? prev : extracted));
}, [codexConfig]);
// 获取 API Key(从 auth JSON
const getCodexAuthApiKey = useCallback((authString: string): string => {
@@ -165,10 +161,6 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
const sanitized = url.trim();
setCodexBaseUrl(sanitized);
if (!sanitized) {
return;
}
isUpdatingCodexBaseUrlRef.current = true;
setCodexConfig((prev) => setCodexBaseUrlInConfig(prev, sanitized));
setTimeout(() => {
@@ -184,10 +176,6 @@ export function useCodexConfigState({ initialData }: UseCodexConfigStateProps) {
const trimmed = modelName.trim();
setCodexModelName(trimmed);
if (!trimmed) {
return;
}
isUpdatingCodexModelNameRef.current = true;
setCodexConfig((prev) => setCodexModelNameInConfig(prev, trimmed));
setTimeout(() => {
+2 -1
View File
@@ -775,7 +775,8 @@
"extractFromCurrent": "Extract from Editor",
"extractNoCommonConfig": "No common config available to extract from editor",
"extractFailed": "Extract failed: {{error}}",
"saveFailed": "Save failed: {{error}}"
"saveFailed": "Save failed: {{error}}",
"modelNameHint": "Specify the model to use, will be auto-updated in config.toml"
},
"geminiConfig": {
"envFile": "Environment Variables (.env)",
+2 -1
View File
@@ -775,7 +775,8 @@
"extractFromCurrent": "編集内容から抽出",
"extractNoCommonConfig": "編集内容から抽出できる共通設定がありません",
"extractFailed": "抽出に失敗しました: {{error}}",
"saveFailed": "保存に失敗しました: {{error}}"
"saveFailed": "保存に失敗しました: {{error}}",
"modelNameHint": "使用するモデルを指定します。config.toml に自動更新されます"
},
"geminiConfig": {
"envFile": "環境変数 (.env)",
+2 -1
View File
@@ -775,7 +775,8 @@
"extractFromCurrent": "从编辑内容提取",
"extractNoCommonConfig": "当前编辑内容没有可提取的通用配置",
"extractFailed": "提取失败: {{error}}",
"saveFailed": "保存失败: {{error}}"
"saveFailed": "保存失败: {{error}}",
"modelNameHint": "指定使用的模型,将自动更新到 config.toml 中"
},
"geminiConfig": {
"envFile": "环境变量 (.env)",
+26 -7
View File
@@ -447,12 +447,23 @@ export const setCodexBaseUrl = (
baseUrl: string,
): string => {
const trimmed = baseUrl.trim();
if (!trimmed) {
return configText;
}
// 归一化原文本中的引号(既能匹配,也能输出稳定格式)
const normalizedText = normalizeQuotes(configText);
// 允许清空:当 baseUrl 为空时,移除 base_url 行
if (!trimmed) {
if (!normalizedText) return normalizedText;
const next = normalizedText
.split("\n")
.filter((line) => !/^\s*base_url\s*=/.test(line))
.join("\n")
// 避免移除后留下过多空行
.replace(/\n{3,}/g, "\n\n")
// 避免开头出现空行
.replace(/^\n+/, "");
return next;
}
const normalizedUrl = trimmed.replace(/\s+/g, "");
const replacementLine = `base_url = "${normalizedUrl}"`;
const pattern = /base_url\s*=\s*(["'])([^"']+)\1/;
@@ -494,13 +505,21 @@ export const setCodexModelName = (
modelName: string,
): string => {
const trimmed = modelName.trim();
if (!trimmed) {
return configText;
}
// 归一化原文本中的引号(既能匹配,也能输出稳定格式)
const normalizedText = normalizeQuotes(configText);
// 允许清空:当 modelName 为空时,移除 model 行
if (!trimmed) {
if (!normalizedText) return normalizedText;
const next = normalizedText
.split("\n")
.filter((line) => !/^\s*model\s*=/.test(line))
.join("\n")
.replace(/\n{3,}/g, "\n\n")
.replace(/^\n+/, "");
return next;
}
const replacementLine = `model = "${trimmed}"`;
const pattern = /^model\s*=\s*["']([^"']+)["']/m;
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import {
extractCodexBaseUrl,
extractCodexModelName,
setCodexBaseUrl,
setCodexModelName,
} from "@/utils/providerConfigUtils";
describe("Codex TOML utils", () => {
it("removes base_url line when set to empty", () => {
const input = [
'model_provider = "openai"',
'base_url = "https://api.example.com/v1"',
'model = "gpt-5-codex"',
"",
].join("\n");
const output = setCodexBaseUrl(input, "");
expect(output).not.toMatch(/^\s*base_url\s*=/m);
expect(extractCodexBaseUrl(output)).toBeUndefined();
expect(extractCodexModelName(output)).toBe("gpt-5-codex");
});
it("removes model line when set to empty", () => {
const input = [
'model_provider = "openai"',
'base_url = "https://api.example.com/v1"',
'model = "gpt-5-codex"',
"",
].join("\n");
const output = setCodexModelName(input, "");
expect(output).not.toMatch(/^\s*model\s*=/m);
expect(extractCodexModelName(output)).toBeUndefined();
expect(extractCodexBaseUrl(output)).toBe("https://api.example.com/v1");
});
it("updates existing values when non-empty", () => {
const input = [
'model_provider = "openai"',
"base_url = 'https://old.example/v1'",
'model = "old-model"',
"",
].join("\n");
const output1 = setCodexBaseUrl(input, " https://new.example/v1 \n");
expect(extractCodexBaseUrl(output1)).toBe("https://new.example/v1");
const output2 = setCodexModelName(output1, " new-model \n");
expect(extractCodexModelName(output2)).toBe("new-model");
});
});