fix(codex): correct config path reporting and folder opening; allow empty config.toml; unify API key field as OPENAI_API_KEY; front-end invoke uses app_type/app fallback for Tauri commands

This commit is contained in:
Jason
2025-08-31 16:39:38 +08:00
parent c98a724935
commit 30fe800ebe
5 changed files with 58 additions and 38 deletions

View File

@@ -104,7 +104,7 @@ pub fn restore_codex_provider_config(provider_id: &str, provider_name: &str) ->
fs::create_dir_all(parent).map_err(|e| format!("创建 Codex 目录失败: {}", e))?; fs::create_dir_all(parent).map_err(|e| format!("创建 Codex 目录失败: {}", e))?;
} }
// 复制 auth.json // 复制 auth.json(必需)
if provider_auth_path.exists() { if provider_auth_path.exists() {
copy_file(&provider_auth_path, &auth_path)?; copy_file(&provider_auth_path, &auth_path)?;
log::info!("已恢复 Codex auth.json"); log::info!("已恢复 Codex auth.json");
@@ -115,15 +115,14 @@ pub fn restore_codex_provider_config(provider_id: &str, provider_name: &str) ->
)); ));
} }
// 复制 config.toml // 复制 config.toml(可选,允许为空;不存在则创建空文件以保持一致性)
if provider_config_path.exists() { if provider_config_path.exists() {
copy_file(&provider_config_path, &config_path)?; copy_file(&provider_config_path, &config_path)?;
log::info!("已恢复 Codex config.toml"); log::info!("已恢复 Codex config.toml");
} else { } else {
return Err(format!( // 写入空文件
"供应商 config.toml 不存在: {}", fs::write(&config_path, "").map_err(|e| format!("创建空的 config.toml 失败: {}", e))?;
provider_config_path.display() log::info!("供应商 config.toml 缺失,已创建空文件");
));
} }
Ok(()) Ok(())
@@ -134,17 +133,21 @@ pub fn import_current_codex_config() -> Result<Value, String> {
let auth_path = get_codex_auth_path(); let auth_path = get_codex_auth_path();
let config_path = get_codex_config_path(); let config_path = get_codex_config_path();
// 参考 Claude Code 行为:主配置缺失时不导入 // 行为放宽:仅要求 auth.json 存在config.toml 可缺失
if !auth_path.exists() || !config_path.exists() { if !auth_path.exists() {
return Err("Codex 配置文件不存在".to_string()); return Err("Codex 配置文件不存在".to_string());
} }
// 读取 auth.json // 读取 auth.json
let auth = read_json_file::<Value>(&auth_path)?; let auth = read_json_file::<Value>(&auth_path)?;
// 读取 config.toml // 读取 config.toml(允许不存在或读取失败时为空)
let config_str = fs::read_to_string(&config_path) let config_str = if config_path.exists() {
.map_err(|e| format!("读取 config.toml 失败: {}", e))?; fs::read_to_string(&config_path)
.map_err(|e| format!("读取 config.toml 失败: {}", e))?
} else {
String::new()
};
// 组合成完整配置 // 组合成完整配置
let settings_config = serde_json::json!({ let settings_config = serde_json::json!({

View File

@@ -383,21 +383,28 @@ pub async fn get_claude_config_status() -> Result<ConfigStatus, String> {
} }
/// 获取应用配置状态(通用) /// 获取应用配置状态(通用)
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
#[tauri::command] #[tauri::command]
pub async fn get_config_status(app_type: Option<AppType>) -> Result<ConfigStatus, String> { pub async fn get_config_status(
let app = app_type.unwrap_or(AppType::Claude); app_type: Option<AppType>,
app: Option<String>,
appType: Option<String>,
) -> Result<ConfigStatus, String> {
let app = app_type
.or_else(|| app.as_deref().map(|s| s.into()))
.or_else(|| appType.as_deref().map(|s| s.into()))
.unwrap_or(AppType::Claude);
match app { match app {
AppType::Claude => Ok(crate::config::get_claude_config_status()), AppType::Claude => Ok(crate::config::get_claude_config_status()),
AppType::Codex => { AppType::Codex => {
use crate::codex_config::{get_codex_auth_path, get_codex_config_path}; use crate::codex_config::{get_codex_auth_path, get_codex_config_dir};
let auth_path = get_codex_auth_path(); let auth_path = get_codex_auth_path();
let config_path = get_codex_config_path();
// 放宽:只要 auth.json 存在即可认为已配置config.toml 允许为空
// Codex 需要两个文件都存在才算配置存在 let exists = auth_path.exists();
let exists = auth_path.exists() && config_path.exists(); let path = get_codex_config_dir().to_string_lossy().to_string();
let path = format!("~/.codex/");
Ok(ConfigStatus { exists, path }) Ok(ConfigStatus { exists, path })
} }
} }
@@ -410,9 +417,18 @@ pub async fn get_claude_code_config_path() -> Result<String, String> {
} }
/// 打开配置文件夹 /// 打开配置文件夹
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
#[tauri::command] #[tauri::command]
pub async fn open_config_folder(app: tauri::AppHandle, app_type: Option<AppType>) -> Result<bool, String> { pub async fn open_config_folder(
let app_type = app_type.unwrap_or(AppType::Claude); app: tauri::AppHandle,
app_type: Option<AppType>,
app_str: Option<String>,
appType: Option<String>,
) -> Result<bool, String> {
let app_type = app_type
.or_else(|| app_str.as_deref().map(|s| s.into()))
.or_else(|| appType.as_deref().map(|s| s.into()))
.unwrap_or(AppType::Claude);
let config_dir = match app_type { let config_dir = match app_type {
AppType::Claude => crate::config::get_claude_config_dir(), AppType::Claude => crate::config::get_claude_config_dir(),

View File

@@ -60,8 +60,8 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setCodexConfig(config.config || ""); setCodexConfig(config.config || "");
try { try {
const auth = config.auth || {}; const auth = config.auth || {};
if (auth && typeof auth.api_key === "string") { if (auth && typeof auth.OPENAI_API_KEY === "string") {
setCodexApiKey(auth.api_key); setCodexApiKey(auth.OPENAI_API_KEY);
} }
} catch { } catch {
// ignore // ignore
@@ -95,9 +95,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
let settingsConfig: Record<string, any>; let settingsConfig: Record<string, any>;
if (isCodex) { if (isCodex) {
// Codex: 验证两个文件 // Codex: 仅要求 auth.json 必填config.toml 可为空
if (!codexAuth.trim() || !codexConfig.trim()) { if (!codexAuth.trim()) {
setError("请填写 auth.json 和 config.toml 配置"); setError("请填写 auth.json 配置");
return; return;
} }
@@ -105,7 +105,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const authJson = JSON.parse(codexAuth); const authJson = JSON.parse(codexAuth);
settingsConfig = { settingsConfig = {
auth: authJson, auth: authJson,
config: codexConfig, config: codexConfig ?? "",
}; };
} catch (err) { } catch (err) {
setError("auth.json 格式错误请检查JSON语法"); setError("auth.json 格式错误请检查JSON语法");
@@ -246,7 +246,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setCodexApiKey(key); setCodexApiKey(key);
try { try {
const auth = JSON.parse(codexAuth || "{}"); const auth = JSON.parse(codexAuth || "{}");
auth.api_key = key.trim(); auth.OPENAI_API_KEY = key.trim();
setCodexAuth(JSON.stringify(auth, null, 2)); setCodexAuth(JSON.stringify(auth, null, 2));
} catch { } catch {
// ignore // ignore
@@ -266,7 +266,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const getCodexAuthApiKey = (authString: string): string => { const getCodexAuthApiKey = (authString: string): string => {
try { try {
const auth = JSON.parse(authString || "{}"); const auth = JSON.parse(authString || "{}");
return typeof auth.api_key === "string" ? auth.api_key : ""; return typeof auth.OPENAI_API_KEY === "string" ? auth.OPENAI_API_KEY : "";
} catch { } catch {
return ""; return "";
} }
@@ -472,7 +472,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
try { try {
const auth = JSON.parse(value || "{}"); const auth = JSON.parse(value || "{}");
const key = const key =
typeof auth.api_key === "string" ? auth.api_key : ""; typeof auth.OPENAI_API_KEY === "string"
? auth.OPENAI_API_KEY
: "";
setCodexApiKey(key); setCodexApiKey(key);
} catch { } catch {
// ignore // ignore
@@ -489,7 +491,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
</div> </div>
<div className="form-group"> <div className="form-group">
<label htmlFor="codexConfig">config.toml (TOML) *</label> <label htmlFor="codexConfig">config.toml (TOML)</label>
<textarea <textarea
id="codexConfig" id="codexConfig"
value={codexConfig} value={codexConfig}
@@ -497,10 +499,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
placeholder={``} placeholder={``}
rows={8} rows={8}
style={{ fontFamily: "monospace", fontSize: "14px" }} style={{ fontFamily: "monospace", fontSize: "14px" }}
required
/> />
<small className="field-hint"> <small className="field-hint">
Codex config.toml Codex config.toml
</small> </small>
</div> </div>
</> </>

View File

@@ -131,7 +131,7 @@ export const tauriAPI = {
// 获取应用配置状态(通用) // 获取应用配置状态(通用)
getConfigStatus: async (app?: AppType): Promise<ConfigStatus> => { getConfigStatus: async (app?: AppType): Promise<ConfigStatus> => {
try { try {
return await invoke("get_config_status", { appType: app }); return await invoke("get_config_status", { app_type: app, app });
} catch (error) { } catch (error) {
console.error("获取配置状态失败:", error); console.error("获取配置状态失败:", error);
return { return {
@@ -145,7 +145,7 @@ export const tauriAPI = {
// 打开配置文件夹 // 打开配置文件夹
openConfigFolder: async (app?: AppType): Promise<void> => { openConfigFolder: async (app?: AppType): Promise<void> => {
try { try {
await invoke("open_config_folder", { appType: app }); await invoke("open_config_folder", { app_type: app, app });
} catch (error) { } catch (error) {
console.error("打开配置文件夹失败:", error); console.error("打开配置文件夹失败:", error);
} }

View File

@@ -1,7 +1,7 @@
export interface Provider { export interface Provider {
id: string; id: string;
name: string; name: string;
settingsConfig: Record<string, any>; // 完整的 Claude Code settings.json 配置 settingsConfig: Record<string, any>; // 应用配置对象:Claude settings.jsonCodex 为 { auth, config }
websiteUrl?: string; websiteUrl?: string;
} }