fix: MiMo ASR API Key 为空竞态修复(三处)

1. CredentialField onBlur: save() fire-and-forget 与 validate 按钮存在 IPC 竞态
   — 用户输入 Key 后立即点「测试连接」时 setCredential 可能晚于 validate 到达后端。
   — 添加 mountedRef 守卫防止组件卸载后 stale save。

2. read_mimo_credentials: 将 .ok().flatten().unwrap_or_default() 改为显式 match
   — keyring 读取失败不再静默吞错,分别用 warn/error 记日志。
This commit is contained in:
H-Chris233 committed 2026-06-09 22:04:42 +08:00
1 parent bba2c90ef2
commit 53bb8fee37
2 files changed
+18 -5

No files matched your search

@@ -506,10 +506,17 @@ pub(crate) fn read_whisper_credentials() -> (String, String, String) {
}
pub(crate) fn read_mimo_credentials() -> (String, String, String) {
let api_key = CredentialsVault::get(CredentialAccount::AsrApiKey)
.ok()
.flatten()
.unwrap_or_default();
let api_key = match CredentialsVault::get(CredentialAccount::AsrApiKey) {
Ok(Some(key)) if !key.trim().is_empty() => key,
Ok(_) => {
log::warn!("[coord] MiMo ASR: asr.api_key 未配置或为空");
String::new()
}
Err(e) => {
log::error!("[coord] MiMo ASR: 读取凭据失败: {e}");
String::new()
}
};
let base_url = CredentialsVault::get(CredentialAccount::AsrEndpoint)
.ok()
.flatten()
@@ -623,6 +623,7 @@ function CredentialField({ label, account, placeholder, mono, mask, defaultValue
const [status, setStatus] = useState<CredentialFieldStatus>('idle');
const debounceRef = useRef<number | null>(null);
const statusRef = useRef<number | null>(null);
const mountedRef = useRef(true);
useEffect(() => {
let cancelled = false;
@@ -652,7 +653,9 @@ function CredentialField({ label, account, placeholder, mono, mask, defaultValue
}, [account]);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
if (debounceRef.current) clearTimeout(debounceRef.current);
if (statusRef.current) clearTimeout(statusRef.current);
};
@@ -680,13 +683,16 @@ function CredentialField({ label, account, placeholder, mono, mask, defaultValue
const save = async (v: string, force = false) => {
if (!loaded || (!dirty && !force)) return;
if (!mountedRef.current) return;
setStatus('saving');
emitSaved('saving', t('common.saving'));
try {
await setCredential(account, v);
if (!mountedRef.current) return;
setDirty(false);
showTemporaryStatus('saved');
} catch (error) {
if (!mountedRef.current) return;
console.error('[settings] failed to save credential', account, error);
showTemporaryStatus('saveError');
}
@@ -707,7 +713,7 @@ function CredentialField({ label, account, placeholder, mono, mask, defaultValue
clearTimeout(debounceRef.current);
debounceRef.current = null;
}
save(value, true);
void save(value, true);
};
const fillDefault = async () => {