mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 20:30:00 +08:00
fix(audit): 复查第二波 — alert/confirm 统一 + chat.js 潜伏语法 bug
## 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 加载失败错误展示(友好 + 可展开技术详情)
This commit is contained in:
@@ -780,7 +780,7 @@ export function render() {
|
||||
if (hermesInstalled === false) {
|
||||
return `
|
||||
<div class="hm-chat-health-banner is-error">
|
||||
<span class="hm-chat-health-icon" aria-hidden="true">⚠</span>
|
||||
<span class="hm-chat-health-icon" aria-hidden="true">${svgIcon('alert-triangle', { size: 14 })}</span>
|
||||
<span class="hm-chat-health-msg">${escHtml(t('engine.chatHealthInstallMissing'))}</span>
|
||||
<a class="hm-chat-health-action" href="#/h/dashboard">${escHtml(t('engine.chatHealthGoDashboard'))}</a>
|
||||
</div>
|
||||
@@ -789,7 +789,7 @@ export function render() {
|
||||
if (!gwOnline) {
|
||||
return `
|
||||
<div class="hm-chat-health-banner is-warn">
|
||||
<span class="hm-chat-health-icon" aria-hidden="true">⚠</span>
|
||||
<span class="hm-chat-health-icon" aria-hidden="true">${svgIcon('alert-triangle', { size: 14 })}</span>
|
||||
<span class="hm-chat-health-msg">${escHtml(t('engine.chatHealthGatewayDown'))}</span>
|
||||
<a class="hm-chat-health-action" href="#/h/dashboard">${escHtml(t('engine.chatHealthGoDashboard'))}</a>
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, '>').replace(/"/g, '"')
|
||||
@@ -52,7 +53,7 @@ export function render() {
|
||||
${error ? `<div style="color:var(--error);padding:20px">${escHtml(error)}</div>` : ''}
|
||||
${(!loading && !error && !gateways.length) ? `
|
||||
<div class="empty-state empty-compact">
|
||||
<div class="empty-icon">⚙️</div>
|
||||
<div class="empty-icon">${svgIcon('settings', { size: 32 })}</div>
|
||||
<div class="empty-title">${escHtml(t('engine.hermesGatewaysEmpty'))}</div>
|
||||
<div class="empty-desc" style="margin-top:8px">${escHtml(t('engine.hermesGatewaysEmptyHint'))}</div>
|
||||
</div>` : ''}
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user