mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-14 00:43:19 +08:00
延续上一轮小白 UX 改造的尾声三连:
## 1. toast 智能行动按钮(U2 收尾)
- humanizeError 输出新增 action 字段:{ label, route?, handler?, kind }
- 自动按错误 kind 给默认按钮:
· gatewayDown → [去启动 Gateway] → /services
· cmdMissing / permission → [打开设置] → /settings
· auth → [检查 API Key] → /models
· network / timeout / rateLimit / generic → 不给(重试由用户控制)
- toast 结构化分支渲染 .toast-action-btn 按钮,点击后用 navigate(route) 或调 handler,并自动关闭 toast
- common.js 加 errorAction.* 三个按钮文案 i18n(11 语言)
## 2. ⓘ 拓展到 gateway / agents
- gateway.js: token label 后加 ⓘ(apikey 术语),renderConfig 末尾 attachTermTooltips
- agents.js: addAgent 弹窗 workspace 字段 label 加 ⓘ,setTimeout 扫 document.body 绑定
- term-tooltip.js 精简表新增 4 个术语:workspace / provider / baseurl + scope(已有)
## 3. sidebar 加术语表入口
- 在两个引擎(OpenClaw + Hermes)的最后一个 section 加「术语」条目
- sidebar.js i18n 新增 glossary 键(11 语言)
- 之前只能从 dashboard quick-actions 进入,现在 sidebar 永久可达
## 4. 顺手 bug 修复
- gateway.js 文件末尾历史残留的多余 `}` (line 348) syntax 错误,删除
- 与之前 hermes/cron.js 同类问题,本次改 ⓘ 时被 node --check 暴露
## 累计变动
- 10 个文件修改
- 7 个新 i18n 键(11 语言)
- Build OK
109 lines
3.5 KiB
JavaScript
109 lines
3.5 KiB
JavaScript
/**
|
||
* Toast 通知组件
|
||
*
|
||
* 入参 message 支持两种:
|
||
* 1) string —— 原来的纯文本(向后兼容)
|
||
* 2) { message, hint?, raw?, action? } —— humanize-error.js 的友好错误对象:
|
||
* - message: 主行(用户视角)
|
||
* - hint: 副行小灰字(行动建议)
|
||
* - raw: 折叠在「技术详情」里的原始错误字符串
|
||
* - action: { label, route?, handler? } 智能行动按钮,点击跳转或调 handler
|
||
*/
|
||
import { t } from '../lib/i18n.js'
|
||
import { navigate } from '../router.js'
|
||
|
||
let _container = null
|
||
|
||
function ensureContainer() {
|
||
if (!_container) {
|
||
_container = document.createElement('div')
|
||
_container.className = 'toast-container'
|
||
document.body.appendChild(_container)
|
||
}
|
||
return _container
|
||
}
|
||
|
||
function isStructuredError(v) {
|
||
return v && typeof v === 'object' && typeof v.message === 'string'
|
||
}
|
||
|
||
export function toast(message, type = 'info', options = {}) {
|
||
// 结构化错误对象需要展示「主行 + hint + 技术详情折叠」,duration 给长一些
|
||
const structured = isStructuredError(message)
|
||
const duration = options.duration || (structured && (message.hint || message.raw) ? 6000 : 3000)
|
||
const action = options.action // 可选的操作按钮(DOM 元素)
|
||
|
||
const container = ensureContainer()
|
||
const el = document.createElement('div')
|
||
el.className = `toast ${type}${structured ? ' toast-structured' : ''}`
|
||
|
||
if (structured) {
|
||
const body = document.createElement('div')
|
||
body.className = 'toast-body'
|
||
|
||
const mainRow = document.createElement('div')
|
||
mainRow.className = 'toast-main'
|
||
mainRow.textContent = message.message
|
||
body.appendChild(mainRow)
|
||
|
||
if (message.hint) {
|
||
const hintRow = document.createElement('div')
|
||
hintRow.className = 'toast-hint'
|
||
hintRow.textContent = message.hint
|
||
body.appendChild(hintRow)
|
||
}
|
||
|
||
if (message.action && message.action.label) {
|
||
const actionBtn = document.createElement('button')
|
||
actionBtn.type = 'button'
|
||
actionBtn.className = 'btn btn-xs btn-primary toast-action-btn'
|
||
actionBtn.textContent = `${message.action.label} →`
|
||
actionBtn.addEventListener('click', () => {
|
||
try {
|
||
if (typeof message.action.handler === 'function') message.action.handler()
|
||
else if (message.action.route) navigate(message.action.route)
|
||
} finally {
|
||
el.remove()
|
||
}
|
||
})
|
||
body.appendChild(actionBtn)
|
||
}
|
||
|
||
if (message.raw) {
|
||
const detail = document.createElement('details')
|
||
detail.className = 'toast-raw'
|
||
const summary = document.createElement('summary')
|
||
summary.textContent = t('common.errorRawLabel')
|
||
detail.appendChild(summary)
|
||
const pre = document.createElement('pre')
|
||
pre.textContent = message.raw
|
||
detail.appendChild(pre)
|
||
body.appendChild(detail)
|
||
}
|
||
|
||
el.appendChild(body)
|
||
} else {
|
||
const textSpan = document.createElement('span')
|
||
if (options.html) {
|
||
textSpan.innerHTML = message
|
||
} else {
|
||
textSpan.textContent = message
|
||
}
|
||
el.appendChild(textSpan)
|
||
}
|
||
|
||
// 如果有操作按钮,添加到 toast 中
|
||
if (action instanceof HTMLElement) {
|
||
el.appendChild(action)
|
||
}
|
||
|
||
container.appendChild(el)
|
||
|
||
setTimeout(() => {
|
||
el.style.opacity = '0'
|
||
el.style.transform = 'translateX(20px)'
|
||
el.style.transition = 'all 250ms ease'
|
||
setTimeout(() => el.remove(), 250)
|
||
}, duration)
|
||
}
|