cloneloop 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. cloneloop-0.1.0.dist-info/METADATA +392 -0
  2. cloneloop-0.1.0.dist-info/RECORD +118 -0
  3. cloneloop-0.1.0.dist-info/WHEEL +4 -0
  4. cloneloop-0.1.0.dist-info/entry_points.txt +5 -0
  5. mcp_ai_supervisor/__init__.py +52 -0
  6. mcp_ai_supervisor/__main__.py +551 -0
  7. mcp_ai_supervisor/debug.py +15 -0
  8. mcp_ai_supervisor/desktop_app/__init__.py +29 -0
  9. mcp_ai_supervisor/desktop_app/desktop_app.py +390 -0
  10. mcp_ai_supervisor/helpers/__init__.py +4 -0
  11. mcp_ai_supervisor/helpers/feedback_helpers.py +140 -0
  12. mcp_ai_supervisor/helpers/image_helpers.py +73 -0
  13. mcp_ai_supervisor/i18n.py +14 -0
  14. mcp_ai_supervisor/log_writer.py +16 -0
  15. mcp_ai_supervisor/logging/__init__.py +8 -0
  16. mcp_ai_supervisor/logging/log_parser.py +293 -0
  17. mcp_ai_supervisor/logging/log_tailer.py +367 -0
  18. mcp_ai_supervisor/py.typed +0 -0
  19. mcp_ai_supervisor/server.py +654 -0
  20. mcp_ai_supervisor/utils/__init__.py +28 -0
  21. mcp_ai_supervisor/utils/error_handler.py +10 -0
  22. mcp_ai_supervisor/utils/memory_monitor.py +11 -0
  23. mcp_ai_supervisor/utils/paths.py +7 -0
  24. mcp_ai_supervisor/utils/resource_manager.py +15 -0
  25. mcp_ai_supervisor/utils/structure_checker.py +291 -0
  26. mcp_ai_supervisor/web/__init__.py +26 -0
  27. mcp_ai_supervisor/web/constants/__init__.py +8 -0
  28. mcp_ai_supervisor/web/constants/message_codes.py +173 -0
  29. mcp_ai_supervisor/web/core/__init__.py +30 -0
  30. mcp_ai_supervisor/web/core/agent_bridge.py +957 -0
  31. mcp_ai_supervisor/web/core/auto_responder.py +332 -0
  32. mcp_ai_supervisor/web/core/context_collector.py +124 -0
  33. mcp_ai_supervisor/web/core/conversation_recorder.py +751 -0
  34. mcp_ai_supervisor/web/core/delegate_manager.py +1193 -0
  35. mcp_ai_supervisor/web/core/feedback_mediator.py +259 -0
  36. mcp_ai_supervisor/web/core/message_channel.py +551 -0
  37. mcp_ai_supervisor/web/core/response_builder.py +333 -0
  38. mcp_ai_supervisor/web/core/scene_detector.py +184 -0
  39. mcp_ai_supervisor/web/core/task_state.py +194 -0
  40. mcp_ai_supervisor/web/locales/en/translation.json +643 -0
  41. mcp_ai_supervisor/web/locales/zh-CN/translation.json +629 -0
  42. mcp_ai_supervisor/web/locales/zh-TW/translation.json +648 -0
  43. mcp_ai_supervisor/web/main.py +747 -0
  44. mcp_ai_supervisor/web/managers/__init__.py +8 -0
  45. mcp_ai_supervisor/web/managers/desktop_manager.py +228 -0
  46. mcp_ai_supervisor/web/managers/server_manager.py +170 -0
  47. mcp_ai_supervisor/web/managers/session_manager.py +515 -0
  48. mcp_ai_supervisor/web/managers/workbench_connector.py +186 -0
  49. mcp_ai_supervisor/web/models/__init__.py +13 -0
  50. mcp_ai_supervisor/web/models/feedback_result.py +16 -0
  51. mcp_ai_supervisor/web/models/feedback_session.py +938 -0
  52. mcp_ai_supervisor/web/models/session_cleaner.py +245 -0
  53. mcp_ai_supervisor/web/models/session_commands.py +213 -0
  54. mcp_ai_supervisor/web/models/session_resolver.py +158 -0
  55. mcp_ai_supervisor/web/models/session_timer.py +242 -0
  56. mcp_ai_supervisor/web/routes/__init__.py +12 -0
  57. mcp_ai_supervisor/web/routes/main_routes.py +965 -0
  58. mcp_ai_supervisor/web/routes/settings_routes.py +195 -0
  59. mcp_ai_supervisor/web/routes/ws_handlers.py +270 -0
  60. mcp_ai_supervisor/web/static/css/audio-management.css +545 -0
  61. mcp_ai_supervisor/web/static/css/notification-settings.css +152 -0
  62. mcp_ai_supervisor/web/static/css/prompt-management.css +566 -0
  63. mcp_ai_supervisor/web/static/css/session-management.css +1428 -0
  64. mcp_ai_supervisor/web/static/css/styles.css +2267 -0
  65. mcp_ai_supervisor/web/static/favicon.ico +0 -0
  66. mcp_ai_supervisor/web/static/icon-192.png +0 -0
  67. mcp_ai_supervisor/web/static/icon.svg +11 -0
  68. mcp_ai_supervisor/web/static/index.html +37 -0
  69. mcp_ai_supervisor/web/static/js/app.js +1721 -0
  70. mcp_ai_supervisor/web/static/js/i18n.js +376 -0
  71. mcp_ai_supervisor/web/static/js/modules/audio/audio-manager.js +610 -0
  72. mcp_ai_supervisor/web/static/js/modules/audio/audio-settings-ui.js +732 -0
  73. mcp_ai_supervisor/web/static/js/modules/connection-monitor.js +435 -0
  74. mcp_ai_supervisor/web/static/js/modules/constants/message-codes.js +168 -0
  75. mcp_ai_supervisor/web/static/js/modules/countdown-manager.js +273 -0
  76. mcp_ai_supervisor/web/static/js/modules/drag-drop-handler.js +677 -0
  77. mcp_ai_supervisor/web/static/js/modules/file-upload-manager.js +555 -0
  78. mcp_ai_supervisor/web/static/js/modules/image-handler.js +199 -0
  79. mcp_ai_supervisor/web/static/js/modules/logger.js +404 -0
  80. mcp_ai_supervisor/web/static/js/modules/notification/notification-manager.js +360 -0
  81. mcp_ai_supervisor/web/static/js/modules/notification/notification-settings.js +344 -0
  82. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-input-buttons.js +427 -0
  83. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-manager.js +414 -0
  84. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-modal.js +458 -0
  85. mcp_ai_supervisor/web/static/js/modules/prompt/prompt-settings-ui.js +524 -0
  86. mcp_ai_supervisor/web/static/js/modules/session/session-data-manager.js +1042 -0
  87. mcp_ai_supervisor/web/static/js/modules/session/session-details-modal.js +594 -0
  88. mcp_ai_supervisor/web/static/js/modules/session/session-ui-renderer.js +836 -0
  89. mcp_ai_supervisor/web/static/js/modules/session-manager.js +1059 -0
  90. mcp_ai_supervisor/web/static/js/modules/settings-manager.js +1002 -0
  91. mcp_ai_supervisor/web/static/js/modules/tab-manager.js +235 -0
  92. mcp_ai_supervisor/web/static/js/modules/textarea-height-manager.js +267 -0
  93. mcp_ai_supervisor/web/static/js/modules/ui-manager.js +578 -0
  94. mcp_ai_supervisor/web/static/js/modules/utils/dom-utils.js +392 -0
  95. mcp_ai_supervisor/web/static/js/modules/utils/status-utils.js +403 -0
  96. mcp_ai_supervisor/web/static/js/modules/utils/time-utils.js +440 -0
  97. mcp_ai_supervisor/web/static/js/modules/utils.js +557 -0
  98. mcp_ai_supervisor/web/static/js/modules/websocket-manager.js +875 -0
  99. mcp_ai_supervisor/web/static/js/modules/workbench-notification.js +103 -0
  100. mcp_ai_supervisor/web/static/js/vendor/marked.min.js +6 -0
  101. mcp_ai_supervisor/web/static/js/vendor/purify.min.js +3 -0
  102. mcp_ai_supervisor/web/templates/components/image-upload.html +43 -0
  103. mcp_ai_supervisor/web/templates/components/settings-card.html +58 -0
  104. mcp_ai_supervisor/web/templates/components/status-indicator.html +31 -0
  105. mcp_ai_supervisor/web/templates/components/toggle-switch.html +19 -0
  106. mcp_ai_supervisor/web/templates/feedback.html +1198 -0
  107. mcp_ai_supervisor/web/templates/index.html +378 -0
  108. mcp_ai_supervisor/web/utils/__init__.py +12 -0
  109. mcp_ai_supervisor/web/utils/browser.py +35 -0
  110. mcp_ai_supervisor/web/utils/compression_config.py +195 -0
  111. mcp_ai_supervisor/web/utils/compression_monitor.py +314 -0
  112. mcp_ai_supervisor/web/utils/ide_opener.py +286 -0
  113. mcp_ai_supervisor/web/utils/network.py +66 -0
  114. mcp_ai_supervisor/web/utils/port_manager.py +340 -0
  115. mcp_ai_supervisor/web/utils/session_cleanup_manager.py +543 -0
  116. mcp_ai_supervisor/web/utils/workbench_client.py +899 -0
  117. mcp_ai_supervisor/workbench/__init__.py +5 -0
  118. mcp_ai_supervisor/workbench/__main__.py +5 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * MCP AI Supervisor - 通知管理模組
