feat: 升级至 v0.3.10 — LLM 配置批量保存 + workspace.path 失败感知 + provider 切换防御性修复

- 新增 config:setBatch IPC:批量写入 8 个 LLM 字段后统一 reloadAdapter,
  解决设置页串行 config:set 在中间态触发"LLM 配置不完整"错误的问题
- SettingsModal.handleSave 和 OnboardingWizard.handleNext 改用 setBatch
- workspace.path 写入独立文件失败时返回 success:false(config:set 和 setBatch 一致),
  避免用户误以为保存成功但下次启动仍使用旧路径
- setBatch 将 provider 切换清空 apiKey 的逻辑移到循环前执行,
  消除对 entries 中 llm.provider 必须出现在 llm.apiKey 之前的隐含顺序依赖
This commit is contained in:
2026-07-21 13:14:42 +08:00
parent 64af91bde9
commit cd681b949a
8 changed files with 210 additions and 46 deletions
+19 -17
View File
@@ -26,23 +26,25 @@ export function OnboardingWizard(): React.JSX.Element | null {
const handleNext = async () => {
if (step < STEPS.length - 1) { setStep(step + 1); return; }
try {
if (window.metona?.config) {
const configSets: Promise<unknown>[] = [];
if (provider.trim()) configSets.push(window.metona.config.set('llm.provider', provider.trim()));
if (baseURL.trim()) configSets.push(window.metona.config.set('llm.baseURL', baseURL.trim()));
if (model.trim()) configSets.push(window.metona.config.set('llm.model', model.trim()));
if (apiKey.trim()) configSets.push(window.metona.config.set('llm.apiKey', apiKey.trim()));
if (workspacePath.trim()) configSets.push(window.metona.config.set('workspace.path', workspacePath.trim()));
configSets.push(window.metona.config.set('onboarding.completed', true));
// M-26 修复: 改用 Promise.allSettled,单个配置写入失败不阻止 onboarding 完成
// 但 onboarding.completed 必须成功,否则用户重启后仍会看到引导
const results = await Promise.allSettled(configSets);
const failedCount = results.filter((r) => r.status === 'rejected').length;
if (failedCount > 0) {
console.error('[OnboardingWizard]', `${failedCount} config(s) failed to save`);
// 用户主动操作失败必须有反馈,否则按钮看起来无响应
import('metona-toast').then((mod) => mod.default.error(`部分配置保存失败(${failedCount} 项),请重试`)).catch(() => {});
// 不调用 setOnboardingCompleted(true),让用户重试
if (window.metona?.config?.setBatch) {
// v0.3.9: 改用批量保存,避免并行 config.set 中间态触发 reloadAdapter 失败
// 旧实现问题:Promise.allSettled 并行 6 个 config.set
// - llm.provider 写入会清空 llm.apiKey(与 llm.apiKey 写入竞态)
// - reloadAdapter 被调用 4 次,中间态必然失败
// - 代码只检查 status === 'rejected',完全忽略 { success: false } 的情况
// - 实际配置失败但前端显示成功并关闭向导
const entries: Array<{ key: string; value: unknown }> = [];
if (provider.trim()) entries.push({ key: 'llm.provider', value: provider.trim() });
if (baseURL.trim()) entries.push({ key: 'llm.baseURL', value: baseURL.trim() });
if (model.trim()) entries.push({ key: 'llm.model', value: model.trim() });
if (apiKey.trim()) entries.push({ key: 'llm.apiKey', value: apiKey.trim() });
if (workspacePath.trim()) entries.push({ key: 'workspace.path', value: workspacePath.trim() });
entries.push({ key: 'onboarding.completed', value: true });
const r = await window.metona.config.setBatch(entries);
if (r && !r.success) {
console.error('[OnboardingWizard]', 'Batch config save failed:', r.error);
import('metona-toast').then((mod) => mod.default.error(r.error ?? '配置保存失败,请重试')).catch(() => {});
return;
}
useAgentStore.getState().setProvider(provider.trim() || 'deepseek', model.trim() || '');