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
session_manager.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
会话状态管理器
|
|
4
|
+
用于管理stop hook和feedback UI之间的会话状态,避免死循环
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional, Dict, Any
|
|
12
|
+
|
|
13
|
+
class SessionManager:
|
|
14
|
+
"""管理会话状态,避免stop hook死循环
|
|
15
|
+
|
|
16
|
+
状态保存在 .workspace/chat_history/{session_id}.json 中
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session_id: Optional[str] = None, project_path: Optional[str] = None):
|
|
20
|
+
"""初始化会话管理器
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
session_id: 会话ID
|
|
24
|
+
project_path: 项目路径,用于定位.workspace目录
|
|
25
|
+
"""
|
|
26
|
+
self.session_id = session_id
|
|
27
|
+
self.project_path = project_path or os.getcwd()
|
|
28
|
+
|
|
29
|
+
# 不再加载所有sessions,改为按需加载单个session
|
|
30
|
+
# self.sessions = self._load_sessions()
|
|
31
|
+
# self._cleanup_old_sessions()
|
|
32
|
+
|
|
33
|
+
def _get_chat_history_path(self, session_id: str) -> Path:
|
|
34
|
+
"""获取指定session的chat_history文件路径
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
session_id: 会话ID
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Path: chat_history文件路径
|
|
41
|
+
"""
|
|
42
|
+
workspace_dir = Path(self.project_path) / '.workspace' / 'chat_history'
|
|
43
|
+
workspace_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
return workspace_dir / f"{session_id}.json"
|
|
45
|
+
|
|
46
|
+
def _load_session_data(self, session_id: str) -> list:
|
|
47
|
+
"""加载指定session的chat_history数据(兼容旧格式)
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
session_id: 会话ID
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
list: chat_history数据数组(内部格式,用于兼容现有代码)
|
|
54
|
+
"""
|
|
55
|
+
chat_file = self._get_chat_history_path(session_id)
|
|
56
|
+
if not chat_file.exists():
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
with open(chat_file, 'r', encoding='utf-8') as f:
|
|
61
|
+
data = json.load(f)
|
|
62
|
+
|
|
63
|
+
# 新格式:{'dialogues': [...], 'control': {...}}
|
|
64
|
+
if isinstance(data, dict) and 'dialogues' in data:
|
|
65
|
+
# 转换为内部数组格式
|
|
66
|
+
result = []
|
|
67
|
+
|
|
68
|
+
# 添加对话记录(自动添加type字段以兼容现有代码)
|
|
69
|
+
for dialogue in data.get('dialogues', []):
|
|
70
|
+
dialogue_with_type = dialogue.copy()
|
|
71
|
+
dialogue_with_type['type'] = 'dialogue'
|
|
72
|
+
result.append(dialogue_with_type)
|
|
73
|
+
|
|
74
|
+
# 添加control记录
|
|
75
|
+
if 'control' in data and data['control']:
|
|
76
|
+
result.append(data['control'])
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
# 旧格式:直接返回数组
|
|
81
|
+
return data if isinstance(data, list) else []
|
|
82
|
+
except (json.JSONDecodeError, IOError):
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
def _save_session_data(self, session_id: str, data: list):
|
|
86
|
+
"""保存指定session的chat_history数据(新格式)
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
session_id: 会话ID
|
|
90
|
+
data: chat_history数据数组
|
|
91
|
+
"""
|
|
92
|
+
chat_file = self._get_chat_history_path(session_id)
|
|
93
|
+
try:
|
|
94
|
+
# 转换为新格式:dialogues数组
|
|
95
|
+
new_format_data = {
|
|
96
|
+
'dialogues': []
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# 过滤出对话项
|
|
100
|
+
for item in data:
|
|
101
|
+
if isinstance(item, dict):
|
|
102
|
+
# 保留 agent 记录(直接添加)
|
|
103
|
+
if item.get('role') == 'agent':
|
|
104
|
+
new_format_data['dialogues'].append(item)
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
item_type = item.get('type')
|
|
108
|
+
if item_type == 'dialogue':
|
|
109
|
+
# 创建新格式的对话项(移除type字段)
|
|
110
|
+
dialogue = {
|
|
111
|
+
'timestamp': item.get('timestamp', ''),
|
|
112
|
+
'time_display': item.get('time_display', ''),
|
|
113
|
+
'messages': item.get('messages', [])
|
|
114
|
+
}
|
|
115
|
+
new_format_data['dialogues'].append(dialogue)
|
|
116
|
+
elif item_type == 'stop_hook_status':
|
|
117
|
+
# 保留control字段(如果存在)
|
|
118
|
+
if 'control' not in new_format_data:
|
|
119
|
+
new_format_data['control'] = item
|
|
120
|
+
|
|
121
|
+
with open(chat_file, 'w', encoding='utf-8') as f:
|
|
122
|
+
json.dump(new_format_data, f, indent=2, ensure_ascii=False)
|
|
123
|
+
except IOError as e:
|
|
124
|
+
print(f"Error saving session data: {e}", file=sys.stderr)
|
|
125
|
+
|
|
126
|
+
def _get_stop_hook_status(self, session_id: str) -> Optional[Dict[str, Any]]:
|
|
127
|
+
"""获取session的stop_hook状态
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
session_id: 会话ID
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Optional[Dict]: stop_hook状态数据,如果不存在则返回None
|
|
134
|
+
"""
|
|
135
|
+
data = self._load_session_data(session_id)
|
|
136
|
+
# 在数组末尾查找stop_hook_status元素
|
|
137
|
+
for item in reversed(data):
|
|
138
|
+
if isinstance(item, dict) and item.get('type') == 'stop_hook_status':
|
|
139
|
+
return item
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def _set_stop_hook_status(self, session_id: str, status: str, action: Optional[str] = None):
|
|
143
|
+
"""设置session的stop_hook状态
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
session_id: 会话ID
|
|
147
|
+
status: 状态值
|
|
148
|
+
action: 动作描述
|
|
149
|
+
"""
|
|
150
|
+
data = self._load_session_data(session_id)
|
|
151
|
+
|
|
152
|
+
# 移除旧的stop_hook_status元素
|
|
153
|
+
data = [item for item in data if not (isinstance(item, dict) and item.get('type') == 'stop_hook_status')]
|
|
154
|
+
|
|
155
|
+
# 添加新的stop_hook_status元素
|
|
156
|
+
data.append({
|
|
157
|
+
'type': 'stop_hook_status',
|
|
158
|
+
'status': status,
|
|
159
|
+
'timestamp': datetime.now().isoformat(),
|
|
160
|
+
'last_action': action or status
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
self._save_session_data(session_id, data)
|
|
164
|
+
|
|
165
|
+
def _clear_stop_hook_status(self, session_id: str):
|
|
166
|
+
"""清除session的stop_hook状态
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
session_id: 会话ID
|
|
170
|
+
"""
|
|
171
|
+
data = self._load_session_data(session_id)
|
|
172
|
+
# 移除stop_hook_status元素
|
|
173
|
+
data = [item for item in data if not (isinstance(item, dict) and item.get('type') == 'stop_hook_status')]
|
|
174
|
+
self._save_session_data(session_id, data)
|
|
175
|
+
|
|
176
|
+
def get_session_status(self, session_id: str) -> Optional[str]:
|
|
177
|
+
"""获取会话状态
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
session_id: 会话ID
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
会话状态: "user_closed_by_button", "timeout_closed", "active" 或 None
|
|
184
|
+
"""
|
|
185
|
+
status_data = self._get_stop_hook_status(session_id)
|
|
186
|
+
if status_data:
|
|
187
|
+
return status_data.get("status")
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def set_session_status(self, session_id: str, status: str, action: Optional[str] = None):
|
|
191
|
+
"""设置会话状态
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
session_id: 会话ID
|
|
195
|
+
status: 状态 ("user_closed_by_button", "timeout_closed", "active")
|
|
196
|
+
action: 最后的动作描述
|
|
197
|
+
"""
|
|
198
|
+
self._set_stop_hook_status(session_id, status, action)
|
|
199
|
+
|
|
200
|
+
def is_feedback_closed(self, session_id: str) -> bool:
|
|
201
|
+
"""检查会话是否因用户关闭feedback而结束
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
session_id: 会话ID
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True如果用户主动关闭了feedback窗口
|
|
208
|
+
"""
|
|
209
|
+
status = self.get_session_status(session_id)
|
|
210
|
+
return status == "user_closed"
|
|
211
|
+
|
|
212
|
+
def mark_feedback_closed(self, session_id: str):
|
|
213
|
+
"""标记feedback被用户关闭(通用方法,保留向后兼容)
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
session_id: 会话ID
|
|
217
|
+
"""
|
|
218
|
+
self.set_session_status(session_id, "user_closed", "feedback_window_closed_by_user")
|
|
219
|
+
|
|
220
|
+
def mark_user_closed_by_button(self, session_id: str):
|
|
221
|
+
"""标记用户点击关闭按钮
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
session_id: 会话ID
|
|
225
|
+
"""
|
|
226
|
+
self.set_session_status(session_id, "user_closed_by_button", "user_clicked_close_button")
|
|
227
|
+
|
|
228
|
+
def mark_timeout_closed(self, session_id: str):
|
|
229
|
+
"""标记超时自动关闭
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
session_id: 会话ID
|
|
233
|
+
"""
|
|
234
|
+
self.set_session_status(session_id, "timeout_closed", "feedback_timeout_auto_closed")
|
|
235
|
+
|
|
236
|
+
def is_user_closed_by_button(self, session_id: str) -> bool:
|
|
237
|
+
"""检查是否用户点击关闭按钮
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
session_id: 会话ID
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
True如果用户点击关闭按钮
|
|
244
|
+
"""
|
|
245
|
+
status = self.get_session_status(session_id)
|
|
246
|
+
return status == "user_closed_by_button"
|
|
247
|
+
|
|
248
|
+
def is_timeout_closed(self, session_id: str) -> bool:
|
|
249
|
+
"""检查是否超时关闭
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
session_id: 会话ID
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True如果超时自动关闭
|
|
256
|
+
"""
|
|
257
|
+
status = self.get_session_status(session_id)
|
|
258
|
+
return status == "timeout_closed"
|
|
259
|
+
|
|
260
|
+
def reset_on_feedback_show(self, session_id: str):
|
|
261
|
+
"""feedback展示时重置状态
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
session_id: 会话ID
|
|
265
|
+
"""
|
|
266
|
+
# 清除所有状态
|
|
267
|
+
self.clear_session(session_id)
|
|
268
|
+
|
|
269
|
+
def get_block_count(self, session_id: str) -> int:
|
|
270
|
+
"""获取会话的阻止次数
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
session_id: 会话ID
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
阻止次数
|
|
277
|
+
"""
|
|
278
|
+
status_data = self._get_stop_hook_status(session_id)
|
|
279
|
+
if status_data:
|
|
280
|
+
return status_data.get("block_count", 0)
|
|
281
|
+
return 0
|
|
282
|
+
|
|
283
|
+
def increment_block_count(self, session_id: str) -> int:
|
|
284
|
+
"""增加会话的阻止次数
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
session_id: 会话ID
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
更新后的阻止次数
|
|
291
|
+
"""
|
|
292
|
+
status_data = self._get_stop_hook_status(session_id)
|
|
293
|
+
if status_data:
|
|
294
|
+
new_count = status_data.get("block_count", 0) + 1
|
|
295
|
+
status_data["block_count"] = new_count
|
|
296
|
+
status_data["timestamp"] = datetime.now().isoformat()
|
|
297
|
+
|
|
298
|
+
# 更新到chat_history
|
|
299
|
+
data = self._load_session_data(session_id)
|
|
300
|
+
# 移除旧的stop_hook_status
|
|
301
|
+
data = [item for item in data if not (isinstance(item, dict) and item.get('type') == 'stop_hook_status')]
|
|
302
|
+
# 添加更新后的status
|
|
303
|
+
data.append(status_data)
|
|
304
|
+
self._save_session_data(session_id, data)
|
|
305
|
+
return new_count
|
|
306
|
+
else:
|
|
307
|
+
# 首次创建
|
|
308
|
+
self._set_stop_hook_status(session_id, "active", "block_count_increment")
|
|
309
|
+
data = self._load_session_data(session_id)
|
|
310
|
+
# 找到刚创建的status并设置block_count
|
|
311
|
+
for item in reversed(data):
|
|
312
|
+
if isinstance(item, dict) and item.get('type') == 'stop_hook_status':
|
|
313
|
+
item["block_count"] = 1
|
|
314
|
+
break
|
|
315
|
+
self._save_session_data(session_id, data)
|
|
316
|
+
return 1
|
|
317
|
+
|
|
318
|
+
def clear_session(self, session_id: str):
|
|
319
|
+
"""清除会话状态
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
session_id: 会话ID
|
|
323
|
+
"""
|
|
324
|
+
self._clear_stop_hook_status(session_id)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def main():
|
|
328
|
+
"""命令行接口"""
|
|
329
|
+
import argparse
|
|
330
|
+
|
|
331
|
+
parser = argparse.ArgumentParser(description='管理会话状态')
|
|
332
|
+
parser.add_argument('action', choices=['get', 'set', 'check', 'mark_closed', 'clear'],
|
|
333
|
+
help='要执行的操作')
|
|
334
|
+
parser.add_argument('session_id', help='会话ID')
|
|
335
|
+
parser.add_argument('--status', help='设置的状态 (用于set操作)')
|
|
336
|
+
parser.add_argument('--state-file', help='状态文件路径')
|
|
337
|
+
|
|
338
|
+
args = parser.parse_args()
|
|
339
|
+
|
|
340
|
+
manager = SessionManager(args.state_file)
|
|
341
|
+
|
|
342
|
+
if args.action == 'get':
|
|
343
|
+
status = manager.get_session_status(args.session_id)
|
|
344
|
+
print(status if status else "none")
|
|
345
|
+
|
|
346
|
+
elif args.action == 'set':
|
|
347
|
+
if not args.status:
|
|
348
|
+
print("Error: --status required for set action", file=sys.stderr)
|
|
349
|
+
sys.exit(1)
|
|
350
|
+
manager.set_session_status(args.session_id, args.status)
|
|
351
|
+
print(f"Session {args.session_id} status set to {args.status}")
|
|
352
|
+
|
|
353
|
+
elif args.action == 'check':
|
|
354
|
+
is_closed = manager.is_feedback_closed(args.session_id)
|
|
355
|
+
print("closed" if is_closed else "active")
|
|
356
|
+
sys.exit(0 if not is_closed else 1)
|
|
357
|
+
|
|
358
|
+
elif args.action == 'mark_closed':
|
|
359
|
+
manager.mark_feedback_closed(args.session_id)
|
|
360
|
+
print(f"Session {args.session_id} marked as closed by user")
|
|
361
|
+
|
|
362
|
+
elif args.action == 'clear':
|
|
363
|
+
manager.clear_session(args.session_id)
|
|
364
|
+
print(f"Session {args.session_id} cleared")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
if __name__ == "__main__":
|
|
368
|
+
main()
|
stop_hook.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Stop Hook处理脚本
|
|
4
|
+
智能处理stop事件,避免死循环
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# 添加当前目录到Python路径
|
|
12
|
+
current_dir = Path(__file__).parent
|
|
13
|
+
sys.path.insert(0, str(current_dir))
|
|
14
|
+
|
|
15
|
+
from session_manager import SessionManager
|
|
16
|
+
from context_formatter import format_for_stop_hook
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
"""主函数"""
|
|
21
|
+
try:
|
|
22
|
+
# 从stdin读取JSON输入
|
|
23
|
+
input_data = json.load(sys.stdin)
|
|
24
|
+
|
|
25
|
+
# 提取关键信息
|
|
26
|
+
session_id = input_data.get('session_id', '')
|
|
27
|
+
stop_hook_active = input_data.get('stop_hook_active', False)
|
|
28
|
+
|
|
29
|
+
# 获取项目路径
|
|
30
|
+
project_path = os.getcwd()
|
|
31
|
+
|
|
32
|
+
# 创建会话管理器(传入project_path)
|
|
33
|
+
manager = SessionManager(session_id=session_id, project_path=project_path)
|
|
34
|
+
|
|
35
|
+
# 决策逻辑
|
|
36
|
+
if session_id:
|
|
37
|
+
# 1. 检查用户是否点击关闭按钮
|
|
38
|
+
if manager.is_user_closed_by_button(session_id):
|
|
39
|
+
# 用户主动点击关闭,完全不提示(静默允许停止)
|
|
40
|
+
# 🔧 立即清除状态,避免死循环
|
|
41
|
+
manager.clear_session(session_id)
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
# 2. 检查是否超时关闭
|
|
45
|
+
if manager.is_timeout_closed(session_id):
|
|
46
|
+
# 超时关闭场景,最多提示2次
|
|
47
|
+
current_block_count = manager.get_block_count(session_id)
|
|
48
|
+
MAX_BLOCK_COUNT = 2
|
|
49
|
+
|
|
50
|
+
if current_block_count >= MAX_BLOCK_COUNT:
|
|
51
|
+
# 超过最大阻止次数,允许停止以避免死循环
|
|
52
|
+
manager.clear_session(session_id)
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
# 未超过2次,继续提示并增加计数
|
|
56
|
+
manager.increment_block_count(session_id)
|
|
57
|
+
else:
|
|
58
|
+
# 3. 正常场景(非关闭状态),重置计数
|
|
59
|
+
# 这样每次正常的stop hook都会重新开始计数
|
|
60
|
+
if manager.get_block_count(session_id) > 0:
|
|
61
|
+
manager.clear_session(session_id)
|
|
62
|
+
|
|
63
|
+
# 4. 默认行为:阻止停止并提示使用feedback工具
|
|
64
|
+
# 使用新的格式化上下文信息
|
|
65
|
+
if session_id:
|
|
66
|
+
reason_text = format_for_stop_hook(session_id, project_path)
|
|
67
|
+
else:
|
|
68
|
+
reason_text = "请你调用 feedback mcp tool 向用户反馈/请示。示例:使用 mcp__feedback__feedback 工具向用户汇报当前工作进度、完成状态或请求下一步指示。"
|
|
69
|
+
|
|
70
|
+
result = {
|
|
71
|
+
"decision": "block",
|
|
72
|
+
"reason": reason_text
|
|
73
|
+
}
|
|
74
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
# 发生错误时,默认允许停止(避免卡死)
|
|
79
|
+
error_result = {
|
|
80
|
+
"decision": "approve",
|
|
81
|
+
"reason": f"Hook处理出错: {str(e)}"
|
|
82
|
+
}
|
|
83
|
+
print(json.dumps(error_result, ensure_ascii=False), file=sys.stderr)
|
|
84
|
+
return 1
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
sys.exit(main())
|
tabs/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
标签页模块 - 导出所有标签页组件
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# 基础标签页
|
|
6
|
+
try:
|
|
7
|
+
from .base_tab import BaseTab
|
|
8
|
+
except ImportError:
|
|
9
|
+
BaseTab = None
|
|
10
|
+
|
|
11
|
+
# 简单标签页
|
|
12
|
+
try:
|
|
13
|
+
from .workflow_tab import WorkflowTab
|
|
14
|
+
except ImportError:
|
|
15
|
+
WorkflowTab = None
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from .taskflow_tab import TaskflowTab
|
|
19
|
+
except ImportError:
|
|
20
|
+
TaskflowTab = None
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from .new_work_tab import NewWorkTab
|
|
24
|
+
except ImportError:
|
|
25
|
+
NewWorkTab = None
|
|
26
|
+
|
|
27
|
+
# 复杂标签页 - 分别导入,避免一个失败影响其他
|
|
28
|
+
try:
|
|
29
|
+
from .chat_tab import ChatTab
|
|
30
|
+
except ImportError:
|
|
31
|
+
ChatTab = None
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
from .stats_tab import StatsTab
|
|
35
|
+
except ImportError:
|
|
36
|
+
StatsTab = None
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from .memory_tab import MemoryTab
|
|
40
|
+
except ImportError:
|
|
41
|
+
MemoryTab = None
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
from .rules_tab import RulesTab
|
|
45
|
+
except ImportError:
|
|
46
|
+
RulesTab = None
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from .todos_tab import TodosTab
|
|
50
|
+
except ImportError:
|
|
51
|
+
TodosTab = None
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
from .checkpoints_tab import CheckpointsTab
|
|
55
|
+
except ImportError:
|
|
56
|
+
CheckpointsTab = None
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
from .new_project_tab import NewProjectTab
|
|
60
|
+
except ImportError:
|
|
61
|
+
NewProjectTab = None
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
from .workspace_tab import WorkspaceTab
|
|
65
|
+
except ImportError:
|
|
66
|
+
WorkspaceTab = None
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
from .chat_history_tab import ChatHistoryTab
|
|
70
|
+
except ImportError:
|
|
71
|
+
ChatHistoryTab = None
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
'BaseTab',
|
|
75
|
+
'WorkflowTab',
|
|
76
|
+
'TaskflowTab',
|
|
77
|
+
'NewWorkTab',
|
|
78
|
+
'ChatTab',
|
|
79
|
+
'ChatHistoryTab',
|
|
80
|
+
'StatsTab',
|
|
81
|
+
'MemoryTab',
|
|
82
|
+
'RulesTab',
|
|
83
|
+
'TodosTab',
|
|
84
|
+
'CheckpointsTab',
|
|
85
|
+
'NewProjectTab',
|
|
86
|
+
'WorkspaceTab'
|
|
87
|
+
]
|
tabs/base_tab.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
选项卡基础类
|
|
3
|
+
|
|
4
|
+
定义所有选项卡的通用接口和行为。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod, ABCMeta
|
|
8
|
+
from PySide6.QtWidgets import QWidget
|
|
9
|
+
from PySide6.QtCore import QObject
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseTabMeta(type(QWidget), ABCMeta):
|
|
13
|
+
"""解决QWidget和ABC的元类冲突"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseTab(QWidget, ABC, metaclass=BaseTabMeta):
|
|
18
|
+
"""选项卡基础类
|
|
19
|
+
|
|
20
|
+
所有选项卡都应该继承此类,并实现_setup_ui方法。
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, parent=None):
|
|
24
|
+
super().__init__(parent)
|
|
25
|
+
self._setup_ui()
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def _setup_ui(self):
|
|
29
|
+
"""子类必须实现的UI创建方法"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def refresh_data(self):
|
|
33
|
+
"""刷新数据的通用方法,子类可以重写"""
|
|
34
|
+
pass
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/* Chat History Tab Modern Styles */
|
|
2
|
+
|
|
3
|
+
/* Global Scroll Area */
|
|
4
|
+
QScrollArea {
|
|
5
|
+
border: none;
|
|
6
|
+
background-color: #1e1e1e;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
QWidget#messagesContainer {
|
|
10
|
+
background-color: #1e1e1e;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* -------------------------------------------------------------------------
|
|
14
|
+
Bubbles
|
|
15
|
+
------------------------------------------------------------------------- */
|
|
16
|
+
QFrame {
|
|
17
|
+
border: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
QFrame#userBubble {
|
|
21
|
+
background-color: #0e639c; /* Blue */
|
|
22
|
+
border-radius: 12px;
|
|
23
|
+
border-top-right-radius: 2px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
QFrame#aiBubble {
|
|
27
|
+
background-color: #252526; /* Dark Gray */
|
|
28
|
+
border: 1px solid #454545;
|
|
29
|
+
border-radius: 12px;
|
|
30
|
+
border-top-left-radius: 2px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
QFrame#agentBubble {
|
|
34
|
+
background-color: #2d2b38; /* Slight Purple/Dark */
|
|
35
|
+
border: 1px solid #453555;
|
|
36
|
+
border-radius: 12px;
|
|
37
|
+
border-top-left-radius: 2px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* -------------------------------------------------------------------------
|
|
41
|
+
Avatars
|
|
42
|
+
------------------------------------------------------------------------- */
|
|
43
|
+
QLabel#avatarLabel {
|
|
44
|
+
font-size: 18px;
|
|
45
|
+
background-color: transparent;
|
|
46
|
+
padding: 4px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* -------------------------------------------------------------------------
|
|
50
|
+
Role Labels
|
|
51
|
+
------------------------------------------------------------------------- */
|
|
52
|
+
QLabel#roleLabel {
|
|
53
|
+
font-size: 11px;
|
|
54
|
+
font-weight: bold;
|
|
55
|
+
color: #cccccc;
|
|
56
|
+
margin-bottom: 2px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* -------------------------------------------------------------------------
|
|
60
|
+
Empty State
|
|
61
|
+
------------------------------------------------------------------------- */
|
|
62
|
+
QLabel#emptyStateLabel {
|
|
63
|
+
color: #6e6e6e;
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
padding: 40px;
|
|
66
|
+
}
|