3
+ * ===================================
4
+ *
5
+ * 處理瀏覽器通知功能,支援新會話通知和緊急狀態通知
6
+ * 使用 Web Notification API,提供極簡的通知體驗
7
+ */
8
+
9
+ (function() {
10
+ 'use strict';
11
+
12
+ // 確保命名空間存在
13
+ window.MCPFeedback = window.MCPFeedback || {};
14
+ const Utils = window.MCPFeedback.Utils;
15
+
16
+ /**
17
+ * 通知管理器建構函數
18
+ */
19
+ function NotificationManager(options) {
20
+ options = options || {};
21
+
22
+ // 通知設定
23
+ this.enabled = false;
24
+ this.permission = 'default';
25
+ this.triggerMode = 'focusLost'; // 預設為失去焦點時通知
26
+
27
+ // 狀態追蹤
28
+ this.lastSessionId = null; // 避免重複通知同一會話
29
+ this.isInitialized = false;
30
+ this.hasFocus = true; // 追蹤視窗焦點狀態
31
+
32
+ // 設定鍵名
33
+ this.STORAGE_KEY = 'notificationsEnabled';
34
+ this.TRIGGER_MODE_KEY = 'notificationTriggerMode';
35
+
36
+ // i18n 翻譯函數
37
+ this.t = options.t || function(key, defaultValue) { return defaultValue || key; };
38
+
39
+ console.log('🔔 NotificationManager 建構完成');
40
+ }
41
+
42
+ /**
43
+ * 初始化通知管理器
44
+ */
45
+ NotificationManager.prototype.initialize = function() {
46
+ if (this.isInitialized) return;
47
+
48
+ // 檢查瀏覽器支援
49
+ if (!this.checkBrowserSupport()) {
50
+ console.warn('⚠️ 瀏覽器不支援 Notification API');
51
+ return;
52
+ }
53
+
54
+ // 載入設定
55
+ this.loadSettings();
56
+
57
+ // 更新權限狀態
58
+ this.updatePermissionStatus();
59
+
60
+ // 設定焦點追蹤
61
+ this.setupFocusTracking();
62
+
63
+ this.isInitialized = true;
64
+ console.log('✅ NotificationManager 初始化完成', {
65
+ enabled: this.enabled,
66
+ permission: this.permission,
67
+ triggerMode: this.triggerMode
68
+ });
69
+ };
70
+
71
+ /**
72
+ * 檢查瀏覽器支援
73
+ */
74
+ NotificationManager.prototype.checkBrowserSupport = function() {
75
+ return 'Notification' in window;
76
+ };
77
+
78
+ /**
79
+ * 載入設定
80
+ */
81
+ NotificationManager.prototype.loadSettings = function() {
82
+ try {
83
+ this.enabled = localStorage.getItem(this.STORAGE_KEY) === 'true';
84
+ this.triggerMode = localStorage.getItem(this.TRIGGER_MODE_KEY) || 'focusLost';
85
+ } catch (error) {
86
+ console.error('❌ 載入通知設定失敗:', error);
87
+ this.enabled = false;
88
+ this.triggerMode = 'focusLost';
89
+ }
90
+ };
91
+
92
+ /**
93
+ * 儲存設定
94
+ */
95
+ NotificationManager.prototype.saveSettings = function() {
96
+ try {
97
+ localStorage.setItem(this.STORAGE_KEY, this.enabled.toString());
98
+ } catch (error) {
99
+ console.error('❌ 儲存通知設定失敗:', error);
100
+ }
101
+ };
102
+
103
+ /**
104
+ * 更新權限狀態
105
+ */
106
+ NotificationManager.prototype.updatePermissionStatus = function() {
107
+ if (this.checkBrowserSupport()) {
108
+ this.permission = Notification.permission;
109
+ }
110
+ };
111
+
112
+ /**
113
+ * 請求通知權限
114
+ */
115
+ NotificationManager.prototype.requestPermission = async function() {
116
+ if (!this.checkBrowserSupport()) {
117
+ throw new Error('瀏覽器不支援通知功能');
118
+ }
119
+
120
+ try {
121
+ const result = await Notification.requestPermission();
122
+ this.permission = result;
123
+ return result;
124
+ } catch (error) {
125
+ console.error('❌ 請求通知權限失敗:', error);
126
+ throw error;
127
+ }
128
+ };
129
+
130
+ /**
131
+ * 啟用通知
132
+ */
133
+ NotificationManager.prototype.enable = async function() {
134
+ // 檢查權限
135
+ if (this.permission === 'default') {
136
+ const result = await this.requestPermission();
137
+ if (result !== 'granted') {
138
+ return false;
139
+ }
140
+ } else if (this.permission === 'denied') {
141
+ console.warn('⚠️ 通知權限已被拒絕');
142
+ return false;
143
+ }
144
+
145
+ this.enabled = true;
146
+ this.saveSettings();
147
+ console.log('✅ 通知已啟用');
148
+ return true;
149
+ };
150
+
151
+ /**
152
+ * 停用通知
153
+ */
154
+ NotificationManager.prototype.disable = function() {
155
+ this.enabled = false;
156
+ this.saveSettings();
157
+ console.log('🔇 通知已停用');
158
+ };
159
+
160
+ /**
161
+ * 設定焦點追蹤
162
+ */
163
+ NotificationManager.prototype.setupFocusTracking = function() {
164
+ const self = this;
165
+
166
+ // 監聽焦點事件
167
+ window.addEventListener('focus', function() {
168
+ self.hasFocus = true;
169
+ console.log('👁️ 視窗獲得焦點');
170
+ });
171
+
172
+ window.addEventListener('blur', function() {
173
+ self.hasFocus = false;
174
+ console.log('👁️ 視窗失去焦點');
175
+ });
176
+ };
177
+
178
+ /**
179
+ * 檢查是否可以顯示通知
180
+ */
181
+ NotificationManager.prototype.canNotify = function() {
182
+ if (!this.enabled || this.permission !== 'granted') {
183
+ return false;
184
+ }
185
+
186
+ // 根據觸發模式判斷
187
+ switch (this.triggerMode) {
188
+ case 'always':
189
+ return true; // 總是通知
190
+ case 'background':
191
+ return document.hidden; // 只在頁面隱藏時通知
192
+ case 'tabSwitch':
193
+ return document.hidden; // 只在切換標籤頁時通知
194
+ case 'focusLost':
195
+ return document.hidden || !this.hasFocus; // 失去焦點或頁面隱藏時通知
196
+ default:
197
+ return document.hidden || !this.hasFocus;
198
+ }
199
+ };
200
+
201
+ /**
202
+ * 新會話通知
203
+ */
204
+ NotificationManager.prototype.notifyNewSession = function(sessionId, projectPath) {
205
+ // 避免重複通知
206
+ if (sessionId === this.lastSessionId) {
207
+ console.log('🔇 跳過重複的會話通知');
208
+ return;
209
+ }
210
+
211
+ // 檢查是否可以通知
212
+ if (!this.canNotify()) {
213
+ console.log('🔇 不符合通知條件', {
214
+ enabled: this.enabled,
215
+ permission: this.permission,
216
+ pageHidden: document.hidden,
217
+ hasFocus: this.hasFocus,
218
+ triggerMode: this.triggerMode
219
+ });
220
+ return;
221
+ }
222
+
223
+ this.lastSessionId = sessionId;
224
+
225
+ try {
226
+ const notification = new Notification(this.t('notification.browser.title', 'MCP AI Supervisor - 新會話'), {
227
+ body: `${this.t('notification.browser.ready', '準備就緒')}: ${this.truncatePath(projectPath)}`,
228
+ icon: '/static/icon-192.png',
229
+ badge: '/static/icon-192.png',
230
+ tag: 'mcp-session',
231
+ timestamp: Date.now(),
232
+ silent: false
233
+ });
234
+
235
+ // 點擊後聚焦視窗
236
+ notification.onclick = () => {
237
+ window.focus();
238
+ notification.close();
239
+ console.log('🖱️ 通知被點擊,視窗已聚焦');
240
+ };
241
+
242
+ // 5秒後自動關閉
243
+ setTimeout(() => notification.close(), 5000);
244
+
245
+ console.log('🔔 已發送新會話通知', {
246
+ sessionId: sessionId,
247
+ projectPath: projectPath
248
+ });
249
+ } catch (error) {
250
+ console.error('❌ 發送通知失敗:', error);
251
+ }
252
+ };
253
+
254
+ /**
255
+ * 緊急通知(連線問題等)
256
+ */
257
+ NotificationManager.prototype.notifyCritical = function(type, message) {
258
+ if (!this.canNotify()) return;
259
+
260
+ try {
261
+ const notification = new Notification(this.t('notification.browser.criticalTitle', 'MCP AI Supervisor - 警告'), {
262
+ body: message,
263
+ icon: '/static/icon-192.png',
264
+ badge: '/static/icon-192.png',
265
+ tag: 'mcp-critical',
266
+ requireInteraction: true, // 需要手動關閉
267
+ timestamp: Date.now()
268
+ });
269
+
270
+ notification.onclick = () => {
271
+ window.focus();
272
+ notification.close();
273
+ console.log('🖱️ 緊急通知被點擊');
274
+ };
275
+
276
+ console.log('⚠️ 已發送緊急通知', {
277
+ type: type,
278
+ message: message
279
+ });
280
+ } catch (error) {
281
+ console.error('❌ 發送緊急通知失敗:', error);
282
+ }
283
+ };
284
+
285
+ /**
286
+ * 路徑截斷顯示
287
+ */
288
+ NotificationManager.prototype.truncatePath = function(path, maxLength) {
289
+ maxLength = maxLength || 50;
290
+ if (!path || path.length <= maxLength) return path || this.t('notification.browser.unknownProject', '未知專案');
291
+ return '...' + path.slice(-(maxLength - 3));
292
+ };
293
+
294
+ /**
295
+ * 設定觸發模式
296
+ */
297
+ NotificationManager.prototype.setTriggerMode = function(mode) {
298
+ const validModes = ['always', 'background', 'tabSwitch', 'focusLost'];
299
+ if (validModes.includes(mode)) {
300
+ this.triggerMode = mode;
301
+ try {
302
+ localStorage.setItem(this.TRIGGER_MODE_KEY, mode);
303
+ console.log('✅ 通知觸發模式已更新:', mode);
304
+ } catch (error) {
305
+ console.error('❌ 儲存觸發模式失敗:', error);
306
+ }
307
+ }
308
+ };
309
+
310
+ /**
311
+ * 獲取當前設定
312
+ */
313
+ NotificationManager.prototype.getSettings = function() {
314
+ return {
315
+ enabled: this.enabled,
316
+ permission: this.permission,
317
+ browserSupported: this.checkBrowserSupport(),
318
+ triggerMode: this.triggerMode
319
+ };
320
+ };
321
+
322
+ /**
323
+ * 測試通知
324
+ */
325
+ NotificationManager.prototype.testNotification = function() {
326
+ if (!this.checkBrowserSupport()) {
327
+ alert(this.t('notification.browser.notSupported', '您的瀏覽器不支援通知功能'));
328
+ return;
329
+ }
330
+
331
+ if (this.permission !== 'granted') {
332
+ alert(this.t('notification.browser.permissionRequired', '請先授權通知權限'));
333
+ return;
334
+ }
335
+
336
+ try {
337
+ const notification = new Notification(this.t('notification.browser.testTitle', '測試通知'), {
338
+ body: this.t('notification.browser.testBody', '這是一個測試通知,5秒後將自動關閉'),
339
+ icon: '/static/icon-192.png',
340
+ tag: 'mcp-test',
341
+ timestamp: Date.now()
342
+ });
343
+
344
+ notification.onclick = () => {
345
+ notification.close();
346
+ };
347
+
348
+ setTimeout(() => notification.close(), 5000);
349
+
350
+ console.log('🔔 測試通知已發送');
351
+ } catch (error) {
352
+ console.error('❌ 測試通知失敗:', error);
353
+ alert('發送測試通知失敗');
354
+ }
355
+ };
356
+
357
+ // 匯出到全域命名空間
358
+ window.MCPFeedback.NotificationManager = NotificationManager;
359
+
360
+ })();
@@ -0,0 +1,344 @@
1
+ /**
2
+ * MCP AI Supervisor - 通知設定介面模組
3
+ * =====================================
4
+ *
5
+ * 處理瀏覽器通知的設定介面,提供簡單的開關控制
6
+ * 與 NotificationManager 配合使用
7
+ */
8
+
9
+ (function() {
10
+ 'use strict';
11
+
12
+ // 確保命名空間存在
13
+ window.MCPFeedback = window.MCPFeedback || {};
14
+ const Utils = window.MCPFeedback.Utils;
15
+
16
+ /**
17
+ * 通知設定介面建構函數
18
+ */
19
+ function NotificationSettings(options) {
20
+ options = options || {};
21
+
22
+ // 容器元素
23
+ this.container = options.container || null;
24
+
25
+ // 通知管理器引用
26
+ this.notificationManager = options.notificationManager || null;
27
+
28
+ // i18n 翻譯函數
29
+ this.t = options.t || function(key, defaultValue) { return defaultValue || key; };
30
+
31
+ // UI 元素引用
32
+ this.toggle = null;
33
+ this.statusDiv = null;
34
+ this.testButton = null;
35
+ this.triggerOptionsDiv = null;
36
+
37
+ console.log('🎨 NotificationSettings 初始化完成');
38
+ }
39
+
40
+ /**
41
+ * 初始化設定介面
42
+ */
43
+ NotificationSettings.prototype.initialize = function() {
44
+ if (!this.container) {
45
+ console.error('❌ NotificationSettings 容器未設定');
46
+ return;
47
+ }
48
+
49
+ if (!this.notificationManager) {
50
+ console.error('❌ NotificationManager 未設定');
51
+ return;
52
+ }
53
+
54
+ this.createUI();
55
+ this.setupEventListeners();
56
+ this.updateUI();
57
+
58
+ // 應用翻譯到動態生成的內容
59
+ if (window.i18nManager) {
60
+ window.i18nManager.applyTranslations();
61
+ }
62
+
63
+ console.log('✅ NotificationSettings 初始化完成');
64
+ };
65
+
66
+ /**
67
+ * 創建 UI 結構
68
+ */
69
+ NotificationSettings.prototype.createUI = function() {
70
+ const html = `
71
+ <!-- 啟用開關 -->
72
+ <div class="setting-item">
73
+ <div class="setting-info">
74
+ <div class="setting-label" data-i18n="notification.settingLabel"></div>
75
+ <div class="setting-description" data-i18n="notification.description"></div>
76
+ <!-- 權限狀態 -->
77
+ <div id="permissionStatus" class="permission-status">
78
+ <!-- 動態更新 -->
79
+ </div>
80
+ </div>
81
+ <div class="setting-control">
82
+ <button type="button" id="notificationToggle" class="toggle-btn" data-i18n-aria-label="aria.toggleNotification">
83
+ <span class="toggle-slider"></span>
84
+ </button>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- 通知觸發情境 -->
89
+ <div class="setting-item notification-trigger" style="display: none;">
90
+ <div class="setting-info">
91
+ <div class="setting-label" data-i18n="notification.triggerTitle"></div>
92
+ <div class="setting-description" data-i18n="notification.triggerDescription"></div>
93
+ </div>
94
+ <div class="trigger-options">
95
+ <label class="radio-option">
96
+ <input type="radio" name="notificationTrigger" value="focusLost" checked>
97
+ <span data-i18n="notification.trigger.focusLost"></span>
98
+ </label>
99
+ <label class="radio-option">
100
+ <input type="radio" name="notificationTrigger" value="tabSwitch">
101
+ <span data-i18n="notification.trigger.tabSwitch"></span>
102
+ </label>
103
+ <label class="radio-option">
104
+ <input type="radio" name="notificationTrigger" value="background">
105
+ <span data-i18n="notification.trigger.background"></span>
106
+ </label>
107
+ <label class="radio-option">
108
+ <input type="radio" name="notificationTrigger" value="always">
109
+ <span data-i18n="notification.trigger.always"></span>
110
+ </label>
111
+ </div>
112
+ </div>
113
+
114
+ <!-- 測試按鈕 -->
115
+ <div class="setting-item notification-actions" style="display: none;">
116
+ <div class="setting-info">
117
+ <div class="setting-label" data-i18n="notification.testTitle"></div>
118
+ <div class="setting-description" data-i18n="notification.testDescription"></div>
119
+ </div>
120
+ <div class="setting-control">
121
+ <button type="button" id="testNotification" class="btn-primary">
122
+ <span data-i18n="notification.test"></span>
123
+ </button>
124
+ </div>
125
+ </div>
126
+ `;
127
+
128
+ this.container.innerHTML = html;
129
+
130
+ // 取得元素引用
131
+ this.toggle = this.container.querySelector('#notificationToggle');
132
+ this.statusDiv = this.container.querySelector('#permissionStatus');
133
+ this.testButton = this.container.querySelector('#testNotification');
134
+ this.triggerOptionsDiv = this.container.querySelector('.notification-trigger');
135
+ };
136
+
137
+ /**
138
+ * 設置事件監聽器
139
+ */
140
+ NotificationSettings.prototype.setupEventListeners = function() {
141
+ const self = this;
142
+
143
+ // 開關切換事件
144
+ this.toggle.addEventListener('click', async function(e) {
145
+ const isActive = self.toggle.classList.contains('active');
146
+ if (!isActive) {
147
+ await self.enableNotifications();
148
+ } else {
149
+ self.disableNotifications();
150
+ }
151
+ });
152
+
153
+ // 測試按鈕事件
154
+ if (this.testButton) {
155
+ this.testButton.addEventListener('click', function() {
156
+ self.notificationManager.testNotification();
157
+ });
158
+ }
159
+
160
+ // 監聽頁面可見性變化,更新權限狀態
161
+ document.addEventListener('visibilitychange', function() {
162
+ self.updatePermissionStatus();
163
+ });
164
+
165
+ // 觸發模式選項事件
166
+ const triggerRadios = this.container.querySelectorAll('input[name="notificationTrigger"]');
167
+ triggerRadios.forEach(function(radio) {
168
+ radio.addEventListener('change', function() {
169
+ if (radio.checked) {
170
+ self.notificationManager.setTriggerMode(radio.value);
171
+ self.showMessage(
172
+ self.t('notification.triggerModeUpdated', '通知觸發模式已更新'),
173
+ 'success'
174
+ );
175
+ }
176
+ });
177
+ });
178
+ };
179
+
180
+ /**
181
+ * 更新 UI 狀態
182
+ */
183
+ NotificationSettings.prototype.updateUI = function() {
184
+ const settings = this.notificationManager.getSettings();
185
+
186
+ // 設定開關狀態
187
+ if (settings.enabled) {
188
+ this.toggle.classList.add('active');
189
+ } else {
190
+ this.toggle.classList.remove('active');
191
+ }
192
+
193
+ // 更新權限狀態顯示
194
+ this.updatePermissionStatus();
195
+
196
+ // 顯示/隱藏測試按鈕和觸發選項
197
+ const actionsDiv = this.container.querySelector('.notification-actions');
198
+ if (actionsDiv) {
199
+ actionsDiv.style.display = (settings.enabled && settings.permission === 'granted') ? 'block' : 'none';
200
+ }
201
+
202
+ if (this.triggerOptionsDiv) {
203
+ this.triggerOptionsDiv.style.display = (settings.enabled && settings.permission === 'granted') ? 'block' : 'none';
204
+
205
+ // 設定當前選中的觸發模式
206
+ const currentMode = settings.triggerMode || 'focusLost';
207
+ const radio = this.container.querySelector(`input[name="notificationTrigger"][value="${currentMode}"]`);
208
+ if (radio) {
209
+ radio.checked = true;
210
+ }
211
+ }
212
+ };
213
+
214
+ /**
215
+ * 啟用通知
216
+ */
217
+ NotificationSettings.prototype.enableNotifications = async function() {
218
+ try {
219
+ const success = await this.notificationManager.enable();
220
+
221
+ if (success) {
222
+ this.showMessage(this.t('notification.enabled', '通知已啟用 ✅'), 'success');
223
+ this.updateUI();
224
+ } else {
225
+ // 權限被拒絕或其他問題
226
+ this.toggle.classList.remove('active');
227
+ this.updatePermissionStatus();
228
+
229
+ if (this.notificationManager.permission === 'denied') {
230
+ this.showMessage(
231
+ this.t('notification.permissionDenied', '瀏覽器已封鎖通知,請在瀏覽器設定中允許'),
232
+ 'error'
233
+ );
234
+ } else {
235
+ this.showMessage(
236
+ this.t('notification.permissionRequired', '需要通知權限才能啟用此功能'),
237
+ 'warning'
238
+ );
239
+ }
240
+ }
241
+ } catch (error) {
242
+ console.error('❌ 啟用通知失敗:', error);
243
+ this.toggle.checked = false;
244
+ this.showMessage(
245
+ this.t('notification.enableFailed', '啟用通知失敗'),
246
+ 'error'
247
+ );
248
+ }
249
+ };
250
+
251
+ /**
252
+ * 停用通知
253
+ */
254
+ NotificationSettings.prototype.disableNotifications = function() {
255
+ this.notificationManager.disable();
256
+ this.showMessage(this.t('notification.disabled', '通知已關閉'), 'info');
257
+ this.updateUI();
258
+ };
259
+
260
+ /**
261
+ * 更新權限狀態顯示
262
+ */
263
+ NotificationSettings.prototype.updatePermissionStatus = function() {
264
+ const settings = this.notificationManager.getSettings();
265
+
266
+ if (!settings.browserSupported) {
267
+ this.statusDiv.innerHTML = `<span data-i18n="notification.notSupported"></span>`;
268
+ this.statusDiv.className = 'permission-status status-unsupported';
269
+ this.toggle.disabled = true;
270
+ return;
271
+ }
272
+
273
+ const statusMessages = {
274
+ 'granted': {
275
+ icon: '✅',
276
+ text: this.t('notification.permissionGranted', '已授權'),
277
+ class: 'status-granted',
278
+ i18nKey: 'notification.permissionGranted'
279
+ },
280
+ 'denied': {
281
+ icon: '❌',
282
+ text: this.t('notification.permissionDeniedStatus', '已拒絕(請在瀏覽器設定中修改)'),
283
+ class: 'status-denied',
284
+ i18nKey: 'notification.permissionDeniedStatus'
285
+ },
286
+ 'default': {
287
+ icon: '⏸',
288
+ text: this.t('notification.permissionDefault', '尚未設定'),
289
+ class: 'status-default',
290
+ i18nKey: 'notification.permissionDefault'
291
+ }
292
+ };
293
+
294
+ const status = statusMessages[settings.permission] || statusMessages['default'];
295
+
296
+ // 將圖標和文字合併在同一個元素內,並加入 data-i18n 屬性以支援動態語言切換
297
+ this.statusDiv.innerHTML = `<span data-i18n="${status.i18nKey}">${status.icon} ${status.text}</span>`;
298
+ this.statusDiv.className = `permission-status ${status.class}`;
299
+ };
300
+
301
+ /**
302
+ * 顯示訊息
303
+ */
304
+ NotificationSettings.prototype.showMessage = function(message, type) {
305
+ // 使用 Utils 的訊息顯示功能
306
+ if (Utils && Utils.showMessage) {
307
+ Utils.showMessage(message, type);
308
+ } else {
309
+ console.log(`[${type}] ${message}`);
310
+ }
311
+ };
312
+
313
+ /**
314
+ * 重新整理介面
315
+ */
316
+ NotificationSettings.prototype.refresh = function() {
317
+ this.updateUI();
318
+ };
319
+
320
+ /**
321
+ * 清理資源
322
+ */
323
+ NotificationSettings.prototype.destroy = function() {
324
+ // 移除事件監聽器
325
+ if (this.toggle) {
326
+ this.toggle.removeEventListener('change', this.enableNotifications);
327
+ }
328
+
329
+ if (this.testButton) {
330
+ this.testButton.removeEventListener('click', this.notificationManager.testNotification);
331
+ }
332
+
333
+ // 清空容器
334
+ if (this.container) {
335
+ this.container.innerHTML = '';
336
+ }
337
+
338
+ console.log('🧹 NotificationSettings 已清理');
339
+ };
340
+
341
+ // 匯出到全域命名空間
342
+ window.MCPFeedback.NotificationSettings = NotificationSettings;
343
+
344
+ })();