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
f4d101a881
commit
58540f3c56
@ -64,7 +64,7 @@ def feedback_ui_with_timeout(project_directory: str, summary: str, timeout: int)
|
||||
Args:
|
||||
project_directory: 專案目錄路徑
|
||||
summary: AI 工作摘要
|
||||
timeout: 超時時間(秒)
|
||||
timeout: 超時時間(秒)- MCP 傳入的超時時間,作為最大限制
|
||||
|
||||
Returns:
|
||||
Optional[FeedbackResult]: 回饋結果,如果用戶取消或超時則返回 None
|
||||
@ -88,24 +88,39 @@ def feedback_ui_with_timeout(project_directory: str, summary: str, timeout: int)
|
||||
}
|
||||
""")
|
||||
|
||||
# 創建主窗口
|
||||
window = FeedbackWindow(project_directory, summary)
|
||||
# 創建主窗口,傳入 MCP 超時時間
|
||||
window = FeedbackWindow(project_directory, summary, timeout)
|
||||
|
||||
# 連接超時信號
|
||||
timeout_occurred = False
|
||||
def on_timeout():
|
||||
nonlocal timeout_occurred
|
||||
timeout_occurred = True
|
||||
|
||||
window.timeout_occurred.connect(on_timeout)
|
||||
|
||||
window.show()
|
||||
|
||||
# 創建超時計時器
|
||||
timeout_timer = QTimer()
|
||||
timeout_timer.setSingleShot(True)
|
||||
timeout_timer.timeout.connect(lambda: _handle_timeout(window, app))
|
||||
timeout_timer.start(timeout * 1000) # 轉換為毫秒
|
||||
# 開始用戶設置的超時倒數(如果啟用)
|
||||
window.start_timeout_if_enabled()
|
||||
|
||||
# 創建 MCP 超時計時器作為後備
|
||||
mcp_timeout_timer = QTimer()
|
||||
mcp_timeout_timer.setSingleShot(True)
|
||||
mcp_timeout_timer.timeout.connect(lambda: _handle_mcp_timeout(window, app))
|
||||
mcp_timeout_timer.start(timeout * 1000) # 轉換為毫秒
|
||||
|
||||
# 運行事件循環直到窗口關閉
|
||||
app.exec()
|
||||
|
||||
# 停止計時器(如果還在運行)
|
||||
timeout_timer.stop()
|
||||
mcp_timeout_timer.stop()
|
||||
window.stop_timeout()
|
||||
|
||||
# 檢查是否超時
|
||||
if hasattr(window, '_timeout_occurred'):
|
||||
if timeout_occurred:
|
||||
raise TimeoutError(f"回饋收集超時,GUI 介面已自動關閉")
|
||||
elif hasattr(window, '_timeout_occurred'):
|
||||
raise TimeoutError(f"回饋收集超時({timeout}秒),GUI 介面已自動關閉")
|
||||
|
||||
# 返回結果
|
||||
@ -113,7 +128,17 @@ def feedback_ui_with_timeout(project_directory: str, summary: str, timeout: int)
|
||||
|
||||
|
||||
def _handle_timeout(window: FeedbackWindow, app: QApplication) -> None:
|
||||
"""處理超時事件"""
|
||||
"""處理超時事件(舊版本,保留向後兼容)"""
|
||||
# 標記超時發生
|
||||
window._timeout_occurred = True
|
||||
# 強制關閉視窗
|
||||
window.force_close()
|
||||
# 退出應用程式
|
||||
app.quit()
|
||||
|
||||
|
||||
def _handle_mcp_timeout(window: FeedbackWindow, app: QApplication) -> None:
|
||||
"""處理 MCP 超時事件(後備機制)"""
|
||||
# 標記超時發生
|
||||
window._timeout_occurred = True
|
||||
# 強制關閉視窗
|
||||
|
@ -10,9 +10,10 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QComboBox, QRadioButton, QButtonGroup, QMessageBox,
|
||||
QCheckBox, QPushButton, QFrame
|
||||
QCheckBox, QPushButton, QFrame, QSpinBox
|
||||
)
|
||||
from ..widgets import SwitchWithLabel
|
||||
from ..widgets.styled_spinbox import StyledSpinBox
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
@ -25,6 +26,7 @@ class SettingsTab(QWidget):
|
||||
language_changed = Signal()
|
||||
layout_change_requested = Signal(bool, str) # 佈局變更請求信號 (combined_mode, orientation)
|
||||
reset_requested = Signal() # 重置設定請求信號
|
||||
timeout_settings_changed = Signal(bool, int) # 超時設置變更信號 (enabled, duration)
|
||||
|
||||
def __init__(self, combined_mode: bool, config_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -87,6 +89,12 @@ class SettingsTab(QWidget):
|
||||
# 添加分隔線
|
||||
self._add_separator(content_layout)
|
||||
|
||||
# === 超時設置 ===
|
||||
self._create_timeout_section(content_layout)
|
||||
|
||||
# 添加分隔線
|
||||
self._add_separator(content_layout)
|
||||
|
||||
# === 重置設定 ===
|
||||
self._create_reset_section(content_layout)
|
||||
|
||||
@ -307,6 +315,62 @@ class SettingsTab(QWidget):
|
||||
|
||||
layout.addLayout(options_layout)
|
||||
|
||||
def _create_timeout_section(self, layout: QVBoxLayout) -> None:
|
||||
"""創建超時設置區域"""
|
||||
header = self._create_section_header(t('timeout.settings.title'), "⏰")
|
||||
layout.addWidget(header)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['timeout_header'] = header
|
||||
|
||||
# 選項容器
|
||||
options_layout = QVBoxLayout()
|
||||
options_layout.setSpacing(12)
|
||||
|
||||
# 啟用超時自動關閉開關
|
||||
self.timeout_enabled_switch = SwitchWithLabel(t('timeout.enable'))
|
||||
self.timeout_enabled_switch.setChecked(self.config_manager.get_timeout_enabled())
|
||||
self.timeout_enabled_switch.toggled.connect(self._on_timeout_enabled_changed)
|
||||
options_layout.addWidget(self.timeout_enabled_switch)
|
||||
|
||||
# 超時時間設置
|
||||
timeout_duration_layout = QHBoxLayout()
|
||||
timeout_duration_layout.setContentsMargins(0, 8, 0, 0)
|
||||
|
||||
# 標籤
|
||||
timeout_duration_label = QLabel(t('timeout.duration.label'))
|
||||
timeout_duration_label.setStyleSheet("""
|
||||
QLabel {
|
||||
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
|
||||
color: #ffffff;
|
||||
font-size: 13px;
|
||||
}
|
||||
""")
|
||||
timeout_duration_layout.addWidget(timeout_duration_label)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['timeout_duration_label'] = timeout_duration_label
|
||||
|
||||
# 彈性空間
|
||||
timeout_duration_layout.addStretch()
|
||||
|
||||
# 時間輸入框
|
||||
self.timeout_duration_spinbox = StyledSpinBox()
|
||||
self.timeout_duration_spinbox.setRange(30, 7200) # 30秒到2小時
|
||||
self.timeout_duration_spinbox.setValue(self.config_manager.get_timeout_duration())
|
||||
self.timeout_duration_spinbox.setSuffix(" " + t('timeout.seconds'))
|
||||
# StyledSpinBox 已經有內建樣式,不需要額外設置
|
||||
self.timeout_duration_spinbox.valueChanged.connect(self._on_timeout_duration_changed)
|
||||
timeout_duration_layout.addWidget(self.timeout_duration_spinbox)
|
||||
|
||||
options_layout.addLayout(timeout_duration_layout)
|
||||
|
||||
# 說明文字
|
||||
description = self._create_description(t('timeout.settings.description'))
|
||||
options_layout.addWidget(description)
|
||||
# 保存引用以便更新
|
||||
self.ui_elements['timeout_description'] = description
|
||||
|
||||
layout.addLayout(options_layout)
|
||||
|
||||
def _create_reset_section(self, layout: QVBoxLayout) -> None:
|
||||
"""創建重置設定區域"""
|
||||
header = self._create_section_header(t('settings.reset.title'), "🔄")
|
||||
@ -422,6 +486,26 @@ class SettingsTab(QWidget):
|
||||
self.config_manager.set_always_center_window(checked)
|
||||
debug_log(f"視窗定位設置已保存: {checked}") # 調試輸出
|
||||
|
||||
def _on_timeout_enabled_changed(self, enabled: bool) -> None:
|
||||
"""超時啟用狀態變更事件處理"""
|
||||
# 立即保存設定
|
||||
self.config_manager.set_timeout_enabled(enabled)
|
||||
debug_log(f"超時啟用設置已保存: {enabled}")
|
||||
|
||||
# 發出信號通知主窗口
|
||||
duration = self.timeout_duration_spinbox.value()
|
||||
self.timeout_settings_changed.emit(enabled, duration)
|
||||
|
||||
def _on_timeout_duration_changed(self, duration: int) -> None:
|
||||
"""超時時間變更事件處理"""
|
||||
# 立即保存設定
|
||||
self.config_manager.set_timeout_duration(duration)
|
||||
debug_log(f"超時時間設置已保存: {duration}")
|
||||
|
||||
# 發出信號通知主窗口
|
||||
enabled = self.timeout_enabled_switch.isChecked()
|
||||
self.timeout_settings_changed.emit(enabled, duration)
|
||||
|
||||
def _on_reset_settings(self) -> None:
|
||||
"""重置設定事件處理"""
|
||||
reply = QMessageBox.question(
|
||||
@ -446,7 +530,8 @@ class SettingsTab(QWidget):
|
||||
self.ui_elements['window_header'].setText(f"🖥️ {t('settings.window.title')}")
|
||||
if 'reset_header' in self.ui_elements:
|
||||
self.ui_elements['reset_header'].setText(f"🔄 {t('settings.reset.title')}")
|
||||
|
||||
if 'timeout_header' in self.ui_elements:
|
||||
self.ui_elements['timeout_header'].setText(f"⏰ {t('timeout.settings.title')}")
|
||||
|
||||
|
||||
# 更新提示文字
|
||||
@ -456,6 +541,8 @@ class SettingsTab(QWidget):
|
||||
self.ui_elements['vertical_hint'].setText(f" {t('settings.layout.combinedVerticalDescription')}")
|
||||
if 'horizontal_hint' in self.ui_elements:
|
||||
self.ui_elements['horizontal_hint'].setText(f" {t('settings.layout.combinedHorizontalDescription')}")
|
||||
if 'timeout_description' in self.ui_elements:
|
||||
self.ui_elements['timeout_description'].setText(t('timeout.settings.description'))
|
||||
|
||||
# 更新按鈕文字
|
||||
if hasattr(self, 'reset_button'):
|
||||
@ -464,6 +551,14 @@ class SettingsTab(QWidget):
|
||||
# 更新切換開關文字
|
||||
if hasattr(self, 'always_center_switch'):
|
||||
self.always_center_switch.setText(t('settings.window.alwaysCenter'))
|
||||
if hasattr(self, 'timeout_enabled_switch'):
|
||||
self.timeout_enabled_switch.setText(t('timeout.enable'))
|
||||
|
||||
# 更新超時相關標籤和控件
|
||||
if 'timeout_duration_label' in self.ui_elements:
|
||||
self.ui_elements['timeout_duration_label'].setText(t('timeout.duration.label'))
|
||||
if hasattr(self, 'timeout_duration_spinbox'):
|
||||
self.timeout_duration_spinbox.setSuffix(" " + t('timeout.seconds'))
|
||||
|
||||
# 更新單選按鈕文字
|
||||
if hasattr(self, 'separate_mode_radio'):
|
||||
@ -492,6 +587,16 @@ class SettingsTab(QWidget):
|
||||
self.always_center_switch.setChecked(always_center)
|
||||
debug_log(f"重新載入視窗定位設置: {always_center}") # 調試輸出
|
||||
|
||||
# 重新載入超時設定
|
||||
if hasattr(self, 'timeout_enabled_switch'):
|
||||
timeout_enabled = self.config_manager.get_timeout_enabled()
|
||||
self.timeout_enabled_switch.setChecked(timeout_enabled)
|
||||
debug_log(f"重新載入超時啟用設置: {timeout_enabled}")
|
||||
if hasattr(self, 'timeout_duration_spinbox'):
|
||||
timeout_duration = self.config_manager.get_timeout_duration()
|
||||
self.timeout_duration_spinbox.setValue(timeout_duration)
|
||||
debug_log(f"重新載入超時時間設置: {timeout_duration}") # 調試輸出
|
||||
|
||||
def set_layout_mode(self, combined_mode: bool) -> None:
|
||||
"""設置佈局模式"""
|
||||
self.combined_mode = combined_mode
|
||||
|
@ -132,10 +132,12 @@ class ImageUploadWidget(QWidget):
|
||||
|
||||
def _on_size_limit_changed(self, index: int) -> None:
|
||||
"""圖片大小限制變更處理"""
|
||||
if self.config_manager:
|
||||
if self.config_manager and index >= 0:
|
||||
size_bytes = self.size_limit_combo.itemData(index)
|
||||
self.config_manager.set_image_size_limit(size_bytes)
|
||||
debug_log(f"圖片大小限制已更新: {size_bytes} bytes")
|
||||
# 處理 None 值
|
||||
if size_bytes is not None:
|
||||
self.config_manager.set_image_size_limit(size_bytes)
|
||||
debug_log(f"圖片大小限制已更新: {size_bytes} bytes")
|
||||
|
||||
def _on_base64_detail_changed(self, state: int) -> None:
|
||||
"""Base64 詳細模式變更處理"""
|
||||
@ -683,6 +685,9 @@ class ImageUploadWidget(QWidget):
|
||||
# 保存當前選擇
|
||||
current_data = self.size_limit_combo.currentData()
|
||||
|
||||
# 暫時斷開信號連接以避免觸發變更事件
|
||||
self.size_limit_combo.blockSignals(True)
|
||||
|
||||
# 清除並重新添加選項
|
||||
self.size_limit_combo.clear()
|
||||
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.unlimited'), 0)
|
||||
@ -696,6 +701,9 @@ class ImageUploadWidget(QWidget):
|
||||
self.size_limit_combo.setCurrentIndex(i)
|
||||
break
|
||||
|
||||
# 重新連接信號
|
||||
self.size_limit_combo.blockSignals(False)
|
||||
|
||||
if hasattr(self, 'base64_checkbox'):
|
||||
self.base64_checkbox.setText(t('images.settings.base64Detail'))
|
||||
self.base64_checkbox.setToolTip(t('images.settings.base64DetailHelp'))
|
||||
|
163
src/mcp_feedback_enhanced/gui/widgets/styled_spinbox.py
Normal file
163
src/mcp_feedback_enhanced/gui/widgets/styled_spinbox.py
Normal file
@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
自定義樣式的 QSpinBox
|
||||
==================
|
||||
|
||||
提供美觀的深色主題 QSpinBox,帶有自定義箭頭按鈕。
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import QSpinBox, QStyleOptionSpinBox, QStyle
|
||||
from PySide6.QtCore import QRect, Qt
|
||||
from PySide6.QtGui import QPainter, QPen, QBrush, QColor
|
||||
|
||||
|
||||
class StyledSpinBox(QSpinBox):
|
||||
"""自定義樣式的 QSpinBox"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._setup_style()
|
||||
|
||||
def _setup_style(self):
|
||||
"""設置基本樣式"""
|
||||
self.setStyleSheet("""
|
||||
QSpinBox {
|
||||
background-color: #3c3c3c;
|
||||
border: 1px solid #555555;
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
min-width: 100px;
|
||||
min-height: 24px;
|
||||
font-family: "Microsoft JhengHei", "微軟正黑體", sans-serif;
|
||||
}
|
||||
|
||||
QSpinBox:focus {
|
||||
border-color: #007acc;
|
||||
background-color: #404040;
|
||||
}
|
||||
|
||||
QSpinBox:hover {
|
||||
background-color: #404040;
|
||||
border-color: #666666;
|
||||
}
|
||||
|
||||
QSpinBox::up-button {
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: top right;
|
||||
width: 20px;
|
||||
border-left: 1px solid #555555;
|
||||
border-bottom: 1px solid #555555;
|
||||
border-top-right-radius: 5px;
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
|
||||
QSpinBox::up-button:hover {
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
|
||||
QSpinBox::up-button:pressed {
|
||||
background-color: #007acc;
|
||||
}
|
||||
|
||||
QSpinBox::down-button {
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: bottom right;
|
||||
width: 20px;
|
||||
border-left: 1px solid #555555;
|
||||
border-top: 1px solid #555555;
|
||||
border-bottom-right-radius: 5px;
|
||||
background-color: #4a4a4a;
|
||||
}
|
||||
|
||||
QSpinBox::down-button:hover {
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
|
||||
QSpinBox::down-button:pressed {
|
||||
background-color: #007acc;
|
||||
}
|
||||
|
||||
QSpinBox::up-arrow {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
QSpinBox::down-arrow {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
""")
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""重寫繪製事件以添加自定義箭頭"""
|
||||
# 先調用父類的繪製方法
|
||||
super().paintEvent(event)
|
||||
|
||||
# 創建畫筆
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
# 獲取按鈕區域
|
||||
opt = QStyleOptionSpinBox()
|
||||
self.initStyleOption(opt)
|
||||
|
||||
# 計算按鈕位置
|
||||
button_width = 20
|
||||
widget_rect = self.rect()
|
||||
|
||||
# 上箭頭按鈕區域
|
||||
up_rect = QRect(
|
||||
widget_rect.width() - button_width,
|
||||
1,
|
||||
button_width - 1,
|
||||
widget_rect.height() // 2 - 1
|
||||
)
|
||||
|
||||
# 下箭頭按鈕區域
|
||||
down_rect = QRect(
|
||||
widget_rect.width() - button_width,
|
||||
widget_rect.height() // 2,
|
||||
button_width - 1,
|
||||
widget_rect.height() // 2 - 1
|
||||
)
|
||||
|
||||
# 繪製上箭頭
|
||||
self._draw_arrow(painter, up_rect, True)
|
||||
|
||||
# 繪製下箭頭
|
||||
self._draw_arrow(painter, down_rect, False)
|
||||
|
||||
def _draw_arrow(self, painter: QPainter, rect: QRect, is_up: bool):
|
||||
"""繪製箭頭"""
|
||||
# 設置畫筆
|
||||
pen = QPen(QColor("#cccccc"), 1)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QBrush(QColor("#cccccc")))
|
||||
|
||||
# 計算箭頭位置
|
||||
center_x = rect.center().x()
|
||||
center_y = rect.center().y()
|
||||
arrow_size = 4
|
||||
|
||||
if is_up:
|
||||
# 上箭頭:▲
|
||||
points = [
|
||||
(center_x, center_y - arrow_size // 2), # 頂點
|
||||
(center_x - arrow_size, center_y + arrow_size // 2), # 左下
|
||||
(center_x + arrow_size, center_y + arrow_size // 2) # 右下
|
||||
]
|
||||
else:
|
||||
# 下箭頭:▼
|
||||
points = [
|
||||
(center_x, center_y + arrow_size // 2), # 底點
|
||||
(center_x - arrow_size, center_y - arrow_size // 2), # 左上
|
||||
(center_x + arrow_size, center_y - arrow_size // 2) # 右上
|
||||
]
|
||||
|
||||
# 繪製三角形
|
||||
from PySide6.QtCore import QPoint
|
||||
triangle = [QPoint(x, y) for x, y in points]
|
||||
painter.drawPolygon(triangle)
|
322
src/mcp_feedback_enhanced/gui/widgets/timeout_widget.py
Normal file
322
src/mcp_feedback_enhanced/gui/widgets/timeout_widget.py
Normal file
@ -0,0 +1,322 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
超時控制組件
|
||||
============
|
||||
|
||||
提供超時設置和倒數計時器顯示功能。
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QHBoxLayout, QVBoxLayout, QLabel,
|
||||
QSpinBox, QPushButton, QFrame
|
||||
)
|
||||
from PySide6.QtCore import Signal, QTimer, Qt
|
||||
from PySide6.QtGui import QFont
|
||||
|
||||
from .switch import SwitchWidget
|
||||
from ...i18n import t
|
||||
from ...debug import gui_debug_log as debug_log
|
||||
|
||||
|
||||
class TimeoutWidget(QWidget):
|
||||
"""超時控制組件"""
|
||||
|
||||
# 信號
|
||||
timeout_occurred = Signal() # 超時發生
|
||||
settings_changed = Signal(bool, int) # 設置變更 (enabled, timeout_seconds)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.timeout_enabled = False
|
||||
self.timeout_seconds = 600 # 預設 10 分鐘
|
||||
self.remaining_seconds = 0
|
||||
|
||||
# 計時器
|
||||
self.countdown_timer = QTimer()
|
||||
self.countdown_timer.timeout.connect(self._update_countdown)
|
||||
|
||||
self._setup_ui()
|
||||
self._connect_signals()
|
||||
|
||||
debug_log("超時控制組件初始化完成")
|
||||
|
||||
def _setup_ui(self):
|
||||
"""設置用戶介面"""
|
||||
# 主布局
|
||||
main_layout = QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(8, 4, 8, 4)
|
||||
main_layout.setSpacing(12)
|
||||
|
||||
# 超時開關區域
|
||||
switch_layout = QHBoxLayout()
|
||||
switch_layout.setSpacing(8)
|
||||
|
||||
self.timeout_label = QLabel(t('timeout.enable'))
|
||||
self.timeout_label.setStyleSheet("color: #cccccc; font-size: 12px;")
|
||||
switch_layout.addWidget(self.timeout_label)
|
||||
|
||||
self.timeout_switch = SwitchWidget()
|
||||
self.timeout_switch.setToolTip(t('timeout.enableTooltip'))
|
||||
switch_layout.addWidget(self.timeout_switch)
|
||||
|
||||
main_layout.addLayout(switch_layout)
|
||||
|
||||
# 分隔線
|
||||
separator = QFrame()
|
||||
separator.setFrameShape(QFrame.VLine)
|
||||
separator.setFrameShadow(QFrame.Sunken)
|
||||
separator.setStyleSheet("color: #464647;")
|
||||
main_layout.addWidget(separator)
|
||||
|
||||
# 超時時間設置區域
|
||||
time_layout = QHBoxLayout()
|
||||
time_layout.setSpacing(8)
|
||||
|
||||
self.time_label = QLabel(t('timeout.duration.label'))
|
||||
self.time_label.setStyleSheet("color: #cccccc; font-size: 12px;")
|
||||
time_layout.addWidget(self.time_label)
|
||||
|
||||
self.time_spinbox = QSpinBox()
|
||||
self.time_spinbox.setRange(30, 7200) # 30秒到2小時
|
||||
self.time_spinbox.setValue(600) # 預設10分鐘
|
||||
self.time_spinbox.setSuffix(" " + t('timeout.seconds'))
|
||||
# 應用自定義樣式
|
||||
style = self._get_spinbox_style(False)
|
||||
self.time_spinbox.setStyleSheet(style)
|
||||
debug_log("QSpinBox 樣式已應用")
|
||||
time_layout.addWidget(self.time_spinbox)
|
||||
|
||||
main_layout.addLayout(time_layout)
|
||||
|
||||
# 分隔線
|
||||
separator2 = QFrame()
|
||||
separator2.setFrameShape(QFrame.VLine)
|
||||
separator2.setFrameShadow(QFrame.Sunken)
|
||||
separator2.setStyleSheet("color: #464647;")
|
||||
main_layout.addWidget(separator2)
|
||||
|
||||
# 倒數計時器顯示區域
|
||||
countdown_layout = QHBoxLayout()
|
||||
countdown_layout.setSpacing(8)
|
||||
|
||||
self.countdown_label = QLabel(t('timeout.remaining'))
|
||||
self.countdown_label.setStyleSheet("color: #cccccc; font-size: 12px;")
|
||||
countdown_layout.addWidget(self.countdown_label)
|
||||
|
||||
self.countdown_display = QLabel("--:--")
|
||||
self.countdown_display.setStyleSheet("""
|
||||
color: #ffa500;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 50px;
|
||||
""")
|
||||
countdown_layout.addWidget(self.countdown_display)
|
||||
|
||||
main_layout.addLayout(countdown_layout)
|
||||
|
||||
# 彈性空間
|
||||
main_layout.addStretch()
|
||||
|
||||
# 初始狀態:隱藏倒數計時器
|
||||
self._update_visibility()
|
||||
|
||||
def _connect_signals(self):
|
||||
"""連接信號"""
|
||||
self.timeout_switch.toggled.connect(self._on_timeout_enabled_changed)
|
||||
self.time_spinbox.valueChanged.connect(self._on_timeout_duration_changed)
|
||||
|
||||
def _on_timeout_enabled_changed(self, enabled: bool):
|
||||
"""超時啟用狀態變更"""
|
||||
self.timeout_enabled = enabled
|
||||
self._update_visibility()
|
||||
|
||||
if enabled:
|
||||
self.start_countdown()
|
||||
else:
|
||||
self.stop_countdown()
|
||||
|
||||
self.settings_changed.emit(enabled, self.timeout_seconds)
|
||||
debug_log(f"超時功能已{'啟用' if enabled else '停用'}")
|
||||
|
||||
def _on_timeout_duration_changed(self, seconds: int):
|
||||
"""超時時間變更"""
|
||||
self.timeout_seconds = seconds
|
||||
|
||||
# 如果正在倒數,重新開始
|
||||
if self.timeout_enabled and self.countdown_timer.isActive():
|
||||
self.start_countdown()
|
||||
|
||||
self.settings_changed.emit(self.timeout_enabled, seconds)
|
||||
debug_log(f"超時時間設置為 {seconds} 秒")
|
||||
|
||||
def _update_visibility(self):
|
||||
"""更新組件可見性"""
|
||||
# 倒數計時器只在啟用超時時顯示
|
||||
self.countdown_label.setVisible(self.timeout_enabled)
|
||||
self.countdown_display.setVisible(self.timeout_enabled)
|
||||
|
||||
# 時間設置在啟用時更明顯
|
||||
style = self._get_spinbox_style(self.timeout_enabled)
|
||||
self.time_spinbox.setStyleSheet(style)
|
||||
debug_log(f"QSpinBox 樣式已更新 (啟用: {self.timeout_enabled})")
|
||||
|
||||
def start_countdown(self):
|
||||
"""開始倒數計時"""
|
||||
if not self.timeout_enabled:
|
||||
return
|
||||
|
||||
self.remaining_seconds = self.timeout_seconds
|
||||
self.countdown_timer.start(1000) # 每秒更新
|
||||
self._update_countdown_display()
|
||||
debug_log(f"開始倒數計時:{self.timeout_seconds} 秒")
|
||||
|
||||
def stop_countdown(self):
|
||||
"""停止倒數計時"""
|
||||
self.countdown_timer.stop()
|
||||
self.countdown_display.setText("--:--")
|
||||
debug_log("倒數計時已停止")
|
||||
|
||||
def _update_countdown(self):
|
||||
"""更新倒數計時"""
|
||||
self.remaining_seconds -= 1
|
||||
self._update_countdown_display()
|
||||
|
||||
if self.remaining_seconds <= 0:
|
||||
self.countdown_timer.stop()
|
||||
self.timeout_occurred.emit()
|
||||
debug_log("倒數計時結束,觸發超時事件")
|
||||
|
||||
def _update_countdown_display(self):
|
||||
"""更新倒數顯示"""
|
||||
if self.remaining_seconds <= 0:
|
||||
self.countdown_display.setText("00:00")
|
||||
self.countdown_display.setStyleSheet("""
|
||||
color: #ff4444;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 50px;
|
||||
""")
|
||||
else:
|
||||
minutes = self.remaining_seconds // 60
|
||||
seconds = self.remaining_seconds % 60
|
||||
time_text = f"{minutes:02d}:{seconds:02d}"
|
||||
self.countdown_display.setText(time_text)
|
||||
|
||||
# 根據剩餘時間調整顏色
|
||||
if self.remaining_seconds <= 60: # 最後1分鐘
|
||||
color = "#ff4444" # 紅色
|
||||
elif self.remaining_seconds <= 300: # 最後5分鐘
|
||||
color = "#ffaa00" # 橙色
|
||||
else:
|
||||
color = "#ffa500" # 黃色
|
||||
|
||||
self.countdown_display.setStyleSheet(f"""
|
||||
color: {color};
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 50px;
|
||||
""")
|
||||
|
||||
def set_timeout_settings(self, enabled: bool, seconds: int):
|
||||
"""設置超時參數"""
|
||||
self.timeout_switch.setChecked(enabled)
|
||||
self.time_spinbox.setValue(seconds)
|
||||
self.timeout_enabled = enabled
|
||||
self.timeout_seconds = seconds
|
||||
self._update_visibility()
|
||||
|
||||
def get_timeout_settings(self) -> tuple[bool, int]:
|
||||
"""獲取超時設置"""
|
||||
return self.timeout_enabled, self.timeout_seconds
|
||||
|
||||
def update_texts(self):
|
||||
"""更新界面文字(用於語言切換)"""
|
||||
self.timeout_label.setText(t('timeout.enable'))
|
||||
self.time_label.setText(t('timeout.duration.label'))
|
||||
self.countdown_label.setText(t('timeout.remaining'))
|
||||
self.timeout_switch.setToolTip(t('timeout.enableTooltip'))
|
||||
self.time_spinbox.setSuffix(" " + t('timeout.seconds'))
|
||||
|
||||
def _get_spinbox_style(self, enabled: bool) -> str:
|
||||
"""獲取 QSpinBox 的樣式字符串"""
|
||||
border_color = "#007acc" if enabled else "#555555"
|
||||
focus_color = "#0099ff" if enabled else "#007acc"
|
||||
|
||||
return f"""
|
||||
QSpinBox {{
|
||||
background-color: #3c3c3c;
|
||||
border: 1px solid {border_color};
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
min-width: 90px;
|
||||
min-height: 24px;
|
||||
}}
|
||||
|
||||
QSpinBox:focus {{
|
||||
border-color: {focus_color};
|
||||
background-color: #404040;
|
||||
}}
|
||||
|
||||
QSpinBox:hover {{
|
||||
background-color: #404040;
|
||||
border-color: #666666;
|
||||
}}
|
||||
|
||||
QSpinBox::up-button {{
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: top right;
|
||||
width: 18px;
|
||||
border-left: 1px solid #555555;
|
||||
border-bottom: 1px solid #555555;
|
||||
border-top-right-radius: 5px;
|
||||
background-color: #4a4a4a;
|
||||
}}
|
||||
|
||||
QSpinBox::up-button:hover {{
|
||||
background-color: #5a5a5a;
|
||||
}}
|
||||
|
||||
QSpinBox::up-button:pressed {{
|
||||
background-color: #007acc;
|
||||
}}
|
||||
|
||||
QSpinBox::down-button {{
|
||||
subcontrol-origin: border;
|
||||
subcontrol-position: bottom right;
|
||||
width: 18px;
|
||||
border-left: 1px solid #555555;
|
||||
border-top: 1px solid #555555;
|
||||
border-bottom-right-radius: 5px;
|
||||
background-color: #4a4a4a;
|
||||
}}
|
||||
|
||||
QSpinBox::down-button:hover {{
|
||||
background-color: #5a5a5a;
|
||||
}}
|
||||
|
||||
QSpinBox::down-button:pressed {{
|
||||
background-color: #007acc;
|
||||
}}
|
||||
|
||||
QSpinBox::up-arrow {{
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-bottom: 6px solid #cccccc;
|
||||
}}
|
||||
|
||||
QSpinBox::down-arrow {{
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 6px solid #cccccc;
|
||||
}}
|
||||
"""
|
@ -152,6 +152,10 @@ class ConfigManager:
|
||||
|
||||
def set_image_size_limit(self, size_bytes: int) -> None:
|
||||
"""設置圖片大小限制(bytes),0 表示無限制"""
|
||||
# 處理 None 值
|
||||
if size_bytes is None:
|
||||
size_bytes = 0
|
||||
|
||||
self.update_partial_config({'image_size_limit': size_bytes})
|
||||
size_mb = size_bytes / (1024 * 1024) if size_bytes > 0 else 0
|
||||
debug_log(f"圖片大小限制設置: {'無限制' if size_bytes == 0 else f'{size_mb:.1f}MB'}")
|
||||
@ -165,6 +169,36 @@ class ConfigManager:
|
||||
self.update_partial_config({'enable_base64_detail': enabled})
|
||||
debug_log(f"Base64 詳細模式設置: {'啟用' if enabled else '停用'}")
|
||||
|
||||
def get_timeout_enabled(self) -> bool:
|
||||
"""獲取是否啟用超時自動關閉"""
|
||||
return self.get('timeout_enabled', False)
|
||||
|
||||
def set_timeout_enabled(self, enabled: bool) -> None:
|
||||
"""設置是否啟用超時自動關閉"""
|
||||
self.update_partial_config({'timeout_enabled': enabled})
|
||||
debug_log(f"超時自動關閉設置: {'啟用' if enabled else '停用'}")
|
||||
|
||||
def get_timeout_duration(self) -> int:
|
||||
"""獲取超時時間(秒)"""
|
||||
return self.get('timeout_duration', 600) # 預設10分鐘
|
||||
|
||||
def set_timeout_duration(self, seconds: int) -> None:
|
||||
"""設置超時時間(秒)"""
|
||||
self.update_partial_config({'timeout_duration': seconds})
|
||||
debug_log(f"超時時間設置: {seconds} 秒")
|
||||
|
||||
def get_timeout_settings(self) -> tuple[bool, int]:
|
||||
"""獲取超時設置(啟用狀態, 超時時間)"""
|
||||
return self.get_timeout_enabled(), self.get_timeout_duration()
|
||||
|
||||
def set_timeout_settings(self, enabled: bool, seconds: int) -> None:
|
||||
"""設置超時設置"""
|
||||
self.update_partial_config({
|
||||
'timeout_enabled': enabled,
|
||||
'timeout_duration': seconds
|
||||
})
|
||||
debug_log(f"超時設置: {'啟用' if enabled else '停用'}, {seconds} 秒")
|
||||
|
||||
def reset_settings(self) -> None:
|
||||
"""重置所有設定到預設值"""
|
||||
try:
|
||||
|
@ -24,13 +24,15 @@ from ...debug import gui_debug_log as debug_log
|
||||
class FeedbackWindow(QMainWindow):
|
||||
"""回饋收集主窗口(重構版)"""
|
||||
language_changed = Signal()
|
||||
timeout_occurred = Signal() # 超時發生信號
|
||||
|
||||
def __init__(self, project_dir: str, summary: str):
|
||||
def __init__(self, project_dir: str, summary: str, timeout_seconds: int = None):
|
||||
super().__init__()
|
||||
self.project_dir = project_dir
|
||||
self.summary = summary
|
||||
self.result = None
|
||||
self.i18n = get_i18n_manager()
|
||||
self.mcp_timeout_seconds = timeout_seconds # MCP 傳入的超時時間
|
||||
|
||||
# 初始化組件
|
||||
self.config_manager = ConfigManager()
|
||||
@ -56,6 +58,9 @@ class FeedbackWindow(QMainWindow):
|
||||
|
||||
debug_log("主窗口初始化完成")
|
||||
|
||||
# 如果啟用了超時,自動開始倒數計時
|
||||
self.start_timeout_if_enabled()
|
||||
|
||||
def _setup_ui(self) -> None:
|
||||
"""設置用戶介面"""
|
||||
self.setWindowTitle(t('app.title'))
|
||||
@ -88,9 +93,72 @@ class FeedbackWindow(QMainWindow):
|
||||
|
||||
def _create_project_header(self, layout: QVBoxLayout) -> None:
|
||||
"""創建專案目錄頭部信息"""
|
||||
# 創建水平布局來放置專案目錄和倒數計時器
|
||||
header_layout = QHBoxLayout()
|
||||
|
||||
self.project_label = QLabel(f"{t('app.projectDirectory')}: {self.project_dir}")
|
||||
self.project_label.setStyleSheet("color: #9e9e9e; font-size: 12px; padding: 4px 0;")
|
||||
layout.addWidget(self.project_label)
|
||||
header_layout.addWidget(self.project_label)
|
||||
|
||||
# 添加彈性空間
|
||||
header_layout.addStretch()
|
||||
|
||||
# 添加倒數計時器顯示(僅顯示部分)
|
||||
self._create_countdown_display(header_layout)
|
||||
|
||||
# 將水平布局添加到主布局
|
||||
header_widget = QWidget()
|
||||
header_widget.setLayout(header_layout)
|
||||
layout.addWidget(header_widget)
|
||||
|
||||
def _create_countdown_display(self, layout: QHBoxLayout) -> None:
|
||||
"""創建倒數計時器顯示組件(僅顯示)"""
|
||||
# 倒數計時器標籤
|
||||
self.countdown_label = QLabel(t('timeout.remaining'))
|
||||
self.countdown_label.setStyleSheet("color: #cccccc; font-size: 12px;")
|
||||
self.countdown_label.setVisible(False) # 預設隱藏
|
||||
layout.addWidget(self.countdown_label)
|
||||
|
||||
# 倒數計時器顯示
|
||||
self.countdown_display = QLabel("--:--")
|
||||
self.countdown_display.setStyleSheet("""
|
||||
color: #ffa500;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 50px;
|
||||
margin-left: 8px;
|
||||
""")
|
||||
self.countdown_display.setVisible(False) # 預設隱藏
|
||||
layout.addWidget(self.countdown_display)
|
||||
|
||||
# 初始化超時控制邏輯
|
||||
self._init_timeout_logic()
|
||||
|
||||
def _init_timeout_logic(self) -> None:
|
||||
"""初始化超時控制邏輯"""
|
||||
# 載入保存的超時設置
|
||||
timeout_enabled, timeout_duration = self.config_manager.get_timeout_settings()
|
||||
|
||||
# 如果有 MCP 超時參數,且用戶設置的時間大於 MCP 時間,則使用 MCP 時間
|
||||
if self.mcp_timeout_seconds is not None:
|
||||
if timeout_duration > self.mcp_timeout_seconds:
|
||||
timeout_duration = self.mcp_timeout_seconds
|
||||
debug_log(f"用戶設置的超時時間 ({timeout_duration}s) 大於 MCP 超時時間 ({self.mcp_timeout_seconds}s),使用 MCP 時間")
|
||||
|
||||
# 保存超時設置
|
||||
self.timeout_enabled = timeout_enabled
|
||||
self.timeout_duration = timeout_duration
|
||||
self.remaining_seconds = 0
|
||||
|
||||
# 創建計時器
|
||||
self.countdown_timer = QTimer()
|
||||
self.countdown_timer.timeout.connect(self._update_countdown)
|
||||
|
||||
# 更新顯示狀態
|
||||
self._update_countdown_visibility()
|
||||
|
||||
|
||||
|
||||
def _create_tab_area(self, layout: QVBoxLayout) -> None:
|
||||
"""創建分頁區域"""
|
||||
@ -170,6 +238,9 @@ class FeedbackWindow(QMainWindow):
|
||||
# 創建分頁
|
||||
self.tab_manager.create_tabs()
|
||||
|
||||
# 連接分頁信號
|
||||
self.tab_manager.connect_signals(self)
|
||||
|
||||
# 將分頁組件放入滾動區域
|
||||
scroll_area.setWidget(self.tab_widget)
|
||||
|
||||
@ -458,6 +529,125 @@ class FeedbackWindow(QMainWindow):
|
||||
self.result = ""
|
||||
self.close()
|
||||
|
||||
def _on_timeout_occurred(self) -> None:
|
||||
"""處理超時事件"""
|
||||
debug_log("用戶設置的超時時間已到,自動關閉視窗")
|
||||
self._timeout_occurred = True
|
||||
self.timeout_occurred.emit()
|
||||
self.force_close()
|
||||
|
||||
def start_timeout_if_enabled(self) -> None:
|
||||
"""如果啟用了超時,自動開始倒數計時"""
|
||||
if hasattr(self, 'tab_manager') and self.tab_manager:
|
||||
timeout_widget = self.tab_manager.get_timeout_widget()
|
||||
if timeout_widget:
|
||||
enabled, _ = timeout_widget.get_timeout_settings()
|
||||
if enabled:
|
||||
timeout_widget.start_countdown()
|
||||
debug_log("窗口顯示時自動開始倒數計時")
|
||||
|
||||
def _on_timeout_settings_changed(self, enabled: bool, seconds: int) -> None:
|
||||
"""處理超時設置變更(從設置頁籤觸發)"""
|
||||
# 檢查是否超過 MCP 超時限制
|
||||
if self.mcp_timeout_seconds is not None and seconds > self.mcp_timeout_seconds:
|
||||
debug_log(f"用戶設置的超時時間 ({seconds}s) 超過 MCP 限制 ({self.mcp_timeout_seconds}s),調整為 MCP 時間")
|
||||
seconds = self.mcp_timeout_seconds
|
||||
|
||||
# 更新內部狀態
|
||||
self.timeout_enabled = enabled
|
||||
self.timeout_duration = seconds
|
||||
|
||||
# 保存設置
|
||||
self.config_manager.set_timeout_settings(enabled, seconds)
|
||||
debug_log(f"超時設置已更新: {'啟用' if enabled else '停用'}, {seconds} 秒")
|
||||
|
||||
# 更新倒數計時器顯示
|
||||
self._update_countdown_visibility()
|
||||
|
||||
# 重新開始倒數計時
|
||||
if enabled:
|
||||
self.start_countdown()
|
||||
else:
|
||||
self.stop_countdown()
|
||||
|
||||
def start_timeout_if_enabled(self) -> None:
|
||||
"""如果啟用了超時,開始倒數計時"""
|
||||
if self.timeout_enabled:
|
||||
self.start_countdown()
|
||||
debug_log("超時倒數計時已開始")
|
||||
|
||||
def stop_timeout(self) -> None:
|
||||
"""停止超時倒數計時"""
|
||||
self.stop_countdown()
|
||||
debug_log("超時倒數計時已停止")
|
||||
|
||||
def start_countdown(self) -> None:
|
||||
"""開始倒數計時"""
|
||||
if not self.timeout_enabled:
|
||||
return
|
||||
|
||||
self.remaining_seconds = self.timeout_duration
|
||||
self.countdown_timer.start(1000) # 每秒更新
|
||||
self._update_countdown_display()
|
||||
debug_log(f"開始倒數計時:{self.timeout_duration} 秒")
|
||||
|
||||
def stop_countdown(self) -> None:
|
||||
"""停止倒數計時"""
|
||||
self.countdown_timer.stop()
|
||||
self.countdown_display.setText("--:--")
|
||||
debug_log("倒數計時已停止")
|
||||
|
||||
def _update_countdown(self) -> None:
|
||||
"""更新倒數計時"""
|
||||
self.remaining_seconds -= 1
|
||||
self._update_countdown_display()
|
||||
|
||||
if self.remaining_seconds <= 0:
|
||||
self.countdown_timer.stop()
|
||||
self._on_timeout_occurred()
|
||||
debug_log("倒數計時結束,觸發超時事件")
|
||||
|
||||
def _update_countdown_display(self) -> None:
|
||||
"""更新倒數顯示"""
|
||||
if self.remaining_seconds <= 0:
|
||||
self.countdown_display.setText("00:00")
|
||||
self.countdown_display.setStyleSheet("""
|
||||
color: #ff4444;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 50px;
|
||||
margin-left: 8px;
|
||||
""")
|
||||
else:
|
||||
minutes = self.remaining_seconds // 60
|
||||
seconds = self.remaining_seconds % 60
|
||||
time_text = f"{minutes:02d}:{seconds:02d}"
|
||||
self.countdown_display.setText(time_text)
|
||||
|
||||
# 根據剩餘時間調整顏色
|
||||
if self.remaining_seconds <= 60: # 最後1分鐘
|
||||
color = "#ff4444" # 紅色
|
||||
elif self.remaining_seconds <= 300: # 最後5分鐘
|
||||
color = "#ffaa00" # 橙色
|
||||
else:
|
||||
color = "#ffa500" # 黃色
|
||||
|
||||
self.countdown_display.setStyleSheet(f"""
|
||||
color: {color};
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 50px;
|
||||
margin-left: 8px;
|
||||
""")
|
||||
|
||||
def _update_countdown_visibility(self) -> None:
|
||||
"""更新倒數計時器可見性"""
|
||||
# 倒數計時器只在啟用超時時顯示
|
||||
self.countdown_label.setVisible(self.timeout_enabled)
|
||||
self.countdown_display.setVisible(self.timeout_enabled)
|
||||
|
||||
def _refresh_ui_texts(self) -> None:
|
||||
"""刷新界面文字"""
|
||||
self.setWindowTitle(t('app.title'))
|
||||
@ -467,6 +657,10 @@ class FeedbackWindow(QMainWindow):
|
||||
self.submit_button.setText(t('buttons.submit'))
|
||||
self.cancel_button.setText(t('buttons.cancel'))
|
||||
|
||||
# 更新倒數計時器文字
|
||||
if hasattr(self, 'countdown_label'):
|
||||
self.countdown_label.setText(t('timeout.remaining'))
|
||||
|
||||
# 更新分頁文字
|
||||
self.tab_manager.update_tab_texts()
|
||||
|
||||
|
@ -327,6 +327,8 @@ class TabManager:
|
||||
self.settings_tab.layout_change_requested.connect(parent._on_layout_change_requested)
|
||||
if hasattr(parent, '_on_reset_settings_requested'):
|
||||
self.settings_tab.reset_requested.connect(parent._on_reset_settings_requested)
|
||||
if hasattr(parent, '_on_timeout_settings_changed'):
|
||||
self.settings_tab.timeout_settings_changed.connect(parent._on_timeout_settings_changed)
|
||||
|
||||
# 連接回饋分頁的圖片貼上信號
|
||||
if self.feedback_tab:
|
||||
|
@ -130,6 +130,22 @@
|
||||
"errorMessage": "Error occurred while resetting settings: {error}"
|
||||
}
|
||||
},
|
||||
"timeout": {
|
||||
"enable": "Auto Close",
|
||||
"enableTooltip": "When enabled, the interface will automatically close after the specified time",
|
||||
"duration": {
|
||||
"label": "Timeout Duration",
|
||||
"description": "Set the auto-close time (30 seconds - 2 hours)"
|
||||
},
|
||||
"seconds": "seconds",
|
||||
"remaining": "Time Remaining",
|
||||
"expired": "Time Expired",
|
||||
"autoCloseMessage": "Interface will automatically close in {seconds} seconds",
|
||||
"settings": {
|
||||
"title": "Timeout Settings",
|
||||
"description": "When enabled, the interface will automatically close after the specified time. The countdown timer will be displayed in the header area."
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"submit": "Submit Feedback",
|
||||
"cancel": "Cancel",
|
||||
|
@ -110,6 +110,22 @@
|
||||
"errorMessage": "重置设置时发生错误:{error}"
|
||||
}
|
||||
},
|
||||
"timeout": {
|
||||
"enable": "自动关闭",
|
||||
"enableTooltip": "启用后将在指定时间后自动关闭界面",
|
||||
"duration": {
|
||||
"label": "超时时间",
|
||||
"description": "设置自动关闭的时间(30秒 - 2小时)"
|
||||
},
|
||||
"seconds": "秒",
|
||||
"remaining": "剩余时间",
|
||||
"expired": "时间已到",
|
||||
"autoCloseMessage": "界面将在 {seconds} 秒后自动关闭",
|
||||
"settings": {
|
||||
"title": "超时设置",
|
||||
"description": "启用后,界面将在指定时间后自动关闭。倒数计时器会显示在顶部区域。"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"submit": "提交反馈",
|
||||
"cancel": "取消",
|
||||
|
@ -96,6 +96,22 @@
|
||||
"sizeLimitExceeded": "圖片 {filename} 大小為 {size},超過 {limit} 限制!",
|
||||
"sizeLimitExceededAdvice": "建議使用圖片編輯軟體壓縮後再上傳,或調整圖片大小限制設定。"
|
||||
},
|
||||
"timeout": {
|
||||
"enable": "自動關閉",
|
||||
"enableTooltip": "啟用後將在指定時間後自動關閉介面",
|
||||
"duration": {
|
||||
"label": "超時時間",
|
||||
"description": "設置自動關閉的時間(30秒 - 2小時)"
|
||||
},
|
||||
"seconds": "秒",
|
||||
"remaining": "剩餘時間",
|
||||
"expired": "時間已到",
|
||||
"autoCloseMessage": "介面將在 {seconds} 秒後自動關閉",
|
||||
"settings": {
|
||||
"title": "超時設置",
|
||||
"description": "啟用後,介面將在指定時間後自動關閉。倒數計時器會顯示在頂部區域。"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "應用設置",
|
||||
"language": {
|
||||
|
@ -519,7 +519,7 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
||||
|
||||
try:
|
||||
# 使用新的 web 模組
|
||||
from .web import launch_web_feedback_ui
|
||||
from .web import launch_web_feedback_ui, stop_web_ui
|
||||
|
||||
# 傳遞 timeout 參數給 Web UI
|
||||
return await launch_web_feedback_ui(project_dir, summary, timeout)
|
||||
@ -532,6 +532,14 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
||||
}
|
||||
except TimeoutError as e:
|
||||
debug_log(f"Web UI 超時: {e}")
|
||||
# 超時時確保停止 Web 服務器
|
||||
try:
|
||||
from .web import stop_web_ui
|
||||
stop_web_ui()
|
||||
debug_log("Web UI 服務器已因超時而停止")
|
||||
except Exception as stop_error:
|
||||
debug_log(f"停止 Web UI 服務器時發生錯誤: {stop_error}")
|
||||
|
||||
return {
|
||||
"command_logs": "",
|
||||
"interactive_feedback": f"回饋收集超時({timeout}秒),介面已自動關閉。",
|
||||
@ -540,6 +548,14 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in
|
||||
except Exception as e:
|
||||
error_msg = f"Web UI 錯誤: {e}"
|
||||
debug_log(f"❌ {error_msg}")
|
||||
# 發生錯誤時也要停止 Web 服務器
|
||||
try:
|
||||
from .web import stop_web_ui
|
||||
stop_web_ui()
|
||||
debug_log("Web UI 服務器已因錯誤而停止")
|
||||
except Exception as stop_error:
|
||||
debug_log(f"停止 Web UI 服務器時發生錯誤: {stop_error}")
|
||||
|
||||
return {
|
||||
"command_logs": "",
|
||||
"interactive_feedback": f"錯誤: {str(e)}",
|
||||
|
@ -153,6 +153,22 @@
|
||||
"timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.",
|
||||
"closing": "Closing..."
|
||||
},
|
||||
"timeout": {
|
||||
"enable": "Auto Close",
|
||||
"enableTooltip": "When enabled, the interface will automatically close after the specified time",
|
||||
"duration": {
|
||||
"label": "Timeout Duration",
|
||||
"description": "Set the auto-close time (30 seconds - 2 hours)"
|
||||
},
|
||||
"seconds": "seconds",
|
||||
"remaining": "Time Remaining",
|
||||
"expired": "⏰ Time expired, interface will close automatically",
|
||||
"autoCloseMessage": "Interface will automatically close in {seconds} seconds",
|
||||
"settings": {
|
||||
"title": "Timeout Settings",
|
||||
"description": "When enabled, the interface will automatically close after the specified time. The countdown timer will be displayed in the header area."
|
||||
}
|
||||
},
|
||||
"dynamic": {
|
||||
"aiSummary": "Test Web UI Functionality\n\n🎯 **Test Items:**\n- Web UI server startup and operation\n- WebSocket real-time communication\n- Feedback submission functionality\n- Image upload and preview\n- Command execution functionality\n- Smart Ctrl+V image pasting\n- Multi-language interface functionality\n\n📋 **Test Steps:**\n1. Test image upload (drag-drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart pasting\n3. Try switching languages (Traditional Chinese/Simplified Chinese/English)\n4. Test command execution functionality\n5. Submit feedback and images\n\nPlease test these features and provide feedback!",
|
||||
"terminalWelcome": "Welcome to Interactive Feedback Terminal\n========================================\nProject Directory: {sessionId}\nEnter commands and press Enter or click Execute button\nSupported commands: ls, dir, pwd, cat, type, etc.\n\n$ "
|
||||
|
@ -153,6 +153,22 @@
|
||||
"timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。",
|
||||
"closing": "正在关闭..."
|
||||
},
|
||||
"timeout": {
|
||||
"enable": "自动关闭",
|
||||
"enableTooltip": "启用后将在指定时间后自动关闭界面",
|
||||
"duration": {
|
||||
"label": "超时时间",
|
||||
"description": "设置自动关闭的时间(30秒 - 2小时)"
|
||||
},
|
||||
"seconds": "秒",
|
||||
"remaining": "剩余时间",
|
||||
"expired": "⏰ 时间已到,界面将自动关闭",
|
||||
"autoCloseMessage": "界面将在 {seconds} 秒后自动关闭",
|
||||
"settings": {
|
||||
"title": "超时设置",
|
||||
"description": "启用后,界面将在指定时间后自动关闭。倒数计时器会显示在顶部区域。"
|
||||
}
|
||||
},
|
||||
"dynamic": {
|
||||
"aiSummary": "测试 Web UI 功能\n\n🎯 **功能测试项目:**\n- Web UI 服务器启动和运行\n- WebSocket 实时通讯\n- 反馈提交功能\n- 图片上传和预览\n- 命令执行功能\n- 智能 Ctrl+V 图片粘贴\n- 多语言界面功能\n\n📋 **测试步骤:**\n1. 测试图片上传(拖拽、选择文件、剪贴板)\n2. 在文本框内按 Ctrl+V 测试智能粘贴\n3. 尝试切换语言(繁中/简中/英文)\n4. 测试命令执行功能\n5. 提交反馈和图片\n\n请测试这些功能并提供反馈!",
|
||||
"terminalWelcome": "欢迎使用交互反馈终端\n========================================\n项目目录: {sessionId}\n输入命令后按 Enter 或点击执行按钮\n支持的命令: ls, dir, pwd, cat, type 等\n\n$ "
|
||||
|
@ -153,6 +153,22 @@
|
||||
"timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。",
|
||||
"closing": "正在關閉..."
|
||||
},
|
||||
"timeout": {
|
||||
"enable": "自動關閉",
|
||||
"enableTooltip": "啟用後將在指定時間後自動關閉介面",
|
||||
"duration": {
|
||||
"label": "超時時間",
|
||||
"description": "設置自動關閉的時間(30秒 - 2小時)"
|
||||
},
|
||||
"seconds": "秒",
|
||||
"remaining": "剩餘時間",
|
||||
"expired": "⏰ 時間已到,介面將自動關閉",
|
||||
"autoCloseMessage": "介面將在 {seconds} 秒後自動關閉",
|
||||
"settings": {
|
||||
"title": "超時設置",
|
||||
"description": "啟用後,介面將在指定時間後自動關閉。倒數計時器會顯示在頂部區域。"
|
||||
}
|
||||
},
|
||||
"dynamic": {
|
||||
"aiSummary": "測試 Web UI 功能\n\n🎯 **功能測試項目:**\n- Web UI 服務器啟動和運行\n- WebSocket 即時通訊\n- 回饋提交功能\n- 圖片上傳和預覽\n- 命令執行功能\n- 智能 Ctrl+V 圖片貼上\n- 多語言介面功能\n\n📋 **測試步驟:**\n1. 測試圖片上傳(拖拽、選擇檔案、剪貼簿)\n2. 在文字框內按 Ctrl+V 測試智能貼上\n3. 嘗試切換語言(繁中/簡中/英文)\n4. 測試命令執行功能\n5. 提交回饋和圖片\n\n請測試這些功能並提供回饋!",
|
||||
"terminalWelcome": "歡迎使用互動回饋終端\n========================================\n專案目錄: {sessionId}\n輸入命令後按 Enter 或點擊執行按鈕\n支援的命令: ls, dir, pwd, cat, type 等\n\n$ "
|
||||
|
@ -218,6 +218,10 @@ async def launch_web_feedback_ui(project_directory: str, summary: str, timeout:
|
||||
finally:
|
||||
# 清理會話(無論成功還是失敗)
|
||||
manager.remove_session(session_id)
|
||||
# 如果沒有其他活躍會話,停止服務器
|
||||
if len(manager.sessions) == 0:
|
||||
debug_log("沒有活躍會話,停止 Web UI 服務器")
|
||||
stop_web_ui()
|
||||
|
||||
|
||||
def stop_web_ui():
|
||||
@ -236,21 +240,22 @@ if __name__ == "__main__":
|
||||
project_dir = os.getcwd()
|
||||
summary = "這是一個測試摘要,用於驗證 Web UI 功能。"
|
||||
|
||||
print(f"啟動 Web UI 測試...")
|
||||
print(f"專案目錄: {project_dir}")
|
||||
print("等待用戶回饋...")
|
||||
from ..debug import debug_log
|
||||
debug_log(f"啟動 Web UI 測試...")
|
||||
debug_log(f"專案目錄: {project_dir}")
|
||||
debug_log("等待用戶回饋...")
|
||||
|
||||
result = await launch_web_feedback_ui(project_dir, summary)
|
||||
|
||||
print("收到回饋結果:")
|
||||
print(f"命令日誌: {result.get('logs', '')}")
|
||||
print(f"互動回饋: {result.get('interactive_feedback', '')}")
|
||||
print(f"圖片數量: {len(result.get('images', []))}")
|
||||
debug_log("收到回饋結果:")
|
||||
debug_log(f"命令日誌: {result.get('logs', '')}")
|
||||
debug_log(f"互動回饋: {result.get('interactive_feedback', '')}")
|
||||
debug_log(f"圖片數量: {len(result.get('images', []))}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用戶取消操作")
|
||||
debug_log("\n用戶取消操作")
|
||||
except Exception as e:
|
||||
print(f"錯誤: {e}")
|
||||
debug_log(f"錯誤: {e}")
|
||||
finally:
|
||||
stop_web_ui()
|
||||
|
||||
|
@ -199,5 +199,26 @@ async def handle_websocket_message(manager: 'WebUIManager', session, data: dict)
|
||||
if command.strip():
|
||||
await session.run_command(command)
|
||||
|
||||
elif message_type == "user_timeout":
|
||||
# 用戶設置的超時已到
|
||||
debug_log(f"收到用戶超時通知: {session.session_id}")
|
||||
# 清理會話資源
|
||||
await session._cleanup_resources_on_timeout()
|
||||
# 如果沒有其他活躍會話,停止服務器
|
||||
if len(manager.sessions) <= 1: # 當前會話即將被移除
|
||||
debug_log("用戶超時,沒有其他活躍會話,準備停止服務器")
|
||||
# 延遲停止服務器,給前端時間關閉
|
||||
import asyncio
|
||||
asyncio.create_task(_delayed_server_stop(manager))
|
||||
|
||||
else:
|
||||
debug_log(f"未知的消息類型: {message_type}")
|
||||
|
||||
|
||||
async def _delayed_server_stop(manager: 'WebUIManager'):
|
||||
"""延遲停止服務器"""
|
||||
import asyncio
|
||||
await asyncio.sleep(5) # 等待 5 秒讓前端有時間關閉
|
||||
from ..main import stop_web_ui
|
||||
stop_web_ui()
|
||||
debug_log("Web UI 服務器已因用戶超時而停止")
|
@ -96,6 +96,13 @@ class FeedbackApp {
|
||||
this.imageSizeLimit = 0; // 0 表示無限制
|
||||
this.enableBase64Detail = false;
|
||||
|
||||
// 超時設定
|
||||
this.timeoutEnabled = false;
|
||||
this.timeoutDuration = 600; // 預設 10 分鐘
|
||||
this.timeoutTimer = null;
|
||||
this.countdownTimer = null;
|
||||
this.remainingSeconds = 0;
|
||||
|
||||
// 立即檢查 DOM 狀態並初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@ -140,6 +147,12 @@ class FeedbackApp {
|
||||
// 確保合併模式狀態正確
|
||||
this.applyCombinedModeState();
|
||||
|
||||
// 初始化超時控制
|
||||
this.setupTimeoutControl();
|
||||
|
||||
// 如果啟用了超時,自動開始倒數計時(在設置載入後)
|
||||
this.startTimeoutIfEnabled();
|
||||
|
||||
console.log('FeedbackApp 初始化完成');
|
||||
}
|
||||
|
||||
@ -1122,6 +1135,22 @@ class FeedbackApp {
|
||||
this.enableBase64Detail = false; // 預設關閉
|
||||
}
|
||||
|
||||
// 載入超時設定
|
||||
if (settings.timeoutEnabled !== undefined) {
|
||||
this.timeoutEnabled = settings.timeoutEnabled;
|
||||
} else {
|
||||
this.timeoutEnabled = false; // 預設關閉
|
||||
}
|
||||
|
||||
if (settings.timeoutDuration !== undefined) {
|
||||
this.timeoutDuration = settings.timeoutDuration;
|
||||
} else {
|
||||
this.timeoutDuration = 600; // 預設 10 分鐘
|
||||
}
|
||||
|
||||
// 更新超時 UI
|
||||
this.updateTimeoutUI();
|
||||
|
||||
// 同步圖片設定到 UI
|
||||
this.syncImageSettings();
|
||||
|
||||
@ -1213,6 +1242,8 @@ $ `;
|
||||
this.autoClose = true;
|
||||
this.imageSizeLimit = 0;
|
||||
this.enableBase64Detail = false;
|
||||
this.timeoutEnabled = false;
|
||||
this.timeoutDuration = 600;
|
||||
|
||||
// 更新佈局模式單選按鈕狀態
|
||||
const layoutRadios = document.querySelectorAll('input[name="layoutMode"]');
|
||||
@ -1229,6 +1260,10 @@ $ `;
|
||||
// 同步圖片設定到 UI
|
||||
this.syncImageSettings();
|
||||
|
||||
// 更新超時 UI
|
||||
this.updateTimeoutUI();
|
||||
this.stopTimeout();
|
||||
|
||||
// 確保語言選擇器與當前語言同步
|
||||
this.syncLanguageSelector();
|
||||
|
||||
@ -1307,6 +1342,210 @@ $ `;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
setupTimeoutControl() {
|
||||
// 設置超時開關監聽器
|
||||
const timeoutToggle = document.getElementById('timeoutToggle');
|
||||
if (timeoutToggle) {
|
||||
timeoutToggle.addEventListener('click', () => {
|
||||
this.toggleTimeout();
|
||||
});
|
||||
}
|
||||
|
||||
// 設置超時時間輸入監聽器
|
||||
const timeoutDuration = document.getElementById('timeoutDuration');
|
||||
if (timeoutDuration) {
|
||||
timeoutDuration.addEventListener('change', (e) => {
|
||||
this.setTimeoutDuration(parseInt(e.target.value));
|
||||
});
|
||||
}
|
||||
|
||||
// 更新界面狀態
|
||||
this.updateTimeoutUI();
|
||||
}
|
||||
|
||||
startTimeoutIfEnabled() {
|
||||
// 如果啟用了超時,自動開始倒數計時
|
||||
if (this.timeoutEnabled) {
|
||||
this.startTimeout();
|
||||
console.log('頁面載入時自動開始倒數計時');
|
||||
}
|
||||
}
|
||||
|
||||
toggleTimeout() {
|
||||
this.timeoutEnabled = !this.timeoutEnabled;
|
||||
this.updateTimeoutUI();
|
||||
this.saveSettings();
|
||||
|
||||
if (this.timeoutEnabled) {
|
||||
this.startTimeout();
|
||||
} else {
|
||||
this.stopTimeout();
|
||||
}
|
||||
|
||||
console.log('超時功能已', this.timeoutEnabled ? '啟用' : '停用');
|
||||
}
|
||||
|
||||
setTimeoutDuration(seconds) {
|
||||
if (seconds >= 30 && seconds <= 7200) {
|
||||
this.timeoutDuration = seconds;
|
||||
this.saveSettings();
|
||||
|
||||
// 如果正在倒數,重新開始
|
||||
if (this.timeoutEnabled && this.timeoutTimer) {
|
||||
this.startTimeout();
|
||||
}
|
||||
|
||||
console.log('超時時間設置為', seconds, '秒');
|
||||
}
|
||||
}
|
||||
|
||||
updateTimeoutUI() {
|
||||
const timeoutToggle = document.getElementById('timeoutToggle');
|
||||
const timeoutDuration = document.getElementById('timeoutDuration');
|
||||
const countdownDisplay = document.getElementById('countdownDisplay');
|
||||
|
||||
if (timeoutToggle) {
|
||||
timeoutToggle.classList.toggle('active', this.timeoutEnabled);
|
||||
}
|
||||
|
||||
if (timeoutDuration) {
|
||||
timeoutDuration.value = this.timeoutDuration;
|
||||
}
|
||||
|
||||
if (countdownDisplay) {
|
||||
countdownDisplay.style.display = this.timeoutEnabled ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
startTimeout() {
|
||||
this.stopTimeout(); // 先停止現有的計時器
|
||||
|
||||
this.remainingSeconds = this.timeoutDuration;
|
||||
|
||||
// 開始主要的超時計時器
|
||||
this.timeoutTimer = setTimeout(() => {
|
||||
this.handleTimeout();
|
||||
}, this.timeoutDuration * 1000);
|
||||
|
||||
// 開始倒數顯示計時器
|
||||
this.countdownTimer = setInterval(() => {
|
||||
this.updateCountdownDisplay();
|
||||
}, 1000);
|
||||
|
||||
this.updateCountdownDisplay();
|
||||
console.log('開始倒數計時:', this.timeoutDuration, '秒');
|
||||
}
|
||||
|
||||
stopTimeout() {
|
||||
if (this.timeoutTimer) {
|
||||
clearTimeout(this.timeoutTimer);
|
||||
this.timeoutTimer = null;
|
||||
}
|
||||
|
||||
if (this.countdownTimer) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = null;
|
||||
}
|
||||
|
||||
const countdownTimer = document.getElementById('countdownTimer');
|
||||
if (countdownTimer) {
|
||||
countdownTimer.textContent = '--:--';
|
||||
countdownTimer.className = 'countdown-timer';
|
||||
}
|
||||
|
||||
console.log('倒數計時已停止');
|
||||
}
|
||||
|
||||
updateCountdownDisplay() {
|
||||
this.remainingSeconds--;
|
||||
|
||||
const countdownTimer = document.getElementById('countdownTimer');
|
||||
if (countdownTimer) {
|
||||
if (this.remainingSeconds <= 0) {
|
||||
countdownTimer.textContent = '00:00';
|
||||
countdownTimer.className = 'countdown-timer danger';
|
||||
} else {
|
||||
const minutes = Math.floor(this.remainingSeconds / 60);
|
||||
const seconds = this.remainingSeconds % 60;
|
||||
const timeText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
countdownTimer.textContent = timeText;
|
||||
|
||||
// 根據剩餘時間調整樣式
|
||||
if (this.remainingSeconds <= 60) {
|
||||
countdownTimer.className = 'countdown-timer danger';
|
||||
} else if (this.remainingSeconds <= 300) {
|
||||
countdownTimer.className = 'countdown-timer warning';
|
||||
} else {
|
||||
countdownTimer.className = 'countdown-timer';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.remainingSeconds <= 0) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleTimeout() {
|
||||
console.log('用戶設置的超時時間已到,自動關閉介面');
|
||||
|
||||
// 通知後端用戶超時
|
||||
this.notifyUserTimeout();
|
||||
|
||||
// 顯示超時訊息
|
||||
const timeoutMessage = window.i18nManager ?
|
||||
window.i18nManager.t('timeout.expired', '⏰ 時間已到,介面將自動關閉') :
|
||||
'⏰ 時間已到,介面將自動關閉';
|
||||
|
||||
this.showMessage(timeoutMessage, 'warning');
|
||||
|
||||
// 禁用所有互動元素
|
||||
this.disableAllInputs();
|
||||
|
||||
// 3秒後自動關閉頁面
|
||||
setTimeout(() => {
|
||||
try {
|
||||
window.close();
|
||||
} catch (e) {
|
||||
console.log('無法關閉視窗,重新載入頁面');
|
||||
window.location.reload();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
notifyUserTimeout() {
|
||||
// 通過 WebSocket 通知後端用戶設置的超時已到
|
||||
if (this.websocket && this.isConnected) {
|
||||
try {
|
||||
this.websocket.send(JSON.stringify({
|
||||
type: 'user_timeout',
|
||||
message: '用戶設置的超時時間已到'
|
||||
}));
|
||||
console.log('已通知後端用戶超時');
|
||||
} catch (error) {
|
||||
console.log('通知後端超時失敗:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disableAllInputs() {
|
||||
// 禁用所有輸入元素
|
||||
const inputs = document.querySelectorAll('input, textarea, button, select');
|
||||
inputs.forEach(input => {
|
||||
input.disabled = true;
|
||||
});
|
||||
|
||||
// 禁用超時控制
|
||||
const timeoutToggle = document.getElementById('timeoutToggle');
|
||||
if (timeoutToggle) {
|
||||
timeoutToggle.style.pointerEvents = 'none';
|
||||
timeoutToggle.style.opacity = '0.5';
|
||||
}
|
||||
|
||||
console.log('所有輸入元素已禁用');
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
try {
|
||||
const settings = {
|
||||
@ -1314,6 +1553,8 @@ $ `;
|
||||
autoClose: this.autoClose,
|
||||
imageSizeLimit: this.imageSizeLimit,
|
||||
enableBase64Detail: this.enableBase64Detail,
|
||||
timeoutEnabled: this.timeoutEnabled,
|
||||
timeoutDuration: this.timeoutDuration,
|
||||
language: window.i18nManager?.currentLanguage || 'zh-TW',
|
||||
activeTab: localStorage.getItem('activeTab'),
|
||||
lastSaved: new Date().toISOString()
|
||||
|
@ -67,6 +67,12 @@
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
@ -79,6 +85,111 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 倒數計時器顯示樣式 */
|
||||
.countdown-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.countdown-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.countdown-timer {
|
||||
color: var(--warning-color);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
min-width: 45px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown-timer.warning {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.countdown-timer.danger {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
/* 超時設置輸入組件樣式 */
|
||||
.timeout-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.timeout-input {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timeout-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.timeout-unit {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 切換開關樣式 */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
background: #666666;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-switch.active {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
|
||||
.toggle-knob {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.toggle-switch.active .toggle-knob {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* 響應式調整 */
|
||||
@media (max-width: 768px) {
|
||||
.timeout-controls {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.timeout-separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.language-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -921,7 +1032,14 @@
|
||||
<!-- 頭部 -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="title" data-i18n="app.title">MCP Feedback Enhanced</h1>
|
||||
<div class="header-left">
|
||||
<h1 class="title" data-i18n="app.title">MCP Feedback Enhanced</h1>
|
||||
<!-- 倒數計時器顯示 -->
|
||||
<div id="countdownDisplay" class="countdown-display" style="display: none;">
|
||||
<span class="countdown-label" data-i18n="timeout.remaining">剩餘時間</span>
|
||||
<span id="countdownTimer" class="countdown-timer">--:--</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="project-info">
|
||||
<span data-i18n="app.projectDirectory">專案目錄</span>: {{ project_directory }}
|
||||
</div>
|
||||
@ -1211,6 +1329,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 超時設定卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h3 class="settings-card-title" data-i18n="timeout.settings.title">⏰ 超時設置</h3>
|
||||
</div>
|
||||
<div class="settings-card-body">
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="timeout.enable">自動關閉</div>
|
||||
<div class="setting-description" data-i18n="timeout.settings.description">
|
||||
啟用後,介面將在指定時間後自動關閉。倒數計時器會顯示在頂部區域。
|
||||
</div>
|
||||
</div>
|
||||
<div id="timeoutToggle" class="toggle-switch">
|
||||
<div class="toggle-knob"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item" style="border-bottom: none;">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label" data-i18n="timeout.duration.label">超時時間</div>
|
||||
<div class="setting-description" data-i18n="timeout.duration.description">
|
||||
設置自動關閉的時間(30秒 - 2小時)
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeout-input-group">
|
||||
<input type="number" id="timeoutDuration" class="timeout-input" min="30" max="7200" value="600">
|
||||
<span class="timeout-unit" data-i18n="timeout.seconds">秒</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 語言設定卡片 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
@ -1363,9 +1513,25 @@
|
||||
<script src="/static/js/i18n.js"></script>
|
||||
<script src="/static/js/app.js"></script>
|
||||
<script>
|
||||
// 初始化全域應用程式實例
|
||||
const sessionId = '{{ session_id }}';
|
||||
window.feedbackApp = new FeedbackApp(sessionId);
|
||||
// 等待 I18nManager 初始化完成後再初始化 FeedbackApp
|
||||
async function initializeApp() {
|
||||
const sessionId = '{{ session_id }}';
|
||||
|
||||
// 確保 I18nManager 已經初始化
|
||||
if (window.i18nManager) {
|
||||
await window.i18nManager.init();
|
||||
}
|
||||
|
||||
// 初始化 FeedbackApp
|
||||
window.feedbackApp = new FeedbackApp(sessionId);
|
||||
}
|
||||
|
||||
// 頁面載入完成後初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||
} else {
|
||||
initializeApp();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user