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
tabs/workspace_tab.py
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工作空间标签页 - 显示工作空间信息
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import platform
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from functools import partial
|
|
10
|
+
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLabel, QMessageBox, QGridLayout, QTreeWidget, QTreeWidgetItem
|
|
11
|
+
from PySide6.QtCore import Qt
|
|
12
|
+
from PySide6.QtGui import QIcon, QBrush, QColor
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from .base_tab import BaseTab
|
|
16
|
+
except ImportError:
|
|
17
|
+
from base_tab import BaseTab
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from ..components.markdown_display import MarkdownDisplayWidget
|
|
21
|
+
except ImportError:
|
|
22
|
+
try:
|
|
23
|
+
from components.markdown_display import MarkdownDisplayWidget
|
|
24
|
+
except ImportError:
|
|
25
|
+
from PySide6.QtWidgets import QTextEdit
|
|
26
|
+
MarkdownDisplayWidget = QTextEdit
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from ..workspace_manager import WorkspaceManager
|
|
30
|
+
except ImportError:
|
|
31
|
+
try:
|
|
32
|
+
from workspace_manager import WorkspaceManager
|
|
33
|
+
except ImportError:
|
|
34
|
+
WorkspaceManager = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WorkspaceTab(BaseTab):
|
|
38
|
+
"""工作空间标签页 - 显示工作空间详细信息"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, workspace_id: str, project_path: Optional[str] = None, parent=None):
|
|
41
|
+
super().__init__(parent)
|
|
42
|
+
self.workspace_id = workspace_id
|
|
43
|
+
self.project_path = project_path
|
|
44
|
+
self.workspace_config = None
|
|
45
|
+
self.stage_template = None
|
|
46
|
+
|
|
47
|
+
# 加载工作空间配置
|
|
48
|
+
self._load_workspace_config()
|
|
49
|
+
|
|
50
|
+
# 创建UI
|
|
51
|
+
self.create_ui()
|
|
52
|
+
|
|
53
|
+
def _load_workspace_config(self):
|
|
54
|
+
"""加载工作空间配置"""
|
|
55
|
+
if not WorkspaceManager:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
manager = WorkspaceManager(self.project_path)
|
|
60
|
+
self.workspace_config = manager.load_workspace_config(self.workspace_id)
|
|
61
|
+
|
|
62
|
+
# 加载阶段模板
|
|
63
|
+
if self.workspace_config:
|
|
64
|
+
stage_template_id = self.workspace_config.get('stage_template_id')
|
|
65
|
+
if stage_template_id:
|
|
66
|
+
self.stage_template = manager.load_stage_template(stage_template_id)
|
|
67
|
+
except Exception:
|
|
68
|
+
# 静默处理加载失败
|
|
69
|
+
self.workspace_config = None
|
|
70
|
+
self.stage_template = None
|
|
71
|
+
|
|
72
|
+
def create_ui(self):
|
|
73
|
+
"""创建工作空间标签页UI"""
|
|
74
|
+
layout = QVBoxLayout(self)
|
|
75
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
|
76
|
+
|
|
77
|
+
# 使用 MarkdownDisplayWidget 显示工作空间基本信息
|
|
78
|
+
display_widget = MarkdownDisplayWidget()
|
|
79
|
+
|
|
80
|
+
# 格式化工作空间信息为Markdown
|
|
81
|
+
markdown_content = self._format_workspace_info()
|
|
82
|
+
display_widget.setMarkdownText(markdown_content)
|
|
83
|
+
|
|
84
|
+
layout.addWidget(display_widget)
|
|
85
|
+
|
|
86
|
+
# 添加文件列表区域(如果有文件)
|
|
87
|
+
files = self.workspace_config.get('files', []) if self.workspace_config else []
|
|
88
|
+
if files:
|
|
89
|
+
self._create_files_section(layout, files)
|
|
90
|
+
|
|
91
|
+
def _format_workspace_info(self) -> str:
|
|
92
|
+
"""格式化工作空间信息为Markdown文本
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
str: 格式化的Markdown文本
|
|
96
|
+
"""
|
|
97
|
+
if not self.workspace_config:
|
|
98
|
+
return "## ⚠️ 无法加载工作空间配置\n\n请检查工作空间ID是否正确。"
|
|
99
|
+
|
|
100
|
+
parts = []
|
|
101
|
+
|
|
102
|
+
# 1. 工作空间基本信息
|
|
103
|
+
parts.append("## 📦 工作空间信息")
|
|
104
|
+
parts.append("")
|
|
105
|
+
parts.append(f"**ID:** `{self.workspace_id}`")
|
|
106
|
+
|
|
107
|
+
goal = self.workspace_config.get('goal', '未设置')
|
|
108
|
+
parts.append(f"**目标:** {goal}")
|
|
109
|
+
|
|
110
|
+
status = self.workspace_config.get('status', '未知')
|
|
111
|
+
parts.append(f"**状态:** {status}")
|
|
112
|
+
|
|
113
|
+
created_at = self.workspace_config.get('created_at', '未知')
|
|
114
|
+
parts.append(f"**创建时间:** {created_at}")
|
|
115
|
+
|
|
116
|
+
updated_at = self.workspace_config.get('updated_at', '未知')
|
|
117
|
+
parts.append(f"**更新时间:** {updated_at}")
|
|
118
|
+
|
|
119
|
+
parts.append("")
|
|
120
|
+
|
|
121
|
+
# 2. 阶段信息
|
|
122
|
+
parts.append("## 📍 阶段信息")
|
|
123
|
+
parts.append("")
|
|
124
|
+
|
|
125
|
+
stage_template_id = self.workspace_config.get('stage_template_id', '未设置')
|
|
126
|
+
parts.append(f"**模板:** `{stage_template_id}`")
|
|
127
|
+
|
|
128
|
+
current_stage_id = self.workspace_config.get('current_stage_id', '未设置')
|
|
129
|
+
parts.append(f"**当前阶段:** `{current_stage_id}`")
|
|
130
|
+
|
|
131
|
+
# 显示当前阶段详细信息
|
|
132
|
+
if self.stage_template and current_stage_id:
|
|
133
|
+
workflow = self.stage_template.get('workflow', {})
|
|
134
|
+
steps = workflow.get('steps', [])
|
|
135
|
+
|
|
136
|
+
for step in steps:
|
|
137
|
+
if step.get('id') == current_stage_id:
|
|
138
|
+
parts.append("")
|
|
139
|
+
parts.append(f"**阶段标题:** {step.get('title', '未知')}")
|
|
140
|
+
parts.append(f"**阶段描述:** {step.get('des', '无描述')}")
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
parts.append("")
|
|
144
|
+
|
|
145
|
+
# 3. 相关文档列表
|
|
146
|
+
documents = self.workspace_config.get('documents', [])
|
|
147
|
+
if documents:
|
|
148
|
+
parts.append("## 📄 相关文档")
|
|
149
|
+
parts.append("")
|
|
150
|
+
for doc in documents:
|
|
151
|
+
if isinstance(doc, dict):
|
|
152
|
+
title = doc.get('title', '未命名文档')
|
|
153
|
+
path = doc.get('path', '')
|
|
154
|
+
parts.append(f"- **{title}** (`{path}`)")
|
|
155
|
+
else:
|
|
156
|
+
parts.append(f"- `{doc}`")
|
|
157
|
+
parts.append("")
|
|
158
|
+
|
|
159
|
+
# 注意:相关文件列表改为独立组件显示,不再在Markdown中显示
|
|
160
|
+
|
|
161
|
+
return "\n".join(parts)
|
|
162
|
+
|
|
163
|
+
def _create_files_section(self, layout, files: list):
|
|
164
|
+
"""创建文件列表显示区域(树形结构)
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
layout: 父布局
|
|
168
|
+
files: 文件路径列表
|
|
169
|
+
"""
|
|
170
|
+
# 导入配置管理
|
|
171
|
+
try:
|
|
172
|
+
from feedback_config import FeedbackConfig
|
|
173
|
+
except ImportError:
|
|
174
|
+
FeedbackConfig = None
|
|
175
|
+
|
|
176
|
+
# 获取配置的IDE
|
|
177
|
+
def get_configured_ide():
|
|
178
|
+
"""获取配置的IDE名称,优先级:配置文件 > 环境变量 > 默认值"""
|
|
179
|
+
ide_name = None
|
|
180
|
+
|
|
181
|
+
# 1. 尝试从配置文件读取
|
|
182
|
+
if FeedbackConfig and self.project_path:
|
|
183
|
+
try:
|
|
184
|
+
config_manager = FeedbackConfig(self.project_path)
|
|
185
|
+
ide_name = config_manager.get_ide()
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
# 2. 如果配置文件没有,使用环境变量
|
|
190
|
+
if not ide_name:
|
|
191
|
+
ide_name = os.getenv('IDE')
|
|
192
|
+
|
|
193
|
+
# 3. 最后使用默认值
|
|
194
|
+
if not ide_name:
|
|
195
|
+
ide_name = 'cursor'
|
|
196
|
+
|
|
197
|
+
return ide_name
|
|
198
|
+
|
|
199
|
+
# 文件去重并保持顺序
|
|
200
|
+
unique_files = list(dict.fromkeys(files))
|
|
201
|
+
|
|
202
|
+
# 路径规范化(移除tag前缀 + 相对路径转绝对路径)
|
|
203
|
+
normalized_files = []
|
|
204
|
+
for file_path in unique_files:
|
|
205
|
+
# 移除路径开头的tag(如 Edit:, Create:, Read: 等)
|
|
206
|
+
cleaned_path = re.sub(r'^[A-Za-z]+:', '', file_path)
|
|
207
|
+
|
|
208
|
+
if not os.path.isabs(cleaned_path) and self.project_path:
|
|
209
|
+
# 相对路径转绝对路径
|
|
210
|
+
abs_path = os.path.join(self.project_path, cleaned_path)
|
|
211
|
+
normalized_files.append(abs_path)
|
|
212
|
+
else:
|
|
213
|
+
normalized_files.append(cleaned_path)
|
|
214
|
+
|
|
215
|
+
# 创建文件列表标题
|
|
216
|
+
title_container = QWidget()
|
|
217
|
+
title_layout = QHBoxLayout(title_container)
|
|
218
|
+
title_layout.setContentsMargins(5, 10, 5, 5)
|
|
219
|
+
title_layout.setSpacing(5)
|
|
220
|
+
|
|
221
|
+
title_label = QLabel("📁 相关文件")
|
|
222
|
+
title_label.setStyleSheet("""
|
|
223
|
+
QLabel {
|
|
224
|
+
font-size: 14px;
|
|
225
|
+
font-weight: bold;
|
|
226
|
+
color: #FFA500;
|
|
227
|
+
padding: 5px 0;
|
|
228
|
+
}
|
|
229
|
+
""")
|
|
230
|
+
title_layout.addWidget(title_label)
|
|
231
|
+
title_layout.addStretch()
|
|
232
|
+
|
|
233
|
+
layout.addWidget(title_container)
|
|
234
|
+
|
|
235
|
+
# 找到所有文件的公共父目录
|
|
236
|
+
common_prefix = self._find_common_prefix(normalized_files)
|
|
237
|
+
|
|
238
|
+
# 创建文件树
|
|
239
|
+
tree_widget = QTreeWidget()
|
|
240
|
+
tree_widget.setHeaderHidden(True)
|
|
241
|
+
tree_widget.setStyleSheet("""
|
|
242
|
+
QTreeWidget {
|
|
243
|
+
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
|
244
|
+
stop:0 rgba(76, 175, 80, 8),
|
|
245
|
+
stop:1 rgba(76, 175, 80, 12));
|
|
246
|
+
border: 2px solid rgba(76, 175, 80, 35);
|
|
247
|
+
border-radius: 8px;
|
|
248
|
+
padding: 4px;
|
|
249
|
+
font-size: 13px;
|
|
250
|
+
outline: none;
|
|
251
|
+
selection-background-color: transparent;
|
|
252
|
+
}
|
|
253
|
+
QTreeWidget::item {
|
|
254
|
+
padding: 3px 6px;
|
|
255
|
+
margin: 0px;
|
|
256
|
+
border-radius: 4px;
|
|
257
|
+
color: #2E7D32;
|
|
258
|
+
min-height: 18px;
|
|
259
|
+
selection-background-color: transparent;
|
|
260
|
+
}
|
|
261
|
+
QTreeWidget::item:hover {
|
|
262
|
+
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
263
|
+
stop:0 rgba(76, 175, 80, 25),
|
|
264
|
+
stop:1 rgba(129, 199, 132, 25));
|
|
265
|
+
border-left: 3px solid #4CAF50;
|
|
266
|
+
padding-left: 3px;
|
|
267
|
+
}
|
|
268
|
+
QTreeWidget::item:selected {
|
|
269
|
+
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
270
|
+
stop:0 rgba(76, 175, 80, 60),
|
|
271
|
+
stop:1 rgba(129, 199, 132, 60));
|
|
272
|
+
border-left: 3px solid #66BB6A;
|
|
273
|
+
padding-left: 3px;
|
|
274
|
+
color: #FFFFFF;
|
|
275
|
+
font-weight: 600;
|
|
276
|
+
selection-background-color: transparent;
|
|
277
|
+
}
|
|
278
|
+
QTreeWidget::item:selected:!active {
|
|
279
|
+
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
280
|
+
stop:0 rgba(76, 175, 80, 60),
|
|
281
|
+
stop:1 rgba(129, 199, 132, 60));
|
|
282
|
+
color: #FFFFFF;
|
|
283
|
+
border-left: 3px solid #66BB6A;
|
|
284
|
+
padding-left: 3px;
|
|
285
|
+
}
|
|
286
|
+
QTreeWidget::item:selected:hover {
|
|
287
|
+
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
288
|
+
stop:0 rgba(76, 175, 80, 70),
|
|
289
|
+
stop:1 rgba(129, 199, 132, 70));
|
|
290
|
+
border-left: 3px solid #81C784;
|
|
291
|
+
color: #FFFFFF;
|
|
292
|
+
}
|
|
293
|
+
QTreeWidget::branch {
|
|
294
|
+
background: transparent;
|
|
295
|
+
}
|
|
296
|
+
QTreeWidget::branch:has-children:!has-siblings:closed,
|
|
297
|
+
QTreeWidget::branch:closed:has-children:has-siblings {
|
|
298
|
+
border-image: none;
|
|
299
|
+
image: url(none);
|
|
300
|
+
margin: 2px;
|
|
301
|
+
}
|
|
302
|
+
QTreeWidget::branch:open:has-children:!has-siblings,
|
|
303
|
+
QTreeWidget::branch:open:has-children:has-siblings {
|
|
304
|
+
border-image: none;
|
|
305
|
+
image: url(none);
|
|
306
|
+
margin: 2px;
|
|
307
|
+
}
|
|
308
|
+
""")
|
|
309
|
+
|
|
310
|
+
# 构建树形结构
|
|
311
|
+
root_name = os.path.basename(common_prefix) if common_prefix else "Files"
|
|
312
|
+
tree_root = {} # 存储目录结构的字典树
|
|
313
|
+
|
|
314
|
+
for file_path in normalized_files:
|
|
315
|
+
# 获取相对路径
|
|
316
|
+
if common_prefix:
|
|
317
|
+
try:
|
|
318
|
+
rel_path = os.path.relpath(file_path, common_prefix)
|
|
319
|
+
except ValueError:
|
|
320
|
+
# 如果无法获取相对路径(例如不同盘符),使用绝对路径
|
|
321
|
+
rel_path = file_path
|
|
322
|
+
else:
|
|
323
|
+
rel_path = file_path
|
|
324
|
+
|
|
325
|
+
# 分割路径
|
|
326
|
+
parts = rel_path.split(os.sep)
|
|
327
|
+
|
|
328
|
+
# 构建树形结构
|
|
329
|
+
current = tree_root
|
|
330
|
+
for part in parts:
|
|
331
|
+
if part not in current:
|
|
332
|
+
current[part] = {}
|
|
333
|
+
current = current[part]
|
|
334
|
+
|
|
335
|
+
# 递归创建树节点
|
|
336
|
+
def create_tree_items(parent_item, tree_dict, current_path):
|
|
337
|
+
"""递归创建树节点
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
parent_item: 父节点(QTreeWidget或QTreeWidgetItem)
|
|
341
|
+
tree_dict: 当前层级的字典树
|
|
342
|
+
current_path: 当前路径
|
|
343
|
+
"""
|
|
344
|
+
for name in sorted(tree_dict.keys()):
|
|
345
|
+
full_path = os.path.join(current_path, name) if current_path else name
|
|
346
|
+
abs_path = os.path.join(common_prefix, full_path) if common_prefix else full_path
|
|
347
|
+
|
|
348
|
+
# 创建节点
|
|
349
|
+
item = QTreeWidgetItem(parent_item)
|
|
350
|
+
item.setText(0, name)
|
|
351
|
+
|
|
352
|
+
# 判断是文件还是目录
|
|
353
|
+
if tree_dict[name]: # 有子节点,是目录
|
|
354
|
+
item.setIcon(0, self._get_folder_icon())
|
|
355
|
+
# 设置目录样式 - 更加突出
|
|
356
|
+
font = item.font(0)
|
|
357
|
+
font.setBold(True)
|
|
358
|
+
font.setPointSize(13)
|
|
359
|
+
item.setFont(0, font)
|
|
360
|
+
# 设置目录颜色为深绿色
|
|
361
|
+
item.setForeground(0, QBrush(QColor(27, 94, 32))) # 深绿色
|
|
362
|
+
# 递归创建子节点
|
|
363
|
+
create_tree_items(item, tree_dict[name], full_path)
|
|
364
|
+
else: # 没有子节点,是文件
|
|
365
|
+
item.setIcon(0, self._get_file_icon())
|
|
366
|
+
# 设置文件颜色为中等绿色
|
|
367
|
+
item.setForeground(0, QBrush(QColor(56, 142, 60))) # 中绿色
|
|
368
|
+
# 保存文件路径到item数据中
|
|
369
|
+
item.setData(0, Qt.UserRole, abs_path)
|
|
370
|
+
# 设置工具提示
|
|
371
|
+
ide_name = get_configured_ide()
|
|
372
|
+
ide_display_names = {
|
|
373
|
+
'cursor': 'Cursor',
|
|
374
|
+
'kiro': 'Kiro',
|
|
375
|
+
'vscode': 'VSCode',
|
|
376
|
+
'code': 'VSCode'
|
|
377
|
+
}
|
|
378
|
+
display_ide = ide_display_names.get(ide_name.lower(), ide_name)
|
|
379
|
+
item.setToolTip(0, f"双击在{display_ide}中打开: {abs_path}")
|
|
380
|
+
|
|
381
|
+
# 创建根节点并添加所有文件
|
|
382
|
+
create_tree_items(tree_widget, tree_root, "")
|
|
383
|
+
|
|
384
|
+
# 展开所有节点
|
|
385
|
+
tree_widget.expandAll()
|
|
386
|
+
|
|
387
|
+
# 连接双击事件
|
|
388
|
+
def on_item_double_clicked(item, column):
|
|
389
|
+
"""处理节点双击事件"""
|
|
390
|
+
file_path = item.data(0, Qt.UserRole)
|
|
391
|
+
if not file_path: # 如果是目录节点,切换展开/折叠
|
|
392
|
+
if item.isExpanded():
|
|
393
|
+
item.setExpanded(False)
|
|
394
|
+
else:
|
|
395
|
+
item.setExpanded(True)
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
# 打开文件
|
|
399
|
+
try:
|
|
400
|
+
# 导入ide_utils模块
|
|
401
|
+
import sys
|
|
402
|
+
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
403
|
+
if parent_dir not in sys.path:
|
|
404
|
+
sys.path.insert(0, parent_dir)
|
|
405
|
+
from ide_utils import open_project_with_ide
|
|
406
|
+
|
|
407
|
+
# 获取IDE名称(使用配置)
|
|
408
|
+
ide_name = get_configured_ide()
|
|
409
|
+
|
|
410
|
+
# 使用通用的IDE打开函数
|
|
411
|
+
success = open_project_with_ide(file_path, ide_name)
|
|
412
|
+
|
|
413
|
+
if not success:
|
|
414
|
+
# 如果失败,使用系统默认编辑器打开
|
|
415
|
+
if platform.system() == "Darwin":
|
|
416
|
+
subprocess.run(["open", file_path], check=True)
|
|
417
|
+
elif platform.system() == "Windows":
|
|
418
|
+
os.startfile(file_path)
|
|
419
|
+
else:
|
|
420
|
+
subprocess.run(["xdg-open", file_path], check=True)
|
|
421
|
+
|
|
422
|
+
except Exception as e:
|
|
423
|
+
# 使用系统默认编辑器打开作为最终后备
|
|
424
|
+
try:
|
|
425
|
+
if platform.system() == "Darwin":
|
|
426
|
+
subprocess.run(["open", file_path], check=True)
|
|
427
|
+
elif platform.system() == "Windows":
|
|
428
|
+
os.startfile(file_path)
|
|
429
|
+
else:
|
|
430
|
+
subprocess.run(["xdg-open", file_path], check=True)
|
|
431
|
+
except Exception as e2:
|
|
432
|
+
file_name = os.path.basename(file_path)
|
|
433
|
+
QMessageBox.warning(self, "打开失败",
|
|
434
|
+
f"无法打开文件: {file_name}\n"
|
|
435
|
+
f"路径: {file_path}\n"
|
|
436
|
+
f"错误: {str(e2)}")
|
|
437
|
+
|
|
438
|
+
tree_widget.itemDoubleClicked.connect(on_item_double_clicked)
|
|
439
|
+
|
|
440
|
+
layout.addWidget(tree_widget)
|
|
441
|
+
|
|
442
|
+
def _find_common_prefix(self, paths: list) -> str:
|
|
443
|
+
"""找到所有路径的公共父目录
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
paths: 文件路径列表
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
str: 公共父目录路径
|
|
450
|
+
"""
|
|
451
|
+
if not paths:
|
|
452
|
+
return ""
|
|
453
|
+
|
|
454
|
+
if len(paths) == 1:
|
|
455
|
+
return os.path.dirname(paths[0])
|
|
456
|
+
|
|
457
|
+
# 分割所有路径
|
|
458
|
+
split_paths = [p.split(os.sep) for p in paths]
|
|
459
|
+
|
|
460
|
+
# 找到最短路径的长度
|
|
461
|
+
min_len = min(len(p) for p in split_paths)
|
|
462
|
+
|
|
463
|
+
# 找到公共前缀
|
|
464
|
+
common = []
|
|
465
|
+
for i in range(min_len):
|
|
466
|
+
parts = [p[i] for p in split_paths]
|
|
467
|
+
if len(set(parts)) == 1: # 所有路径在这一层都相同
|
|
468
|
+
common.append(parts[0])
|
|
469
|
+
else:
|
|
470
|
+
break
|
|
471
|
+
|
|
472
|
+
# 如果找到公共前缀,返回公共目录
|
|
473
|
+
if common:
|
|
474
|
+
return os.sep.join(common)
|
|
475
|
+
|
|
476
|
+
# 如果没有公共前缀,返回空字符串
|
|
477
|
+
return ""
|
|
478
|
+
|
|
479
|
+
def _get_folder_icon(self) -> QIcon:
|
|
480
|
+
"""获取文件夹图标
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
QIcon: 文件夹图标
|
|
484
|
+
"""
|
|
485
|
+
# 使用系统提供的文件夹图标或自定义图标
|
|
486
|
+
# 这里使用标准图标,也可以自定义
|
|
487
|
+
from PySide6.QtWidgets import QStyle
|
|
488
|
+
style = self.style()
|
|
489
|
+
icon = style.standardIcon(QStyle.SP_DirIcon)
|
|
490
|
+
return icon
|
|
491
|
+
|
|
492
|
+
def _get_file_icon(self) -> QIcon:
|
|
493
|
+
"""获取文件图标
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
QIcon: 文件图标
|
|
497
|
+
"""
|
|
498
|
+
# 使用系统提供的文件图标或自定义图标
|
|
499
|
+
from PySide6.QtWidgets import QStyle
|
|
500
|
+
style = self.style()
|
|
501
|
+
icon = style.standardIcon(QStyle.SP_FileIcon)
|
|
502
|
+
return icon
|
ui/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UI 模块 - 导出UI组件
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# 紧凑反馈界面
|
|
6
|
+
try:
|
|
7
|
+
from .compact_feedback_ui import CompactFeedbackUI
|
|
8
|
+
except ImportError:
|
|
9
|
+
CompactFeedbackUI = None
|
|
10
|
+
|
|
11
|
+
# 会话列表界面
|
|
12
|
+
try:
|
|
13
|
+
from .session_list_ui import SessionListUI
|
|
14
|
+
except ImportError:
|
|
15
|
+
SessionListUI = None
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'CompactFeedbackUI',
|
|
19
|
+
'SessionListUI'
|
|
20
|
+
]
|
ui/__main__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
会话列表UI模块入口
|
|
3
|
+
支持: python -m src-min.ui (本地开发) 或 python -m ui (PyPI安装)
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
from PySide6.QtWidgets import QApplication
|
|
7
|
+
from .session_list_ui import SessionListUI
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
app = QApplication(sys.argv)
|
|
11
|
+
window = SessionListUI()
|
|
12
|
+
window.show()
|
|
13
|
+
sys.exit(app.exec())
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|