feedback-mcp 1.0.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of feedback-mcp might be problematic. Click here for more details.
- add_command_dialog.py +712 -0
- command.py +636 -0
- components/__init__.py +15 -0
- components/agent_popup.py +187 -0
- components/chat_history.py +281 -0
- components/command_popup.py +399 -0
- components/feedback_text_edit.py +1125 -0
- components/file_popup.py +417 -0
- components/history_popup.py +582 -0
- components/markdown_display.py +262 -0
- context_formatter.py +301 -0
- debug_logger.py +107 -0
- feedback_config.py +144 -0
- feedback_mcp-1.0.64.dist-info/METADATA +327 -0
- feedback_mcp-1.0.64.dist-info/RECORD +41 -0
- feedback_mcp-1.0.64.dist-info/WHEEL +5 -0
- feedback_mcp-1.0.64.dist-info/entry_points.txt +2 -0
- feedback_mcp-1.0.64.dist-info/top_level.txt +20 -0
- feedback_ui.py +1680 -0
- get_session_id.py +53 -0
- git_operations.py +579 -0
- ide_utils.py +313 -0
- path_config.py +89 -0
- post_task_hook.py +78 -0
- record.py +188 -0
- server.py +746 -0
- session_manager.py +368 -0
- stop_hook.py +87 -0
- tabs/__init__.py +87 -0
- tabs/base_tab.py +34 -0
- tabs/chat_history_style.qss +66 -0
- tabs/chat_history_tab.py +1000 -0
- tabs/chat_tab.py +1931 -0
- tabs/workspace_tab.py +502 -0
- ui/__init__.py +20 -0
- ui/__main__.py +16 -0
- ui/compact_feedback_ui.py +376 -0
- ui/session_list_ui.py +793 -0
- ui/styles/session_list.qss +158 -0
- window_position_manager.py +197 -0
- workspace_manager.py +253 -0
feedback_ui.py
ADDED
|
@@ -0,0 +1,1680 @@
|
|
|
1
|
+
"""
|
|
2
|
+
重构后的反馈UI - 使用模块化架构
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import json
|
|
7
|
+
import argparse
|
|
8
|
+
import base64
|
|
9
|
+
# 移除不必要的导入
|
|
10
|
+
# import markdown - 未使用
|
|
11
|
+
# import requests - 未使用
|
|
12
|
+
# import yaml - 未使用
|
|
13
|
+
# import glob - 未使用
|
|
14
|
+
# from io import BytesIO - 未使用
|
|
15
|
+
# from datetime import datetime, timedelta - 未使用
|
|
16
|
+
# from pathlib import Path - 未使用
|
|
17
|
+
from typing import Optional, TypedDict, List, Dict
|
|
18
|
+
|
|
19
|
+
# 导入窗口位置管理器
|
|
20
|
+
try:
|
|
21
|
+
from window_position_manager import WindowPositionManager
|
|
22
|
+
except ImportError:
|
|
23
|
+
WindowPositionManager = None
|
|
24
|
+
|
|
25
|
+
from PySide6.QtWidgets import (
|
|
26
|
+
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
27
|
+
QLabel, QPushButton, QProgressBar, QTabWidget, QMessageBox
|
|
28
|
+
)
|
|
29
|
+
from PySide6.QtCore import Qt, QTimer, QSettings, Signal, QThread
|
|
30
|
+
from PySide6.QtGui import QPalette, QColor, QGuiApplication
|
|
31
|
+
|
|
32
|
+
# 导入统一日志系统
|
|
33
|
+
from debug_logger import get_debug_logger
|
|
34
|
+
from session_manager import SessionManager
|
|
35
|
+
|
|
36
|
+
# 导入IDE工具
|
|
37
|
+
try:
|
|
38
|
+
from ide_utils import focus_cursor_to_project, is_macos
|
|
39
|
+
except ImportError:
|
|
40
|
+
# 如果导入失败,设置默认函数
|
|
41
|
+
def focus_cursor_to_project(project_path: str) -> bool:
|
|
42
|
+
return False
|
|
43
|
+
def is_macos() -> bool:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# 导入模块化组件 - 修复PyArmor加密环境下的导入问题
|
|
47
|
+
import sys
|
|
48
|
+
import os
|
|
49
|
+
|
|
50
|
+
# 确保当前目录在Python路径中
|
|
51
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
52
|
+
if current_dir not in sys.path:
|
|
53
|
+
sys.path.insert(0, current_dir)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
# 只导入必要的ChatTab、WorkspaceTab和ChatHistoryTab
|
|
57
|
+
from tabs import ChatTab, WorkspaceTab, ChatHistoryTab
|
|
58
|
+
except ImportError as e:
|
|
59
|
+
# 如果导入失败,设置为None
|
|
60
|
+
ChatTab = None
|
|
61
|
+
WorkspaceTab = None
|
|
62
|
+
ChatHistoryTab = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FeedbackResult(TypedDict):
|
|
69
|
+
content: List[Dict[str, str]] # 结构化内容数组,每个元素包含type和text
|
|
70
|
+
images: Optional[List[str]] # Base64 encoded images
|
|
71
|
+
|
|
72
|
+
class VersionCheckThread(QThread):
|
|
73
|
+
"""版本检查线程 - 在独立线程中执行网络请求"""
|
|
74
|
+
|
|
75
|
+
# 定义信号:参数为(latest_version, current_version)
|
|
76
|
+
version_checked = Signal(str, str)
|
|
77
|
+
|
|
78
|
+
def __init__(self, current_version: str, parent=None):
|
|
79
|
+
super().__init__(parent)
|
|
80
|
+
self.current_version = current_version
|
|
81
|
+
self._stop_requested = False
|
|
82
|
+
|
|
83
|
+
def request_stop(self):
|
|
84
|
+
"""请求停止线程"""
|
|
85
|
+
self._stop_requested = True
|
|
86
|
+
|
|
87
|
+
def run(self):
|
|
88
|
+
"""在独立线程中执行版本检查"""
|
|
89
|
+
try:
|
|
90
|
+
if self._stop_requested:
|
|
91
|
+
return
|
|
92
|
+
import requests
|
|
93
|
+
resp = requests.get('https://pypi.org/pypi/feedback-mcp/json', timeout=5)
|
|
94
|
+
if self._stop_requested:
|
|
95
|
+
return
|
|
96
|
+
if resp.status_code == 200:
|
|
97
|
+
latest = resp.json()['info']['version']
|
|
98
|
+
# 发送信号到主线程
|
|
99
|
+
self.version_checked.emit(latest, self.current_version)
|
|
100
|
+
except Exception:
|
|
101
|
+
# 静默处理错误,不发送信号
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_dark_mode_palette(app: QApplication):
|
|
106
|
+
darkPalette = app.palette()
|
|
107
|
+
darkPalette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
108
|
+
darkPalette.setColor(QPalette.WindowText, Qt.white)
|
|
109
|
+
darkPalette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(127, 127, 127))
|
|
110
|
+
darkPalette.setColor(QPalette.Base, QColor(42, 42, 42))
|
|
111
|
+
darkPalette.setColor(QPalette.AlternateBase, QColor(66, 66, 66))
|
|
112
|
+
darkPalette.setColor(QPalette.ToolTipBase, QColor(53, 53, 53))
|
|
113
|
+
darkPalette.setColor(QPalette.ToolTipText, Qt.white)
|
|
114
|
+
darkPalette.setColor(QPalette.Text, Qt.white)
|
|
115
|
+
darkPalette.setColor(QPalette.Disabled, QPalette.Text, QColor(127, 127, 127))
|
|
116
|
+
darkPalette.setColor(QPalette.Dark, QColor(35, 35, 35))
|
|
117
|
+
darkPalette.setColor(QPalette.Shadow, QColor(20, 20, 20))
|
|
118
|
+
darkPalette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
119
|
+
darkPalette.setColor(QPalette.ButtonText, Qt.white)
|
|
120
|
+
darkPalette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(127, 127, 127))
|
|
121
|
+
darkPalette.setColor(QPalette.BrightText, Qt.red)
|
|
122
|
+
darkPalette.setColor(QPalette.Link, QColor(42, 130, 218))
|
|
123
|
+
darkPalette.setColor(QPalette.Highlight, QColor(42, 130, 218))
|
|
124
|
+
darkPalette.setColor(QPalette.Disabled, QPalette.Highlight, QColor(80, 80, 80))
|
|
125
|
+
darkPalette.setColor(QPalette.HighlightedText, Qt.white)
|
|
126
|
+
darkPalette.setColor(QPalette.Disabled, QPalette.HighlightedText, QColor(127, 127, 127))
|
|
127
|
+
darkPalette.setColor(QPalette.PlaceholderText, QColor(127, 127, 127))
|
|
128
|
+
return darkPalette
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class FeedbackUI(QMainWindow):
|
|
132
|
+
"""重构后的反馈UI主界面"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, prompt: str, predefined_options: Optional[List[str]] = None, project_path: Optional[str] = None, work_title: Optional[str] = None, timeout: int = 60, skip_auth_check: bool = False, skip_init_check: bool = False, session_id: Optional[str] = None, workspace_id: Optional[str] = None, files: Optional[List[str]] = None, bugdetail: Optional[str] = None, ide: Optional[str] = None):
|
|
135
|
+
super().__init__()
|
|
136
|
+
|
|
137
|
+
# 基本参数
|
|
138
|
+
self.prompt = prompt
|
|
139
|
+
self.predefined_options = predefined_options or []
|
|
140
|
+
self.project_path = project_path
|
|
141
|
+
self.work_title = work_title or ""
|
|
142
|
+
self.timeout = timeout
|
|
143
|
+
self.skip_init_check = skip_init_check
|
|
144
|
+
self.elapsed_time = 0
|
|
145
|
+
self.session_id = session_id # 保存会话ID
|
|
146
|
+
self.workspace_id = workspace_id # 保存工作空间ID
|
|
147
|
+
self.files = files or [] # 保存文件列表
|
|
148
|
+
self.bugdetail = bugdetail # 保存bug详情
|
|
149
|
+
self.ide = ide # 保存指定的IDE
|
|
150
|
+
|
|
151
|
+
# 如果传入了IDE参数,设置环境变量以便其他模块使用
|
|
152
|
+
if ide:
|
|
153
|
+
os.environ['IDE'] = ide
|
|
154
|
+
try:
|
|
155
|
+
logger = get_debug_logger()
|
|
156
|
+
logger.info(f"设置IDE环境变量: {ide}")
|
|
157
|
+
except:
|
|
158
|
+
pass # 忽略日志错误
|
|
159
|
+
|
|
160
|
+
# 展示feedback时,重置stop hook状态
|
|
161
|
+
if self.session_id:
|
|
162
|
+
try:
|
|
163
|
+
manager = SessionManager(session_id=self.session_id, project_path=self.project_path)
|
|
164
|
+
manager.reset_on_feedback_show(self.session_id)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
try:
|
|
167
|
+
logger = get_debug_logger()
|
|
168
|
+
logger.log_warning(f"Failed to reset stop hook state: {e}", "UI")
|
|
169
|
+
except:
|
|
170
|
+
pass # 忽略日志错误
|
|
171
|
+
|
|
172
|
+
# 结果存储
|
|
173
|
+
self.feedback_result = None
|
|
174
|
+
self.is_temp_close = False # 临时关闭标记(精简版按钮)
|
|
175
|
+
|
|
176
|
+
# 定时器
|
|
177
|
+
self.countdown_timer = QTimer()
|
|
178
|
+
self.countdown_timer.timeout.connect(self._update_countdown)
|
|
179
|
+
self.countdown_timer.setSingleShot(False) # 确保定时器可以重复触发
|
|
180
|
+
|
|
181
|
+
# 双击ESC关闭的计时器
|
|
182
|
+
self.esc_timer = QTimer()
|
|
183
|
+
self.esc_timer.setSingleShot(True)
|
|
184
|
+
self.esc_timer.timeout.connect(self._reset_esc_count)
|
|
185
|
+
self.esc_press_count = 0
|
|
186
|
+
|
|
187
|
+
# UI组件
|
|
188
|
+
self.main_tab_widget = None
|
|
189
|
+
self.chat_tab = None
|
|
190
|
+
self.chat_history_tab = None
|
|
191
|
+
self.memory_tab = None
|
|
192
|
+
self.rules_tab = None
|
|
193
|
+
self.todos_tab = None
|
|
194
|
+
self.checkpoints_tab = None
|
|
195
|
+
self.stats_tab = None
|
|
196
|
+
self.workflow_tab = None
|
|
197
|
+
self.taskflow_tab = None
|
|
198
|
+
self.new_work_tab = None
|
|
199
|
+
|
|
200
|
+
# 设置窗口
|
|
201
|
+
if project_path:
|
|
202
|
+
project_name = os.path.basename(os.path.normpath(project_path))
|
|
203
|
+
if self.work_title:
|
|
204
|
+
self.setWindowTitle(f"{project_name} - {self.work_title}")
|
|
205
|
+
else:
|
|
206
|
+
self.setWindowTitle(project_name)
|
|
207
|
+
else:
|
|
208
|
+
if self.work_title:
|
|
209
|
+
self.setWindowTitle(f"Interactive Feedback - {self.work_title}")
|
|
210
|
+
else:
|
|
211
|
+
self.setWindowTitle("Interactive Feedback")
|
|
212
|
+
self.setMinimumSize(550, 600)
|
|
213
|
+
self.resize(550, 900)
|
|
214
|
+
|
|
215
|
+
# 设置窗口始终置顶
|
|
216
|
+
from PySide6.QtCore import Qt
|
|
217
|
+
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
218
|
+
|
|
219
|
+
# 检查项目初始化状态
|
|
220
|
+
self.project_initialized = True if skip_init_check else self._check_project_initialization()
|
|
221
|
+
|
|
222
|
+
# 直接创建UI,不再进行认证检查
|
|
223
|
+
self._create_ui()
|
|
224
|
+
|
|
225
|
+
# 设置智能窗口位置(避免重叠)
|
|
226
|
+
self._set_smart_position()
|
|
227
|
+
|
|
228
|
+
# Start countdown timer (无论认证状态如何都启动)
|
|
229
|
+
if self.timeout > 0:
|
|
230
|
+
try:
|
|
231
|
+
self.countdown_timer.start(1000) # Update every second
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger = get_debug_logger()
|
|
234
|
+
logger.log_error(f"启动倒计时器失败: {e}", "UI")
|
|
235
|
+
|
|
236
|
+
# 设置快捷键
|
|
237
|
+
self._setup_shortcuts()
|
|
238
|
+
|
|
239
|
+
# 初始化版本检查线程
|
|
240
|
+
self.version_check_thread = None
|
|
241
|
+
|
|
242
|
+
# 30秒后在独立线程中检查新版本
|
|
243
|
+
if self.timeout > 0:
|
|
244
|
+
QTimer.singleShot(30000, self._start_version_check)
|
|
245
|
+
|
|
246
|
+
def _get_version(self):
|
|
247
|
+
"""获取版本号 - 优先从包元数据读取,然后从文件读取"""
|
|
248
|
+
# 方案1: 从包元数据读取(适用于pip安装后)
|
|
249
|
+
try:
|
|
250
|
+
from importlib.metadata import version
|
|
251
|
+
return version('feedback-mcp')
|
|
252
|
+
except Exception:
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
# 方案2: 从version.txt读取(适用于开发环境)
|
|
256
|
+
try:
|
|
257
|
+
from pathlib import Path
|
|
258
|
+
version_file = Path(__file__).parent.parent / 'version.txt'
|
|
259
|
+
if version_file.exists():
|
|
260
|
+
return version_file.read_text().strip()
|
|
261
|
+
except Exception:
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
# 方案3: 从pyproject.toml读取(适用于开发环境)
|
|
265
|
+
try:
|
|
266
|
+
from pathlib import Path
|
|
267
|
+
pyproject_file = Path(__file__).parent.parent / 'pyproject.toml'
|
|
268
|
+
if pyproject_file.exists():
|
|
269
|
+
content = pyproject_file.read_text()
|
|
270
|
+
for line in content.split('\n'):
|
|
271
|
+
if line.startswith('version ='):
|
|
272
|
+
return line.split('=')[1].strip().strip('"')
|
|
273
|
+
except Exception:
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
# 最终降级方案
|
|
277
|
+
return "1.0.0"
|
|
278
|
+
|
|
279
|
+
def _start_version_check(self):
|
|
280
|
+
"""启动版本检查线程"""
|
|
281
|
+
try:
|
|
282
|
+
current_version = self._get_version()
|
|
283
|
+
# 创建并启动版本检查线程
|
|
284
|
+
self.version_check_thread = VersionCheckThread(current_version) # 移除 parent
|
|
285
|
+
# 连接信号到槽函数
|
|
286
|
+
self.version_check_thread.version_checked.connect(self._on_version_checked)
|
|
287
|
+
# 线程结束后清理引用并删除对象
|
|
288
|
+
self.version_check_thread.finished.connect(self._on_version_check_finished)
|
|
289
|
+
# 启动线程
|
|
290
|
+
self.version_check_thread.start()
|
|
291
|
+
except Exception:
|
|
292
|
+
pass # 静默处理错误
|
|
293
|
+
|
|
294
|
+
def _on_version_check_finished(self):
|
|
295
|
+
"""版本检查线程结束的回调"""
|
|
296
|
+
try:
|
|
297
|
+
if self.version_check_thread:
|
|
298
|
+
self.version_check_thread.deleteLater()
|
|
299
|
+
self.version_check_thread = None
|
|
300
|
+
except Exception:
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
def _on_version_checked(self, latest: str, current: str):
|
|
304
|
+
"""版本检查完成的回调(在主线程中执行)"""
|
|
305
|
+
try:
|
|
306
|
+
# 版本比较:只有当latest > current时才提示更新
|
|
307
|
+
if self._version_compare(latest, current) > 0:
|
|
308
|
+
# 更新版本标签文本和样式
|
|
309
|
+
self.version_label.setText(f"当前版本 {current} | 🔔 有新版本 {latest}")
|
|
310
|
+
self.version_label.setStyleSheet("""
|
|
311
|
+
QLabel {
|
|
312
|
+
color: #4CAF50;
|
|
313
|
+
font-size: 10px;
|
|
314
|
+
padding: 2px 6px;
|
|
315
|
+
text-decoration: underline;
|
|
316
|
+
}
|
|
317
|
+
""")
|
|
318
|
+
# 更新tooltip,提示可以点击
|
|
319
|
+
self.version_label.setToolTip(f"发现新版本 v{latest}\n点击复制更新命令")
|
|
320
|
+
# 设置鼠标指针为手型
|
|
321
|
+
self.version_label.setCursor(Qt.PointingHandCursor)
|
|
322
|
+
# 启用鼠标事件
|
|
323
|
+
self.version_label.setMouseTracking(True)
|
|
324
|
+
# 保存最新版本号,供点击事件使用
|
|
325
|
+
self.latest_version = latest
|
|
326
|
+
# 添加点击事件
|
|
327
|
+
self.version_label.mousePressEvent = self._on_version_label_clicked
|
|
328
|
+
except Exception:
|
|
329
|
+
pass # 静默处理错误
|
|
330
|
+
|
|
331
|
+
def _on_version_label_clicked(self, event):
|
|
332
|
+
"""处理版本标签点击事件 - 复制更新命令并弹窗提示"""
|
|
333
|
+
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
334
|
+
if hasattr(self, 'latest_version'):
|
|
335
|
+
# 复制更新命令到剪贴板
|
|
336
|
+
update_command = f"pip install --upgrade feedback-mcp"
|
|
337
|
+
QApplication.clipboard().setText(update_command)
|
|
338
|
+
|
|
339
|
+
# 显示弹窗提示
|
|
340
|
+
msg_box = QMessageBox(self)
|
|
341
|
+
msg_box.setWindowTitle("版本更新")
|
|
342
|
+
msg_box.setIcon(QMessageBox.Icon.Information)
|
|
343
|
+
msg_box.setText(f"已复制更新指令到剪贴板,请升级\n\n更新命令:{update_command}")
|
|
344
|
+
msg_box.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
345
|
+
|
|
346
|
+
# 应用暗色主题样式
|
|
347
|
+
msg_box.setStyleSheet("""
|
|
348
|
+
QMessageBox {
|
|
349
|
+
background-color: #2b2b2b;
|
|
350
|
+
color: #ffffff;
|
|
351
|
+
}
|
|
352
|
+
QMessageBox QLabel {
|
|
353
|
+
color: #ffffff;
|
|
354
|
+
}
|
|
355
|
+
QMessageBox QPushButton {
|
|
356
|
+
background-color: #3c3c3c;
|
|
357
|
+
color: #ffffff;
|
|
358
|
+
border: 1px solid #555555;
|
|
359
|
+
padding: 5px 15px;
|
|
360
|
+
border-radius: 3px;
|
|
361
|
+
}
|
|
362
|
+
QMessageBox QPushButton:hover {
|
|
363
|
+
background-color: #4a4a4a;
|
|
364
|
+
}
|
|
365
|
+
QMessageBox QPushButton:pressed {
|
|
366
|
+
background-color: #2a2a2a;
|
|
367
|
+
}
|
|
368
|
+
""")
|
|
369
|
+
|
|
370
|
+
msg_box.exec()
|
|
371
|
+
|
|
372
|
+
def _check_project_initialization(self) -> bool:
|
|
373
|
+
"""检查项目是否已初始化(检查.agent和_agent-local目录是否存在)"""
|
|
374
|
+
if not self.project_path:
|
|
375
|
+
return False
|
|
376
|
+
|
|
377
|
+
agent_dir = os.path.join(self.project_path, ".agent")
|
|
378
|
+
agent_local_dir = os.path.join(self.project_path, "_agent-local")
|
|
379
|
+
|
|
380
|
+
return os.path.exists(agent_dir) and os.path.exists(agent_local_dir)
|
|
381
|
+
|
|
382
|
+
def _create_initialization_status_widget(self, header_layout):
|
|
383
|
+
"""创建项目初始化状态显示组件"""
|
|
384
|
+
if not self.project_path:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# 如果跳过初始化检查,不显示初始化组件
|
|
388
|
+
if self.skip_init_check:
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
# 只有未初始化时才显示组件,已初始化时保持界面简洁
|
|
392
|
+
if not self.project_initialized:
|
|
393
|
+
# 未初始化,显示初始化按钮,样式与其他header按钮保持一致
|
|
394
|
+
init_button = QPushButton("项目初始化")
|
|
395
|
+
init_button.setMaximumWidth(100)
|
|
396
|
+
init_button.clicked.connect(self._show_initialization_command)
|
|
397
|
+
# 使用与精简版按钮相同的样式风格,但使用警告色调
|
|
398
|
+
init_button.setStyleSheet("""
|
|
399
|
+
QPushButton {
|
|
400
|
+
background-color: #FF9800;
|
|
401
|
+
color: white;
|
|
402
|
+
border: none;
|
|
403
|
+
padding: 4px 8px;
|
|
404
|
+
border-radius: 3px;
|
|
405
|
+
font-size: 11px;
|
|
406
|
+
}
|
|
407
|
+
QPushButton:hover {
|
|
408
|
+
background-color: #F57C00;
|
|
409
|
+
}
|
|
410
|
+
QPushButton:pressed {
|
|
411
|
+
background-color: #E65100;
|
|
412
|
+
}
|
|
413
|
+
""")
|
|
414
|
+
header_layout.addWidget(init_button)
|
|
415
|
+
|
|
416
|
+
def _show_initialization_dialog(self):
|
|
417
|
+
"""显示项目初始化提示弹窗(优化版:去除延迟,直接显示)"""
|
|
418
|
+
from PySide6.QtWidgets import QMessageBox
|
|
419
|
+
|
|
420
|
+
msg_box = QMessageBox(self)
|
|
421
|
+
msg_box.setWindowTitle("项目未初始化")
|
|
422
|
+
msg_box.setIcon(QMessageBox.Icon.Warning)
|
|
423
|
+
|
|
424
|
+
# 将详细信息直接放在主文本中,不使用详细文本
|
|
425
|
+
main_text = """检测到当前项目尚未初始化"""
|
|
426
|
+
|
|
427
|
+
msg_box.setText(main_text)
|
|
428
|
+
msg_box.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
429
|
+
|
|
430
|
+
# 应用暗色主题样式
|
|
431
|
+
msg_box.setStyleSheet("""
|
|
432
|
+
QMessageBox {
|
|
433
|
+
background-color: #2b2b2b;
|
|
434
|
+
color: #ffffff;
|
|
435
|
+
}
|
|
436
|
+
QMessageBox QLabel {
|
|
437
|
+
color: #ffffff;
|
|
438
|
+
}
|
|
439
|
+
QMessageBox QPushButton {
|
|
440
|
+
background-color: #3c3c3c;
|
|
441
|
+
color: #ffffff;
|
|
442
|
+
border: 1px solid #555555;
|
|
443
|
+
padding: 5px 15px;
|
|
444
|
+
border-radius: 3px;
|
|
445
|
+
}
|
|
446
|
+
QMessageBox QPushButton:hover {
|
|
447
|
+
background-color: #4a4a4a;
|
|
448
|
+
}
|
|
449
|
+
QMessageBox QPushButton:pressed {
|
|
450
|
+
background-color: #2a2a2a;
|
|
451
|
+
}
|
|
452
|
+
""")
|
|
453
|
+
|
|
454
|
+
# 直接显示对话框,去除延迟
|
|
455
|
+
try:
|
|
456
|
+
result = msg_box.exec()
|
|
457
|
+
if result == QMessageBox.StandardButton.Ok:
|
|
458
|
+
# 用户点击确定,自动发送初始化命令反馈
|
|
459
|
+
init_message = "请执行命令初始化该项目的AI工具 npm exec --registry=https://omp-npm.acewill.net/ -- workflow-mcp-init"
|
|
460
|
+
self.feedback_result = {
|
|
461
|
+
'content': [{"type": "text", "text": init_message}],
|
|
462
|
+
'images': []
|
|
463
|
+
}
|
|
464
|
+
# 关闭当前窗口,返回反馈
|
|
465
|
+
self.close()
|
|
466
|
+
except Exception as e:
|
|
467
|
+
logger = get_debug_logger()
|
|
468
|
+
logger.log_error(f"显示初始化对话框失败: {e}", "UI")
|
|
469
|
+
|
|
470
|
+
def _show_initialization_command(self):
|
|
471
|
+
"""显示初始化命令信息对话框"""
|
|
472
|
+
from PySide6.QtWidgets import QMessageBox
|
|
473
|
+
|
|
474
|
+
msg_box = QMessageBox(self)
|
|
475
|
+
msg_box.setWindowTitle("项目初始化")
|
|
476
|
+
msg_box.setIcon(QMessageBox.Icon.Information)
|
|
477
|
+
|
|
478
|
+
command_text = "npm exec --registry=https://omp-npm.acewill.net/ -- workflow-mcp-init"
|
|
479
|
+
|
|
480
|
+
# 将详细信息直接放在主文本中,不使用详细文本
|
|
481
|
+
main_text = f"""请在项目根目录下执行以下命令:
|
|
482
|
+
|
|
483
|
+
{command_text}
|
|
484
|
+
|
|
485
|
+
命令执行完成后,将会创建以下目录:
|
|
486
|
+
• .agent/ - 代理配置目录
|
|
487
|
+
• _agent-local/ - 本地代理数据目录
|
|
488
|
+
|
|
489
|
+
初始化完成后,请重新打开此界面以使用完整功能。"""
|
|
490
|
+
|
|
491
|
+
msg_box.setText(main_text)
|
|
492
|
+
msg_box.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
493
|
+
|
|
494
|
+
# 应用暗色主题样式
|
|
495
|
+
msg_box.setStyleSheet("""
|
|
496
|
+
QMessageBox {
|
|
497
|
+
background-color: #2b2b2b;
|
|
498
|
+
color: #ffffff;
|
|
499
|
+
}
|
|
500
|
+
QMessageBox QLabel {
|
|
501
|
+
color: #ffffff;
|
|
502
|
+
}
|
|
503
|
+
QMessageBox QPushButton {
|
|
504
|
+
background-color: #3c3c3c;
|
|
505
|
+
color: #ffffff;
|
|
506
|
+
border: 1px solid #555555;
|
|
507
|
+
padding: 5px 15px;
|
|
508
|
+
border-radius: 3px;
|
|
509
|
+
}
|
|
510
|
+
QMessageBox QPushButton:hover {
|
|
511
|
+
background-color: #4a4a4a;
|
|
512
|
+
}
|
|
513
|
+
QMessageBox QPushButton:pressed {
|
|
514
|
+
background-color: #2a2a2a;
|
|
515
|
+
}
|
|
516
|
+
""")
|
|
517
|
+
|
|
518
|
+
result = msg_box.exec()
|
|
519
|
+
if result == QMessageBox.StandardButton.Ok:
|
|
520
|
+
# 用户点击确定,自动发送初始化命令反馈
|
|
521
|
+
init_message = "请执行命令初始化该项目的AI工具 npm exec --registry=https://omp-npm.acewill.net/ -- workflow-mcp-init"
|
|
522
|
+
self.feedback_result = {
|
|
523
|
+
'content': [{"type": "text", "text": init_message}],
|
|
524
|
+
'images': []
|
|
525
|
+
}
|
|
526
|
+
# 关闭当前窗口,返回反馈
|
|
527
|
+
self.close()
|
|
528
|
+
|
|
529
|
+
def _create_ui(self):
|
|
530
|
+
"""创建主界面"""
|
|
531
|
+
# 设置中央部件
|
|
532
|
+
central_widget = QWidget()
|
|
533
|
+
self.setCentralWidget(central_widget)
|
|
534
|
+
|
|
535
|
+
# 主布局
|
|
536
|
+
layout = QVBoxLayout(central_widget)
|
|
537
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
|
538
|
+
|
|
539
|
+
# 添加状态栏
|
|
540
|
+
self.statusBar().showMessage("就绪", 2000)
|
|
541
|
+
|
|
542
|
+
# Header with GitLab auth status
|
|
543
|
+
header_layout = QHBoxLayout()
|
|
544
|
+
|
|
545
|
+
# 版本号标签 - 左上角
|
|
546
|
+
self.version_label = QLabel(f"v{self._get_version()}")
|
|
547
|
+
self.version_label.setStyleSheet("""
|
|
548
|
+
QLabel {
|
|
549
|
+
color: #888888;
|
|
550
|
+
font-size: 9px;
|
|
551
|
+
padding: 2px 6px;
|
|
552
|
+
}
|
|
553
|
+
""")
|
|
554
|
+
self.version_label.setToolTip("当前版本")
|
|
555
|
+
header_layout.addWidget(self.version_label)
|
|
556
|
+
|
|
557
|
+
# 已移除GitLab认证状态显示
|
|
558
|
+
|
|
559
|
+
# 项目初始化状态显示
|
|
560
|
+
self._create_initialization_status_widget(header_layout)
|
|
561
|
+
|
|
562
|
+
header_layout.addStretch() # Push content to center
|
|
563
|
+
|
|
564
|
+
# IDE设置按钮(放在注销按钮右侧,显示IDE按钮左侧)
|
|
565
|
+
self.ide_settings_button = QPushButton("设置IDE")
|
|
566
|
+
self.ide_settings_button.setMaximumWidth(80)
|
|
567
|
+
self.ide_settings_button.clicked.connect(self._show_ide_settings_dialog)
|
|
568
|
+
self.ide_settings_button.setStyleSheet("""
|
|
569
|
+
QPushButton {
|
|
570
|
+
background-color: #2196F3;
|
|
571
|
+
color: white;
|
|
572
|
+
border: none;
|
|
573
|
+
padding: 4px 8px;
|
|
574
|
+
border-radius: 3px;
|
|
575
|
+
font-size: 11px;
|
|
576
|
+
}
|
|
577
|
+
QPushButton:hover {
|
|
578
|
+
background-color: #1976D2;
|
|
579
|
+
}
|
|
580
|
+
QPushButton:pressed {
|
|
581
|
+
background-color: #0D47A1;
|
|
582
|
+
}
|
|
583
|
+
""")
|
|
584
|
+
self.ide_settings_button.setToolTip("设置默认IDE")
|
|
585
|
+
header_layout.addWidget(self.ide_settings_button)
|
|
586
|
+
|
|
587
|
+
# 显示IDE按钮
|
|
588
|
+
# 优先使用配置文件,其次使用传入的ide参数(环境变量)
|
|
589
|
+
# DEBUG: 打印IDE参数状态
|
|
590
|
+
print(f"[DEBUG] FeedbackUI初始化 - self.ide={self.ide}, 环境变量IDE={os.getenv('IDE')}")
|
|
591
|
+
|
|
592
|
+
# 尝试从配置文件读取IDE
|
|
593
|
+
ide_from_config = None
|
|
594
|
+
if self.project_path:
|
|
595
|
+
try:
|
|
596
|
+
from feedback_config import FeedbackConfig
|
|
597
|
+
config_manager = FeedbackConfig(self.project_path)
|
|
598
|
+
ide_from_config = config_manager.get_ide()
|
|
599
|
+
except Exception:
|
|
600
|
+
pass # 忽略错误,使用默认值
|
|
601
|
+
|
|
602
|
+
# 确定最终使用的IDE:配置文件 > 环境变量参数 > 默认
|
|
603
|
+
final_ide = ide_from_config or self.ide
|
|
604
|
+
|
|
605
|
+
if final_ide:
|
|
606
|
+
# 动态生成IDE显示名称
|
|
607
|
+
# 如果IDE名称全小写,则首字母大写;否则保留原样
|
|
608
|
+
ide_display_name = final_ide if any(c.isupper() for c in final_ide) else final_ide.capitalize()
|
|
609
|
+
if final_ide.lower() == "vscode":
|
|
610
|
+
ide_display_name = "VSCode"
|
|
611
|
+
try:
|
|
612
|
+
logger = get_debug_logger()
|
|
613
|
+
logger.info(f"使用IDE: {final_ide} -> 显示名称: {ide_display_name}")
|
|
614
|
+
except:
|
|
615
|
+
pass # 忽略日志错误
|
|
616
|
+
else:
|
|
617
|
+
# 没有配置IDE
|
|
618
|
+
ide_display_name = "IDE"
|
|
619
|
+
try:
|
|
620
|
+
logger = get_debug_logger()
|
|
621
|
+
logger.info("未配置IDE")
|
|
622
|
+
except:
|
|
623
|
+
pass # 忽略日志错误
|
|
624
|
+
|
|
625
|
+
self.ide_button = QPushButton(f"打开{ide_display_name}")
|
|
626
|
+
self.ide_button.setMaximumWidth(100)
|
|
627
|
+
self.ide_button.clicked.connect(self._open_cursor_ide)
|
|
628
|
+
self.ide_button.setStyleSheet("""
|
|
629
|
+
QPushButton {
|
|
630
|
+
background-color: #4CAF50;
|
|
631
|
+
color: white;
|
|
632
|
+
border: none;
|
|
633
|
+
padding: 4px 8px;
|
|
634
|
+
border-radius: 3px;
|
|
635
|
+
font-size: 11px;
|
|
636
|
+
}
|
|
637
|
+
QPushButton:hover {
|
|
638
|
+
background-color: #45a049;
|
|
639
|
+
}
|
|
640
|
+
QPushButton:pressed {
|
|
641
|
+
background-color: #3d8b40;
|
|
642
|
+
}
|
|
643
|
+
""")
|
|
644
|
+
self.ide_button.setToolTip(f"使用 {ide_display_name} 打开当前项目")
|
|
645
|
+
header_layout.addWidget(self.ide_button)
|
|
646
|
+
|
|
647
|
+
# 稍后处理按钮(临时关闭)
|
|
648
|
+
self.compact_button = QPushButton("稍后处理")
|
|
649
|
+
self.compact_button.setMaximumWidth(80)
|
|
650
|
+
self.compact_button.clicked.connect(self._temp_close)
|
|
651
|
+
self.compact_button.setStyleSheet("""
|
|
652
|
+
QPushButton {
|
|
653
|
+
background-color: #607D8B;
|
|
654
|
+
color: white;
|
|
655
|
+
border: none;
|
|
656
|
+
padding: 4px 8px;
|
|
657
|
+
border-radius: 3px;
|
|
658
|
+
font-size: 11px;
|
|
659
|
+
}
|
|
660
|
+
QPushButton:hover {
|
|
661
|
+
background-color: #546E7A;
|
|
662
|
+
}
|
|
663
|
+
QPushButton:pressed {
|
|
664
|
+
background-color: #455A64;
|
|
665
|
+
}
|
|
666
|
+
""")
|
|
667
|
+
header_layout.addWidget(self.compact_button)
|
|
668
|
+
|
|
669
|
+
layout.addLayout(header_layout)
|
|
670
|
+
|
|
671
|
+
# 创建标签页容器 - 与原版保持一致的命名
|
|
672
|
+
self.main_tab_widget = QTabWidget()
|
|
673
|
+
self.main_tab_widget.currentChanged.connect(self._on_main_tab_changed)
|
|
674
|
+
|
|
675
|
+
# 注意:与原版保持一致,不设置自定义样式,使用系统默认QTabWidget样式
|
|
676
|
+
|
|
677
|
+
# 只创建必要的对话标签页
|
|
678
|
+
self._create_chat_tab() # 反馈
|
|
679
|
+
self._create_chat_history_tab() # 对话记录
|
|
680
|
+
|
|
681
|
+
# 如果传入了workspace_id,创建工作空间tab
|
|
682
|
+
if self.workspace_id:
|
|
683
|
+
self._create_workspace_tab()
|
|
684
|
+
|
|
685
|
+
layout.addWidget(self.main_tab_widget)
|
|
686
|
+
|
|
687
|
+
# 🆕 如果项目未初始化,显示弹窗提示(与原版保持一致)
|
|
688
|
+
if not self.skip_init_check and not self.project_initialized:
|
|
689
|
+
self._show_initialization_dialog()
|
|
690
|
+
|
|
691
|
+
def _create_chat_tab(self):
|
|
692
|
+
"""创建聊天标签页"""
|
|
693
|
+
self.chat_tab = ChatTab(
|
|
694
|
+
prompt=self.prompt,
|
|
695
|
+
predefined_options=self.predefined_options,
|
|
696
|
+
project_path=self.project_path,
|
|
697
|
+
work_title=self.work_title,
|
|
698
|
+
timeout=self.timeout,
|
|
699
|
+
files=self.files,
|
|
700
|
+
bugdetail=self.bugdetail,
|
|
701
|
+
session_id=self.session_id,
|
|
702
|
+
workspace_id=self.workspace_id,
|
|
703
|
+
parent=self
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# 连接信号
|
|
707
|
+
self.chat_tab.feedback_submitted.connect(self._handle_feedback_submitted)
|
|
708
|
+
self.chat_tab.command_executed.connect(self._handle_command_execution)
|
|
709
|
+
self.chat_tab.option_executed.connect(self._execute_option_immediately)
|
|
710
|
+
self.chat_tab.text_changed.connect(self._on_text_changed)
|
|
711
|
+
|
|
712
|
+
self.main_tab_widget.addTab(self.chat_tab, "反馈")
|
|
713
|
+
|
|
714
|
+
def _create_chat_history_tab(self):
|
|
715
|
+
"""创建对话记录标签页"""
|
|
716
|
+
if ChatHistoryTab:
|
|
717
|
+
self.chat_history_tab = ChatHistoryTab(
|
|
718
|
+
project_path=self.project_path,
|
|
719
|
+
session_id=self.session_id,
|
|
720
|
+
workspace_id=self.workspace_id,
|
|
721
|
+
parent=self
|
|
722
|
+
)
|
|
723
|
+
self.main_tab_widget.addTab(self.chat_history_tab, "对话记录")
|
|
724
|
+
|
|
725
|
+
def _create_workspace_tab(self):
|
|
726
|
+
"""创建工作空间标签页
|
|
727
|
+
|
|
728
|
+
只有在以下条件都满足时才创建工作空间tab:
|
|
729
|
+
1. WorkspaceTab类可用
|
|
730
|
+
2. 传入了workspace_id
|
|
731
|
+
3. 能够成功加载工作空间配置
|
|
732
|
+
"""
|
|
733
|
+
if not WorkspaceTab or not self.workspace_id:
|
|
734
|
+
return
|
|
735
|
+
|
|
736
|
+
# 验证是否能加载工作空间配置
|
|
737
|
+
try:
|
|
738
|
+
from workspace_manager import WorkspaceManager
|
|
739
|
+
manager = WorkspaceManager(self.project_path)
|
|
740
|
+
config = manager.load_workspace_config(self.workspace_id)
|
|
741
|
+
|
|
742
|
+
# 只有成功加载到配置时才创建tab
|
|
743
|
+
if config:
|
|
744
|
+
self.workspace_tab = WorkspaceTab(
|
|
745
|
+
workspace_id=self.workspace_id,
|
|
746
|
+
project_path=self.project_path,
|
|
747
|
+
parent=self
|
|
748
|
+
)
|
|
749
|
+
self.main_tab_widget.addTab(self.workspace_tab, "工作空间")
|
|
750
|
+
except Exception:
|
|
751
|
+
# 加载失败时不创建tab
|
|
752
|
+
pass
|
|
753
|
+
|
|
754
|
+
def _create_memory_tab(self):
|
|
755
|
+
"""创建记忆选项卡"""
|
|
756
|
+
if MemoryTab and self.project_path:
|
|
757
|
+
self.memory_tab = MemoryTab(self.project_path, parent=self)
|
|
758
|
+
self.main_tab_widget.addTab(self.memory_tab, "记忆")
|
|
759
|
+
|
|
760
|
+
def _create_rules_tab(self):
|
|
761
|
+
"""创建规则选项卡"""
|
|
762
|
+
if RulesTab and self.project_path:
|
|
763
|
+
self.rules_tab = RulesTab(self.project_path, parent=self)
|
|
764
|
+
self.main_tab_widget.addTab(self.rules_tab, "规则")
|
|
765
|
+
|
|
766
|
+
def _create_todos_tab_deprecated(self):
|
|
767
|
+
"""创建Todos选项卡"""
|
|
768
|
+
# 确保正确导入TodosTab
|
|
769
|
+
try:
|
|
770
|
+
from tabs.todos_tab import TodosTab as LocalTodosTab
|
|
771
|
+
except ImportError:
|
|
772
|
+
LocalTodosTab = None
|
|
773
|
+
|
|
774
|
+
if LocalTodosTab and self.project_path:
|
|
775
|
+
try:
|
|
776
|
+
self.todos_tab = LocalTodosTab()
|
|
777
|
+
# 初始化项目路径
|
|
778
|
+
self.todos_tab.initialize_manager(self.project_path)
|
|
779
|
+
self.main_tab_widget.addTab(self.todos_tab, "Todos")
|
|
780
|
+
# 临时隐藏todos选项卡
|
|
781
|
+
self.todos_tab_index = self.main_tab_widget.count() - 1
|
|
782
|
+
self.main_tab_widget.setTabVisible(self.todos_tab_index, False)
|
|
783
|
+
except Exception as e:
|
|
784
|
+
self.todos_tab = None
|
|
785
|
+
else:
|
|
786
|
+
# 如果导入失败或没有项目路径,设置为None
|
|
787
|
+
self.todos_tab = None
|
|
788
|
+
|
|
789
|
+
def _create_checkpoints_tab_deprecated(self):
|
|
790
|
+
"""创建检查点选项卡"""
|
|
791
|
+
if CheckpointsTab and self.project_path:
|
|
792
|
+
self.checkpoints_tab = CheckpointsTab(self.project_path, parent=self)
|
|
793
|
+
self.main_tab_widget.addTab(self.checkpoints_tab, "检查点")
|
|
794
|
+
|
|
795
|
+
def _create_workflow_tabs_deprecated(self):
|
|
796
|
+
"""创建工作流相关标签页"""
|
|
797
|
+
# 当前工作流标签页
|
|
798
|
+
try:
|
|
799
|
+
current_workflow_tab = CurrentWorkflowWidget(project_path=self.project_path)
|
|
800
|
+
self.main_tab_widget.addTab(current_workflow_tab, "当前工作流")
|
|
801
|
+
self.current_workflow_tab_index = self.main_tab_widget.count() - 1
|
|
802
|
+
self.current_workflow_tab_widget = current_workflow_tab
|
|
803
|
+
except ImportError:
|
|
804
|
+
# 如果无法导入,创建空白标签页占位
|
|
805
|
+
from PySide6.QtWidgets import QWidget
|
|
806
|
+
current_workflow_tab = QWidget()
|
|
807
|
+
self.main_tab_widget.addTab(current_workflow_tab, "当前工作流")
|
|
808
|
+
self.current_workflow_tab_index = self.main_tab_widget.count() - 1
|
|
809
|
+
self.current_workflow_tab_widget = current_workflow_tab
|
|
810
|
+
|
|
811
|
+
# 当前任务流标签页
|
|
812
|
+
try:
|
|
813
|
+
current_taskflow_tab = CurrentTaskflowWidget(project_path=self.project_path)
|
|
814
|
+
self.main_tab_widget.addTab(current_taskflow_tab, "当前任务流")
|
|
815
|
+
self.current_taskflow_tab_index = self.main_tab_widget.count() - 1
|
|
816
|
+
self.current_taskflow_tab_widget = current_taskflow_tab
|
|
817
|
+
except ImportError:
|
|
818
|
+
# 如果无法导入,创建空白标签页占位
|
|
819
|
+
from PySide6.QtWidgets import QWidget
|
|
820
|
+
current_taskflow_tab = QWidget()
|
|
821
|
+
self.main_tab_widget.addTab(current_taskflow_tab, "当前任务流")
|
|
822
|
+
self.current_taskflow_tab_index = self.main_tab_widget.count() - 1
|
|
823
|
+
self.current_taskflow_tab_widget = current_taskflow_tab
|
|
824
|
+
|
|
825
|
+
# 注意:根据原版UI,默认只显示"对话"、"新工作"、"统计"三个标签页
|
|
826
|
+
# "当前工作流"和"当前任务流"标签页保持隐藏状态,但功能保留以备需要时显示
|
|
827
|
+
self.main_tab_widget.setTabVisible(self.current_workflow_tab_index, False)
|
|
828
|
+
self.main_tab_widget.setTabVisible(self.current_taskflow_tab_index, False)
|
|
829
|
+
|
|
830
|
+
def _create_new_project_tab_deprecated(self):
|
|
831
|
+
"""创建新项目选项卡"""
|
|
832
|
+
if NewProjectTab:
|
|
833
|
+
self.new_project_tab = NewProjectTab(parent=self)
|
|
834
|
+
self.main_tab_widget.addTab(self.new_project_tab, "新项目")
|
|
835
|
+
# 临时隐藏新项目选项卡
|
|
836
|
+
self.new_project_tab_index = self.main_tab_widget.count() - 1
|
|
837
|
+
self.main_tab_widget.setTabVisible(self.new_project_tab_index, False)
|
|
838
|
+
else:
|
|
839
|
+
# 如果导入失败,设置为None
|
|
840
|
+
self.new_project_tab = None
|
|
841
|
+
|
|
842
|
+
def _create_new_work_tab(self):
|
|
843
|
+
"""创建新工作标签页"""
|
|
844
|
+
self.new_work_tab = NewWorkTab(self.project_path, parent=self)
|
|
845
|
+
|
|
846
|
+
# 连接信号
|
|
847
|
+
self.new_work_tab.workflow_executed.connect(self._execute_workflow)
|
|
848
|
+
self.new_work_tab.taskflow_executed.connect(self._execute_taskflow)
|
|
849
|
+
|
|
850
|
+
self.main_tab_widget.addTab(self.new_work_tab, "新工作")
|
|
851
|
+
|
|
852
|
+
def _create_config_tab(self):
|
|
853
|
+
"""创建配置标签页"""
|
|
854
|
+
# 配置功能已移除,IDE现在只从环境变量读取
|
|
855
|
+
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout
|
|
856
|
+
config_widget = QWidget()
|
|
857
|
+
layout = QVBoxLayout(config_widget)
|
|
858
|
+
label = QLabel("IDE配置请通过环境变量设置\n\n例如: IDE=cursor")
|
|
859
|
+
label.setAlignment(Qt.AlignCenter)
|
|
860
|
+
label.setStyleSheet("color: #888888; font-size: 14px;")
|
|
861
|
+
layout.addWidget(label)
|
|
862
|
+
self.main_tab_widget.addTab(config_widget, "配置")
|
|
863
|
+
|
|
864
|
+
def _create_stats_tab(self):
|
|
865
|
+
"""创建统计标签页"""
|
|
866
|
+
self.stats_tab = StatsTab(project_path=self.project_path, parent=self)
|
|
867
|
+
self.main_tab_widget.addTab(self.stats_tab, "统计")
|
|
868
|
+
|
|
869
|
+
def _on_ide_config_changed(self, ide_name: str):
|
|
870
|
+
"""IDE配置变更时的处理"""
|
|
871
|
+
# IDE配置已改为从环境变量读取,此函数保留为空实现
|
|
872
|
+
pass
|
|
873
|
+
|
|
874
|
+
def _handle_feedback_submitted(self, content_parts: List[Dict[str, str]], images: List[str]):
|
|
875
|
+
"""处理反馈提交"""
|
|
876
|
+
# 停止倒计时
|
|
877
|
+
if self.countdown_timer.isActive():
|
|
878
|
+
self.countdown_timer.stop()
|
|
879
|
+
|
|
880
|
+
# 用户正常提交,在后台清除stop hook状态
|
|
881
|
+
if self.session_id:
|
|
882
|
+
import threading
|
|
883
|
+
def clear_session_bg():
|
|
884
|
+
try:
|
|
885
|
+
manager = SessionManager(session_id=self.session_id, project_path=self.project_path)
|
|
886
|
+
manager.clear_session(self.session_id)
|
|
887
|
+
except Exception as e:
|
|
888
|
+
logger = get_debug_logger()
|
|
889
|
+
logger.log_warning(f"Failed to clear session on submit: {e}", "UI")
|
|
890
|
+
threading.Thread(target=clear_session_bg, daemon=True).start()
|
|
891
|
+
|
|
892
|
+
# 设置结果
|
|
893
|
+
self.feedback_result = {
|
|
894
|
+
'content': content_parts,
|
|
895
|
+
'images': images
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
self.close()
|
|
899
|
+
|
|
900
|
+
def _handle_command_execution(self, command_content: str):
|
|
901
|
+
"""处理指令执行"""
|
|
902
|
+
if command_content:
|
|
903
|
+
# 构建指令内容的结构化格式
|
|
904
|
+
content_parts = [{"type": "command", "text": command_content}]
|
|
905
|
+
|
|
906
|
+
self.feedback_result = {
|
|
907
|
+
'content': content_parts,
|
|
908
|
+
'images': []
|
|
909
|
+
}
|
|
910
|
+
self.close()
|
|
911
|
+
|
|
912
|
+
def _execute_option_immediately(self, option_index: int):
|
|
913
|
+
"""立即执行选项"""
|
|
914
|
+
if 0 <= option_index < len(self.predefined_options):
|
|
915
|
+
option_text = self.predefined_options[option_index]
|
|
916
|
+
|
|
917
|
+
content_parts = [{"type": "options", "text": option_text}]
|
|
918
|
+
self._handle_feedback_submitted(content_parts, [])
|
|
919
|
+
|
|
920
|
+
def _execute_workflow(self, workflow_name: str):
|
|
921
|
+
"""执行工作流"""
|
|
922
|
+
command = f"/work use {workflow_name}"
|
|
923
|
+
self._handle_command_execution(command)
|
|
924
|
+
|
|
925
|
+
def _execute_taskflow(self, taskflow_name: str):
|
|
926
|
+
"""执行任务流"""
|
|
927
|
+
command = f"/task use {taskflow_name}"
|
|
928
|
+
self._handle_command_execution(command)
|
|
929
|
+
|
|
930
|
+
def _on_text_changed(self):
|
|
931
|
+
"""文本变化处理(委托给聊天标签页)"""
|
|
932
|
+
pass
|
|
933
|
+
|
|
934
|
+
def _on_main_tab_changed(self, index):
|
|
935
|
+
"""主标签页切换处理 - 优化版:减少QTimer使用,改为直接同步调用"""
|
|
936
|
+
# 当切换到当前工作流选项卡时,直接刷新数据和显示
|
|
937
|
+
if hasattr(self, 'current_workflow_tab_index') and index == self.current_workflow_tab_index and hasattr(self, 'current_workflow_tab_widget'):
|
|
938
|
+
try:
|
|
939
|
+
# 直接刷新,不使用QTimer延迟
|
|
940
|
+
if hasattr(self.current_workflow_tab_widget, 'refresh_data'):
|
|
941
|
+
self.current_workflow_tab_widget.refresh_data()
|
|
942
|
+
self.current_workflow_tab_widget.show()
|
|
943
|
+
self.current_workflow_tab_widget.update()
|
|
944
|
+
except Exception as e:
|
|
945
|
+
logger = get_debug_logger()
|
|
946
|
+
logger.log_error(f"Error refreshing current workflow tab: {e}", "UI")
|
|
947
|
+
|
|
948
|
+
# 当切换到当前任务流选项卡时,直接刷新数据和显示
|
|
949
|
+
if hasattr(self, 'current_taskflow_tab_index') and index == self.current_taskflow_tab_index and hasattr(self, 'current_taskflow_tab_widget'):
|
|
950
|
+
try:
|
|
951
|
+
# 直接刷新,不使用QTimer延迟
|
|
952
|
+
if hasattr(self.current_taskflow_tab_widget, 'refresh_data'):
|
|
953
|
+
self.current_taskflow_tab_widget.refresh_data()
|
|
954
|
+
self.current_taskflow_tab_widget.show()
|
|
955
|
+
self.current_taskflow_tab_widget.update()
|
|
956
|
+
except Exception as e:
|
|
957
|
+
logger = get_debug_logger()
|
|
958
|
+
logger.log_error(f"Error refreshing current taskflow tab: {e}", "UI")
|
|
959
|
+
|
|
960
|
+
# 当切换到统计选项卡时,刷新数据(统计是最后一个选项卡)
|
|
961
|
+
if hasattr(self, 'stats_tab') and self.main_tab_widget.tabText(index) == "统计":
|
|
962
|
+
self.stats_tab.refresh_data()
|
|
963
|
+
|
|
964
|
+
def _open_cursor_ide(self):
|
|
965
|
+
"""打开配置的IDE"""
|
|
966
|
+
try:
|
|
967
|
+
if not self.project_path:
|
|
968
|
+
self.statusBar().showMessage("❌ 请先选择项目路径", 3000)
|
|
969
|
+
return
|
|
970
|
+
|
|
971
|
+
# 获取当前配置的IDE
|
|
972
|
+
try:
|
|
973
|
+
from ide_utils import open_project_with_ide
|
|
974
|
+
from feedback_config import FeedbackConfig
|
|
975
|
+
|
|
976
|
+
# 优先从配置文件读取,其次使用传入的IDE参数(来自环境变量)
|
|
977
|
+
config_manager = FeedbackConfig(self.project_path)
|
|
978
|
+
ide_to_use = config_manager.get_ide() or self.ide
|
|
979
|
+
|
|
980
|
+
# 如果没有IDE配置,提示用户配置
|
|
981
|
+
if not ide_to_use:
|
|
982
|
+
reply = QMessageBox.question(
|
|
983
|
+
self,
|
|
984
|
+
"未配置IDE",
|
|
985
|
+
"尚未配置默认IDE,是否现在设置?",
|
|
986
|
+
QMessageBox.Yes | QMessageBox.No
|
|
987
|
+
)
|
|
988
|
+
if reply == QMessageBox.Yes:
|
|
989
|
+
self._show_ide_settings_dialog()
|
|
990
|
+
return
|
|
991
|
+
|
|
992
|
+
success = open_project_with_ide(self.project_path, ide_to_use)
|
|
993
|
+
|
|
994
|
+
# 动态获取IDE显示名称
|
|
995
|
+
# 如果是动态IDE,直接使用名称
|
|
996
|
+
if ide_to_use:
|
|
997
|
+
ide_display = ide_to_use if any(c.isupper() for c in ide_to_use) else ide_to_use.capitalize()
|
|
998
|
+
else:
|
|
999
|
+
ide_display = 'IDE'
|
|
1000
|
+
|
|
1001
|
+
if success:
|
|
1002
|
+
self.statusBar().showMessage(f"✅ {ide_display} 已打开", 3000)
|
|
1003
|
+
else:
|
|
1004
|
+
# 提供更详细的错误提示
|
|
1005
|
+
from ide_utils import is_ide_available
|
|
1006
|
+
|
|
1007
|
+
if not is_ide_available(ide_to_use):
|
|
1008
|
+
self.statusBar().showMessage(f"❌ {ide_display} 未安装或不在PATH中", 3000)
|
|
1009
|
+
else:
|
|
1010
|
+
self.statusBar().showMessage(f"❌ 打开 {ide_display} 失败", 3000)
|
|
1011
|
+
|
|
1012
|
+
except ImportError:
|
|
1013
|
+
# 回退到原来的Cursor逻辑
|
|
1014
|
+
success = focus_cursor_to_project(self.project_path)
|
|
1015
|
+
if success:
|
|
1016
|
+
self.statusBar().showMessage("✅ Cursor IDE 已打开", 3000)
|
|
1017
|
+
else:
|
|
1018
|
+
if not is_macos():
|
|
1019
|
+
self.statusBar().showMessage("❌ 此功能仅支持 macOS", 3000)
|
|
1020
|
+
else:
|
|
1021
|
+
self.statusBar().showMessage("❌ 打开 Cursor IDE 失败", 3000)
|
|
1022
|
+
|
|
1023
|
+
except Exception as e:
|
|
1024
|
+
self.statusBar().showMessage(f"❌ 打开IDE出错: {e}", 3000)
|
|
1025
|
+
|
|
1026
|
+
def _show_ide_settings_dialog(self):
|
|
1027
|
+
"""显示IDE设置对话框"""
|
|
1028
|
+
from PySide6.QtWidgets import (
|
|
1029
|
+
QDialog, QVBoxLayout, QRadioButton, QLineEdit,
|
|
1030
|
+
QPushButton, QLabel, QButtonGroup, QHBoxLayout
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
try:
|
|
1034
|
+
from feedback_config import FeedbackConfig
|
|
1035
|
+
except ImportError:
|
|
1036
|
+
QMessageBox.warning(self, "导入错误", "无法加载配置模块")
|
|
1037
|
+
return
|
|
1038
|
+
|
|
1039
|
+
dialog = QDialog(self)
|
|
1040
|
+
dialog.setWindowTitle("设置IDE")
|
|
1041
|
+
dialog.setMinimumWidth(400)
|
|
1042
|
+
layout = QVBoxLayout(dialog)
|
|
1043
|
+
|
|
1044
|
+
# 加载当前配置
|
|
1045
|
+
config_manager = FeedbackConfig(self.project_path)
|
|
1046
|
+
current_ide = config_manager.get_ide()
|
|
1047
|
+
|
|
1048
|
+
# 说明文字
|
|
1049
|
+
info_label = QLabel("选择默认IDE(用于打开项目):")
|
|
1050
|
+
layout.addWidget(info_label)
|
|
1051
|
+
|
|
1052
|
+
# 常用IDE单选按钮组
|
|
1053
|
+
button_group = QButtonGroup(dialog)
|
|
1054
|
+
button_group.setExclusive(False) # 允许取消勾选
|
|
1055
|
+
radio_buttons = {}
|
|
1056
|
+
|
|
1057
|
+
ides = ["cursor", "vscode", "kiro", "qoder", "pycharm", "intellij"]
|
|
1058
|
+
for ide in ides:
|
|
1059
|
+
rb = QRadioButton(ide.capitalize() if ide != "vscode" else "VSCode")
|
|
1060
|
+
rb.setProperty("ide_value", ide)
|
|
1061
|
+
radio_buttons[ide] = rb
|
|
1062
|
+
button_group.addButton(rb)
|
|
1063
|
+
layout.addWidget(rb)
|
|
1064
|
+
|
|
1065
|
+
# 如果当前配置匹配,选中该按钮
|
|
1066
|
+
if current_ide and current_ide.lower() == ide:
|
|
1067
|
+
rb.setChecked(True)
|
|
1068
|
+
|
|
1069
|
+
# 分隔线
|
|
1070
|
+
layout.addSpacing(10)
|
|
1071
|
+
separator_label = QLabel("或输入自定义IDE命令:")
|
|
1072
|
+
layout.addWidget(separator_label)
|
|
1073
|
+
|
|
1074
|
+
# 自定义IDE输入框
|
|
1075
|
+
custom_input = QLineEdit()
|
|
1076
|
+
custom_input.setPlaceholderText("例如:code, idea, sublime")
|
|
1077
|
+
|
|
1078
|
+
# 如果当前配置是自定义的,填充到输入框
|
|
1079
|
+
if current_ide and current_ide.lower() not in ides:
|
|
1080
|
+
custom_input.setText(current_ide)
|
|
1081
|
+
|
|
1082
|
+
layout.addWidget(custom_input)
|
|
1083
|
+
|
|
1084
|
+
# 添加交互联动
|
|
1085
|
+
def on_radio_clicked(clicked_button):
|
|
1086
|
+
"""当点击单选按钮时的处理"""
|
|
1087
|
+
# 如果点击的是已选中的按钮,取消选中
|
|
1088
|
+
if clicked_button.isChecked():
|
|
1089
|
+
# 取消其他所有按钮的选中状态(实现互斥)
|
|
1090
|
+
for rb in radio_buttons.values():
|
|
1091
|
+
if rb != clicked_button:
|
|
1092
|
+
rb.setChecked(False)
|
|
1093
|
+
# 清空自定义输入框
|
|
1094
|
+
custom_input.clear()
|
|
1095
|
+
|
|
1096
|
+
def on_custom_input_changed():
|
|
1097
|
+
"""当输入自定义命令时,取消所有预设单选按钮的选中"""
|
|
1098
|
+
if custom_input.text().strip():
|
|
1099
|
+
for rb in radio_buttons.values():
|
|
1100
|
+
rb.setChecked(False)
|
|
1101
|
+
|
|
1102
|
+
# 连接信号
|
|
1103
|
+
for rb in radio_buttons.values():
|
|
1104
|
+
rb.clicked.connect(lambda checked=False, btn=rb: on_radio_clicked(btn))
|
|
1105
|
+
custom_input.textChanged.connect(on_custom_input_changed)
|
|
1106
|
+
|
|
1107
|
+
# 按钮行
|
|
1108
|
+
button_layout = QHBoxLayout()
|
|
1109
|
+
|
|
1110
|
+
clear_button = QPushButton("清除配置")
|
|
1111
|
+
clear_button.clicked.connect(lambda: self._clear_ide_config(dialog, config_manager))
|
|
1112
|
+
|
|
1113
|
+
ok_button = QPushButton("确定")
|
|
1114
|
+
ok_button.setDefault(True)
|
|
1115
|
+
|
|
1116
|
+
cancel_button = QPushButton("取消")
|
|
1117
|
+
|
|
1118
|
+
button_layout.addWidget(clear_button)
|
|
1119
|
+
button_layout.addStretch()
|
|
1120
|
+
button_layout.addWidget(ok_button)
|
|
1121
|
+
button_layout.addWidget(cancel_button)
|
|
1122
|
+
|
|
1123
|
+
layout.addLayout(button_layout)
|
|
1124
|
+
|
|
1125
|
+
# 连接信号
|
|
1126
|
+
def save_and_close():
|
|
1127
|
+
ide_name = None
|
|
1128
|
+
|
|
1129
|
+
# 优先检查预设单选按钮
|
|
1130
|
+
selected_preset = None
|
|
1131
|
+
for ide, rb in radio_buttons.items():
|
|
1132
|
+
if rb.isChecked():
|
|
1133
|
+
selected_preset = ide
|
|
1134
|
+
break
|
|
1135
|
+
|
|
1136
|
+
if selected_preset:
|
|
1137
|
+
# 使用预设IDE
|
|
1138
|
+
config_manager.set_ide(ide=selected_preset)
|
|
1139
|
+
ide_name = selected_preset
|
|
1140
|
+
self.statusBar().showMessage(f"✅ IDE已设置为: {selected_preset.capitalize()}", 3000)
|
|
1141
|
+
else:
|
|
1142
|
+
# 检查自定义输入框
|
|
1143
|
+
custom_text = custom_input.text().strip()
|
|
1144
|
+
if custom_text:
|
|
1145
|
+
config_manager.set_ide(custom_command=custom_text)
|
|
1146
|
+
ide_name = custom_text
|
|
1147
|
+
self.statusBar().showMessage(f"✅ IDE已设置为: {custom_text}", 3000)
|
|
1148
|
+
|
|
1149
|
+
# 更新打开IDE按钮的文本
|
|
1150
|
+
if ide_name:
|
|
1151
|
+
ide_display = ide_name if any(c.isupper() for c in ide_name) else ide_name.capitalize()
|
|
1152
|
+
if ide_name.lower() == "vscode":
|
|
1153
|
+
ide_display = "VSCode"
|
|
1154
|
+
self.ide_button.setText(f"打开{ide_display}")
|
|
1155
|
+
|
|
1156
|
+
dialog.accept()
|
|
1157
|
+
|
|
1158
|
+
ok_button.clicked.connect(save_and_close)
|
|
1159
|
+
cancel_button.clicked.connect(dialog.reject)
|
|
1160
|
+
|
|
1161
|
+
# 应用暗色主题
|
|
1162
|
+
dialog.setStyleSheet("""
|
|
1163
|
+
QDialog {
|
|
1164
|
+
background-color: #2b2b2b;
|
|
1165
|
+
color: #ffffff;
|
|
1166
|
+
}
|
|
1167
|
+
QLabel {
|
|
1168
|
+
color: #ffffff;
|
|
1169
|
+
}
|
|
1170
|
+
QRadioButton {
|
|
1171
|
+
color: #ffffff;
|
|
1172
|
+
}
|
|
1173
|
+
QLineEdit {
|
|
1174
|
+
background-color: #3c3c3c;
|
|
1175
|
+
color: #ffffff;
|
|
1176
|
+
border: 1px solid #555555;
|
|
1177
|
+
padding: 5px;
|
|
1178
|
+
border-radius: 3px;
|
|
1179
|
+
}
|
|
1180
|
+
QPushButton {
|
|
1181
|
+
background-color: #3c3c3c;
|
|
1182
|
+
color: #ffffff;
|
|
1183
|
+
border: 1px solid #555555;
|
|
1184
|
+
padding: 5px 15px;
|
|
1185
|
+
border-radius: 3px;
|
|
1186
|
+
}
|
|
1187
|
+
QPushButton:hover {
|
|
1188
|
+
background-color: #4a4a4a;
|
|
1189
|
+
}
|
|
1190
|
+
QPushButton:pressed {
|
|
1191
|
+
background-color: #2a2a2a;
|
|
1192
|
+
}
|
|
1193
|
+
""")
|
|
1194
|
+
|
|
1195
|
+
dialog.exec()
|
|
1196
|
+
|
|
1197
|
+
def _clear_ide_config(self, dialog, config_manager):
|
|
1198
|
+
"""清除IDE配置"""
|
|
1199
|
+
config_manager.clear_ide()
|
|
1200
|
+
self.statusBar().showMessage("✅ IDE配置已清除", 3000)
|
|
1201
|
+
# 恢复默认按钮文本
|
|
1202
|
+
self.ide_button.setText("打开IDE")
|
|
1203
|
+
dialog.accept()
|
|
1204
|
+
|
|
1205
|
+
def _check_updates(self):
|
|
1206
|
+
"""检查更新"""
|
|
1207
|
+
import requests
|
|
1208
|
+
import subprocess
|
|
1209
|
+
from PySide6.QtWidgets import QMessageBox
|
|
1210
|
+
|
|
1211
|
+
try:
|
|
1212
|
+
# 获取GitLab认证
|
|
1213
|
+
if hasattr(self, 'auth_status_widget') and self.auth_status_widget:
|
|
1214
|
+
auth = self.auth_status_widget.auth
|
|
1215
|
+
if not auth.is_authenticated():
|
|
1216
|
+
QMessageBox.warning(self, "需要认证", "请先进行GitLab认证")
|
|
1217
|
+
return
|
|
1218
|
+
else:
|
|
1219
|
+
QMessageBox.warning(self, "认证错误", "无法获取GitLab认证状态")
|
|
1220
|
+
return
|
|
1221
|
+
|
|
1222
|
+
# 禁用按钮,防止重复点击
|
|
1223
|
+
self.update_button.setEnabled(False)
|
|
1224
|
+
self.update_button.setText("检查中...")
|
|
1225
|
+
|
|
1226
|
+
# 获取远程version.txt
|
|
1227
|
+
url = "https://gitlab.acewill.cn/api/v4/projects/ai%2Fagent-dev/repository/files/version.txt/raw?ref=3.5"
|
|
1228
|
+
headers = {"Authorization": f"Bearer {auth.load_token()}"}
|
|
1229
|
+
|
|
1230
|
+
response = requests.get(url, headers=headers, timeout=10)
|
|
1231
|
+
if response.status_code != 200:
|
|
1232
|
+
self._reset_update_button()
|
|
1233
|
+
QMessageBox.warning(self, "获取失败", f"无法获取远程版本信息: {response.status_code}")
|
|
1234
|
+
return
|
|
1235
|
+
|
|
1236
|
+
remote_version = response.text.strip()
|
|
1237
|
+
|
|
1238
|
+
# 读取本地version.txt
|
|
1239
|
+
try:
|
|
1240
|
+
if self.project_path:
|
|
1241
|
+
version_file = os.path.join(self.project_path, "version.txt")
|
|
1242
|
+
else:
|
|
1243
|
+
version_file = "version.txt"
|
|
1244
|
+
|
|
1245
|
+
with open(version_file, "r", encoding="utf-8") as f:
|
|
1246
|
+
local_version = f.read().strip()
|
|
1247
|
+
except:
|
|
1248
|
+
local_version = "1.0.0"
|
|
1249
|
+
|
|
1250
|
+
self._reset_update_button()
|
|
1251
|
+
|
|
1252
|
+
# 比较版本 - 使用版本号解析比较
|
|
1253
|
+
if self._version_compare(remote_version, local_version) > 0:
|
|
1254
|
+
# 检查是否有更新对话框可用
|
|
1255
|
+
if UpdateInfoDialog:
|
|
1256
|
+
# 显示详细的更新信息对话框
|
|
1257
|
+
update_dialog = UpdateInfoDialog(local_version, remote_version, self.project_path, self)
|
|
1258
|
+
if update_dialog.exec() == QDialog.Accepted and update_dialog.should_update:
|
|
1259
|
+
# 用户确认更新,继续执行git pull
|
|
1260
|
+
pass
|
|
1261
|
+
else:
|
|
1262
|
+
return # 用户取消更新
|
|
1263
|
+
else:
|
|
1264
|
+
# 回退到原有的简单对话框
|
|
1265
|
+
reply = QMessageBox.question(
|
|
1266
|
+
self, "发现更新",
|
|
1267
|
+
f"本地版本: {local_version}\n远程版本: {remote_version}\n\n是否立即更新?",
|
|
1268
|
+
QMessageBox.Yes | QMessageBox.No
|
|
1269
|
+
)
|
|
1270
|
+
if reply != QMessageBox.Yes:
|
|
1271
|
+
return
|
|
1272
|
+
# 执行git pull - 在server.py脚本所在目录执行
|
|
1273
|
+
try:
|
|
1274
|
+
# 获取server.py脚本所在的目录
|
|
1275
|
+
server_dir = os.path.dirname(os.path.abspath(__file__))
|
|
1276
|
+
|
|
1277
|
+
result = subprocess.run(
|
|
1278
|
+
["git", "pull"],
|
|
1279
|
+
capture_output=True,
|
|
1280
|
+
text=True,
|
|
1281
|
+
cwd=server_dir,
|
|
1282
|
+
timeout=30
|
|
1283
|
+
)
|
|
1284
|
+
if result.returncode == 0:
|
|
1285
|
+
QMessageBox.information(self, "更新成功", "代码已更新到最新版本")
|
|
1286
|
+
else:
|
|
1287
|
+
QMessageBox.critical(self, "更新失败", f"git pull失败:\n{result.stderr}")
|
|
1288
|
+
except subprocess.TimeoutExpired:
|
|
1289
|
+
QMessageBox.critical(self, "更新失败", "git pull超时")
|
|
1290
|
+
except Exception as e:
|
|
1291
|
+
QMessageBox.critical(self, "更新失败", f"执行git pull失败: {e}")
|
|
1292
|
+
else:
|
|
1293
|
+
QMessageBox.information(self, "已是最新", "当前已是最新版本")
|
|
1294
|
+
|
|
1295
|
+
except requests.RequestException as e:
|
|
1296
|
+
self._reset_update_button()
|
|
1297
|
+
QMessageBox.critical(self, "网络错误", f"检查更新失败: {e}")
|
|
1298
|
+
except Exception as e:
|
|
1299
|
+
self._reset_update_button()
|
|
1300
|
+
QMessageBox.critical(self, "检查失败", f"检查更新失败: {e}")
|
|
1301
|
+
|
|
1302
|
+
def _reset_update_button(self):
|
|
1303
|
+
"""重置更新按钮状态"""
|
|
1304
|
+
self.update_button.setEnabled(True)
|
|
1305
|
+
self.update_button.setText("检查更新")
|
|
1306
|
+
|
|
1307
|
+
def _version_compare(self, version1: str, version2: str) -> int:
|
|
1308
|
+
"""
|
|
1309
|
+
比较两个版本号
|
|
1310
|
+
|
|
1311
|
+
Args:
|
|
1312
|
+
version1: 第一个版本号
|
|
1313
|
+
version2: 第二个版本号
|
|
1314
|
+
|
|
1315
|
+
Returns:
|
|
1316
|
+
int: 1 if version1 > version2, -1 if version1 < version2, 0 if equal
|
|
1317
|
+
"""
|
|
1318
|
+
try:
|
|
1319
|
+
# 解析版本号为整数列表
|
|
1320
|
+
v1_parts = [int(x) for x in version1.split('.')]
|
|
1321
|
+
v2_parts = [int(x) for x in version2.split('.')]
|
|
1322
|
+
|
|
1323
|
+
# 补齐较短的版本号(比如 1.0 补齐为 1.0.0)
|
|
1324
|
+
max_length = max(len(v1_parts), len(v2_parts))
|
|
1325
|
+
v1_parts.extend([0] * (max_length - len(v1_parts)))
|
|
1326
|
+
v2_parts.extend([0] * (max_length - len(v2_parts)))
|
|
1327
|
+
|
|
1328
|
+
# 逐位比较
|
|
1329
|
+
for v1, v2 in zip(v1_parts, v2_parts):
|
|
1330
|
+
if v1 > v2:
|
|
1331
|
+
return 1
|
|
1332
|
+
elif v1 < v2:
|
|
1333
|
+
return -1
|
|
1334
|
+
|
|
1335
|
+
return 0 # 版本号相等
|
|
1336
|
+
|
|
1337
|
+
except ValueError:
|
|
1338
|
+
# 如果无法解析版本号,回退到字符串比较
|
|
1339
|
+
if version1 > version2:
|
|
1340
|
+
return 1
|
|
1341
|
+
elif version1 < version2:
|
|
1342
|
+
return -1
|
|
1343
|
+
else:
|
|
1344
|
+
return 0
|
|
1345
|
+
|
|
1346
|
+
def _set_smart_position(self):
|
|
1347
|
+
"""设置智能窗口位置,避免多窗口重叠"""
|
|
1348
|
+
if WindowPositionManager:
|
|
1349
|
+
try:
|
|
1350
|
+
# 获取下一个窗口位置
|
|
1351
|
+
x, y = WindowPositionManager.get_next_position('main')
|
|
1352
|
+
self.move(x, y)
|
|
1353
|
+
# 保存当前位置供后续清理
|
|
1354
|
+
self._window_position = (x, y)
|
|
1355
|
+
except Exception as e:
|
|
1356
|
+
print(f"设置窗口位置失败: {e}")
|
|
1357
|
+
# 如果失败,使用默认居中
|
|
1358
|
+
self._center_window()
|
|
1359
|
+
else:
|
|
1360
|
+
# 没有位置管理器时,使用默认居中
|
|
1361
|
+
self._center_window()
|
|
1362
|
+
|
|
1363
|
+
def _center_window(self):
|
|
1364
|
+
"""将窗口居中显示"""
|
|
1365
|
+
from PySide6.QtGui import QGuiApplication
|
|
1366
|
+
screen = QGuiApplication.primaryScreen()
|
|
1367
|
+
if screen:
|
|
1368
|
+
screen_geometry = screen.availableGeometry()
|
|
1369
|
+
x = screen_geometry.x() + (screen_geometry.width() - self.width()) // 2
|
|
1370
|
+
y = screen_geometry.y() + (screen_geometry.height() - self.height()) // 2
|
|
1371
|
+
self.move(x, y)
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def _update_countdown(self):
|
|
1375
|
+
"""更新倒计时 - 优化版:增强错误处理,避免加密环境下的异常"""
|
|
1376
|
+
try:
|
|
1377
|
+
self.elapsed_time += 1
|
|
1378
|
+
|
|
1379
|
+
# 更新聊天标签页的进度条(增加安全检查)
|
|
1380
|
+
if self.chat_tab and hasattr(self.chat_tab, 'update_progress'):
|
|
1381
|
+
try:
|
|
1382
|
+
self.chat_tab.update_progress(self.elapsed_time)
|
|
1383
|
+
except Exception as e:
|
|
1384
|
+
logger = get_debug_logger()
|
|
1385
|
+
logger.log_warning(f"Failed to update chat progress: {e}", "UI")
|
|
1386
|
+
|
|
1387
|
+
# 检查是否超时
|
|
1388
|
+
if self.elapsed_time >= self.timeout:
|
|
1389
|
+
self.countdown_timer.stop()
|
|
1390
|
+
# 超时前检查输入框是否有内容,如果有则保存到历史记录
|
|
1391
|
+
if self.chat_tab and hasattr(self.chat_tab, 'save_input_to_history'):
|
|
1392
|
+
try:
|
|
1393
|
+
self.chat_tab.save_input_to_history()
|
|
1394
|
+
except Exception as e:
|
|
1395
|
+
logger = get_debug_logger()
|
|
1396
|
+
logger.log_warning(f"Failed to save input to history on timeout: {e}", "UI")
|
|
1397
|
+
# 自动提交空反馈
|
|
1398
|
+
self._handle_feedback_submitted([], [])
|
|
1399
|
+
except Exception as e:
|
|
1400
|
+
logger = get_debug_logger()
|
|
1401
|
+
logger.log_error(f"倒计时更新失败: {e}", "UI")
|
|
1402
|
+
# 确保定时器停止,避免无限循环
|
|
1403
|
+
if self.countdown_timer.isActive():
|
|
1404
|
+
self.countdown_timer.stop()
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
|
|
1408
|
+
def _temp_close(self):
|
|
1409
|
+
"""临时关闭(精简版按钮),不写入结果"""
|
|
1410
|
+
self.is_temp_close = True
|
|
1411
|
+
self.close()
|
|
1412
|
+
|
|
1413
|
+
def closeEvent(self, event):
|
|
1414
|
+
"""关闭事件处理"""
|
|
1415
|
+
# 清理窗口位置记录
|
|
1416
|
+
if WindowPositionManager and hasattr(self, '_window_position'):
|
|
1417
|
+
try:
|
|
1418
|
+
x, y = self._window_position
|
|
1419
|
+
WindowPositionManager.remove_position('main', x, y)
|
|
1420
|
+
except Exception:
|
|
1421
|
+
pass # 静默处理错误
|
|
1422
|
+
|
|
1423
|
+
# 停止并等待版本检查线程结束
|
|
1424
|
+
try:
|
|
1425
|
+
if self.version_check_thread is not None:
|
|
1426
|
+
if self.version_check_thread.isRunning():
|
|
1427
|
+
self.version_check_thread.request_stop()
|
|
1428
|
+
# 等待线程结束,超时后强制终止
|
|
1429
|
+
if not self.version_check_thread.wait(3000): # 等待3秒
|
|
1430
|
+
# 超时,强制终止线程
|
|
1431
|
+
self.version_check_thread.terminate()
|
|
1432
|
+
self.version_check_thread.wait(1000) # 等待终止完成
|
|
1433
|
+
# 清理引用
|
|
1434
|
+
self.version_check_thread = None
|
|
1435
|
+
except (RuntimeError, AttributeError):
|
|
1436
|
+
pass # 对象可能已被删除
|
|
1437
|
+
|
|
1438
|
+
# 停止定时器
|
|
1439
|
+
if self.countdown_timer.isActive():
|
|
1440
|
+
self.countdown_timer.stop()
|
|
1441
|
+
|
|
1442
|
+
# 停止ESC定时器
|
|
1443
|
+
if hasattr(self, 'esc_timer') and self.esc_timer.isActive():
|
|
1444
|
+
self.esc_timer.stop()
|
|
1445
|
+
|
|
1446
|
+
# 清理 chat_tab 中的弹窗等子控件,避免 Qt 对象销毁顺序问题
|
|
1447
|
+
if self.chat_tab:
|
|
1448
|
+
try:
|
|
1449
|
+
# 清理输入框的指令弹窗和文件弹窗
|
|
1450
|
+
if hasattr(self.chat_tab, 'input_text'):
|
|
1451
|
+
input_text = self.chat_tab.input_text
|
|
1452
|
+
if hasattr(input_text, '_close_command_popup'):
|
|
1453
|
+
input_text._close_command_popup()
|
|
1454
|
+
if hasattr(input_text, '_close_file_popup'):
|
|
1455
|
+
input_text._close_file_popup()
|
|
1456
|
+
except Exception:
|
|
1457
|
+
pass # 静默处理错误
|
|
1458
|
+
|
|
1459
|
+
# 处理延迟删除队列,确保 deleteLater 的对象被正确删除
|
|
1460
|
+
try:
|
|
1461
|
+
QApplication.processEvents()
|
|
1462
|
+
except Exception:
|
|
1463
|
+
pass
|
|
1464
|
+
|
|
1465
|
+
# 在关闭前保存输入框内容到历史记录(无论是超时还是用户主动关闭)
|
|
1466
|
+
if self.chat_tab and hasattr(self.chat_tab, 'save_input_to_history'):
|
|
1467
|
+
try:
|
|
1468
|
+
self.chat_tab.save_input_to_history()
|
|
1469
|
+
except Exception as e:
|
|
1470
|
+
logger = get_debug_logger()
|
|
1471
|
+
logger.log_warning(f"Failed to save input to history on close: {e}", "UI")
|
|
1472
|
+
|
|
1473
|
+
# 临时关闭(精简版按钮):不写入结果,直接关闭
|
|
1474
|
+
if self.is_temp_close:
|
|
1475
|
+
event.accept()
|
|
1476
|
+
return
|
|
1477
|
+
|
|
1478
|
+
# 如果没有反馈结果(说明是用户主动关闭,而不是正常提交或超时),设置特定的反馈结果
|
|
1479
|
+
if not self.feedback_result:
|
|
1480
|
+
# 区分关闭方式
|
|
1481
|
+
if self.session_id:
|
|
1482
|
+
try:
|
|
1483
|
+
manager = SessionManager(session_id=self.session_id, project_path=self.project_path)
|
|
1484
|
+
|
|
1485
|
+
# 判断是超时关闭还是用户点击关闭
|
|
1486
|
+
if self.elapsed_time >= self.timeout:
|
|
1487
|
+
# 超时自动关闭
|
|
1488
|
+
manager.mark_timeout_closed(self.session_id)
|
|
1489
|
+
else:
|
|
1490
|
+
# 用户主动关闭(点击关闭按钮或快捷键)
|
|
1491
|
+
manager.mark_user_closed_by_button(self.session_id)
|
|
1492
|
+
except Exception as e:
|
|
1493
|
+
logger = get_debug_logger()
|
|
1494
|
+
logger.log_warning(f"Failed to mark session close type: {e}", "UI")
|
|
1495
|
+
|
|
1496
|
+
self.feedback_result = {
|
|
1497
|
+
'content': [{"type": "text", "text": "STOP!请立即停止任何工作,不要再调用任何工具、回复任何消息。STOP!"}],
|
|
1498
|
+
'images': []
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
# 保存设置
|
|
1502
|
+
settings = QSettings("FeedbackUI", "MainWindow")
|
|
1503
|
+
settings.setValue("geometry", self.saveGeometry())
|
|
1504
|
+
settings.setValue("state", self.saveState())
|
|
1505
|
+
|
|
1506
|
+
event.accept()
|
|
1507
|
+
|
|
1508
|
+
def _setup_shortcuts(self):
|
|
1509
|
+
"""设置快捷键"""
|
|
1510
|
+
from PySide6.QtGui import QShortcut, QKeySequence
|
|
1511
|
+
|
|
1512
|
+
# Cmd+W 或 Ctrl+W 关闭窗口
|
|
1513
|
+
close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
|
|
1514
|
+
close_shortcut.activated.connect(self._handle_close_shortcut)
|
|
1515
|
+
|
|
1516
|
+
# macOS 上的 Cmd+W
|
|
1517
|
+
if sys.platform == "darwin":
|
|
1518
|
+
cmd_close_shortcut = QShortcut(QKeySequence("Meta+W"), self)
|
|
1519
|
+
cmd_close_shortcut.activated.connect(self._handle_close_shortcut)
|
|
1520
|
+
|
|
1521
|
+
def _handle_close_shortcut(self):
|
|
1522
|
+
"""处理关闭快捷键"""
|
|
1523
|
+
# 直接关闭窗口,让closeEvent处理统一逻辑
|
|
1524
|
+
self.close()
|
|
1525
|
+
|
|
1526
|
+
def keyPressEvent(self, event):
|
|
1527
|
+
"""处理按键事件"""
|
|
1528
|
+
from PySide6.QtCore import Qt
|
|
1529
|
+
|
|
1530
|
+
# 检测双击ESC
|
|
1531
|
+
if event.key() == Qt.Key_Escape:
|
|
1532
|
+
self.esc_press_count += 1
|
|
1533
|
+
|
|
1534
|
+
if self.esc_press_count == 1:
|
|
1535
|
+
# 第一次按ESC,启动计时器(500ms内需要再按一次)
|
|
1536
|
+
self.esc_timer.start(500)
|
|
1537
|
+
elif self.esc_press_count == 2:
|
|
1538
|
+
# 第二次按ESC,关闭窗口
|
|
1539
|
+
self.esc_timer.stop()
|
|
1540
|
+
self.esc_press_count = 0
|
|
1541
|
+
|
|
1542
|
+
# 直接关闭窗口,让closeEvent处理统一逻辑
|
|
1543
|
+
self.close()
|
|
1544
|
+
return # 避免事件继续传播
|
|
1545
|
+
|
|
1546
|
+
# 调用父类处理
|
|
1547
|
+
super().keyPressEvent(event)
|
|
1548
|
+
|
|
1549
|
+
def _reset_esc_count(self):
|
|
1550
|
+
"""重置ESC计数器"""
|
|
1551
|
+
self.esc_press_count = 0
|
|
1552
|
+
|
|
1553
|
+
def run(self) -> FeedbackResult:
|
|
1554
|
+
"""运行反馈界面并返回结果"""
|
|
1555
|
+
# 确保窗口显示在最前面
|
|
1556
|
+
self.show()
|
|
1557
|
+
self.raise_() # 把窗口提到前台
|
|
1558
|
+
self.activateWindow() # 激活窗口
|
|
1559
|
+
|
|
1560
|
+
# 在macOS上确保窗口获得焦点
|
|
1561
|
+
import platform
|
|
1562
|
+
if platform.system() == 'Darwin': # macOS
|
|
1563
|
+
self.setWindowState(self.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
|
|
1564
|
+
|
|
1565
|
+
# 启动事件循环
|
|
1566
|
+
app = QApplication.instance()
|
|
1567
|
+
app.exec()
|
|
1568
|
+
|
|
1569
|
+
# 临时关闭时返回None,不写入结果
|
|
1570
|
+
if self.is_temp_close:
|
|
1571
|
+
return None
|
|
1572
|
+
return self.feedback_result or {"content": [], "images": []}
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
def feedback_ui(prompt: str, predefined_options: Optional[List[str]] = None, output_file: Optional[str] = None, project_path: Optional[str] = None, work_title: Optional[str] = None, timeout: int = 60, skip_init_check: bool = False, session_id: Optional[str] = None, workspace_id: Optional[str] = None, files: Optional[List[str]] = None, bugdetail: Optional[str] = None, ide: Optional[str] = None) -> Optional[FeedbackResult]:
|
|
1576
|
+
"""
|
|
1577
|
+
创建并显示反馈UI界面
|
|
1578
|
+
|
|
1579
|
+
Args:
|
|
1580
|
+
prompt: 显示给用户的提示信息
|
|
1581
|
+
predefined_options: 预定义的选项列表
|
|
1582
|
+
output_file: 输出文件路径(暂未使用)
|
|
1583
|
+
project_path: 项目路径
|
|
1584
|
+
timeout: 超时时间(秒)
|
|
1585
|
+
|
|
1586
|
+
Returns:
|
|
1587
|
+
FeedbackResult: 包含用户反馈和图片的结果
|
|
1588
|
+
"""
|
|
1589
|
+
# 首先确保有QApplication实例 - 这在PyArmor加密环境中非常重要
|
|
1590
|
+
app = QApplication.instance()
|
|
1591
|
+
if app is None:
|
|
1592
|
+
app = QApplication(sys.argv)
|
|
1593
|
+
# 设置应用程序退出策略,避免在加密环境中出现问题
|
|
1594
|
+
app.setQuitOnLastWindowClosed(True)
|
|
1595
|
+
|
|
1596
|
+
# 设置暗色主题
|
|
1597
|
+
try:
|
|
1598
|
+
app.setPalette(get_dark_mode_palette(app))
|
|
1599
|
+
app.setStyle("Fusion") # 与原版保持一致:设置Fusion样式
|
|
1600
|
+
except Exception as e:
|
|
1601
|
+
logger = get_debug_logger()
|
|
1602
|
+
logger.log_warning(f"主题设置失败: {e}", "UI")
|
|
1603
|
+
|
|
1604
|
+
# 创建反馈UI(现在QApplication已经存在)
|
|
1605
|
+
try:
|
|
1606
|
+
ui = FeedbackUI(prompt, predefined_options, project_path, work_title, timeout, skip_auth_check=False, skip_init_check=skip_init_check, session_id=session_id, workspace_id=workspace_id, files=files, bugdetail=bugdetail, ide=ide) # 恢复认证检查
|
|
1607
|
+
except Exception as e:
|
|
1608
|
+
logger = get_debug_logger()
|
|
1609
|
+
logger.log_error(f"FeedbackUI创建失败: {e}", "UI")
|
|
1610
|
+
import traceback
|
|
1611
|
+
traceback.print_exc()
|
|
1612
|
+
return {"content": [], "images": []}
|
|
1613
|
+
|
|
1614
|
+
# 运行并获取结果
|
|
1615
|
+
try:
|
|
1616
|
+
result = ui.run()
|
|
1617
|
+
return result
|
|
1618
|
+
except Exception as e:
|
|
1619
|
+
logger = get_debug_logger()
|
|
1620
|
+
logger.log_error(f"UI运行失败: {e}", "UI")
|
|
1621
|
+
import traceback
|
|
1622
|
+
traceback.print_exc()
|
|
1623
|
+
return {"content": [], "images": []}
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
if __name__ == "__main__":
|
|
1627
|
+
import argparse
|
|
1628
|
+
import pickle
|
|
1629
|
+
|
|
1630
|
+
parser = argparse.ArgumentParser(description='Feedback UI')
|
|
1631
|
+
parser.add_argument('--prompt', required=True, help='显示给用户的提示信息')
|
|
1632
|
+
parser.add_argument('--predefined-options', help='预定义选项(用|||分隔)')
|
|
1633
|
+
parser.add_argument('--project-path', help='项目路径')
|
|
1634
|
+
parser.add_argument('--work-title', help='当前工作标题')
|
|
1635
|
+
parser.add_argument('--timeout', type=int, default=60, help='超时时间(秒)')
|
|
1636
|
+
parser.add_argument('--skip-init-check', action='store_true', help='跳过项目初始化检查')
|
|
1637
|
+
parser.add_argument('--session-id', help='Claude Code会话ID')
|
|
1638
|
+
parser.add_argument('--workspace-id', help='工作空间ID')
|
|
1639
|
+
parser.add_argument('--files', help='AI创建或修改的文件路径(用|||分隔)')
|
|
1640
|
+
parser.add_argument('--bugdetail', help='正在修复的bug简介')
|
|
1641
|
+
parser.add_argument('--ide', help='指定使用的IDE(例如:cursor/vscode/kiro/qoder等)')
|
|
1642
|
+
parser.add_argument('--output-file', help='输出文件路径')
|
|
1643
|
+
|
|
1644
|
+
args = parser.parse_args()
|
|
1645
|
+
|
|
1646
|
+
# 解析预定义选项
|
|
1647
|
+
predefined_options = None
|
|
1648
|
+
if args.predefined_options:
|
|
1649
|
+
predefined_options = args.predefined_options.split('|||')
|
|
1650
|
+
|
|
1651
|
+
# 解析文件列表
|
|
1652
|
+
files = None
|
|
1653
|
+
if args.files:
|
|
1654
|
+
files = args.files.split('|||')
|
|
1655
|
+
|
|
1656
|
+
# 调用反馈UI
|
|
1657
|
+
result = feedback_ui(
|
|
1658
|
+
prompt=args.prompt,
|
|
1659
|
+
predefined_options=predefined_options,
|
|
1660
|
+
project_path=args.project_path,
|
|
1661
|
+
work_title=args.work_title,
|
|
1662
|
+
timeout=args.timeout,
|
|
1663
|
+
skip_init_check=args.skip_init_check,
|
|
1664
|
+
session_id=args.session_id,
|
|
1665
|
+
workspace_id=args.workspace_id,
|
|
1666
|
+
files=files,
|
|
1667
|
+
bugdetail=args.bugdetail,
|
|
1668
|
+
ide=args.ide
|
|
1669
|
+
)
|
|
1670
|
+
|
|
1671
|
+
# 如果指定了输出文件且有结果,写入文件
|
|
1672
|
+
if args.output_file and result is not None:
|
|
1673
|
+
try:
|
|
1674
|
+
with open(args.output_file, 'wb') as f:
|
|
1675
|
+
pickle.dump(result, f)
|
|
1676
|
+
except Exception as e:
|
|
1677
|
+
print(f"写入输出文件失败: {e}", file=sys.stderr)
|
|
1678
|
+
sys.exit(1)
|
|
1679
|
+
elif result is not None:
|
|
1680
|
+
print(f"结果: {result}")
|