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:
@@ -440,35 +440,29 @@ function LLMSettings() {
|
||||
}
|
||||
setSaving(true);
|
||||
try {
|
||||
const setConfig = window.metona?.config?.set;
|
||||
if (!setConfig) {
|
||||
const setBatch = window.metona?.config?.setBatch;
|
||||
if (!setBatch) {
|
||||
import('metona-toast').then((mod) => mod.default.error('配置 API 不可用')).catch(() => {});
|
||||
return;
|
||||
}
|
||||
// 串行保存:避免并发 IPC 调用(reloadAdapter 内部 lastConfigSig 比较会跳过中间态的重载)
|
||||
const fields: Array<[string, unknown]> = [
|
||||
['llm.provider', provider],
|
||||
['llm.model', model],
|
||||
['llm.apiKey', apiKey],
|
||||
['llm.baseURL', baseURL],
|
||||
['ollama.numCtx', numCtx],
|
||||
['deepseek.contextWindow', dsCtxWindow],
|
||||
['agnes.contextWindow', agnesCtxWindow],
|
||||
['mimo.contextWindow', mimoCtxWindow],
|
||||
// v0.3.9: 批量保存,避免串行保存中间态触发 reloadAdapter 失败
|
||||
// 旧实现:串行 config:set 8 次,provider 切换后第 1 步会清空 apiKey,
|
||||
// 此时 reloadAdapter 读到空 apiKey 返回 false,前端 toast 报"配置不全",
|
||||
// 但所有字段实际已写入,第二次点保存才显示"已保存"。
|
||||
// 新实现:一次性传所有字段,后端先写入全部,最后统一 reloadAdapter 一次。
|
||||
const entries: Array<{ key: string; value: unknown }> = [
|
||||
{ key: 'llm.provider', value: provider },
|
||||
{ key: 'llm.model', value: model },
|
||||
{ key: 'llm.apiKey', value: apiKey },
|
||||
{ key: 'llm.baseURL', value: baseURL },
|
||||
{ key: 'ollama.numCtx', value: numCtx },
|
||||
{ key: 'deepseek.contextWindow', value: dsCtxWindow },
|
||||
{ key: 'agnes.contextWindow', value: agnesCtxWindow },
|
||||
{ key: 'mimo.contextWindow', value: mimoCtxWindow },
|
||||
];
|
||||
let firstError: string | null = null;
|
||||
for (const [key, value] of fields) {
|
||||
try {
|
||||
const r = await setConfig(key, value);
|
||||
if (r && !r.success && !firstError) {
|
||||
firstError = r.error ?? '配置保存失败';
|
||||
}
|
||||
} catch (err) {
|
||||
if (!firstError) firstError = (err as Error).message;
|
||||
}
|
||||
}
|
||||
if (firstError) {
|
||||
import('metona-toast').then((mod) => mod.default.error(firstError!)).catch(() => {});
|
||||
const r = await setBatch(entries);
|
||||
if (r && !r.success) {
|
||||
import('metona-toast').then((mod) => mod.default.error(r.error ?? '配置保存失败')).catch(() => {});
|
||||
} else {
|
||||
import('metona-toast').then((mod) => mod.default.success('配置已保存')).catch(() => {});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user