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,555 @@
1
+ /**
2
+ * 現代化檔案上傳管理器
3
+ * 使用事件委託模式,避免重複事件監聽器問題
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ // 確保命名空間存在
10
+ if (!window.MCPFeedback) {
11
+ window.MCPFeedback = {};
12
+ }
13
+
14
+ /**
15
+ * 檔案上傳管理器建構函數
16
+ */
17
+ function FileUploadManager(options) {
18
+ options = options || {};
19
+
20
+ // 配置選項
21
+ this.maxFileSize = options.maxFileSize || 0; // 0 表示無限制
22
+ this.enableBase64Detail = options.enableBase64Detail || false;
23
+ this.acceptedTypes = options.acceptedTypes || 'image/*';
24
+ this.maxFiles = options.maxFiles || 10;
25
+
26
+ // 狀態管理
27
+ this.files = [];
28
+ this.isInitialized = false;
29
+ this.debounceTimeout = null;
30
+ this.lastClickTime = 0;
31
+ this.isProcessingClick = false;
32
+ this.lastClickTime = 0;
33
+
34
+ // 事件回調
35
+ this.onFileAdd = options.onFileAdd || null;
36
+ this.onFileRemove = options.onFileRemove || null;
37
+ this.onSettingsChange = options.onSettingsChange || null;
38
+
39
+ // 綁定方法上下文
40
+ this.handleDelegatedEvent = this.handleDelegatedEvent.bind(this);
41
+ this.handleGlobalPaste = this.handleGlobalPaste.bind(this);
42
+
43
+ console.log('📁 FileUploadManager 初始化完成');
44
+ }
45
+
46
+ /**
47
+ * 初始化檔案上傳管理器
48
+ */
49
+ FileUploadManager.prototype.initialize = function() {
50
+ if (this.isInitialized) {
51
+ console.warn('⚠️ FileUploadManager 已經初始化過了');
52
+ return;
53
+ }
54
+
55
+ this.setupEventDelegation();
56
+ this.setupGlobalPasteHandler();
57
+ this.isInitialized = true;
58
+
59
+ console.log('✅ FileUploadManager 事件委託設置完成');
60
+ };
61
+
62
+ /**
63
+ * 設置事件委託
64
+ * 使用單一事件監聽器處理所有檔案上傳相關事件
65
+ */
66
+ FileUploadManager.prototype.setupEventDelegation = function() {
67
+ // 移除舊的事件監聽器
68
+ document.removeEventListener('click', this.handleDelegatedEvent);
69
+ document.removeEventListener('dragover', this.handleDelegatedEvent);
70
+ document.removeEventListener('dragleave', this.handleDelegatedEvent);
71
+ document.removeEventListener('drop', this.handleDelegatedEvent);
72
+ document.removeEventListener('change', this.handleDelegatedEvent);
73
+
74
+ // 設置新的事件委託
75
+ document.addEventListener('click', this.handleDelegatedEvent);
76
+ document.addEventListener('dragover', this.handleDelegatedEvent);
77
+ document.addEventListener('dragleave', this.handleDelegatedEvent);
78
+ document.addEventListener('drop', this.handleDelegatedEvent);
79
+ document.addEventListener('change', this.handleDelegatedEvent);
80
+ };
81
+
82
+ /**
83
+ * 處理委託事件
84
+ */
85
+ FileUploadManager.prototype.handleDelegatedEvent = function(event) {
86
+ const target = event.target;
87
+
88
+ // 處理檔案移除按鈕點擊
89
+ const removeBtn = target.closest('.image-remove-btn');
90
+ if (removeBtn) {
91
+ event.preventDefault();
92
+ event.stopPropagation();
93
+ this.handleRemoveFile(removeBtn);
94
+ return;
95
+ }
96
+
97
+ // 處理檔案輸入變更
98
+ if (target.type === 'file' && event.type === 'change') {
99
+ this.handleFileInputChange(target, event);
100
+ return;
101
+ }
102
+
103
+ // 處理上傳區域事件 - 只處理直接點擊上傳區域的情況
104
+ const uploadArea = target.closest('.image-upload-area');
105
+ if (uploadArea && event.type === 'click') {
106
+ // 確保不是點擊 input 元素本身
107
+ if (target.type === 'file') {
108
+ return;
109
+ }
110
+
111
+ // 確保不是點擊預覽圖片或移除按鈕
112
+ if (target.closest('.image-preview-item') || target.closest('.image-remove-btn')) {
113
+ return;
114
+ }
115
+
116
+ this.handleUploadAreaClick(uploadArea, event);
117
+ return;
118
+ }
119
+
120
+ // 處理拖放事件
121
+ if (uploadArea && (event.type === 'dragover' || event.type === 'dragleave' || event.type === 'drop')) {
122
+ switch (event.type) {
123
+ case 'dragover':
124
+ this.handleDragOver(uploadArea, event);
125
+ break;
126
+ case 'dragleave':
127
+ this.handleDragLeave(uploadArea, event);
128
+ break;
129
+ case 'drop':
130
+ this.handleDrop(uploadArea, event);
131
+ break;
132
+ }
133
+ }
134
+ };
135
+
136
+ /**
137
+ * 處理上傳區域點擊(使用防抖機制)
138
+ */
139
+ FileUploadManager.prototype.handleUploadAreaClick = function(uploadArea, event) {
140
+ event.preventDefault();
141
+ event.stopPropagation();
142
+
143
+ // 強力防抖機制 - 防止無限循環
144
+ const now = Date.now();
145
+ if (this.lastClickTime && (now - this.lastClickTime) < 500) {
146
+ console.log('🚫 防抖:忽略重複點擊,間隔:', now - this.lastClickTime, 'ms');
147
+ return;
148
+ }
149
+ this.lastClickTime = now;
150
+
151
+ // 如果已經有待處理的點擊,忽略新的點擊
152
+ if (this.isProcessingClick) {
153
+ console.log('🚫 正在處理點擊,忽略新的點擊');
154
+ return;
155
+ }
156
+
157
+ this.isProcessingClick = true;
158
+
159
+ const fileInput = uploadArea.querySelector('input[type="file"]');
160
+ if (fileInput) {
161
+ console.log('🖱️ 觸發檔案選擇:', fileInput.id);
162
+
163
+ // 重置 input 值以確保可以重複選擇同一檔案
164
+ fileInput.value = '';
165
+
166
+ // 使用 setTimeout 確保在下一個事件循環中執行,避免事件冒泡問題
167
+ const self = this;
168
+ setTimeout(function() {
169
+ try {
170
+ fileInput.click();
171
+ console.log('✅ 檔案選擇對話框已觸發');
172
+ } catch (error) {
173
+ console.error('❌ 檔案選擇對話框觸發失敗:', error);
174
+ } finally {
175
+ // 重置處理狀態
176
+ setTimeout(function() {
177
+ self.isProcessingClick = false;
178
+ }, 100);
179
+ }
180
+ }, 50);
181
+ } else {
182
+ this.isProcessingClick = false;
183
+ }
184
+ };
185
+
186
+ /**
187
+ * 處理檔案輸入變更
188
+ */
189
+ FileUploadManager.prototype.handleFileInputChange = function(fileInput, event) {
190
+ const files = event.target.files;
191
+ if (files && files.length > 0) {
192
+ console.log('📁 檔案選擇變更:', files.length, '個檔案');
193
+ this.processFiles(Array.from(files), fileInput);
194
+ }
195
+ };
196
+
197
+ /**
198
+ * 處理拖放事件
199
+ */
200
+ FileUploadManager.prototype.handleDragOver = function(uploadArea, event) {
201
+ event.preventDefault();
202
+ uploadArea.classList.add('dragover');
203
+ };
204
+
205
+ FileUploadManager.prototype.handleDragLeave = function(uploadArea, event) {
206
+ event.preventDefault();
207
+ // 只有當滑鼠真正離開上傳區域時才移除樣式
208
+ if (!uploadArea.contains(event.relatedTarget)) {
209
+ uploadArea.classList.remove('dragover');
210
+ }
211
+ };
212
+
213
+ FileUploadManager.prototype.handleDrop = function(uploadArea, event) {
214
+ event.preventDefault();
215
+ uploadArea.classList.remove('dragover');
216
+
217
+ const files = event.dataTransfer.files;
218
+ if (files && files.length > 0) {
219
+ console.log('📁 拖放檔案:', files.length, '個檔案');
220
+ this.processFiles(Array.from(files), uploadArea.querySelector('input[type="file"]'));
221
+ }
222
+ };
223
+
224
+ /**
225
+ * 處理檔案移除
226
+ */
227
+ FileUploadManager.prototype.handleRemoveFile = function(removeBtn) {
228
+ const index = parseInt(removeBtn.dataset.index);
229
+ if (!isNaN(index) && index >= 0 && index < this.files.length) {
230
+ const removedFile = this.files.splice(index, 1)[0];
231
+ console.log('🗑️ 移除檔案:', removedFile.name);
232
+
233
+ this.updateAllPreviews();
234
+
235
+ if (this.onFileRemove) {
236
+ this.onFileRemove(removedFile, index);
237
+ }
238
+ }
239
+ };
240
+
241
+ /**
242
+ * 設置全域剪貼板貼上處理
243
+ */
244
+ FileUploadManager.prototype.setupGlobalPasteHandler = function() {
245
+ document.removeEventListener('paste', this.handleGlobalPaste);
246
+ document.addEventListener('paste', this.handleGlobalPaste);
247
+ };
248
+
249
+ /**
250
+ * 處理全域剪貼板貼上
251
+ */
252
+ FileUploadManager.prototype.handleGlobalPaste = function(event) {
253
+ const items = event.clipboardData.items;
254
+ const imageFiles = [];
255
+
256
+ for (let i = 0; i < items.length; i++) {
257
+ const item = items[i];
258
+ if (item.type.indexOf('image') !== -1) {
259
+ const file = item.getAsFile();
260
+ if (file) {
261
+ imageFiles.push(file);
262
+ }
263
+ }
264
+ }
265
+
266
+ if (imageFiles.length > 0) {
267
+ event.preventDefault();
268
+ console.log('📋 剪貼板貼上圖片:', imageFiles.length, '個檔案');
269
+ this.processFiles(imageFiles);
270
+ }
271
+ };
272
+
273
+ /**
274
+ * 處理檔案
275
+ */
276
+ FileUploadManager.prototype.processFiles = function(files, sourceInput) {
277
+ const validFiles = [];
278
+
279
+ for (let i = 0; i < files.length; i++) {
280
+ const file = files[i];
281
+
282
+ // 檢查檔案類型
283
+ if (!file.type.startsWith('image/')) {
284
+ console.warn('⚠️ 跳過非圖片檔案:', file.name);
285
+ continue;
286
+ }
287
+
288
+ // 檢查檔案大小
289
+ if (this.maxFileSize > 0 && file.size > this.maxFileSize) {
290
+ const sizeLimit = this.formatFileSize(this.maxFileSize);
291
+ console.warn('⚠️ 檔案過大:', file.name, '超過限制', sizeLimit);
292
+ const message = window.i18nManager ?
293
+ window.i18nManager.t('fileUpload.fileSizeExceeded', {
294
+ limit: sizeLimit,
295
+ filename: file.name
296
+ }) :
297
+ '圖片大小超過限制 (' + sizeLimit + '): ' + file.name;
298
+ this.showMessage(message, 'warning');
299
+ continue;
300
+ }
301
+
302
+ // 檢查檔案數量限制
303
+ if (this.files.length + validFiles.length >= this.maxFiles) {
304
+ console.warn('⚠️ 檔案數量超過限制:', this.maxFiles);
305
+ const message = window.i18nManager ?
306
+ window.i18nManager.t('fileUpload.maxFilesExceeded', { maxFiles: this.maxFiles }) :
307
+ '最多只能上傳 ' + this.maxFiles + ' 個檔案';
308
+ this.showMessage(message, 'warning');
309
+ break;
310
+ }
311
+
312
+ validFiles.push(file);
313
+ }
314
+
315
+ // 處理有效檔案
316
+ if (validFiles.length > 0) {
317
+ this.addFiles(validFiles);
318
+ }
319
+ };
320
+
321
+ /**
322
+ * 添加檔案到列表
323
+ */
324
+ FileUploadManager.prototype.addFiles = function(files) {
325
+ const promises = files.map(file => this.fileToBase64(file));
326
+
327
+ const self = this;
328
+ Promise.all(promises)
329
+ .then(function(base64Results) {
330
+ base64Results.forEach(function(base64, index) {
331
+ const file = files[index];
332
+ const fileData = {
333
+ name: file.name,
334
+ size: file.size,
335
+ type: file.type,
336
+ data: base64,
337
+ timestamp: Date.now()
338
+ };
339
+
340
+ self.files.push(fileData);
341
+ console.log('✅ 檔案已添加:', file.name);
342
+
343
+ if (self.onFileAdd) {
344
+ self.onFileAdd(fileData);
345
+ }
346
+ });
347
+
348
+ self.updateAllPreviews();
349
+ })
350
+ .catch(function(error) {
351
+ console.error('❌ 檔案處理失敗:', error);
352
+ const message = window.i18nManager ?
353
+ window.i18nManager.t('fileUpload.processingFailed', '檔案處理失敗,請重試') :
354
+ '檔案處理失敗,請重試';
355
+ self.showMessage(message, 'error');
356
+ });
357
+ };
358
+
359
+ /**
360
+ * 將檔案轉換為 Base64
361
+ */
362
+ FileUploadManager.prototype.fileToBase64 = function(file) {
363
+ return new Promise(function(resolve, reject) {
364
+ const reader = new FileReader();
365
+ reader.onload = function() {
366
+ resolve(reader.result.split(',')[1]);
367
+ };
368
+ reader.onerror = reject;
369
+ reader.readAsDataURL(file);
370
+ });
371
+ };
372
+
373
+ /**
374
+ * 更新所有預覽容器
375
+ */
376
+ FileUploadManager.prototype.updateAllPreviews = function() {
377
+ const previewContainers = document.querySelectorAll('.image-preview-container');
378
+ const self = this;
379
+
380
+ previewContainers.forEach(function(container) {
381
+ self.updatePreviewContainer(container);
382
+ });
383
+
384
+ this.updateFileCount();
385
+ console.log('🖼️ 已更新', previewContainers.length, '個預覽容器');
386
+ };
387
+
388
+ /**
389
+ * 更新單個預覽容器
390
+ */
391
+ FileUploadManager.prototype.updatePreviewContainer = function(container) {
392
+ container.innerHTML = '';
393
+
394
+ const self = this;
395
+ this.files.forEach(function(file, index) {
396
+ const previewElement = self.createPreviewElement(file, index);
397
+ container.appendChild(previewElement);
398
+ });
399
+ };
400
+
401
+ /**
402
+ * 創建預覽元素
403
+ */
404
+ FileUploadManager.prototype.createPreviewElement = function(file, index) {
405
+ const preview = document.createElement('div');
406
+ preview.className = 'image-preview-item';
407
+
408
+ // 圖片元素
409
+ const img = document.createElement('img');
410
+ img.src = 'data:' + file.type + ';base64,' + file.data;
411
+ img.alt = file.name;
412
+ img.title = file.name + ' (' + this.formatFileSize(file.size) + ')';
413
+
414
+ // 檔案資訊
415
+ const info = document.createElement('div');
416
+ info.className = 'image-info';
417
+
418
+ const name = document.createElement('div');
419
+ name.className = 'image-name';
420
+ name.textContent = file.name;
421
+
422
+ const size = document.createElement('div');
423
+ size.className = 'image-size';
424
+ size.textContent = this.formatFileSize(file.size);
425
+
426
+ // 移除按鈕
427
+ const removeBtn = document.createElement('button');
428
+ removeBtn.className = 'image-remove-btn';
429
+ removeBtn.textContent = '×';
430
+ removeBtn.title = '移除圖片';
431
+ removeBtn.dataset.index = index;
432
+ removeBtn.setAttribute('aria-label', '移除圖片 ' + file.name);
433
+
434
+ // 組裝元素
435
+ info.appendChild(name);
436
+ info.appendChild(size);
437
+ preview.appendChild(img);
438
+ preview.appendChild(info);
439
+ preview.appendChild(removeBtn);
440
+
441
+ return preview;
442
+ };
443
+
444
+ /**
445
+ * 更新檔案計數顯示
446
+ */
447
+ FileUploadManager.prototype.updateFileCount = function() {
448
+ const count = this.files.length;
449
+ const countElements = document.querySelectorAll('.image-count');
450
+
451
+ countElements.forEach(function(element) {
452
+ element.textContent = count > 0 ? '(' + count + ')' : '';
453
+ });
454
+
455
+ // 更新上傳區域狀態
456
+ const uploadAreas = document.querySelectorAll('.image-upload-area');
457
+ uploadAreas.forEach(function(area) {
458
+ if (count > 0) {
459
+ area.classList.add('has-images');
460
+ } else {
461
+ area.classList.remove('has-images');
462
+ }
463
+ });
464
+ };
465
+
466
+ /**
467
+ * 格式化檔案大小
468
+ */
469
+ FileUploadManager.prototype.formatFileSize = function(bytes) {
470
+ if (bytes === 0) return '0 Bytes';
471
+
472
+ const k = 1024;
473
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
474
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
475
+
476
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
477
+ };
478
+
479
+ /**
480
+ * 顯示訊息
481
+ */
482
+ FileUploadManager.prototype.showMessage = function(message, type) {
483
+ // 使用現有的 Utils.showMessage 如果可用
484
+ if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
485
+ const messageType = type === 'warning' ? window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING :
486
+ type === 'error' ? window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR :
487
+ window.MCPFeedback.Utils.CONSTANTS.MESSAGE_INFO;
488
+ window.MCPFeedback.Utils.showMessage(message, messageType);
489
+ } else {
490
+ // 後備方案
491
+ console.log('[' + type.toUpperCase() + ']', message);
492
+ alert(message);
493
+ }
494
+ };
495
+
496
+ /**
497
+ * 更新設定
498
+ */
499
+ FileUploadManager.prototype.updateSettings = function(settings) {
500
+ this.maxFileSize = settings.imageSizeLimit || 0;
501
+ this.enableBase64Detail = settings.enableBase64Detail || false;
502
+
503
+ console.log('⚙️ FileUploadManager 設定已更新:', {
504
+ maxFileSize: this.maxFileSize,
505
+ enableBase64Detail: this.enableBase64Detail
506
+ });
507
+ };
508
+
509
+ /**
510
+ * 獲取檔案列表
511
+ */
512
+ FileUploadManager.prototype.getFiles = function() {
513
+ return this.files.slice(); // 返回副本
514
+ };
515
+
516
+ /**
517
+ * 清空所有檔案
518
+ */
519
+ FileUploadManager.prototype.clearFiles = function() {
520
+ this.files = [];
521
+ this.updateAllPreviews();
522
+ console.log('🗑️ 已清空所有檔案');
523
+ };
524
+
525
+ /**
526
+ * 清理資源
527
+ */
528
+ FileUploadManager.prototype.cleanup = function() {
529
+ // 移除事件監聽器
530
+ document.removeEventListener('click', this.handleDelegatedEvent);
531
+ document.removeEventListener('dragover', this.handleDelegatedEvent);
532
+ document.removeEventListener('dragleave', this.handleDelegatedEvent);
533
+ document.removeEventListener('drop', this.handleDelegatedEvent);
534
+ document.removeEventListener('change', this.handleDelegatedEvent);
535
+ document.removeEventListener('paste', this.handleGlobalPaste);
536
+
537
+ // 清理防抖計時器
538
+ if (this.debounceTimeout) {
539
+ clearTimeout(this.debounceTimeout);
540
+ this.debounceTimeout = null;
541
+ }
542
+
543
+ // 清空檔案
544
+ this.clearFiles();
545
+
546
+ this.isInitialized = false;
547
+ console.log('🧹 FileUploadManager 資源已清理');
548
+ };
549
+
550
+ // 將 FileUploadManager 加入命名空間
551
+ window.MCPFeedback.FileUploadManager = FileUploadManager;
552
+
553
+ console.log('✅ FileUploadManager 模組載入完成');
554
+
555
+ })();