mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
✨ 調整頁籤位置、修復命令功能
This commit is contained in:
parent
0383499ca5
commit
6203a75aab
@ -465,15 +465,14 @@ class ImageUploadWidget(QWidget):
|
||||
"size": file_size
|
||||
}
|
||||
added_count += 1
|
||||
debug_log(f"圖片添加成功: {os.path.basename(file_path)}, 數據大小: {len(raw_data)} bytes")
|
||||
debug_log(f"圖片添加成功: {os.path.basename(file_path)}")
|
||||
|
||||
except Exception as e:
|
||||
debug_log(f"添加圖片失敗: {e}")
|
||||
QMessageBox.warning(self, "錯誤", f"無法載入圖片 {os.path.basename(file_path)}:\n{str(e)}")
|
||||
|
||||
debug_log(f"共添加 {added_count} 張圖片")
|
||||
debug_log(f"當前總共有 {len(self.images)} 張圖片")
|
||||
if added_count > 0:
|
||||
debug_log(f"共添加 {added_count} 張圖片,當前總數: {len(self.images)}")
|
||||
self._refresh_preview()
|
||||
self._update_status()
|
||||
self.images_changed.emit()
|
||||
@ -550,21 +549,8 @@ class ImageUploadWidget(QWidget):
|
||||
|
||||
self.status_label.setText(t('images.statusWithSize', count=count, size=size_str))
|
||||
|
||||
# 詳細調試信息
|
||||
debug_log(f"=== 圖片狀態更新 ===")
|
||||
debug_log(f"圖片數量: {count}")
|
||||
debug_log(f"總大小: {total_size} bytes ({size_str})")
|
||||
for i, (image_id, img) in enumerate(self.images.items(), 1):
|
||||
data_size = len(img["data"]) if isinstance(img["data"], bytes) else 0
|
||||
# 智能顯示每張圖片的大小
|
||||
if data_size < 1024:
|
||||
data_str = f"{data_size} B"
|
||||
elif data_size < 1024 * 1024:
|
||||
data_str = f"{data_size/1024:.1f} KB"
|
||||
else:
|
||||
data_str = f"{data_size/(1024*1024):.1f} MB"
|
||||
debug_log(f"圖片 {i}: {img['name']} - 數據大小: {data_str}")
|
||||
debug_log(f"==================")
|
||||
# 基本調試信息
|
||||
debug_log(f"圖片狀態: {count} 張圖片,總大小: {size_str}")
|
||||
|
||||
def get_images_data(self) -> List[dict]:
|
||||
"""獲取圖片數據"""
|
||||
@ -672,6 +658,58 @@ class FeedbackWindow(QMainWindow):
|
||||
"""回饋收集主窗口"""
|
||||
language_changed = Signal()
|
||||
|
||||
# 統一按鈕樣式常量
|
||||
BUTTON_BASE_STYLE = """
|
||||
QPushButton {
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
"""
|
||||
|
||||
PRIMARY_BUTTON_STYLE = BUTTON_BASE_STYLE + """
|
||||
QPushButton {
|
||||
background-color: #0e639c;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
"""
|
||||
|
||||
SUCCESS_BUTTON_STYLE = BUTTON_BASE_STYLE + """
|
||||
QPushButton {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
"""
|
||||
|
||||
DANGER_BUTTON_STYLE = BUTTON_BASE_STYLE + """
|
||||
QPushButton {
|
||||
background-color: #f44336;
|
||||
color: #ffffff;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #d32f2f;
|
||||
color: #ffffff;
|
||||
}
|
||||
"""
|
||||
|
||||
SECONDARY_BUTTON_STYLE = BUTTON_BASE_STYLE + """
|
||||
QPushButton {
|
||||
background-color: #666666;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #555555;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, project_dir: str, summary: str):
|
||||
super().__init__()
|
||||
self.project_dir = project_dir
|
||||
@ -719,26 +757,16 @@ class FeedbackWindow(QMainWindow):
|
||||
header_layout = QHBoxLayout(header_widget)
|
||||
header_layout.setContentsMargins(0, 0, 0, 8)
|
||||
|
||||
# 專案目錄信息
|
||||
# 專案目錄信息 - 修改為單行顯示
|
||||
self.project_label = QLabel(f"{t('app.projectDirectory')}: {self.project_dir}")
|
||||
self.project_label.setStyleSheet("color: #9e9e9e; font-size: 12px; padding: 4px 0;") # 增大字體
|
||||
self.project_label.setWordWrap(True)
|
||||
self.project_label.setStyleSheet("color: #9e9e9e; font-size: 12px; padding: 4px 0;")
|
||||
# 移除 setWordWrap(True) 以實現單行顯示
|
||||
header_layout.addWidget(self.project_label)
|
||||
|
||||
header_layout.addStretch()
|
||||
|
||||
layout.addWidget(header_widget)
|
||||
|
||||
def _create_menu_bar(self) -> None:
|
||||
"""創建菜單欄"""
|
||||
# 移除菜單欄實現,改用工具欄中的下拉選擇器
|
||||
pass
|
||||
|
||||
def _change_language(self, language: str) -> None:
|
||||
"""更改語言"""
|
||||
# 移除舊的實現,語言變更現在通過下拉選擇器處理
|
||||
pass
|
||||
|
||||
def _refresh_ui_texts(self) -> None:
|
||||
"""刷新界面文字"""
|
||||
# 更新窗口標題
|
||||
@ -774,13 +802,13 @@ class FeedbackWindow(QMainWindow):
|
||||
"""更新元件文字"""
|
||||
# 更新分頁標籤
|
||||
if hasattr(self, 'tab_widget'):
|
||||
# AI 摘要分頁
|
||||
self.tab_widget.setTabText(0, t('tabs.summary'))
|
||||
# 回饋分頁
|
||||
self.tab_widget.setTabText(1, t('tabs.feedback'))
|
||||
# 命令分頁
|
||||
# 回饋分頁 - 現在是第一個
|
||||
self.tab_widget.setTabText(0, t('tabs.feedback'))
|
||||
# AI 摘要分頁 - 現在是第二個
|
||||
self.tab_widget.setTabText(1, t('tabs.summary'))
|
||||
# 命令分頁 - 現在是第三個
|
||||
self.tab_widget.setTabText(2, t('tabs.command'))
|
||||
# 語言設置分頁
|
||||
# 語言設置分頁 - 現在是第四個
|
||||
self.tab_widget.setTabText(3, t('tabs.language'))
|
||||
|
||||
# 更新專案目錄標籤
|
||||
@ -1037,12 +1065,12 @@ class FeedbackWindow(QMainWindow):
|
||||
self.tab_widget = QTabWidget()
|
||||
self.tab_widget.setMinimumHeight(500) # 增加分頁區域高度
|
||||
|
||||
# AI 工作摘要分頁
|
||||
self._create_summary_tab()
|
||||
|
||||
# 回饋分頁
|
||||
# 回饋分頁 - 移到第一個位置
|
||||
self._create_feedback_tab()
|
||||
|
||||
# AI 工作摘要分頁 - 移到第二個位置
|
||||
self._create_summary_tab()
|
||||
|
||||
# 命令分頁
|
||||
self._create_command_tab()
|
||||
|
||||
@ -1140,128 +1168,125 @@ class FeedbackWindow(QMainWindow):
|
||||
self.tab_widget.addTab(feedback_widget, t('tabs.feedback'))
|
||||
|
||||
def _create_command_tab(self) -> None:
|
||||
"""創建命令分頁(優化布局)"""
|
||||
"""創建命令分頁(終端機風格布局)"""
|
||||
command_widget = QWidget()
|
||||
command_layout = QVBoxLayout(command_widget)
|
||||
command_layout.setSpacing(0) # 緊湊佈局
|
||||
command_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# 使用分割器來管理命令輸入和輸出區域
|
||||
command_splitter = QSplitter(Qt.Vertical)
|
||||
command_splitter.setChildrenCollapsible(False)
|
||||
# 命令標題區域(頂部)
|
||||
header_widget = QWidget()
|
||||
header_layout = QVBoxLayout(header_widget)
|
||||
header_layout.setSpacing(6)
|
||||
header_layout.setContentsMargins(12, 8, 12, 8)
|
||||
|
||||
# 命令輸入區域
|
||||
command_input_widget = QWidget()
|
||||
command_input_layout = QVBoxLayout(command_input_widget)
|
||||
command_input_layout.setSpacing(8)
|
||||
command_input_layout.setContentsMargins(12, 12, 12, 8)
|
||||
|
||||
command_group = QGroupBox()
|
||||
command_layout = QVBoxLayout(command_group)
|
||||
command_layout.setSpacing(8)
|
||||
command_layout.setContentsMargins(12, 8, 12, 12)
|
||||
|
||||
# 命令標題
|
||||
self.command_title = QLabel(t('command.title'))
|
||||
self.command_title.setFont(QFont("", 13, QFont.Bold)) # 增大字體
|
||||
self.command_title.setStyleSheet("color: #007acc; margin-bottom: 6px;")
|
||||
command_layout.addWidget(self.command_title)
|
||||
self.command_title.setFont(QFont("", 13, QFont.Bold))
|
||||
self.command_title.setStyleSheet("color: #007acc; margin-bottom: 4px;")
|
||||
header_layout.addWidget(self.command_title)
|
||||
|
||||
# 說明文字
|
||||
self.command_description = QLabel(t('command.description'))
|
||||
self.command_description.setStyleSheet("color: #9e9e9e; font-size: 11px; margin-bottom: 10px;") # 增大字體
|
||||
self.command_description.setStyleSheet("color: #9e9e9e; font-size: 11px; margin-bottom: 6px;")
|
||||
self.command_description.setWordWrap(True)
|
||||
command_layout.addWidget(self.command_description)
|
||||
header_layout.addWidget(self.command_description)
|
||||
|
||||
# 命令輸入和執行按鈕
|
||||
input_layout = QHBoxLayout()
|
||||
self.command_input = QLineEdit()
|
||||
self.command_input.setPlaceholderText(t('command.placeholder'))
|
||||
self.command_input.setMinimumHeight(36) # 增加輸入框高度
|
||||
self.command_input.setStyleSheet("""
|
||||
QLineEdit {
|
||||
background-color: #2d2d30;
|
||||
border: 1px solid #464647;
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
}
|
||||
""")
|
||||
self.command_input.returnPressed.connect(self._run_command)
|
||||
input_layout.addWidget(self.command_input)
|
||||
command_layout.addWidget(header_widget)
|
||||
|
||||
self.run_command_button = QPushButton(t('buttons.runCommand'))
|
||||
self.run_command_button.clicked.connect(self._run_command)
|
||||
self.run_command_button.setFixedSize(110, 36) # 調整按鈕大小
|
||||
self.run_command_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #0e639c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
""")
|
||||
input_layout.addWidget(self.run_command_button)
|
||||
|
||||
command_layout.addLayout(input_layout)
|
||||
command_input_layout.addWidget(command_group)
|
||||
|
||||
# 命令輸出區域
|
||||
# 命令輸出區域(中間,佔大部分空間)
|
||||
output_widget = QWidget()
|
||||
output_layout = QVBoxLayout(output_widget)
|
||||
output_layout.setSpacing(8)
|
||||
output_layout.setContentsMargins(12, 8, 12, 12)
|
||||
|
||||
output_group = QGroupBox()
|
||||
output_group_layout = QVBoxLayout(output_group)
|
||||
output_group_layout.setSpacing(8)
|
||||
output_group_layout.setContentsMargins(12, 8, 12, 12)
|
||||
output_layout.setSpacing(6)
|
||||
output_layout.setContentsMargins(12, 4, 12, 8)
|
||||
|
||||
self.command_output_label = QLabel(t('command.output'))
|
||||
self.command_output_label.setFont(QFont("", 13, QFont.Bold)) # 增大字體
|
||||
self.command_output_label.setStyleSheet("color: #007acc; margin-bottom: 6px;")
|
||||
output_group_layout.addWidget(self.command_output_label)
|
||||
self.command_output_label.setFont(QFont("", 12, QFont.Bold))
|
||||
self.command_output_label.setStyleSheet("color: #007acc; margin-bottom: 4px;")
|
||||
output_layout.addWidget(self.command_output_label)
|
||||
|
||||
self.command_output = QTextEdit()
|
||||
self.command_output.setReadOnly(True)
|
||||
self.command_output.setFont(QFont("Consolas", 11)) # 增大等寬字體
|
||||
self.command_output.setMinimumHeight(220) # 增加最小高度
|
||||
# 改進輸出區域樣式
|
||||
self.command_output.setFont(QFont("Consolas", 11))
|
||||
# 終端機風格樣式
|
||||
self.command_output.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid #555;
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
color: #e0e0e0;
|
||||
line-height: 1.3;
|
||||
color: #00ff00;
|
||||
line-height: 1.4;
|
||||
}
|
||||
QScrollBar:vertical {
|
||||
background-color: #2a2a2a;
|
||||
width: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
background-color: #555;
|
||||
border-radius: 6px;
|
||||
min-height: 20px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
""")
|
||||
output_group_layout.addWidget(self.command_output, 1)
|
||||
output_layout.addWidget(self.command_output, 1) # 佔據剩餘空間
|
||||
|
||||
output_layout.addWidget(output_group, 1)
|
||||
command_layout.addWidget(output_widget, 1) # 輸出區域佔大部分空間
|
||||
|
||||
# 添加到分割器
|
||||
command_splitter.addWidget(command_input_widget)
|
||||
command_splitter.addWidget(output_widget)
|
||||
# 命令輸入區域(底部,固定高度)
|
||||
input_widget = QWidget()
|
||||
input_widget.setFixedHeight(70) # 固定高度
|
||||
input_layout = QVBoxLayout(input_widget)
|
||||
input_layout.setSpacing(6)
|
||||
input_layout.setContentsMargins(12, 8, 12, 12)
|
||||
|
||||
# 設置分割器的初始比例(命令輸入:輸出 = 1:3)
|
||||
command_splitter.setStretchFactor(0, 1)
|
||||
command_splitter.setStretchFactor(1, 3)
|
||||
command_splitter.setSizes([120, 350])
|
||||
# 命令輸入和執行按鈕(水平布局)
|
||||
input_row_layout = QHBoxLayout()
|
||||
input_row_layout.setSpacing(8)
|
||||
|
||||
# 設置主布局
|
||||
main_layout = QVBoxLayout(command_widget)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.addWidget(command_splitter)
|
||||
# 提示符號標籤
|
||||
prompt_label = QLabel("$")
|
||||
prompt_label.setStyleSheet("color: #00ff00; font-family: 'Consolas', 'Monaco', monospace; font-size: 14px; font-weight: bold;")
|
||||
prompt_label.setFixedWidth(20)
|
||||
input_row_layout.addWidget(prompt_label)
|
||||
|
||||
self.command_input = QLineEdit()
|
||||
self.command_input.setPlaceholderText(t('command.placeholder'))
|
||||
self.command_input.setMinimumHeight(36)
|
||||
# 終端機風格輸入框
|
||||
self.command_input.setStyleSheet("""
|
||||
QLineEdit {
|
||||
background-color: #1a1a1a;
|
||||
border: 2px solid #333;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
color: #00ff00;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border-color: #007acc;
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
""")
|
||||
self.command_input.returnPressed.connect(self._run_command)
|
||||
input_row_layout.addWidget(self.command_input, 1) # 佔據大部分空間
|
||||
|
||||
self.run_command_button = QPushButton(t('buttons.runCommand'))
|
||||
self.run_command_button.clicked.connect(self._run_command)
|
||||
self.run_command_button.setFixedSize(80, 36)
|
||||
self.run_command_button.setStyleSheet(self.PRIMARY_BUTTON_STYLE)
|
||||
input_row_layout.addWidget(self.run_command_button)
|
||||
|
||||
input_layout.addLayout(input_row_layout)
|
||||
|
||||
command_layout.addWidget(input_widget) # 輸入區域在底部
|
||||
|
||||
self.tab_widget.addTab(command_widget, t('tabs.command'))
|
||||
|
||||
|
||||
def _create_action_buttons(self, layout: QVBoxLayout) -> None:
|
||||
"""創建操作按鈕"""
|
||||
button_layout = QHBoxLayout()
|
||||
@ -1271,19 +1296,7 @@ class FeedbackWindow(QMainWindow):
|
||||
self.cancel_button = QPushButton(t('buttons.cancel'))
|
||||
self.cancel_button.clicked.connect(self._cancel_feedback)
|
||||
self.cancel_button.setFixedSize(130, 40) # 增大按鈕尺寸
|
||||
self.cancel_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #666666;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #555555;
|
||||
}
|
||||
""")
|
||||
self.cancel_button.setStyleSheet(self.SECONDARY_BUTTON_STYLE)
|
||||
button_layout.addWidget(self.cancel_button)
|
||||
|
||||
# 提交按鈕
|
||||
@ -1291,19 +1304,7 @@ class FeedbackWindow(QMainWindow):
|
||||
self.submit_button.clicked.connect(self._submit_feedback)
|
||||
self.submit_button.setFixedSize(160, 40) # 增大按鈕尺寸
|
||||
self.submit_button.setDefault(True)
|
||||
self.submit_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #0e639c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #005a9e;
|
||||
}
|
||||
""")
|
||||
self.submit_button.setStyleSheet(self.PRIMARY_BUTTON_STYLE)
|
||||
button_layout.addWidget(self.submit_button)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
@ -1370,15 +1371,147 @@ class FeedbackWindow(QMainWindow):
|
||||
except Exception as e:
|
||||
debug_log(f"智能貼上失敗: {e}")
|
||||
|
||||
def _append_command_output(self, text: str) -> None:
|
||||
"""添加命令輸出並自動滾動到底部"""
|
||||
if hasattr(self, 'command_output'):
|
||||
# 移動光標到最後
|
||||
cursor = self.command_output.textCursor()
|
||||
cursor.movePosition(cursor.MoveOperation.End)
|
||||
self.command_output.setTextCursor(cursor)
|
||||
|
||||
# 插入文本
|
||||
self.command_output.insertPlainText(text)
|
||||
|
||||
# 確保滾動到最底部
|
||||
scrollbar = self.command_output.verticalScrollBar()
|
||||
scrollbar.setValue(scrollbar.maximum())
|
||||
|
||||
# 刷新界面
|
||||
QApplication.processEvents()
|
||||
|
||||
def _read_command_output(self) -> None:
|
||||
"""讀取命令輸出(非阻塞方式)"""
|
||||
if not hasattr(self, 'command_process') or not self.command_process:
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
return
|
||||
|
||||
# 檢查進程是否還在運行
|
||||
if self.command_process.poll() is None:
|
||||
try:
|
||||
# 檢查是否有可讀取的輸出(非阻塞)
|
||||
import select
|
||||
import sys
|
||||
|
||||
if sys.platform == "win32":
|
||||
# Windows 下使用不同的方法
|
||||
try:
|
||||
# 嘗試讀取一行,但設置較短的超時
|
||||
import threading
|
||||
import queue
|
||||
|
||||
if not hasattr(self, '_output_queue'):
|
||||
self._output_queue = queue.Queue()
|
||||
self._reader_thread = threading.Thread(
|
||||
target=self._read_process_output_thread,
|
||||
daemon=True
|
||||
)
|
||||
self._reader_thread.start()
|
||||
|
||||
# 從隊列中獲取輸出(非阻塞)
|
||||
try:
|
||||
while True:
|
||||
output = self._output_queue.get_nowait()
|
||||
if output is None: # 進程結束信號
|
||||
break
|
||||
self._append_command_output(output)
|
||||
except queue.Empty:
|
||||
pass # 沒有新輸出,繼續等待
|
||||
|
||||
except ImportError:
|
||||
# 如果threading不可用,使用原來的方法但加上非阻塞檢查
|
||||
output = self.command_process.stdout.readline()
|
||||
if output:
|
||||
filtered_output = self._filter_command_output(output)
|
||||
if filtered_output:
|
||||
self._append_command_output(filtered_output)
|
||||
else:
|
||||
# Unix/Linux/macOS 下使用 select
|
||||
ready, _, _ = select.select([self.command_process.stdout], [], [], 0.1)
|
||||
if ready:
|
||||
output = self.command_process.stdout.readline()
|
||||
if output:
|
||||
# 過濾不必要的輸出行
|
||||
filtered_output = self._filter_command_output(output)
|
||||
if filtered_output:
|
||||
self._append_command_output(filtered_output)
|
||||
|
||||
# 檢查命令執行超時(30秒)
|
||||
if not hasattr(self, '_command_start_time'):
|
||||
self._command_start_time = time.time()
|
||||
elif time.time() - self._command_start_time > 30:
|
||||
self._append_command_output(f"\n⚠️ 命令執行超過30秒,自動終止...")
|
||||
self._terminate_command()
|
||||
|
||||
except Exception as e:
|
||||
debug_log(f"讀取命令輸出錯誤: {e}")
|
||||
else:
|
||||
# 進程結束,停止計時器並讀取剩餘輸出
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
|
||||
# 清理資源
|
||||
if hasattr(self, '_output_queue'):
|
||||
delattr(self, '_output_queue')
|
||||
if hasattr(self, '_reader_thread'):
|
||||
delattr(self, '_reader_thread')
|
||||
if hasattr(self, '_command_start_time'):
|
||||
delattr(self, '_command_start_time')
|
||||
|
||||
try:
|
||||
# 讀取剩餘的輸出
|
||||
remaining_output, _ = self.command_process.communicate(timeout=2)
|
||||
if remaining_output and remaining_output.strip():
|
||||
filtered_output = self._filter_command_output(remaining_output)
|
||||
if filtered_output:
|
||||
self._append_command_output(filtered_output)
|
||||
except subprocess.TimeoutExpired:
|
||||
debug_log("讀取剩餘輸出超時")
|
||||
except Exception as e:
|
||||
debug_log(f"讀取剩餘輸出錯誤: {e}")
|
||||
|
||||
return_code = self.command_process.returncode
|
||||
self._append_command_output(f"\n進程結束,返回碼: {return_code}\n")
|
||||
|
||||
def _run_command(self) -> None:
|
||||
"""執行命令"""
|
||||
command = self.command_input.text().strip()
|
||||
if not command:
|
||||
return
|
||||
|
||||
self.command_output.append(f"$ {command}")
|
||||
# 如果已經有命令在執行,先停止
|
||||
if hasattr(self, 'timer') and self.timer.isActive():
|
||||
self._terminate_command()
|
||||
|
||||
self._append_command_output(f"$ {command}\n")
|
||||
|
||||
# 清空輸入欄位
|
||||
self.command_input.clear()
|
||||
|
||||
# 保存當前命令用於輸出過濾
|
||||
self._last_command = command
|
||||
|
||||
try:
|
||||
# 準備環境變數以避免不必要的輸出
|
||||
env = os.environ.copy()
|
||||
# 禁用npm的進度顯示和其他多餘輸出
|
||||
env['NO_UPDATE_NOTIFIER'] = '1'
|
||||
env['NPM_CONFIG_UPDATE_NOTIFIER'] = 'false'
|
||||
env['NPM_CONFIG_FUND'] = 'false'
|
||||
env['NPM_CONFIG_AUDIT'] = 'false'
|
||||
env['NPM_CONFIG_PROGRESS'] = 'false'
|
||||
env['CI'] = 'true' # 這會讓很多工具使用非互動模式
|
||||
|
||||
# 在專案目錄中執行命令
|
||||
self.command_process = subprocess.Popen(
|
||||
command,
|
||||
@ -1388,38 +1521,126 @@ class FeedbackWindow(QMainWindow):
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
universal_newlines=True
|
||||
universal_newlines=True,
|
||||
env=env # 使用修改過的環境變數
|
||||
)
|
||||
|
||||
# 初始化命令開始時間
|
||||
self._command_start_time = time.time()
|
||||
|
||||
# 清理之前的資源
|
||||
if hasattr(self, '_output_queue'):
|
||||
delattr(self, '_output_queue')
|
||||
if hasattr(self, '_reader_thread'):
|
||||
delattr(self, '_reader_thread')
|
||||
|
||||
# 使用計時器讀取輸出
|
||||
self.timer = QTimer()
|
||||
self.timer.timeout.connect(self._read_command_output)
|
||||
self.timer.start(100)
|
||||
|
||||
except Exception as e:
|
||||
self.command_output.append(f"錯誤: {str(e)}")
|
||||
self._append_command_output(f"錯誤: {str(e)}\n")
|
||||
# 發生錯誤時也要確保輸入欄位已清空
|
||||
self.command_input.clear()
|
||||
|
||||
def _read_command_output(self) -> None:
|
||||
"""讀取命令輸出"""
|
||||
if self.command_process and self.command_process.poll() is None:
|
||||
def _read_process_output_thread(self) -> None:
|
||||
"""在後台線程中讀取進程輸出(Windows專用)"""
|
||||
try:
|
||||
while self.command_process and self.command_process.poll() is None:
|
||||
try:
|
||||
output = self.command_process.stdout.readline()
|
||||
if output:
|
||||
# 過濾不必要的輸出行
|
||||
filtered_output = self._filter_command_output(output)
|
||||
if filtered_output:
|
||||
self._output_queue.put(filtered_output)
|
||||
else:
|
||||
# 沒有輸出時稍微休眠,避免CPU過度使用
|
||||
time.sleep(0.05)
|
||||
except Exception as e:
|
||||
debug_log(f"後台線程讀取輸出錯誤: {e}")
|
||||
break
|
||||
|
||||
# 進程結束,發送結束信號
|
||||
if hasattr(self, '_output_queue'):
|
||||
self._output_queue.put(None)
|
||||
|
||||
except Exception as e:
|
||||
debug_log(f"後台線程錯誤: {e}")
|
||||
|
||||
def _filter_command_output(self, output: str) -> str:
|
||||
"""過濾命令輸出,移除不必要的信息"""
|
||||
if not output or not output.strip():
|
||||
return ""
|
||||
|
||||
# 需要過濾的模式
|
||||
filter_patterns = [
|
||||
# npm 相關的無關輸出
|
||||
"npm WARN config global",
|
||||
"npm WARN config user",
|
||||
"npm notice",
|
||||
"npm fund",
|
||||
"npm audit",
|
||||
"added",
|
||||
"found 0 vulnerabilities",
|
||||
"up to date",
|
||||
"packages are looking for funding",
|
||||
"run `npm fund` for details",
|
||||
# Python 相關的無關輸出
|
||||
"WARNING:",
|
||||
"Traceback",
|
||||
# 其他工具的無關輸出
|
||||
"deprecated",
|
||||
"WARN",
|
||||
]
|
||||
|
||||
# 檢查是否包含過濾模式
|
||||
for pattern in filter_patterns:
|
||||
if pattern.lower() in output.lower():
|
||||
return ""
|
||||
|
||||
# 對於npm --version,只保留版本號行
|
||||
if hasattr(self, '_last_command') and 'npm' in self._last_command and '--version' in self._last_command:
|
||||
# 如果輸出看起來像版本號(數字.數字.數字格式)
|
||||
import re
|
||||
version_pattern = r'^\d+\.\d+\.\d+'
|
||||
if re.match(version_pattern, output.strip()):
|
||||
return output
|
||||
# 過濾掉其他非版本號的輸出
|
||||
elif not any(char.isdigit() for char in output):
|
||||
return ""
|
||||
|
||||
return output
|
||||
|
||||
def _terminate_command(self) -> None:
|
||||
"""終止當前執行的命令"""
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
|
||||
if hasattr(self, 'command_process') and self.command_process:
|
||||
try:
|
||||
output = self.command_process.stdout.readline()
|
||||
if output:
|
||||
self.command_output.insertPlainText(output)
|
||||
# 自動滾動到底部
|
||||
cursor = self.command_output.textCursor()
|
||||
cursor.movePosition(cursor.End)
|
||||
self.command_output.setTextCursor(cursor)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# 進程結束
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
if self.command_process:
|
||||
return_code = self.command_process.returncode
|
||||
self.command_output.append(f"\n進程結束,返回碼: {return_code}\n")
|
||||
|
||||
# 嘗試優雅地終止進程
|
||||
self.command_process.terminate()
|
||||
|
||||
# 等待一段時間,如果進程沒有結束,強制殺死
|
||||
try:
|
||||
self.command_process.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
self.command_process.kill()
|
||||
self._append_command_output("強制終止進程")
|
||||
|
||||
except Exception as e:
|
||||
debug_log(f"終止命令進程錯誤: {e}")
|
||||
|
||||
# 清理資源
|
||||
if hasattr(self, '_output_queue'):
|
||||
delattr(self, '_output_queue')
|
||||
if hasattr(self, '_reader_thread'):
|
||||
delattr(self, '_reader_thread')
|
||||
if hasattr(self, '_command_start_time'):
|
||||
delattr(self, '_command_start_time')
|
||||
|
||||
def _submit_feedback(self) -> None:
|
||||
"""提交回饋"""
|
||||
feedback_text = self.feedback_input.toPlainText().strip()
|
||||
@ -1448,13 +1669,9 @@ class FeedbackWindow(QMainWindow):
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
"""處理視窗關閉事件"""
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
if self.command_process:
|
||||
try:
|
||||
self.command_process.terminate()
|
||||
except:
|
||||
pass
|
||||
# 清理命令執行相關資源
|
||||
if hasattr(self, 'timer') or hasattr(self, 'command_process'):
|
||||
self._terminate_command()
|
||||
|
||||
# 清理圖片上傳組件中的臨時文件
|
||||
if hasattr(self, 'image_upload') and self.image_upload:
|
||||
|
Loading…
x
Reference in New Issue
Block a user