新增智能文字輸入框,支援從剪貼簿貼上圖片功能,並更新相關提示信息。優化用戶回饋流程,改善界面交互體驗。

This commit is contained in:
Minidoracat 2025-06-02 12:26:07 +08:00
parent 9ea5b8b5b7
commit f148841cc0
8 changed files with 144 additions and 19 deletions

View File

@ -45,6 +45,33 @@ class FeedbackResult(TypedDict):
images: List[dict] 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): class ImagePreviewWidget(QLabel):
"""圖片預覽元件""" """圖片預覽元件"""
@ -832,9 +859,11 @@ class FeedbackWindow(QMainWindow):
feedback_layout.addWidget(self.feedback_description) feedback_layout.addWidget(self.feedback_description)
# 文字輸入框 # 文字輸入框
self.feedback_input = QTextEdit() self.feedback_input = SmartTextEdit()
self.feedback_input.setPlaceholderText(t('feedback_placeholder')) self.feedback_input.setPlaceholderText(t('feedback_placeholder'))
self.feedback_input.setMinimumHeight(120) self.feedback_input.setMinimumHeight(120)
# 連接智能貼上信號
self.feedback_input.image_paste_requested.connect(self._handle_image_paste_from_textarea)
feedback_layout.addWidget(self.feedback_input) feedback_layout.addWidget(self.feedback_input)
layout.addWidget(feedback_group, stretch=2) # 給更多空間 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: def _run_command(self) -> None:
"""執行命令""" """執行命令"""
command = self.command_input.text().strip() command = self.command_input.text().strip()

View File

