mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-31 21:29:59 +08:00
OpenClaw 内核已实现 4 个 push.web.* RPC(vapidPublicKey / subscribe / unsubscribe / test),
但 ClawPanel 完全没接。这次打通整条链路:浏览器 → Service Worker → 内核 → 系统通知中心。
## 收益(用户视角)
- ClawPanel 浏览器标签/桌面应用关掉后,Agent / Cron / 渠道消息仍能弹到
Windows / macOS / iOS / Android 系统通知中心
- 锁屏可见,可离线接收(推送服务由浏览器厂商分发)
- 是下一版主推卖点
## 实施(无需新增 Tauri 命令)
- wsClient.request 直接走 WebSocket 调内核 4 个 RPC
## 前端封装 src/lib/push-web.js
- isPushSupported() / pushPermission() / requestPushPermission()
- ensureServiceWorker() 注册 /push-sw.js(幂等)
- subscribePush() 完整流程:权限 → SW → push.web.vapidPublicKey → PushManager.subscribe → push.web.subscribe 上报内核
- unsubscribePush() 本地取消 + 通知内核清理
- sendTestPush(title, body) 调 push.web.test 广播测试
- getCurrentSubscription() / isLocallySubscribed() 状态查询
- urlBase64ToUint8Array / arrayBufferToBase64Url 工具函数
(VAPID 公钥 base64url ↔ 二进制,订阅 keys 编码)
## Service Worker public/push-sw.js
- skipWaiting + clients.claim 立即激活
- push 事件:解析 JSON payload → showNotification(含 icon / badge / tag / requireInteraction)
- notificationclick:优先聚焦已打开标签 + postMessage 跳转 url;
没有窗口就 openWindow 新开
- 所有路径容错(payload 解析失败 fallback 到默认文案)
## UI 页面 src/pages/notifications.js
- 状态行:通知权限 + 订阅状态(彩色徽章)
- 端点摘要(订阅成功后展示截断的 endpoint,方便用户确认)
- 三个动作按钮(互斥):启用 / 取消订阅 / 发测试通知
- 测试通知会显示「已投递到 N 个订阅」提示
- 不支持环境(Tauri 1.x 桌面壳或老浏览器)显示友好的「Push not supported here」空状态
- 全程走 humanizeError 友好错误提示
## i18n src/locales/modules/notifications.js
- 26 个键 × 11 语言全覆盖
- 含权限徽章 / 操作按钮 / 流程提示 / 不支持环境说明
## 入口
- OpenClaw 引擎「配置」section 新增「推送通知」入口
- sidebar.notifications i18n(短词「推送通知 / Push」)
- 路由 /notifications 注册到 OpenClaw 引擎
- Hermes 引擎暂不注册(push.web.* 是 OpenClaw 内核的 RPC)
## CSS
- 加 .push-status-row / .push-status-item / .push-status-label / .push-status-value
- 复用现有 .lazy-deps-badge.{ok,warn,unknown} 样式
## 待跟进
- iOS Safari 16.4+ 需用户先把 ClawPanel 添加到主屏才能收 push(已知限制,文档跟进)
- 真实流量(不只是 push.web.test)需 OpenClaw 内核侧把通知事件主动 send 出来;
本 PR 把订阅渠道彻底打通,后续内核怎么用现成订阅发送是另一题
- 累计变动:4 新文件 + 4 修改
42 lines
6.3 KiB
JavaScript
42 lines
6.3 KiB
JavaScript
import { _ } from '../helper.js'
|
||
|
||
export default {
|
||
collapse: _('折叠/展开', 'Collapse / Expand', '摺疊/展開', '折りたたみ/展開', '접기/펼치기', 'Thu gọn', 'Colapsar', 'Recolher', 'Свернуть', 'Réduire', 'Einklappen'),
|
||
closeMenu: _('关闭菜单', 'Close menu', '關閉菜單', 'メニューを閉じる', '메뉴 닫기', 'Đóng menu', 'Cerrar menú', 'Fechar menu', 'Закрыть меню', 'Fermer le menu', 'Menü schließen'),
|
||
themeLight: _('日间模式', 'Light Mode', '日間模式', 'ライトモード', '라이트 모드', 'Sáng', 'Claro', 'Claro', 'Светлая', 'Clair', 'Hell'),
|
||
themeDark: _('夜间模式', 'Dark Mode', '夜間模式', 'ダークモード', '다크 모드', 'Tối', 'Oscuro', 'Escuro', 'Тёмная', 'Sombre', 'Dunkel'),
|
||
sectionMonitor: _('监控', 'Monitor', '監控', 'モニター', '모니터링', 'Giám sát', 'Monitoreo', 'Monitoramento', 'Мониторинг', 'Surveillance', 'Überwachung'),
|
||
sectionConfig: _('配置', 'Config', '設定', '設定', '설정', 'Cấu hình', 'Configuración', 'Configuração', 'Настройки', 'Configuration', 'Konfiguration'),
|
||
sectionData: _('数据', 'Data', '資料', 'データ', '데이터', 'Dữ liệu', 'Datos', 'Dados', 'Данные', 'Données', 'Daten'),
|
||
sectionManage: _('管理', 'Manage', '管理', '管理', '관리', 'Quản lý', 'Gestión', 'Gestão', 'Управление', 'Gestion', 'Verwaltung'),
|
||
sectionExtension: _('扩展', 'Extensions', '擴充', '拡張', '확장', 'Mở rộng', 'Extensiones', 'Extensões', 'Расширения', '', 'Erweiterungen'),
|
||
dashboard: _('仪表盘', 'Dashboard', '儀表盤', 'ダッシュボード', '대시보드', 'Bảng điều khiển', 'Panel', 'Painel', 'Панель', 'Tableau de bord'),
|
||
assistant: _('晴辰助手', 'Assistant', '', 'アシスタント', '어시스턴트', 'Trợ lý', 'Asistente', 'Assistente', 'Ассистент', '', 'Assistent'),
|
||
chat: _('实时聊天', 'Live Chat', '實時聊天', 'ライブチャット', '실시간 채팅', 'Trò chuyện', 'Chat', 'Chat', 'Чат', 'Chat', 'Live-Chat'),
|
||
sessions: _('会话浏览', 'Sessions', '會話瀏覽', 'セッション', '세션'),
|
||
services: _('服务管理', 'Services', '服務管理', 'サービス管理', '서비스 관리', 'Dịch vụ', 'Servicios', 'Serviços', 'Сервисы', '', 'Dienste'),
|
||
logs: _('日志查看', 'Logs', '日誌查看', 'ログ', '로그', 'Nhật ký', 'Registros', '', 'Журналы', 'Journaux', 'Protokolle'),
|
||
models: _('模型配置', 'Models', '模型設定', 'モデル設定', '모델 설정', 'Mô hình', 'Modelos', 'Modelos', 'Модели', 'Modèles', 'Modelle'),
|
||
agents: _('Agent 管理', 'Agents', '', 'Agent 管理', 'Agent 관리', 'Agent', 'Agentes', 'Agentes', 'Агенты', '', 'Agenten'),
|
||
gateway: _('Gateway', 'Gateway'),
|
||
channels: _('消息渠道', 'Channels', '訊息頻道', 'チャンネル', '채널', 'Kênh', 'Canales', 'Canais', 'Каналы', 'Canaux', 'Kanäle'),
|
||
communication: _('通信与自动化', 'Communication', '通信與自動化', '通信と自動化', '통신 및 자동화', 'Truyền thông', 'Comunicación', 'Comunicação', 'Коммуникации', '', 'Kommunikation'),
|
||
security: _('安全设置', 'Security', '安全設定', 'セキュリティ', '보안 설정', 'Bảo mật', 'Seguridad', 'Segurança', 'Безопасность', 'Sécurité', 'Sicherheit'),
|
||
memory: _('记忆文件', 'Memory', '記憶檔案', 'メモリ', '메모리', 'Bộ nhớ', 'Memoria', 'Memória', 'Память', 'Mémoire', 'Speicher'),
|
||
dreaming: _('梦境模式', 'Dreaming', '夢境模式', 'ドリーミング', '드리밍', 'Dreaming', 'Dreaming', 'Dreaming', 'Dreaming', 'Dreaming', 'Dreaming'),
|
||
cron: _('定时任务', 'Cron Jobs', '定時任務', 'スケジュールタスク', '예약 작업', 'Tác vụ định kỳ', 'Tareas', 'Tarefas', 'Планировщик', 'Tâches planifiées', 'Geplante Aufgaben'),
|
||
usage: _('使用情况', 'Usage', '使用情況', '使用状況', '사용 현황', 'Sử dụng', 'Uso', 'Uso', 'Использование', 'Utilisation', 'Nutzung'),
|
||
skills: _('Skills', 'Skills'),
|
||
pluginHub: _('插件中心', 'Plugin Hub', '插件中心', 'プラグインハブ', '플러그인 허브', 'Trung tâm plugin', 'Centro de plugins', 'Centro de plugins', 'Центр плагинов', 'Centre de plugins', 'Plugin-Hub'),
|
||
extensions: _('扩展与主题', 'Extensions', '擴展與主題', '拡張とテーマ', '확장 및 테마'),
|
||
settings: _('面板设置', 'Settings', '面板設定', 'パネル設定', '패널 설정', 'Cài đặt', 'Configuración', 'Configurações', 'Настройки', 'Paramètres', 'Einstellungen'),
|
||
diagnose: _('连接诊断', 'Connection Diagnosis', '連線診斷', '接続診断', '연결 진단', 'Chẩn đoán kết nối', 'Diagnóstico de conexión', 'Diagnóstico de conexão', 'Диагностика подключения', 'Diagnostic de connexion', 'Verbindungsdiagnose'),
|
||
chatDebug: _('系统诊断', 'Diagnostics', '系統诊斷', 'システム診断', '시스템 진단', 'Chẩn đoán', 'Diagnóstico', 'Diagnóstico', 'Диагностика', 'Diagnostic', 'Diagnose'),
|
||
checkRepair: _('检测与修复', 'Check & Repair', '檢測與修復', '検出と修復', '검사 및 수리', 'Kiểm tra & Sửa chữa', 'Verificar y reparar', 'Verificar e reparar', 'Проверка и ремонт', 'Vérifier et réparer', 'Prüfen & Reparieren'),
|
||
routeMap: _('路由地图', 'Route Map', '路由地圖', 'ルートマップ', '라우트 맵', 'Bản đồ tuyến', 'Mapa de rutas', 'Mapa de rotas', 'Карта маршрутов', 'Carte des routes', 'Routenkarte'),
|
||
about: _('关于', 'About', '關於', 'について', '정보', 'Giới thiệu', 'Acerca de', 'Sobre', 'О программе', 'À propos', 'Über'),
|
||
glossary: _('术语', 'Glossary', '術語', '用語', '용어', 'Thuật ngữ', 'Glosario', 'Glossário', 'Глоссарий', 'Glossaire', 'Glossar'),
|
||
notifications: _('推送通知', 'Push', '推送通知', 'プッシュ', '푸시', 'Đẩy', 'Push', 'Push', 'Push', 'Push', 'Push'),
|
||
setup: _('初始设置', 'Setup', '初始設定', '初期設定', '초기 설정', 'Thiết lập', 'Configuración inicial', 'Configuração inicial', 'Начальная настройка', 'Configuration initiale', 'Ersteinrichtung'),
|
||
}
|