fix(settings): 处理 PR 审核意见 — 验证失败文案 / 切换回滚 / 计时器依赖

- ProvidersSection: 验证失败时显示 validateFailed 文案,不再对 result.ok=false
  误报「连接检查通过」
- ProvidersSection: LLM/ASR 切换失败时下拉框回滚到 committed provider,避免 UI
  停在新选项而后端仍是旧 provider;去掉 handler 末尾的 rethrow —— 作为 SelectLite
  onChange 是即发即忘调用,rethrow 会变成未处理的 promise rejection
- CheckUpdateButton: 自动收起计时器只依赖 status,不再因 useAutoUpdate 每次渲染
  返回新 updater 对象而被反复 clear/reset
- 新增 i18n key settings.providers.validateFailed(zh-CN/zh-TW/en/ja/ko)
This commit is contained in:
baiqing committed 2026-05-22 21:09:11 +08:00
1 parent 52c49fcfad
commit 7dc1600c4c
7 files changed
+24 -9

No files matched your search

+1
View File
@@ -660,6 +660,7 @@ export const en: typeof zhCN = {
selectModel: 'Select a model to fill the field above',
modelSaved: 'Saved model {{model}}.',
validateSuccess: 'Connection check passed.',
validateFailed: 'Connection check failed.',
providerHttpStatus: 'Provider returned HTTP {{status}}. Check the API key permissions or endpoint.',
endpointMustUseHttps: 'Endpoint must use HTTPS (localhost/127.0.0.1 are allowed for local testing).',
endpointInvalid: 'Endpoint format is invalid.',
+1
View File
@@ -662,6 +662,7 @@ export const ja: typeof zhCN = {
selectModel: 'モデルを選んで上記欄に入力',
modelSaved: 'モデル {{model}} を保存しました。',
validateSuccess: '接続チェックに合格しました。',
validateFailed: '接続チェックに失敗しました。',
providerHttpStatus: 'サプライヤーが {{status}} を返しました。API Key 権限またはエンドポイントを確認してください。',
endpointMustUseHttps: 'Endpoint は HTTPS を使用する必要があります(localhost/127.0.0.1 を除く)。',
endpointInvalid: 'Endpoint の形式が無効です。',
+1
View File
@@ -662,6 +662,7 @@ export const ko: typeof zhCN = {
selectModel: '모델을 선택해 위 필드에 입력',
modelSaved: '모델 {{model}} 을(를) 저장했습니다.',
validateSuccess: '연결 확인을 통과했습니다.',
validateFailed: '연결 확인에 실패했습니다.',
providerHttpStatus: '공급자가 {{status}} 를 반환했습니다. API Key 권한 또는 Endpoint 를 확인해 주세요.',
endpointMustUseHttps: 'Endpoint 는 HTTPS 를 사용해야 합니다(localhost/127.0.0.1 제외).',
endpointInvalid: 'Endpoint 형식이 올바르지 않습니다.',
+1
View File
@@ -658,6 +658,7 @@ export const zhCN = {
selectModel: '选择一个模型写入上方字段',
modelSaved: '已保存模型 {{model}}。',
validateSuccess: '连接检查通过。',
validateFailed: '连接检查未通过。',
providerHttpStatus: '供应商接口返回 {{status}},请检查 API Key 权限或 Endpoint。',
endpointMustUseHttps: 'Endpoint 必须使用 HTTPS(本地 localhost/127.0.0.1 测试除外)。',
endpointInvalid: 'Endpoint 格式不合法。',
+1
View File
@@ -660,6 +660,7 @@ export const zhTW: typeof zhCN = {
selectModel: '選擇一個模型寫入上方字段',
modelSaved: '已保存模型 {{model}}。',
validateSuccess: '連接檢查通過。',
validateFailed: '連接檢查未通過。',
providerHttpStatus: '供應商接口返回 {{status}},請檢查 API Key 權限或 Endpoint。',
endpointMustUseHttps: 'Endpoint 必須使用 HTTPS(本地 localhost/127.0.0.1 測試除外)。',
endpointInvalid: 'Endpoint 格式不合法。',
@@ -22,7 +22,10 @@ export function CheckUpdateButton({ channel }: { channel: UpdateChannel }) {
return () => window.clearTimeout(id);
}
return undefined;
}, [status, updater]);
// 只按 status 触发:useAutoUpdate 每次渲染都返回新 updater 对象,把它放进
// 依赖会让父组件每次重渲染都把 2.5s 自动收起计时器清掉重置。
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [status]);
const upToDate = status === 'none';
const failed = status === 'error';
@@ -224,13 +224,17 @@ export function ProvidersSection() {
setCommittedLlmProvider(id);
emitSaved('saved', t('common.saved'));
} catch (err) {
// seq 守卫:只有当前 call 还是最新时才把 saving 翻成 failed;
// 旧 call 早被 newer call 的 emitSaved('saving') 覆盖,再叠 failed 会
// 把 newer 正在跑的 saving 假伪成失败。
// seq 守卫:只有当前 call 还是最新时才翻 failed + 回滚下拉框;旧 call 早被
// newer call 的 emitSaved('saving') 覆盖,不要插手。
if (seq === llmSwitchSeqRef.current) {
emitSaved('failed', t('common.operationFailed'));
// 后端切换失败 → 下拉框退回真实生效的 committed provider,免得 UI 停在
// 新选项、实际 provider 仍是旧的,造成显示与后端不一致。
setLlmProvider(committedLlmProvider);
}
throw err;
// 不再 rethrow:本 handler 作为 SelectLite onChange 是即发即忘调用,
// rethrow 会变成未处理的 promise rejection。错误已 emitSaved + 记日志。
console.error('[settings] switch LLM provider failed', err);
}
};
@@ -277,12 +281,12 @@ export function ProvidersSection() {
setCommittedAsrProvider(id);
emitSaved('saved', t('common.saved'));
} catch (err) {
// seq 守卫同上 onLlmProviderChange:旧 call 不要把 newer call 的 saving
// 伪造成 failed。
// seq 守卫 + 回滚 + 不 rethrow,同 onLlmProviderChange。
if (seq === asrSwitchSeqRef.current) {
emitSaved('failed', t('common.operationFailed'));
setAsrProvider(committedAsrProvider);
}
throw err;
console.error('[settings] switch ASR provider failed', err);
}
};
@@ -473,7 +477,10 @@ function ProviderTools({ kind, modelAccount, onModelSelected }: { kind: 'llm' |
setResult('loading', t('settings.providers.validating'));
try {
const result = await validateProviderCredentials(kind);
setResult(result.ok ? 'success' : 'error', t('settings.providers.validateSuccess'));
setResult(
result.ok ? 'success' : 'error',
t(result.ok ? 'settings.providers.validateSuccess' : 'settings.providers.validateFailed'),
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if ((kind === 'llm' && message === 'llmModelMissing') || (kind === 'asr' && message === 'asrModelMissing')) {