@ -20,7 +20,7 @@
"feedback": { "feedback": {
"title": "Your Feedback", "title": "Your Feedback",
"description": "Please describe your thoughts, suggestions, or changes needed for the AI work results.", "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": { "command": {
"title": "Command Execution", "title": "Command Execution",
@ -35,11 +35,16 @@
"clear": "Clear", "clear": "Clear",
"status": "{count} images selected", "status": "{count} images selected",
"statusWithSize": "{count} images selected (Total {size})", "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}\"?", "deleteConfirm": "Are you sure you want to remove image \"{filename}\"?",
"deleteTitle": "Confirm Delete", "deleteTitle": "Confirm Delete",
"sizeWarning": "Image file size cannot exceed 1MB", "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": { "buttons": {
"submit": "Submit Feedback", "submit": "Submit Feedback",

View File

@ -20,7 +20,7 @@
"feedback": { "feedback": {
"title": "您的反馈", "title": "您的反馈",
"description": "请描述您对 AI 工作结果的想法、建议或需要修改的地方。", "description": "请描述您对 AI 工作结果的想法、建议或需要修改的地方。",
"placeholder": "请在这里输入您的反馈、建议或问题...\n\n💡 小提示:按 Ctrl+Enter 可快速提交反馈" "placeholder": "请在这里输入您的反馈、建议或问题...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交反馈\n• 按 Ctrl+V 可直接贴上剪贴板图片"
}, },
"command": { "command": {
"title": "命令执行", "title": "命令执行",
@ -35,11 +35,16 @@
"clear": "清除", "clear": "清除",
"status": "已选择 {count} 张图片", "status": "已选择 {count} 张图片",
"statusWithSize": "已选择 {count} 张图片 (总计 {size})", "statusWithSize": "已选择 {count} 张图片 (总计 {size})",
"dragHint": "🎯 拖拽图片到这里 (PNG、JPG、JPEG、GIF、BMP、WebP)", "dragHint": "🎯 拖拽图片到这里 或 按 Ctrl+V 贴上剪贴板图片 (PNG、JPG、JPEG、GIF、BMP、WebP)",
"deleteConfirm": "确定要移除图片 \"{filename}\" 吗?", "deleteConfirm": "确定要移除图片 \"{filename}\" 吗?",
"deleteTitle": "确认删除", "deleteTitle": "确认删除",
"sizeWarning": "图片文件大小不能超过 1MB", "sizeWarning": "图片文件大小不能超过 1MB",
"formatError": "不支持的图片格式" "formatError": "不支持的图片格式",
"paste_images": "📋 从剪贴板粘贴",
"paste_failed": "粘贴失败,剪贴板中没有图片",
"paste_no_image": "剪贴板中没有图片可粘贴",
"paste_image_from_textarea": "已将图片从文本框智能贴到图片区域",
"images_clear": "清除所有图片"
}, },
"buttons": { "buttons": {
"submit": "提交反馈", "submit": "提交反馈",

View File

@ -20,7 +20,7 @@
"feedback": { "feedback": {
"title": "您的回饋", "title": "您的回饋",
"description": "請描述您對 AI 工作結果的想法、建議或需要修改的地方。", "description": "請描述您對 AI 工作結果的想法、建議或需要修改的地方。",
"placeholder": "請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋" "placeholder": "請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交回饋\n• 按 Ctrl+V 可直接貼上剪貼簿圖片"
}, },
"command": { "command": {
"title": "命令執行", "title": "命令執行",
@ -35,11 +35,16 @@
"clear": "清除", "clear": "清除",
"status": "已選擇 {count} 張圖片", "status": "已選擇 {count} 張圖片",
"statusWithSize": "已選擇 {count} 張圖片 (總計 {size})", "statusWithSize": "已選擇 {count} 張圖片 (總計 {size})",
"dragHint": "🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)", "dragHint": "🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)",
"deleteConfirm": "確定要移除圖片 \"{filename}\" 嗎?", "deleteConfirm": "確定要移除圖片 \"{filename}\" 嗎?",
"deleteTitle": "確認刪除", "deleteTitle": "確認刪除",
"sizeWarning": "圖片文件大小不能超過 1MB", "sizeWarning": "圖片文件大小不能超過 1MB",
"formatError": "不支援的圖片格式" "formatError": "不支援的圖片格式",
"paste_images": "📋 從剪貼簿貼上",
"paste_failed": "貼上失敗,剪貼簿中沒有圖片",
"paste_no_image": "剪貼簿中沒有圖片可貼上",
"paste_image_from_textarea": "已將圖片從文字框智能貼到圖片區域",
"images_clear": "清除所有圖片"
}, },
"buttons": { "buttons": {
"submit": "提交回饋", "submit": "提交回饋",

View File

@ -43,7 +43,7 @@ class I18nManager {
feedback: { feedback: {
title: '💬 您的回饋', title: '💬 您的回饋',
description: '請在這裡輸入您的回饋、建議或問題。您的意見將幫助 AI 更好地理解您的需求。', description: '請在這裡輸入您的回饋、建議或問題。您的意見將幫助 AI 更好地理解您的需求。',
placeholder: '請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋' placeholder: '請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交回饋\n• 按 Ctrl+V 可直接貼上剪貼簿圖片'
}, },
command: { command: {
title: '⚡ 命令執行', title: '⚡ 命令執行',
@ -55,7 +55,7 @@ class I18nManager {
title: '🖼️ 圖片附件(可選)', title: '🖼️ 圖片附件(可選)',
status: '已選擇 {count} 張圖片', status: '已選擇 {count} 張圖片',
statusWithSize: '已選擇 {count} 張圖片 (總計 {size})', statusWithSize: '已選擇 {count} 張圖片 (總計 {size})',
dragHint: '🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)', dragHint: '🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)',
deleteConfirm: '確定要移除圖片 "{filename}" 嗎?', deleteConfirm: '確定要移除圖片 "{filename}" 嗎?',
deleteTitle: '確認刪除' deleteTitle: '確認刪除'
}, },
@ -75,6 +75,8 @@ class I18nManager {
commandFinished: '命令執行完成', commandFinished: '命令執行完成',
pasteSuccess: '已從剪貼板貼上圖片', pasteSuccess: '已從剪貼板貼上圖片',
pasteFailed: '無法從剪貼板獲取圖片', pasteFailed: '無法從剪貼板獲取圖片',
paste_no_image: '剪貼簿中沒有圖片可貼上',
paste_image_from_textarea: '已將圖片從文字框智能貼到圖片區域',
invalidFileType: '不支援的文件類型', invalidFileType: '不支援的文件類型',
fileTooLarge: '文件過大(最大 1MB' fileTooLarge: '文件過大(最大 1MB'
}, },
@ -100,7 +102,7 @@ class I18nManager {
feedback: { feedback: {
title: '💬 Your Feedback', title: '💬 Your Feedback',
description: 'Please enter your feedback, suggestions, or questions here. Your input helps AI better understand your needs.', 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: { command: {
title: '⚡ Command Execution', title: '⚡ Command Execution',
@ -112,7 +114,7 @@ class I18nManager {
title: '🖼️ Image Attachments (Optional)', title: '🖼️ Image Attachments (Optional)',
status: '{count} images selected', status: '{count} images selected',
statusWithSize: '{count} images selected (Total {size})', 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}"?', deleteConfirm: 'Are you sure you want to remove image "{filename}"?',
deleteTitle: 'Confirm Delete' deleteTitle: 'Confirm Delete'
}, },
@ -132,6 +134,8 @@ class I18nManager {
commandFinished: 'Command finished', commandFinished: 'Command finished',
pasteSuccess: 'Image pasted from clipboard', pasteSuccess: 'Image pasted from clipboard',
pasteFailed: 'Failed to get image 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', invalidFileType: 'Unsupported file type',
fileTooLarge: 'File too large (max 1MB)' fileTooLarge: 'File too large (max 1MB)'
}, },
@ -157,7 +161,7 @@ class I18nManager {
feedback: { feedback: {
title: '💬 您的反馈', title: '💬 您的反馈',
description: '请在这里输入您的反馈、建议或问题。您的意见将帮助 AI 更好地理解您的需求。', description: '请在这里输入您的反馈、建议或问题。您的意见将帮助 AI 更好地理解您的需求。',
placeholder: '请在这里输入您的反馈、建议或问题...\n\n💡 小提示:按 Ctrl+Enter 可快速提交反馈' placeholder: '请在这里输入您的反馈、建议或问题...\n\n💡 小提示:\n• 按 Ctrl+Enter 可快速提交反馈\n• 按 Ctrl+V 可直接贴上剪贴板图片'
}, },
command: { command: {
title: '⚡ 命令执行', title: '⚡ 命令执行',
@ -169,7 +173,7 @@ class I18nManager {
title: '🖼️ 图片附件(可选)', title: '🖼️ 图片附件(可选)',
status: '已选择 {count} 张图片', status: '已选择 {count} 张图片',
statusWithSize: '已选择 {count} 张图片 (总计 {size})', statusWithSize: '已选择 {count} 张图片 (总计 {size})',
dragHint: '🎯 拖拽图片到这里 (PNG、JPG、JPEG、GIF、BMP、WebP)', dragHint: '🎯 拖拽图片到这里 或 按 Ctrl+V 贴上剪贴板图片 (PNG、JPG、JPEG、GIF、BMP、WebP)',
deleteConfirm: '确定要移除图片 "{filename}" 吗?', deleteConfirm: '确定要移除图片 "{filename}" 吗?',
deleteTitle: '确认删除' deleteTitle: '确认删除'
}, },
@ -189,6 +193,8 @@ class I18nManager {
commandFinished: '命令执行完成', commandFinished: '命令执行完成',
pasteSuccess: '已从剪贴板粘贴图片', pasteSuccess: '已从剪贴板粘贴图片',
pasteFailed: '无法从剪贴板获取图片', pasteFailed: '无法从剪贴板获取图片',
paste_no_image: '剪贴板中没有图片可粘贴',
paste_image_from_textarea: '已将图片从文字框智能贴到图片区域',
invalidFileType: '不支持的文件类型', invalidFileType: '不支持的文件类型',
fileTooLarge: '文件过大(最大 1MB' fileTooLarge: '文件过大(最大 1MB'
}, },
@ -361,6 +367,8 @@ class I18nManager {
'command_finished': 'status.commandFinished', 'command_finished': 'status.commandFinished',
'paste_success': 'status.pasteSuccess', 'paste_success': 'status.pasteSuccess',
'paste_failed': 'status.pasteFailed', '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', 'invalid_file_type': 'status.invalidFileType',
'file_too_large': 'status.fileTooLarge' 'file_too_large': 'status.fileTooLarge'
}; };

View File

@ -656,7 +656,7 @@
<textarea <textarea
id="feedbackText" id="feedbackText"
class="text-input" class="text-input"
placeholder="請在這裡輸入您的回饋、建議或問題...&#10;&#10;💡 小提示:按 Ctrl+Enter 可快速提交回饋" placeholder="請在這裡輸入您的回饋、建議或問題...&#10;&#10;💡 小提示:&#10;按 Ctrl+Enter 可快速提交回饋&#10;• 按 Ctrl+V 可直接貼上剪貼簿圖片"
></textarea> ></textarea>
</div> </div>
@ -669,7 +669,7 @@
<button class="upload-btn danger" onclick="clearAllImages()" id="clearBtn">❌ 清除</button> <button class="upload-btn danger" onclick="clearAllImages()" id="clearBtn">❌ 清除</button>
</div> </div>
<div class="drop-zone" id="dropZone"> <div class="drop-zone" id="dropZone">
🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP) 🎯 拖拽圖片到這裡 或 按 Ctrl+V 貼上剪貼簿圖片 (PNG、JPG、JPEG、GIF、BMP、WebP)
</div> </div>
<div class="image-status" id="imageStatus">已選擇 0 張圖片</div> <div class="image-status" id="imageStatus">已選擇 0 張圖片</div>
<div class="image-preview-area" id="imagePreviewArea"></div> <div class="image-preview-area" id="imagePreviewArea"></div>
@ -1007,9 +1007,66 @@
e.preventDefault(); e.preventDefault();
submitFeedback(); 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) { function switchTab(tabName) {
// 隱藏所有分頁內容 // 隱藏所有分頁內容

View File

@ -69,7 +69,7 @@ def test_qt_gui():
debug_log(" - X刪除按鈕") debug_log(" - X刪除按鈕")
debug_log(" - 視窗大小調整") debug_log(" - 視窗大小調整")
debug_log(" - 分割器調整") debug_log(" - 分割器調整")
debug_log() debug_log("")
# 啟動 GUI # 啟動 GUI
result = feedback_ui(project_directory, prompt) result = feedback_ui(project_directory, prompt)

View File

@ -25,6 +25,9 @@ import webbrowser
import time import time
import sys import sys
import os import os
import socket
import threading
from pathlib import Path
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
# 添加專案根目錄到 Python 路徑 # 添加專案根目錄到 Python 路徑