mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-29 04:10:00 +08:00
fix(web-mode): consolidate Tauri event subscription helper to silence transformCallback errors (#256)
- Add shared safeTauriListen helper in tauri-api.js that returns a noop unsubscriber when running outside Tauri, so dynamic-importing @tauri-apps/api/event in the browser no longer throws 'Cannot read properties of undefined (reading transformCallback)'. - Replace 4 bare 'await import(@tauri-apps/api/event)' call sites (about.js hermes upgrade button + channels.js three install/action flows) that previously crashed the page on web mode. - Drop the duplicated local tauriListen helpers in hermes dashboard / chat store and route them through the shared helper.
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
* - File attachment uploads (kept out of scope for Phase 4).
|
||||
* - Full tmux-like run resume (Tauri events are in-process and reliable).
|
||||
*/
|
||||
import { api, isTauriRuntime } from '../../../lib/tauri-api.js'
|
||||
import { api, isTauriRuntime, safeTauriListen } from '../../../lib/tauri-api.js'
|
||||
|
||||
// ---------- constants ----------
|
||||
|
||||
@@ -208,24 +208,6 @@ function mapSessionSummary(s) {
|
||||
|
||||
// ---------- Tauri event bridge ----------
|
||||
//
|
||||
// Streaming relies on Tauri's `hermes-run-*` events. In Web mode (远程浏览器
|
||||
// 访问 ClawPanel)these events don't exist — and importing
|
||||
// `@tauri-apps/api/event` itself touches `window.__TAURI_INTERNALS__.transformCallback`
|
||||
// which crashes with "Cannot read properties of undefined (reading 'transformCallback')".
|
||||
//
|
||||
// To stay safe we short-circuit to a no-op unsubscriber when not running inside
|
||||
// Tauri.
|
||||
|
||||
let _listenFn = null
|
||||
async function tauriListen(event, cb) {
|
||||
if (!isTauriRuntime()) return () => {}
|
||||
if (!_listenFn) {
|
||||
const mod = await import('@tauri-apps/api/event')
|
||||
_listenFn = mod.listen
|
||||
}
|
||||
return _listenFn(event, cb)
|
||||
}
|
||||
|
||||
// ---------- store implementation ----------
|
||||
|
||||
function createStore() {
|
||||
@@ -645,7 +627,7 @@ function createStore() {
|
||||
async function attachStreamListeners(runSessionId) {
|
||||
detachStreamListeners()
|
||||
const runSession = () => state.sessions.find(x => x.id === runSessionId) || null
|
||||
const u1 = await tauriListen('hermes-run-delta', (e) => {
|
||||
const u1 = await safeTauriListen('hermes-run-delta', (e) => {
|
||||
const delta = e?.payload?.delta || ''
|
||||
if (!delta) return
|
||||
const s = runSession()
|
||||
@@ -659,7 +641,7 @@ function createStore() {
|
||||
msg.content += delta
|
||||
notify()
|
||||
})
|
||||
const u2 = await tauriListen('hermes-run-tool', (e) => {
|
||||
const u2 = await safeTauriListen('hermes-run-tool', (e) => {
|
||||
const evt = e?.payload || {}
|
||||
const evtType = evt.event || ''
|
||||
const toolName = evt.tool || evt.tool_name || evt.name || 'tool'
|
||||
@@ -703,7 +685,7 @@ function createStore() {
|
||||
}
|
||||
notify()
|
||||
})
|
||||
const u3 = await tauriListen('hermes-run-done', () => {
|
||||
const u3 = await safeTauriListen('hermes-run-done', () => {
|
||||
const s = runSession()
|
||||
if (!s) { cleanupAfterRun(); return }
|
||||
|
||||
@@ -740,7 +722,7 @@ function createStore() {
|
||||
persistSessions()
|
||||
cleanupAfterRun()
|
||||
})
|
||||
const u4 = await tauriListen('hermes-run-error', (e) => {
|
||||
const u4 = await safeTauriListen('hermes-run-error', (e) => {
|
||||
const err = e?.payload?.error || 'unknown error'
|
||||
const s = runSession()
|
||||
if (s) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Hermes Agent 仪表盘
|
||||
*/
|
||||
import { t } from '../../../lib/i18n.js'
|
||||
import { api, isTauriRuntime } from '../../../lib/tauri-api.js'
|
||||
import { api, safeTauriListen } from '../../../lib/tauri-api.js'
|
||||
import {
|
||||
loadHermesProviders,
|
||||
inferProviderByBaseUrl,
|
||||
@@ -20,20 +20,6 @@ const ICONS = {
|
||||
// Provider registry—异步加载,第一次 render 前填充
|
||||
let hermesProviders = []
|
||||
|
||||
// Lazy Tauri event listen (avoid top-level await for vite build).
|
||||
// Web 模式下 `@tauri-apps/api/event` 的模块顶层会触碰 `window.__TAURI_INTERNALS__.transformCallback`
|
||||
// 导致 "Cannot read properties of undefined (reading 'transformCallback')",
|
||||
// 因此非 Tauri 环境直接 noop。
|
||||
let _listenFn = null
|
||||
async function tauriListen(event, cb) {
|
||||
if (!isTauriRuntime()) return () => {}
|
||||
if (!_listenFn) {
|
||||
const mod = await import('@tauri-apps/api/event')
|
||||
_listenFn = mod.listen
|
||||
}
|
||||
return _listenFn(event, cb)
|
||||
}
|
||||
|
||||
const HERMES_DASHBOARD_URL = 'http://127.0.0.1:9119/'
|
||||
|
||||
/**
|
||||
@@ -862,7 +848,7 @@ export function render() {
|
||||
async function setupListeners() {
|
||||
try {
|
||||
// 监听 Guardian 推送的状态变化
|
||||
const unlisten1 = await tauriListen('hermes-gateway-status', (evt) => {
|
||||
const unlisten1 = await safeTauriListen('hermes-gateway-status', (evt) => {
|
||||
const data = evt.payload
|
||||
if (info) {
|
||||
const wasRunning = info.gatewayRunning
|
||||
@@ -877,13 +863,13 @@ export function render() {
|
||||
unlisteners.push(unlisten1)
|
||||
|
||||
// 监听 Guardian 日志(显示在消息区)
|
||||
const unlisten2 = await tauriListen('hermes-guardian-log', (evt) => {
|
||||
const unlisten2 = await safeTauriListen('hermes-guardian-log', (evt) => {
|
||||
showGwMsg(evt.payload || '', false)
|
||||
})
|
||||
unlisteners.push(unlisten2)
|
||||
|
||||
// 监听 config.yaml 自愈事件(api_server guardian)
|
||||
const unlisten3 = await tauriListen('hermes-config-patched', async (evt) => {
|
||||
const unlisten3 = await safeTauriListen('hermes-config-patched', async (evt) => {
|
||||
const { toast } = await import('../../../components/toast.js')
|
||||
const msg = evt?.payload?.message || t('engine.dashConfigPatched')
|
||||
toast(msg, 'info', { duration: 6000 })
|
||||
|
||||
@@ -9,6 +9,27 @@ export function isTauriRuntime() {
|
||||
return !!window.__TAURI_INTERNALS__ || !!window.__TAURI__ || window.location?.hostname === 'tauri.localhost'
|
||||
}
|
||||
|
||||
let _tauriListenFn = null
|
||||
|
||||
/**
|
||||
* 安全订阅 Tauri 事件。Web 模式下返回 noop unsubscriber,
|
||||
* 避免动态 import `@tauri-apps/api/event` 时触碰
|
||||
* `window.__TAURI_INTERNALS__.transformCallback` 引发
|
||||
* "Cannot read properties of undefined" 报错(issue #256)。
|
||||
*
|
||||
* 用法:
|
||||
* const unlisten = await safeTauriListen('hermes-install-log', e => ...)
|
||||
* unlisten() // 取消订阅
|
||||
*/
|
||||
export async function safeTauriListen(event, cb) {
|
||||
if (!isTauriRuntime()) return () => {}
|
||||
if (!_tauriListenFn) {
|
||||
const mod = await import('@tauri-apps/api/event')
|
||||
_tauriListenFn = mod.listen
|
||||
}
|
||||
return _tauriListenFn(event, cb)
|
||||
}
|
||||
|
||||
// 仅在 Node.js 后端实现的命令(Tauri Rust 不处理),强制走 webInvoke
|
||||
const WEB_ONLY_CMDS = new Set([
|
||||
'instance_list', 'instance_add', 'instance_remove', 'instance_set_active',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 关于页面
|
||||
* 版本信息、项目链接、相关项目、系统环境
|
||||
*/
|
||||
import { api } from '../lib/tauri-api.js'
|
||||
import { api, safeTauriListen } from '../lib/tauri-api.js'
|
||||
import { toast } from '../components/toast.js'
|
||||
import { showUpgradeModal, showConfirm, showContentModal } from '../components/modal.js'
|
||||
import { setUpgrading } from '../lib/app-state.js'
|
||||
@@ -217,8 +217,7 @@ async function loadHermesData(page) {
|
||||
|
||||
let unlisten = null
|
||||
try {
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
unlisten = await listen('hermes-install-log', (e) => {
|
||||
unlisten = await safeTauriListen('hermes-install-log', (e) => {
|
||||
modal.appendLog(String(e.payload))
|
||||
})
|
||||
} catch (_) {}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 消息渠道管理
|
||||
* 渠道列表 + Agent 对接(多绑定、独立配置、渠道测试)
|
||||
*/
|
||||
import { api, invalidate } from '../lib/tauri-api.js'
|
||||
import { api, invalidate, safeTauriListen } from '../lib/tauri-api.js'
|
||||
import { toast } from '../components/toast.js'
|
||||
import { showContentModal, showConfirm } from '../components/modal.js'
|
||||
import { icon } from '../lib/icons.js'
|
||||
@@ -1530,7 +1530,7 @@ async function openConfigDialog(pid, page, state, accountId) {
|
||||
const logBox = actionResultEl.querySelector('#channel-action-log-box')
|
||||
const progressBar = actionResultEl.querySelector('#channel-action-progress-bar')
|
||||
const progressText = actionResultEl.querySelector('#channel-action-progress-text')
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
const listen = safeTauriListen
|
||||
let unlistenLog = null, unlistenProgress = null
|
||||
let _qrTimer = null
|
||||
const cleanup = () => { unlistenLog?.(); unlistenProgress?.(); clearTimeout(_qrTimer) }
|
||||
@@ -1923,7 +1923,7 @@ async function openConfigDialog(pid, page, state, accountId) {
|
||||
const logBox = actionResultEl.querySelector('#channel-action-log-box')
|
||||
const progressBar = actionResultEl.querySelector('#channel-action-progress-bar')
|
||||
const progressText = actionResultEl.querySelector('#channel-action-progress-text')
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
const listen = safeTauriListen
|
||||
let unlistenLog = null
|
||||
let unlistenProgress = null
|
||||
let unlistenDone = null
|
||||
@@ -2110,7 +2110,7 @@ async function openConfigDialog(pid, page, state, accountId) {
|
||||
const progressText = resultEl.querySelector('#plugin-progress-text')
|
||||
let unlistenLog, unlistenProgress
|
||||
try {
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
const listen = safeTauriListen
|
||||
unlistenLog = await listen('plugin-log', (e) => {
|
||||
logBox.textContent += e.payload + '\n'
|
||||
logBox.scrollTop = logBox.scrollHeight
|
||||
|
||||
Reference in New Issue
Block a user