mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 02:22:26 +08:00
✨ 增加系統通知功能
This commit is contained in:
parent
7b6b177031
commit
2ec789280a
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 - 警告"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 - 警告"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/* ===== 音效設定控制項樣式 ===== */
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 會話已創建,正在更新內容...',
|
||||
|
@ -70,17 +70,18 @@
|
||||
*/
|
||||
AudioSettingsUI.prototype.createUI = function() {
|
||||
const html = `
|
||||
<div class="audio-management-section">
|
||||
<div class="audio-management-header">
|
||||
<h4 class="audio-management-title" data-i18n="audio.notification.title">
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h3 class="settings-card-title" data-i18n="audio.notification.title">
|
||||
🔊 音效通知設定
|
||||
</h4>
|
||||
</div>
|
||||
<div class="audio-management-description" data-i18n="audio.notification.description">
|
||||
設定會話更新時的音效通知
|
||||
</h3>
|
||||
</div>
|
||||
<div class="settings-card-body">
|
||||
<div class="audio-management-description" data-i18n="audio.notification.description">
|
||||
設定會話更新時的音效通知
|
||||
</div>
|
||||
|
||||
<div class="audio-settings-controls">
|
||||
<div class="audio-settings-controls">
|
||||
<!-- 啟用開關 -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
@ -142,6 +143,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
})();
|
@ -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 = `
|
||||
<!-- 啟用開關 -->
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="notification.settingLabel">瀏覽器通知</div>
|
||||
<div class="setting-description" data-i18n="notification.description">
|
||||
新會話建立時通知(僅在背景執行時)
|
||||
</div>
|
||||
<!-- 權限狀態 -->
|
||||
<div id="permissionStatus" class="permission-status">
|
||||
<!-- 動態更新 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button type="button" id="notificationToggle" class="toggle-btn" aria-label="切換通知">
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知觸發情境 -->
|
||||
<div class="setting-item notification-trigger" style="display: none;">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="notification.triggerTitle">通知觸發情境</div>
|
||||
<div class="setting-description" data-i18n="notification.triggerDescription">選擇何時接收通知</div>
|
||||
</div>
|
||||
<div class="trigger-options">
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="focusLost" checked>
|
||||
<span data-i18n="notification.trigger.focusLost">視窗失去焦點時(切換到其他應用程式)</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="tabSwitch">
|
||||
<span data-i18n="notification.trigger.tabSwitch">切換到其他標籤頁時</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="background">
|
||||
<span data-i18n="notification.trigger.background">視窗最小化或隱藏時</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="notificationTrigger" value="always">
|
||||
<span data-i18n="notification.trigger.always">總是通知(包括前景)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 測試按鈕 -->
|
||||
<div class="setting-item notification-actions" style="display: none;">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="notification.testTitle">測試通知</div>
|
||||
<div class="setting-description" data-i18n="notification.testDescription">發送測試通知以確認功能正常</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<button type="button" id="testNotification" class="btn-primary">
|
||||
<span data-i18n="notification.test">發送測試通知</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 = `<span data-i18n="notification.notSupported">⚠️ 您的瀏覽器不支援通知功能</span>`;
|
||||
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 = `<span data-i18n="${status.i18nKey}">${status.icon} ${status.text}</span>`;
|
||||
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;
|
||||
|
||||
})();
|
@ -14,6 +14,7 @@
|
||||
<link rel="stylesheet" href="/static/css/session-management.css">
|
||||
<link rel="stylesheet" href="/static/css/prompt-management.css">
|
||||
<link rel="stylesheet" href="/static/css/audio-management.css">
|
||||
<link rel="stylesheet" href="/static/css/notification-settings.css">
|
||||
<style>
|
||||
/* 僅保留必要的頁面特定樣式和響應式調整 */
|
||||
|
||||
@ -821,13 +822,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 音效通知設定卡片 -->
|
||||
<!-- 音效通知設定 -->
|
||||
<div id="audioManagementContainer">
|
||||
<!-- 音效管理 UI 將在這裡動態生成 -->
|
||||
</div>
|
||||
|
||||
<!-- 瀏覽器通知設定卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h3 class="settings-card-title" data-i18n="audio.notification.title">🔊 音效通知設定</h3>
|
||||
<h3 class="settings-card-title">
|
||||
<span data-i18n="notification.title">🔔 瀏覽器通知</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="settings-card-body" id="audioManagementContainer">
|
||||
<!-- 音效管理 UI 將在這裡動態生成 -->
|
||||
<div class="settings-card-body" id="notificationSettingsContainer">
|
||||
<!-- 通知設定 UI 將在這裡動態生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1052,6 +1060,10 @@
|
||||
<script src="/static/js/modules/audio/audio-manager.js?v=2025010510"></script>
|
||||
<script src="/static/js/modules/audio/audio-settings-ui.js?v=2025010510"></script>
|
||||
|
||||
<!-- 通知模組 -->
|
||||
<script src="/static/js/modules/notification/notification-manager.js?v=2025010510"></script>
|
||||
<script src="/static/js/modules/notification/notification-settings.js?v=2025010510"></script>
|
||||
|
||||
<!-- 其他模組 -->
|
||||
<script src="/static/js/modules/utils.js?v=2025010510"></script>
|
||||
<script src="/static/js/modules/tab-manager.js?v=2025010510"></script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user