myagent-ai 1.5.6 → 1.5.7
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.
- package/agents/__pycache__/main_agent.cpython-312.pyc +0 -0
- package/agents/main_agent.py +98 -1
- package/install/install.ps1 +14 -2
- package/install/install.sh +17 -3
- package/package.json +1 -1
- package/web/__pycache__/api_server.cpython-312.pyc +0 -0
- package/web/api_server.py +39 -7
- package/web/ui/chat.html +429 -9
- package/web/ui/index.html +145 -22
|
Binary file
|
package/agents/main_agent.py
CHANGED
|
@@ -115,6 +115,31 @@ class MainAgent(BaseAgent):
|
|
|
115
115
|
self._org_context_mtime: float = 0 # 文件修改时间,用于检测变更
|
|
116
116
|
# Token 上下文管理器 (滚动摘要 + 预算控制)
|
|
117
117
|
self.context_manager = ContextManager(ContextConfig())
|
|
118
|
+
# 执行事件追踪(用于前端展示命令执行过程)
|
|
119
|
+
self._execution_events: List[Dict] = []
|
|
120
|
+
self._exec_event_counter: int = 0
|
|
121
|
+
|
|
122
|
+
def _add_exec_event(self, event_type: str, data: Dict):
|
|
123
|
+
"""记录一个执行事件(供前端展示)"""
|
|
124
|
+
import time as _time
|
|
125
|
+
self._exec_event_counter += 1
|
|
126
|
+
event = {
|
|
127
|
+
"id": self._exec_event_counter,
|
|
128
|
+
"type": event_type,
|
|
129
|
+
"timestamp": _time.time(),
|
|
130
|
+
**data,
|
|
131
|
+
}
|
|
132
|
+
self._execution_events.append(event)
|
|
133
|
+
logger.debug(f"[exec-event] {event_type}: {data.get('title', '')[:80]}")
|
|
134
|
+
|
|
135
|
+
def get_execution_events(self) -> List[Dict]:
|
|
136
|
+
"""获取本次处理中的所有执行事件"""
|
|
137
|
+
return self._execution_events
|
|
138
|
+
|
|
139
|
+
def clear_execution_events(self):
|
|
140
|
+
"""清空执行事件"""
|
|
141
|
+
self._execution_events = []
|
|
142
|
+
self._exec_event_counter = 0
|
|
118
143
|
|
|
119
144
|
async def process(self, context: AgentContext) -> AgentContext:
|
|
120
145
|
"""
|
|
@@ -133,6 +158,8 @@ class MainAgent(BaseAgent):
|
|
|
133
158
|
context.task_id = task_id
|
|
134
159
|
self._iteration_count = 0
|
|
135
160
|
self._current_task_id = task_id
|
|
161
|
+
# 清空上一轮的执行事件
|
|
162
|
+
self.clear_execution_events()
|
|
136
163
|
|
|
137
164
|
# 注册到配置广播器(用于热重载通知)
|
|
138
165
|
if self.config_broadcaster:
|
|
@@ -217,10 +244,26 @@ class MainAgent(BaseAgent):
|
|
|
217
244
|
content=response.content or "",
|
|
218
245
|
tool_calls=response.tool_calls)
|
|
219
246
|
)
|
|
247
|
+
# 记录工具调用事件
|
|
248
|
+
for tc in response.tool_calls:
|
|
249
|
+
self._add_exec_event("tool_call", {
|
|
250
|
+
"title": f"调用工具: {tc['name']}",
|
|
251
|
+
"tool_name": tc["name"],
|
|
252
|
+
"arguments": tc.get("arguments", {}),
|
|
253
|
+
})
|
|
220
254
|
# 执行工具调用
|
|
221
255
|
tool_results = await self._handle_tool_calls(
|
|
222
256
|
response.tool_calls, context, task_id
|
|
223
257
|
)
|
|
258
|
+
# 记录工具结果事件
|
|
259
|
+
for tc, result in tool_results:
|
|
260
|
+
self._add_exec_event("tool_result", {
|
|
261
|
+
"title": f"工具结果: {tc['name']}",
|
|
262
|
+
"tool_name": tc["name"],
|
|
263
|
+
"success": result.get("success", False),
|
|
264
|
+
"summary": truncate_str(result.get("output", result.get("error", "")), 500),
|
|
265
|
+
"result": result,
|
|
266
|
+
})
|
|
224
267
|
# 再将工具结果加入消息历史
|
|
225
268
|
for tc, result in tool_results:
|
|
226
269
|
context.conversation_history.append(
|
|
@@ -510,13 +553,33 @@ class MainAgent(BaseAgent):
|
|
|
510
553
|
"error": f"[权限] 当前 Agent 没有'{label}'权限,操作被拒绝",
|
|
511
554
|
"metadata": {"permission_denied": skill_perm},
|
|
512
555
|
})
|
|
556
|
+
self._add_exec_event("skill_call", {
|
|
557
|
+
"title": f"技能调用被拒: {skill_name}",
|
|
558
|
+
"skill_name": skill_name,
|
|
559
|
+
"success": False,
|
|
560
|
+
"error": f"权限不足: {label}",
|
|
561
|
+
})
|
|
513
562
|
continue
|
|
514
563
|
|
|
564
|
+
# 记录技能调用事件
|
|
565
|
+
self._add_exec_event("skill_call", {
|
|
566
|
+
"title": f"调用技能: {skill_name}",
|
|
567
|
+
"skill_name": skill_name,
|
|
568
|
+
"params": action.get("params", {}),
|
|
569
|
+
})
|
|
515
570
|
result = await self.skills.execute(
|
|
516
571
|
action.get("name", ""),
|
|
517
572
|
**action.get("params", {}),
|
|
518
573
|
)
|
|
519
|
-
|
|
574
|
+
result_dict = result.to_dict()
|
|
575
|
+
results.append(result_dict)
|
|
576
|
+
# 记录技能结果事件
|
|
577
|
+
self._add_exec_event("skill_result", {
|
|
578
|
+
"title": f"技能结果: {skill_name}",
|
|
579
|
+
"skill_name": skill_name,
|
|
580
|
+
"success": result_dict.get("success", False),
|
|
581
|
+
"summary": truncate_str(str(result_dict.get("output", result_dict.get("error", ""))), 500),
|
|
582
|
+
})
|
|
520
583
|
|
|
521
584
|
elif action_type == "code" and self.executor:
|
|
522
585
|
# 权限检查: execution
|
|
@@ -526,8 +589,27 @@ class MainAgent(BaseAgent):
|
|
|
526
589
|
"error": "[权限] 当前 Agent 没有代码执行权限,操作被拒绝",
|
|
527
590
|
"metadata": {"permission_denied": "execution"},
|
|
528
591
|
})
|
|
592
|
+
self._add_exec_event("code_exec", {
|
|
593
|
+
"title": f"代码执行被拒",
|
|
594
|
+
"language": action.get("language", "unknown"),
|
|
595
|
+
"code_preview": truncate_str(action.get("code", ""), 200),
|
|
596
|
+
"success": False,
|
|
597
|
+
"error": "代码执行权限不足",
|
|
598
|
+
})
|
|
529
599
|
continue
|
|
530
600
|
|
|
601
|
+
code_lang = action.get("language", "python")
|
|
602
|
+
code_text = action.get("code", "")
|
|
603
|
+
# 记录代码执行开始事件
|
|
604
|
+
self._add_exec_event("code_exec", {
|
|
605
|
+
"title": f"执行 {code_lang} 代码",
|
|
606
|
+
"language": code_lang,
|
|
607
|
+
"code": code_text,
|
|
608
|
+
"code_preview": truncate_str(code_text, 200),
|
|
609
|
+
"status": "running",
|
|
610
|
+
"timeout": timeout_seconds if 'timeout_seconds' in dir() else 120,
|
|
611
|
+
})
|
|
612
|
+
|
|
531
613
|
# 注入权限检查器到 executor(用于更细粒度的检查)
|
|
532
614
|
self.executor.set_permission_checker(
|
|
533
615
|
self.check_permission, self.name
|
|
@@ -563,6 +645,21 @@ class MainAgent(BaseAgent):
|
|
|
563
645
|
|
|
564
646
|
result_dict = exec_result.to_dict()
|
|
565
647
|
|
|
648
|
+
# 记录代码执行结果事件
|
|
649
|
+
self._add_exec_event("code_result", {
|
|
650
|
+
"title": f"{'超时' if exec_result.timed_out else '成功' if exec_result.success else '失败'}: {code_lang}",
|
|
651
|
+
"language": code_lang,
|
|
652
|
+
"code_preview": truncate_str(action.get("code", ""), 200),
|
|
653
|
+
"success": exec_result.success,
|
|
654
|
+
"timed_out": exec_result.timed_out,
|
|
655
|
+
"exit_code": exec_result.exit_code,
|
|
656
|
+
"execution_time": round(exec_result.execution_time, 3),
|
|
657
|
+
"stdout": truncate_str(exec_result.stdout, 5000),
|
|
658
|
+
"stderr": truncate_str(exec_result.stderr, 3000),
|
|
659
|
+
"error": truncate_str(exec_result.error, 2000),
|
|
660
|
+
"result": result_dict,
|
|
661
|
+
})
|
|
662
|
+
|
|
566
663
|
# 超时后自动触发 LLM 诊断分析
|
|
567
664
|
if exec_result.timed_out:
|
|
568
665
|
logger.info(
|
package/install/install.ps1
CHANGED
|
@@ -9,15 +9,27 @@ param(
|
|
|
9
9
|
|
|
10
10
|
$ErrorActionPreference = "Stop"
|
|
11
11
|
$PKG_NAME = "myagent-ai"
|
|
12
|
-
$PKG_VERSION = "
|
|
12
|
+
$PKG_VERSION = ""
|
|
13
|
+
|
|
14
|
+
# 动态获取 npm 最新版本号
|
|
15
|
+
function Get-NpmVersion {
|
|
16
|
+
try {
|
|
17
|
+
$ver = (npm view $PKG_NAME version 2>$null)
|
|
18
|
+
if ($ver) { $script:PKG_VERSION = $ver }
|
|
19
|
+
} catch {}
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
# Allow running scripts for the current process
|
|
15
23
|
if ($PSVersionTable.PSVersion.Major -ge 5) {
|
|
16
24
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force
|
|
17
25
|
}
|
|
18
26
|
|
|
27
|
+
# 尝试获取最新版本
|
|
28
|
+
Get-NpmVersion
|
|
29
|
+
|
|
30
|
+
$verDisplay = if ($PKG_VERSION) { " v$PKG_VERSION" } else { "" }
|
|
19
31
|
Write-Host ""
|
|
20
|
-
Write-Host " MyAgent Installer
|
|
32
|
+
Write-Host " MyAgent Installer$verDisplay" -ForegroundColor Cyan
|
|
21
33
|
Write-Host ""
|
|
22
34
|
|
|
23
35
|
if ($PSVersionTable.PSVersion.Major -lt 5) {
|
package/install/install.sh
CHANGED
|
@@ -21,14 +21,25 @@ NO_DEPS=false
|
|
|
21
21
|
DRY_RUN=false
|
|
22
22
|
SCRIPT_URL="https://raw.githubusercontent.com/ctz168/myagent/main/install/install.sh"
|
|
23
23
|
PKG_NAME="myagent-ai"
|
|
24
|
-
PKG_VERSION="
|
|
24
|
+
PKG_VERSION=""
|
|
25
|
+
|
|
26
|
+
# 动态获取 npm 最新版本号
|
|
27
|
+
fetch_version() {
|
|
28
|
+
PKG_VERSION=""
|
|
29
|
+
local npm_ver
|
|
30
|
+
npm_ver="$(npm view "$PKG_NAME" version 2>/dev/null)" || true
|
|
31
|
+
if [ -n "$npm_ver" ]; then
|
|
32
|
+
PKG_VERSION="$npm_ver"
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
25
35
|
|
|
26
36
|
for arg in "$@"; do
|
|
27
37
|
case "$arg" in
|
|
28
38
|
--no-deps) NO_DEPS=true ;;
|
|
29
39
|
--dry-run) DRY_RUN=true ;;
|
|
30
40
|
--help|-h)
|
|
31
|
-
|
|
41
|
+
fetch_version
|
|
42
|
+
echo "MyAgent Installer${PKG_VERSION:+ v$PKG_VERSION}"
|
|
32
43
|
echo ""
|
|
33
44
|
echo "Usage: curl -fsSL $SCRIPT_URL | bash [-s -- OPTIONS]"
|
|
34
45
|
echo ""
|
|
@@ -60,9 +71,12 @@ detect_os() {
|
|
|
60
71
|
esac
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
# 尝试获取最新版本(获取失败则不显示版本号)
|
|
75
|
+
fetch_version
|
|
76
|
+
|
|
63
77
|
OS="$(detect_os)"
|
|
64
78
|
echo ""
|
|
65
|
-
echo -e " ${BOLD}${ACCENT}MyAgent${NC} Installer v$PKG_VERSION"
|
|
79
|
+
echo -e " ${BOLD}${ACCENT}MyAgent${NC} Installer${PKG_VERSION:+ v$PKG_VERSION}"
|
|
66
80
|
echo ""
|
|
67
81
|
success "$OS detected"
|
|
68
82
|
|
package/package.json
CHANGED
|
Binary file
|
package/web/api_server.py
CHANGED
|
@@ -447,6 +447,11 @@ class ApiServer:
|
|
|
447
447
|
# 记忆保存已由 MainAgent._process_inner() 完成,此处不再重复保存
|
|
448
448
|
# (MainAgent 内部会保存 user + assistant 消息到短期记忆)
|
|
449
449
|
|
|
450
|
+
# ── 收集执行事件(供前端展示命令执行过程) ──
|
|
451
|
+
exec_events = []
|
|
452
|
+
if self.core.main_agent:
|
|
453
|
+
exec_events = self.core.main_agent.get_execution_events()
|
|
454
|
+
|
|
450
455
|
# ── 执行模式: 从回复中提取并更新任务计划 ──
|
|
451
456
|
if chat_mode == "exec" and self.core.llm:
|
|
452
457
|
try:
|
|
@@ -454,7 +459,10 @@ class ApiServer:
|
|
|
454
459
|
except Exception as tp_err:
|
|
455
460
|
logger.warning(f"任务计划更新失败: {tp_err}")
|
|
456
461
|
|
|
457
|
-
|
|
462
|
+
resp_data = {"response": response, "session_id": session_id, "agent_name": agent_path, "agent_path": agent_path}
|
|
463
|
+
if exec_events:
|
|
464
|
+
resp_data["exec_events"] = exec_events
|
|
465
|
+
return web.json_response(resp_data)
|
|
458
466
|
except Exception as e:
|
|
459
467
|
logger.error(f"Chat error: {e}", exc_info=True)
|
|
460
468
|
return web.json_response({"error": str(e)}, status=500)
|
|
@@ -843,13 +851,10 @@ class ApiServer:
|
|
|
843
851
|
if not cfg_file.exists():
|
|
844
852
|
continue
|
|
845
853
|
agent_path = f"{prefix}{d.name}" if not prefix else f"{prefix}/{d.name}"
|
|
846
|
-
# 迁移: 为缺少 id/created_at/updated_at 的旧 agent 补全字段
|
|
847
854
|
# 跳过符号链接目录(如 p -> 配置助手),避免重复处理
|
|
848
855
|
if d.is_symlink():
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
except (json.JSONDecodeError, ValueError):
|
|
852
|
-
continue
|
|
856
|
+
continue
|
|
857
|
+
# 迁移: 为缺少 id/created_at/updated_at 的旧 agent 补全字段
|
|
853
858
|
else:
|
|
854
859
|
self._migrate_agent_config(agent_path)
|
|
855
860
|
# 迁移可能删除了损坏的文件
|
|
@@ -1178,6 +1183,26 @@ class ApiServer:
|
|
|
1178
1183
|
"agent_path": path,
|
|
1179
1184
|
}, status=403)
|
|
1180
1185
|
if ad.exists():
|
|
1186
|
+
# 清理该 Agent 相关的会话和记忆数据
|
|
1187
|
+
if self.core.memory:
|
|
1188
|
+
try:
|
|
1189
|
+
# 删除相关会话 (session_id 格式: agent_session_xxx 或 agent_web_xxx)
|
|
1190
|
+
conn = self.core.memory._get_conn()
|
|
1191
|
+
like_pattern = f"{path}%"
|
|
1192
|
+
rows = conn.execute(
|
|
1193
|
+
"SELECT DISTINCT session_id FROM memories WHERE session_id LIKE ?",
|
|
1194
|
+
(like_pattern,),
|
|
1195
|
+
).fetchall()
|
|
1196
|
+
for row in rows:
|
|
1197
|
+
sid = row["session_id"]
|
|
1198
|
+
# 删除该会话的所有记忆
|
|
1199
|
+
conn.execute("DELETE FROM memories WHERE session_id = ?", (sid,))
|
|
1200
|
+
conn.execute("DELETE FROM session_names WHERE session_id = ?", (sid,))
|
|
1201
|
+
logger.info(f" 清理会话记忆: {sid}")
|
|
1202
|
+
conn.commit()
|
|
1203
|
+
logger.info(f" 已清理 Agent '{path}' 的 {len(rows)} 个会话记忆")
|
|
1204
|
+
except Exception as e:
|
|
1205
|
+
logger.warning(f"清理会话记忆失败: {e}")
|
|
1181
1206
|
shutil.rmtree(ad)
|
|
1182
1207
|
logger.info(f"删除 Agent: {path}")
|
|
1183
1208
|
return web.json_response({"ok": True})
|
|
@@ -1573,7 +1598,12 @@ class ApiServer:
|
|
|
1573
1598
|
return web.json_response({"error": "权限管理器未初始化"}, status=500)
|
|
1574
1599
|
data = await request.json()
|
|
1575
1600
|
try:
|
|
1576
|
-
|
|
1601
|
+
# data 格式: {"execution": true, "file_read": false, ...}
|
|
1602
|
+
if isinstance(data, dict):
|
|
1603
|
+
for perm, value in data.items():
|
|
1604
|
+
if isinstance(value, bool) and perm in PermissionManager.ALL_PERMISSIONS:
|
|
1605
|
+
pm.set_default_permission(perm, value)
|
|
1606
|
+
pm.save()
|
|
1577
1607
|
return web.json_response({"ok": True})
|
|
1578
1608
|
except Exception as e:
|
|
1579
1609
|
return web.json_response({"error": str(e)}, status=500)
|
|
@@ -1599,6 +1629,7 @@ class ApiServer:
|
|
|
1599
1629
|
data = await request.json()
|
|
1600
1630
|
try:
|
|
1601
1631
|
pm.set_permissions(agent, data)
|
|
1632
|
+
pm.save()
|
|
1602
1633
|
return web.json_response({"ok": True})
|
|
1603
1634
|
except Exception as e:
|
|
1604
1635
|
return web.json_response({"error": str(e)}, status=500)
|
|
@@ -1611,6 +1642,7 @@ class ApiServer:
|
|
|
1611
1642
|
agent = request.match_info["agent"]
|
|
1612
1643
|
try:
|
|
1613
1644
|
pm.reset_agent_permissions(agent)
|
|
1645
|
+
pm.save()
|
|
1614
1646
|
return web.json_response({"ok": True})
|
|
1615
1647
|
except Exception as e:
|
|
1616
1648
|
return web.json_response({"error": str(e)}, status=500)
|
package/web/ui/chat.html
CHANGED
|
@@ -21,6 +21,33 @@
|
|
|
21
21
|
--sidebar-w:280px;--header-h:60px;--agent-panel-w:300px;
|
|
22
22
|
--transition:all .2s ease;
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
/* ── Theme System ── */
|
|
26
|
+
[data-theme="claude"]{
|
|
27
|
+
--bg:#f5f0e8;--bg2:#ebe5d9;--bg3:#e2dace;--bg4:#d6cfc3;--bg5:#c8bfb0;
|
|
28
|
+
--text:#2c2c2c;--text2:#5a5a5a;--text3:#8a8a8a;
|
|
29
|
+
--accent:#c96442;--accent2:#d97a5a;--accent-light:#fceee8;--accent-dark:#a0502e;
|
|
30
|
+
--user-bubble:#c96442;--user-text:#ffffff;
|
|
31
|
+
--bot-bubble:#ffffff;--bot-text:#2c2c2c;
|
|
32
|
+
--ok:#4a9c6d;--warn:#c4862b;--danger:#c94444;--info:#4a7fc9;
|
|
33
|
+
--border:#d6cfc3;--border-light:#e2dace;
|
|
34
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.06);
|
|
35
|
+
--shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.04);
|
|
36
|
+
--shadow-lg:0 10px 25px rgba(0,0,0,.1);
|
|
37
|
+
}
|
|
38
|
+
[data-theme="dark"]{
|
|
39
|
+
--bg:#0f1117;--bg2:#1a1d27;--bg3:#232733;--bg4:#2e3347;--bg5:#3a3f57;
|
|
40
|
+
--text:#e1e4ed;--text2:#8b92a5;--text3:#5c6378;
|
|
41
|
+
--accent:#6366f1;--accent2:#818cf8;--accent-light:#2d2f5e;--accent-dark:#9da3f8;
|
|
42
|
+
--user-bubble:#6366f1;--user-text:#ffffff;
|
|
43
|
+
--bot-bubble:#1a1d27;--bot-text:#e1e4ed;
|
|
44
|
+
--ok:#22c55e;--warn:#f59e0b;--danger:#ef4444;--info:#3b82f6;
|
|
45
|
+
--border:#2e3347;--border-light:#232733;
|
|
46
|
+
--shadow-sm:0 1px 2px rgba(0,0,0,.2);
|
|
47
|
+
--shadow:0 1px 3px rgba(0,0,0,.3),0 1px 2px rgba(0,0,0,.2);
|
|
48
|
+
--shadow-lg:0 10px 25px rgba(0,0,0,.4);
|
|
49
|
+
}
|
|
50
|
+
|
|
24
51
|
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,'Noto Sans SC',sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;overflow:hidden}
|
|
25
52
|
a{color:var(--accent);text-decoration:none}
|
|
26
53
|
button{font:inherit;cursor:pointer;border:none;background:none;color:inherit}
|
|
@@ -1281,6 +1308,111 @@ input,textarea,select{font:inherit}
|
|
|
1281
1308
|
.tts-voice-item.active{background:var(--accent-light);color:var(--accent)}
|
|
1282
1309
|
.tts-voice-item .voice-icon{font-size:14px}
|
|
1283
1310
|
.tts-voice-item .voice-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1311
|
+
|
|
1312
|
+
/* ── Dark Theme Overrides ── */
|
|
1313
|
+
[data-theme="dark"] .message-bubble code{background:rgba(255,255,255,.1)}
|
|
1314
|
+
[data-theme="dark"] .message-row.user .message-bubble code{background:rgba(255,255,255,.15)}
|
|
1315
|
+
[data-theme="dark"] .message-bubble pre{background:#0a0c10;color:#cdd6f4}
|
|
1316
|
+
[data-theme="dark"] .group-msg-bubble code{background:rgba(255,255,255,.08)}
|
|
1317
|
+
[data-theme="dark"] .group-msg-bubble pre{background:#0a0c10;color:#cdd6f4}
|
|
1318
|
+
[data-theme="dark"] .toast-error{background:rgba(127,29,29,.8);color:#fca5a5;border-color:rgba(239,68,68,.4)}
|
|
1319
|
+
[data-theme="dark"] .toast-success{background:rgba(6,78,59,.8);color:#6ee7b7;border-color:rgba(16,185,129,.4)}
|
|
1320
|
+
[data-theme="dark"] .toast-info{background:rgba(30,64,175,.8);color:#93c5fd;border-color:rgba(59,130,246,.4)}
|
|
1321
|
+
[data-theme="dark"] .modal-overlay{background:rgba(0,0,0,.5)}
|
|
1322
|
+
[data-theme="dark"] .exec-badge.local{background:rgba(16,185,129,.15);color:#6ee7b7}
|
|
1323
|
+
[data-theme="dark"] .exec-badge.sandbox{background:rgba(245,158,11,.15);color:#fcd34d}
|
|
1324
|
+
[data-theme="dark"] .mode-btn.active-chat{background:var(--bg2);color:var(--accent);box-shadow:var(--shadow-sm)}
|
|
1325
|
+
[data-theme="dark"] .mode-btn.active-exec{background:var(--accent);color:#fff;box-shadow:var(--shadow-sm)}
|
|
1326
|
+
[data-theme="dark"] .exec-mode-btn.active-local{background:rgba(16,185,129,.15);color:#6ee7b7;border-color:rgba(16,185,129,.3)}
|
|
1327
|
+
[data-theme="dark"] .exec-mode-btn.active-local:hover{background:rgba(16,185,129,.25)}
|
|
1328
|
+
[data-theme="dark"] .lock-indicator{background:rgba(127,29,29,.3);border-color:rgba(239,68,68,.3);color:#fca5a5}
|
|
1329
|
+
[data-theme="dark"] .lock-indicator:hover{background:rgba(127,29,29,.5)}
|
|
1330
|
+
[data-theme="dark"] .code-block-wrapper pre{background:#0a0c10;color:#cdd6f4}
|
|
1331
|
+
[data-theme="dark"] .code-copy-btn{background:var(--bg4);color:var(--text2)}
|
|
1332
|
+
[data-theme="dark"] .code-copy-btn:hover{background:var(--bg5);color:var(--text)}
|
|
1333
|
+
[data-theme="dark"] .code-copy-btn.copied{background:rgba(16,185,129,.3);color:#a7f3d0}
|
|
1334
|
+
[data-theme="dark"] .group-member-role.owner{background:rgba(245,158,11,.15);color:#fcd34d}
|
|
1335
|
+
[data-theme="dark"] .group-member-role.admin{background:rgba(59,130,246,.15);color:#93c5fd}
|
|
1336
|
+
[data-theme="dark"] .setup-wizard-overlay{background:rgba(10,10,20,.9)}
|
|
1337
|
+
[data-theme="dark"] .largetext-option:hover,[data-theme="dark"] .largetext-option.selected{border-color:var(--accent);background:rgba(99,102,241,.1)}
|
|
1338
|
+
[data-theme="dark"] .platform-toggle{background:var(--bg4)}
|
|
1339
|
+
[data-theme="dark"] .quick-action{background:var(--bg2);border-color:var(--border)}
|
|
1340
|
+
[data-theme="dark"] .quick-action:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-light)}
|
|
1341
|
+
[data-theme="dark"] .task-delete:hover{background:rgba(127,29,29,.3)}
|
|
1342
|
+
[data-theme="dark"] .platform-card-btn.delete:hover{background:rgba(127,29,29,.3)}
|
|
1343
|
+
[data-theme="dark"] ::-webkit-scrollbar-thumb{background:var(--bg4)}
|
|
1344
|
+
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover{background:var(--bg5)}
|
|
1345
|
+
[data-theme="claude"] .sidebar-logo{background:linear-gradient(135deg,#c96442,#a0502e)}
|
|
1346
|
+
[data-theme="claude"] .new-chat-btn{background:var(--accent)}
|
|
1347
|
+
[data-theme="claude"] .new-chat-btn:hover{background:var(--accent2)}
|
|
1348
|
+
|
|
1349
|
+
/* ── Execution Events Panel ── */
|
|
1350
|
+
.exec-events-panel{margin-top:10px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;background:var(--bg)}
|
|
1351
|
+
.exec-events-header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:var(--bg2);border-bottom:1px solid var(--border);cursor:pointer;font-size:12px;font-weight:600;color:var(--text2);user-select:none;transition:var(--transition)}
|
|
1352
|
+
.exec-events-header:hover{background:var(--bg3);color:var(--text)}
|
|
1353
|
+
.exec-events-header .events-chevron{transition:transform .2s ease;font-size:10px}
|
|
1354
|
+
.exec-events-header.expanded .events-chevron{transform:rotate(90deg)}
|
|
1355
|
+
.exec-events-header .events-count{background:var(--accent);color:#fff;font-size:10px;padding:0 6px;border-radius:10px;font-weight:700;min-width:18px;text-align:center}
|
|
1356
|
+
.exec-events-body{max-height:0;overflow:hidden;transition:max-height .3s ease}
|
|
1357
|
+
.exec-events-body.expanded{max-height:600px;overflow-y:auto}
|
|
1358
|
+
.exec-event-item{display:flex;align-items:flex-start;gap:8px;padding:8px 12px;border-bottom:1px solid var(--border-light);font-size:12px;transition:background .15s}
|
|
1359
|
+
.exec-event-item:last-child{border-bottom:none}
|
|
1360
|
+
.exec-event-item:hover{background:var(--bg2)}
|
|
1361
|
+
.exec-event-icon{width:20px;height:20px;border-radius:4px;display:grid;place-items:center;flex-shrink:0;font-size:11px;margin-top:1px}
|
|
1362
|
+
.exec-event-icon.tool{background:#eff6ff;color:#3b82f6}
|
|
1363
|
+
.exec-event-icon.code-run{background:#fef3c7;color:#d97706}
|
|
1364
|
+
.exec-event-icon.code-ok{background:#dcfce7;color:#16a34a}
|
|
1365
|
+
.exec-event-icon.code-fail{background:#fee2e2;color:#dc2626}
|
|
1366
|
+
.exec-event-icon.code-timeout{background:#fce7f3;color:#db2777}
|
|
1367
|
+
.exec-event-icon.skill{background:#f3e8ff;color:#7c3aed}
|
|
1368
|
+
.exec-event-body{flex:1;min-width:0}
|
|
1369
|
+
.exec-event-title{font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1370
|
+
.exec-event-meta{font-size:11px;color:var(--text3);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}
|
|
1371
|
+
.exec-event-meta .meta-tag{padding:0 4px;border-radius:3px;background:var(--bg3);white-space:nowrap}
|
|
1372
|
+
.exec-event-code{margin-top:4px;padding:4px 8px;background:var(--bg3);border-radius:4px;font-family:'SF Mono',Monaco,Consolas,'Courier New',monospace;font-size:11px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;transition:var(--transition)}
|
|
1373
|
+
.exec-event-code:hover{background:var(--bg4);color:var(--text)}
|
|
1374
|
+
.exec-event-summary{margin-top:3px;font-size:11px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
|
|
1375
|
+
.exec-event-result-btn{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;margin-top:4px;border-radius:4px;background:var(--bg3);color:var(--accent);font-size:11px;cursor:pointer;border:none;transition:var(--transition)}
|
|
1376
|
+
.exec-event-result-btn:hover{background:var(--accent-light);color:var(--accent-dark)}
|
|
1377
|
+
.exec-event-result-btn svg{width:12px;height:12px}
|
|
1378
|
+
|
|
1379
|
+
/* ── Execution Result Modal ── */
|
|
1380
|
+
.exec-result-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1000;display:flex;align-items:center;justify-content:center;animation:fadeIn .15s ease}
|
|
1381
|
+
.exec-result-modal{background:var(--bg);border:1px solid var(--border);border-radius:12px;width:min(680px,90vw);max-height:80vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.25);animation:slideUp .2s ease}
|
|
1382
|
+
.exec-result-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--border);flex-shrink:0}
|
|
1383
|
+
.exec-result-modal-header h3{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}
|
|
1384
|
+
.exec-result-modal-header h3 .status-icon{font-size:16px}
|
|
1385
|
+
.exec-result-modal-close{width:28px;height:28px;border-radius:6px;display:grid;place-items:center;cursor:pointer;color:var(--text3);transition:var(--transition);border:none;background:none}
|
|
1386
|
+
.exec-result-modal-close:hover{background:var(--bg3);color:var(--text)}
|
|
1387
|
+
.exec-result-modal-tabs{display:flex;border-bottom:1px solid var(--border);padding:0 18px;flex-shrink:0}
|
|
1388
|
+
.exec-result-tab{padding:8px 14px;font-size:12px;font-weight:600;color:var(--text3);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;transition:var(--transition)}
|
|
1389
|
+
.exec-result-tab:hover{color:var(--text2)}
|
|
1390
|
+
.exec-result-tab.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
1391
|
+
.exec-result-modal-body{flex:1;overflow-y:auto;padding:16px 18px;min-height:0}
|
|
1392
|
+
.exec-result-section{display:none}
|
|
1393
|
+
.exec-result-section.active{display:block}
|
|
1394
|
+
.exec-result-section pre{background:var(--bg3);border:1px solid var(--border-light);border-radius:8px;padding:12px 14px;font-family:'SF Mono',Monaco,Consolas,'Courier New',monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-all;max-height:50vh;overflow-y:auto;color:var(--text)}
|
|
1395
|
+
.exec-result-info{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin-bottom:12px}
|
|
1396
|
+
.exec-result-info-item{padding:8px 12px;background:var(--bg2);border-radius:6px}
|
|
1397
|
+
.exec-result-info-item .label{font-size:11px;color:var(--text3);margin-bottom:2px}
|
|
1398
|
+
.exec-result-info-item .value{font-size:13px;font-weight:600;color:var(--text)}
|
|
1399
|
+
.exec-result-empty{text-align:center;padding:40px;color:var(--text3);font-size:13px}
|
|
1400
|
+
|
|
1401
|
+
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
|
1402
|
+
@keyframes slideUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
|
1403
|
+
|
|
1404
|
+
/* Dark theme overrides for exec events */
|
|
1405
|
+
[data-theme="dark"] .exec-event-icon.tool{background:rgba(59,130,246,.15);color:#93c5fd}
|
|
1406
|
+
[data-theme="dark"] .exec-event-icon.code-run{background:rgba(217,119,6,.15);color:#fcd34d}
|
|
1407
|
+
[data-theme="dark"] .exec-event-icon.code-ok{background:rgba(22,163,74,.15);color:#86efac}
|
|
1408
|
+
[data-theme="dark"] .exec-event-icon.code-fail{background:rgba(220,38,38,.15);color:#fca5a5}
|
|
1409
|
+
[data-theme="dark"] .exec-event-icon.code-timeout{background:rgba(219,39,119,.15);color:#f9a8d4}
|
|
1410
|
+
[data-theme="dark"] .exec-event-icon.skill{background:rgba(124,58,237,.15);color:#c4b5fd}
|
|
1411
|
+
[data-theme="dark"] .exec-events-panel{background:var(--bg2);border-color:var(--border)}
|
|
1412
|
+
[data-theme="dark"] .exec-result-modal{background:var(--bg2);border-color:var(--border)}
|
|
1413
|
+
[data-theme="dark"] .exec-result-modal-body pre{background:#0a0c10;color:#cdd6f4}
|
|
1414
|
+
[data-theme="dark"] .exec-result-info-item{background:var(--bg3)}
|
|
1415
|
+
|
|
1284
1416
|
</style>
|
|
1285
1417
|
</head>
|
|
1286
1418
|
<body>
|
|
@@ -1378,6 +1510,9 @@ input,textarea,select{font:inherit}
|
|
|
1378
1510
|
<button class="header-btn" id="clearChatBtn" onclick="clearCurrentChat()" title="清空当前对话">
|
|
1379
1511
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
1380
1512
|
</button>
|
|
1513
|
+
<button class="header-btn" id="themeToggle" title="切换主题">
|
|
1514
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
1515
|
+
</button>
|
|
1381
1516
|
</div>
|
|
1382
1517
|
</div>
|
|
1383
1518
|
|
|
@@ -1500,6 +1635,49 @@ input,textarea,select{font:inherit}
|
|
|
1500
1635
|
<div id="groupModalContainer"></div>
|
|
1501
1636
|
|
|
1502
1637
|
<script>
|
|
1638
|
+
// ── Theme Management ──
|
|
1639
|
+
function initTheme() {
|
|
1640
|
+
const saved = localStorage.getItem('myagent-theme') || 'claude';
|
|
1641
|
+
document.documentElement.setAttribute('data-theme', saved);
|
|
1642
|
+
updateThemeIcon(saved);
|
|
1643
|
+
}
|
|
1644
|
+
function toggleTheme() {
|
|
1645
|
+
const current = document.documentElement.getAttribute('data-theme') || 'claude';
|
|
1646
|
+
const next = current === 'claude' ? 'dark' : 'claude';
|
|
1647
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
1648
|
+
localStorage.setItem('myagent-theme', next);
|
|
1649
|
+
updateThemeIcon(next);
|
|
1650
|
+
}
|
|
1651
|
+
function updateThemeIcon(theme) {
|
|
1652
|
+
const btn = document.getElementById('themeToggle');
|
|
1653
|
+
if (!btn) return;
|
|
1654
|
+
if (theme === 'dark') {
|
|
1655
|
+
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
1656
|
+
btn.title = '切换到 Claude 风格';
|
|
1657
|
+
} else {
|
|
1658
|
+
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
1659
|
+
btn.title = '切换到夜间模式';
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
// Initialize theme on load
|
|
1663
|
+
initTheme();
|
|
1664
|
+
// Bind theme toggle
|
|
1665
|
+
document.getElementById('themeToggle')?.addEventListener('click', toggleTheme);
|
|
1666
|
+
|
|
1667
|
+
// ── Sidebar Collapse ──
|
|
1668
|
+
function toggleSidebar() {
|
|
1669
|
+
const sidebar = document.querySelector('.sidebar');
|
|
1670
|
+
if (!sidebar) return;
|
|
1671
|
+
sidebar.classList.toggle('hidden');
|
|
1672
|
+
localStorage.setItem('myagent-sidebar-collapsed', sidebar.classList.contains('hidden'));
|
|
1673
|
+
}
|
|
1674
|
+
// Restore sidebar state
|
|
1675
|
+
(function() {
|
|
1676
|
+
if (localStorage.getItem('myagent-sidebar-collapsed') === 'true') {
|
|
1677
|
+
document.querySelector('.sidebar')?.classList.add('hidden');
|
|
1678
|
+
}
|
|
1679
|
+
})();
|
|
1680
|
+
|
|
1503
1681
|
// ── State ──
|
|
1504
1682
|
const state = {
|
|
1505
1683
|
sessions: [], // [{id, name, messages: int, last: string}]
|
|
@@ -1616,6 +1794,8 @@ const StatePersistence = {
|
|
|
1616
1794
|
|
|
1617
1795
|
// ── Init ──
|
|
1618
1796
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1797
|
+
// Initialize theme
|
|
1798
|
+
initTheme();
|
|
1619
1799
|
// Restore persisted UI state
|
|
1620
1800
|
StatePersistence.restoreUIState();
|
|
1621
1801
|
|
|
@@ -2717,7 +2897,8 @@ async function saveAgentModal(editPath, parentPath) {
|
|
|
2717
2897
|
|
|
2718
2898
|
async function deleteAgent(agentPath) {
|
|
2719
2899
|
if (agentPath === 'default') { toast('不能删除默认 Agent', 'error'); return; }
|
|
2720
|
-
|
|
2900
|
+
var msg = '确定删除 Agent "' + agentPath + '" 吗?\n\n删除后将同时清理:\n 📁 工作目录及所有文件\n 💬 该 Agent 的所有会话历史\n 🧠 相关记忆数据\n 🔗 所有子 Agent\n\n⚠️ 此操作不可撤销!';
|
|
2901
|
+
if (!confirm(msg)) return;
|
|
2721
2902
|
try {
|
|
2722
2903
|
await api('/api/agents/' + encodeURIComponent(agentPath), { method: 'DELETE' });
|
|
2723
2904
|
toast('Agent 已删除', 'success');
|
|
@@ -3014,11 +3195,14 @@ function renderMessages() {
|
|
|
3014
3195
|
</div>` : '';
|
|
3015
3196
|
const ttsIndicator = ttsManager && ttsManager.isPlaying && ttsManager.currentMsgIndex === i ?
|
|
3016
3197
|
' <span class="tts-playing-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg></span>' : '';
|
|
3198
|
+
const execEventsHtml = (!isUser && msg.exec_events && msg.exec_events.length > 0)
|
|
3199
|
+
? renderExecEvents(msg.exec_events, i) : '';
|
|
3017
3200
|
html += `
|
|
3018
3201
|
<div class="message-row ${msg.role}">
|
|
3019
3202
|
<div class="message-avatar">${avatar}</div>
|
|
3020
|
-
<div>
|
|
3203
|
+
<div style="flex:1;min-width:0">
|
|
3021
3204
|
<div class="message-bubble">${content}${ttsIndicator}</div>
|
|
3205
|
+
${execEventsHtml}
|
|
3022
3206
|
${msg.time ? `<div class="message-time">${formatTime(msg.time)}</div>` : ''}
|
|
3023
3207
|
${actionBtns}
|
|
3024
3208
|
</div>
|
|
@@ -3032,6 +3216,242 @@ function renderMessages() {
|
|
|
3032
3216
|
scrollToBottom();
|
|
3033
3217
|
}
|
|
3034
3218
|
|
|
3219
|
+
// ── Execution Events Rendering ──
|
|
3220
|
+
function renderExecEvents(events, msgIndex) {
|
|
3221
|
+
// 过滤出有意义的事件(仅展示调用/结果对,跳过中间状态)
|
|
3222
|
+
const displayEvents = events.filter(e =>
|
|
3223
|
+
['tool_call','tool_result','skill_call','skill_result','code_exec','code_result'].includes(e.type)
|
|
3224
|
+
);
|
|
3225
|
+
if (displayEvents.length === 0) return '';
|
|
3226
|
+
|
|
3227
|
+
// 统计执行步骤数
|
|
3228
|
+
const stepCount = displayEvents.filter(e => e.type === 'code_exec' || e.type === 'code_result' || e.type === 'tool_call' || e.type === 'skill_call').length;
|
|
3229
|
+
const successCount = displayEvents.filter(e => (e.type === 'code_result' || e.type === 'tool_result' || e.type === 'skill_result') && e.success !== false).length;
|
|
3230
|
+
const failCount = displayEvents.filter(e => (e.type === 'code_result' || e.type === 'tool_result' || e.type === 'skill_result') && e.success === false).length;
|
|
3231
|
+
|
|
3232
|
+
let itemsHtml = '';
|
|
3233
|
+
for (const evt of displayEvents) {
|
|
3234
|
+
const iconClass = getEventIconClass(evt);
|
|
3235
|
+
const iconEmoji = getEventIconEmoji(evt);
|
|
3236
|
+
const metaHtml = getEventMetaHtml(evt);
|
|
3237
|
+
|
|
3238
|
+
let bodyExtra = '';
|
|
3239
|
+
// 代码预览
|
|
3240
|
+
if (evt.code_preview && (evt.type === 'code_exec' || evt.type === 'code_result')) {
|
|
3241
|
+
bodyExtra += `<div class="exec-event-code" onclick="showExecResultModal(${msgIndex}, ${evt.id})" title="点击查看完整结果">${escapeHtml(evt.code_preview)}</div>`;
|
|
3242
|
+
}
|
|
3243
|
+
// 结果摘要
|
|
3244
|
+
if (evt.summary && (evt.type === 'tool_result' || evt.type === 'skill_result')) {
|
|
3245
|
+
bodyExtra += `<div class="exec-event-summary">${escapeHtml(evt.summary)}</div>`;
|
|
3246
|
+
}
|
|
3247
|
+
// 查看详情按钮(仅结果类型事件)
|
|
3248
|
+
if ((evt.type === 'code_result') && (evt.stdout || evt.stderr || evt.error)) {
|
|
3249
|
+
bodyExtra += `<button class="exec-event-result-btn" onclick="showExecResultModal(${msgIndex}, ${evt.id})"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg> 查看详情</button>`;
|
|
3250
|
+
}
|
|
3251
|
+
if ((evt.type === 'tool_result' || evt.type === 'skill_result') && evt.result) {
|
|
3252
|
+
bodyExtra += `<button class="exec-event-result-btn" onclick="showToolResultModal(${msgIndex}, ${evt.id})"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg> 查看详情</button>`;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
itemsHtml += `
|
|
3256
|
+
<div class="exec-event-item">
|
|
3257
|
+
<div class="exec-event-icon ${iconClass}">${iconEmoji}</div>
|
|
3258
|
+
<div class="exec-event-body">
|
|
3259
|
+
<div class="exec-event-title">${escapeHtml(evt.title || '')}</div>
|
|
3260
|
+
${metaHtml ? `<div class="exec-event-meta">${metaHtml}</div>` : ''}
|
|
3261
|
+
${bodyExtra}
|
|
3262
|
+
</div>
|
|
3263
|
+
</div>`;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
return `
|
|
3267
|
+
<div class="exec-events-panel">
|
|
3268
|
+
<div class="exec-events-header" onclick="toggleExecEventsPanel(this)">
|
|
3269
|
+
<span class="events-chevron">▶</span>
|
|
3270
|
+
<span>⚡ 执行过程</span>
|
|
3271
|
+
<span class="events-count">${stepCount}</span>
|
|
3272
|
+
${successCount > 0 ? `<span style="color:var(--ok);font-weight:400;font-size:11px">${successCount} 成功</span>` : ''}
|
|
3273
|
+
${failCount > 0 ? `<span style="color:var(--danger);font-weight:400;font-size:11px">${failCount} 失败</span>` : ''}
|
|
3274
|
+
</div>
|
|
3275
|
+
<div class="exec-events-body">
|
|
3276
|
+
${itemsHtml}
|
|
3277
|
+
</div>
|
|
3278
|
+
</div>`;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
function getEventIconClass(evt) {
|
|
3282
|
+
if (evt.type === 'tool_call') return 'tool';
|
|
3283
|
+
if (evt.type === 'tool_result') return evt.success === false ? 'code-fail' : 'tool';
|
|
3284
|
+
if (evt.type === 'skill_call') return 'skill';
|
|
3285
|
+
if (evt.type === 'skill_result') return evt.success === false ? 'code-fail' : 'skill';
|
|
3286
|
+
if (evt.type === 'code_exec') return 'code-run';
|
|
3287
|
+
if (evt.type === 'code_result') {
|
|
3288
|
+
if (evt.timed_out) return 'code-timeout';
|
|
3289
|
+
return evt.success ? 'code-ok' : 'code-fail';
|
|
3290
|
+
}
|
|
3291
|
+
return 'tool';
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
function getEventIconEmoji(evt) {
|
|
3295
|
+
if (evt.type === 'tool_call' || evt.type === 'tool_result') return '🔧';
|
|
3296
|
+
if (evt.type === 'skill_call' || evt.type === 'skill_result') return '🛠';
|
|
3297
|
+
if (evt.type === 'code_exec') return '▶';
|
|
3298
|
+
if (evt.type === 'code_result') {
|
|
3299
|
+
if (evt.timed_out) return '⏰';
|
|
3300
|
+
return evt.success ? '✅' : '❌';
|
|
3301
|
+
}
|
|
3302
|
+
return '📌';
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
function getEventMetaHtml(evt) {
|
|
3306
|
+
const tags = [];
|
|
3307
|
+
if (evt.language) tags.push(`<span class="meta-tag">${escapeHtml(evt.language)}</span>`);
|
|
3308
|
+
if (evt.tool_name || evt.skill_name) tags.push(`<span class="meta-tag">${escapeHtml(evt.tool_name || evt.skill_name)}</span>`);
|
|
3309
|
+
if (evt.execution_time !== undefined) tags.push(`<span class="meta-tag">${evt.execution_time}s</span>`);
|
|
3310
|
+
if (evt.exit_code !== undefined) tags.push(`<span class="meta-tag">exit: ${evt.exit_code}</span>`);
|
|
3311
|
+
if (evt.timed_out) tags.push(`<span class="meta-tag" style="color:var(--danger)">超时</span>`);
|
|
3312
|
+
return tags.join('');
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
function toggleExecEventsPanel(header) {
|
|
3316
|
+
header.classList.toggle('expanded');
|
|
3317
|
+
const body = header.nextElementSibling;
|
|
3318
|
+
body.classList.toggle('expanded');
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
// ── Execution Result Modal ──
|
|
3322
|
+
function showExecResultModal(msgIndex, eventId) {
|
|
3323
|
+
const msg = state.messages[msgIndex];
|
|
3324
|
+
if (!msg || !msg.exec_events) return;
|
|
3325
|
+
const evt = msg.exec_events.find(e => e.id === eventId);
|
|
3326
|
+
if (!evt) return;
|
|
3327
|
+
|
|
3328
|
+
const lang = evt.language || 'unknown';
|
|
3329
|
+
const isSuccess = evt.success && !evt.timed_out;
|
|
3330
|
+
const statusIcon = evt.timed_out ? '⏰' : isSuccess ? '✅' : '❌';
|
|
3331
|
+
const statusText = evt.timed_out ? '执行超时' : isSuccess ? '执行成功' : '执行失败';
|
|
3332
|
+
|
|
3333
|
+
const hasStdout = evt.stdout && evt.stdout.trim();
|
|
3334
|
+
const hasStderr = evt.stderr && evt.stderr.trim();
|
|
3335
|
+
const hasError = evt.error && evt.error.trim();
|
|
3336
|
+
const hasCode = evt.code && evt.code.trim();
|
|
3337
|
+
|
|
3338
|
+
// Build info grid
|
|
3339
|
+
let infoHtml = `
|
|
3340
|
+
<div class="exec-result-info">
|
|
3341
|
+
<div class="exec-result-info-item"><div class="label">语言</div><div class="value">${escapeHtml(lang)}</div></div>
|
|
3342
|
+
<div class="exec-result-info-item"><div class="label">状态</div><div class="value">${statusIcon} ${statusText}</div></div>
|
|
3343
|
+
${evt.execution_time !== undefined ? `<div class="exec-result-info-item"><div class="label">耗时</div><div class="value">${evt.execution_time}s</div></div>` : ''}
|
|
3344
|
+
${evt.exit_code !== undefined ? `<div class="exec-result-info-item"><div class="label">退出码</div><div class="value">${evt.exit_code}</div></div>` : ''}
|
|
3345
|
+
</div>`;
|
|
3346
|
+
|
|
3347
|
+
// Build tabs
|
|
3348
|
+
const tabs = [];
|
|
3349
|
+
const sections = [];
|
|
3350
|
+
if (hasCode) {
|
|
3351
|
+
tabs.push({id:'code', label:'源代码'});
|
|
3352
|
+
sections.push({id:'code', content: escapeHtml(evt.code), active: !hasStdout});
|
|
3353
|
+
}
|
|
3354
|
+
if (hasStdout) {
|
|
3355
|
+
tabs.push({id:'stdout', label:'标准输出'});
|
|
3356
|
+
sections.push({id:'stdout', content: escapeHtml(evt.stdout), active: true});
|
|
3357
|
+
}
|
|
3358
|
+
if (hasStderr) {
|
|
3359
|
+
tabs.push({id:'stderr', label:'标准错误'});
|
|
3360
|
+
sections.push({id:'stderr', content: escapeHtml(evt.stderr)});
|
|
3361
|
+
}
|
|
3362
|
+
if (hasError) {
|
|
3363
|
+
tabs.push({id:'error', label:'错误信息'});
|
|
3364
|
+
sections.push({id:'error', content: escapeHtml(evt.error)});
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
if (tabs.length === 0) {
|
|
3368
|
+
infoHtml += '<div class="exec-result-empty">无详细输出</div>';
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
const tabsHtml = tabs.map((t, idx) =>
|
|
3372
|
+
`<button class="exec-result-tab ${t.active ? 'active' : ''}" onclick="switchExecResultTab(this, '${t.id}')">${t.label}</button>`
|
|
3373
|
+
).join('');
|
|
3374
|
+
|
|
3375
|
+
const sectionsHtml = sections.map(s =>
|
|
3376
|
+
`<div class="exec-result-section ${s.active ? 'active' : ''}" id="exec-section-${s.id}"><pre>${s.content}</pre></div>`
|
|
3377
|
+
).join('');
|
|
3378
|
+
|
|
3379
|
+
const modalHtml = `
|
|
3380
|
+
<div class="exec-result-modal-overlay" onclick="closeExecResultModal(event)">
|
|
3381
|
+
<div class="exec-result-modal" onclick="event.stopPropagation()">
|
|
3382
|
+
<div class="exec-result-modal-header">
|
|
3383
|
+
<h3><span class="status-icon">${statusIcon}</span> ${escapeHtml(evt.title || '执行结果')} <span style="font-weight:400;font-size:12px;color:var(--text3)">${escapeHtml(lang)}</span></h3>
|
|
3384
|
+
<button class="exec-result-modal-close" onclick="closeExecResultModal()">✕</button>
|
|
3385
|
+
</div>
|
|
3386
|
+
<div class="exec-result-modal-body">
|
|
3387
|
+
${infoHtml}
|
|
3388
|
+
${tabs.length > 0 ? `<div class="exec-result-modal-tabs">${tabsHtml}</div>` : ''}
|
|
3389
|
+
${sectionsHtml}
|
|
3390
|
+
</div>
|
|
3391
|
+
</div>
|
|
3392
|
+
</div>`;
|
|
3393
|
+
|
|
3394
|
+
// Remove existing modal
|
|
3395
|
+
const existing = document.getElementById('execResultModalContainer');
|
|
3396
|
+
if (existing) existing.remove();
|
|
3397
|
+
// Add modal
|
|
3398
|
+
const container = document.createElement('div');
|
|
3399
|
+
container.id = 'execResultModalContainer';
|
|
3400
|
+
container.innerHTML = modalHtml;
|
|
3401
|
+
document.body.appendChild(container);
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function showToolResultModal(msgIndex, eventId) {
|
|
3405
|
+
const msg = state.messages[msgIndex];
|
|
3406
|
+
if (!msg || !msg.exec_events) return;
|
|
3407
|
+
const evt = msg.exec_events.find(e => e.id === eventId);
|
|
3408
|
+
if (!evt) return;
|
|
3409
|
+
|
|
3410
|
+
const name = evt.tool_name || evt.skill_name || 'unknown';
|
|
3411
|
+
const isSuccess = evt.success !== false;
|
|
3412
|
+
const statusIcon = isSuccess ? '✅' : '❌';
|
|
3413
|
+
|
|
3414
|
+
const resultText = evt.result
|
|
3415
|
+
? escapeHtml(typeof evt.result === 'string' ? evt.result : JSON.stringify(evt.result, null, 2))
|
|
3416
|
+
: (evt.summary ? escapeHtml(evt.summary) : '无结果');
|
|
3417
|
+
|
|
3418
|
+
const modalHtml = `
|
|
3419
|
+
<div class="exec-result-modal-overlay" onclick="closeExecResultModal(event)">
|
|
3420
|
+
<div class="exec-result-modal" onclick="event.stopPropagation()">
|
|
3421
|
+
<div class="exec-result-modal-header">
|
|
3422
|
+
<h3><span class="status-icon">${statusIcon}</span> ${escapeHtml(name)}</h3>
|
|
3423
|
+
<button class="exec-result-modal-close" onclick="closeExecResultModal()">✕</button>
|
|
3424
|
+
</div>
|
|
3425
|
+
<div class="exec-result-modal-body">
|
|
3426
|
+
<pre>${resultText}</pre>
|
|
3427
|
+
</div>
|
|
3428
|
+
</div>
|
|
3429
|
+
</div>`;
|
|
3430
|
+
|
|
3431
|
+
const existing = document.getElementById('execResultModalContainer');
|
|
3432
|
+
if (existing) existing.remove();
|
|
3433
|
+
const container = document.createElement('div');
|
|
3434
|
+
container.id = 'execResultModalContainer';
|
|
3435
|
+
container.innerHTML = modalHtml;
|
|
3436
|
+
document.body.appendChild(container);
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
function switchExecResultTab(btn, tabId) {
|
|
3440
|
+
// Deactivate all tabs
|
|
3441
|
+
btn.parentElement.querySelectorAll('.exec-result-tab').forEach(t => t.classList.remove('active'));
|
|
3442
|
+
btn.classList.add('active');
|
|
3443
|
+
// Show target section, hide others
|
|
3444
|
+
document.querySelectorAll('.exec-result-section').forEach(s => s.classList.remove('active'));
|
|
3445
|
+
const target = document.getElementById('exec-section-' + tabId);
|
|
3446
|
+
if (target) target.classList.add('active');
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
function closeExecResultModal(e) {
|
|
3450
|
+
if (e && e.target !== e.currentTarget) return;
|
|
3451
|
+
const container = document.getElementById('execResultModalContainer');
|
|
3452
|
+
if (container) container.remove();
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3035
3455
|
function renderMarkdown(text) {
|
|
3036
3456
|
if (!text) return '';
|
|
3037
3457
|
// Code blocks with copy button
|
|
@@ -3154,6 +3574,7 @@ async function sendMessage() {
|
|
|
3154
3574
|
role: 'assistant',
|
|
3155
3575
|
content: data.response || '(无回复)',
|
|
3156
3576
|
time: new Date().toISOString(),
|
|
3577
|
+
exec_events: data.exec_events || [],
|
|
3157
3578
|
});
|
|
3158
3579
|
|
|
3159
3580
|
// Update session in list
|
|
@@ -3173,8 +3594,8 @@ async function sendMessage() {
|
|
|
3173
3594
|
state.agentSessions[state.activeAgent] = [...state.sessions];
|
|
3174
3595
|
renderSessions();
|
|
3175
3596
|
|
|
3176
|
-
// Auto-play TTS if enabled
|
|
3177
|
-
if (ttsManager.enabled && data.response) {
|
|
3597
|
+
// Auto-play TTS if enabled (skip command execution results)
|
|
3598
|
+
if (ttsManager.enabled && data.response && !data.response.match(/^\s*[✅❌⏰]\s*\[执行结果\]/m)) {
|
|
3178
3599
|
const idx = state.messages.length - 1;
|
|
3179
3600
|
ttsManager.speak(idx);
|
|
3180
3601
|
}
|
|
@@ -3371,11 +3792,6 @@ function autoResize(el) {
|
|
|
3371
3792
|
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
|
|
3372
3793
|
}
|
|
3373
3794
|
|
|
3374
|
-
// ── Sidebar Toggle ──
|
|
3375
|
-
function toggleSidebar() {
|
|
3376
|
-
document.getElementById('sidebar').classList.toggle('hidden');
|
|
3377
|
-
}
|
|
3378
|
-
|
|
3379
3795
|
// ── Toast ──
|
|
3380
3796
|
function toast(message, type = 'info') {
|
|
3381
3797
|
const container = document.getElementById('toastContainer');
|
|
@@ -4870,6 +5286,10 @@ const ttsManager = {
|
|
|
4870
5286
|
const msg = state.messages[msgIndex];
|
|
4871
5287
|
if (!msg || msg.role !== 'user' && !msg.content) return;
|
|
4872
5288
|
|
|
5289
|
+
// 跳过命令执行结果(以 [执行结果] 开头的消息)
|
|
5290
|
+
var rawText = msg.content.replace(/<[^>]+>/g, '');
|
|
5291
|
+
if (/^\s*[✅❌⏰]\s*\[执行结果\]/m.test(rawText)) return;
|
|
5292
|
+
|
|
4873
5293
|
// 去除 HTML 标签(msg.content 是 HTML 格式,SVG 图标等会被朗读)
|
|
4874
5294
|
let text = msg.content
|
|
4875
5295
|
.replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '') // 移除 SVG 图标
|
package/web/ui/index.html
CHANGED
|
@@ -6,7 +6,34 @@
|
|
|
6
6
|
<title>MyAgent 管理后台</title>
|
|
7
7
|
<style>
|
|
8
8
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
9
|
-
:root{
|
|
9
|
+
:root{
|
|
10
|
+
--bg:#0f1117;--bg2:#1a1d27;--bg3:#232733;--bg4:#2e3347;--bg5:#3a3f57;
|
|
11
|
+
--surface:#1a1d27;--surface2:#232733;
|
|
12
|
+
--text:#e1e4ed;--text2:#8b92a5;--text3:#5c6378;
|
|
13
|
+
--accent:#6366f1;--accent2:#818cf8;--accent-light:#2d2f5e;--accent-dark:#9da3f8;
|
|
14
|
+
--primary:#6366f1;--primary-h:#818cf8;
|
|
15
|
+
--ok:#22c55e;--success:#22c55e;--warn:#f59e0b;--danger:#ef4444;--info:#3b82f6;
|
|
16
|
+
--border:#2e3347;--border-light:#232733;
|
|
17
|
+
--radius:8px;
|
|
18
|
+
}
|
|
19
|
+
[data-theme="claude"]{
|
|
20
|
+
--bg:#f5f0e8;--bg2:#ebe5d9;--bg3:#e2dace;--bg4:#d6cfc3;--bg5:#c8bfb0;
|
|
21
|
+
--surface:#ebe5d9;--surface2:#e2dace;
|
|
22
|
+
--text:#2c2c2c;--text2:#5a5a5a;--text3:#8a8a8a;
|
|
23
|
+
--accent:#c96442;--accent2:#d97a5a;--accent-light:#fceee8;--accent-dark:#a0502e;
|
|
24
|
+
--primary:#c96442;--primary-h:#d97a5a;
|
|
25
|
+
--ok:#4a9c6d;--success:#4a9c6d;--warn:#c4862b;--danger:#c94444;--info:#4a7fc9;
|
|
26
|
+
--border:#d6cfc3;--border-light:#e2dace;
|
|
27
|
+
}
|
|
28
|
+
[data-theme="dark"]{
|
|
29
|
+
--bg:#0f1117;--bg2:#1a1d27;--bg3:#232733;--bg4:#2e3347;--bg5:#3a3f57;
|
|
30
|
+
--surface:#1a1d27;--surface2:#232733;
|
|
31
|
+
--text:#e1e4ed;--text2:#8b92a5;--text3:#5c6378;
|
|
32
|
+
--accent:#6366f1;--accent2:#818cf8;--accent-light:#2d2f5e;--accent-dark:#9da3f8;
|
|
33
|
+
--primary:#6366f1;--primary-h:#818cf8;
|
|
34
|
+
--ok:#22c55e;--success:#22c55e;--warn:#f59e0b;--danger:#ef4444;--info:#3b82f6;
|
|
35
|
+
--border:#2e3347;--border-light:#232733;
|
|
36
|
+
}
|
|
10
37
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex}
|
|
11
38
|
.sidebar{width:220px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
|
12
39
|
.sidebar .logo{padding:20px;font-size:18px;font-weight:700;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}
|
|
@@ -106,29 +133,48 @@ tr:hover{background:var(--surface2)}
|
|
|
106
133
|
.status-msg.loading{color:var(--info)}
|
|
107
134
|
.status-msg.success{color:var(--success);background:rgba(34,197,94,.1)}
|
|
108
135
|
.status-msg.error{color:var(--danger);background:rgba(239,68,68,.1)}
|
|
136
|
+
/* ── Theme & Sidebar ── */
|
|
137
|
+
.sidebar{transition:width .3s ease;overflow:hidden;position:relative}
|
|
138
|
+
.sidebar.collapsed{width:52px}
|
|
139
|
+
.sidebar.collapsed .nav-item{justify-content:center;padding:10px 0}
|
|
140
|
+
.sidebar.collapsed .nav-item .icon-text{display:none}
|
|
141
|
+
.sidebar.collapsed .logo .logo-text{display:none}
|
|
142
|
+
.sidebar.collapsed .logo{justify-content:center;padding:20px 8px}
|
|
143
|
+
.sidebar.collapsed .sidebar-footer-text{display:none}
|
|
144
|
+
.sidebar-toggle{position:absolute;top:50%;right:-14px;transform:translateY(-50%);width:28px;height:28px;border-radius:50%;background:var(--surface);border:1px solid var(--border);display:grid;place-items:center;cursor:pointer;z-index:25;color:var(--text2);font-size:14px;transition:all .15s}
|
|
145
|
+
.sidebar-toggle:hover{background:var(--primary);color:#fff;border-color:var(--primary)}
|
|
146
|
+
.header-btn{width:36px;height:36px;border-radius:6px;display:grid;place-items:center;color:var(--text2);cursor:pointer;border:none;background:none;transition:all .15s}
|
|
147
|
+
.header-btn:hover{background:var(--surface2);color:var(--text)}
|
|
148
|
+
[data-theme="claude"] .log-viewer,[data-theme="claude"] .config-preview{background:#faf6ef;color:#5a5a5a}
|
|
149
|
+
[data-theme="claude"] .log-viewer .INFO,[data-theme="claude"] .config-preview .key{color:#4a7fc9}
|
|
150
|
+
[data-theme="claude"] .badge-green{background:#4a9c6d22}
|
|
151
|
+
[data-theme="claude"] .badge-red{background:#c9444422}
|
|
152
|
+
[data-theme="claude"] .badge-yellow{background:#c4862b22}
|
|
153
|
+
[data-theme="claude"] .badge-blue{background:#4a7fc922}
|
|
109
154
|
</style>
|
|
110
155
|
</head>
|
|
111
156
|
<body>
|
|
112
|
-
<div class="sidebar">
|
|
113
|
-
<div class="
|
|
157
|
+
<div class="sidebar" id="adminSidebar">
|
|
158
|
+
<div class="sidebar-toggle" onclick="toggleSidebar()" id="sidebarToggle">◀</div>
|
|
159
|
+
<div class="logo"><span>🤖</span> <span class="logo-text">MyAgent</span></div>
|
|
114
160
|
<div class="nav">
|
|
115
|
-
<div class="nav-item active" onclick="showPage('dashboard')"><span class="icon">📊</span>仪表盘</div>
|
|
116
|
-
<div class="nav-item" onclick="showPage('agents')"><span class="icon">🧠</span>Agent 管理</div>
|
|
117
|
-
<div class="nav-item" onclick="showPage('platforms')"><span class="icon">💬</span>聊天平台</div>
|
|
118
|
-
<div class="nav-item" onclick="showPage('organization')"><span class="icon">🏢</span>组织管理</div>
|
|
119
|
-
<div class="nav-item" onclick="showPage('departments')"><span class="icon">🏛</span>部门管理</div>
|
|
120
|
-
<div class="nav-item" onclick="showPage('sessions')"><span class="icon">📂</span>会话管理</div>
|
|
121
|
-
<div class="nav-item" onclick="showPage('memory')"><span class="icon">🧠</span>记忆管理</div>
|
|
122
|
-
<div class="nav-item" onclick="showPage('permissions')"><span class="icon">🔑</span>权限管理</div>
|
|
123
|
-
<div class="nav-item" onclick="showPage('llm')"><span class="icon">⚙️</span>大模型设置</div>
|
|
124
|
-
<div class="nav-item" onclick="showPage('system')"><span class="icon">📦</span>系统配置</div>
|
|
125
|
-
<div class="nav-item" onclick="showPage('executor')"><span class="icon">🔧</span>执行引擎</div>
|
|
126
|
-
<div class="nav-item" onclick="showPage('skills')"><span class="icon">🛠</span>技能管理</div>
|
|
127
|
-
<div class="nav-item" onclick="showPage('files')"><span class="icon">📁</span>工作目录</div>
|
|
128
|
-
<div class="nav-item" onclick="showPage('logs')"><span class="icon">📋</span>查看日志</div>
|
|
129
|
-
<div class="nav-item" onclick="showPage('tasks')"><span class="icon">📌</span>任务记录</div>
|
|
161
|
+
<div class="nav-item active" onclick="showPage('dashboard')"><span class="icon">📊</span><span class="icon-text">仪表盘</span></div>
|
|
162
|
+
<div class="nav-item" onclick="showPage('agents')"><span class="icon">🧠</span><span class="icon-text">Agent 管理</span></div>
|
|
163
|
+
<div class="nav-item" onclick="showPage('platforms')"><span class="icon">💬</span><span class="icon-text">聊天平台</span></div>
|
|
164
|
+
<div class="nav-item" onclick="showPage('organization')"><span class="icon">🏢</span><span class="icon-text">组织管理</span></div>
|
|
165
|
+
<div class="nav-item" onclick="showPage('departments')"><span class="icon">🏛</span><span class="icon-text">部门管理</span></div>
|
|
166
|
+
<div class="nav-item" onclick="showPage('sessions')"><span class="icon">📂</span><span class="icon-text">会话管理</span></div>
|
|
167
|
+
<div class="nav-item" onclick="showPage('memory')"><span class="icon">🧠</span><span class="icon-text">记忆管理</span></div>
|
|
168
|
+
<div class="nav-item" onclick="showPage('permissions')"><span class="icon">🔑</span><span class="icon-text">权限管理</span></div>
|
|
169
|
+
<div class="nav-item" onclick="showPage('llm')"><span class="icon">⚙️</span><span class="icon-text">大模型设置</span></div>
|
|
170
|
+
<div class="nav-item" onclick="showPage('system')"><span class="icon">📦</span><span class="icon-text">系统配置</span></div>
|
|
171
|
+
<div class="nav-item" onclick="showPage('executor')"><span class="icon">🔧</span><span class="icon-text">执行引擎</span></div>
|
|
172
|
+
<div class="nav-item" onclick="showPage('skills')"><span class="icon">🛠</span><span class="icon-text">技能管理</span></div>
|
|
173
|
+
<div class="nav-item" onclick="showPage('files')"><span class="icon">📁</span><span class="icon-text">工作目录</span></div>
|
|
174
|
+
<div class="nav-item" onclick="showPage('logs')"><span class="icon">📋</span><span class="icon-text">查看日志</span></div>
|
|
175
|
+
<div class="nav-item" onclick="showPage('tasks')"><span class="icon">📌</span><span class="icon-text">任务记录</span></div>
|
|
130
176
|
</div>
|
|
131
|
-
<div style="padding:12px;border-top:1px solid var(--border);font-size:12px;color:var(--text2)">
|
|
177
|
+
<div style="padding:12px;border-top:1px solid var(--border);font-size:12px;color:var(--text2)" class="sidebar-footer-text">
|
|
132
178
|
<span id="sidebarVersion">v...</span>
|
|
133
179
|
<span id="updateBadge" style="display:none;margin-left:6px;color:var(--danger);font-weight:bold;cursor:pointer" onclick="doUpdate()" title="点击更新">[有新版本]</span>
|
|
134
180
|
· <a href="#" onclick="api('/api/status').then(r=>showToast('Running: '+r.running,'success'))" style="color:var(--primary)">状态</a>
|
|
@@ -136,7 +182,7 @@ tr:hover{background:var(--surface2)}
|
|
|
136
182
|
</div>
|
|
137
183
|
</div>
|
|
138
184
|
<div class="main">
|
|
139
|
-
<div class="header"><h2 id="pageTitle">📊 仪表盘</h2><div><span class="status-dot"></span>运行中</div></div>
|
|
185
|
+
<div class="header"><div style="display:flex;align-items:center;gap:12px"><h2 id="pageTitle">📊 仪表盘</h2><button class="header-btn" id="themeToggle" title="切换主题"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></div><div style="display:flex;align-items:center;gap:8px"><span class="status-dot"></span>运行中</div></div>
|
|
140
186
|
<div class="content" id="content"></div>
|
|
141
187
|
</div>
|
|
142
188
|
<div id="modalContainer"></div>
|
|
@@ -199,6 +245,17 @@ async function doUpdate(){
|
|
|
199
245
|
}catch(e){showToast('更新失败','danger')}
|
|
200
246
|
}
|
|
201
247
|
// 页面加载时获取版本号,30秒后自动检查更新
|
|
248
|
+
// ── Theme Management ──
|
|
249
|
+
function initTheme(){const s=localStorage.getItem('myagent-theme')||'claude';document.documentElement.setAttribute('data-theme',s);updateThemeIcon(s)}
|
|
250
|
+
function toggleTheme(){const c=document.documentElement.getAttribute('data-theme')||'claude';const n=c==='claude'?'dark':'claude';document.documentElement.setAttribute('data-theme',n);localStorage.setItem('myagent-theme',n);updateThemeIcon(n)}
|
|
251
|
+
function updateThemeIcon(t){const b=document.getElementById('themeToggle');if(!b)return;if(t==='dark'){b.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';b.title='切换到 Claude 风格'}else{b.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';b.title='切换到夜间模式'}}
|
|
252
|
+
// ── Sidebar Collapse ──
|
|
253
|
+
function toggleSidebar(){const s=document.getElementById('adminSidebar');const t=document.getElementById('sidebarToggle');if(!s)return;s.classList.toggle('collapsed');const isCollapsed=s.classList.contains('collapsed');t.textContent=isCollapsed?'▶':'◀';localStorage.setItem('myagent-admin-sidebar-collapsed',isCollapsed)}
|
|
254
|
+
// Initialize
|
|
255
|
+
initTheme();
|
|
256
|
+
document.getElementById('themeToggle')?.addEventListener('click',toggleTheme);
|
|
257
|
+
if(localStorage.getItem('myagent-admin-sidebar-collapsed')==='true'){document.getElementById('adminSidebar')?.classList.add('collapsed');const t=document.getElementById('sidebarToggle');if(t)t.textContent='▶'}
|
|
258
|
+
|
|
202
259
|
loadVersion();
|
|
203
260
|
setTimeout(()=>checkUpdate(false),30000);
|
|
204
261
|
function showConfirm(title,msg,onOk){
|
|
@@ -347,6 +404,7 @@ async function openEditAgentModal(path){
|
|
|
347
404
|
<div class="tab" onclick="agentTabSwitch(this,'atKB')">知识库</div>
|
|
348
405
|
<div class="tab" onclick="agentTabSwitch(this,'atSessions')">会话历史</div>
|
|
349
406
|
<div class="tab" onclick="agentTabSwitch(this,'atSettings')">设置</div>
|
|
407
|
+
<div class="tab" onclick="agentTabSwitch(this,'atPerms')">🔑 权限</div>
|
|
350
408
|
</div>
|
|
351
409
|
|
|
352
410
|
<!-- 基本信息 -->
|
|
@@ -405,17 +463,23 @@ async function openEditAgentModal(path){
|
|
|
405
463
|
<button class="btn btn-primary" onclick="doSaveAgentSettings('${escHtml(path)}')">保存设置</button>
|
|
406
464
|
</div>
|
|
407
465
|
</div>
|
|
466
|
+
|
|
467
|
+
<!-- 权限 -->
|
|
468
|
+
<div id="atPerms" class="hidden">
|
|
469
|
+
<div id="atPermsContent"><div class="empty">加载中...</div></div>
|
|
470
|
+
</div>
|
|
408
471
|
</div></div>`;
|
|
409
472
|
}
|
|
410
473
|
|
|
411
474
|
function agentTabSwitch(el,tabId){
|
|
412
475
|
el.parentElement.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
|
|
413
476
|
el.classList.add('active');
|
|
414
|
-
const allTabs=['atBasic','atSoul','atKB','atSessions','atSettings'];
|
|
477
|
+
const allTabs=['atBasic','atSoul','atKB','atSessions','atSettings','atPerms'];
|
|
415
478
|
allTabs.forEach(id=>$(id).classList.toggle('hidden',id!==tabId));
|
|
416
479
|
// Lazy load
|
|
417
480
|
if(tabId==='atKB')loadAgentKB();
|
|
418
481
|
if(tabId==='atSessions')loadAgentSessions();
|
|
482
|
+
if(tabId==='atPerms')loadAgentPerms();
|
|
419
483
|
}
|
|
420
484
|
|
|
421
485
|
async function doSaveAgent(path){
|
|
@@ -506,8 +570,67 @@ async function viewSessionMsgs(sid){
|
|
|
506
570
|
$('sessionsContent').innerHTML=html;
|
|
507
571
|
}
|
|
508
572
|
|
|
573
|
+
async function loadAgentPerms(){
|
|
574
|
+
const path=window._currentEditAgentPath;if(!path)return;
|
|
575
|
+
const [agentPerms,globalPerms]=await Promise.all([
|
|
576
|
+
api('/api/permissions/'+encodeURIComponent(path)),
|
|
577
|
+
api('/api/permissions')
|
|
578
|
+
]);
|
|
579
|
+
const perms=globalPerms.all_permissions||[];
|
|
580
|
+
const labels=globalPerms.labels||{};
|
|
581
|
+
const defaults=globalPerms.defaults||{};
|
|
582
|
+
const ap=agentPerms.permissions||{};
|
|
583
|
+
let html='<h4 style="font-size:14px;color:var(--text2);margin-bottom:12px">Agent 权限配置</h4>';
|
|
584
|
+
html+='<p style="color:var(--text2);font-size:12px;margin-bottom:16px">为该 Agent 单独配置权限。未设置的项目将使用全局默认值。</p>';
|
|
585
|
+
html+='<div class="grid" style="grid-template-columns:repeat(3,1fr);gap:12px">';
|
|
586
|
+
for(const p of perms){
|
|
587
|
+
const label=labels[p]||p;
|
|
588
|
+
const defVal=defaults[p]!==false;
|
|
589
|
+
const curVal=ap[p]!==undefined?ap[p]:defVal;
|
|
590
|
+
const checked=curVal?'checked':'';
|
|
591
|
+
const isDefault=ap[p]===undefined?'(默认)':'';
|
|
592
|
+
html+=`<div class="form-group" style="display:flex;align-items:center;gap:10px"><label style="flex:1;font-weight:500">${label} <span style="color:var(--text3);font-size:11px">${isDefault}</span></label><input type="checkbox" id="agent_perm_${p}" ${checked} style="width:18px;height:18px;cursor:pointer"></div>`;
|
|
593
|
+
}
|
|
594
|
+
html+='</div>';
|
|
595
|
+
html+='<div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveAgentPermsFromTab()">保存权限</button>';
|
|
596
|
+
html+='<button class="btn btn-ghost" onclick="resetAgentPermsFromTab()">重置为默认</button></div>';
|
|
597
|
+
$('atPermsContent').innerHTML=html;
|
|
598
|
+
}
|
|
599
|
+
async function saveAgentPermsFromTab(){
|
|
600
|
+
const path=window._currentEditAgentPath;if(!path)return;
|
|
601
|
+
const [agentPerms,globalPerms]=await Promise.all([
|
|
602
|
+
api('/api/permissions/'+encodeURIComponent(path)),
|
|
603
|
+
api('/api/permissions')
|
|
604
|
+
]);
|
|
605
|
+
const perms=globalPerms.all_permissions||[];
|
|
606
|
+
const defaults=globalPerms.defaults||{};
|
|
607
|
+
const ap=agentPerms.permissions||{};
|
|
608
|
+
const data={};
|
|
609
|
+
for(const p of perms){
|
|
610
|
+
const el=document.getElementById('agent_perm_'+p);
|
|
611
|
+
if(!el)continue;
|
|
612
|
+
const defVal=defaults[p]!==false;
|
|
613
|
+
const curVal=el.checked;
|
|
614
|
+
// Only include if different from default
|
|
615
|
+
if(curVal!==defVal){
|
|
616
|
+
data[p]=curVal;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const r=await api('/api/permissions/'+encodeURIComponent(path),{method:'PUT',body:JSON.stringify(data)});
|
|
620
|
+
if(r.error){showToast(r.error,'danger');return}
|
|
621
|
+
showToast('Agent 权限已保存','success');
|
|
622
|
+
}
|
|
623
|
+
async function resetAgentPermsFromTab(){
|
|
624
|
+
const path=window._currentEditAgentPath;if(!path)return;
|
|
625
|
+
if(!confirm('确认将此 Agent 的权限重置为全局默认值?'))return;
|
|
626
|
+
const r=await api('/api/permissions/'+encodeURIComponent(path),{method:'DELETE'});
|
|
627
|
+
if(r.error){showToast(r.error,'danger');return}
|
|
628
|
+
showToast('已重置为默认权限','success');
|
|
629
|
+
loadAgentPerms();
|
|
630
|
+
}
|
|
631
|
+
|
|
509
632
|
function confirmDeleteAgent(path,name){
|
|
510
|
-
showConfirm('删除 Agent','确认删除 Agent "'+escHtml(name)+'"
|
|
633
|
+
showConfirm('删除 Agent','确认删除 Agent "'+escHtml(name)+'" 吗?\n\n删除后将同时清理:\n 📁 工作目录及所有文件\n 💬 该 Agent 的所有会话历史\n 🧠 相关记忆数据\n 🔗 所有子 Agent\n\n⚠️ 此操作不可撤销!',async()=>{
|
|
511
634
|
const r=await api(`/api/agents/${encodeURIComponent(path)}`,{method:'DELETE'});
|
|
512
635
|
if(r.error){showToast(r.error,'danger');return}
|
|
513
636
|
closeModal();showToast('已删除','success');renderAgents();
|