feat: sync current providers to live files after config import

Core Improvements:
- Add sync_current_providers_live command to synchronize in-memory provider
  settings to corresponding live files (~/.claude/settings.json or ~/.codex/auth.json)
- Introduce partial-success state to distinguish between 'import succeeded
  but sync failed' scenario, providing clear user feedback
- Remove unused skip_live_backfill parameter from switch_provider command
- Separate responsibilities: backend handles import/backup, frontend handles
  sync/error presentation

Technical Details:
- Codex: sync auth.json + config.toml with MCP configuration
- Claude: sync settings.json
- Bidirectional sync: read back after write to update in-memory settings_config
- Full i18n support (English and Chinese)
- Graceful handling when no current provider is active

Affected Files:
- Backend: import_export.rs, commands.rs, lib.rs
- Frontend: useImportExport.ts, ImportExportSection.tsx, settings.ts
- i18n: en.json, zh.json

This ensures SSOT (Single Source of Truth) consistency between config.json
and live configuration files after import operations.
This commit is contained in:
Jason
2025-10-27 13:20:59 +08:00
parent 76a8d1760b
commit d064cb8555
8 changed files with 207 additions and 45 deletions

View File

@@ -167,6 +167,20 @@ function ImportStatusMessage({
);
}
if (status === "partial-success") {
return (
<div
className={`${baseClass} border-yellow-200 bg-yellow-100/70 text-yellow-700`}
>
<AlertCircle className="mt-0.5 h-4 w-4" />
<div className="space-y-1">
<p className="font-medium">{t("settings.importPartialSuccess")}</p>
<p>{t("settings.importPartialHint")}</p>
</div>
</div>
);
}
const message = errorMessage || t("settings.importFailed");
return (

View File

@@ -3,7 +3,12 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { settingsApi } from "@/lib/api";
export type ImportStatus = "idle" | "importing" | "success" | "error";
export type ImportStatus =
| "idle"
| "importing"
| "success"
| "partial-success"
| "error";
export interface UseImportExportOptions {
onImportSuccess?: () => void | Promise<void>;
@@ -86,8 +91,22 @@ export function useImportExport(
try {
const result = await settingsApi.importConfigFromFile(selectedFile);
if (result.success) {
setBackupId(result.backupId ?? null);
if (!result.success) {
setStatus("error");
const message =
result.message ||
t("settings.configCorrupted", {
defaultValue: "配置文件已损坏或格式不正确",
});
setErrorMessage(message);
toast.error(message);
return;
}
setBackupId(result.backupId ?? null);
try {
await settingsApi.syncCurrentProvidersLive();
setStatus("success");
toast.success(
t("settings.importSuccess", {
@@ -98,15 +117,15 @@ export function useImportExport(
successTimerRef.current = window.setTimeout(() => {
void onImportSuccess?.();
}, 1500);
} else {
setStatus("error");
const message =
result.message ||
t("settings.configCorrupted", {
defaultValue: "配置文件已损坏或格式不正确",
});
setErrorMessage(message);
toast.error(message);
} catch (error) {
console.error("[useImportExport] Failed to sync live config", error);
setStatus("partial-success");
toast.warning(
t("settings.importPartialSuccess", {
defaultValue:
"配置已导入,但同步到当前供应商失败。请手动重新选择一次供应商。",
}),
);
}
} catch (error) {
console.error("[useImportExport] Failed to import config", error);

View File

@@ -123,6 +123,9 @@
"importing": "Importing...",
"importSuccess": "Import Successful!",
"importFailed": "Import Failed",
"syncLiveFailed": "Imported, but failed to sync to the current provider. Please reselect the provider manually.",
"importPartialSuccess": "Config imported, but failed to sync to the current provider.",
"importPartialHint": "Please manually reselect the provider to refresh the live configuration.",
"configExported": "Config exported to:",
"exportFailed": "Export failed",
"selectFileFailed": "Failed to select file",

View File

@@ -123,6 +123,9 @@
"importing": "导入中...",
"importSuccess": "导入成功!",
"importFailed": "导入失败",
"syncLiveFailed": "已导入,但同步到当前供应商失败,请手动重新选择一次供应商。",
"importPartialSuccess": "配置已导入,但同步到当前供应商失败。",
"importPartialHint": "请手动重新选择一次供应商以刷新对应配置。",
"configExported": "配置已导出到:",
"exportFailed": "导出失败",
"selectFileFailed": "选择文件失败",

View File

@@ -97,6 +97,16 @@ export const settingsApi = {
});
},
async syncCurrentProvidersLive(): Promise<void> {
const result = (await invoke("sync_current_providers_live")) as {
success?: boolean;
message?: string;
};
if (!result?.success) {
throw new Error(result?.message || "Sync current providers failed");
}
},
async openExternal(url: string): Promise<void> {
try {
const u = new URL(url);