From d30714d406c5db27679e36c8994999bb2bed6b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Thu, 14 May 2026 07:24:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(audit):=20=E5=A4=8D=E6=9F=A5=E7=AC=AC?= =?UTF-8?q?=E4=BA=8C=E6=B3=A2=20=E2=80=94=20alert/confirm=20=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=20+=20chat.js=20=E6=BD=9C=E4=BC=8F=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Bug #6 — chat.js 末尾多余 `}` 让 Vite build 卡死 src/engines/hermes/pages/chat.js 文件结尾 line 1635 已经闭合 render 函数,但 line 1636 多了一个孤立的 `}`,导致 esbuild 解析陷入回溯(build 卡在 transforming 47 modules 不退出)。删除多余括号后 build 1.55s 通过。 ## Bug #7 — alert() / window.confirm 跨平台行为不一致 - src/engines/hermes/pages/setup.js 3 处 alert() · API Key 必填校验 · 配置保存失败 · Gateway 启动失败 fallback - src/engines/hermes/pages/memory.js 2 处 window.confirm · 关闭未保存编辑窗 · 取消编辑前的确认 - src/engines/hermes/pages/env-editor.js 1 处 confirm() · 删除环境变量 Tauri webview 在 macOS / Windows / Linux 三平台对原生 alert/confirm 处理不同(macOS 需要 webview 设置 navigation_handler,部分场景直接 忽略)。统一改用项目 components/toast + components/modal.showConfirm, 保证跨平台一致 + 可被样式化。 涉及函数 closeWithConfirm 改为 async 以等待 showConfirm Promise。 ## Bug #8 — gateways.js 错误显示与其他页面风格不一致 src/engines/hermes/pages/gateways.js `error = String(e?.message || e)` 直接显示 raw error 字符串,与 profiles/kanban/oauth 三个最近升级的页面不一致。改用 humanizeError 让用户看到友好提示 + 可折叠原始错误详情。 emoji ⚙️ 设置图标改用项目 svg-icons.js 的 settings SVG,与 Bug #5 emoji→SVG 重构保持一致。 ## 验证 - npm run build:PASS(1.55s) - 受影响场景: - 安装向导 API Key 缺失提示(toast 而不是阻塞 alert) - 记忆页未保存确认(modal 而不是平台原生 confirm) - 环境变量删除确认(modal 而不是平台原生 confirm) - 多 Gateway 加载失败错误展示(友好 + 可展开技术详情) --- src/engines/hermes/pages/chat.js | 4 ++-- src/engines/hermes/pages/env-editor.js | 8 +++++++- src/engines/hermes/pages/gateways.js | 6 ++++-- src/engines/hermes/pages/memory.js | 24 +++++++++++++++++++----- src/engines/hermes/pages/setup.js | 8 +++++--- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/engines/hermes/pages/chat.js b/src/engines/hermes/pages/chat.js index ac8811f..c5e2696 100644 --- a/src/engines/hermes/pages/chat.js +++ b/src/engines/hermes/pages/chat.js @@ -780,7 +780,7 @@ export function render() { if (hermesInstalled === false) { return `
- + ${escHtml(t('engine.chatHealthInstallMissing'))} ${escHtml(t('engine.chatHealthGoDashboard'))}
@@ -789,7 +789,7 @@ export function render() { if (!gwOnline) { return `
- + ${escHtml(t('engine.chatHealthGatewayDown'))} ${escHtml(t('engine.chatHealthGoDashboard'))}
diff --git a/src/engines/hermes/pages/env-editor.js b/src/engines/hermes/pages/env-editor.js index b369c19..91b1672 100644 --- a/src/engines/hermes/pages/env-editor.js +++ b/src/engines/hermes/pages/env-editor.js @@ -9,6 +9,7 @@ */ import { api } from '../../../lib/tauri-api.js' import { toast } from '../../../components/toast.js' +import { showConfirm } from '../../../components/modal.js' import { humanizeError } from '../../../lib/humanize-error.js' // NOTE: i18n keys for this page are not yet wired up in src/locales; using @@ -254,7 +255,12 @@ export function render() { } }) rowEl.querySelector('.env-delete-btn')?.addEventListener('click', async () => { - if (!confirm(`确定删除 ${row.key} 吗?`)) return + const ok = await showConfirm({ + message: `确定删除 ${row.key} 吗?`, + confirmText: '删除', + variant: 'danger', + }) + if (!ok) return try { await api.hermesEnvDelete(row.key) rows.splice(idx, 1) diff --git a/src/engines/hermes/pages/gateways.js b/src/engines/hermes/pages/gateways.js index 7ef1fbb..476c4bd 100644 --- a/src/engines/hermes/pages/gateways.js +++ b/src/engines/hermes/pages/gateways.js @@ -18,6 +18,7 @@ import { api } from '../../../lib/tauri-api.js' import { toast } from '../../../components/toast.js' import { showModal, showConfirm } from '../../../components/modal.js' import { humanizeError } from '../../../lib/humanize-error.js' +import { svgIcon } from '../lib/svg-icons.js' function escHtml(s) { return String(s ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') @@ -52,7 +53,7 @@ export function render() { ${error ? `
${escHtml(error)}
` : ''} ${(!loading && !error && !gateways.length) ? `
-
⚙️
+
${svgIcon('settings', { size: 32 })}
${escHtml(t('engine.hermesGatewaysEmpty'))}
${escHtml(t('engine.hermesGatewaysEmptyHint'))}
` : ''} @@ -123,7 +124,8 @@ export function render() { profiles = arr.map(p => (typeof p === 'string' ? p : (p.name || ''))).filter(Boolean) if (!profiles.includes('default')) profiles.unshift('default') } catch (e) { - error = String(e?.message || e) + // 保留 Error 对象、给 humanizeError 输出友好提示 + error = humanizeError(e, t('engine.hermesGatewaysLoadFailed') || 'Load failed') } finally { loading = false draw() diff --git a/src/engines/hermes/pages/memory.js b/src/engines/hermes/pages/memory.js index 656e635..45a2f1d 100644 --- a/src/engines/hermes/pages/memory.js +++ b/src/engines/hermes/pages/memory.js @@ -12,7 +12,7 @@ import { t } from '../../../lib/i18n.js' import { api } from '../../../lib/tauri-api.js' import { toast } from '../../../components/toast.js' -import { showContentModal } from '../../../components/modal.js' +import { showContentModal, showConfirm } from '../../../components/modal.js' import { humanizeError } from '../../../lib/humanize-error.js' function escHtml(s) { @@ -135,13 +135,20 @@ export function render() { const ta = overlay.querySelector('#hm-mem-modal-textarea') const cancelBtn = overlay.querySelector('[data-action="cancel"]') const saveBtn = overlay.querySelector('#hm-mem-modal-save') - const closeWithConfirm = () => { + const closeWithConfirm = async () => { if (!editing) { overlay.remove() return } const dirty = editing.buffer !== (data[editing.key] || '') - if (dirty && !confirm(t('engine.memoryUnsaved'))) return + if (dirty) { + const ok = await showConfirm({ + message: t('engine.memoryUnsaved'), + confirmText: t('common.confirm') || 'OK', + variant: 'danger', + }) + if (!ok) return + } editing = null overlay.remove() } @@ -183,10 +190,17 @@ export function render() { }) } - function cancelEdit() { + async function cancelEdit() { if (!editing) return const dirty = editing.buffer !== (data[editing.key] || '') - if (dirty && !confirm(t('engine.memoryUnsaved'))) return + if (dirty) { + const ok = await showConfirm({ + message: t('engine.memoryUnsaved'), + confirmText: t('common.confirm') || 'OK', + variant: 'danger', + }) + if (!ok) return + } editing = null document.querySelector('.hm-mem-modal-overlay')?.remove() draw() diff --git a/src/engines/hermes/pages/setup.js b/src/engines/hermes/pages/setup.js index 5ae91cd..a24e660 100644 --- a/src/engines/hermes/pages/setup.js +++ b/src/engines/hermes/pages/setup.js @@ -5,6 +5,7 @@ */ import { t } from '../../../lib/i18n.js' import { api, invalidate, isTauriRuntime } from '../../../lib/tauri-api.js' +import { toast } from '../../../components/toast.js' import { getActiveEngine } from '../../../lib/engine-manager.js' import { loadHermesProviders, @@ -611,7 +612,7 @@ export function render() { const provider = matched?.id || 'custom' if (!apiKey) { - alert('请输入 API Key') + toast(t('engine.installCustomEmpty') || '请输入 API Key', 'warning') return } try { @@ -619,7 +620,8 @@ export function render() { phase = 'gateway' await refreshHermes() } catch (e) { - alert(`配置保存失败: ${e}`) + const msg = String(e?.message || e).replace(/^Error:\s*/, '') + toast(`${t('engine.configSaveFailed') || '配置保存失败'}: ${msg}`, 'error') } } @@ -640,7 +642,7 @@ export function render() { errEl.textContent = msg || t('engine.gatewayStartFailed') errEl.style.display = 'block' } else { - alert(msg || t('engine.gatewayStartFailed')) + toast(msg || t('engine.gatewayStartFailed'), 'error') } } finally { gwStarting = false