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,732 @@
1
+ /**
2
+ * MCP AI Supervisor - 音效設定 UI 模組
3
+ * ======================================
4
+ *
5
+ * 處理音效通知設定的使用者介面
6
+ * 參考 prompt-settings-ui.js 的設計模式
7
+ */
8
+
9
+ (function() {
10
+ 'use strict';
11
+
12
+ // 確保命名空間存在
13
+ window.MCPFeedback = window.MCPFeedback || {};
14
+ const Utils = window.MCPFeedback.Utils;
15
+
16
+ /**
17
+ * 音效設定 UI 建構函數
18
+ */
19
+ function AudioSettingsUI(options) {
20
+ options = options || {};
21
+
22
+ // 容器元素
23
+ this.container = options.container || null;
24
+
25
+ // 音效管理器引用
26
+ this.audioManager = options.audioManager || null;
27
+
28
+ // i18n 翻譯函數
29
+ this.t = options.t || function(key, defaultValue) { return defaultValue || key; };
30
+
31
+ // UI 元素引用
32
+ this.enabledToggle = null;
33
+ this.volumeSlider = null;
34
+ this.volumeValue = null;
35
+ this.audioSelect = null;
36
+ this.testButton = null;
37
+ this.uploadButton = null;
38
+ this.uploadInput = null;
39
+ this.audioList = null;
40
+
41
+ console.log('🎨 AudioSettingsUI 初始化完成');
42
+ }
43
+
44
+ /**
45
+ * 初始化 UI
46
+ */
47
+ AudioSettingsUI.prototype.initialize = function() {
48
+ if (!this.container) {
49
+ console.error('❌ AudioSettingsUI 容器未設定');
50
+ return;
51
+ }
52
+
53
+ if (!this.audioManager) {
54
+ console.error('❌ AudioManager 未設定');
55
+ return;
56
+ }
57
+
58
+ this.createUI();
59
+ this.setupEventListeners();
60
+ this.refreshUI();
61
+
62
+ // 主動應用翻譯到新創建的元素
63
+ this.applyInitialTranslations();
64
+
65
+ console.log('✅ AudioSettingsUI 初始化完成');
66
+ };
67
+
68
+ /**
69
+ * 創建 UI 結構
70
+ */
71
+ AudioSettingsUI.prototype.createUI = function() {
72
+ const html = `
73
+ <div class="settings-card">
74
+ <div class="settings-card-header">
75
+ <h3 class="settings-card-title" data-i18n="audio.notification.title">
76
+ 🔊 音效通知設定
77
+ </h3>
78
+ </div>
79
+ <div class="settings-card-body">
80
+ <div class="audio-management-description" data-i18n="audio.notification.description">
81
+ 設定會話更新時的音效通知
82
+ </div>
83
+
84
+ <div class="audio-settings-controls">
85
+ <!-- 啟用開關 -->
86
+ <div class="setting-item">
87
+ <div class="setting-info">
88
+ <div class="setting-label" data-i18n="audio.notification.enabled"></div>
89
+ <div class="setting-description" data-i18n="audio.notification.enabledDesc"></div>
90
+ </div>
91
+ <div class="setting-control">
92
+ <button type="button" id="audioNotificationEnabled" class="toggle-btn" data-i18n-aria-label="aria.toggleAudioNotification">
93
+ <span class="toggle-slider"></span>
94
+ </button>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- 音量控制 -->
99
+ <div class="audio-setting-item">
100
+ <label class="audio-setting-label" data-i18n="audio.notification.volume">音量</label>
101
+ <div class="audio-volume-control">
102
+ <input type="range" id="audioVolumeSlider" class="audio-volume-slider"
103
+ min="0" max="100" value="50">
104
+ <span id="audioVolumeValue" class="audio-volume-value">50%</span>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- 音效選擇 -->
109
+ <div class="audio-setting-item">
110
+ <label class="audio-setting-label" data-i18n="audio.notification.selectAudio">選擇音效</label>
111
+ <div class="audio-select-control">
112
+ <select id="audioSelect" class="audio-select">
113
+ <!-- 選項將動態生成 -->
114
+ </select>
115
+ <button type="button" id="audioTestButton" class="btn btn-secondary audio-test-btn">
116
+ <span data-i18n="audio.notification.testPlay">測試播放</span>
117
+ </button>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- 自訂音效上傳 -->
122
+ <div class="audio-setting-item">
123
+ <label class="audio-setting-label" data-i18n="audio.notification.uploadCustom">上傳自訂音效</label>
124
+ <div class="audio-upload-control">
125
+ <input type="file" id="audioUploadInput" class="audio-upload-input"
126
+ accept="audio/mp3,audio/wav,audio/ogg" style="display: none;">
127
+ <button type="button" id="audioUploadButton" class="btn btn-primary audio-upload-btn">
128
+ 📁 <span data-i18n="audio.notification.chooseFile">選擇檔案</span>
129
+ </button>
130
+ <span class="audio-upload-hint" data-i18n="audio.notification.supportedFormats">
131
+ 支援 MP3、WAV、OGG 格式
132
+ </span>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- 自訂音效列表 -->
137
+ <div class="audio-setting-item">
138
+ <label class="audio-setting-label" data-i18n="audio.notification.customAudios">自訂音效</label>
139
+ <div class="audio-custom-list" id="audioCustomList">
140
+ <!-- 自訂音效列表將在這裡動態生成 -->
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ `;
147
+
148
+ this.container.insertAdjacentHTML('beforeend', html);
149
+
150
+ // 獲取 UI 元素引用
151
+ this.enabledToggle = this.container.querySelector('#audioNotificationEnabled');
152
+ this.volumeSlider = this.container.querySelector('#audioVolumeSlider');
153
+ this.volumeValue = this.container.querySelector('#audioVolumeValue');
154
+ this.audioSelect = this.container.querySelector('#audioSelect');
155
+ this.testButton = this.container.querySelector('#audioTestButton');
156
+ this.uploadButton = this.container.querySelector('#audioUploadButton');
157
+ this.uploadInput = this.container.querySelector('#audioUploadInput');
158
+ this.audioList = this.container.querySelector('#audioCustomList');
159
+ };
160
+
161
+ /**
162
+ * 設置事件監聽器
163
+ */
164
+ AudioSettingsUI.prototype.setupEventListeners = function() {
165
+ const self = this;
166
+
167
+ // 啟用開關事件
168
+ if (this.enabledToggle) {
169
+ this.enabledToggle.addEventListener('click', function() {
170
+ const newValue = !self.enabledToggle.classList.contains('active');
171
+ self.handleEnabledChange(newValue);
172
+ });
173
+ }
174
+
175
+ // 音量滑桿事件
176
+ if (this.volumeSlider) {
177
+ this.volumeSlider.addEventListener('input', function(e) {
178
+ self.handleVolumeChange(parseInt(e.target.value));
179
+ });
180
+ }
181
+
182
+ // 音效選擇事件
183
+ if (this.audioSelect) {
184
+ this.audioSelect.addEventListener('change', function(e) {
185
+ self.handleAudioSelect(e.target.value);
186
+ });
187
+ }
188
+
189
+ // 測試播放事件
190
+ if (this.testButton) {
191
+ this.testButton.addEventListener('click', function() {
192
+ self.handleTestPlay();
193
+ });
194
+ }
195
+
196
+ // 上傳按鈕事件
197
+ if (this.uploadButton) {
198
+ this.uploadButton.addEventListener('click', function() {
199
+ self.uploadInput.click();
200
+ });
201
+ }
202
+
203
+ // 檔案上傳事件
204
+ if (this.uploadInput) {
205
+ this.uploadInput.addEventListener('change', function(e) {
206
+ self.handleFileUpload(e.target.files[0]);
207
+ });
208
+ }
209
+
210
+ // 設置音效管理器回調
211
+ if (this.audioManager) {
212
+ this.audioManager.onSettingsChange = function(settings) {
213
+ console.log('🎨 音效設定變更,重新渲染 UI');
214
+ self.refreshUI();
215
+ };
216
+ }
217
+
218
+ // 語言變更將由 i18n.js 直接調用 updateAudioSelectTranslations 方法
219
+ };
220
+
221
+ /**
222
+ * 處理啟用狀態變更
223
+ */
224
+ AudioSettingsUI.prototype.handleEnabledChange = function(enabled) {
225
+ try {
226
+ this.audioManager.setEnabled(enabled);
227
+ this.updateControlsState();
228
+ this.showSuccess(this.t('audio.notification.enabledChanged', '音效通知設定已更新'));
229
+ } catch (error) {
230
+ console.error('❌ 設定啟用狀態失敗:', error);
231
+ this.showError(error.message);
232
+ // 恢復原狀態
233
+ this.enabledToggle.classList.toggle('active', this.audioManager.getSettings().enabled);
234
+ }
235
+ };
236
+
237
+ /**
238
+ * 處理音量變更
239
+ */
240
+ AudioSettingsUI.prototype.handleVolumeChange = function(volume) {
241
+ try {
242
+ this.audioManager.setVolume(volume);
243
+ this.volumeValue.textContent = volume + '%';
244
+ } catch (error) {
245
+ console.error('❌ 設定音量失敗:', error);
246
+ this.showError(error.message);
247
+ }
248
+ };
249
+
250
+ /**
251
+ * 處理音效選擇
252
+ */
253
+ AudioSettingsUI.prototype.handleAudioSelect = function(audioId) {
254
+ try {
255
+ this.audioManager.setSelectedAudio(audioId);
256
+ this.showSuccess(this.t('audio.notification.audioSelected', '音效已選擇'));
257
+ } catch (error) {
258
+ console.error('❌ 選擇音效失敗:', error);
259
+ this.showError(error.message);
260
+ // 恢復原選擇
261
+ this.audioSelect.value = this.audioManager.getSettings().selectedAudioId;
262
+ }
263
+ };
264
+
265
+ /**
266
+ * 處理測試播放
267
+ */
268
+ AudioSettingsUI.prototype.handleTestPlay = function() {
269
+ try {
270
+ const selectedAudioId = this.audioSelect.value;
271
+ const audioData = this.audioManager.getAudioById(selectedAudioId);
272
+
273
+ if (audioData) {
274
+ this.audioManager.playAudio(audioData);
275
+ this.showSuccess(this.t('audio.notification.testPlaying', '正在播放測試音效'));
276
+ } else {
277
+ this.showError(this.t('audio.notification.audioNotFound', '找不到選擇的音效'));
278
+ }
279
+ } catch (error) {
280
+ console.error('❌ 測試播放失敗:', error);
281
+ this.showError(error.message);
282
+ }
283
+ };
284
+
285
+ /**
286
+ * 處理檔案上傳
287
+ */
288
+ AudioSettingsUI.prototype.handleFileUpload = function(file) {
289
+ if (!file) return;
290
+
291
+ // 生成預設檔案名稱(去除副檔名)
292
+ const defaultName = file.name.replace(/\.[^/.]+$/, '');
293
+
294
+ // 顯示美觀的名稱輸入模態框
295
+ this.showAudioNameModal(defaultName, (audioName) => {
296
+ if (!audioName || !audioName.trim()) {
297
+ this.showError(this.t('audio.notification.nameRequired', '音效名稱不能為空'));
298
+ return;
299
+ }
300
+
301
+ // 顯示上傳中狀態
302
+ this.uploadButton.disabled = true;
303
+ this.uploadButton.innerHTML = '⏳ <span data-i18n="audio.notification.uploading">上傳中...</span>';
304
+
305
+ this.audioManager.addCustomAudio(audioName.trim(), file)
306
+ .then(audioData => {
307
+ this.showSuccess(this.t('audio.notification.uploadSuccess', '音效上傳成功: ') + audioData.name);
308
+ this.refreshAudioSelect();
309
+ this.refreshCustomAudioList();
310
+ // 清空檔案輸入
311
+ this.uploadInput.value = '';
312
+ })
313
+ .catch(error => {
314
+ console.error('❌ 上傳音效失敗:', error);
315
+ this.showError(error.message);
316
+ })
317
+ .finally(() => {
318
+ // 恢復按鈕狀態
319
+ this.uploadButton.disabled = false;
320
+ this.uploadButton.innerHTML = '📁 <span data-i18n="audio.notification.chooseFile">選擇檔案</span>';
321
+ });
322
+ });
323
+ };
324
+
325
+ /**
326
+ * 處理刪除自訂音效
327
+ */
328
+ AudioSettingsUI.prototype.handleDeleteCustomAudio = function(audioId) {
329
+ const audioData = this.audioManager.getAudioById(audioId);
330
+ if (!audioData) return;
331
+
332
+ const confirmMessage = this.t('audio.notification.deleteConfirm', '確定要刪除音效 "{name}" 嗎?')
333
+ .replace('{name}', audioData.name);
334
+
335
+ if (!confirm(confirmMessage)) return;
336
+
337
+ try {
338
+ this.audioManager.removeCustomAudio(audioId);
339
+ this.showSuccess(this.t('audio.notification.deleteSuccess', '音效已刪除'));
340
+ this.refreshAudioSelect();
341
+ this.refreshCustomAudioList();
342
+ } catch (error) {
343
+ console.error('❌ 刪除音效失敗:', error);
344
+ this.showError(error.message);
345
+ }
346
+ };
347
+
348
+ /**
349
+ * 刷新整個 UI
350
+ */
351
+ AudioSettingsUI.prototype.refreshUI = function() {
352
+ const settings = this.audioManager.getSettings();
353
+
354
+ // 更新啟用狀態
355
+ if (this.enabledToggle) {
356
+ this.enabledToggle.classList.toggle('active', settings.enabled);
357
+ }
358
+
359
+ // 更新音量
360
+ if (this.volumeSlider && this.volumeValue) {
361
+ this.volumeSlider.value = settings.volume;
362
+ this.volumeValue.textContent = settings.volume + '%';
363
+ }
364
+
365
+ // 更新音效選擇
366
+ this.refreshAudioSelect();
367
+
368
+ // 更新自訂音效列表
369
+ this.refreshCustomAudioList();
370
+
371
+ // 更新控制項狀態
372
+ this.updateControlsState();
373
+ };
374
+
375
+ /**
376
+ * 刷新音效選擇下拉選單
377
+ */
378
+ AudioSettingsUI.prototype.refreshAudioSelect = function() {
379
+ if (!this.audioSelect) return;
380
+
381
+ const settings = this.audioManager.getSettings();
382
+ const allAudios = this.audioManager.getAllAudios();
383
+
384
+ // 清空現有選項
385
+ this.audioSelect.innerHTML = '';
386
+
387
+ // 新增音效選項
388
+ allAudios.forEach(audio => {
389
+ const option = document.createElement('option');
390
+ option.value = audio.id;
391
+
392
+ // 使用翻譯後的名稱
393
+ let displayName = audio.name;
394
+ if (audio.isDefault) {
395
+ // 為預設音效提供翻譯
396
+ const translationKey = this.getDefaultAudioTranslationKey(audio.id);
397
+ if (translationKey) {
398
+ displayName = this.t(translationKey, audio.name);
399
+ }
400
+ displayName += ' (' + this.t('audio.notification.default', '預設') + ')';
401
+ }
402
+
403
+ option.textContent = displayName;
404
+
405
+ // 為預設音效選項新增 data-i18n 屬性,以便語言切換時自動更新
406
+ if (audio.isDefault) {
407
+ const translationKey = this.getDefaultAudioTranslationKey(audio.id);
408
+ if (translationKey) {
409
+ option.setAttribute('data-audio-id', audio.id);
410
+ option.setAttribute('data-is-default', 'true');
411
+ option.setAttribute('data-translation-key', translationKey);
412
+ }
413
+ }
414
+
415
+ if (audio.id === settings.selectedAudioId) {
416
+ option.selected = true;
417
+ }
418
+ this.audioSelect.appendChild(option);
419
+ });
420
+ };
421
+
422
+ /**
423
+ * 刷新自訂音效列表
424
+ */
425
+ AudioSettingsUI.prototype.refreshCustomAudioList = function() {
426
+ if (!this.audioList) return;
427
+
428
+ const customAudios = this.audioManager.getSettings().customAudios;
429
+
430
+ if (customAudios.length === 0) {
431
+ this.audioList.innerHTML = `
432
+ <div class="audio-empty-state">
433
+ <div style="font-size: 32px; margin-bottom: 8px;">🎵</div>
434
+ <div data-i18n="audio.notification.noCustomAudios">尚未上傳任何自訂音效</div>
435
+ </div>
436
+ `;
437
+ return;
438
+ }
439
+
440
+ let html = '';
441
+ customAudios.forEach(audio => {
442
+ html += this.createCustomAudioItemHTML(audio);
443
+ });
444
+
445
+ this.audioList.innerHTML = html;
446
+ this.setupCustomAudioEvents();
447
+ };
448
+
449
+ /**
450
+ * 創建自訂音效項目 HTML
451
+ */
452
+ AudioSettingsUI.prototype.createCustomAudioItemHTML = function(audio) {
453
+ const createdDate = new Date(audio.createdAt).toLocaleDateString();
454
+
455
+ return `
456
+ <div class="audio-custom-item" data-audio-id="${audio.id}">
457
+ <div class="audio-custom-info">
458
+ <div class="audio-custom-name">${Utils.escapeHtml(audio.name)}</div>
459
+ <div class="audio-custom-meta">
460
+ <span data-i18n="audio.notification.created">建立於</span>: ${createdDate}
461
+ | <span data-i18n="audio.notification.format">格式</span>: ${audio.mimeType}
462
+ </div>
463
+ </div>
464
+ <div class="audio-custom-actions">
465
+ <button type="button" class="btn btn-sm btn-secondary audio-play-btn"
466
+ data-audio-id="${audio.id}" title="播放">
467
+ ▶️
468
+ </button>
469
+ <button type="button" class="btn btn-sm btn-danger audio-delete-btn"
470
+ data-audio-id="${audio.id}" title="刪除">
471
+ 🗑️
472
+ </button>
473
+ </div>
474
+ </div>
475
+ `;
476
+ };
477
+
478
+ /**
479
+ * 設置自訂音效項目事件
480
+ */
481
+ AudioSettingsUI.prototype.setupCustomAudioEvents = function() {
482
+ const self = this;
483
+
484
+ // 播放按鈕事件
485
+ const playButtons = this.audioList.querySelectorAll('.audio-play-btn');
486
+ playButtons.forEach(button => {
487
+ button.addEventListener('click', function() {
488
+ const audioId = button.getAttribute('data-audio-id');
489
+ const audioData = self.audioManager.getAudioById(audioId);
490
+ if (audioData) {
491
+ self.audioManager.playAudio(audioData);
492
+ }
493
+ });
494
+ });
495
+
496
+ // 刪除按鈕事件
497
+ const deleteButtons = this.audioList.querySelectorAll('.audio-delete-btn');
498
+ deleteButtons.forEach(button => {
499
+ button.addEventListener('click', function() {
500
+ const audioId = button.getAttribute('data-audio-id');
501
+ self.handleDeleteCustomAudio(audioId);
502
+ });
503
+ });
504
+ };
505
+
506
+ /**
507
+ * 更新控制項狀態
508
+ */
509
+ AudioSettingsUI.prototype.updateControlsState = function() {
510
+ const enabled = this.enabledToggle ? this.enabledToggle.classList.contains('active') : false;
511
+
512
+ // 根據啟用狀態禁用/啟用控制項
513
+ const controls = [
514
+ this.volumeSlider,
515
+ this.audioSelect,
516
+ this.testButton,
517
+ this.uploadButton
518
+ ];
519
+
520
+ controls.forEach(control => {
521
+ if (control) {
522
+ control.disabled = !enabled;
523
+ }
524
+ });
525
+ };
526
+
527
+ /**
528
+ * 顯示成功訊息
529
+ */
530
+ AudioSettingsUI.prototype.showSuccess = function(message) {
531
+ if (Utils && Utils.showMessage) {
532
+ Utils.showMessage(message, Utils.CONSTANTS.MESSAGE_SUCCESS);
533
+ } else {
534
+ console.log('✅', message);
535
+ }
536
+ };
537
+
538
+ /**
539
+ * 顯示錯誤訊息
540
+ */
541
+ AudioSettingsUI.prototype.showError = function(message) {
542
+ if (Utils && Utils.showMessage) {
543
+ Utils.showMessage(message, Utils.CONSTANTS.MESSAGE_ERROR);
544
+ } else {
545
+ console.error('❌', message);
546
+ }
547
+ };
548
+
549
+ /**
550
+ * 顯示音效名稱輸入模態框
551
+ */
552
+ AudioSettingsUI.prototype.showAudioNameModal = function(defaultName, onConfirm) {
553
+ const self = this;
554
+
555
+ // 創建模態框 HTML
556
+ const modalHTML = `
557
+ <div class="audio-name-modal-overlay" id="audioNameModalOverlay">
558
+ <div class="audio-name-modal">
559
+ <div class="audio-name-modal-header">
560
+ <h4 data-i18n="audio.notification.enterAudioName">輸入音效名稱</h4>
561
+ <button type="button" class="audio-name-modal-close" id="audioNameModalClose">×</button>
562
+ </div>
563
+ <div class="audio-name-modal-body">
564
+ <label for="audioNameInput" data-i18n="audio.notification.audioName">音效名稱:</label>
565
+ <input type="text" id="audioNameInput" class="audio-name-input"
566
+ value="${Utils.escapeHtml(defaultName)}"
567
+ placeholder="${this.t('audio.notification.audioNamePlaceholder', '請輸入音效名稱...')}"
568
+ maxlength="50">
569
+ <div class="audio-name-hint" data-i18n="audio.notification.audioNameHint">
570
+ 留空將使用預設檔案名稱
571
+ </div>
572
+ </div>
573
+ <div class="audio-name-modal-footer">
574
+ <button type="button" class="btn btn-secondary" id="audioNameModalCancel">
575
+ <span data-i18n="buttons.cancel">取消</span>
576
+ </button>
577
+ <button type="button" class="btn btn-primary" id="audioNameModalConfirm">
578
+ <span data-i18n="buttons.ok">確定</span>
579
+ </button>
580
+ </div>
581
+ </div>
582
+ </div>
583
+ `;
584
+
585
+ // 新增模態框到頁面
586
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
587
+
588
+ // 獲取元素引用
589
+ const overlay = document.getElementById('audioNameModalOverlay');
590
+ const input = document.getElementById('audioNameInput');
591
+ const closeBtn = document.getElementById('audioNameModalClose');
592
+ const cancelBtn = document.getElementById('audioNameModalCancel');
593
+ const confirmBtn = document.getElementById('audioNameModalConfirm');
594
+
595
+ // 聚焦輸入框並選中文字
596
+ setTimeout(() => {
597
+ input.focus();
598
+ input.select();
599
+ }, 100);
600
+
601
+ // 關閉模態框函數
602
+ const closeModal = () => {
603
+ if (overlay && overlay.parentNode) {
604
+ overlay.parentNode.removeChild(overlay);
605
+ }
606
+ };
607
+
608
+ // 確認函數
609
+ const confirm = () => {
610
+ const audioName = input.value.trim() || defaultName;
611
+ closeModal();
612
+ if (onConfirm) {
613
+ onConfirm(audioName);
614
+ }
615
+ };
616
+
617
+ // 事件監聽器
618
+ closeBtn.addEventListener('click', closeModal);
619
+ cancelBtn.addEventListener('click', closeModal);
620
+ confirmBtn.addEventListener('click', confirm);
621
+
622
+ // 點擊遮罩關閉
623
+ overlay.addEventListener('click', function(e) {
624
+ if (e.target === overlay) {
625
+ closeModal();
626
+ }
627
+ });
628
+
629
+ // Enter 鍵確認,Escape 鍵取消
630
+ input.addEventListener('keydown', function(e) {
631
+ if (e.key === 'Enter') {
632
+ e.preventDefault();
633
+ confirm();
634
+ } else if (e.key === 'Escape') {
635
+ e.preventDefault();
636
+ closeModal();
637
+ }
638
+ });
639
+ };
640
+
641
+
642
+
643
+ /**
644
+ * 應用初始翻譯到新創建的元素
645
+ */
646
+ AudioSettingsUI.prototype.applyInitialTranslations = function() {
647
+ if (!this.container) return;
648
+
649
+ // 對容器內所有有 data-i18n 屬性的元素應用翻譯
650
+ const elements = this.container.querySelectorAll('[data-i18n]');
651
+ elements.forEach(element => {
652
+ const key = element.getAttribute('data-i18n');
653
+ const translation = this.t(key);
654
+ if (translation && translation !== key) {
655
+ element.textContent = translation;
656
+ }
657
+ });
658
+
659
+ // 對有 data-i18n-placeholder 屬性的元素應用翻譯
660
+ const placeholderElements = this.container.querySelectorAll('[data-i18n-placeholder]');
661
+ placeholderElements.forEach(element => {
662
+ const key = element.getAttribute('data-i18n-placeholder');
663
+ const translation = this.t(key);
664
+ if (translation && translation !== key) {
665
+ element.placeholder = translation;
666
+ }
667
+ });
668
+
669
+ // 對有 data-i18n-aria-label 屬性的元素應用翻譯
670
+ const ariaLabelElements = this.container.querySelectorAll('[data-i18n-aria-label]');
671
+ ariaLabelElements.forEach(element => {
672
+ const key = element.getAttribute('data-i18n-aria-label');
673
+ const translation = this.t(key);
674
+ if (translation && translation !== key) {
675
+ element.setAttribute('aria-label', translation);
676
+ }
677
+ });
678
+
679
+ console.log('🌐 AudioSettingsUI 初始翻譯已應用');
680
+ };
681
+
682
+ /**
683
+ * 更新所有翻譯(包括靜態文字和動態內容)
684
+ */
685
+ AudioSettingsUI.prototype.updateTranslations = function() {
686
+ // 更新所有靜態文字元素
687
+ this.applyInitialTranslations();
688
+
689
+ // 更新音效選擇器的翻譯
690
+ this.updateAudioSelectTranslations();
691
+
692
+ console.log('🌐 AudioSettingsUI 翻譯已更新');
693
+ };
694
+
695
+ /**
696
+ * 更新音效選擇器的翻譯
697
+ */
698
+ AudioSettingsUI.prototype.updateAudioSelectTranslations = function() {
699
+ if (!this.audioSelect) return;
700
+
701
+ const options = this.audioSelect.querySelectorAll('option[data-is-default="true"]');
702
+ options.forEach(option => {
703
+ const audioId = option.getAttribute('data-audio-id');
704
+ const translationKey = option.getAttribute('data-translation-key');
705
+
706
+ if (audioId && translationKey) {
707
+ const audioData = this.audioManager.getAudioById(audioId);
708
+ if (audioData) {
709
+ const translatedName = this.t(translationKey, audioData.name);
710
+ const defaultText = this.t('audio.notification.default', '預設');
711
+ option.textContent = translatedName + ' (' + defaultText + ')';
712
+ }
713
+ }
714
+ });
715
+ };
716
+
717
+ /**
718
+ * 獲取預設音效的翻譯鍵值
719
+ */
720
+ AudioSettingsUI.prototype.getDefaultAudioTranslationKey = function(audioId) {
721
+ const translationMap = {
722
+ 'default-beep': 'audio.notification.defaultBeep',
723
+ 'notification-ding': 'audio.notification.notificationDing',
724
+ 'soft-chime': 'audio.notification.softChime'
725
+ };
726
+ return translationMap[audioId] || null;
727
+ };
728
+
729
+ // 匯出到全域命名空間
730
+ window.MCPFeedback.AudioSettingsUI = AudioSettingsUI;
731
+
732
+ })();