From dcac1d6d217b569e667d448c085cce7ca92197db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Thu, 14 May 2026 05:45:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor(hermes):=20UX=20=E5=B0=8F=E7=99=BD?= =?UTF-8?q?=E5=8C=96=E6=94=B6=E5=B0=BE=20-=206=20=E4=B8=AA=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E7=BB=9F=E4=B8=80=20humanizeError=20+=20=E4=BF=AE=20u?= =?UTF-8?q?sage.js=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把 raw error toast 升级为 humanizeError,让用户看到分类友好的错误提示 (自动加 action button 如「打开设置」「重试」「查看文档」)。 ## sessions.js (5 处) - 加载会话失败 → humanizeError(err, t('engine.sessionsLoadFailed')) - 详情加载失败 → humanizeError(err, t('engine.sessionsDetailLoadFailed')) - 切换会话失败 → humanizeError(err, t('engine.sessionsSwitchFailed')) - 删除失败 → humanizeError(err, t('engine.chatDeleteFailed')) - 导出失败 → humanizeError(err, t('engine.sessionsExportFailed')) ## extensions.js (5 处) - dashboard 打开失败 (probe / start) - 2 处 - 主题保存失败 → humanizeError(err, t('engine.extensionsThemeSaveFailed')) - 加载扩展失败 → humanizeError(err, t('engine.extensionsLoadFailed')) - 重新扫描失败 → humanizeError(err, t('engine.extensionsRescanFailed')) ## env-editor.js (4 处) - reveal / save / delete / load 错误 → humanizeError - 文案保留 inline 中文(页面尚未 i18n 化) ## usage.js (1 bug + 1 升级) - **修真 bug**: catch (_) {...} 内部却用了 err 引用 → 改为 catch (err) {...} - 失败时 humanizeError(err, t('engine.usageLoadFailed')) ## lazy-deps.js (1 处) - features API 失败显示 humanizeError + escapeHtml ## config.js (2 处) - 加载配置失败 → humanizeError(err, t('engine.hermesConfigLoadFailed')) - 保存配置失败 → humanizeError(err, t('engine.hermesConfigSaveFailed')) ## hermes.css 修 lint - .hm-kanban-task-summary 加 line-clamp 标准属性(不只是 -webkit-line-clamp) ## i18n 新增 8 键 - sessionsLoadFailed / sessionsSwitchFailed / usageLoadFailed - extensionsThemeSaveFailed / extensionsLoadFailed / extensionsRescanFailed - hermesConfigLoadFailed / hermesConfigSaveFailed - 全部 × 3 语言 ## 累计影响 - 6 个 hermes 页面(sessions/extensions/env-editor/usage/lazy-deps/config)统一错误处理 - 修 usage.js 1 个真 bug(catch 参数名错配) - 8 个新 i18n × 3 语言 - 1 个 CSS lint 警告修复 - npm build ✓ --- src/engines/hermes/pages/config.js | 5 +++-- src/engines/hermes/pages/env-editor.js | 9 +++++---- src/engines/hermes/pages/extensions.js | 11 ++++++----- src/engines/hermes/pages/lazy-deps.js | 2 +- src/engines/hermes/pages/sessions.js | 11 ++++++----- src/engines/hermes/pages/usage.js | 5 +++-- src/engines/hermes/style/hermes.css | 1 + src/locales/modules/engine.js | 9 +++++++++ 8 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/engines/hermes/pages/config.js b/src/engines/hermes/pages/config.js index 982c641..264fc94 100644 --- a/src/engines/hermes/pages/config.js +++ b/src/engines/hermes/pages/config.js @@ -4,6 +4,7 @@ import { t } from '../../../lib/i18n.js' import { api } from '../../../lib/tauri-api.js' import { toast } from '../../../components/toast.js' +import { humanizeError } from '../../../lib/humanize-error.js' export function render() { const el = document.createElement('div') @@ -61,7 +62,7 @@ export function render() { const data = await api.hermesConfigRawRead() yaml = data?.yaml || '' } catch (err) { - error = String(err?.message || err).replace(/^Error:\s*/, '') + error = humanizeError(err, t('engine.hermesConfigLoadFailed') || 'Load config failed') } finally { loading = false draw() @@ -78,7 +79,7 @@ export function render() { await api.hermesConfigRawWrite(yaml) toast(t('engine.hermesConfigSaveSuccess'), 'success') } catch (err) { - error = String(err?.message || err).replace(/^Error:\s*/, '') + error = humanizeError(err, t('engine.hermesConfigSaveFailed') || 'Save failed') toast(error, 'error') } finally { saving = false diff --git a/src/engines/hermes/pages/env-editor.js b/src/engines/hermes/pages/env-editor.js index e906ad4..b369c19 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 { humanizeError } from '../../../lib/humanize-error.js' // NOTE: i18n keys for this page are not yet wired up in src/locales; using // inline Chinese copy (with occasional English fallback) for now. When the @@ -214,7 +215,7 @@ export function render() { row.revealed = true renderList() } catch (err) { - toast(String(err).replace(/^Error:\s*/, ''), 'error') + toast(humanizeError(err, '读取失败'), 'error') } }) rowEl.querySelector('.env-cancel-btn')?.addEventListener('click', () => { @@ -249,7 +250,7 @@ export function render() { toast('已保存', 'success') renderList() } catch (err) { - toast(String(err).replace(/^Error:\s*/, ''), 'error') + toast(humanizeError(err, '保存失败'), 'error') } }) rowEl.querySelector('.env-delete-btn')?.addEventListener('click', async () => { @@ -260,7 +261,7 @@ export function render() { toast('已删除', 'success') renderList() } catch (err) { - toast(String(err).replace(/^Error:\s*/, ''), 'error') + toast(humanizeError(err, '删除失败'), 'error') } }) }) @@ -282,7 +283,7 @@ export function render() { isNew: false, })) } catch (err) { - loadError = String(err).replace(/^Error:\s*/, '') + loadError = humanizeError(err, '加载失败') const errEl = el.querySelector('#env-error') if (errEl) { errEl.textContent = loadError diff --git a/src/engines/hermes/pages/extensions.js b/src/engines/hermes/pages/extensions.js index 935713f..7e93b5b 100644 --- a/src/engines/hermes/pages/extensions.js +++ b/src/engines/hermes/pages/extensions.js @@ -2,6 +2,7 @@ import { api } from '../../../lib/tauri-api.js' import { icon } from '../../../lib/icons.js' import { toast } from '../../../components/toast.js' import { t } from '../../../lib/i18n.js' +import { humanizeError } from '../../../lib/humanize-error.js' function esc(value) { return String(value || '') @@ -137,14 +138,14 @@ export function render() { const probe = await api.hermesDashboardProbe().catch(() => ({ running: false, port: 9119 })) if (probe?.running) { try { await openWith(probe.port || 9119) } - catch (err) { toast(t('engine.dashNativePanelOpenFail') + ': ' + (err?.message || err), 'error') } + catch (err) { toast(humanizeError(err, t('engine.dashNativePanelOpenFail')), 'error') } return } // 2. auto-start const r = await api.hermesDashboardStart().catch(() => ({ started: false, kind: 'spawn_failed', port: probe?.port || 9119 })) if (r?.started) { try { await openWith(r.port || 9119) } - catch (err) { toast(t('engine.dashNativePanelOpenFail') + ': ' + (err?.message || err), 'error') } + catch (err) { toast(humanizeError(err, t('engine.dashNativePanelOpenFail')), 'error') } return } // 3. 失败 → toast(dashboard 页面有完整安装流程,这里只引导) @@ -166,7 +167,7 @@ export function render() { toast(t('engine.extensionsThemeSaved'), 'success') draw() } catch (err) { - toast(String(err?.message || err).replace(/^Error:\s*/, ''), 'error') + toast(humanizeError(err, t('engine.extensionsThemeSaveFailed') || 'Save theme failed'), 'error') } }) }) @@ -187,7 +188,7 @@ export function render() { plugins = Array.isArray(pluginData) ? pluginData : [] analytics = usageData || null } catch (err) { - error = String(err?.message || err).replace(/^Error:\s*/, '') + error = humanizeError(err, t('engine.extensionsLoadFailed') || 'Load failed') } finally { loading = false draw() @@ -200,7 +201,7 @@ export function render() { await load() toast(t('engine.extensionsPluginsRescanned'), 'success') } catch (err) { - toast(String(err?.message || err).replace(/^Error:\s*/, ''), 'error') + toast(humanizeError(err, t('engine.extensionsRescanFailed') || 'Rescan failed'), 'error') } } diff --git a/src/engines/hermes/pages/lazy-deps.js b/src/engines/hermes/pages/lazy-deps.js index 3c1fa10..4681021 100644 --- a/src/engines/hermes/pages/lazy-deps.js +++ b/src/engines/hermes/pages/lazy-deps.js @@ -71,7 +71,7 @@ async function loadAndRender(page) { try { featuresResp = await api.hermesLazyDepsFeatures() } catch (e) { - content.innerHTML = `
${escapeHtml(t('hermesLazyDeps.loadFailed'))}: ${escapeHtml(String(e))}
` + content.innerHTML = `
${escapeHtml(humanizeError(e, t('hermesLazyDeps.loadFailed')))}
` return } diff --git a/src/engines/hermes/pages/sessions.js b/src/engines/hermes/pages/sessions.js index ae58bae..725bc98 100644 --- a/src/engines/hermes/pages/sessions.js +++ b/src/engines/hermes/pages/sessions.js @@ -4,6 +4,7 @@ import { toast } from '../../../components/toast.js' import { showConfirm } from '../../../components/modal.js' import { icon } from '../../../lib/icons.js' import { getChatStore, getSourceLabel } from '../lib/chat-store.js' +import { humanizeError } from '../../../lib/humanize-error.js' function escHtml(value) { return String(value ?? '') @@ -163,7 +164,7 @@ export function render() { selected = new Set([...selected].filter(key => rows.some(s => sessionKey(s) === key))) selectedKey = selectedKey && rows.some(s => sessionKey(s) === selectedKey) ? selectedKey : (visible[0] ? sessionKey(visible[0]) : null) } catch (err) { - toast(String(err?.message || err), 'error') + toast(humanizeError(err, t('engine.sessionsLoadFailed') || 'Load sessions failed'), 'error') } finally { loading = false draw() @@ -184,7 +185,7 @@ export function render() { session.source = session.source || detail?.source || '' session.messageCount = session.messageCount || session.messages.length } catch (err) { - toast(t('engine.sessionsDetailLoadFailed') + ': ' + (err?.message || err), 'error') + toast(humanizeError(err, t('engine.sessionsDetailLoadFailed')), 'error') } finally { detailLoadingKey = null if (redraw) draw() @@ -342,7 +343,7 @@ export function render() { await store.switchSession(session.id) window.location.hash = '#/h/chat' } catch (err) { - toast(String(err?.message || err), 'error') + toast(humanizeError(err, t('engine.sessionsSwitchFailed') || 'Switch failed'), 'error') } finally { busy = false draw() @@ -364,7 +365,7 @@ export function render() { if (session.profile === store.state.activeProfile) await store.loadSessions() toast(t('engine.chatSessionDeleted'), 'success') } catch (err) { - toast(t('engine.chatDeleteFailed') + ': ' + (err?.message || err), 'error') + toast(humanizeError(err, t('engine.chatDeleteFailed')), 'error') } draw() } @@ -475,7 +476,7 @@ export function render() { URL.revokeObjectURL(url) toast(t('engine.sessionsExportSuccess'), 'success') } catch (err) { - toast(t('engine.sessionsExportFailed') + ': ' + (err?.message || err), 'error') + toast(humanizeError(err, t('engine.sessionsExportFailed')), 'error') } finally { btn.disabled = false } diff --git a/src/engines/hermes/pages/usage.js b/src/engines/hermes/pages/usage.js index 5b1a507..f3f8237 100644 --- a/src/engines/hermes/pages/usage.js +++ b/src/engines/hermes/pages/usage.js @@ -1,6 +1,7 @@ import { api } from '../../../lib/tauri-api.js' import { t } from '../../../lib/i18n.js' import { icon } from '../../../lib/icons.js' +import { humanizeError } from '../../../lib/humanize-error.js' const DAY_MS = 24 * 60 * 60 * 1000 @@ -374,8 +375,8 @@ export function render() { if (!alive) return sessions = Array.isArray(rows) ? rows : [] analytics = null - } catch (_) { - error = err?.message || String(err) + } catch (err) { + error = humanizeError(err, t('engine.usageLoadFailed') || 'Load usage failed') } } finally { if (!alive) return diff --git a/src/engines/hermes/style/hermes.css b/src/engines/hermes/style/hermes.css index 7ab790a..5f1107d 100644 --- a/src/engines/hermes/style/hermes.css +++ b/src/engines/hermes/style/hermes.css @@ -5265,6 +5265,7 @@ body[data-active-engine="hermes"][data-theme="dark"] { margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 2; + line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } diff --git a/src/locales/modules/engine.js b/src/locales/modules/engine.js index 64835ca..38e1632 100644 --- a/src/locales/modules/engine.js +++ b/src/locales/modules/engine.js @@ -614,6 +614,15 @@ export default { hermesGroupChatRunFailed: _('运行失败', 'Run failed', '執行失敗'), hermesGroupChatNoOutput: _('(无输出)', '(no output)', '(無輸出)'), hermesGroupChatWebUnsupported: _('Web 模式不支持群聊(依赖 hermesAgentRun,需桌面端事件桥)。请用桌面客户端。', 'Group chat is not supported in Web mode (requires desktop event bridge). Use the desktop app.', 'Web 模式不支援群聊(依賴 hermesAgentRun,需桌面端事件橋)。請用桌面客戶端。'), + // Hermes UX 小白化:补齐 humanizeError fallback 用到的 i18n + sessionsLoadFailed: _('加载会话失败', 'Load sessions failed', '載入會話失敗'), + sessionsSwitchFailed: _('切换会话失败', 'Switch session failed', '切換會話失敗'), + usageLoadFailed: _('加载用量失败', 'Load usage failed', '載入用量失敗'), + extensionsThemeSaveFailed: _('保存主题失败', 'Save theme failed', '儲存主題失敗'), + extensionsLoadFailed: _('加载扩展失败', 'Load extensions failed', '載入擴充功能失敗'), + extensionsRescanFailed: _('重新扫描失败', 'Rescan failed', '重新掃描失敗'), + hermesConfigLoadFailed: _('加载配置失败', 'Load config failed', '載入設定失敗'), + hermesConfigSaveFailed: _('保存配置失败', 'Save config failed', '儲存設定失敗'), // Web 模式(远程浏览器)下流式聊天暂不可用 chatWebModeStreamingUnsupported: _( 'Web 模式暂不支持 Hermes 实时流式聊天(依赖桌面端事件桥)。请打开桌面客户端使用此功能。',