diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index ba9e854..eea72ef 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -471,5 +471,42 @@ "testPlaying": "Playing test audio", "audioNotFound": "Selected audio not found" } + }, + "notification": { + "title": "Browser Notifications", + "settingLabel": "Browser Notifications", + "description": "Get notified when new sessions are created (only when in background)", + "enabled": "Notifications enabled ✅", + "disabled": "Notifications disabled", + "permissionRequired": "Notification permission required to enable this feature", + "permissionDenied": "Browser has blocked notifications, please allow in browser settings", + "permissionGranted": "Granted", + "permissionDeniedStatus": "Denied (please modify in browser settings)", + "permissionDefault": "Not set", + "notSupported": "Your browser does not support notifications", + "enableFailed": "Failed to enable notifications", + "test": "Send test notification", + "testTitle": "Test Notification", + "testDescription": "Send a test notification to confirm it's working", + "autoplayBlocked": "Browser blocked autoplay, please click the page to enable audio notifications", + "triggerTitle": "Notification Trigger Context", + "triggerDescription": "Choose when to receive notifications", + "triggerModeUpdated": "Notification trigger mode updated", + "trigger": { + "focusLost": "When window loses focus (switching to other apps)", + "tabSwitch": "When switching to other browser tabs", + "background": "When window is minimized or hidden", + "always": "Always notify (including foreground)" + }, + "browser": { + "title": "MCP Feedback - New Session", + "ready": "Ready", + "unknownProject": "Unknown Project", + "testTitle": "Test Notification", + "testBody": "This is a test notification, it will close automatically in 5 seconds", + "notSupported": "Your browser does not support notifications", + "permissionRequired": "Please grant notification permission first", + "criticalTitle": "MCP Feedback - Warning" + } } } diff --git a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json index 0578ec8..29f0830 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -470,5 +470,42 @@ }, "stats": { "detailedStats": "详细统计信息" + }, + "notification": { + "title": "浏览器通知", + "settingLabel": "浏览器通知", + "description": "新会话建立时通知(仅在后台运行时)", + "enabled": "通知已启用 ✅", + "disabled": "通知已关闭", + "permissionRequired": "需要通知权限才能启用此功能", + "permissionDenied": "浏览器已封锁通知,请在浏览器设置中允许", + "permissionGranted": "已授权", + "permissionDeniedStatus": "已拒绝(请在浏览器设置中修改)", + "permissionDefault": "尚未设置", + "notSupported": "您的浏览器不支持通知功能", + "enableFailed": "启用通知失败", + "test": "发送测试通知", + "testTitle": "测试通知", + "testDescription": "发送测试通知以确认功能正常", + "autoplayBlocked": "浏览器阻止音效自动播放,请点击页面以启用音效通知", + "triggerTitle": "通知触发场景", + "triggerDescription": "选择何时接收通知", + "triggerModeUpdated": "通知触发模式已更新", + "trigger": { + "focusLost": "窗口失去焦点时(切换到其他应用程序)", + "tabSwitch": "切换到其他标签页时", + "background": "窗口最小化或隐藏时", + "always": "总是通知(包括前景)" + }, + "browser": { + "title": "MCP Feedback - 新会话", + "ready": "准备就绪", + "unknownProject": "未知项目", + "testTitle": "测试通知", + "testBody": "这是一个测试通知,5秒后将自动关闭", + "notSupported": "您的浏览器不支持通知功能", + "permissionRequired": "请先授权通知权限", + "criticalTitle": "MCP Feedback - 警告" + } } } diff --git a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json index 2e8e147..57dbc8c 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -475,5 +475,42 @@ }, "stats": { "detailedStats": "詳細統計資訊" + }, + "notification": { + "title": "瀏覽器通知", + "settingLabel": "瀏覽器通知", + "description": "新會話建立時通知(僅在背景執行時)", + "enabled": "通知已啟用 ✅", + "disabled": "通知已關閉", + "permissionRequired": "需要通知權限才能啟用此功能", + "permissionDenied": "瀏覽器已封鎖通知,請在瀏覽器設定中允許", + "permissionGranted": "已授權", + "permissionDeniedStatus": "已拒絕(請在瀏覽器設定中修改)", + "permissionDefault": "尚未設定", + "notSupported": "您的瀏覽器不支援通知功能", + "enableFailed": "啟用通知失敗", + "test": "發送測試通知", + "testTitle": "測試通知", + "testDescription": "發送測試通知以確認功能正常", + "autoplayBlocked": "瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知", + "triggerTitle": "通知觸發情境", + "triggerDescription": "選擇何時接收通知", + "triggerModeUpdated": "通知觸發模式已更新", + "trigger": { + "focusLost": "視窗失去焦點時(切換到其他應用程式)", + "tabSwitch": "切換到其他標籤頁時", + "background": "視窗最小化或隱藏時", + "always": "總是通知(包括前景)" + }, + "browser": { + "title": "MCP Feedback - 新會話", + "ready": "準備就緒", + "unknownProject": "未知專案", + "testTitle": "測試通知", + "testBody": "這是一個測試通知,5秒後將自動關閉", + "notSupported": "您的瀏覽器不支援通知功能", + "permissionRequired": "請先授權通知權限", + "criticalTitle": "MCP Feedback - 警告" + } } } diff --git a/src/mcp_feedback_enhanced/web/static/css/audio-management.css b/src/mcp_feedback_enhanced/web/static/css/audio-management.css index c10ec86..1629327 100644 --- a/src/mcp_feedback_enhanced/web/static/css/audio-management.css +++ b/src/mcp_feedback_enhanced/web/static/css/audio-management.css @@ -8,44 +8,15 @@ /* ===== 音效管理區塊樣式 ===== */ -.audio-management-section { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - border-radius: 12px; - padding: 20px; - margin-bottom: 20px; - transition: all 0.3s ease; -} - -.audio-management-section:hover { - border-color: var(--accent-color); - box-shadow: 0 2px 8px rgba(0, 122, 204, 0.1); -} - -.audio-management-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - padding-bottom: 12px; - border-bottom: 1px solid var(--border-color); -} - -.audio-management-title { - color: var(--text-primary); - font-size: 16px; - font-weight: 600; - margin: 0; - display: flex; - align-items: center; - gap: 8px; -} +/* 音效描述文字 */ .audio-management-description { color: var(--text-secondary); font-size: 14px; margin-bottom: 20px; line-height: 1.4; + padding-bottom: 16px; + border-bottom: 1px solid var(--border-color); } /* ===== 音效設定控制項樣式 ===== */ diff --git a/src/mcp_feedback_enhanced/web/static/css/notification-settings.css b/src/mcp_feedback_enhanced/web/static/css/notification-settings.css new file mode 100644 index 0000000..6cef93d --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/css/notification-settings.css @@ -0,0 +1,152 @@ +/** + * MCP Feedback Enhanced - 瀏覽器通知設定樣式 + * ======================================== + */ + +/* 權限狀態顯示 */ +.permission-status { + font-size: 12px; + margin-top: 8px; + display: block; /* 使用 block 讓內容自然流動 */ +} + +.permission-status span { + display: inline-block; /* 確保內容在同一行 */ + white-space: nowrap; /* 防止文字換行 */ + line-height: 1.2; /* 適當的行高 */ +} + +/* 權限狀態樣式 */ +.status-granted { + color: #059669; +} + +.status-denied { + color: #dc2626; +} + +.status-default { + color: #6366f1; +} + +.status-unsupported { + color: #d97706; +} + +/* 觸發情境選項 */ +.notification-trigger { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +.trigger-options { + margin-top: 12px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.radio-option { + display: flex; + align-items: center; + padding: 8px 12px; + background-color: var(--bg-secondary, #f9fafb); + border-radius: 6px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.radio-option:hover { + background-color: var(--bg-hover, #f3f4f6); +} + +.radio-option input[type="radio"] { + margin-right: 10px; + cursor: pointer; +} + +.radio-option span { + font-size: 14px; + color: var(--text-primary, #1f2937); + line-height: 1.4; +} + +/* 測試按鈕區塊 */ +.notification-actions { + margin-top: 16px; +} + +.notification-actions .btn-primary { + padding: 8px 16px; + font-size: 14px; + background-color: var(--primary-color, #007acc); + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.notification-actions .btn-primary:hover { + background-color: var(--primary-hover, #0066b3); +} + +/* 通知設定專用的 setting-info 調整 */ +#notificationSettings .setting-info { + max-width: calc(100% - 100px); /* 為開關預留空間 */ +} + +/* 響應式設計 */ +@media (max-width: 768px) { + .permission-status { + font-size: 11px; + } + + #notificationSettings .setting-info { + max-width: calc(100% - 80px); /* 小螢幕上調整空間 */ + } + + .radio-option { + padding: 6px 10px; + } + + .radio-option span { + font-size: 13px; + } +} + +/* 深色模式支援 */ +@media (prefers-color-scheme: dark) { + .status-granted { + color: #34d399; + } + + .status-denied { + color: #f87171; + } + + .status-default { + color: #a5b4fc; + } + + .status-unsupported { + color: #fbbf24; + } + + .notification-trigger { + border-top-color: rgba(255, 255, 255, 0.1); + } + + .radio-option { + background-color: rgba(255, 255, 255, 0.05); + } + + .radio-option:hover { + background-color: rgba(255, 255, 255, 0.1); + } + + .radio-option span { + color: #e5e7eb; + } +} \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 8c848c4..7b23812 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -42,6 +42,10 @@ this.audioManager = null; this.audioSettingsUI = null; + // 通知管理器 + this.notificationManager = null; + this.notificationSettings = null; + // 自動提交管理器 this.autoSubmitManager = null; @@ -239,25 +243,28 @@ // 10. 初始化音效管理器 self.initializeAudioManagers(); - // 11. 初始化自動提交管理器 + // 11. 初始化通知管理器 + self.initializeNotificationManager(); + + // 12. 初始化自動提交管理器 self.initializeAutoSubmitManager(); - // 12. 初始化 Textarea 高度管理器 + // 13. 初始化 Textarea 高度管理器 self.initializeTextareaHeightManager(); - // 13. 應用設定到 UI + // 14. 應用設定到 UI self.settingsManager.applyToUI(); - // 14. 初始化各個管理器 + // 15. 初始化各個管理器 self.uiManager.initTabs(); self.imageHandler.init(); - // 15. 檢查並啟動自動提交(如果條件滿足) + // 16. 檢查並啟動自動提交(如果條件滿足) setTimeout(function() { self.checkAndStartAutoSubmit(); }, 500); // 延遲 500ms 確保所有初始化完成 - // 16. 播放啟動音效(如果音效已啟用) + // 17. 播放啟動音效(如果音效已啟用) setTimeout(function() { if (self.audioManager) { self.audioManager.playStartupNotification(); @@ -523,6 +530,52 @@ } }; + /** + * 初始化通知管理器 + */ + FeedbackApp.prototype.initializeNotificationManager = function() { + console.log('🔔 初始化通知管理器...'); + + try { + // 檢查通知模組是否已載入 + if (!window.MCPFeedback.NotificationManager) { + console.warn('⚠️ 通知模組未載入,跳過初始化'); + return; + } + + // 1. 初始化通知管理器 + this.notificationManager = new window.MCPFeedback.NotificationManager({ + t: window.i18nManager ? window.i18nManager.t.bind(window.i18nManager) : function(key, defaultValue) { return defaultValue || key; } + }); + this.notificationManager.initialize(); + + // 2. 初始化通知設定 UI + if (window.MCPFeedback.NotificationSettings) { + const notificationContainer = document.querySelector('#notificationSettingsContainer'); + console.log('🔍 通知設定容器:', notificationContainer); + + if (notificationContainer) { + this.notificationSettings = new window.MCPFeedback.NotificationSettings({ + container: notificationContainer, + notificationManager: this.notificationManager, + t: window.i18nManager ? window.i18nManager.t.bind(window.i18nManager) : function(key, defaultValue) { return defaultValue || key; } + }); + this.notificationSettings.initialize(); + console.log('✅ 通知設定 UI 初始化完成'); + } else { + console.error('❌ 找不到通知設定容器元素 notificationSettingsContainer'); + } + } else { + console.warn('⚠️ NotificationSettings 模組未載入'); + } + + console.log('✅ 通知管理器初始化完成'); + + } catch (error) { + console.error('❌ 通知管理器初始化失敗:', error); + } + }; + /** * 初始化 Textarea 高度管理器 */ @@ -720,6 +773,14 @@ this.audioManager.playNotification(); } + // 發送瀏覽器通知 + if (this.notificationManager && data.session_info) { + this.notificationManager.notifyNewSession( + data.session_info.session_id, + data.session_info.project_directory || data.project_directory || '未知專案' + ); + } + // 顯示新會話通知 window.MCPFeedback.Utils.showMessage( data.message || '新的 MCP 會話已創建,正在更新內容...', diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js index 19d197e..587a95d 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js @@ -70,17 +70,18 @@ */ AudioSettingsUI.prototype.createUI = function() { const html = ` -
-
-

+
+
+

🔊 音效通知設定 -

+

-
- 設定會話更新時的音效通知 -
- -
+
+
+ 設定會話更新時的音效通知 +
+ +
@@ -142,6 +143,7 @@
+
`; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-manager.js new file mode 100644 index 0000000..b779f29 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-manager.js @@ -0,0 +1,360 @@ +/** + * MCP Feedback Enhanced - 通知管理模組 + * =================================== + * + * 處理瀏覽器通知功能,支援新會話通知和緊急狀態通知 + * 使用 Web Notification API,提供極簡的通知體驗 + */ + +(function() { + 'use strict'; + + // 確保命名空間存在 + window.MCPFeedback = window.MCPFeedback || {}; + const Utils = window.MCPFeedback.Utils; + + /** + * 通知管理器建構函數 + */ + function NotificationManager(options) { + options = options || {}; + + // 通知設定 + this.enabled = false; + this.permission = 'default'; + this.triggerMode = 'focusLost'; // 預設為失去焦點時通知 + + // 狀態追蹤 + this.lastSessionId = null; // 避免重複通知同一會話 + this.isInitialized = false; + this.hasFocus = true; // 追蹤視窗焦點狀態 + + // 設定鍵名 + this.STORAGE_KEY = 'notificationsEnabled'; + this.TRIGGER_MODE_KEY = 'notificationTriggerMode'; + + // i18n 翻譯函數 + this.t = options.t || function(key, defaultValue) { return defaultValue || key; }; + + console.log('🔔 NotificationManager 建構完成'); + } + + /** + * 初始化通知管理器 + */ + NotificationManager.prototype.initialize = function() { + if (this.isInitialized) return; + + // 檢查瀏覽器支援 + if (!this.checkBrowserSupport()) { + console.warn('⚠️ 瀏覽器不支援 Notification API'); + return; + } + + // 載入設定 + this.loadSettings(); + + // 更新權限狀態 + this.updatePermissionStatus(); + + // 設定焦點追蹤 + this.setupFocusTracking(); + + this.isInitialized = true; + console.log('✅ NotificationManager 初始化完成', { + enabled: this.enabled, + permission: this.permission, + triggerMode: this.triggerMode + }); + }; + + /** + * 檢查瀏覽器支援 + */ + NotificationManager.prototype.checkBrowserSupport = function() { + return 'Notification' in window; + }; + + /** + * 載入設定 + */ + NotificationManager.prototype.loadSettings = function() { + try { + this.enabled = localStorage.getItem(this.STORAGE_KEY) === 'true'; + this.triggerMode = localStorage.getItem(this.TRIGGER_MODE_KEY) || 'focusLost'; + } catch (error) { + console.error('❌ 載入通知設定失敗:', error); + this.enabled = false; + this.triggerMode = 'focusLost'; + } + }; + + /** + * 儲存設定 + */ + NotificationManager.prototype.saveSettings = function() { + try { + localStorage.setItem(this.STORAGE_KEY, this.enabled.toString()); + } catch (error) { + console.error('❌ 儲存通知設定失敗:', error); + } + }; + + /** + * 更新權限狀態 + */ + NotificationManager.prototype.updatePermissionStatus = function() { + if (this.checkBrowserSupport()) { + this.permission = Notification.permission; + } + }; + + /** + * 請求通知權限 + */ + NotificationManager.prototype.requestPermission = async function() { + if (!this.checkBrowserSupport()) { + throw new Error('瀏覽器不支援通知功能'); + } + + try { + const result = await Notification.requestPermission(); + this.permission = result; + return result; + } catch (error) { + console.error('❌ 請求通知權限失敗:', error); + throw error; + } + }; + + /** + * 啟用通知 + */ + NotificationManager.prototype.enable = async function() { + // 檢查權限 + if (this.permission === 'default') { + const result = await this.requestPermission(); + if (result !== 'granted') { + return false; + } + } else if (this.permission === 'denied') { + console.warn('⚠️ 通知權限已被拒絕'); + return false; + } + + this.enabled = true; + this.saveSettings(); + console.log('✅ 通知已啟用'); + return true; + }; + + /** + * 停用通知 + */ + NotificationManager.prototype.disable = function() { + this.enabled = false; + this.saveSettings(); + console.log('🔇 通知已停用'); + }; + + /** + * 設定焦點追蹤 + */ + NotificationManager.prototype.setupFocusTracking = function() { + const self = this; + + // 監聽焦點事件 + window.addEventListener('focus', function() { + self.hasFocus = true; + console.log('👁️ 視窗獲得焦點'); + }); + + window.addEventListener('blur', function() { + self.hasFocus = false; + console.log('👁️ 視窗失去焦點'); + }); + }; + + /** + * 檢查是否可以顯示通知 + */ + NotificationManager.prototype.canNotify = function() { + if (!this.enabled || this.permission !== 'granted') { + return false; + } + + // 根據觸發模式判斷 + switch (this.triggerMode) { + case 'always': + return true; // 總是通知 + case 'background': + return document.hidden; // 只在頁面隱藏時通知 + case 'tabSwitch': + return document.hidden; // 只在切換標籤頁時通知 + case 'focusLost': + return document.hidden || !this.hasFocus; // 失去焦點或頁面隱藏時通知 + default: + return document.hidden || !this.hasFocus; + } + }; + + /** + * 新會話通知 + */ + NotificationManager.prototype.notifyNewSession = function(sessionId, projectPath) { + // 避免重複通知 + if (sessionId === this.lastSessionId) { + console.log('🔇 跳過重複的會話通知'); + return; + } + + // 檢查是否可以通知 + if (!this.canNotify()) { + console.log('🔇 不符合通知條件', { + enabled: this.enabled, + permission: this.permission, + pageHidden: document.hidden, + hasFocus: this.hasFocus, + triggerMode: this.triggerMode + }); + return; + } + + this.lastSessionId = sessionId; + + try { + const notification = new Notification(this.t('notification.browser.title', 'MCP Feedback - 新會話'), { + body: `${this.t('notification.browser.ready', '準備就緒')}: ${this.truncatePath(projectPath)}`, + icon: '/static/icon-192.png', + badge: '/static/icon-192.png', + tag: 'mcp-session', + timestamp: Date.now(), + silent: false + }); + + // 點擊後聚焦視窗 + notification.onclick = () => { + window.focus(); + notification.close(); + console.log('🖱️ 通知被點擊,視窗已聚焦'); + }; + + // 5秒後自動關閉 + setTimeout(() => notification.close(), 5000); + + console.log('🔔 已發送新會話通知', { + sessionId: sessionId, + projectPath: projectPath + }); + } catch (error) { + console.error('❌ 發送通知失敗:', error); + } + }; + + /** + * 緊急通知(連線問題等) + */ + NotificationManager.prototype.notifyCritical = function(type, message) { + if (!this.canNotify()) return; + + try { + const notification = new Notification(this.t('notification.browser.criticalTitle', 'MCP Feedback - 警告'), { + body: message, + icon: '/static/icon-192.png', + badge: '/static/icon-192.png', + tag: 'mcp-critical', + requireInteraction: true, // 需要手動關閉 + timestamp: Date.now() + }); + + notification.onclick = () => { + window.focus(); + notification.close(); + console.log('🖱️ 緊急通知被點擊'); + }; + + console.log('⚠️ 已發送緊急通知', { + type: type, + message: message + }); + } catch (error) { + console.error('❌ 發送緊急通知失敗:', error); + } + }; + + /** + * 路徑截斷顯示 + */ + NotificationManager.prototype.truncatePath = function(path, maxLength) { + maxLength = maxLength || 50; + if (!path || path.length <= maxLength) return path || this.t('notification.browser.unknownProject', '未知專案'); + return '...' + path.slice(-(maxLength - 3)); + }; + + /** + * 設定觸發模式 + */ + NotificationManager.prototype.setTriggerMode = function(mode) { + const validModes = ['always', 'background', 'tabSwitch', 'focusLost']; + if (validModes.includes(mode)) { + this.triggerMode = mode; + try { + localStorage.setItem(this.TRIGGER_MODE_KEY, mode); + console.log('✅ 通知觸發模式已更新:', mode); + } catch (error) { + console.error('❌ 儲存觸發模式失敗:', error); + } + } + }; + + /** + * 獲取當前設定 + */ + NotificationManager.prototype.getSettings = function() { + return { + enabled: this.enabled, + permission: this.permission, + browserSupported: this.checkBrowserSupport(), + triggerMode: this.triggerMode + }; + }; + + /** + * 測試通知 + */ + NotificationManager.prototype.testNotification = function() { + if (!this.checkBrowserSupport()) { + alert(this.t('notification.browser.notSupported', '您的瀏覽器不支援通知功能')); + return; + } + + if (this.permission !== 'granted') { + alert(this.t('notification.browser.permissionRequired', '請先授權通知權限')); + return; + } + + try { + const notification = new Notification(this.t('notification.browser.testTitle', '測試通知'), { + body: this.t('notification.browser.testBody', '這是一個測試通知,5秒後將自動關閉'), + icon: '/static/icon-192.png', + tag: 'mcp-test', + timestamp: Date.now() + }); + + notification.onclick = () => { + notification.close(); + }; + + setTimeout(() => notification.close(), 5000); + + console.log('🔔 測試通知已發送'); + } catch (error) { + console.error('❌ 測試通知失敗:', error); + alert('發送測試通知失敗'); + } + }; + + // 匯出到全域命名空間 + window.MCPFeedback.NotificationManager = NotificationManager; + +})(); \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js b/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js new file mode 100644 index 0000000..9656cb5 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js @@ -0,0 +1,341 @@ +/** + * MCP Feedback Enhanced - 通知設定介面模組 + * ===================================== + * + * 處理瀏覽器通知的設定介面,提供簡單的開關控制 + * 與 NotificationManager 配合使用 + */ + +(function() { + 'use strict'; + + // 確保命名空間存在 + window.MCPFeedback = window.MCPFeedback || {}; + const Utils = window.MCPFeedback.Utils; + + /** + * 通知設定介面建構函數 + */ + function NotificationSettings(options) { + options = options || {}; + + // 容器元素 + this.container = options.container || null; + + // 通知管理器引用 + this.notificationManager = options.notificationManager || null; + + // i18n 翻譯函數 + this.t = options.t || function(key, defaultValue) { return defaultValue || key; }; + + // UI 元素引用 + this.toggle = null; + this.statusDiv = null; + this.testButton = null; + this.triggerOptionsDiv = null; + + console.log('🎨 NotificationSettings 初始化完成'); + } + + /** + * 初始化設定介面 + */ + NotificationSettings.prototype.initialize = function() { + if (!this.container) { + console.error('❌ NotificationSettings 容器未設定'); + return; + } + + if (!this.notificationManager) { + console.error('❌ NotificationManager 未設定'); + return; + } + + this.createUI(); + this.setupEventListeners(); + this.updateUI(); + + console.log('✅ NotificationSettings 初始化完成'); + }; + + /** + * 創建 UI 結構 + */ + NotificationSettings.prototype.createUI = function() { + const html = ` + +
+
+
瀏覽器通知
+
+ 新會話建立時通知(僅在背景執行時) +
+ +
+ +
+
+
+ +
+
+ + + + + + + `; + + this.container.innerHTML = html; + + // 取得元素引用 + this.toggle = this.container.querySelector('#notificationToggle'); + this.statusDiv = this.container.querySelector('#permissionStatus'); + this.testButton = this.container.querySelector('#testNotification'); + this.triggerOptionsDiv = this.container.querySelector('.notification-trigger'); + }; + + /** + * 設置事件監聽器 + */ + NotificationSettings.prototype.setupEventListeners = function() { + const self = this; + + // 開關切換事件 + this.toggle.addEventListener('click', async function(e) { + const isActive = self.toggle.classList.contains('active'); + if (!isActive) { + await self.enableNotifications(); + } else { + self.disableNotifications(); + } + }); + + // 測試按鈕事件 + if (this.testButton) { + this.testButton.addEventListener('click', function() { + self.notificationManager.testNotification(); + }); + } + + // 監聽頁面可見性變化,更新權限狀態 + document.addEventListener('visibilitychange', function() { + self.updatePermissionStatus(); + }); + + // 觸發模式選項事件 + const triggerRadios = this.container.querySelectorAll('input[name="notificationTrigger"]'); + triggerRadios.forEach(function(radio) { + radio.addEventListener('change', function() { + if (radio.checked) { + self.notificationManager.setTriggerMode(radio.value); + self.showMessage( + self.t('notification.triggerModeUpdated', '通知觸發模式已更新'), + 'success' + ); + } + }); + }); + }; + + /** + * 更新 UI 狀態 + */ + NotificationSettings.prototype.updateUI = function() { + const settings = this.notificationManager.getSettings(); + + // 設定開關狀態 + if (settings.enabled) { + this.toggle.classList.add('active'); + } else { + this.toggle.classList.remove('active'); + } + + // 更新權限狀態顯示 + this.updatePermissionStatus(); + + // 顯示/隱藏測試按鈕和觸發選項 + const actionsDiv = this.container.querySelector('.notification-actions'); + if (actionsDiv) { + actionsDiv.style.display = (settings.enabled && settings.permission === 'granted') ? 'block' : 'none'; + } + + if (this.triggerOptionsDiv) { + this.triggerOptionsDiv.style.display = (settings.enabled && settings.permission === 'granted') ? 'block' : 'none'; + + // 設定當前選中的觸發模式 + const currentMode = settings.triggerMode || 'focusLost'; + const radio = this.container.querySelector(`input[name="notificationTrigger"][value="${currentMode}"]`); + if (radio) { + radio.checked = true; + } + } + }; + + /** + * 啟用通知 + */ + NotificationSettings.prototype.enableNotifications = async function() { + try { + const success = await this.notificationManager.enable(); + + if (success) { + this.showMessage(this.t('notification.enabled', '通知已啟用 ✅'), 'success'); + this.updateUI(); + } else { + // 權限被拒絕或其他問題 + this.toggle.classList.remove('active'); + this.updatePermissionStatus(); + + if (this.notificationManager.permission === 'denied') { + this.showMessage( + this.t('notification.permissionDenied', '瀏覽器已封鎖通知,請在瀏覽器設定中允許'), + 'error' + ); + } else { + this.showMessage( + this.t('notification.permissionRequired', '需要通知權限才能啟用此功能'), + 'warning' + ); + } + } + } catch (error) { + console.error('❌ 啟用通知失敗:', error); + this.toggle.checked = false; + this.showMessage( + this.t('notification.enableFailed', '啟用通知失敗'), + 'error' + ); + } + }; + + /** + * 停用通知 + */ + NotificationSettings.prototype.disableNotifications = function() { + this.notificationManager.disable(); + this.showMessage(this.t('notification.disabled', '通知已關閉'), 'info'); + this.updateUI(); + }; + + /** + * 更新權限狀態顯示 + */ + NotificationSettings.prototype.updatePermissionStatus = function() { + const settings = this.notificationManager.getSettings(); + + if (!settings.browserSupported) { + this.statusDiv.innerHTML = `⚠️ 您的瀏覽器不支援通知功能`; + this.statusDiv.className = 'permission-status status-unsupported'; + this.toggle.disabled = true; + return; + } + + const statusMessages = { + 'granted': { + icon: '✅', + text: this.t('notification.permissionGranted', '已授權'), + class: 'status-granted', + i18nKey: 'notification.permissionGranted' + }, + 'denied': { + icon: '❌', + text: this.t('notification.permissionDeniedStatus', '已拒絕(請在瀏覽器設定中修改)'), + class: 'status-denied', + i18nKey: 'notification.permissionDeniedStatus' + }, + 'default': { + icon: '⏸', + text: this.t('notification.permissionDefault', '尚未設定'), + class: 'status-default', + i18nKey: 'notification.permissionDefault' + } + }; + + const status = statusMessages[settings.permission] || statusMessages['default']; + + // 將圖標和文字合併在同一個元素內,並加入 data-i18n 屬性以支援動態語言切換 + this.statusDiv.innerHTML = `${status.icon} ${status.text}`; + this.statusDiv.className = `permission-status ${status.class}`; + }; + + /** + * 顯示訊息 + */ + NotificationSettings.prototype.showMessage = function(message, type) { + // 使用 Utils 的訊息顯示功能 + if (Utils && Utils.showMessage) { + Utils.showMessage(message, type); + } else { + console.log(`[${type}] ${message}`); + } + }; + + /** + * 重新整理介面 + */ + NotificationSettings.prototype.refresh = function() { + this.updateUI(); + }; + + /** + * 清理資源 + */ + NotificationSettings.prototype.destroy = function() { + // 移除事件監聽器 + if (this.toggle) { + this.toggle.removeEventListener('change', this.enableNotifications); + } + + if (this.testButton) { + this.testButton.removeEventListener('click', this.notificationManager.testNotification); + } + + // 清空容器 + if (this.container) { + this.container.innerHTML = ''; + } + + console.log('🧹 NotificationSettings 已清理'); + }; + + // 匯出到全域命名空間 + window.MCPFeedback.NotificationSettings = NotificationSettings; + +})(); \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index ee2efee..2b3f774 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -14,6 +14,7 @@ +