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
add_command_dialog.py
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
"""
|
|
2
|
+
添加指令对话框组件
|
|
3
|
+
独立的UI组件,用于创建自定义指令文件
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import re
|
|
9
|
+
from typing import Optional, Dict, Any
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from PySide6.QtWidgets import (
|
|
13
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
14
|
+
QTextEdit, QPushButton, QMessageBox, QFormLayout, QRadioButton, QButtonGroup, QWidget, QApplication
|
|
15
|
+
)
|
|
16
|
+
from PySide6.QtCore import Qt, QTimer
|
|
17
|
+
from PySide6.QtGui import QFont
|
|
18
|
+
except ImportError:
|
|
19
|
+
from PyQt5.QtWidgets import (
|
|
20
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
21
|
+
QTextEdit, QPushButton, QMessageBox, QFormLayout, QRadioButton, QButtonGroup, QWidget, QApplication
|
|
22
|
+
)
|
|
23
|
+
from PyQt5.QtCore import Qt, QTimer
|
|
24
|
+
from PyQt5.QtGui import QFont
|
|
25
|
+
|
|
26
|
+
# 导入路径配置模块
|
|
27
|
+
try:
|
|
28
|
+
from path_config import get_path_config
|
|
29
|
+
PATH_CONFIG_AVAILABLE = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
PATH_CONFIG_AVAILABLE = False
|
|
32
|
+
|
|
33
|
+
# 导入调试日志模块
|
|
34
|
+
try:
|
|
35
|
+
from debug_logger import get_debug_logger
|
|
36
|
+
DEBUG_LOG_AVAILABLE = True
|
|
37
|
+
except ImportError:
|
|
38
|
+
DEBUG_LOG_AVAILABLE = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class AddCommandDialog(QDialog):
|
|
42
|
+
"""添加指令对话框"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, project_path: str, parent=None):
|
|
45
|
+
super().__init__(parent)
|
|
46
|
+
self.project_path = project_path
|
|
47
|
+
# 获取路径配置实例
|
|
48
|
+
if PATH_CONFIG_AVAILABLE:
|
|
49
|
+
self.path_config = get_path_config()
|
|
50
|
+
else:
|
|
51
|
+
self.path_config = None
|
|
52
|
+
|
|
53
|
+
self.setWindowTitle("添加新指令")
|
|
54
|
+
self.setModal(True)
|
|
55
|
+
self.resize(500, 400)
|
|
56
|
+
|
|
57
|
+
self._create_ui()
|
|
58
|
+
self._apply_styles()
|
|
59
|
+
|
|
60
|
+
# 连接信号
|
|
61
|
+
self.title_input.textChanged.connect(self._update_button_state)
|
|
62
|
+
self.content_input.textChanged.connect(self._update_button_state)
|
|
63
|
+
|
|
64
|
+
# 设置初始状态
|
|
65
|
+
self._update_button_state()
|
|
66
|
+
|
|
67
|
+
def _create_ui(self):
|
|
68
|
+
"""创建对话框UI"""
|
|
69
|
+
layout = QVBoxLayout(self)
|
|
70
|
+
layout.setSpacing(15)
|
|
71
|
+
layout.setContentsMargins(20, 20, 20, 20)
|
|
72
|
+
|
|
73
|
+
# 表单区域
|
|
74
|
+
form_layout = QFormLayout()
|
|
75
|
+
form_layout.setSpacing(10)
|
|
76
|
+
|
|
77
|
+
# 指令保存位置说明
|
|
78
|
+
location_label = QLabel()
|
|
79
|
+
display_path = self.path_config.get_project_commands_display_path() if self.path_config else ".claude/commands/"
|
|
80
|
+
location_label.setText(f"指令将保存到: {display_path}")
|
|
81
|
+
location_label.setStyleSheet("color: #888; font-style: italic;")
|
|
82
|
+
form_layout.addRow("保存位置:", location_label)
|
|
83
|
+
|
|
84
|
+
# 指令标题输入
|
|
85
|
+
self.title_input = QLineEdit()
|
|
86
|
+
self.title_input.setPlaceholderText("请输入指令标题(将作为文件名)")
|
|
87
|
+
self.title_input.setMinimumHeight(35)
|
|
88
|
+
form_layout.addRow("指令标题:", self.title_input)
|
|
89
|
+
|
|
90
|
+
layout.addLayout(form_layout)
|
|
91
|
+
|
|
92
|
+
# 指令内容输入
|
|
93
|
+
content_label = QLabel("指令内容:")
|
|
94
|
+
content_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
|
|
95
|
+
layout.addWidget(content_label)
|
|
96
|
+
|
|
97
|
+
self.content_input = QTextEdit()
|
|
98
|
+
self.content_input.setPlaceholderText("请输入指令的具体内容...\n\n例如:\n请分析用户的需求并提供解决方案\n注意事项:\n1. 分析要全面\n2. 方案要可行\n3. 考虑用户体验")
|
|
99
|
+
self.content_input.setMinimumHeight(200)
|
|
100
|
+
layout.addWidget(self.content_input)
|
|
101
|
+
|
|
102
|
+
# 按钮区域
|
|
103
|
+
button_layout = QHBoxLayout()
|
|
104
|
+
button_layout.addStretch()
|
|
105
|
+
|
|
106
|
+
# 取消按钮
|
|
107
|
+
cancel_button = QPushButton("取消")
|
|
108
|
+
cancel_button.setMinimumSize(80, 35)
|
|
109
|
+
cancel_button.clicked.connect(self.reject)
|
|
110
|
+
button_layout.addWidget(cancel_button)
|
|
111
|
+
|
|
112
|
+
# 确定按钮
|
|
113
|
+
self.ok_button = QPushButton("创建指令")
|
|
114
|
+
self.ok_button.setMinimumSize(80, 35)
|
|
115
|
+
self.ok_button.clicked.connect(self._save_command)
|
|
116
|
+
self.ok_button.setDefault(True)
|
|
117
|
+
button_layout.addWidget(self.ok_button)
|
|
118
|
+
|
|
119
|
+
layout.addLayout(button_layout)
|
|
120
|
+
|
|
121
|
+
def _apply_styles(self):
|
|
122
|
+
"""应用样式"""
|
|
123
|
+
self.setStyleSheet("""
|
|
124
|
+
QDialog {
|
|
125
|
+
background-color: #2b2b2b;
|
|
126
|
+
color: white;
|
|
127
|
+
}
|
|
128
|
+
QLabel {
|
|
129
|
+
color: white;
|
|
130
|
+
font-size: 12px;
|
|
131
|
+
}
|
|
132
|
+
QLineEdit {
|
|
133
|
+
background-color: #404040;
|
|
134
|
+
border: 1px solid #555;
|
|
135
|
+
border-radius: 4px;
|
|
136
|
+
padding: 8px;
|
|
137
|
+
color: white;
|
|
138
|
+
font-size: 12px;
|
|
139
|
+
}
|
|
140
|
+
QLineEdit:focus {
|
|
141
|
+
border-color: #2196F3;
|
|
142
|
+
}
|
|
143
|
+
QTextEdit {
|
|
144
|
+
background-color: #404040;
|
|
145
|
+
border: 1px solid #555;
|
|
146
|
+
border-radius: 4px;
|
|
147
|
+
padding: 8px;
|
|
148
|
+
color: white;
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
}
|
|
151
|
+
QTextEdit:focus {
|
|
152
|
+
border-color: #2196F3;
|
|
153
|
+
}
|
|
154
|
+
QPushButton {
|
|
155
|
+
background-color: #666666;
|
|
156
|
+
color: white;
|
|
157
|
+
border: none;
|
|
158
|
+
padding: 8px 16px;
|
|
159
|
+
border-radius: 4px;
|
|
160
|
+
font-size: 12px;
|
|
161
|
+
}
|
|
162
|
+
QPushButton:hover {
|
|
163
|
+
background-color: #777777;
|
|
164
|
+
}
|
|
165
|
+
QPushButton:pressed {
|
|
166
|
+
background-color: #555555;
|
|
167
|
+
}
|
|
168
|
+
QPushButton:disabled {
|
|
169
|
+
background-color: #444444;
|
|
170
|
+
color: #888888;
|
|
171
|
+
}
|
|
172
|
+
QPushButton#create_button {
|
|
173
|
+
background-color: #4CAF50;
|
|
174
|
+
color: white;
|
|
175
|
+
}
|
|
176
|
+
QPushButton#create_button:hover {
|
|
177
|
+
background-color: #45a049;
|
|
178
|
+
color: white;
|
|
179
|
+
}
|
|
180
|
+
QPushButton#create_button:pressed {
|
|
181
|
+
background-color: #3d8b40;
|
|
182
|
+
color: white;
|
|
183
|
+
}
|
|
184
|
+
""")
|
|
185
|
+
|
|
186
|
+
# 设置创建按钮的特殊样式
|
|
187
|
+
self.ok_button.setObjectName("create_button")
|
|
188
|
+
|
|
189
|
+
def _update_button_state(self):
|
|
190
|
+
"""更新按钮状态"""
|
|
191
|
+
title = self.title_input.text().strip()
|
|
192
|
+
content = self.content_input.toPlainText().strip()
|
|
193
|
+
|
|
194
|
+
# 只有标题和内容都不为空时才启用按钮
|
|
195
|
+
self.ok_button.setEnabled(bool(title and content))
|
|
196
|
+
|
|
197
|
+
def _save_command(self):
|
|
198
|
+
"""保存新指令"""
|
|
199
|
+
title = self.title_input.text().strip()
|
|
200
|
+
content = self.content_input.toPlainText().strip()
|
|
201
|
+
|
|
202
|
+
# 记录开始保存
|
|
203
|
+
if DEBUG_LOG_AVAILABLE:
|
|
204
|
+
logger = get_debug_logger()
|
|
205
|
+
logger.log(f"开始保存指令: 标题='{title}', 内容长度={len(content)}", "SAVE")
|
|
206
|
+
|
|
207
|
+
# 验证输入
|
|
208
|
+
if not title:
|
|
209
|
+
if DEBUG_LOG_AVAILABLE:
|
|
210
|
+
logger = get_debug_logger()
|
|
211
|
+
logger.log_error("指令保存", "标题为空")
|
|
212
|
+
QMessageBox.warning(self, "输入错误", "请输入指令标题")
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
if not content:
|
|
216
|
+
if DEBUG_LOG_AVAILABLE:
|
|
217
|
+
logger = get_debug_logger()
|
|
218
|
+
logger.log_error("指令保存", "内容为空")
|
|
219
|
+
QMessageBox.warning(self, "输入错误", "请输入指令内容")
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
# 检查文件名是否合法
|
|
223
|
+
if not self._is_valid_filename(title):
|
|
224
|
+
if DEBUG_LOG_AVAILABLE:
|
|
225
|
+
logger = get_debug_logger()
|
|
226
|
+
logger.log_error("指令保存", f"文件名非法: '{title}'")
|
|
227
|
+
QMessageBox.warning(self, "文件名错误",
|
|
228
|
+
"文件名包含非法字符,请使用字母、数字、下划线或中文")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# 根据选择的类型确定保存目录
|
|
233
|
+
# 只支持项目指令
|
|
234
|
+
if True: # 始终保存为项目指令
|
|
235
|
+
# 项目指令:使用路径配置
|
|
236
|
+
if not self.path_config:
|
|
237
|
+
QMessageBox.critical(self, "配置错误", "路径配置不可用,无法保存项目指令")
|
|
238
|
+
return
|
|
239
|
+
prompts_dir = self.path_config.ensure_project_commands_dir(self.project_path)
|
|
240
|
+
if DEBUG_LOG_AVAILABLE:
|
|
241
|
+
logger.log(f"使用路径配置保存项目指令: {prompts_dir}", "SAVE")
|
|
242
|
+
path_type = "项目"
|
|
243
|
+
else:
|
|
244
|
+
# 私有指令:保存到与脚本同目录的prompts/
|
|
245
|
+
if self.path_config:
|
|
246
|
+
prompts_dir = self.path_config.ensure_personal_commands_dir()
|
|
247
|
+
if DEBUG_LOG_AVAILABLE:
|
|
248
|
+
logger.log(f"使用路径配置保存个人指令: {prompts_dir}", "SAVE")
|
|
249
|
+
else:
|
|
250
|
+
# 降级处理
|
|
251
|
+
prompts_dir = os.path.join(self.project_path, "prompts")
|
|
252
|
+
os.makedirs(prompts_dir, exist_ok=True)
|
|
253
|
+
if DEBUG_LOG_AVAILABLE:
|
|
254
|
+
logger.log(f"使用降级路径保存个人指令: {prompts_dir}", "SAVE")
|
|
255
|
+
path_type = "私有"
|
|
256
|
+
|
|
257
|
+
# 生成文件路径
|
|
258
|
+
filename = f"{title}.md"
|
|
259
|
+
file_path = os.path.join(prompts_dir, filename)
|
|
260
|
+
|
|
261
|
+
if DEBUG_LOG_AVAILABLE:
|
|
262
|
+
logger = get_debug_logger()
|
|
263
|
+
logger.log(f"生成文件路径: {file_path}", "SAVE")
|
|
264
|
+
|
|
265
|
+
# 检查文件是否已存在
|
|
266
|
+
if os.path.exists(file_path):
|
|
267
|
+
if DEBUG_LOG_AVAILABLE:
|
|
268
|
+
logger.log(f"文件已存在,询问是否覆盖: {file_path}", "SAVE")
|
|
269
|
+
reply = QMessageBox.question(
|
|
270
|
+
self, "文件已存在",
|
|
271
|
+
f"指令文件 '{filename}' 已存在,是否覆盖?",
|
|
272
|
+
QMessageBox.Yes | QMessageBox.No,
|
|
273
|
+
QMessageBox.No
|
|
274
|
+
)
|
|
275
|
+
if reply == QMessageBox.No:
|
|
276
|
+
if DEBUG_LOG_AVAILABLE:
|
|
277
|
+
logger.log("用户选择不覆盖,取消保存", "SAVE")
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
# 创建文件内容
|
|
281
|
+
file_content = self._generate_file_content(content)
|
|
282
|
+
if DEBUG_LOG_AVAILABLE:
|
|
283
|
+
logger.log(f"生成文件内容,长度: {len(file_content)}", "SAVE")
|
|
284
|
+
|
|
285
|
+
# 写入文件
|
|
286
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
287
|
+
f.write(file_content)
|
|
288
|
+
|
|
289
|
+
# 记录保存成功
|
|
290
|
+
if DEBUG_LOG_AVAILABLE:
|
|
291
|
+
logger.log_save_operation(f"{path_type}指令", file_path, True)
|
|
292
|
+
|
|
293
|
+
# 先关闭对话框,确保界面刷新
|
|
294
|
+
if DEBUG_LOG_AVAILABLE:
|
|
295
|
+
logger.log("准备关闭对话框并发出变更信号", "SAVE")
|
|
296
|
+
self.accept() # 先关闭对话框,触发刷新
|
|
297
|
+
|
|
298
|
+
# 然后显示成功消息(即使失败也不影响功能)
|
|
299
|
+
try:
|
|
300
|
+
try:
|
|
301
|
+
relative_path = os.path.relpath(file_path)
|
|
302
|
+
except ValueError:
|
|
303
|
+
relative_path = file_path
|
|
304
|
+
|
|
305
|
+
QMessageBox.information(
|
|
306
|
+
self, "创建成功",
|
|
307
|
+
f"{path_type}指令文件已创建: {filename}\n位置: {relative_path}"
|
|
308
|
+
)
|
|
309
|
+
except Exception as msg_error:
|
|
310
|
+
if DEBUG_LOG_AVAILABLE:
|
|
311
|
+
logger.log_error("消息框显示", str(msg_error))
|
|
312
|
+
|
|
313
|
+
except Exception as e:
|
|
314
|
+
if DEBUG_LOG_AVAILABLE:
|
|
315
|
+
logger = get_debug_logger()
|
|
316
|
+
logger.log_save_operation("指令", file_path if 'file_path' in locals() else 'unknown', False, str(e))
|
|
317
|
+
QMessageBox.critical(
|
|
318
|
+
self, "创建失败",
|
|
319
|
+
f"创建指令文件时发生错误:\n{str(e)}"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def _is_valid_filename(self, filename: str) -> bool:
|
|
323
|
+
"""检查文件名是否合法"""
|
|
324
|
+
# 禁止的字符
|
|
325
|
+
invalid_chars = '<>:"/\\|?*'
|
|
326
|
+
for char in invalid_chars:
|
|
327
|
+
if char in filename:
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
# 禁止的文件名
|
|
331
|
+
invalid_names = [
|
|
332
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
333
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
334
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
|
|
335
|
+
]
|
|
336
|
+
if filename.upper() in invalid_names:
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
# 不能为空或只有空格
|
|
340
|
+
if not filename.strip():
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
return True
|
|
344
|
+
|
|
345
|
+
def _generate_file_content(self, content: str) -> str:
|
|
346
|
+
"""生成.md文件内容"""
|
|
347
|
+
# 从内容中提取前50个字符作为描述
|
|
348
|
+
description = content[:50].replace('\n', ' ').strip()
|
|
349
|
+
if len(content) > 50:
|
|
350
|
+
description += "..."
|
|
351
|
+
|
|
352
|
+
# 生成YAML前置内容
|
|
353
|
+
yaml_content = f"""---
|
|
354
|
+
description: {description}
|
|
355
|
+
globs:
|
|
356
|
+
alwaysApply: false
|
|
357
|
+
---
|
|
358
|
+
{content}
|
|
359
|
+
"""
|
|
360
|
+
return yaml_content
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class EditCommandDialog(QDialog):
|
|
364
|
+
"""编辑指令对话框"""
|
|
365
|
+
|
|
366
|
+
def __init__(self, project_path: str, command_data: Dict[str, Any], parent=None):
|
|
367
|
+
super().__init__(parent)
|
|
368
|
+
self.project_path = project_path
|
|
369
|
+
self.command_data = command_data
|
|
370
|
+
self.original_file_path = command_data.get('full_path', '')
|
|
371
|
+
|
|
372
|
+
# 获取路径配置实例
|
|
373
|
+
if PATH_CONFIG_AVAILABLE:
|
|
374
|
+
self.path_config = get_path_config()
|
|
375
|
+
else:
|
|
376
|
+
self.path_config = None
|
|
377
|
+
|
|
378
|
+
self.setWindowTitle("编辑指令")
|
|
379
|
+
self.setModal(True)
|
|
380
|
+
self.resize(500, 450)
|
|
381
|
+
|
|
382
|
+
self._create_ui()
|
|
383
|
+
self._apply_styles()
|
|
384
|
+
self._load_command_data()
|
|
385
|
+
|
|
386
|
+
# 连接信号
|
|
387
|
+
self.title_input.textChanged.connect(self._update_button_state)
|
|
388
|
+
self.content_input.textChanged.connect(self._update_button_state)
|
|
389
|
+
|
|
390
|
+
# 设置初始状态
|
|
391
|
+
self._update_button_state()
|
|
392
|
+
|
|
393
|
+
def _create_ui(self):
|
|
394
|
+
"""创建对话框UI"""
|
|
395
|
+
layout = QVBoxLayout(self)
|
|
396
|
+
layout.setSpacing(15)
|
|
397
|
+
layout.setContentsMargins(20, 20, 20, 20)
|
|
398
|
+
|
|
399
|
+
# 标题
|
|
400
|
+
title_label = QLabel("编辑自定义指令")
|
|
401
|
+
title_label.setAlignment(Qt.AlignCenter)
|
|
402
|
+
title_font = QFont()
|
|
403
|
+
title_font.setPointSize(14)
|
|
404
|
+
title_font.setBold(True)
|
|
405
|
+
title_label.setFont(title_font)
|
|
406
|
+
layout.addWidget(title_label)
|
|
407
|
+
|
|
408
|
+
# 表单区域
|
|
409
|
+
form_layout = QFormLayout()
|
|
410
|
+
form_layout.setSpacing(10)
|
|
411
|
+
|
|
412
|
+
# 指令类型选择(第一行)
|
|
413
|
+
type_widget = QWidget()
|
|
414
|
+
type_layout = QHBoxLayout(type_widget)
|
|
415
|
+
type_layout.setContentsMargins(0, 0, 0, 0)
|
|
416
|
+
self.type_button_group = QButtonGroup()
|
|
417
|
+
|
|
418
|
+
# 项目指令选项
|
|
419
|
+
self.project_radio = QRadioButton("项目指令")
|
|
420
|
+
display_path = self.path_config.get_project_commands_display_path() if self.path_config else ".cursor/rules/"
|
|
421
|
+
self.project_radio.setToolTip(f"保存到 {display_path} 目录,仅当前项目可用")
|
|
422
|
+
self.type_button_group.addButton(self.project_radio, 0)
|
|
423
|
+
type_layout.addWidget(self.project_radio)
|
|
424
|
+
|
|
425
|
+
# 私有指令选项
|
|
426
|
+
self.private_radio = QRadioButton("私有指令")
|
|
427
|
+
self.private_radio.setToolTip("保存到 prompts/ 目录,个人全局可用")
|
|
428
|
+
self.type_button_group.addButton(self.private_radio, 1)
|
|
429
|
+
type_layout.addWidget(self.private_radio)
|
|
430
|
+
|
|
431
|
+
type_layout.addStretch() # 推送按钮到左侧
|
|
432
|
+
form_layout.addRow("指令类型:", type_widget)
|
|
433
|
+
|
|
434
|
+
# 指令标题输入
|
|
435
|
+
self.title_input = QLineEdit()
|
|
436
|
+
self.title_input.setPlaceholderText("请输入指令标题(将作为文件名)")
|
|
437
|
+
self.title_input.setMinimumHeight(35)
|
|
438
|
+
form_layout.addRow("指令标题:", self.title_input)
|
|
439
|
+
|
|
440
|
+
layout.addLayout(form_layout)
|
|
441
|
+
|
|
442
|
+
# 指令内容输入
|
|
443
|
+
content_label = QLabel("指令内容:")
|
|
444
|
+
content_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
|
|
445
|
+
layout.addWidget(content_label)
|
|
446
|
+
|
|
447
|
+
self.content_input = QTextEdit()
|
|
448
|
+
self.content_input.setPlaceholderText("请输入指令的具体内容...")
|
|
449
|
+
self.content_input.setMinimumHeight(200)
|
|
450
|
+
layout.addWidget(self.content_input)
|
|
451
|
+
|
|
452
|
+
# 按钮区域
|
|
453
|
+
button_layout = QHBoxLayout()
|
|
454
|
+
button_layout.addStretch()
|
|
455
|
+
|
|
456
|
+
# 删除按钮已移除 - 用户需直接删除 .md 文件
|
|
457
|
+
|
|
458
|
+
# 取消按钮
|
|
459
|
+
cancel_button = QPushButton("取消")
|
|
460
|
+
cancel_button.setMinimumSize(80, 35)
|
|
461
|
+
cancel_button.clicked.connect(self.reject)
|
|
462
|
+
button_layout.addWidget(cancel_button)
|
|
463
|
+
|
|
464
|
+
# 保存按钮
|
|
465
|
+
self.save_button = QPushButton("💾 保存修改")
|
|
466
|
+
self.save_button.setMinimumSize(100, 35)
|
|
467
|
+
self.save_button.clicked.connect(self._save_command)
|
|
468
|
+
self.save_button.setDefault(True)
|
|
469
|
+
button_layout.addWidget(self.save_button)
|
|
470
|
+
|
|
471
|
+
layout.addLayout(button_layout)
|
|
472
|
+
|
|
473
|
+
def _apply_styles(self):
|
|
474
|
+
"""应用样式"""
|
|
475
|
+
self.setStyleSheet("""
|
|
476
|
+
QDialog {
|
|
477
|
+
background-color: #2b2b2b;
|
|
478
|
+
color: white;
|
|
479
|
+
}
|
|
480
|
+
QLabel {
|
|
481
|
+
color: white;
|
|
482
|
+
font-size: 12px;
|
|
483
|
+
}
|
|
484
|
+
QLineEdit {
|
|
485
|
+
background-color: #404040;
|
|
486
|
+
border: 1px solid #555;
|
|
487
|
+
border-radius: 4px;
|
|
488
|
+
padding: 8px;
|
|
489
|
+
color: white;
|
|
490
|
+
font-size: 12px;
|
|
491
|
+
}
|
|
492
|
+
QLineEdit:focus {
|
|
493
|
+
border-color: #2196F3;
|
|
494
|
+
}
|
|
495
|
+
QTextEdit {
|
|
496
|
+
background-color: #404040;
|
|
497
|
+
border: 1px solid #555;
|
|
498
|
+
border-radius: 4px;
|
|
499
|
+
padding: 8px;
|
|
500
|
+
color: white;
|
|
501
|
+
font-size: 12px;
|
|
502
|
+
}
|
|
503
|
+
QTextEdit:focus {
|
|
504
|
+
border-color: #2196F3;
|
|
505
|
+
}
|
|
506
|
+
QPushButton {
|
|
507
|
+
background-color: #666666;
|
|
508
|
+
color: white;
|
|
509
|
+
border: none;
|
|
510
|
+
padding: 8px 16px;
|
|
511
|
+
border-radius: 4px;
|
|
512
|
+
font-size: 12px;
|
|
513
|
+
}
|
|
514
|
+
QPushButton:hover {
|
|
515
|
+
background-color: #777777;
|
|
516
|
+
}
|
|
517
|
+
QPushButton:pressed {
|
|
518
|
+
background-color: #555555;
|
|
519
|
+
}
|
|
520
|
+
QPushButton:disabled {
|
|
521
|
+
background-color: #444444;
|
|
522
|
+
color: #888888;
|
|
523
|
+
}
|
|
524
|
+
QPushButton#create_button {
|
|
525
|
+
background-color: #4CAF50;
|
|
526
|
+
color: white;
|
|
527
|
+
}
|
|
528
|
+
QPushButton#create_button:hover {
|
|
529
|
+
background-color: #45a049;
|
|
530
|
+
color: white;
|
|
531
|
+
}
|
|
532
|
+
QPushButton#create_button:pressed {
|
|
533
|
+
background-color: #3d8b40;
|
|
534
|
+
color: white;
|
|
535
|
+
}
|
|
536
|
+
""")
|
|
537
|
+
|
|
538
|
+
# 设置保存按钮的特殊样式
|
|
539
|
+
self.save_button.setObjectName("create_button")
|
|
540
|
+
|
|
541
|
+
def _load_command_data(self):
|
|
542
|
+
"""加载现有指令数据"""
|
|
543
|
+
try:
|
|
544
|
+
# 设置标题
|
|
545
|
+
title = self.command_data.get('title', '')
|
|
546
|
+
# 移除.md扩展名(如果存在)
|
|
547
|
+
if title.endswith('.md'):
|
|
548
|
+
title = title[:-4]
|
|
549
|
+
self.title_input.setText(title)
|
|
550
|
+
|
|
551
|
+
# 设置指令类型(根据路径判断)
|
|
552
|
+
path_type = self.command_data.get('path_type', '')
|
|
553
|
+
# 始终为项目指令(已移除私有指令)
|
|
554
|
+
|
|
555
|
+
# 加载文件内容
|
|
556
|
+
if self.original_file_path and os.path.exists(self.original_file_path):
|
|
557
|
+
with open(self.original_file_path, 'r', encoding='utf-8') as f:
|
|
558
|
+
content = f.read()
|
|
559
|
+
|
|
560
|
+
# 解析YAML前置内容
|
|
561
|
+
if content.startswith('---'):
|
|
562
|
+
# 找到第二个---的位置
|
|
563
|
+
end_yaml = content.find('---', 3)
|
|
564
|
+
if end_yaml != -1:
|
|
565
|
+
# 提取实际内容(去掉YAML前置部分)
|
|
566
|
+
actual_content = content[end_yaml + 3:].strip()
|
|
567
|
+
self.content_input.setPlainText(actual_content)
|
|
568
|
+
else:
|
|
569
|
+
# 如果格式不正确,显示整个文件内容
|
|
570
|
+
self.content_input.setPlainText(content)
|
|
571
|
+
else:
|
|
572
|
+
# 如果没有YAML前置,显示整个文件内容
|
|
573
|
+
self.content_input.setPlainText(content)
|
|
574
|
+
|
|
575
|
+
except Exception as e:
|
|
576
|
+
QMessageBox.warning(
|
|
577
|
+
self, "加载失败",
|
|
578
|
+
f"加载指令文件时发生错误:\n{str(e)}"
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
def _update_button_state(self):
|
|
582
|
+
"""更新按钮状态"""
|
|
583
|
+
title = self.title_input.text().strip()
|
|
584
|
+
content = self.content_input.toPlainText().strip()
|
|
585
|
+
|
|
586
|
+
# 只有标题和内容都不为空时才启用保存按钮
|
|
587
|
+
self.save_button.setEnabled(bool(title and content))
|
|
588
|
+
|
|
589
|
+
def _save_command(self):
|
|
590
|
+
"""保存指令修改"""
|
|
591
|
+
title = self.title_input.text().strip()
|
|
592
|
+
content = self.content_input.toPlainText().strip()
|
|
593
|
+
|
|
594
|
+
# 验证输入
|
|
595
|
+
if not title:
|
|
596
|
+
QMessageBox.warning(self, "输入错误", "请输入指令标题")
|
|
597
|
+
return
|
|
598
|
+
|
|
599
|
+
if not content:
|
|
600
|
+
QMessageBox.warning(self, "输入错误", "请输入指令内容")
|
|
601
|
+
return
|
|
602
|
+
|
|
603
|
+
# 检查文件名是否合法
|
|
604
|
+
if not self._is_valid_filename(title):
|
|
605
|
+
QMessageBox.warning(self, "文件名错误",
|
|
606
|
+
"文件名包含非法字符,请使用字母、数字、下划线或中文")
|
|
607
|
+
return
|
|
608
|
+
|
|
609
|
+
try:
|
|
610
|
+
# 根据选择的类型确定保存目录
|
|
611
|
+
if self.project_radio.isChecked():
|
|
612
|
+
# 项目指令:使用路径配置
|
|
613
|
+
if not self.path_config:
|
|
614
|
+
QMessageBox.critical(self, "配置错误", "路径配置不可用,无法保存项目指令")
|
|
615
|
+
return
|
|
616
|
+
prompts_dir = self.path_config.ensure_project_commands_dir(self.project_path)
|
|
617
|
+
path_type = "项目"
|
|
618
|
+
else:
|
|
619
|
+
# 私有指令:保存到与脚本同目录的prompts/
|
|
620
|
+
if self.path_config:
|
|
621
|
+
prompts_dir = self.path_config.ensure_personal_commands_dir()
|
|
622
|
+
else:
|
|
623
|
+
# 降级处理
|
|
624
|
+
prompts_dir = os.path.join(self.project_path, "prompts")
|
|
625
|
+
os.makedirs(prompts_dir, exist_ok=True)
|
|
626
|
+
path_type = "私有"
|
|
627
|
+
|
|
628
|
+
# 生成新的文件路径
|
|
629
|
+
filename = f"{title}.md"
|
|
630
|
+
new_file_path = os.path.join(prompts_dir, filename)
|
|
631
|
+
|
|
632
|
+
# 如果文件路径或名称改变了,需要处理原文件
|
|
633
|
+
if new_file_path != self.original_file_path:
|
|
634
|
+
# 检查新文件是否已存在
|
|
635
|
+
if os.path.exists(new_file_path):
|
|
636
|
+
reply = QMessageBox.question(
|
|
637
|
+
self, "文件已存在",
|
|
638
|
+
f"指令文件 '{filename}' 已存在,是否覆盖?",
|
|
639
|
+
QMessageBox.Yes | QMessageBox.No,
|
|
640
|
+
QMessageBox.No
|
|
641
|
+
)
|
|
642
|
+
if reply == QMessageBox.No:
|
|
643
|
+
return
|
|
644
|
+
|
|
645
|
+
# 删除原文件(如果存在且不同于新文件)
|
|
646
|
+
if os.path.exists(self.original_file_path):
|
|
647
|
+
os.remove(self.original_file_path)
|
|
648
|
+
|
|
649
|
+
# 创建文件内容
|
|
650
|
+
file_content = self._generate_file_content(content)
|
|
651
|
+
|
|
652
|
+
# 写入文件
|
|
653
|
+
with open(new_file_path, 'w', encoding='utf-8') as f:
|
|
654
|
+
f.write(file_content)
|
|
655
|
+
|
|
656
|
+
# 显示成功消息
|
|
657
|
+
relative_path = os.path.relpath(new_file_path, os.getcwd()) if self.project_path else new_file_path
|
|
658
|
+
QMessageBox.information(
|
|
659
|
+
self, "保存成功",
|
|
660
|
+
f"{path_type}指令文件已保存: {filename}\n位置: {relative_path}"
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
# 关闭对话框
|
|
664
|
+
self.accept()
|
|
665
|
+
|
|
666
|
+
except Exception as e:
|
|
667
|
+
QMessageBox.critical(
|
|
668
|
+
self, "保存失败",
|
|
669
|
+
f"保存指令文件时发生错误:\n{str(e)}"
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
# 删除指令方法已移除 - 用户需直接删除 .md 文件
|
|
673
|
+
|
|
674
|
+
def _is_valid_filename(self, filename: str) -> bool:
|
|
675
|
+
"""检查文件名是否合法"""
|
|
676
|
+
# 禁止的字符
|
|
677
|
+
invalid_chars = '<>:"/\\|?*'
|
|
678
|
+
for char in invalid_chars:
|
|
679
|
+
if char in filename:
|
|
680
|
+
return False
|
|
681
|
+
|
|
682
|
+
# 禁止的文件名
|
|
683
|
+
invalid_names = [
|
|
684
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
685
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
686
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
|
|
687
|
+
]
|
|
688
|
+
if filename.upper() in invalid_names:
|
|
689
|
+
return False
|
|
690
|
+
|
|
691
|
+
# 不能为空或只有空格
|
|
692
|
+
if not filename.strip():
|
|
693
|
+
return False
|
|
694
|
+
|
|
695
|
+
return True
|
|
696
|
+
|
|
697
|
+
def _generate_file_content(self, content: str) -> str:
|
|
698
|
+
"""生成.md文件内容"""
|
|
699
|
+
# 从内容中提取前50个字符作为描述
|
|
700
|
+
description = content[:50].replace('\n', ' ').strip()
|
|
701
|
+
if len(content) > 50:
|
|
702
|
+
description += "..."
|
|
703
|
+
|
|
704
|
+
# 生成YAML前置内容
|
|
705
|
+
yaml_content = f"""---
|
|
706
|
+
description: {description}
|
|
707
|
+
globs:
|
|
708
|
+
alwaysApply: false
|
|
709
|
+
---
|
|
710
|
+
{content}
|
|
711
|
+
"""
|
|
712
|
+
return yaml_content
|