Files
CC-Switch/src/components/providers/forms/CodexConfigEditor.tsx
T
Jason 88d5ffba44 fix(codex): move common-config TOML merge off smol-toml to backend toml_edit
updateTomlCommonConfigSnippet re-serialized the whole document through
smol-toml (parse -> deepMerge -> stringify): comments dropped, keys
reordered, and empty parent table headers synthesized -- the
long-standing "config.toml keeps getting reordered" symptom (audit C5,
introduced in 083e48bf).

Replace it with a backend command backed by the same
merge_toml_table_like / remove_toml_table_like used when writing live
configs, so the form preview and the live write share one merge
semantic and user formatting survives edit-time merges. The frontend
sync helper is deleted outright to keep the pattern from coming back.

Making the form operations async exposes them to interleaving, so a
result is discarded unless it is still current when it lands:

- a per-hook sequence number (last operation wins) covers rapid
  toggle/save races where an earlier merge resolves after a later
  removal and would flip the switch back;
- a config-baseline check covers the user hand-editing the TOML while
  a merge is in flight -- the stale result must not clobber their edit.
  The checkbox state self-heals via the existing inference effect.

Regression tests pin both by resolving a suspended merge after a newer
operation / an external edit, plus backend tests locking comment and
key-order preservation, scalar override, and value-matched removal.
2026-07-08 22:46:09 +08:00

120 lines
3.0 KiB
TypeScript

import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { CodexAuthSection, CodexConfigSection } from "./CodexConfigSections";
import { CodexCommonConfigModal } from "./CodexCommonConfigModal";
interface CodexConfigEditorProps {
authValue: string;
configValue: string;
providerName?: string;
showRemoteCompaction?: boolean;
isProxyTakeover?: boolean;
onAuthChange: (value: string) => void;
onConfigChange: (value: string) => void;
onAuthBlur?: () => void;
useCommonConfig: boolean;
onCommonConfigToggle: (checked: boolean) => void | Promise<void>;
commonConfigSnippet: string;
onCommonConfigSnippetChange: (value: string) => boolean | Promise<boolean>;
onCommonConfigErrorClear: () => void;
commonConfigError: string;
authError: string;
configError: string; // config.toml 错误提示
onExtract?: () => void;
isExtracting?: boolean;
}
const CodexConfigEditor: React.FC<CodexConfigEditorProps> = ({
authValue,
configValue,
providerName,
showRemoteCompaction,
isProxyTakeover = false,
onAuthChange,
onConfigChange,
onAuthBlur,
useCommonConfig,
onCommonConfigToggle,
commonConfigSnippet,
onCommonConfigSnippetChange,
onCommonConfigErrorClear,
commonConfigError,
authError,
configError,
onExtract,
isExtracting,
}) => {
const { t } = useTranslation();
const [isCommonConfigModalOpen, setIsCommonConfigModalOpen] = useState(false);
const handleCloseCommonConfigModal = () => {
onCommonConfigErrorClear();
setIsCommonConfigModalOpen(false);
};
return (
<div className="space-y-6">
{isProxyTakeover && (
<div className="p-3 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-700 rounded-lg">
<p className="text-xs text-amber-600 dark:text-amber-400">
{t("codexConfig.proxyTakeoverStorageNotice")}
</p>
</div>
)}
{/* Auth JSON Section */}
<CodexAuthSection
value={authValue}
onChange={onAuthChange}
onBlur={onAuthBlur}
error={authError}
isProxyTakeover={isProxyTakeover}
/>
{/* Config TOML Section */}
<CodexConfigSection
value={configValue}
onChange={onConfigChange}
providerName={providerName}
showRemoteCompaction={showRemoteCompaction}
useCommonConfig={useCommonConfig}
onCommonConfigToggle={onCommonConfigToggle}
onEditCommonConfig={() => setIsCommonConfigModalOpen(true)}
commonConfigError={commonConfigError}
configError={configError}
isProxyTakeover={isProxyTakeover}
/>
{/* Common Config Modal */}
<CodexCommonConfigModal
isOpen={isCommonConfigModalOpen}
onClose={handleCloseCommonConfigModal}
value={commonConfigSnippet}
onSave={onCommonConfigSnippetChange}
error={commonConfigError}
onExtract={onExtract}
isExtracting={isExtracting}
/>
</div>
);
};
export default CodexConfigEditor;