增加系統通知功能

This commit is contained in:
Minidoracat 2025-06-28 00:57:38 +08:00
parent 7b6b177031
commit 2ec789280a
10 changed files with 1061 additions and 51 deletions

View File

@ -471,5 +471,42 @@
"testPlaying": "Playing test audio", "testPlaying": "Playing test audio",
"audioNotFound": "Selected audio not found" "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"
}
} }
} }

View File

@ -470,5 +470,42 @@
}, },
"stats": { "stats": {
"detailedStats": "详细统计信息" "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 - 警告"
}
} }
} }

View File

@ -475,5 +475,42 @@
}, },
"stats": { "stats": {
"detailedStats": "詳細統計資訊" "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 - 警告"
}
} }
} }

View File

@ -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 { .audio-management-description {
color: var(--text-secondary); color: var(--text-secondary);
font-size: 14px; font-size: 14px;
margin-bottom: 20px; margin-bottom: 20px;
line-height: 1.4; line-height: 1.4;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-color);
} }
/* ===== 音效設定控制項樣式 ===== */ /* ===== 音效設定控制項樣式 ===== */

View File

@ -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;
}
}

View File

@ -42,6 +42,10 @@
this.audioManager = null; this.audioManager = null;
this.audioSettingsUI = null; this.audioSettingsUI = null;
// 通知管理器
this.notificationManager = null;
this.notificationSettings = null;
// 自動提交管理器 // 自動提交管理器
this.autoSubmitManager = null; this.autoSubmitManager = null;
@ -239,25 +243,28 @@
// 10. 初始化音效管理器 // 10. 初始化音效管理器
self.initializeAudioManagers(); self.initializeAudioManagers();
// 11. 初始化自動提交管理器 // 11. 初始化通知管理器
self.initializeNotificationManager();
// 12. 初始化自動提交管理器
self.initializeAutoSubmitManager(); self.initializeAutoSubmitManager();
// 12. 初始化 Textarea 高度管理器 // 13. 初始化 Textarea 高度管理器
self.initializeTextareaHeightManager(); self.initializeTextareaHeightManager();
// 13. 應用設定到 UI // 14. 應用設定到 UI
self.settingsManager.applyToUI(); self.settingsManager.applyToUI();
// 14. 初始化各個管理器 // 15. 初始化各個管理器
self.uiManager.initTabs(); self.uiManager.initTabs();
self.imageHandler.init(); self.imageHandler.init();
// 15. 檢查並啟動自動提交(如果條件滿足) // 16. 檢查並啟動自動提交(如果條件滿足)
setTimeout(function() { setTimeout(function() {
self.checkAndStartAutoSubmit(); self.checkAndStartAutoSubmit();
}, 500); // 延遲 500ms 確保所有初始化完成 }, 500); // 延遲 500ms 確保所有初始化完成
// 16. 播放啟動音效(如果音效已啟用) // 17. 播放啟動音效(如果音效已啟用)
setTimeout(function() { setTimeout(function() {
if (self.audioManager) { if (self.audioManager) {
self.audioManager.playStartupNotification(); 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 高度管理器 * 初始化 Textarea 高度管理器
*/ */
@ -720,6 +773,14 @@
this.audioManager.playNotification(); 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( window.MCPFeedback.Utils.showMessage(
data.message || '新的 MCP 會話已創建,正在更新內容...', data.message || '新的 MCP 會話已創建,正在更新內容...',

View File

@ -70,17 +70,18 @@
*/ */
AudioSettingsUI.prototype.createUI = function() { AudioSettingsUI.prototype.createUI = function() {
const html = ` const html = `
<div class="audio-management-section"> <div class="settings-card">
<div class="audio-management-header"> <div class="settings-card-header">
<h4 class="audio-management-title" data-i18n="audio.notification.title"> <h3 class="settings-card-title" data-i18n="audio.notification.title">
🔊 音效通知設定 🔊 音效通知設定
</h4> </h3>
</div> </div>
<div class="audio-management-description" data-i18n="audio.notification.description"> <div class="settings-card-body">
設定會話更新時的音效通知 <div class="audio-management-description" data-i18n="audio.notification.description">
</div> 設定會話更新時的音效通知
</div>
<div class="audio-settings-controls">
<div class="audio-settings-controls">
<!-- 啟用開關 --> <!-- 啟用開關 -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-info"> <div class="setting-info">
@ -142,6 +143,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
`; `;

View File

@ -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;
})();

View File

@ -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;
})();

View File

@ -14,6 +14,7 @@
<link rel="stylesheet" href="/static/css/session-management.css"> <link rel="stylesheet" href="/static/css/session-management.css">
<link rel="stylesheet" href="/static/css/prompt-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/audio-management.css">
<link rel="stylesheet" href="/static/css/notification-settings.css">
<style> <style>
/* 僅保留必要的頁面特定樣式和響應式調整 */ /* 僅保留必要的頁面特定樣式和響應式調整 */
@ -821,13 +822,20 @@
</div> </div>
</div> </div>
<!-- 音效通知設定卡片 --> <!-- 音效通知設定 -->
<div id="audioManagementContainer">
<!-- 音效管理 UI 將在這裡動態生成 -->
</div>
<!-- 瀏覽器通知設定卡片 -->
<div class="settings-card"> <div class="settings-card">
<div class="settings-card-header"> <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>
<div class="settings-card-body" id="audioManagementContainer"> <div class="settings-card-body" id="notificationSettingsContainer">
<!-- 音效管理 UI 將在這裡動態生成 --> <!-- 通知設定 UI 將在這裡動態生成 -->
</div> </div>
</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-manager.js?v=2025010510"></script>
<script src="/static/js/modules/audio/audio-settings-ui.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/utils.js?v=2025010510"></script>
<script src="/static/js/modules/tab-manager.js?v=2025010510"></script> <script src="/static/js/modules/tab-manager.js?v=2025010510"></script>