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,578 @@
1
+ /**
2
+ * MCP AI Supervisor - UI 管理模組
3
+ * =================================
4
+ *
5
+ * 處理 UI 狀態更新、指示器管理和頁籤切換
6
+ */
7
+
8
+ (function() {
9
+ 'use strict';
10
+
11
+ // 確保命名空間和依賴存在
12
+ window.MCPFeedback = window.MCPFeedback || {};
13
+ const Utils = window.MCPFeedback.Utils;
14
+
15
+ /**
16
+ * UI 管理器建構函數
17
+ */
18
+ function UIManager(options) {
19
+ options = options || {};
20
+
21
+ // 當前狀態
22
+ this.currentTab = options.currentTab || 'combined';
23
+ this.feedbackState = Utils.CONSTANTS.FEEDBACK_WAITING;
24
+ this.layoutMode = options.layoutMode || 'combined-vertical';
25
+ this.lastSubmissionTime = null;
26
+
27
+ // UI 元素
28
+ this.connectionIndicator = null;
29
+ this.connectionText = null;
30
+ this.tabButtons = null;
31
+ this.tabContents = null;
32
+ this.submitBtn = null;
33
+ this.feedbackText = null;
34
+
35
+ // 回調函數
36
+ this.onTabChange = options.onTabChange || null;
37
+ this.onLayoutModeChange = options.onLayoutModeChange || null;
38
+
39
+ // 初始化防抖函數
40
+ this.initDebounceHandlers();
41
+
42
+ this.initUIElements();
43
+ }
44
+
45
+ /**
46
+ * 初始化防抖處理器
47
+ */
48
+ UIManager.prototype.initDebounceHandlers = function() {
49
+ // 為狀態指示器更新添加防抖
50
+ this._debouncedUpdateStatusIndicator = Utils.DOM.debounce(
51
+ this._originalUpdateStatusIndicator.bind(this),
52
+ 100,
53
+ false
54
+ );
55
+
56
+ // 為狀態指示器元素更新添加防抖
57
+ this._debouncedUpdateStatusIndicatorElement = Utils.DOM.debounce(
58
+ this._originalUpdateStatusIndicatorElement.bind(this),
59
+ 50,
60
+ false
61
+ );
62
+ };
63
+
64
+ /**
65
+ * 初始化 UI 元素
66
+ */
67
+ UIManager.prototype.initUIElements = function() {
68
+ // 基本 UI 元素
69
+ this.connectionIndicator = Utils.safeQuerySelector('#connectionIndicator');
70
+ this.connectionText = Utils.safeQuerySelector('#connectionText');
71
+
72
+ // 頁籤相關元素
73
+ this.tabButtons = document.querySelectorAll('.tab-button');
74
+ this.tabContents = document.querySelectorAll('.tab-content');
75
+
76
+ // 回饋相關元素
77
+ this.submitBtn = Utils.safeQuerySelector('#submitBtn');
78
+
79
+ console.log('✅ UI 元素初始化完成');
80
+ };
81
+
82
+ /**
83
+ * 初始化頁籤功能
84
+ */
85
+ UIManager.prototype.initTabs = function() {
86
+ const self = this;
87
+
88
+ // 設置頁籤點擊事件
89
+ this.tabButtons.forEach(function(button) {
90
+ button.addEventListener('click', function() {
91
+ const tabName = button.getAttribute('data-tab');
92
+ self.switchTab(tabName);
93
+ });
94
+ });
95
+
96
+ // 根據佈局模式確定初始頁籤
97
+ let initialTab = this.currentTab;
98
+ if (this.layoutMode.startsWith('combined')) {
99
+ initialTab = 'combined';
100
+ } else if (this.currentTab === 'combined') {
101
+ initialTab = 'feedback';
102
+ }
103
+
104
+ // 設置初始頁籤
105
+ this.setInitialTab(initialTab);
106
+ };
107
+
108
+ /**
109
+ * 設置初始頁籤(不觸發保存)
110
+ */
111
+ UIManager.prototype.setInitialTab = function(tabName) {
112
+ this.currentTab = tabName;
113
+ this.updateTabDisplay(tabName);
114
+ this.handleSpecialTabs(tabName);
115
+ console.log('初始化頁籤: ' + tabName);
116
+ };
117
+
118
+ /**
119
+ * 切換頁籤
120
+ */
121
+ UIManager.prototype.switchTab = function(tabName) {
122
+ this.currentTab = tabName;
123
+ this.updateTabDisplay(tabName);
124
+ this.handleSpecialTabs(tabName);
125
+
126
+ // 觸發回調
127
+ if (this.onTabChange) {
128
+ this.onTabChange(tabName);
129
+ }
130
+
131
+ console.log('切換到頁籤: ' + tabName);
132
+ };
133
+
134
+ /**
135
+ * 更新頁籤顯示
136
+ */
137
+ UIManager.prototype.updateTabDisplay = function(tabName) {
138
+ // 更新按鈕狀態
139
+ this.tabButtons.forEach(function(button) {
140
+ if (button.getAttribute('data-tab') === tabName) {
141
+ button.classList.add('active');
142
+ } else {
143
+ button.classList.remove('active');
144
+ }
145
+ });
146
+
147
+ // 更新內容顯示
148
+ this.tabContents.forEach(function(content) {
149
+ if (content.id === 'tab-' + tabName) {
150
+ content.classList.add('active');
151
+ } else {
152
+ content.classList.remove('active');
153
+ }
154
+ });
155
+ };
156
+
157
+ /**
158
+ * 處理特殊頁籤
159
+ */
160
+ UIManager.prototype.handleSpecialTabs = function(tabName) {
161
+ if (tabName === 'combined') {
162
+ this.handleCombinedMode();
163
+ }
164
+ };
165
+
166
+ /**
167
+ * 處理合併模式
168
+ */
169
+ UIManager.prototype.handleCombinedMode = function() {
170
+ console.log('切換到組合模式');
171
+
172
+ // 確保合併模式的佈局樣式正確應用
173
+ const combinedTab = Utils.safeQuerySelector('#tab-combined');
174
+ if (combinedTab) {
175
+ combinedTab.classList.remove('combined-vertical', 'combined-horizontal');
176
+ if (this.layoutMode === 'combined-vertical') {
177
+ combinedTab.classList.add('combined-vertical');
178
+ } else if (this.layoutMode === 'combined-horizontal') {
179
+ combinedTab.classList.add('combined-horizontal');
180
+ }
181
+ }
182
+ };
183
+
184
+ /**
185
+ * 更新頁籤可見性
186
+ */
187
+ UIManager.prototype.updateTabVisibility = function() {
188
+ const combinedTab = document.querySelector('.tab-button[data-tab="combined"]');
189
+ const feedbackTab = document.querySelector('.tab-button[data-tab="feedback"]');
190
+ const summaryTab = document.querySelector('.tab-button[data-tab="summary"]');
191
+
192
+ // 只使用合併模式:顯示合併模式頁籤,隱藏回饋和AI摘要頁籤
193
+ if (combinedTab) combinedTab.style.display = 'inline-block';
194
+ if (feedbackTab) feedbackTab.style.display = 'none';
195
+ if (summaryTab) summaryTab.style.display = 'none';
196
+ };
197
+
198
+ /**
199
+ * 設置回饋狀態
200
+ */
201
+ UIManager.prototype.setFeedbackState = function(state, sessionId) {
202
+ const previousState = this.feedbackState;
203
+ this.feedbackState = state;
204
+
205
+ if (sessionId) {
206
+ console.log('🔄 會話 ID: ' + sessionId.substring(0, 8) + '...');
207
+ }
208
+
209
+ console.log('📊 狀態變更: ' + previousState + ' → ' + state);
210
+ this.updateUIState();
211
+ this.updateStatusIndicator();
212
+ };
213
+
214
+ /**
215
+ * 更新 UI 狀態
216
+ */
217
+ UIManager.prototype.updateUIState = function() {
218
+ this.updateSubmitButton();
219
+ this.updateFeedbackInputs();
220
+ this.updateImageUploadAreas();
221
+ };
222
+
223
+ /**
224
+ * 更新提交按鈕狀態
225
+ */
226
+ UIManager.prototype.updateSubmitButton = function() {
227
+ const submitButtons = [
228
+ Utils.safeQuerySelector('#submitBtn')
229
+ ].filter(function(btn) { return btn !== null; });
230
+
231
+ const self = this;
232
+ submitButtons.forEach(function(button) {
233
+ if (!button) return;
234
+
235
+ switch (self.feedbackState) {
236
+ case Utils.CONSTANTS.FEEDBACK_WAITING:
237
+ button.textContent = window.i18nManager ? window.i18nManager.t('buttons.submit') : '提交回饋';
238
+ button.className = 'btn btn-primary';
239
+ button.disabled = false;
240
+ break;
241
+ case Utils.CONSTANTS.FEEDBACK_PROCESSING:
242
+ button.textContent = window.i18nManager ? window.i18nManager.t('buttons.processing') : '處理中...';
243
+ button.className = 'btn btn-secondary';
244
+ button.disabled = true;
245
+ break;
246
+ case Utils.CONSTANTS.FEEDBACK_SUBMITTED:
247
+ button.textContent = window.i18nManager ? window.i18nManager.t('buttons.submitted') : '已提交';
248
+ button.className = 'btn btn-success';
249
+ button.disabled = true;
250
+ break;
251
+ }
252
+ });
253
+ };
254
+
255
+ /**
256
+ * 更新回饋輸入框狀態
257
+ */
258
+ UIManager.prototype.updateFeedbackInputs = function() {
259
+ const feedbackInput = Utils.safeQuerySelector('#combinedFeedbackText');
260
+ const canInput = this.feedbackState === Utils.CONSTANTS.FEEDBACK_WAITING;
261
+
262
+ if (feedbackInput) {
263
+ feedbackInput.disabled = !canInput;
264
+ }
265
+ };
266
+
267
+ /**
268
+ * 更新圖片上傳區域狀態
269
+ */
270
+ UIManager.prototype.updateImageUploadAreas = function() {
271
+ const uploadAreas = [
272
+ Utils.safeQuerySelector('#feedbackImageUploadArea'),
273
+ Utils.safeQuerySelector('#combinedImageUploadArea')
274
+ ].filter(function(area) { return area !== null; });
275
+
276
+ const canUpload = this.feedbackState === Utils.CONSTANTS.FEEDBACK_WAITING;
277
+ uploadAreas.forEach(function(area) {
278
+ if (canUpload) {
279
+ area.classList.remove('disabled');
280
+ } else {
281
+ area.classList.add('disabled');
282
+ }
283
+ });
284
+ };
285
+
286
+ /**
287
+ * 更新狀態指示器(原始版本,供防抖使用)
288
+ */
289
+ UIManager.prototype._originalUpdateStatusIndicator = function() {
290
+ const feedbackStatusIndicator = Utils.safeQuerySelector('#feedbackStatusIndicator');
291
+ const combinedStatusIndicator = Utils.safeQuerySelector('#combinedFeedbackStatusIndicator');
292
+
293
+ const statusInfo = this.getStatusInfo();
294
+
295
+ if (feedbackStatusIndicator) {
296
+ this._originalUpdateStatusIndicatorElement(feedbackStatusIndicator, statusInfo);
297
+ }
298
+
299
+ if (combinedStatusIndicator) {
300
+ this._originalUpdateStatusIndicatorElement(combinedStatusIndicator, statusInfo);
301
+ }
302
+
303
+ // 減少重複日誌:只在狀態真正改變時記錄
304
+ if (!this._lastStatusInfo || this._lastStatusInfo.status !== statusInfo.status) {
305
+ console.log('✅ 狀態指示器已更新: ' + statusInfo.status + ' - ' + statusInfo.title);
306
+ this._lastStatusInfo = statusInfo;
307
+ }
308
+ };
309
+
310
+ /**
311
+ * 更新狀態指示器(防抖版本)
312
+ */
313
+ UIManager.prototype.updateStatusIndicator = function() {
314
+ if (this._debouncedUpdateStatusIndicator) {
315
+ this._debouncedUpdateStatusIndicator();
316
+ } else {
317
+ // 回退到原始方法(防抖未初始化時)
318
+ this._originalUpdateStatusIndicator();
319
+ }
320
+ };
321
+
322
+ /**
323
+ * 獲取狀態信息
324
+ */
325
+ UIManager.prototype.getStatusInfo = function() {
326
+ let icon, title, message, status;
327
+
328
+ switch (this.feedbackState) {
329
+ case Utils.CONSTANTS.FEEDBACK_WAITING:
330
+ icon = '';
331
+ title = window.i18nManager ? window.i18nManager.t('status.waiting.title') : 'Waiting';
332
+ message = window.i18nManager ? window.i18nManager.t('status.waiting.message') : 'Please provide your feedback';
333
+ status = 'waiting';
334
+ break;
335
+
336
+ case Utils.CONSTANTS.FEEDBACK_PROCESSING:
337
+ icon = '';
338
+ title = window.i18nManager ? window.i18nManager.t('status.processing.title') : 'Processing';
339
+ message = window.i18nManager ? window.i18nManager.t('status.processing.message') : 'Submitting your feedback...';
340
+ status = 'processing';
341
+ break;
342
+
343
+ case Utils.CONSTANTS.FEEDBACK_SUBMITTED:
344
+ const timeStr = this.lastSubmissionTime ?
345
+ new Date(this.lastSubmissionTime).toLocaleTimeString() : '';
346
+ icon = '';
347
+ title = window.i18nManager ? window.i18nManager.t('status.submitted.title') : 'Submitted';
348
+ message = window.i18nManager ? window.i18nManager.t('status.submitted.message') : 'Waiting for next MCP call';
349
+ if (timeStr) {
350
+ message += ' (' + timeStr + ')';
351
+ }
352
+ status = 'submitted';
353
+ break;
354
+
355
+ default:
356
+ icon = '';
357
+ title = window.i18nManager ? window.i18nManager.t('status.waiting.title') : 'Waiting';
358
+ message = window.i18nManager ? window.i18nManager.t('status.waiting.message') : '請提供您的回饋意見';
359
+ status = 'waiting';
360
+ }
361
+
362
+ return { icon: icon, title: title, message: message, status: status };
363
+ };
364
+
365
+ /**
366
+ * 更新單個狀態指示器元素(原始版本,供防抖使用)
367
+ */
368
+ UIManager.prototype._originalUpdateStatusIndicatorElement = function(element, statusInfo) {
369
+ if (!element) return;
370
+
371
+ // 更新狀態類別
372
+ element.className = 'feedback-status-indicator status-' + statusInfo.status;
373
+ element.style.display = 'block';
374
+
375
+ // 更新標題
376
+ const titleElement = element.querySelector('.status-title');
377
+ if (titleElement) {
378
+ titleElement.textContent = statusInfo.icon + ' ' + statusInfo.title;
379
+ }
380
+
381
+ // 更新訊息
382
+ const messageElement = element.querySelector('.status-message');
383
+ if (messageElement) {
384
+ messageElement.textContent = statusInfo.message;
385
+ }
386
+
387
+ // 減少重複日誌:只記錄元素 ID 變化
388
+ if (element.id) {
389
+ console.log('🔧 已更新狀態指示器: ' + element.id + ' -> ' + statusInfo.status);
390
+ }
391
+ };
392
+
393
+ /**
394
+ * 更新單個狀態指示器元素(防抖版本)
395
+ */
396
+ UIManager.prototype.updateStatusIndicatorElement = function(element, statusInfo) {
397
+ if (this._debouncedUpdateStatusIndicatorElement) {
398
+ this._debouncedUpdateStatusIndicatorElement(element, statusInfo);
399
+ } else {
400
+ // 回退到原始方法(防抖未初始化時)
401
+ this._originalUpdateStatusIndicatorElement(element, statusInfo);
402
+ }
403
+ };
404
+
405
+ /**
406
+ * 更新連接狀態
407
+ */
408
+ UIManager.prototype.updateConnectionStatus = function(status, text) {
409
+ if (this.connectionIndicator) {
410
+ this.connectionIndicator.className = 'connection-indicator ' + status;
411
+ }
412
+ if (this.connectionText) {
413
+ this.connectionText.textContent = text;
414
+ }
415
+ };
416
+
417
+ /**
418
+ * 安全地渲染 Markdown 內容
419
+ */
420
+ UIManager.prototype.renderMarkdownSafely = function(content) {
421
+ try {
422
+ if (content === undefined || content === null) {
423
+ console.warn('⚠️ renderMarkdownSafely: 收到 null/undefined,使用空字串');
424
+ return '';
425
+ }
426
+ // 檢查 marked 和 DOMPurify 是否可用
427
+ if (typeof window.marked === 'undefined' || typeof window.DOMPurify === 'undefined') {
428
+ console.warn('⚠️ Markdown 庫未載入,使用純文字顯示');
429
+ return this.escapeHtml(content);
430
+ }
431
+
432
+ // 使用 marked 解析 Markdown
433
+ const htmlContent = window.marked.parse(content);
434
+
435
+ // 使用 DOMPurify 清理 HTML
436
+ const cleanHtml = window.DOMPurify.sanitize(htmlContent, {
437
+ ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'strong', 'em', 'code', 'pre', 'ul', 'ol', 'li', 'blockquote', 'a', 'hr', 'del', 's', 'table', 'thead', 'tbody', 'tr', 'td', 'th'],
438
+ ALLOWED_ATTR: ['href', 'title', 'class', 'align', 'style'],
439
+ ALLOW_DATA_ATTR: false
440
+ });
441
+
442
+ return cleanHtml;
443
+ } catch (error) {
444
+ console.error('❌ Markdown 渲染失敗:', error);
445
+ return this.escapeHtml(content);
446
+ }
447
+ };
448
+
449
+ /**
450
+ * HTML 轉義函數
451
+ */
452
+ UIManager.prototype.escapeHtml = function(text) {
453
+ const div = document.createElement('div');
454
+ div.textContent = text;
455
+ return div.innerHTML;
456
+ };
457
+
458
+ /**
459
+ * 更新 AI 摘要內容
460
+ */
461
+ UIManager.prototype.updateAISummaryContent = function(summary) {
462
+ console.log('📝 更新 AI 摘要內容...', '內容長度:', summary ? summary.length : 'undefined');
463
+ console.log('📝 marked 可用:', typeof window.marked !== 'undefined');
464
+ console.log('📝 DOMPurify 可用:', typeof window.DOMPurify !== 'undefined');
465
+
466
+ // API 競態或異常響應可能缺 summary;勿用空內容覆蓋模板/SSR 已渲染的最後一次有效摘要
467
+ if (summary === undefined || summary === null) {
468
+ console.warn('📝 updateAISummaryContent: summary 為 null/undefined,保留頁面現有內容');
469
+ return;
470
+ }
471
+
472
+ // 渲染 Markdown 內容
473
+ const renderedContent = this.renderMarkdownSafely(summary);
474
+ console.log('📝 渲染後內容長度:', renderedContent ? renderedContent.length : 'undefined');
475
+
476
+ const summaryContent = Utils.safeQuerySelector('#summaryContent');
477
+ if (summaryContent) {
478
+ summaryContent.innerHTML = renderedContent;
479
+ console.log('✅ 已更新分頁模式摘要內容(Markdown 渲染)');
480
+ } else {
481
+ console.warn('⚠️ 找不到 #summaryContent 元素');
482
+ }
483
+
484
+ const combinedSummaryContent = Utils.safeQuerySelector('#combinedSummaryContent');
485
+ if (combinedSummaryContent) {
486
+ combinedSummaryContent.innerHTML = renderedContent;
487
+ console.log('✅ 已更新合併模式摘要內容(Markdown 渲染)');
488
+ } else {
489
+ console.warn('⚠️ 找不到 #combinedSummaryContent 元素');
490
+ }
491
+ };
492
+
493
+ /**
494
+ * 重置回饋表單
495
+ * @param {boolean} clearText - 是否清空文字內容,預設為 false
496
+ */
497
+ UIManager.prototype.resetFeedbackForm = function(clearText) {
498
+ console.log('🔄 重置回饋表單...');
499
+
500
+ // 根據參數決定是否清空回饋輸入
501
+ const feedbackInput = Utils.safeQuerySelector('#combinedFeedbackText');
502
+ if (feedbackInput) {
503
+ if (clearText === true) {
504
+ feedbackInput.value = '';
505
+ console.log('📝 已清空文字內容');
506
+ }
507
+ // 只有在等待狀態才啟用輸入框
508
+ const canInput = this.feedbackState === Utils.CONSTANTS.FEEDBACK_WAITING;
509
+ feedbackInput.disabled = !canInput;
510
+ }
511
+
512
+ // 重新啟用提交按鈕
513
+ const submitButtons = [
514
+ Utils.safeQuerySelector('#submitBtn')
515
+ ].filter(function(btn) { return btn !== null; });
516
+
517
+ submitButtons.forEach(function(button) {
518
+ button.disabled = false;
519
+ const defaultText = window.i18nManager ? window.i18nManager.t('buttons.submit') : '提交回饋';
520
+ button.textContent = button.getAttribute('data-original-text') || defaultText;
521
+ });
522
+
523
+ console.log('✅ 回饋表單重置完成');
524
+ };
525
+
526
+ /**
527
+ * 應用佈局模式
528
+ */
529
+ UIManager.prototype.applyLayoutMode = function(layoutMode) {
530
+ this.layoutMode = layoutMode;
531
+
532
+ const expectedClassName = 'layout-' + layoutMode;
533
+ if (document.body.className !== expectedClassName) {
534
+ console.log('應用佈局模式: ' + layoutMode);
535
+ document.body.className = expectedClassName;
536
+ }
537
+
538
+ this.updateTabVisibility();
539
+
540
+ // 如果當前頁籤不是合併模式,則切換到合併模式頁籤
541
+ if (this.currentTab !== 'combined') {
542
+ this.currentTab = 'combined';
543
+ }
544
+
545
+ // 觸發回調
546
+ if (this.onLayoutModeChange) {
547
+ this.onLayoutModeChange(layoutMode);
548
+ }
549
+ };
550
+
551
+ /**
552
+ * 獲取當前頁籤
553
+ */
554
+ UIManager.prototype.getCurrentTab = function() {
555
+ return this.currentTab;
556
+ };
557
+
558
+ /**
559
+ * 獲取當前回饋狀態
560
+ */
561
+ UIManager.prototype.getFeedbackState = function() {
562
+ return this.feedbackState;
563
+ };
564
+
565
+ /**
566
+ * 設置最後提交時間
567
+ */
568
+ UIManager.prototype.setLastSubmissionTime = function(timestamp) {
569
+ this.lastSubmissionTime = timestamp;
570
+ this.updateStatusIndicator();
571
+ };
572
+
573
+ // 將 UIManager 加入命名空間
574
+ window.MCPFeedback.UIManager = UIManager;
575
+
576
+ console.log('✅ UIManager 模組載入完成');
577
+
578
+ })();