feat(codex): 增加 Codex 预设供应商(官方、PackyCode);在添加供应商时支持一键预设与 API Key 自动写入 auth.json;UI 同步 Codex 预设按钮与字段

This commit is contained in:
Jason
2025-08-30 22:08:41 +08:00
parent c10ace7a84
commit eea5e4123b
2 changed files with 159 additions and 1 deletions

View File

@@ -10,6 +10,7 @@ import {
setApiKeyInConfig,
} from "../utils/providerConfigUtils";
import { providerPresets } from "../config/providerPresets";
import { codexProviderPresets } from "../config/codexProviderPresets";
import "./AddProviderModal.css";
interface ProviderFormProps {
@@ -45,6 +46,10 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
// Codex 特有的状态
const [codexAuth, setCodexAuth] = useState("");
const [codexConfig, setCodexConfig] = useState("");
const [codexApiKey, setCodexApiKey] = useState("");
const [selectedCodexPreset, setSelectedCodexPreset] = useState<number | null>(
null,
);
// 初始化 Codex 配置
useEffect(() => {
@@ -53,6 +58,14 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
if (typeof config === "object" && config !== null) {
setCodexAuth(JSON.stringify(config.auth || {}, null, 2));
setCodexConfig(config.config || "");
try {
const auth = config.auth || {};
if (auth && typeof auth.api_key === "string") {
setCodexApiKey(auth.api_key);
}
} catch {
// ignore
}
}
}
}, [isCodex, initialData]);
@@ -186,6 +199,33 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setDisableCoAuthored(hasCoAuthoredDisabled);
};
// Codex: 应用预设
const applyCodexPreset = (
preset: (typeof codexProviderPresets)[0],
index: number,
) => {
const authString = JSON.stringify(preset.auth || {}, null, 2);
setCodexAuth(authString);
setCodexConfig(preset.config || "");
setFormData({
name: preset.name,
websiteUrl: preset.websiteUrl,
settingsConfig: formData.settingsConfig,
});
setSelectedCodexPreset(index);
// 同步 API Key 输入框
try {
const auth = JSON.parse(authString);
const key = typeof auth.api_key === "string" ? auth.api_key : "";
setCodexApiKey(key);
} catch {
setCodexApiKey("");
}
};
// 处理 API Key 输入并自动更新配置
const handleApiKeyChange = (key: string) => {
setApiKey(key);
@@ -207,6 +247,18 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setDisableCoAuthored(hasCoAuthoredDisabled);
};
// Codex: 处理 API Key 输入并写回 auth.json
const handleCodexApiKeyChange = (key: string) => {
setCodexApiKey(key);
try {
const auth = JSON.parse(codexAuth || "{}");
auth.api_key = key.trim();
setCodexAuth(JSON.stringify(auth, null, 2));
} catch {
// ignore
}
};
// 根据当前配置决定是否展示 API Key 输入框
const showApiKey =
selectedPreset !== null || hasApiKeyField(formData.settingsConfig);
@@ -216,6 +268,21 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
selectedPreset !== null &&
providerPresets[selectedPreset]?.isOfficial === true;
// Codex: 控制显示 API Key 与官方标记
const getCodexAuthApiKey = (authString: string): string => {
try {
const auth = JSON.parse(authString || "{}");
return typeof auth.api_key === "string" ? auth.api_key : "";
} catch {
return "";
}
};
const showCodexApiKey =
selectedCodexPreset !== null || getCodexAuthApiKey(codexAuth) !== "";
const isCodexOfficialPreset =
selectedCodexPreset !== null &&
codexProviderPresets[selectedCodexPreset]?.isOfficial === true;
// 初始时从配置中同步 API Key编辑模式
useEffect(() => {
if (initialData) {
@@ -289,6 +356,26 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
</div>
)}
{showPresets && isCodex && (
<div className="presets">
<label> key</label>
<div className="preset-buttons">
{codexProviderPresets.map((preset, index) => (
<button
key={index}
type="button"
className={`preset-btn ${
selectedCodexPreset === index ? "selected" : ""
} ${preset.isOfficial ? "official" : ""}`}
onClick={() => applyCodexPreset(preset, index)}
>
{preset.name}
</button>
))}
</div>
</div>
)}
<div className="form-group">
<label htmlFor="name"> *</label>
<input
@@ -333,6 +420,36 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
</div>
)}
{isCodex && (
<div
className={`form-group api-key-group ${!showCodexApiKey ? "hidden" : ""}`}
>
<label htmlFor="codexApiKey">API Key *</label>
<input
type="text"
id="codexApiKey"
value={codexApiKey}
onChange={(e) => handleCodexApiKeyChange(e.target.value)}
placeholder={
isCodexOfficialPreset
? "官方无需填写 API Key直接保存即可"
: "只需要填这里,上方 auth.json 会自动填充"
}
disabled={isCodexOfficialPreset}
autoComplete="off"
style={
isCodexOfficialPreset
? {
backgroundColor: "#f5f5f5",
cursor: "not-allowed",
color: "#999",
}
: {}
}
/>
</div>
)}
<div className="form-group">
<label htmlFor="websiteUrl"></label>
<input
@@ -355,7 +472,17 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
<textarea
id="codexAuth"
value={codexAuth}
onChange={(e) => setCodexAuth(e.target.value)}
onChange={(e) => {
const value = e.target.value;
setCodexAuth(value);
try {
const auth = JSON.parse(value || "{}");
const key = typeof auth.api_key === "string" ? auth.api_key : "";
setCodexApiKey(key);
} catch {
// ignore
}
}}
placeholder={`{
"api_key": "your-codex-api-key"
}`}

View File

@@ -0,0 +1,31 @@
/**
* Codex 预设供应商配置模板
*/
export interface CodexProviderPreset {
name: string;
websiteUrl: string;
auth: Record<string, any>; // 将写入 ~/.codex/auth.json
config: string; // 将写入 ~/.codex/config.tomlTOML 字符串)
isOfficial?: boolean; // 标识是否为官方预设
}
export const codexProviderPresets: CodexProviderPreset[] = [
{
name: "Codex官方",
websiteUrl: "https://codex",
isOfficial: true,
// 官方一般不需要在 auth.json 里预置 key由用户根据实际环境填写
auth: {},
config: `# Codex 默认配置模板\n# 根据你的 Codex 安装或文档进行调整\nmodel = "default"\ntemperature = 0.7`,
},
{
name: "PackyCode",
websiteUrl: "https://www.packycode.com",
// PackyCode 一般通过 API Key请将占位符替换为你的实际 key
auth: {
api_key: "sk-your-api-key-here",
},
config: `# Codex 配置模板 - PackyCode\n# 如有需要可添加 base_url: \n# base_url = "https://api.packycode.com"\nmodel = "default"\ntemperature = 0.7`,
},
];