From f148841cc02a268b0da46168ca18b3775bfd1f2e Mon Sep 17 00:00:00 2001 From: Minidoracat Date: Mon, 2 Jun 2025 12:26:07 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=96=B0=E5=A2=9E=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E6=96=87=E5=AD=97=E8=BC=B8=E5=85=A5=E6=A1=86=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8F=B4=E5=BE=9E=E5=89=AA=E8=B2=BC=E7=B0=BF=E8=B2=BC?= =?UTF-8?q?=E4=B8=8A=E5=9C=96=E7=89=87=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=9B=B8=E9=97=9C=E6=8F=90=E7=A4=BA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E3=80=82=E5=84=AA=E5=8C=96=E7=94=A8=E6=88=B6=E5=9B=9E?= =?UTF-8?q?=E9=A5=8B=E6=B5=81=E7=A8=8B=EF=BC=8C=E6=94=B9=E5=96=84=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E4=BA=A4=E4=BA=92=E9=AB=94=E9=A9=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mcp_feedback_enhanced/feedback_ui.py | 44 ++++++++++++- .../locales/en/translations.json | 11 +++- .../locales/zh-CN/translations.json | 11 +++- .../locales/zh-TW/translations.json | 11 +++- src/mcp_feedback_enhanced/static/i18n.js | 20 ++++-- .../templates/feedback.html | 61 ++++++++++++++++++- src/mcp_feedback_enhanced/test_qt_gui.py | 2 +- src/mcp_feedback_enhanced/test_web_ui.py | 3 + 8 files changed, 144 insertions(+), 19 deletions(-) diff --git a/src/mcp_feedback_enhanced/feedback_ui.py b/src/mcp_feedback_enhanced/feedback_ui.py index 795d1e4..c0d885f 100644 --- a/src/mcp_feedback_enhanced/feedback_ui.py +++ b/src/mcp_feedback_enhanced/feedback_ui.py @@ -45,6 +45,33 @@ class FeedbackResult(TypedDict): images: List[dict] +# ===== 自定義文字輸入框 ===== +class SmartTextEdit(QTextEdit): + """支援智能 Ctrl+V 的文字輸入框""" + image_paste_requested = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + + def keyPressEvent(self, event): + """處理按鍵事件,實現智能 Ctrl+V""" + if event.key() == Qt.Key_V and event.modifiers() == Qt.ControlModifier: + # 檢查剪貼簿是否有圖片 + clipboard = QApplication.clipboard() + + if clipboard.mimeData().hasImage(): + # 如果有圖片,發送信號通知主窗口處理圖片貼上 + self.image_paste_requested.emit() + # 不執行預設的文字貼上行為 + return + else: + # 如果沒有圖片,執行正常的文字貼上 + super().keyPressEvent(event) + else: + # 其他按鍵正常處理 + super().keyPressEvent(event) + + # ===== 圖片預覽元件 ===== class ImagePreviewWidget(QLabel): """圖片預覽元件""" @@ -832,9 +859,11 @@ class FeedbackWindow(QMainWindow): feedback_layout.addWidget(self.feedback_description) # 文字輸入框 - self.feedback_input = QTextEdit() + self.feedback_input = SmartTextEdit() self.feedback_input.setPlaceholderText(t('feedback_placeholder')) self.feedback_input.setMinimumHeight(120) + # 連接智能貼上信號 + self.feedback_input.image_paste_requested.connect(self._handle_image_paste_from_textarea) feedback_layout.addWidget(self.feedback_input) layout.addWidget(feedback_group, stretch=2) # 給更多空間 @@ -990,6 +1019,19 @@ class FeedbackWindow(QMainWindow): } """) + def _handle_image_paste_from_textarea(self) -> None: + """處理從文字框智能貼上圖片的功能""" + try: + # 調用圖片上傳組件的剪貼簿貼上功能 + self.image_upload.paste_from_clipboard() + + # 顯示智能貼上提示 + # 可以在這裡添加狀態提示,比如狀態欄或臨時通知 + debug_log("智能貼上:已將圖片從文字框貼到圖片區域") + + except Exception as e: + debug_log(f"智能貼上失敗: {e}") + def _run_command(self) -> None: """執行命令""" command = self.command_input.text().strip() diff --git a/src/mcp_feedback_enhanced/locales/en/translations.json b/src/mcp_feedback_enhanced/locales/en/translations.json index a6f2a29..357bf3b 100644 --- a/src/mcp_feedback_enhanced/locales/en/translations.json +++ b/src/mcp_feedback_enhanced/locales/en/translations.json @@ -20,7 +20,7 @@ "feedback": { "title": "Your Feedback", "description": "Please describe your thoughts, suggestions, or changes needed for the AI work results.", - "placeholder": "Please enter your feedback, suggestions, or questions here...\n\n💡 Tip: Press Ctrl+Enter to submit quickly" + "placeholder": "Please enter your feedback, suggestions, or questions here...\n\n💡 Tips:\n• Press Ctrl+Enter to submit quickly\n• Press Ctrl+V to paste images from clipboard" }, "command": { "title": "Command Execution", @@ -35,11 +35,16 @@ "clear": "Clear", "status": "{count} images selected", "statusWithSize": "{count} images selected (Total {size})", - "dragHint": "🎯 Drag images here (PNG, JPG, JPEG, GIF, BMP, WebP)", + "dragHint": "🎯 Drag images here or press Ctrl+V to paste from clipboard (PNG, JPG, JPEG, GIF, BMP, WebP)", "deleteConfirm": "Are you sure you want to remove image \"{filename}\"?", "deleteTitle": "Confirm Delete", "sizeWarning": "Image file size cannot exceed 1MB", - "formatError": "Unsupported image format" + "formatError": "Unsupported image format", + "paste_images": "📋 Paste from Clipboard", + "paste_failed": "Paste failed, no image in clipboard", + "paste_no_image": "No image in clipboard to paste", + "paste_image_from_textarea": "Image intelligently pasted from text area to image area", + "images_clear": "Clear all images" }, "buttons": { "submit": "Submit Feedback", diff --git a/src/mcp_feedback_enhanced/locales/zh-CN/translations.json b/src/mcp_feedback_enhanced/locales/zh-CN/translations.json index fd5a4da..41d4934 100644 --- a/src/mcp_feedback_enhanced/locales/zh-CN/translations.json +++ b/src/mcp_feedback_enhanced/locales/zh-CN/translations.json @@ -20,7 +20,7 @@ "feedback": { "title": "您的反馈", "description": "请描述您对 AI 工作结果的想法、建议或需要修改的地方。", - "placeholder": "请在这里输入您的反馈、建议或问题...\n\n💡 小提示:按 Ctrl+Enter 可快速提交反馈" + "placeholder": "请在这里输入您的反馈、建议或问题...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交反馈\n• 按 Ctrl+V 可直接贴上剪贴板图片" }, "command": { "title": "命令执行", @@ -35,11 +35,16 @@ "clear": "清除", "status": "已选择 {count} 张图片", "statusWithSize": "已选择 {count} 张图片 (总计 {size})", - "dragHint": "🎯 拖拽图片到这里 (PNG、JPG、JPEG、GIF、BMP、WebP)", + "dragHint": "🎯 拖拽图片到这里 或 按 Ctrl+V 贴上剪贴板图片 (PNG、JPG、JPEG、GIF、BMP、WebP)", "deleteConfirm": "确定要移除图片 \"{filename}\" 吗?", "deleteTitle": "确认删除", "sizeWarning": "图片文件大小不能超过 1MB", - "formatError": "不支持的图片格式" + "formatError": "不支持的图片格式", + "paste_images": "📋 从剪贴板粘贴", + "paste_failed": "粘贴失败,剪贴板中没有图片", + "paste_no_image": "剪贴板中没有图片可粘贴", + "paste_image_from_textarea": "已将图片从文本框智能贴到图片区域", + "images_clear": "清除所有图片" }, "buttons": { "submit": "提交反馈", diff --git a/src/mcp_feedback_enhanced/locales/zh-TW/translations.json b/src/mcp_feedback_enhanced/locales/zh-TW/translations.json index 7c35cf2..ba69ad0 100644 --- a/src/mcp_feedback_enhanced/locales/zh-TW/translations.json +++ b/src/mcp_feedback_enhanced/locales/zh-TW/translations.json @@ -20,7 +20,7 @@ "feedback": { "title": "您的回饋", "description": "請描述您對 AI 工作結果的想法、建議或需要修改的地方。", - "placeholder": "請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋" + "placeholder": "請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交回饋\n• 按 Ctrl+V 可直接貼上剪貼簿圖片" }, "command": { "title": "命令執行", @@ -35,11 +35,16 @@ "clear": "清除", "status": "已選擇 {count} 張圖片", "statusWithSize": "已選擇 {count} 張圖片 (總計 {size})", - "dragHint": "🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)", + "dragHint": "🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)", "deleteConfirm": "確定要移除圖片 \"{filename}\" 嗎?", "deleteTitle": "確認刪除", "sizeWarning": "圖片文件大小不能超過 1MB", - "formatError": "不支援的圖片格式" + "formatError": "不支援的圖片格式", + "paste_images": "📋 從剪貼簿貼上", + "paste_failed": "貼上失敗,剪貼簿中沒有圖片", + "paste_no_image": "剪貼簿中沒有圖片可貼上", + "paste_image_from_textarea": "已將圖片從文字框智能貼到圖片區域", + "images_clear": "清除所有圖片" }, "buttons": { "submit": "提交回饋", diff --git a/src/mcp_feedback_enhanced/static/i18n.js b/src/mcp_feedback_enhanced/static/i18n.js index cfb866a..a913792 100644 --- a/src/mcp_feedback_enhanced/static/i18n.js +++ b/src/mcp_feedback_enhanced/static/i18n.js @@ -43,7 +43,7 @@ class I18nManager { feedback: { title: '💬 您的回饋', description: '請在這裡輸入您的回饋、建議或問題。您的意見將幫助 AI 更好地理解您的需求。', - placeholder: '請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋' + placeholder: '請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交回饋\n• 按 Ctrl+V 可直接貼上剪貼簿圖片' }, command: { title: '⚡ 命令執行', @@ -55,7 +55,7 @@ class I18nManager { title: '🖼️ 圖片附件(可選)', status: '已選擇 {count} 張圖片', statusWithSize: '已選擇 {count} 張圖片 (總計 {size})', - dragHint: '🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)', + dragHint: '🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)', deleteConfirm: '確定要移除圖片 "{filename}" 嗎?', deleteTitle: '確認刪除' }, @@ -75,6 +75,8 @@ class I18nManager { commandFinished: '命令執行完成', pasteSuccess: '已從剪貼板貼上圖片', pasteFailed: '無法從剪貼板獲取圖片', + paste_no_image: '剪貼簿中沒有圖片可貼上', + paste_image_from_textarea: '已將圖片從文字框智能貼到圖片區域', invalidFileType: '不支援的文件類型', fileTooLarge: '文件過大(最大 1MB)' }, @@ -100,7 +102,7 @@ class I18nManager { feedback: { title: '💬 Your Feedback', description: 'Please enter your feedback, suggestions, or questions here. Your input helps AI better understand your needs.', - placeholder: 'Please enter your feedback, suggestions, or questions here...\n\n💡 Tip: Press Ctrl+Enter to submit quickly' + placeholder: 'Please enter your feedback, suggestions, or questions here...\n\n💡 Tips:\n• Press Ctrl+Enter to submit quickly\n• Press Ctrl+V to paste images from clipboard' }, command: { title: '⚡ Command Execution', @@ -112,7 +114,7 @@ class I18nManager { title: '🖼️ Image Attachments (Optional)', status: '{count} images selected', statusWithSize: '{count} images selected (Total {size})', - dragHint: '🎯 Drag images here (PNG, JPG, JPEG, GIF, BMP, WebP)', + dragHint: '🎯 Drag images here or press Ctrl+V to paste from clipboard (PNG, JPG, JPEG, GIF, BMP, WebP)', deleteConfirm: 'Are you sure you want to remove image "{filename}"?', deleteTitle: 'Confirm Delete' }, @@ -132,6 +134,8 @@ class I18nManager { commandFinished: 'Command finished', pasteSuccess: 'Image pasted from clipboard', pasteFailed: 'Failed to get image from clipboard', + paste_no_image: 'No image to paste from clipboard', + paste_image_from_textarea: 'Image pasted from text area to image area', invalidFileType: 'Unsupported file type', fileTooLarge: 'File too large (max 1MB)' }, @@ -157,7 +161,7 @@ class I18nManager { feedback: { title: '💬 您的反馈', description: '请在这里输入您的反馈、建议或问题。您的意见将帮助 AI 更好地理解您的需求。', - placeholder: '请在这里输入您的反馈、建议或问题...\n\n💡 小提示:按 Ctrl+Enter 可快速提交反馈' + placeholder: '请在这里输入您的反馈、建议或问题...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交反馈\n• 按 Ctrl+V 可直接贴上剪贴板图片' }, command: { title: '⚡ 命令执行', @@ -169,7 +173,7 @@ class I18nManager { title: '🖼️ 图片附件(可选)', status: '已选择 {count} 张图片', statusWithSize: '已选择 {count} 张图片 (总计 {size})', - dragHint: '🎯 拖拽图片到这里 (PNG、JPG、JPEG、GIF、BMP、WebP)', + dragHint: '🎯 拖拽图片到这里 或 按 Ctrl+V 贴上剪贴板图片 (PNG、JPG、JPEG、GIF、BMP、WebP)', deleteConfirm: '确定要移除图片 "{filename}" 吗?', deleteTitle: '确认删除' }, @@ -189,6 +193,8 @@ class I18nManager { commandFinished: '命令执行完成', pasteSuccess: '已从剪贴板粘贴图片', pasteFailed: '无法从剪贴板获取图片', + paste_no_image: '剪贴板中没有图片可粘贴', + paste_image_from_textarea: '已将图片从文字框智能贴到图片区域', invalidFileType: '不支持的文件类型', fileTooLarge: '文件过大(最大 1MB)' }, @@ -361,6 +367,8 @@ class I18nManager { 'command_finished': 'status.commandFinished', 'paste_success': 'status.pasteSuccess', 'paste_failed': 'status.pasteFailed', + 'paste_no_image': 'status.paste_no_image', + 'paste_image_from_textarea': 'status.paste_image_from_textarea', 'invalid_file_type': 'status.invalidFileType', 'file_too_large': 'status.fileTooLarge' }; diff --git a/src/mcp_feedback_enhanced/templates/feedback.html b/src/mcp_feedback_enhanced/templates/feedback.html index 4826793..0434dc9 100644 --- a/src/mcp_feedback_enhanced/templates/feedback.html +++ b/src/mcp_feedback_enhanced/templates/feedback.html @@ -656,7 +656,7 @@ @@ -669,7 +669,7 @@
- 🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP) + 🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)
已選擇 0 張圖片
@@ -1007,9 +1007,66 @@ e.preventDefault(); submitFeedback(); } + + // Ctrl+V 智能貼上 + if (e.ctrlKey && e.key === 'v') { + // 先檢查剪貼簿是否包含圖片 + checkClipboardForImage().then(hasImage => { + if (hasImage) { + // 如果有圖片,無論焦點在哪裡都優先貼到圖片區域 + e.preventDefault(); + pasteFromClipboard(); + + // 提供額外的使用者提示 + const activeElement = document.activeElement; + const isInTextArea = activeElement && activeElement.id === 'feedbackText'; + if (isInTextArea) { + showStatusMessage(t('paste_image_from_textarea'), 'success'); + } + } else { + // 如果沒有圖片,檢查是否在文字輸入區域 + const activeElement = document.activeElement; + const isTextInput = activeElement && ( + activeElement.tagName === 'TEXTAREA' || + activeElement.tagName === 'INPUT' || + activeElement.contentEditable === 'true' + ); + + if (isTextInput) { + // 在文字輸入區域且剪貼簿只有文字,允許正常的文字貼上 + // 不需要 preventDefault(),讓瀏覽器執行預設行為 + } else { + // 不在文字輸入區域且沒有圖片時,提示用戶 + e.preventDefault(); + showStatusMessage(t('paste_no_image'), 'info'); + } + } + }).catch(err => { + // 如果檢查剪貼簿失敗,允許正常的文字貼上行為 + console.warn('檢查剪貼簿失敗:', err); + }); + } }); } + // 檢查剪貼簿是否包含圖片 + async function checkClipboardForImage() { + try { + const items = await navigator.clipboard.read(); + for (const item of items) { + for (const type of item.types) { + if (type.startsWith('image/')) { + return true; + } + } + } + return false; + } catch (err) { + // 如果無法讀取剪貼簿,返回 false + return false; + } + } + // 分頁切換功能 function switchTab(tabName) { // 隱藏所有分頁內容 diff --git a/src/mcp_feedback_enhanced/test_qt_gui.py b/src/mcp_feedback_enhanced/test_qt_gui.py index ff18f0d..04f8cde 100644 --- a/src/mcp_feedback_enhanced/test_qt_gui.py +++ b/src/mcp_feedback_enhanced/test_qt_gui.py @@ -69,7 +69,7 @@ def test_qt_gui(): debug_log(" - X刪除按鈕") debug_log(" - 視窗大小調整") debug_log(" - 分割器調整") - debug_log() + debug_log("") # 啟動 GUI result = feedback_ui(project_directory, prompt) diff --git a/src/mcp_feedback_enhanced/test_web_ui.py b/src/mcp_feedback_enhanced/test_web_ui.py index a425242..ff7718c 100644 --- a/src/mcp_feedback_enhanced/test_web_ui.py +++ b/src/mcp_feedback_enhanced/test_web_ui.py @@ -25,6 +25,9 @@ import webbrowser import time import sys import os +import socket +import threading +from pathlib import Path from typing import Dict, Any, Optional # 添加專案根目錄到 Python 路徑