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,582 @@
|
|
|
1
|
+
"""
|
|
2
|
+
历史记录弹窗组件 - 聊天对话样式展示历史记录
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Dict, Any, Optional
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QFrame, QVBoxLayout, QScrollArea, QWidget, QLabel, QPushButton, QHBoxLayout,
|
|
9
|
+
QApplication, QGridLayout, QSizePolicy
|
|
10
|
+
)
|
|
11
|
+
from PySide6.QtCore import Qt, Signal, QPoint
|
|
12
|
+
from PySide6.QtGui import QKeyEvent, QFont, QTextCursor
|
|
13
|
+
|
|
14
|
+
# 设置日志
|
|
15
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HistoryPopup(QFrame):
|
|
20
|
+
"""历史记录弹窗组件 - 聊天对话样式"""
|
|
21
|
+
|
|
22
|
+
# 信号定义
|
|
23
|
+
content_inserted = Signal(str) # 内容插入信号
|
|
24
|
+
content_copied = Signal(str) # 内容复制信号
|
|
25
|
+
popup_closed = Signal() # 弹窗关闭信号
|
|
26
|
+
|
|
27
|
+
def __init__(self, parent=None):
|
|
28
|
+
super().__init__(parent)
|
|
29
|
+
self.history_records = [] # 存储历史记录数据
|
|
30
|
+
self.parent_window = None # 父窗口引用,用于访问输入框
|
|
31
|
+
|
|
32
|
+
self._setup_ui()
|
|
33
|
+
self._setup_style()
|
|
34
|
+
|
|
35
|
+
# 设置窗口属性
|
|
36
|
+
self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
|
|
37
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
38
|
+
|
|
39
|
+
def _setup_ui(self):
|
|
40
|
+
"""设置UI"""
|
|
41
|
+
layout = QVBoxLayout(self)
|
|
42
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
43
|
+
layout.setSpacing(0)
|
|
44
|
+
|
|
45
|
+
# 标题栏
|
|
46
|
+
title_frame = QFrame()
|
|
47
|
+
title_frame.setStyleSheet("""
|
|
48
|
+
QFrame {
|
|
49
|
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
|
50
|
+
border-top-left-radius: 8px;
|
|
51
|
+
border-top-right-radius: 8px;
|
|
52
|
+
padding: 12px;
|
|
53
|
+
}
|
|
54
|
+
""")
|
|
55
|
+
title_layout = QHBoxLayout(title_frame)
|
|
56
|
+
title_layout.setContentsMargins(12, 8, 12, 8)
|
|
57
|
+
|
|
58
|
+
self.title_label = QLabel("💬 聊天历史")
|
|
59
|
+
self.title_label.setStyleSheet("""
|
|
60
|
+
QLabel {
|
|
61
|
+
color: white;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
font-weight: bold;
|
|
64
|
+
}
|
|
65
|
+
""")
|
|
66
|
+
title_layout.addWidget(self.title_label)
|
|
67
|
+
|
|
68
|
+
# 关闭按钮
|
|
69
|
+
close_button = QPushButton("✕")
|
|
70
|
+
close_button.setFixedSize(20, 20)
|
|
71
|
+
close_button.setStyleSheet("""
|
|
72
|
+
QPushButton {
|
|
73
|
+
background: transparent;
|
|
74
|
+
color: white;
|
|
75
|
+
border: none;
|
|
76
|
+
font-size: 14px;
|
|
77
|
+
font-weight: bold;
|
|
78
|
+
}
|
|
79
|
+
QPushButton:hover {
|
|
80
|
+
background: rgba(255, 255, 255, 0.2);
|
|
81
|
+
border-radius: 10px;
|
|
82
|
+
}
|
|
83
|
+
""")
|
|
84
|
+
close_button.clicked.connect(self.close)
|
|
85
|
+
title_layout.addWidget(close_button)
|
|
86
|
+
|
|
87
|
+
layout.addWidget(title_frame)
|
|
88
|
+
|
|
89
|
+
# 聊天记录滚动容器
|
|
90
|
+
self.scroll_area = QScrollArea()
|
|
91
|
+
self.scroll_area.setWidgetResizable(True)
|
|
92
|
+
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
93
|
+
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
94
|
+
self.scroll_area.setStyleSheet("""
|
|
95
|
+
QScrollArea {
|
|
96
|
+
background: #1e1e1e;
|
|
97
|
+
border: none;
|
|
98
|
+
}
|
|
99
|
+
QScrollBar:vertical {
|
|
100
|
+
background: #2b2b2b;
|
|
101
|
+
width: 8px;
|
|
102
|
+
border-radius: 4px;
|
|
103
|
+
}
|
|
104
|
+
QScrollBar::handle:vertical {
|
|
105
|
+
background: #555;
|
|
106
|
+
border-radius: 4px;
|
|
107
|
+
min-height: 30px;
|
|
108
|
+
}
|
|
109
|
+
QScrollBar::handle:vertical:hover {
|
|
110
|
+
background: #666;
|
|
111
|
+
}
|
|
112
|
+
""")
|
|
113
|
+
|
|
114
|
+
# 内容容器widget
|
|
115
|
+
self.content_widget = QWidget()
|
|
116
|
+
self.content_widget.setStyleSheet("background: #1e1e1e;")
|
|
117
|
+
self.content_layout = QVBoxLayout(self.content_widget)
|
|
118
|
+
self.content_layout.setSpacing(12)
|
|
119
|
+
self.content_layout.setContentsMargins(12, 12, 12, 12)
|
|
120
|
+
|
|
121
|
+
self.scroll_area.setWidget(self.content_widget)
|
|
122
|
+
layout.addWidget(self.scroll_area)
|
|
123
|
+
|
|
124
|
+
# 底部提示栏
|
|
125
|
+
hint_frame = QFrame()
|
|
126
|
+
hint_frame.setStyleSheet("""
|
|
127
|
+
QFrame {
|
|
128
|
+
background: #2b2b2b;
|
|
129
|
+
border-bottom-left-radius: 8px;
|
|
130
|
+
border-bottom-right-radius: 8px;
|
|
131
|
+
padding: 8px;
|
|
132
|
+
}
|
|
133
|
+
""")
|
|
134
|
+
hint_layout = QHBoxLayout(hint_frame)
|
|
135
|
+
|
|
136
|
+
self.hint_label = QLabel("💡 点击消息可插入或复制 | ESC 关闭")
|
|
137
|
+
self.hint_label.setStyleSheet("""
|
|
138
|
+
QLabel {
|
|
139
|
+
color: #888;
|
|
140
|
+
font-size: 11px;
|
|
141
|
+
}
|
|
142
|
+
""")
|
|
143
|
+
hint_layout.addWidget(self.hint_label, alignment=Qt.AlignCenter)
|
|
144
|
+
|
|
145
|
+
layout.addWidget(hint_frame)
|
|
146
|
+
|
|
147
|
+
def _setup_style(self):
|
|
148
|
+
"""设置样式"""
|
|
149
|
+
self.setStyleSheet("""
|
|
150
|
+
HistoryPopup {
|
|
151
|
+
background: #1e1e1e;
|
|
152
|
+
border: 1px solid #444;
|
|
153
|
+
border-radius: 8px;
|
|
154
|
+
}
|
|
155
|
+
""")
|
|
156
|
+
|
|
157
|
+
def set_history_records(self, records: List[Dict[str, Any]], parent_window=None):
|
|
158
|
+
"""设置历史记录列表
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
records: 历史记录列表,每个记录包含content, time_display等字段
|
|
162
|
+
parent_window: 父窗口引用,用于访问输入框
|
|
163
|
+
"""
|
|
164
|
+
self.history_records = records
|
|
165
|
+
self.parent_window = parent_window
|
|
166
|
+
self._update_history_list()
|
|
167
|
+
|
|
168
|
+
def _update_history_list(self):
|
|
169
|
+
"""更新历史记录列表"""
|
|
170
|
+
# 清空现有内容
|
|
171
|
+
while self.content_layout.count():
|
|
172
|
+
child = self.content_layout.takeAt(0)
|
|
173
|
+
if child.widget():
|
|
174
|
+
child.widget().deleteLater()
|
|
175
|
+
|
|
176
|
+
if not self.history_records:
|
|
177
|
+
# 显示空状态
|
|
178
|
+
empty_label = QLabel("暂无聊天记录")
|
|
179
|
+
empty_label.setAlignment(Qt.AlignCenter)
|
|
180
|
+
empty_label.setStyleSheet("""
|
|
181
|
+
QLabel {
|
|
182
|
+
color: #666;
|
|
183
|
+
padding: 40px;
|
|
184
|
+
font-size: 14px;
|
|
185
|
+
}
|
|
186
|
+
""")
|
|
187
|
+
self.content_layout.addWidget(empty_label)
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
# 添加历史记录项(最新的在下面,模拟聊天界面)
|
|
191
|
+
for record in self.history_records:
|
|
192
|
+
self._create_dialogue_item(record)
|
|
193
|
+
|
|
194
|
+
# 添加空白占位,让内容可以滚动到底部
|
|
195
|
+
spacer = QWidget()
|
|
196
|
+
spacer.setFixedHeight(20)
|
|
197
|
+
self.content_layout.addWidget(spacer)
|
|
198
|
+
|
|
199
|
+
def _create_dialogue_item(self, record: Dict[str, Any]):
|
|
200
|
+
"""创建对话记录项(兼容旧格式和新格式)
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
record: 记录数据
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
# 新格式:不含type字段,直接包含messages数组
|
|
207
|
+
if 'messages' in record and isinstance(record.get('messages'), list):
|
|
208
|
+
# 显示时间分隔线
|
|
209
|
+
if record.get('time_display'):
|
|
210
|
+
time_label = QLabel(record['time_display'])
|
|
211
|
+
time_label.setAlignment(Qt.AlignCenter)
|
|
212
|
+
time_label.setStyleSheet("""
|
|
213
|
+
QLabel {
|
|
214
|
+
color: #666;
|
|
215
|
+
font-size: 10px;
|
|
216
|
+
padding: 4px;
|
|
217
|
+
margin: 8px 0;
|
|
218
|
+
}
|
|
219
|
+
""")
|
|
220
|
+
self.content_layout.addWidget(time_label)
|
|
221
|
+
|
|
222
|
+
# 显示对话消息
|
|
223
|
+
for msg in record['messages']:
|
|
224
|
+
self._create_message_bubble(msg)
|
|
225
|
+
else:
|
|
226
|
+
# 兼容旧格式的单条消息
|
|
227
|
+
msg = {
|
|
228
|
+
'role': 'user',
|
|
229
|
+
'content': record.get('content', ''),
|
|
230
|
+
'time': record.get('time_display', '').split(' ')[-1] if 'time_display' in record else ''
|
|
231
|
+
}
|
|
232
|
+
self._create_message_bubble(msg)
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.error(f"创建对话记录项失败: {e}")
|
|
236
|
+
|
|
237
|
+
def _create_message_bubble(self, msg: Dict[str, Any]):
|
|
238
|
+
"""创建消息气泡
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
msg: 消息数据
|
|
242
|
+
"""
|
|
243
|
+
role = msg.get('role', 'user')
|
|
244
|
+
content = msg.get('content', '')
|
|
245
|
+
time = msg.get('time', '')
|
|
246
|
+
|
|
247
|
+
# 创建消息容器
|
|
248
|
+
msg_container = QWidget()
|
|
249
|
+
msg_layout = QHBoxLayout(msg_container)
|
|
250
|
+
msg_layout.setContentsMargins(0, 4, 0, 4)
|
|
251
|
+
msg_layout.setSpacing(10)
|
|
252
|
+
msg_layout.setAlignment(Qt.AlignLeft) # 统一左对齐
|
|
253
|
+
|
|
254
|
+
# 头像 - 根据角色显示不同的图标和背景
|
|
255
|
+
if role == 'user':
|
|
256
|
+
avatar_label = QLabel("👤")
|
|
257
|
+
avatar_bg = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
|
258
|
+
else:
|
|
259
|
+
avatar_label = QLabel("🤖")
|
|
260
|
+
avatar_bg = "linear-gradient(135deg, #00c853 0%, #00897b 100%)"
|
|
261
|
+
|
|
262
|
+
avatar_label.setFixedSize(36, 36)
|
|
263
|
+
avatar_label.setAlignment(Qt.AlignCenter)
|
|
264
|
+
avatar_label.setStyleSheet(f"""
|
|
265
|
+
QLabel {{
|
|
266
|
+
background: {avatar_bg};
|
|
267
|
+
border-radius: 18px;
|
|
268
|
+
font-size: 18px;
|
|
269
|
+
}}
|
|
270
|
+
""")
|
|
271
|
+
msg_layout.addWidget(avatar_label, alignment=Qt.AlignTop)
|
|
272
|
+
|
|
273
|
+
# 消息内容区域
|
|
274
|
+
content_frame = QFrame()
|
|
275
|
+
# 移除最大宽度限制,让内容框架填充整个可用宽度
|
|
276
|
+
content_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
277
|
+
content_layout = QVBoxLayout(content_frame)
|
|
278
|
+
content_layout.setContentsMargins(0, 0, 0, 0)
|
|
279
|
+
content_layout.setSpacing(4)
|
|
280
|
+
|
|
281
|
+
# 用户名称和时间行
|
|
282
|
+
header_layout = QHBoxLayout()
|
|
283
|
+
header_layout.setSpacing(8)
|
|
284
|
+
|
|
285
|
+
# 用户名称
|
|
286
|
+
name_label = QLabel("用户" if role == 'user' else "AI助手")
|
|
287
|
+
name_label.setStyleSheet("""
|
|
288
|
+
QLabel {
|
|
289
|
+
color: #fff;
|
|
290
|
+
font-size: 12px;
|
|
291
|
+
font-weight: bold;
|
|
292
|
+
}
|
|
293
|
+
""")
|
|
294
|
+
header_layout.addWidget(name_label)
|
|
295
|
+
|
|
296
|
+
# 时间标签
|
|
297
|
+
if time:
|
|
298
|
+
time_label = QLabel(time)
|
|
299
|
+
time_label.setStyleSheet("""
|
|
300
|
+
QLabel {
|
|
301
|
+
color: #888;
|
|
302
|
+
font-size: 10px;
|
|
303
|
+
}
|
|
304
|
+
""")
|
|
305
|
+
header_layout.addWidget(time_label)
|
|
306
|
+
|
|
307
|
+
header_layout.addStretch()
|
|
308
|
+
content_layout.addLayout(header_layout)
|
|
309
|
+
|
|
310
|
+
# 消息内容框架
|
|
311
|
+
bubble_frame = QFrame()
|
|
312
|
+
bubble_frame.setStyleSheet("""
|
|
313
|
+
QFrame {
|
|
314
|
+
background: #2b2b2b;
|
|
315
|
+
border: 1px solid #3a3a3a;
|
|
316
|
+
border-radius: 8px;
|
|
317
|
+
padding: 10px 12px;
|
|
318
|
+
}
|
|
319
|
+
""")
|
|
320
|
+
|
|
321
|
+
bubble_layout = QVBoxLayout(bubble_frame)
|
|
322
|
+
bubble_layout.setContentsMargins(0, 0, 0, 0)
|
|
323
|
+
|
|
324
|
+
# 消息内容
|
|
325
|
+
content_label = QLabel(content[:300] + "..." if len(content) > 300 else content)
|
|
326
|
+
content_label.setWordWrap(True)
|
|
327
|
+
content_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
|
328
|
+
content_label.setStyleSheet("""
|
|
329
|
+
QLabel {
|
|
330
|
+
color: #e0e0e0;
|
|
331
|
+
font-size: 13px;
|
|
332
|
+
line-height: 1.5;
|
|
333
|
+
}
|
|
334
|
+
""")
|
|
335
|
+
if len(content) > 300:
|
|
336
|
+
content_label.setToolTip(content)
|
|
337
|
+
bubble_layout.addWidget(content_label)
|
|
338
|
+
|
|
339
|
+
content_layout.addWidget(bubble_frame)
|
|
340
|
+
|
|
341
|
+
# 添加操作按钮(悬浮显示)
|
|
342
|
+
self._add_hover_buttons(bubble_frame, content)
|
|
343
|
+
|
|
344
|
+
msg_layout.addWidget(content_frame, 1) # 设置stretch factor为1,让内容框架占据所有可用空间
|
|
345
|
+
|
|
346
|
+
self.content_layout.addWidget(msg_container)
|
|
347
|
+
|
|
348
|
+
def _add_hover_buttons(self, bubble_frame, content):
|
|
349
|
+
"""添加悬浮操作按钮
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
bubble_frame: 消息气泡框架
|
|
353
|
+
content: 消息内容
|
|
354
|
+
"""
|
|
355
|
+
# 创建按钮容器
|
|
356
|
+
button_container = QWidget(bubble_frame)
|
|
357
|
+
button_container.setStyleSheet("""
|
|
358
|
+
QWidget {
|
|
359
|
+
background: rgba(0, 0, 0, 0.8);
|
|
360
|
+
border-radius: 4px;
|
|
361
|
+
}
|
|
362
|
+
""")
|
|
363
|
+
button_layout = QHBoxLayout(button_container)
|
|
364
|
+
button_layout.setContentsMargins(4, 4, 4, 4)
|
|
365
|
+
button_layout.setSpacing(4)
|
|
366
|
+
|
|
367
|
+
# 插入按钮
|
|
368
|
+
insert_btn = QPushButton("📥")
|
|
369
|
+
insert_btn.setToolTip("插入到输入框")
|
|
370
|
+
insert_btn.setFixedSize(24, 24)
|
|
371
|
+
insert_btn.setStyleSheet("""
|
|
372
|
+
QPushButton {
|
|
373
|
+
background: transparent;
|
|
374
|
+
color: white;
|
|
375
|
+
border: none;
|
|
376
|
+
font-size: 14px;
|
|
377
|
+
}
|
|
378
|
+
QPushButton:hover {
|
|
379
|
+
background: rgba(255, 255, 255, 0.2);
|
|
380
|
+
border-radius: 4px;
|
|
381
|
+
}
|
|
382
|
+
""")
|
|
383
|
+
insert_btn.clicked.connect(lambda: self._handle_insert(content))
|
|
384
|
+
button_layout.addWidget(insert_btn)
|
|
385
|
+
|
|
386
|
+
# 复制按钮
|
|
387
|
+
copy_btn = QPushButton("📋")
|
|
388
|
+
copy_btn.setToolTip("复制到剪贴板")
|
|
389
|
+
copy_btn.setFixedSize(24, 24)
|
|
390
|
+
copy_btn.setStyleSheet("""
|
|
391
|
+
QPushButton {
|
|
392
|
+
background: transparent;
|
|
393
|
+
color: white;
|
|
394
|
+
border: none;
|
|
395
|
+
font-size: 14px;
|
|
396
|
+
}
|
|
397
|
+
QPushButton:hover {
|
|
398
|
+
background: rgba(255, 255, 255, 0.2);
|
|
399
|
+
border-radius: 4px;
|
|
400
|
+
}
|
|
401
|
+
""")
|
|
402
|
+
copy_btn.clicked.connect(lambda: self._handle_copy(content))
|
|
403
|
+
button_layout.addWidget(copy_btn)
|
|
404
|
+
|
|
405
|
+
# 初始隐藏,悬浮时显示
|
|
406
|
+
button_container.hide()
|
|
407
|
+
button_container.raise_() # 确保按钮在最上层
|
|
408
|
+
|
|
409
|
+
# 设置悬浮事件
|
|
410
|
+
def show_buttons(event):
|
|
411
|
+
# 计算按钮位置(右上角)
|
|
412
|
+
button_container.move(
|
|
413
|
+
bubble_frame.width() - button_container.width() - 8,
|
|
414
|
+
8
|
|
415
|
+
)
|
|
416
|
+
button_container.show()
|
|
417
|
+
|
|
418
|
+
def hide_buttons(event):
|
|
419
|
+
button_container.hide()
|
|
420
|
+
|
|
421
|
+
bubble_frame.enterEvent = show_buttons
|
|
422
|
+
bubble_frame.leaveEvent = hide_buttons
|
|
423
|
+
|
|
424
|
+
def _handle_insert(self, content: str):
|
|
425
|
+
"""处理插入操作"""
|
|
426
|
+
try:
|
|
427
|
+
# 尝试直接插入到输入框
|
|
428
|
+
if self._insert_to_textbox(content):
|
|
429
|
+
# 插入成功,发出信号并关闭弹窗
|
|
430
|
+
self.content_inserted.emit(content)
|
|
431
|
+
self.close()
|
|
432
|
+
else:
|
|
433
|
+
# 插入失败,回退到复制功能
|
|
434
|
+
logger.warning("插入失败,回退到复制功能")
|
|
435
|
+
self._handle_copy(content)
|
|
436
|
+
except Exception as e:
|
|
437
|
+
logger.error(f"插入内容失败: {e}")
|
|
438
|
+
# 出错时回退到复制功能
|
|
439
|
+
self._handle_copy(content)
|
|
440
|
+
|
|
441
|
+
def _handle_copy(self, content: str):
|
|
442
|
+
"""处理复制操作"""
|
|
443
|
+
try:
|
|
444
|
+
clipboard = QApplication.clipboard()
|
|
445
|
+
clipboard.setText(content)
|
|
446
|
+
|
|
447
|
+
# 发出信号并关闭弹窗
|
|
448
|
+
self.content_copied.emit(content)
|
|
449
|
+
self.close()
|
|
450
|
+
|
|
451
|
+
except Exception as e:
|
|
452
|
+
logger.error(f"复制到剪贴板失败: {e}")
|
|
453
|
+
|
|
454
|
+
def _insert_to_textbox(self, content: str) -> bool:
|
|
455
|
+
"""将内容插入到对话框输入框中
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
content: 要插入的内容
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
bool: 插入是否成功
|
|
462
|
+
"""
|
|
463
|
+
try:
|
|
464
|
+
# 尝试获取主窗口的聊天标签页和输入框
|
|
465
|
+
if self.parent_window:
|
|
466
|
+
# 方法1:通过parent_window查找chat_tab
|
|
467
|
+
if hasattr(self.parent_window, 'chat_tab') and self.parent_window.chat_tab:
|
|
468
|
+
feedback_text = self.parent_window.chat_tab.feedback_text
|
|
469
|
+
if feedback_text:
|
|
470
|
+
return self._do_insert(feedback_text, content)
|
|
471
|
+
|
|
472
|
+
# 方法2:向上查找包含FeedbackTextEdit的窗口
|
|
473
|
+
from PySide6.QtWidgets import QTextEdit
|
|
474
|
+
parent = self.parent_window
|
|
475
|
+
while parent:
|
|
476
|
+
# 在所有子控件中查找FeedbackTextEdit
|
|
477
|
+
feedback_widgets = parent.findChildren(QTextEdit)
|
|
478
|
+
for widget in feedback_widgets:
|
|
479
|
+
if hasattr(widget, 'pasted_images'): # FeedbackTextEdit特有属性
|
|
480
|
+
return self._do_insert(widget, content)
|
|
481
|
+
|
|
482
|
+
parent = parent.parent()
|
|
483
|
+
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
except Exception as e:
|
|
487
|
+
logger.error(f"查找输入框失败: {e}")
|
|
488
|
+
return False
|
|
489
|
+
|
|
490
|
+
def _do_insert(self, text_widget, content: str) -> bool:
|
|
491
|
+
"""执行插入操作
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
text_widget: 文本输入控件
|
|
495
|
+
content: 要插入的内容
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
bool: 插入是否成功
|
|
499
|
+
"""
|
|
500
|
+
try:
|
|
501
|
+
# 获取当前文本
|
|
502
|
+
current_text = text_widget.toPlainText()
|
|
503
|
+
|
|
504
|
+
# 如果输入框有内容,在末尾添加换行后插入新内容
|
|
505
|
+
if current_text.strip():
|
|
506
|
+
new_text = current_text + "\n\n" + content
|
|
507
|
+
else:
|
|
508
|
+
new_text = content
|
|
509
|
+
|
|
510
|
+
# 设置新文本
|
|
511
|
+
text_widget.setPlainText(new_text)
|
|
512
|
+
|
|
513
|
+
# 设置焦点到输入框
|
|
514
|
+
text_widget.setFocus()
|
|
515
|
+
|
|
516
|
+
# 将光标移动到末尾
|
|
517
|
+
cursor = text_widget.textCursor()
|
|
518
|
+
cursor.movePosition(QTextCursor.End)
|
|
519
|
+
text_widget.setTextCursor(cursor)
|
|
520
|
+
|
|
521
|
+
logger.info(f"成功插入内容: {content[:50]}...")
|
|
522
|
+
return True
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
logger.error(f"执行插入操作失败: {e}")
|
|
526
|
+
return False
|
|
527
|
+
|
|
528
|
+
def keyPressEvent(self, event: QKeyEvent):
|
|
529
|
+
"""处理键盘事件"""
|
|
530
|
+
if event.key() == Qt.Key_Escape:
|
|
531
|
+
# ESC键关闭弹窗
|
|
532
|
+
self.popup_closed.emit()
|
|
533
|
+
self.close()
|
|
534
|
+
else:
|
|
535
|
+
super().keyPressEvent(event)
|
|
536
|
+
|
|
537
|
+
def show_at_position(self, position: QPoint):
|
|
538
|
+
"""在指定位置显示弹窗"""
|
|
539
|
+
# 获取屏幕信息
|
|
540
|
+
screen = QApplication.primaryScreen()
|
|
541
|
+
screen_geometry = screen.availableGeometry()
|
|
542
|
+
|
|
543
|
+
# 设置弹窗大小与feedback UI一致:500x700
|
|
544
|
+
popup_width = 500
|
|
545
|
+
popup_height = 700
|
|
546
|
+
|
|
547
|
+
# 设置弹窗大小
|
|
548
|
+
self.resize(popup_width, popup_height)
|
|
549
|
+
|
|
550
|
+
# 使用传入的位置(左边缘对齐)
|
|
551
|
+
if position and position.x() >= 0 and position.y() >= 0:
|
|
552
|
+
x = position.x() # 使用传入的x坐标(已经是左对齐)
|
|
553
|
+
y = position.y() # 使用传入的y坐标
|
|
554
|
+
|
|
555
|
+
# 确保不超出屏幕右边界
|
|
556
|
+
if x + popup_width > screen_geometry.right():
|
|
557
|
+
x = screen_geometry.right() - popup_width - 10
|
|
558
|
+
|
|
559
|
+
# 确保不超出屏幕底部
|
|
560
|
+
if y + popup_height > screen_geometry.bottom():
|
|
561
|
+
y = screen_geometry.bottom() - popup_height - 10
|
|
562
|
+
|
|
563
|
+
# 确保不超出屏幕左边界和顶部
|
|
564
|
+
if x < screen_geometry.x():
|
|
565
|
+
x = screen_geometry.x()
|
|
566
|
+
if y < screen_geometry.y():
|
|
567
|
+
y = screen_geometry.y()
|
|
568
|
+
else:
|
|
569
|
+
# 居中显示
|
|
570
|
+
x = screen_geometry.x() + (screen_geometry.width() - popup_width) // 2
|
|
571
|
+
y = screen_geometry.y() + (screen_geometry.height() - popup_height) // 2
|
|
572
|
+
|
|
573
|
+
self.move(x, y)
|
|
574
|
+
self.show()
|
|
575
|
+
self.setFocus()
|
|
576
|
+
|
|
577
|
+
# 滚动到底部显示最新消息
|
|
578
|
+
self.scroll_area.verticalScrollBar().setValue(
|
|
579
|
+
self.scroll_area.verticalScrollBar().maximum()
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
logger.info(f"历史记录弹窗显示在位置: ({x}, {y}), 大小: {popup_width}x{popup_height}")
|