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,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent 内容弹窗组件
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
from PySide6.QtWidgets import (
|
|
7
|
+
QFrame, QVBoxLayout, QLabel, QPushButton, QHBoxLayout, QApplication
|
|
8
|
+
)
|
|
9
|
+
from PySide6.QtCore import Qt, Signal, QPoint
|
|
10
|
+
from PySide6.QtGui import QKeyEvent
|
|
11
|
+
|
|
12
|
+
from .markdown_display import MarkdownDisplayWidget
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentPopup(QFrame):
|
|
16
|
+
"""Agent 内容弹窗组件"""
|
|
17
|
+
|
|
18
|
+
popup_closed = Signal()
|
|
19
|
+
|
|
20
|
+
def __init__(self, parent=None):
|
|
21
|
+
super().__init__(parent)
|
|
22
|
+
self._setup_ui()
|
|
23
|
+
self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
|
|
24
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
25
|
+
|
|
26
|
+
def _setup_ui(self):
|
|
27
|
+
"""设置UI"""
|
|
28
|
+
layout = QVBoxLayout(self)
|
|
29
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
30
|
+
layout.setSpacing(0)
|
|
31
|
+
|
|
32
|
+
# 标题栏
|
|
33
|
+
title_frame = QFrame()
|
|
34
|
+
title_frame.setStyleSheet("""
|
|
35
|
+
QFrame {
|
|
36
|
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
|
37
|
+
border-top-left-radius: 8px;
|
|
38
|
+
border-top-right-radius: 8px;
|
|
39
|
+
}
|
|
40
|
+
""")
|
|
41
|
+
title_layout = QHBoxLayout(title_frame)
|
|
42
|
+
title_layout.setContentsMargins(12, 8, 12, 8)
|
|
43
|
+
|
|
44
|
+
self.title_label = QLabel()
|
|
45
|
+
self.title_label.setStyleSheet("""
|
|
46
|
+
QLabel {
|
|
47
|
+
color: white;
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
font-weight: bold;
|
|
50
|
+
}
|
|
51
|
+
""")
|
|
52
|
+
title_layout.addWidget(self.title_label)
|
|
53
|
+
|
|
54
|
+
close_button = QPushButton("✕")
|
|
55
|
+
close_button.setFixedSize(20, 20)
|
|
56
|
+
close_button.setStyleSheet("""
|
|
57
|
+
QPushButton {
|
|
58
|
+
background: transparent;
|
|
59
|
+
color: white;
|
|
60
|
+
border: none;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
font-weight: bold;
|
|
63
|
+
}
|
|
64
|
+
QPushButton:hover {
|
|
65
|
+
background: rgba(255, 255, 255, 0.2);
|
|
66
|
+
border-radius: 10px;
|
|
67
|
+
}
|
|
68
|
+
""")
|
|
69
|
+
close_button.clicked.connect(self.close)
|
|
70
|
+
title_layout.addWidget(close_button)
|
|
71
|
+
|
|
72
|
+
layout.addWidget(title_frame)
|
|
73
|
+
|
|
74
|
+
# 内容区域
|
|
75
|
+
self.content_widget = MarkdownDisplayWidget()
|
|
76
|
+
self.content_widget.setStyleSheet("""
|
|
77
|
+
QTextEdit {
|
|
78
|
+
background: #1e1e1e;
|
|
79
|
+
border: none;
|
|
80
|
+
padding: 12px;
|
|
81
|
+
color: #e0e0e0;
|
|
82
|
+
font-size: 13px;
|
|
83
|
+
}
|
|
84
|
+
QScrollBar:vertical {
|
|
85
|
+
background: #2b2b2b;
|
|
86
|
+
width: 8px;
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
}
|
|
89
|
+
QScrollBar::handle:vertical {
|
|
90
|
+
background: #555;
|
|
91
|
+
border-radius: 4px;
|
|
92
|
+
min-height: 30px;
|
|
93
|
+
}
|
|
94
|
+
QScrollBar::handle:vertical:hover {
|
|
95
|
+
background: #666;
|
|
96
|
+
}
|
|
97
|
+
""")
|
|
98
|
+
layout.addWidget(self.content_widget, 1)
|
|
99
|
+
|
|
100
|
+
# 底部统计栏
|
|
101
|
+
stats_frame = QFrame()
|
|
102
|
+
stats_frame.setStyleSheet("""
|
|
103
|
+
QFrame {
|
|
104
|
+
background: #2b2b2b;
|
|
105
|
+
border-bottom-left-radius: 8px;
|
|
106
|
+
border-bottom-right-radius: 8px;
|
|
107
|
+
padding: 8px;
|
|
108
|
+
}
|
|
109
|
+
""")
|
|
110
|
+
stats_layout = QHBoxLayout(stats_frame)
|
|
111
|
+
|
|
112
|
+
self.stats_label = QLabel()
|
|
113
|
+
self.stats_label.setStyleSheet("""
|
|
114
|
+
QLabel {
|
|
115
|
+
color: #888;
|
|
116
|
+
font-size: 11px;
|
|
117
|
+
}
|
|
118
|
+
""")
|
|
119
|
+
stats_layout.addWidget(self.stats_label, alignment=Qt.AlignCenter)
|
|
120
|
+
|
|
121
|
+
layout.addWidget(stats_frame)
|
|
122
|
+
|
|
123
|
+
# 整体样式
|
|
124
|
+
self.setStyleSheet("""
|
|
125
|
+
AgentPopup {
|
|
126
|
+
background: #1e1e1e;
|
|
127
|
+
border: 1px solid #444;
|
|
128
|
+
border-radius: 8px;
|
|
129
|
+
}
|
|
130
|
+
""")
|
|
131
|
+
|
|
132
|
+
def set_agent_data(self, agent_record: Dict):
|
|
133
|
+
"""设置 agent 数据"""
|
|
134
|
+
subagent_type = agent_record.get('subagent_type', 'Agent')
|
|
135
|
+
description = agent_record.get('description', '')
|
|
136
|
+
content = agent_record.get('content', '')
|
|
137
|
+
duration_ms = agent_record.get('duration_ms', 0)
|
|
138
|
+
tokens = agent_record.get('tokens', 0)
|
|
139
|
+
tool_calls = agent_record.get('tool_calls', 0)
|
|
140
|
+
|
|
141
|
+
# 设置标题
|
|
142
|
+
self.title_label.setText(f"{subagent_type}: {description}")
|
|
143
|
+
|
|
144
|
+
# 设置内容
|
|
145
|
+
self.content_widget.setMarkdownText(content)
|
|
146
|
+
|
|
147
|
+
# 设置统计信息
|
|
148
|
+
stats_text = f"耗时: {duration_ms}ms | Tokens: {tokens} | 工具调用: {tool_calls}次"
|
|
149
|
+
self.stats_label.setText(stats_text)
|
|
150
|
+
|
|
151
|
+
def show_at_position(self, position: QPoint):
|
|
152
|
+
"""在指定位置显示弹窗"""
|
|
153
|
+
screen = QApplication.primaryScreen()
|
|
154
|
+
screen_geometry = screen.availableGeometry()
|
|
155
|
+
|
|
156
|
+
popup_width = 500
|
|
157
|
+
popup_height = 700
|
|
158
|
+
|
|
159
|
+
self.resize(popup_width, popup_height)
|
|
160
|
+
|
|
161
|
+
if position and position.x() >= 0 and position.y() >= 0:
|
|
162
|
+
x = position.x()
|
|
163
|
+
y = position.y()
|
|
164
|
+
|
|
165
|
+
if x + popup_width > screen_geometry.right():
|
|
166
|
+
x = screen_geometry.right() - popup_width - 10
|
|
167
|
+
if y + popup_height > screen_geometry.bottom():
|
|
168
|
+
y = screen_geometry.bottom() - popup_height - 10
|
|
169
|
+
if x < screen_geometry.x():
|
|
170
|
+
x = screen_geometry.x()
|
|
171
|
+
if y < screen_geometry.y():
|
|
172
|
+
y = screen_geometry.y()
|
|
173
|
+
else:
|
|
174
|
+
x = screen_geometry.x() + (screen_geometry.width() - popup_width) // 2
|
|
175
|
+
y = screen_geometry.y() + (screen_geometry.height() - popup_height) // 2
|
|
176
|
+
|
|
177
|
+
self.move(x, y)
|
|
178
|
+
self.show()
|
|
179
|
+
self.setFocus()
|
|
180
|
+
|
|
181
|
+
def keyPressEvent(self, event: QKeyEvent):
|
|
182
|
+
"""处理键盘事件"""
|
|
183
|
+
if event.key() == Qt.Key_Escape:
|
|
184
|
+
self.popup_closed.emit()
|
|
185
|
+
self.close()
|
|
186
|
+
else:
|
|
187
|
+
super().keyPressEvent(event)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""
|
|
2
|
+
聊天历史记录管理模块
|
|
3
|
+
|
|
4
|
+
功能:
|
|
5
|
+
1. 保存用户输入的聊天内容
|
|
6
|
+
2. 加载和显示历史记录
|
|
7
|
+
3. 提供弹窗方式显示历史记录
|
|
8
|
+
4. 支持插入和复制功能
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import List, Dict, Optional
|
|
15
|
+
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
16
|
+
from PySide6.QtCore import QPoint
|
|
17
|
+
|
|
18
|
+
# 导入历史记录弹窗组件
|
|
19
|
+
try:
|
|
20
|
+
from .history_popup import HistoryPopup
|
|
21
|
+
HISTORY_POPUP_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
try:
|
|
24
|
+
from history_popup import HistoryPopup
|
|
25
|
+
HISTORY_POPUP_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
HISTORY_POPUP_AVAILABLE = False
|
|
28
|
+
print("Warning: HistoryPopup component not available")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ChatHistoryManager:
|
|
32
|
+
"""聊天历史记录管理器"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, project_path: Optional[str] = None, session_id: Optional[str] = None):
|
|
35
|
+
self.project_path = project_path
|
|
36
|
+
self.session_id = session_id
|
|
37
|
+
|
|
38
|
+
def get_history_file_path(self) -> Optional[str]:
|
|
39
|
+
"""获取历史记录文件路径"""
|
|
40
|
+
# 如果没有session_id,返回None
|
|
41
|
+
if not self.session_id:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
if self.project_path:
|
|
45
|
+
return os.path.join(self.project_path, '.workspace', 'chat_history', f'{self.session_id}.json')
|
|
46
|
+
else:
|
|
47
|
+
# 如果没有项目路径,使用脚本目录
|
|
48
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
49
|
+
return os.path.join(script_dir, '..', '.workspace', 'chat_history', f'{self.session_id}.json')
|
|
50
|
+
|
|
51
|
+
def save_to_history(self, content: str) -> bool:
|
|
52
|
+
"""保存内容到历史记录
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
content: 要保存的内容
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
bool: 保存是否成功
|
|
59
|
+
"""
|
|
60
|
+
if not content.strip():
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# 获取历史记录文件路径
|
|
65
|
+
history_file = self.get_history_file_path()
|
|
66
|
+
|
|
67
|
+
# 如果没有session_id,静默跳过
|
|
68
|
+
if not history_file:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
# 读取完整的文件数据
|
|
72
|
+
file_data = {}
|
|
73
|
+
if os.path.exists(history_file):
|
|
74
|
+
with open(history_file, 'r', encoding='utf-8') as f:
|
|
75
|
+
data = json.load(f)
|
|
76
|
+
|
|
77
|
+
# 新格式:{'dialogues': [...], 'control': {...}}
|
|
78
|
+
if isinstance(data, dict) and 'dialogues' in data:
|
|
79
|
+
file_data = data
|
|
80
|
+
# 旧格式数组,转换为新格式
|
|
81
|
+
elif isinstance(data, list):
|
|
82
|
+
file_data = {
|
|
83
|
+
'dialogues': [record for record in data if isinstance(record, dict) and record.get('type') != 'stop_hook_status']
|
|
84
|
+
}
|
|
85
|
+
else:
|
|
86
|
+
file_data = {'dialogues': []}
|
|
87
|
+
else:
|
|
88
|
+
# 文件不存在,初始化为新格式
|
|
89
|
+
file_data = {'dialogues': []}
|
|
90
|
+
|
|
91
|
+
# 添加新记录到dialogues数组
|
|
92
|
+
new_record = {
|
|
93
|
+
'content': content.strip(),
|
|
94
|
+
'timestamp': datetime.now().isoformat(),
|
|
95
|
+
'time_display': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# 确保dialogues字段存在
|
|
99
|
+
if 'dialogues' not in file_data:
|
|
100
|
+
file_data['dialogues'] = []
|
|
101
|
+
|
|
102
|
+
file_data['dialogues'].append(new_record)
|
|
103
|
+
|
|
104
|
+
# 保存完整的文件数据
|
|
105
|
+
os.makedirs(os.path.dirname(history_file), exist_ok=True)
|
|
106
|
+
with open(history_file, 'w', encoding='utf-8') as f:
|
|
107
|
+
json.dump(file_data, f, ensure_ascii=False, indent=2)
|
|
108
|
+
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"保存历史记录失败: {e}")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def load_history_from_file(self) -> List[Dict]:
|
|
116
|
+
"""从文件加载历史记录(兼容旧格式和新格式)"""
|
|
117
|
+
try:
|
|
118
|
+
history_file = self.get_history_file_path()
|
|
119
|
+
|
|
120
|
+
# 如果没有session_id,返回空列表
|
|
121
|
+
if not history_file:
|
|
122
|
+
return []
|
|
123
|
+
|
|
124
|
+
if os.path.exists(history_file):
|
|
125
|
+
with open(history_file, 'r', encoding='utf-8') as f:
|
|
126
|
+
data = json.load(f)
|
|
127
|
+
|
|
128
|
+
# 新格式:{'dialogues': [...], 'control': {...}}
|
|
129
|
+
if isinstance(data, dict) and 'dialogues' in data:
|
|
130
|
+
# 返回dialogues数组
|
|
131
|
+
return data.get('dialogues', [])
|
|
132
|
+
|
|
133
|
+
# 旧格式数组
|
|
134
|
+
if isinstance(data, list):
|
|
135
|
+
# 过滤掉control记录,保留对话记录
|
|
136
|
+
return [record for record in data if isinstance(record, dict) and record.get('type') != 'stop_hook_status']
|
|
137
|
+
|
|
138
|
+
return []
|
|
139
|
+
return []
|
|
140
|
+
except Exception as e:
|
|
141
|
+
print(f"加载历史记录失败: {e}")
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
def get_recent_history(self, count: Optional[int] = None) -> List[Dict]:
|
|
145
|
+
"""获取最近的历史记录
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
count: 获取记录数量,如果为None则返回所有记录
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List[Dict]: 历史记录列表
|
|
152
|
+
"""
|
|
153
|
+
history = self.load_history_from_file()
|
|
154
|
+
if count is None:
|
|
155
|
+
return history # 返回所有历史记录
|
|
156
|
+
return history[-count:]
|
|
157
|
+
|
|
158
|
+
def get_agent_records_after_last_user(self) -> List[Dict]:
|
|
159
|
+
"""获取最后一条用户消息之后的所有 agent 记录"""
|
|
160
|
+
history = self.load_history_from_file()
|
|
161
|
+
|
|
162
|
+
# 找到最后一条用户消息的索引
|
|
163
|
+
last_user_idx = -1
|
|
164
|
+
for i, record in enumerate(history):
|
|
165
|
+
if record.get('role') == 'user' or 'messages' in record:
|
|
166
|
+
last_user_idx = i
|
|
167
|
+
|
|
168
|
+
# 返回之后的所有 agent 记录
|
|
169
|
+
if last_user_idx >= 0:
|
|
170
|
+
return [r for r in history[last_user_idx + 1:] if r.get('role') == 'agent']
|
|
171
|
+
|
|
172
|
+
# 如果没有用户消息,返回所有 agent 记录
|
|
173
|
+
return [r for r in history if r.get('role') == 'agent']
|
|
174
|
+
|
|
175
|
+
def _get_draft_file_path(self) -> Optional[str]:
|
|
176
|
+
"""获取草稿文件路径(独立文件,避免被其他代码覆盖)"""
|
|
177
|
+
if not self.project_path or not self.session_id:
|
|
178
|
+
return None
|
|
179
|
+
return os.path.join(self.project_path, '.workspace', 'chat_history', f'{self.session_id}_draft.json')
|
|
180
|
+
|
|
181
|
+
def get_latest_draft(self) -> Optional[dict]:
|
|
182
|
+
"""获取最近的未提交草稿"""
|
|
183
|
+
try:
|
|
184
|
+
draft_file = self._get_draft_file_path()
|
|
185
|
+
if not draft_file or not os.path.exists(draft_file):
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
with open(draft_file, 'r', encoding='utf-8') as f:
|
|
189
|
+
return json.load(f)
|
|
190
|
+
except Exception:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def save_draft(self, text: str) -> bool:
|
|
194
|
+
"""保存草稿到独立文件"""
|
|
195
|
+
try:
|
|
196
|
+
draft_file = self._get_draft_file_path()
|
|
197
|
+
if not draft_file:
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
os.makedirs(os.path.dirname(draft_file), exist_ok=True)
|
|
201
|
+
with open(draft_file, 'w', encoding='utf-8') as f:
|
|
202
|
+
json.dump({'text': text}, f, ensure_ascii=False)
|
|
203
|
+
|
|
204
|
+
return True
|
|
205
|
+
except Exception:
|
|
206
|
+
return False
|
|
207
|
+
|
|
208
|
+
def clear_draft(self) -> bool:
|
|
209
|
+
"""删除草稿文件"""
|
|
210
|
+
try:
|
|
211
|
+
draft_file = self._get_draft_file_path()
|
|
212
|
+
if draft_file and os.path.exists(draft_file):
|
|
213
|
+
os.remove(draft_file)
|
|
214
|
+
return True
|
|
215
|
+
except Exception:
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
def show_history_dialog(self, parent=None) -> None:
|
|
219
|
+
"""显示历史记录弹窗
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
parent: 父窗口
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
# 加载历史记录
|
|
226
|
+
history = self.get_recent_history()
|
|
227
|
+
|
|
228
|
+
if not history:
|
|
229
|
+
QMessageBox.information(parent, "历史记录", "暂无历史记录")
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
# 检查弹窗组件是否可用
|
|
233
|
+
if not HISTORY_POPUP_AVAILABLE:
|
|
234
|
+
QMessageBox.critical(parent, "错误", "历史记录弹窗组件不可用")
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
# 显示历史记录弹窗
|
|
238
|
+
self._show_history_popup(parent, history)
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"显示历史记录失败: {e}")
|
|
242
|
+
QMessageBox.critical(parent, "错误", f"显示历史记录失败: {str(e)}")
|
|
243
|
+
|
|
244
|
+
def _show_history_popup(self, parent, history: List[Dict]):
|
|
245
|
+
"""显示历史记录弹窗"""
|
|
246
|
+
try:
|
|
247
|
+
# 创建弹窗
|
|
248
|
+
popup = HistoryPopup(parent)
|
|
249
|
+
|
|
250
|
+
# 设置历史记录数据
|
|
251
|
+
popup.set_history_records(history, parent)
|
|
252
|
+
|
|
253
|
+
# 连接信号
|
|
254
|
+
popup.content_inserted.connect(lambda content: print(f"✅ 内容已插入: {content[:50]}..."))
|
|
255
|
+
popup.content_copied.connect(lambda content: print(f"✅ 内容已复制: {content[:50]}..."))
|
|
256
|
+
popup.popup_closed.connect(lambda: print("📝 历史记录弹窗已关闭"))
|
|
257
|
+
|
|
258
|
+
# 计算弹窗位置(在历史按钮附近)
|
|
259
|
+
if hasattr(parent, 'history_button'):
|
|
260
|
+
button = parent.history_button
|
|
261
|
+
# 获取按钮的全局位置
|
|
262
|
+
button_pos = button.mapToGlobal(button.rect().bottomLeft())
|
|
263
|
+
# 稍微偏移一下位置
|
|
264
|
+
popup_pos = QPoint(button_pos.x() - 200, button_pos.y() + 5)
|
|
265
|
+
else:
|
|
266
|
+
# 如果找不到按钮,在父窗口中央显示
|
|
267
|
+
if parent:
|
|
268
|
+
parent_rect = parent.geometry()
|
|
269
|
+
popup_pos = QPoint(
|
|
270
|
+
parent_rect.x() + parent_rect.width() // 2 - 250,
|
|
271
|
+
parent_rect.y() + parent_rect.height() // 2 - 200
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
popup_pos = QPoint(100, 100)
|
|
275
|
+
|
|
276
|
+
# 显示弹窗
|
|
277
|
+
popup.show_at_position(popup_pos)
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
print(f"显示历史记录弹窗失败: {e}")
|
|
281
|
+
QMessageBox.critical(parent, "错误", f"显示历史记录弹窗失败: {str(e)}")
|