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
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
紧凑反馈界面 - 精简版反馈界面,只显示进度条和基本操作
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional, List, TypedDict
|
|
7
|
+
# 添加路径以导入session_manager
|
|
8
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
9
|
+
try:
|
|
10
|
+
from session_manager import SessionManager
|
|
11
|
+
except ImportError:
|
|
12
|
+
SessionManager = None
|
|
13
|
+
try:
|
|
14
|
+
from window_position_manager import WindowPositionManager
|
|
15
|
+
except ImportError:
|
|
16
|
+
WindowPositionManager = None
|
|
17
|
+
from PySide6.QtWidgets import (
|
|
18
|
+
QMainWindow, QWidget, QVBoxLayout, QProgressBar
|
|
19
|
+
)
|
|
20
|
+
from PySide6.QtCore import Qt, Signal, QTimer
|
|
21
|
+
from PySide6.QtGui import QIcon, QGuiApplication
|
|
22
|
+
|
|
23
|
+
class FeedbackResult(TypedDict):
|
|
24
|
+
interactive_feedback: str
|
|
25
|
+
images: Optional[List[str]] # Base64 encoded images
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CompactFeedbackUI(QMainWindow):
|
|
29
|
+
"""精简版反馈界面,只显示进度条和基本操作"""
|
|
30
|
+
|
|
31
|
+
# 信号定义
|
|
32
|
+
feedback_submitted = Signal(str, list) # 反馈内容, 图片列表
|
|
33
|
+
restore_main_ui = Signal() # 恢复主界面
|
|
34
|
+
|
|
35
|
+
def __init__(self, main_ui, timeout: int = 60, parent=None):
|
|
36
|
+
super().__init__(parent)
|
|
37
|
+
self.main_ui = main_ui
|
|
38
|
+
self.timeout = timeout
|
|
39
|
+
# 继承主界面的已过时间
|
|
40
|
+
self.elapsed_time = getattr(main_ui, 'elapsed_time', 0)
|
|
41
|
+
# 标记是否正在恢复主界面(用于区分关闭原因)
|
|
42
|
+
self.is_restoring = False
|
|
43
|
+
|
|
44
|
+
# 用于拖动窗口
|
|
45
|
+
self.dragging = False
|
|
46
|
+
self.drag_start_pos = None
|
|
47
|
+
|
|
48
|
+
# 双击ESC关闭的计时器
|
|
49
|
+
self.esc_timer = QTimer()
|
|
50
|
+
self.esc_timer.setSingleShot(True)
|
|
51
|
+
self.esc_timer.timeout.connect(self._reset_esc_count)
|
|
52
|
+
self.esc_press_count = 0
|
|
53
|
+
|
|
54
|
+
# 设置窗口属性 - 隐藏标题栏,设置圆角
|
|
55
|
+
# 使用主窗口的work_title
|
|
56
|
+
if hasattr(main_ui, 'work_title') and main_ui.work_title:
|
|
57
|
+
self.setWindowTitle(main_ui.work_title)
|
|
58
|
+
elif hasattr(main_ui, 'project_path') and main_ui.project_path:
|
|
59
|
+
project_name = os.path.basename(os.path.normpath(main_ui.project_path))
|
|
60
|
+
self.setWindowTitle(project_name)
|
|
61
|
+
else:
|
|
62
|
+
self.setWindowTitle("Feedback")
|
|
63
|
+
|
|
64
|
+
# 隐藏标题栏和边框,保持置顶
|
|
65
|
+
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
|
|
66
|
+
# 设置窗口背景透明,让CSS圆角生效
|
|
67
|
+
self.setAttribute(Qt.WA_TranslucentBackground)
|
|
68
|
+
|
|
69
|
+
# 设置固定尺寸
|
|
70
|
+
self.setFixedSize(170, 60)
|
|
71
|
+
|
|
72
|
+
# 设置窗口透明度
|
|
73
|
+
self.setWindowOpacity(0.95)
|
|
74
|
+
|
|
75
|
+
# 设置智能窗口位置(避免重叠)
|
|
76
|
+
self._set_smart_position()
|
|
77
|
+
|
|
78
|
+
# 创建UI
|
|
79
|
+
self._create_compact_ui()
|
|
80
|
+
|
|
81
|
+
# 精简版不启动自己的计时器,通过定时器从主界面同步时间
|
|
82
|
+
self.sync_timer = QTimer()
|
|
83
|
+
self.sync_timer.timeout.connect(self._sync_from_main)
|
|
84
|
+
if self.timeout > 0:
|
|
85
|
+
self.sync_timer.start(1000) # 每秒同步一次
|
|
86
|
+
|
|
87
|
+
# 设置快捷键
|
|
88
|
+
self._setup_shortcuts()
|
|
89
|
+
|
|
90
|
+
def _create_compact_ui(self):
|
|
91
|
+
from PySide6.QtWidgets import QLabel
|
|
92
|
+
from PySide6.QtCore import Qt
|
|
93
|
+
|
|
94
|
+
central_widget = QWidget()
|
|
95
|
+
self.setCentralWidget(central_widget)
|
|
96
|
+
|
|
97
|
+
# 设置中央部件的背景和圆角
|
|
98
|
+
central_widget.setStyleSheet("""
|
|
99
|
+
QWidget {
|
|
100
|
+
background-color: rgba(43, 43, 43, 255);
|
|
101
|
+
border-radius: 25px;
|
|
102
|
+
}
|
|
103
|
+
""")
|
|
104
|
+
|
|
105
|
+
layout = QVBoxLayout(central_widget)
|
|
106
|
+
layout.setContentsMargins(15, 10, 15, 10)
|
|
107
|
+
layout.setSpacing(3)
|
|
108
|
+
|
|
109
|
+
# 标题文字在上方
|
|
110
|
+
self.title_label = QLabel()
|
|
111
|
+
self.title_label.setAlignment(Qt.AlignCenter)
|
|
112
|
+
self.title_label.setStyleSheet("""
|
|
113
|
+
QLabel {
|
|
114
|
+
color: white;
|
|
115
|
+
font-size: 11px;
|
|
116
|
+
background-color: transparent;
|
|
117
|
+
}
|
|
118
|
+
""")
|
|
119
|
+
# 获取标题文本
|
|
120
|
+
title_text = self._get_title_text()
|
|
121
|
+
self.title_label.setText(title_text)
|
|
122
|
+
layout.addWidget(self.title_label)
|
|
123
|
+
|
|
124
|
+
# 进度条在下方,显示时间
|
|
125
|
+
if self.timeout > 0:
|
|
126
|
+
self.progress_bar = QProgressBar()
|
|
127
|
+
self.progress_bar.setRange(0, self.timeout)
|
|
128
|
+
self.progress_bar.setValue(self.elapsed_time)
|
|
129
|
+
self.progress_bar.setTextVisible(True) # 显示进度条内的时间
|
|
130
|
+
self.progress_bar.setFormat(self._format_time(self.elapsed_time))
|
|
131
|
+
self.progress_bar.setAlignment(Qt.AlignCenter)
|
|
132
|
+
self.progress_bar.setStyleSheet("""
|
|
133
|
+
QProgressBar {
|
|
134
|
+
border-radius: 10px;
|
|
135
|
+
background-color: rgba(255, 255, 255, 30);
|
|
136
|
+
height: 16px;
|
|
137
|
+
border: none;
|
|
138
|
+
color: white;
|
|
139
|
+
font-size: 10px;
|
|
140
|
+
font-weight: 500;
|
|
141
|
+
text-align: center;
|
|
142
|
+
}
|
|
143
|
+
QProgressBar::chunk {
|
|
144
|
+
background-color: #4CAF50;
|
|
145
|
+
border-radius: 10px;
|
|
146
|
+
}
|
|
147
|
+
""")
|
|
148
|
+
|
|
149
|
+
# 设置提示信息
|
|
150
|
+
self.setToolTip("双击切换到完整版")
|
|
151
|
+
|
|
152
|
+
layout.addWidget(self.progress_bar)
|
|
153
|
+
|
|
154
|
+
def _sync_from_main(self):
|
|
155
|
+
"""从主界面同步时间"""
|
|
156
|
+
if hasattr(self.main_ui, 'elapsed_time'):
|
|
157
|
+
self.elapsed_time = self.main_ui.elapsed_time
|
|
158
|
+
self._update_display()
|
|
159
|
+
|
|
160
|
+
def _update_display(self):
|
|
161
|
+
"""更新显示(不增加时间,只更新显示)"""
|
|
162
|
+
# 更新标题文字(如果需要)
|
|
163
|
+
if hasattr(self, 'title_label'):
|
|
164
|
+
title_text = self._get_title_text()
|
|
165
|
+
self.title_label.setText(title_text)
|
|
166
|
+
|
|
167
|
+
# 更新进度条的值和时间文字
|
|
168
|
+
if hasattr(self, 'progress_bar'):
|
|
169
|
+
if self.elapsed_time >= self.timeout:
|
|
170
|
+
self.progress_bar.setValue(self.timeout)
|
|
171
|
+
else:
|
|
172
|
+
self.progress_bar.setValue(self.elapsed_time)
|
|
173
|
+
# 更新进度条内的时间文字
|
|
174
|
+
self.progress_bar.setFormat(self._format_time(self.elapsed_time))
|
|
175
|
+
|
|
176
|
+
def _get_title_text(self) -> str:
|
|
177
|
+
"""获取标题文本"""
|
|
178
|
+
# 优先使用work_title,其次是项目名称
|
|
179
|
+
if hasattr(self.main_ui, 'work_title') and self.main_ui.work_title:
|
|
180
|
+
title = self.main_ui.work_title
|
|
181
|
+
elif hasattr(self.main_ui, 'project_path') and self.main_ui.project_path:
|
|
182
|
+
title = os.path.basename(os.path.normpath(self.main_ui.project_path))
|
|
183
|
+
else:
|
|
184
|
+
title = "等待中"
|
|
185
|
+
|
|
186
|
+
# 如果标题太长,适当截断
|
|
187
|
+
if len(title) > 15:
|
|
188
|
+
title = title[:13] + ".."
|
|
189
|
+
|
|
190
|
+
return title
|
|
191
|
+
|
|
192
|
+
def _format_time(self, seconds: int) -> str:
|
|
193
|
+
"""格式化时间显示"""
|
|
194
|
+
if seconds < 60:
|
|
195
|
+
return f"{seconds}s"
|
|
196
|
+
else:
|
|
197
|
+
minutes = seconds // 60
|
|
198
|
+
return f"{minutes}m"
|
|
199
|
+
|
|
200
|
+
def _set_smart_position(self):
|
|
201
|
+
"""设置智能窗口位置 - 靠右侧垂直排列"""
|
|
202
|
+
# 获取屏幕信息
|
|
203
|
+
screen = QGuiApplication.primaryScreen()
|
|
204
|
+
if screen:
|
|
205
|
+
screen_geometry = screen.availableGeometry()
|
|
206
|
+
screen_width = screen_geometry.width()
|
|
207
|
+
screen_height = screen_geometry.height()
|
|
208
|
+
screen_x = screen_geometry.x()
|
|
209
|
+
screen_y = screen_geometry.y()
|
|
210
|
+
else:
|
|
211
|
+
screen_width = 1920
|
|
212
|
+
screen_height = 1080
|
|
213
|
+
screen_x = 0
|
|
214
|
+
screen_y = 0
|
|
215
|
+
|
|
216
|
+
# 窗口尺寸
|
|
217
|
+
window_width = 170
|
|
218
|
+
window_height = 60
|
|
219
|
+
margin = 0 # 紧贴右侧,无间隙
|
|
220
|
+
spacing = 5 # 窗口间距
|
|
221
|
+
start_y = 200 # 起始 Y 位置
|
|
222
|
+
|
|
223
|
+
# 计算右侧位置(紧贴右边缘)
|
|
224
|
+
x = screen_x + screen_width - window_width - margin
|
|
225
|
+
|
|
226
|
+
if WindowPositionManager:
|
|
227
|
+
try:
|
|
228
|
+
# 获取已有的精简版窗口数量
|
|
229
|
+
positions = WindowPositionManager._load_positions()
|
|
230
|
+
compact_count = len(positions.get('compact', []))
|
|
231
|
+
|
|
232
|
+
# 计算垂直位置(从 top 200px 开始向下排列)
|
|
233
|
+
y = screen_y + start_y + (compact_count * (window_height + spacing))
|
|
234
|
+
|
|
235
|
+
# 检查是否超出屏幕底部
|
|
236
|
+
if y + window_height > screen_y + screen_height:
|
|
237
|
+
# 如果超出,从第二列开始
|
|
238
|
+
x -= (window_width + spacing)
|
|
239
|
+
y = screen_y + start_y
|
|
240
|
+
|
|
241
|
+
self.move(x, y)
|
|
242
|
+
# 保存位置记录
|
|
243
|
+
WindowPositionManager._add_position('compact', x, y)
|
|
244
|
+
self._window_position = (x, y)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
print(f"设置精简版窗口位置失败: {e}")
|
|
247
|
+
# 如果失败,使用默认位置
|
|
248
|
+
self.move(x, screen_y + start_y)
|
|
249
|
+
else:
|
|
250
|
+
# 没有位置管理器时,直接放在右侧起始位置
|
|
251
|
+
self.move(x, screen_y + start_y)
|
|
252
|
+
|
|
253
|
+
def _restore_main_ui(self):
|
|
254
|
+
"""恢复到完整版界面"""
|
|
255
|
+
# 停止精简版同步计时器
|
|
256
|
+
if hasattr(self, 'sync_timer'):
|
|
257
|
+
self.sync_timer.stop()
|
|
258
|
+
|
|
259
|
+
# 标记正在恢复,避免closeEvent中停止主界面计时器
|
|
260
|
+
self.is_restoring = True
|
|
261
|
+
|
|
262
|
+
# 发送恢复信号
|
|
263
|
+
self.restore_main_ui.emit()
|
|
264
|
+
|
|
265
|
+
# 关闭精简版窗口
|
|
266
|
+
self.close()
|
|
267
|
+
|
|
268
|
+
def mousePressEvent(self, event):
|
|
269
|
+
"""处理鼠标按下事件 - 开始拖动"""
|
|
270
|
+
if event.button() == Qt.LeftButton:
|
|
271
|
+
self.dragging = True
|
|
272
|
+
self.drag_start_pos = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
|
273
|
+
event.accept()
|
|
274
|
+
|
|
275
|
+
def mouseMoveEvent(self, event):
|
|
276
|
+
"""处理鼠标移动事件 - 拖动窗口"""
|
|
277
|
+
if event.buttons() == Qt.LeftButton and self.dragging:
|
|
278
|
+
self.move(event.globalPosition().toPoint() - self.drag_start_pos)
|
|
279
|
+
event.accept()
|
|
280
|
+
|
|
281
|
+
def mouseReleaseEvent(self, event):
|
|
282
|
+
"""处理鼠标释放事件 - 结束拖动"""
|
|
283
|
+
self.dragging = False
|
|
284
|
+
event.accept()
|
|
285
|
+
|
|
286
|
+
def mouseDoubleClickEvent(self, event):
|
|
287
|
+
"""处理双击事件 - 切换到完整版"""
|
|
288
|
+
if event.button() == Qt.LeftButton:
|
|
289
|
+
self._restore_main_ui()
|
|
290
|
+
event.accept()
|
|
291
|
+
|
|
292
|
+
def closeEvent(self, event):
|
|
293
|
+
"""窗口关闭事件"""
|
|
294
|
+
# 清理窗口位置记录
|
|
295
|
+
if WindowPositionManager and hasattr(self, '_window_position'):
|
|
296
|
+
try:
|
|
297
|
+
x, y = self._window_position
|
|
298
|
+
WindowPositionManager.remove_position('compact', x, y)
|
|
299
|
+
except Exception:
|
|
300
|
+
pass # 静默处理错误
|
|
301
|
+
|
|
302
|
+
# 停止同步计时器
|
|
303
|
+
if hasattr(self, 'sync_timer'):
|
|
304
|
+
self.sync_timer.stop()
|
|
305
|
+
|
|
306
|
+
# 只有在非恢复状态下才处理关闭(避免意外操作主界面)
|
|
307
|
+
if not getattr(self, 'is_restoring', False):
|
|
308
|
+
# 停止主界面计时器
|
|
309
|
+
if hasattr(self.main_ui, 'countdown_timer'):
|
|
310
|
+
self.main_ui.countdown_timer.stop()
|
|
311
|
+
|
|
312
|
+
# 如果主界面没有反馈结果,设置用户关闭窗口的结果
|
|
313
|
+
if not getattr(self.main_ui, 'feedback_result', None):
|
|
314
|
+
# 标记会话为用户关闭状态
|
|
315
|
+
if SessionManager and hasattr(self.main_ui, 'session_id') and self.main_ui.session_id:
|
|
316
|
+
try:
|
|
317
|
+
session_manager = SessionManager()
|
|
318
|
+
session_manager.mark_feedback_closed(self.main_ui.session_id)
|
|
319
|
+
except Exception:
|
|
320
|
+
pass # 静默处理错误
|
|
321
|
+
|
|
322
|
+
self.main_ui.feedback_result = {
|
|
323
|
+
'content': [{"type": "text", "text": "STOP!请立即停止任何工作,不要再调用任何工具、回复任何消息。STOP!"}],
|
|
324
|
+
'images': []
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# 关闭主界面
|
|
328
|
+
self.main_ui.close()
|
|
329
|
+
|
|
330
|
+
event.accept()
|
|
331
|
+
|
|
332
|
+
def _setup_shortcuts(self):
|
|
333
|
+
"""设置快捷键"""
|
|
334
|
+
from PySide6.QtGui import QShortcut, QKeySequence
|
|
335
|
+
|
|
336
|
+
# Cmd+W 或 Ctrl+W 关闭窗口
|
|
337
|
+
close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
|
|
338
|
+
close_shortcut.activated.connect(self._handle_close_shortcut)
|
|
339
|
+
|
|
340
|
+
# macOS 上的 Cmd+W
|
|
341
|
+
import sys
|
|
342
|
+
if sys.platform == "darwin":
|
|
343
|
+
cmd_close_shortcut = QShortcut(QKeySequence("Meta+W"), self)
|
|
344
|
+
cmd_close_shortcut.activated.connect(self._handle_close_shortcut)
|
|
345
|
+
|
|
346
|
+
def _handle_close_shortcut(self):
|
|
347
|
+
"""处理关闭快捷键"""
|
|
348
|
+
# 直接关闭窗口,让closeEvent处理统一逻辑
|
|
349
|
+
self.close()
|
|
350
|
+
|
|
351
|
+
def keyPressEvent(self, event):
|
|
352
|
+
"""处理按键事件"""
|
|
353
|
+
from PySide6.QtCore import Qt
|
|
354
|
+
|
|
355
|
+
# 检测双击ESC
|
|
356
|
+
if event.key() == Qt.Key_Escape:
|
|
357
|
+
self.esc_press_count += 1
|
|
358
|
+
|
|
359
|
+
if self.esc_press_count == 1:
|
|
360
|
+
# 第一次按ESC,启动计时器(500ms内需要再按一次)
|
|
361
|
+
self.esc_timer.start(500)
|
|
362
|
+
elif self.esc_press_count == 2:
|
|
363
|
+
# 第二次按ESC,关闭窗口
|
|
364
|
+
self.esc_timer.stop()
|
|
365
|
+
self.esc_press_count = 0
|
|
366
|
+
|
|
367
|
+
# 直接关闭窗口,让closeEvent处理统一逻辑
|
|
368
|
+
self.close()
|
|
369
|
+
return # 避免事件继续传播
|
|
370
|
+
|
|
371
|
+
# 调用父类处理
|
|
372
|
+
super().keyPressEvent(event)
|
|
373
|
+
|
|
374
|
+
def _reset_esc_count(self):
|
|
375
|
+
"""重置ESC计数器"""
|
|
376
|
+
self.esc_press_count = 0
|