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.

@@ -0,0 +1,399 @@
1
+ """
2
+ 指令弹窗组件 - 当用户在输入框中输入"/"时显示可用指令列表
3
+ """
4
+
5
+ from typing import List, Dict, Any, Optional
6
+ from PySide6.QtWidgets import (
7
+ QFrame, QVBoxLayout, QListWidget, QListWidgetItem,
8
+ QLabel, QApplication, QScrollArea, QWidget, QGridLayout, QPushButton, QHBoxLayout
9
+ )
10
+ from PySide6.QtCore import Qt, Signal, QPoint, QTimer
11
+ from PySide6.QtGui import QKeyEvent, QFont
12
+
13
+
14
+
15
+ class CommandPopup(QFrame):
16
+ """指令弹窗组件"""
17
+
18
+ # 信号定义
19
+ command_selected = Signal(str, dict) # 选中指令内容, 完整指令数据
20
+ popup_closed = Signal() # 弹窗关闭
21
+ add_command_requested = Signal(str, str) # 请求添加指令,参数:project_path, command_type
22
+
23
+ def __init__(self, parent=None):
24
+ super().__init__(parent)
25
+ self.commands = [] # 存储指令数据
26
+ self.filtered_commands = [] # 过滤后的指令
27
+ self.filter_text = "" # 过滤文本
28
+
29
+ # 导航相关属性
30
+ self.current_index = -1 # 当前选中的按钮索引
31
+ self.command_buttons = [] # 存储指令按钮
32
+
33
+ # 存储项目路径和指令类型,用于添加指令
34
+ self.project_path = ""
35
+ self.command_type = ""
36
+
37
+ self._setup_ui()
38
+ self._setup_style()
39
+
40
+ # 设置窗口属性 - 使用 Tool 而不是 Popup,避免抢夺焦点
41
+ self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
42
+ self.setAttribute(Qt.WA_ShowWithoutActivating) # 显示时不激活窗口
43
+ # 不设置 WA_DeleteOnClose,我们手动管理对象生命周期
44
+
45
+ def set_project_path(self, project_path: str):
46
+ """设置项目路径"""
47
+ self.project_path = project_path
48
+
49
+ def set_command_type(self, command_type: str):
50
+ """设置指令类型"""
51
+ self.command_type = command_type
52
+ # 更新添加按钮的可见性
53
+ if hasattr(self, 'add_button'):
54
+ # 系统指令不显示添加按钮(不可编辑)
55
+ self.add_button.setVisible(command_type in ['project', 'personal'])
56
+
57
+ def _setup_ui(self):
58
+ """设置UI"""
59
+ layout = QVBoxLayout(self)
60
+ layout.setContentsMargins(2, 2, 2, 2)
61
+ layout.setSpacing(0)
62
+
63
+ # 标题区域 - 包含标题和添加按钮
64
+ title_widget = QWidget()
65
+ title_layout = QHBoxLayout(title_widget)
66
+ title_layout.setContentsMargins(8, 4, 8, 4)
67
+ title_layout.setSpacing(8)
68
+
69
+ # 标题标签(左对齐)
70
+ self.title_label = QLabel("📝 选择指令")
71
+ self.title_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
72
+ font = QFont()
73
+ font.setPointSize(10)
74
+ font.setBold(True)
75
+ self.title_label.setFont(font)
76
+ title_layout.addWidget(self.title_label)
77
+
78
+ # 添加指令按钮已移除
79
+ # 用户需要直接编辑 .md 文件来管理指令
80
+
81
+ layout.addWidget(title_widget)
82
+
83
+ # 指令网格容器
84
+ self.scroll_area = QScrollArea()
85
+ self.scroll_area.setMaximumHeight(400) # 减小最大高度
86
+ self.scroll_area.setMinimumHeight(150) # 减小最小高度
87
+ self.scroll_area.setMinimumWidth(300) # 减小宽度从400到300
88
+ self.scroll_area.setWidgetResizable(True)
89
+ self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
90
+ self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
91
+
92
+ # 网格容器widget
93
+ self.grid_widget = QWidget()
94
+ self.grid_layout = QGridLayout(self.grid_widget)
95
+ self.grid_layout.setSpacing(4)
96
+ self.grid_layout.setContentsMargins(4, 4, 4, 4)
97
+
98
+ self.scroll_area.setWidget(self.grid_widget)
99
+ layout.addWidget(self.scroll_area)
100
+
101
+ # 提示标签
102
+ self.hint_label = QLabel("↑↓ 方向键选择 | 1-9 数字快速选择 | Enter 确认 | Esc 取消")
103
+ self.hint_label.setAlignment(Qt.AlignCenter)
104
+ hint_font = QFont()
105
+ hint_font.setPointSize(8)
106
+ self.hint_label.setFont(hint_font)
107
+ layout.addWidget(self.hint_label)
108
+
109
+ # 添加指令方法已移除
110
+ # 用户需要直接编辑 .md 文件来管理指令
111
+
112
+ def _setup_style(self):
113
+ """设置样式"""
114
+ self.setStyleSheet("""
115
+ CommandPopup {
116
+ background-color: #2b2b2b;
117
+ border: 1px solid #555555;
118
+ border-radius: 4px;
119
+ }
120
+ QLabel {
121
+ color: #ffffff;
122
+ padding: 4px;
123
+ }
124
+ QScrollArea {
125
+ background-color: #2b2b2b;
126
+ border: none;
127
+ }
128
+ QPushButton {
129
+ background-color: #2b2b2b;
130
+ border: 1px solid #3a3a3a;
131
+ border-radius: 4px;
132
+ color: #cccccc;
133
+ padding: 4px 8px; /* 减小内边距从8px 12px */
134
+ text-align: left;
135
+ min-height: 16px; /* 减小最小高度从20px */
136
+ max-height: 24px; /* 设置最大高度 */
137
+ font-size: 11px; /* 减小字体从12px */
138
+ }
139
+ QPushButton:hover {
140
+ background-color: #353535;
141
+ border-color: #4a4a4a;
142
+ }
143
+ QPushButton:focus {
144
+ background-color: #0078d4;
145
+ border-color: #0078d4;
146
+ color: white;
147
+ }
148
+ /* 添加指令小图标按钮样式 */
149
+ QPushButton#add_command_icon_button {
150
+ background-color: #4CAF50;
151
+ border: 1px solid #4CAF50;
152
+ border-radius: 12px; /* 圆形按钮 24px/2 = 12px */
153
+ color: white;
154
+ font-weight: bold;
155
+ font-size: 14px;
156
+ padding: 0px;
157
+ text-align: center;
158
+ min-height: 22px;
159
+ max-height: 22px;
160
+ min-width: 22px;
161
+ max-width: 22px;
162
+ }
163
+ QPushButton#add_command_icon_button:hover {
164
+ background-color: #45a049;
165
+ border-color: #45a049;
166
+ }
167
+ QPushButton#add_command_icon_button:pressed {
168
+ background-color: #3d8b40;
169
+ border-color: #3d8b40;
170
+ }
171
+ """)
172
+
173
+ def set_commands(self, commands: List[Dict[str, Any]]):
174
+ """设置指令列表"""
175
+ self.commands = commands
176
+ self._update_filtered_commands()
177
+
178
+ def set_filter(self, filter_text: str):
179
+ """设置过滤文本"""
180
+ self.filter_text = filter_text.lower()
181
+ self._update_filtered_commands()
182
+
183
+ def _update_filtered_commands(self):
184
+ """更新过滤后的指令列表"""
185
+ # 过滤指令
186
+ if self.filter_text:
187
+ self.filtered_commands = [
188
+ cmd for cmd in self.commands
189
+ if (self.filter_text in cmd.get('title', '').lower() or
190
+ self.filter_text in cmd.get('content', '').lower())
191
+ ]
192
+ else:
193
+ self.filtered_commands = self.commands.copy()
194
+
195
+ # 更新UI
196
+ self._update_list_widget()
197
+
198
+ def _update_list_widget(self):
199
+ """更新网格布局 - 支持分类显示"""
200
+ # 清空现有按钮
201
+ for button in self.command_buttons:
202
+ button.deleteLater()
203
+ self.command_buttons.clear()
204
+
205
+ # 清空布局
206
+ while self.grid_layout.count():
207
+ child = self.grid_layout.takeAt(0)
208
+ if child.widget():
209
+ child.widget().deleteLater()
210
+
211
+ if not self.filtered_commands:
212
+ # 显示空状态
213
+ empty_label = QLabel("😔 没有找到匹配的指令")
214
+ empty_label.setAlignment(Qt.AlignCenter)
215
+ empty_label.setStyleSheet("color: #888888; padding: 20px;")
216
+ self.grid_layout.addWidget(empty_label, 0, 0, 1, 2)
217
+ return
218
+
219
+ # 按类别分组指令
220
+ categories = {}
221
+ for cmd in self.filtered_commands:
222
+ category = cmd.get('category', None) # 允许 None,不设置默认值
223
+ if category not in categories:
224
+ categories[category] = []
225
+ categories[category].append(cmd)
226
+
227
+ # 显示所有指令(按分类)
228
+ row = 0
229
+ button_index = 0
230
+
231
+ for category, commands in categories.items():
232
+ # 只有存在有效 category 时才显示分类标题
233
+ if category: # None 或空字符串时跳过标题
234
+ category_label = QLabel(category)
235
+ category_label.setStyleSheet("""
236
+ QLabel {
237
+ color: #4CAF50;
238
+ font-weight: bold;
239
+ padding: 4px 0px;
240
+ font-size: 11px;
241
+ }
242
+ """)
243
+ self.grid_layout.addWidget(category_label, row, 0, 1, 2)
244
+ row += 1
245
+
246
+ # 添加该分类下的指令按钮
247
+ for i, command in enumerate(commands):
248
+ title = command.get('title', '未命名指令')
249
+
250
+ # 使用数字序号,更直观
251
+ sequence_num = button_index + 1
252
+ display_text = f"{sequence_num}. {title}"
253
+
254
+ button = QPushButton(display_text)
255
+ button.setToolTip(command.get('content', ''))
256
+ button.clicked.connect(lambda checked, cmd=command, idx=button_index: self._on_button_clicked(cmd, idx))
257
+
258
+ # 设置按钮属性用于键盘导航
259
+ button.setProperty('button_index', button_index)
260
+
261
+ # 单列布局 - 所有按钮都在第0列,跨两列显示
262
+ self.grid_layout.addWidget(button, row, 0, 1, 2) # row, col, rowspan, colspan
263
+ self.command_buttons.append(button)
264
+ button_index += 1
265
+ row += 1
266
+
267
+ def _on_button_clicked(self, command: Dict[str, Any], index: int):
268
+ """处理按钮点击"""
269
+ # 日志记录已移除
270
+ self._select_command(command)
271
+
272
+ def _select_command(self, command: Dict[str, Any]):
273
+ """选择指令"""
274
+ content = command.get('content', '')
275
+ # 日志记录已移除
276
+ if content:
277
+ # 发出信号时同时传递内容和完整的指令数据
278
+ self.command_selected.emit(content, command)
279
+ self.close()
280
+
281
+ def keyPressEvent(self, event: QKeyEvent):
282
+ """处理键盘事件 - 支持方向键和数字键"""
283
+ if event.key() == Qt.Key_Escape:
284
+ # ESC键关闭弹窗
285
+ self.popup_closed.emit()
286
+ self.close()
287
+
288
+ elif event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right):
289
+ # 方向键导航
290
+ self._handle_arrow_navigation(event.key())
291
+ event.accept()
292
+
293
+ elif event.key() in (Qt.Key_Return, Qt.Key_Enter):
294
+ # Enter键确认选择
295
+ self._confirm_selection()
296
+ event.accept()
297
+
298
+ elif event.text().isdigit():
299
+ # 数字键快速选择(1-9)
300
+ num = int(event.text())
301
+ if num > 0 and num <= len(self.filtered_commands):
302
+ command = self.filtered_commands[num - 1]
303
+ self._select_command(command)
304
+ event.accept()
305
+
306
+ else:
307
+ super().keyPressEvent(event)
308
+
309
+ def show_at_position(self, position: QPoint):
310
+ """在指定位置显示弹窗"""
311
+ # 调整位置确保弹窗完全可见
312
+ screen = QApplication.primaryScreen()
313
+ screen_geometry = screen.availableGeometry()
314
+
315
+ popup_size = self.sizeHint()
316
+
317
+ # 调整X坐标
318
+ if position.x() + popup_size.width() > screen_geometry.right():
319
+ position.setX(screen_geometry.right() - popup_size.width())
320
+ if position.x() < screen_geometry.left():
321
+ position.setX(screen_geometry.left())
322
+
323
+ # 调整Y坐标 - 默认在对话框上方显示
324
+ position.setY(position.y() - popup_size.height() - 10) # 显示在上方,留10px间距
325
+
326
+ # 如果上方空间不够,则显示在下方
327
+ if position.y() < screen_geometry.top():
328
+ position.setY(position.y() + popup_size.height() + 35) # 显示在下方
329
+
330
+ self.move(position)
331
+ self.show()
332
+ # 不获取焦点,让输入框保持焦点
333
+ # self.setFocus()
334
+
335
+ def _handle_arrow_navigation(self, key):
336
+ """处理方向键导航"""
337
+ if not self.command_buttons:
338
+ return
339
+
340
+ # 初始化或更新当前索引
341
+ if self.current_index == -1:
342
+ self.current_index = 0
343
+ else:
344
+ if key == Qt.Key_Up:
345
+ # 向上移动(单列布局)
346
+ if self.current_index > 0:
347
+ self.current_index -= 1
348
+ elif key == Qt.Key_Down:
349
+ # 向下移动(单列布局)
350
+ if self.current_index < len(self.command_buttons) - 1:
351
+ self.current_index += 1
352
+ # 单列布局不需要左右导航
353
+
354
+ # 更新按钮焦点和样式
355
+ self._update_button_focus()
356
+
357
+ def _update_button_focus(self):
358
+ """更新按钮焦点状态"""
359
+ for i, button in enumerate(self.command_buttons):
360
+ if i == self.current_index:
361
+ button.setFocus()
362
+ button.setStyleSheet("""
363
+ QPushButton {
364
+ background-color: #0078d4;
365
+ border: 1px solid #0078d4;
366
+ color: white;
367
+ padding: 4px 8px;
368
+ text-align: left;
369
+ min-height: 16px;
370
+ max-height: 24px;
371
+ font-size: 11px;
372
+ }
373
+ """)
374
+ # 确保选中的按钮在可视区域内
375
+ self.scroll_area.ensureWidgetVisible(button)
376
+ else:
377
+ button.setStyleSheet("""
378
+ QPushButton {
379
+ background-color: #2b2b2b;
380
+ border: 1px solid #3a3a3a;
381
+ color: #cccccc;
382
+ padding: 4px 8px;
383
+ text-align: left;
384
+ min-height: 16px;
385
+ max-height: 24px;
386
+ font-size: 11px;
387
+ }
388
+ QPushButton:hover {
389
+ background-color: #353535;
390
+ border-color: #4a4a4a;
391
+ }
392
+ """)
393
+
394
+ def _confirm_selection(self):
395
+ """确认当前选择"""
396
+ if self.current_index >= 0 and self.current_index < len(self.filtered_commands):
397
+ command = self.filtered_commands[self.current_index]
398
+ self._select_command(command)
399
+