myagent-ai 1.24.3 → 1.24.6
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/README.md +5 -2
- package/core/agent_storage.py +15 -0
- package/package.json +1 -1
- package/web/api_server.py +99 -0
- package/web/ui/admin/admin-agentchat.js +207 -75
- package/web/ui/admin/admin-core.js +1 -1
- package/web/ui/chat/groupchat.js +17 -4
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install
|
|
|
130
130
|
myagent-ai update
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
该命令会自动完成:npm 升级全局包 →
|
|
133
|
+
该命令会自动完成:npm 升级全局包 → 检查新增依赖 → 自动启动。
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
@@ -207,8 +207,11 @@ myagent-ai install
|
|
|
207
207
|
# 重装依赖(删除 venv 重建)
|
|
208
208
|
myagent-ai reinstall
|
|
209
209
|
|
|
210
|
-
#
|
|
210
|
+
# 卸载(清除所有数据)
|
|
211
211
|
myagent-ai uninstall
|
|
212
|
+
|
|
213
|
+
# 卸载但保留数据(配置、记忆等)
|
|
214
|
+
myagent-ai uninstall --keep-data
|
|
212
215
|
```
|
|
213
216
|
|
|
214
217
|
> 首次运行会自动安装所有依赖,后续启动秒开。
|
package/core/agent_storage.py
CHANGED
|
@@ -393,6 +393,7 @@ class AgentStorage:
|
|
|
393
393
|
def migrate_from_json(self, agents_dir: Path, force: bool = False) -> dict:
|
|
394
394
|
"""
|
|
395
395
|
从文件系统迁移 agent 配置到数据库。
|
|
396
|
+
迁移完成后删除 config.json 防止重复迁移。
|
|
396
397
|
|
|
397
398
|
Args:
|
|
398
399
|
agents_dir: agents 数据目录路径(~/.myagent/data/agents/)
|
|
@@ -408,6 +409,13 @@ class AgentStorage:
|
|
|
408
409
|
logger.info(f"Agent 数据目录不存在,跳过迁移: {agents_dir}")
|
|
409
410
|
return result
|
|
410
411
|
|
|
412
|
+
# [v1.24.5] 检查迁移标记文件,如果已迁移过则跳过
|
|
413
|
+
_migrated_flag = agents_dir.parent / ".migrated_to_db"
|
|
414
|
+
if _migrated_flag.exists() and self.count() > 0 and not force:
|
|
415
|
+
logger.info(f"数据库已有 {self.count()} 个 agent,且迁移标记存在,跳过迁移")
|
|
416
|
+
result["skipped"] = self.count()
|
|
417
|
+
return result
|
|
418
|
+
|
|
411
419
|
if self.count() > 0 and not force:
|
|
412
420
|
logger.info(f"数据库已有 {self.count()} 个 agent,跳过迁移(使用 force 强制重新迁移)")
|
|
413
421
|
result["skipped"] = self.count()
|
|
@@ -457,6 +465,13 @@ class AgentStorage:
|
|
|
457
465
|
with self._lock:
|
|
458
466
|
conn.commit()
|
|
459
467
|
|
|
468
|
+
# [v1.24.5] 迁移完成后写入标记文件,防止卸载重装后重复迁移
|
|
469
|
+
if result["migrated"] > 0:
|
|
470
|
+
try:
|
|
471
|
+
_migrated_flag.write_text(now, encoding="utf-8")
|
|
472
|
+
except Exception as e:
|
|
473
|
+
logger.warning(f"写入迁移标记失败: {e}")
|
|
474
|
+
|
|
460
475
|
logger.info(f"Agent 配置迁移完成: {result['migrated']} 迁移, {result['skipped']} 跳过, {result['errors']} 错误")
|
|
461
476
|
return result
|
|
462
477
|
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -484,6 +484,8 @@ class ApiServer:
|
|
|
484
484
|
r.add_get("/api/agent-chat/pairs", self.handle_get_agent_chat_pairs)
|
|
485
485
|
r.add_get("/api/agent-chat/messages", self.handle_get_agent_chat_messages)
|
|
486
486
|
r.add_delete("/api/agent-chat/messages", self.handle_clear_agent_chat_messages)
|
|
487
|
+
# [v1.24.5] 所有用户-Agent私聊会话查看
|
|
488
|
+
r.add_get("/api/private-chats/sessions", self.handle_get_private_chat_sessions)
|
|
487
489
|
# ── 部门管理 ──
|
|
488
490
|
r.add_get("/api/departments", self.handle_dept_tree)
|
|
489
491
|
r.add_post("/api/departments", self.handle_create_dept)
|
|
@@ -8411,6 +8413,103 @@ window.addEventListener('beforeunload', function() {{
|
|
|
8411
8413
|
deleted = mgr.clear_agent_chats(group_id=gid)
|
|
8412
8414
|
return web.json_response({"ok": True, "deleted": deleted})
|
|
8413
8415
|
|
|
8416
|
+
# ── [v1.24.5] 所有用户-Agent 私聊会话 API ──
|
|
8417
|
+
|
|
8418
|
+
async def handle_get_private_chat_sessions(self, request):
|
|
8419
|
+
"""GET /api/private-chats/sessions - 获取所有用户与 Agent 的私聊会话列表
|
|
8420
|
+
支持 ?agent=xxx 按指定 agent 筛选
|
|
8421
|
+
"""
|
|
8422
|
+
if not self.core.memory:
|
|
8423
|
+
return web.json_response([])
|
|
8424
|
+
agent = request.query.get("agent", "")
|
|
8425
|
+
conn = self.core.memory._get_conn()
|
|
8426
|
+
_hidden = ('llm_output', 'llm_input', 'tool_result_raw', 'conversation_insight')
|
|
8427
|
+
|
|
8428
|
+
if agent:
|
|
8429
|
+
# 按 agent 路径筛选:旧格式 session_id LIKE '{agent}_%' 或新格式 metadata 包含 agent_path
|
|
8430
|
+
metadata_pattern = '%"agent_path":"' + agent + '"%'
|
|
8431
|
+
rows = conn.execute(
|
|
8432
|
+
"SELECT m.session_id, COUNT(*) as msg_count, MAX(m.created_at) as last_active "
|
|
8433
|
+
"FROM memories m "
|
|
8434
|
+
"WHERE m.category = 'session' AND m.role != '' "
|
|
8435
|
+
"AND m.key NOT IN (?,?,?,?) "
|
|
8436
|
+
"AND (m.session_id LIKE ? OR (m.session_id LIKE 'sess_%' AND m.metadata LIKE ?)) "
|
|
8437
|
+
"GROUP BY m.session_id ORDER BY last_active DESC LIMIT 200",
|
|
8438
|
+
(*_hidden, agent + "_%", metadata_pattern)
|
|
8439
|
+
).fetchall()
|
|
8440
|
+
else:
|
|
8441
|
+
# 所有非群聊会话(session_id 不以 'grp_' 开头)
|
|
8442
|
+
rows = conn.execute(
|
|
8443
|
+
"SELECT m.session_id, COUNT(*) as msg_count, MAX(m.created_at) as last_active "
|
|
8444
|
+
"FROM memories m "
|
|
8445
|
+
"WHERE m.category = 'session' AND m.role != '' "
|
|
8446
|
+
"AND m.key NOT IN (?,?,?,?) "
|
|
8447
|
+
"AND m.session_id NOT LIKE 'grp_%' "
|
|
8448
|
+
"GROUP BY m.session_id ORDER BY last_active DESC LIMIT 200",
|
|
8449
|
+
(*_hidden,)
|
|
8450
|
+
).fetchall()
|
|
8451
|
+
|
|
8452
|
+
# 获取每个 agent 的名称映射
|
|
8453
|
+
agents_db = self._agent_store.list_all() if hasattr(self, '_agent_store') else []
|
|
8454
|
+
agent_map = {}
|
|
8455
|
+
for ac in agents_db:
|
|
8456
|
+
agent_map[ac.path] = ac.name or ac.path
|
|
8457
|
+
|
|
8458
|
+
sessions = []
|
|
8459
|
+
sids = [r["session_id"] for r in rows]
|
|
8460
|
+
# 批量获取会话名称
|
|
8461
|
+
name_map = self.core.memory.list_session_names(sids) if sids else {}
|
|
8462
|
+
|
|
8463
|
+
for r in rows:
|
|
8464
|
+
sid = r["session_id"]
|
|
8465
|
+
# 推断 agent 路径
|
|
8466
|
+
agent_path = ''
|
|
8467
|
+
agent_name = ''
|
|
8468
|
+
if sid.startswith('grp_'):
|
|
8469
|
+
continue
|
|
8470
|
+
# 从新格式 metadata 中提取 agent_path
|
|
8471
|
+
meta_row = conn.execute(
|
|
8472
|
+
"SELECT metadata FROM memories WHERE session_id = ? AND metadata LIKE '%agent_path%' LIMIT 1",
|
|
8473
|
+
(sid,)
|
|
8474
|
+
).fetchone()
|
|
8475
|
+
if meta_row and meta_row["metadata"]:
|
|
8476
|
+
try:
|
|
8477
|
+
meta = json.loads(meta_row["metadata"])
|
|
8478
|
+
agent_path = meta.get("agent_path", "")
|
|
8479
|
+
except:
|
|
8480
|
+
pass
|
|
8481
|
+
# 旧格式 fallback: 从 session_id 前缀提取
|
|
8482
|
+
if not agent_path:
|
|
8483
|
+
if '_web_' in sid:
|
|
8484
|
+
agent_path = sid.split('_web_')[0]
|
|
8485
|
+
elif '_cli_' in sid:
|
|
8486
|
+
agent_path = sid.split('_cli_')[0]
|
|
8487
|
+
else:
|
|
8488
|
+
agent_path = sid.split('_')[0] if '_' in sid else 'default'
|
|
8489
|
+
|
|
8490
|
+
agent_name = agent_map.get(agent_path, agent_path)
|
|
8491
|
+
# 获取最后一条用户消息作为预览
|
|
8492
|
+
preview = ''
|
|
8493
|
+
preview_row = conn.execute(
|
|
8494
|
+
"SELECT content FROM memories WHERE session_id = ? AND role = 'user' AND category = 'session' "
|
|
8495
|
+
"ORDER BY created_at DESC LIMIT 1",
|
|
8496
|
+
(sid,)
|
|
8497
|
+
).fetchone()
|
|
8498
|
+
if preview_row:
|
|
8499
|
+
preview = (preview_row["content"] or "")[:100]
|
|
8500
|
+
|
|
8501
|
+
sessions.append({
|
|
8502
|
+
"id": sid,
|
|
8503
|
+
"agent_path": agent_path,
|
|
8504
|
+
"agent_name": agent_name,
|
|
8505
|
+
"messages": r["msg_count"],
|
|
8506
|
+
"last": r["last_active"],
|
|
8507
|
+
"display_name": name_map.get(sid, ""),
|
|
8508
|
+
"preview": preview,
|
|
8509
|
+
})
|
|
8510
|
+
|
|
8511
|
+
return web.json_response(sessions)
|
|
8512
|
+
|
|
8414
8513
|
async def start(self, port: int = 8767, host: str = "127.0.0.1"):
|
|
8415
8514
|
# 加载禁用技能列表
|
|
8416
8515
|
self._load_disabled_skills()
|
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [v1.
|
|
3
|
-
*
|
|
2
|
+
* [v1.24.5] 私聊记录查看模块
|
|
3
|
+
* 支持查看所有用户-Agent私聊会话 + Agent间群聊私聊记录
|
|
4
|
+
* 路径:
|
|
5
|
+
* /api/private-chats/sessions - 所有用户-Agent私聊(支持 ?agent=xxx 筛选)
|
|
6
|
+
* /api/agent-chat/pairs - Agent间私聊对
|
|
7
|
+
* /api/agent-chat/messages - Agent间私聊详情
|
|
4
8
|
*/
|
|
5
9
|
async function renderAgentChat() {
|
|
6
10
|
const content = $('content');
|
|
7
11
|
if (!content) return;
|
|
8
12
|
|
|
9
|
-
let
|
|
13
|
+
let currentTab = 'user-agent'; // 'user-agent' | 'agent-agent'
|
|
14
|
+
let currentFilter = { group_id: '', agent: '' };
|
|
10
15
|
|
|
11
16
|
function esc(s) { return (s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
// ── 数据加载 ──
|
|
19
|
+
async function loadPrivateSessions() {
|
|
20
|
+
const q = new URLSearchParams();
|
|
21
|
+
if (currentFilter.agent) q.set('agent', currentFilter.agent);
|
|
22
|
+
const data = await api('/api/private-chats/sessions?' + q.toString());
|
|
23
|
+
return Array.isArray(data) ? data : [];
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
async function
|
|
26
|
+
async function loadAgentPairs() {
|
|
21
27
|
const q = new URLSearchParams();
|
|
22
28
|
if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
|
|
23
29
|
const data = await api('/api/agent-chat/pairs?' + q.toString());
|
|
24
30
|
return Array.isArray(data) ? data : [];
|
|
25
31
|
}
|
|
26
32
|
|
|
27
|
-
async function
|
|
33
|
+
async function loadAgentMessages(fromAgent, toAgent) {
|
|
28
34
|
const q = new URLSearchParams();
|
|
29
35
|
if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
|
|
30
36
|
if (fromAgent) q.set('from_agent', fromAgent);
|
|
@@ -34,79 +40,187 @@ async function renderAgentChat() {
|
|
|
34
40
|
return Array.isArray(data) ? data : [];
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
async function loadGroups() {
|
|
44
|
+
try {
|
|
45
|
+
const data = await api('/api/groups');
|
|
46
|
+
return Array.isArray(data) ? data : (data.groups || data.data || []);
|
|
47
|
+
} catch { return []; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function loadAgents() {
|
|
51
|
+
if (allAgentsCache.length) return allAgentsCache;
|
|
52
|
+
const data = await api('/api/agents');
|
|
53
|
+
allAgentsCache = Array.isArray(data) ? data : [];
|
|
54
|
+
return allAgentsCache;
|
|
55
|
+
}
|
|
56
|
+
|
|
37
57
|
function formatTime(ts) {
|
|
38
58
|
if (!ts) return '';
|
|
39
|
-
const d = new Date(ts * 1000);
|
|
59
|
+
const d = new Date(ts * 1000 || ts);
|
|
40
60
|
const pad = n => String(n).padStart(2, '0');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function truncate(s, len) {
|
|
45
|
-
if (!s) return '';
|
|
46
|
-
return s.length > len ? s.slice(0, len) + '...' : s;
|
|
61
|
+
if (isNaN(d.getTime())) return String(ts);
|
|
62
|
+
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
// ── 主界面 ──
|
|
50
66
|
content.innerHTML = `
|
|
51
67
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
52
68
|
<div>
|
|
53
|
-
<h2 style="font-size:20px;font-weight:600;color:var(--text)"
|
|
54
|
-
<p style="font-size:13px;color:var(--text3);margin-top:4px"
|
|
55
|
-
</div>
|
|
56
|
-
<div style="display:flex;gap:8px">
|
|
57
|
-
<button id="acRefreshBtn" onclick="window._acRefresh()" style="padding:6px 14px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;font-size:13px">
|
|
58
|
-
刷新
|
|
59
|
-
</button>
|
|
60
|
-
<button id="acClearBtn" onclick="window._acClearAll()" style="padding:6px 14px;background:var(--danger);color:#fff;border:none;border-radius:var(--radius);cursor:pointer;font-size:13px">
|
|
61
|
-
清空全部
|
|
62
|
-
</button>
|
|
69
|
+
<h2 style="font-size:20px;font-weight:600;color:var(--text)">私聊记录</h2>
|
|
70
|
+
<p style="font-size:13px;color:var(--text3);margin-top:4px">查看用户与 Agent 的私聊会话,以及群聊中 Agent 间私下沟通</p>
|
|
63
71
|
</div>
|
|
72
|
+
<button id="acRefreshBtn" onclick="window._acRefresh()" style="padding:6px 14px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;font-size:13px">
|
|
73
|
+
刷新
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
<!-- Tab 切换 -->
|
|
77
|
+
<div style="display:flex;gap:0;margin-bottom:16px;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;width:fit-content">
|
|
78
|
+
<div id="acTabUA" onclick="window._acSwitchTab('user-agent')" style="padding:8px 20px;cursor:pointer;font-size:13px;font-weight:600;background:var(--accent);color:#fff">👤 用户-Agent 私聊</div>
|
|
79
|
+
<div id="acTabAA" onclick="window._acSwitchTab('agent-agent')" style="padding:8px 20px;cursor:pointer;font-size:13px;font-weight:600;background:var(--bg3);color:var(--text2)">🤖 Agent间私聊</div>
|
|
64
80
|
</div>
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
<!-- 筛选区 -->
|
|
82
|
+
<div id="acFilters" style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
|
|
83
|
+
<select id="acAgentFilter" onchange="window._acFilterChange()" style="padding:6px 10px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:13px;min-width:180px">
|
|
84
|
+
<option value="">全部 Agent</option>
|
|
85
|
+
</select>
|
|
86
|
+
<select id="acGroupFilter" onchange="window._acFilterChange()" style="display:none;padding:6px 10px;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:var(--radius);font-size:13px;min-width:160px">
|
|
67
87
|
<option value="">全部群聊</option>
|
|
68
88
|
</select>
|
|
69
89
|
</div>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">聊天对象</div>
|
|
73
|
-
<div id="acPairsList" style="flex:1;overflow-y:auto;padding:8px">
|
|
74
|
-
<div style="text-align:center;color:var(--text3);padding:40px;font-size:13px">加载中...</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
<div id="acChatPanel" style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
|
|
78
|
-
<div id="acChatHeader" style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">选择左侧聊天对象查看详情</div>
|
|
79
|
-
<div id="acChatMessages" style="flex:1;overflow-y:auto;padding:16px">
|
|
80
|
-
<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无聊天记录</div>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
90
|
+
<!-- 内容区 -->
|
|
91
|
+
<div id="acContent"></div>
|
|
84
92
|
`;
|
|
85
93
|
|
|
86
|
-
// ──
|
|
94
|
+
// ── 加载 Agent 列表(用于筛选下拉) ──
|
|
95
|
+
const agents = await loadAgents();
|
|
96
|
+
const agentSel = $('acAgentFilter');
|
|
97
|
+
agents.forEach(a => {
|
|
98
|
+
const opt = document.createElement('option');
|
|
99
|
+
opt.value = a.path || '';
|
|
100
|
+
opt.textContent = (a.avatar_emoji || '') + ' ' + escHtml(a.name || a.path);
|
|
101
|
+
agentSel.appendChild(opt);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ── 加载群列表(Agent间私聊 tab 使用) ──
|
|
87
105
|
const groups = await loadGroups();
|
|
88
|
-
const
|
|
106
|
+
const groupSel = $('acGroupFilter');
|
|
89
107
|
groups.forEach(g => {
|
|
90
108
|
const opt = document.createElement('option');
|
|
91
109
|
opt.value = g.id || g.group_id || '';
|
|
92
110
|
opt.textContent = g.name || g.group_name || g.id || '未命名';
|
|
93
|
-
|
|
111
|
+
groupSel.appendChild(opt);
|
|
94
112
|
});
|
|
95
113
|
|
|
96
|
-
// ──
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
114
|
+
// ── Tab 切换 ──
|
|
115
|
+
window._acSwitchTab = (tab) => {
|
|
116
|
+
currentTab = tab;
|
|
117
|
+
const tabUA = $('acTabUA');
|
|
118
|
+
const tabAA = $('acTabAA');
|
|
119
|
+
const agentFilter = $('acAgentFilter');
|
|
120
|
+
const groupFilter = $('acGroupFilter');
|
|
121
|
+
if (tab === 'user-agent') {
|
|
122
|
+
tabUA.style.background = 'var(--accent)'; tabUA.style.color = '#fff';
|
|
123
|
+
tabAA.style.background = 'var(--bg3)'; tabAA.style.color = 'var(--text2)';
|
|
124
|
+
agentFilter.style.display = '';
|
|
125
|
+
groupFilter.style.display = 'none';
|
|
126
|
+
} else {
|
|
127
|
+
tabAA.style.background = 'var(--accent)'; tabAA.style.color = '#fff';
|
|
128
|
+
tabUA.style.background = 'var(--bg3)'; tabUA.style.color = 'var(--text2)';
|
|
129
|
+
agentFilter.style.display = 'none';
|
|
130
|
+
groupFilter.style.display = '';
|
|
131
|
+
}
|
|
132
|
+
renderContent();
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
window._acFilterChange = () => {
|
|
136
|
+
currentFilter.agent = $('acAgentFilter').value;
|
|
137
|
+
currentFilter.group_id = $('acGroupFilter').value;
|
|
138
|
+
renderContent();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ── 用户-Agent 私聊列表渲染 ──
|
|
142
|
+
async function renderUserAgentSessions() {
|
|
143
|
+
const sessions = await loadPrivateSessions();
|
|
144
|
+
const container = $('acContent');
|
|
145
|
+
|
|
146
|
+
if (!sessions.length) {
|
|
147
|
+
container.innerHTML = '<div class="empty">暂无用户-Agent私聊会话</div>';
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 按 agent 分组
|
|
152
|
+
const grouped = {};
|
|
153
|
+
for (const s of sessions) {
|
|
154
|
+
const key = s.agent_path || 'default';
|
|
155
|
+
if (!grouped[key]) grouped[key] = { name: s.agent_name || key, sessions: [] };
|
|
156
|
+
grouped[key].sessions.push(s);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let html = `<div style="font-size:13px;color:var(--text3);margin-bottom:12px">共 ${sessions.length} 个私聊会话,${Object.keys(grouped).length} 个 Agent</div>`;
|
|
160
|
+
|
|
161
|
+
for (const [agentPath, group] of Object.entries(grouped)) {
|
|
162
|
+
html += `<div style="margin-bottom:20px">
|
|
163
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer" onclick="this.parentElement.querySelector('.ac-group-sessions').classList.toggle('hidden')">
|
|
164
|
+
<span style="font-size:16px;transition:transform .2s">📂</span>
|
|
165
|
+
<span style="font-size:14px;font-weight:600;color:var(--text)">${esc(group.name)}</span>
|
|
166
|
+
<span style="font-size:11px;color:var(--text3);background:var(--bg3);padding:2px 8px;border-radius:10px">${group.sessions.length} 个会话</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="ac-group-sessions" style="margin-left:24px">`;
|
|
169
|
+
|
|
170
|
+
for (const s of group.sessions) {
|
|
171
|
+
const displayName = s.display_name || s.id;
|
|
172
|
+
const truncated = displayName.length > 40 ? displayName.slice(0, 40) + '...' : displayName;
|
|
173
|
+
const preview = s.preview ? esc(s.preview) : '<span style="color:var(--text3)">无预览</span>';
|
|
174
|
+
const lastTime = formatTime(s.last);
|
|
175
|
+
const agentPathSafe = esc(s.agent_path).replace(/'/g, "\\'");
|
|
176
|
+
html += `<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-radius:6px;margin-bottom:4px;cursor:pointer;transition:background .15s;border:1px solid var(--border)"
|
|
177
|
+
onmouseover="this.style.background='var(--bg3)'" onmouseout="this.style.background='transparent'"
|
|
178
|
+
onclick="window._acViewSession('${esc(s.id)}','${agentPathSafe}')">
|
|
179
|
+
<div style="flex:1;min-width:0">
|
|
180
|
+
<div style="font-size:13px;font-weight:500;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(s.id)}">${esc(truncated)}</div>
|
|
181
|
+
<div style="font-size:11px;color:var(--text3);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${preview}</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div style="flex-shrink:0;text-align:right;margin-left:12px">
|
|
184
|
+
<div style="font-size:11px;color:var(--text3)">${lastTime}</div>
|
|
185
|
+
<div style="font-size:11px;color:var(--text3)">${s.messages} 条消息</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>`;
|
|
188
|
+
}
|
|
189
|
+
html += '</div></div>';
|
|
190
|
+
}
|
|
191
|
+
container.innerHTML = html;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── 查看私聊会话消息 ──
|
|
195
|
+
window._acViewSession = async (sid, agentPath) => {
|
|
196
|
+
// 复用 admin-sessions.js 的 viewSession 逻辑
|
|
197
|
+
window._viewSessionSid = sid;
|
|
198
|
+
window._viewSessionOffset = 0;
|
|
199
|
+
_navSubState = 'view:' + sid;
|
|
200
|
+
navigateTo('agentchat', 'view:' + sid, _loadSessionMessages);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// ── Agent间私聊渲染 ──
|
|
204
|
+
async function renderAgentAgentChats() {
|
|
205
|
+
const pairs = await loadAgentPairs();
|
|
206
|
+
const container = $('acContent');
|
|
207
|
+
|
|
100
208
|
if (!pairs.length) {
|
|
101
|
-
|
|
209
|
+
container.innerHTML = '<div class="empty">暂无Agent间私聊记录</div>';
|
|
102
210
|
return;
|
|
103
211
|
}
|
|
104
|
-
|
|
212
|
+
|
|
213
|
+
let html = `<div style="display:grid;grid-template-columns:340px 1fr;gap:16px;min-height:500px">
|
|
214
|
+
<div style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
|
|
215
|
+
<div style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">聊天对象</div>
|
|
216
|
+
<div id="acPairsList" style="flex:1;overflow-y:auto;padding:8px">`;
|
|
217
|
+
|
|
218
|
+
for (const p of pairs) {
|
|
105
219
|
const a = esc(p.from_name || p.from_agent);
|
|
106
220
|
const b = esc(p.to_name || p.to_agent);
|
|
107
221
|
const last = formatTime(p.last_ts);
|
|
108
222
|
const count = p.cnt || p.count || 0;
|
|
109
|
-
|
|
223
|
+
html += `<div onclick="window._acSelectPair('${esc(p.from_agent)}','${esc(p.to_agent)}','${a}','${b}')"
|
|
110
224
|
style="padding:10px 12px;border-radius:6px;cursor:pointer;margin-bottom:4px;transition:background .15s"
|
|
111
225
|
onmouseover="this.style.background='var(--bg3)'" onmouseout="this.style.background='transparent'">
|
|
112
226
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
|
|
@@ -115,21 +229,36 @@ async function renderAgentChat() {
|
|
|
115
229
|
</div>
|
|
116
230
|
<div style="font-size:11px;color:var(--text3)">最后沟通: ${last}</div>
|
|
117
231
|
</div>`;
|
|
118
|
-
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
html += `</div></div>
|
|
235
|
+
<div style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
|
|
236
|
+
<div id="acChatHeader" style="padding:12px 16px;border-bottom:1px solid var(--border);font-weight:600;font-size:14px;color:var(--text2)">选择左侧聊天对象查看详情</div>
|
|
237
|
+
<div id="acChatMessages" style="flex:1;overflow-y:auto;padding:16px">
|
|
238
|
+
<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无聊天记录</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>`;
|
|
242
|
+
|
|
243
|
+
container.innerHTML = html;
|
|
244
|
+
|
|
245
|
+
// Agent间私聊清空按钮
|
|
246
|
+
const clearBtnHtml = `<button onclick="window._acClearAgentChats()" style="margin-top:12px;padding:6px 14px;background:var(--danger);color:#fff;border:none;border-radius:var(--radius);cursor:pointer;font-size:13px">清空Agent间私聊</button>`;
|
|
247
|
+
container.insertAdjacentHTML('beforeend', clearBtnHtml);
|
|
119
248
|
}
|
|
120
249
|
|
|
121
|
-
// ──
|
|
122
|
-
async
|
|
250
|
+
// ── Agent间私聊消息查看 ──
|
|
251
|
+
window._acSelectPair = async (fromAgent, toAgent, fromName, toName) => {
|
|
123
252
|
const header = $('acChatHeader');
|
|
124
|
-
header.innerHTML = `<span style="color:var(--accent)">${esc(fromName)}</span> ↔ <span style="color:var(--accent)">${esc(toName)}</span> 的私聊记录`;
|
|
253
|
+
if (header) header.innerHTML = `<span style="color:var(--accent)">${esc(fromName)}</span> ↔ <span style="color:var(--accent)">${esc(toName)}</span> 的私聊记录`;
|
|
125
254
|
|
|
126
|
-
const msgs = await
|
|
255
|
+
const msgs = await loadAgentMessages(fromAgent, toAgent);
|
|
127
256
|
const container = $('acChatMessages');
|
|
257
|
+
if (!container) return;
|
|
128
258
|
if (!msgs.length) {
|
|
129
259
|
container.innerHTML = '<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无记录</div>';
|
|
130
260
|
return;
|
|
131
261
|
}
|
|
132
|
-
// 按 time 正序显示
|
|
133
262
|
const sorted = [...msgs].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
|
|
134
263
|
container.innerHTML = sorted.map(m => {
|
|
135
264
|
const isFrom = m.from_agent === fromAgent;
|
|
@@ -145,30 +274,33 @@ async function renderAgentChat() {
|
|
|
145
274
|
</div>`;
|
|
146
275
|
}).join('');
|
|
147
276
|
container.scrollTop = container.scrollHeight;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── 全局方法 ──
|
|
151
|
-
window._acRefresh = async () => { await renderPairs(); };
|
|
152
|
-
window._acFilterChange = () => {
|
|
153
|
-
currentFilter.group_id = $('acGroupFilter').value;
|
|
154
|
-
renderPairs();
|
|
155
277
|
};
|
|
156
|
-
|
|
157
|
-
window.
|
|
158
|
-
if (!confirm('
|
|
278
|
+
|
|
279
|
+
window._acClearAgentChats = async () => {
|
|
280
|
+
if (!confirm('确定要清空所有Agent间私聊记录吗?此操作不可恢复。')) return;
|
|
159
281
|
const q = new URLSearchParams();
|
|
160
282
|
if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
|
|
161
283
|
try {
|
|
162
284
|
const data = await api('/api/agent-chat/messages?' + q.toString(), { method: 'DELETE' });
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
$('acChatHeader').textContent = '选择左侧聊天对象查看详情';
|
|
167
|
-
} catch (e) { toast('清空失败: ' + (e.message || e), 'error'); }
|
|
285
|
+
showToast('已清空 ' + (data.deleted || 0) + ' 条记录', 'success');
|
|
286
|
+
renderContent();
|
|
287
|
+
} catch (e) { showToast('清空失败: ' + (e.message || e), 'danger'); }
|
|
168
288
|
};
|
|
169
289
|
|
|
290
|
+
// ── 渲染主内容 ──
|
|
291
|
+
async function renderContent() {
|
|
292
|
+
if (currentTab === 'user-agent') {
|
|
293
|
+
await renderUserAgentSessions();
|
|
294
|
+
} else {
|
|
295
|
+
await renderAgentAgentChats();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ── 全局刷新 ──
|
|
300
|
+
window._acRefresh = async () => { await renderContent(); };
|
|
301
|
+
|
|
170
302
|
// 初始加载
|
|
171
|
-
await
|
|
303
|
+
await renderContent();
|
|
172
304
|
}
|
|
173
305
|
|
|
174
306
|
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ── admin-core.js — Shared infrastructure (loaded FIRST) ── */
|
|
2
2
|
|
|
3
3
|
const API='';
|
|
4
|
-
const pages={dashboard:'📊 仪表盘',agents:'🤖 Agent 管理',platforms:'🌐 聊天平台',organization:'🏢 组织管理',departments:'🏛 部门管理',agentchat:'
|
|
4
|
+
const pages={dashboard:'📊 仪表盘',agents:'🤖 Agent 管理',platforms:'🌐 聊天平台',organization:'🏢 组织管理',departments:'🏛 部门管理',agentchat:'💬 私聊记录',sessions:'📋 会话管理',memory:'🧠 记忆管理',permissions:'🔑 权限管理',llm:'🧬 大模型设置',system:'⚙️ 系统配置',executor:'🔧 执行引擎',skills:'🛠 技能管理',files:'📁 工作目录',logs:'📜 查看日志',tasks:'📌 任务记录'};
|
|
5
5
|
let currentPage='dashboard';
|
|
6
6
|
let allAgentsCache=[];
|
|
7
7
|
let allModelsCache=[];
|
package/web/ui/chat/groupchat.js
CHANGED
|
@@ -444,10 +444,23 @@ function renderGroupSidebar(groupData) {
|
|
|
444
444
|
list.innerHTML = html;
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
// [v1.23.79]
|
|
447
|
+
// [v1.23.79] 从群聊成员列表/消息头像点击,直接发起私聊
|
|
448
448
|
function startPrivateChatFromGroup(agentPath) {
|
|
449
449
|
if (typeof selectAgent === 'function') {
|
|
450
|
-
|
|
450
|
+
// 仅做群聊清理(停轮询、清 URL/localStorage),不加载旧 agent 的 sessions
|
|
451
|
+
_stopGroupPolling(currentGroupId);
|
|
452
|
+
currentView = 'chat';
|
|
453
|
+
currentGroupId = null;
|
|
454
|
+
groupMessages = [];
|
|
455
|
+
StatePersistence.remove('currentView');
|
|
456
|
+
StatePersistence.remove('currentGroupId');
|
|
457
|
+
// 清除群聊 URL 中的 ?g= 参数
|
|
458
|
+
var cleanUrl = '/ui/chat/chat_container.html';
|
|
459
|
+
if (typeof state !== 'undefined' && agentPath && agentPath !== 'default') {
|
|
460
|
+
cleanUrl += '?a=' + encodeURIComponent(agentPath);
|
|
461
|
+
}
|
|
462
|
+
window.history.replaceState({}, '', cleanUrl);
|
|
463
|
+
// 让 selectAgent 处理完整的私聊切换
|
|
451
464
|
selectAgent(agentPath);
|
|
452
465
|
}
|
|
453
466
|
}
|
|
@@ -500,7 +513,7 @@ function _renderGroupMessagesInner() {
|
|
|
500
513
|
var agentColor = msg.agent_color || msg.color || 'var(--accent)';
|
|
501
514
|
var agentRole = msg.agent_role || msg.role_detail || '';
|
|
502
515
|
var agentPath = msg.agent || '';
|
|
503
|
-
var _clickAvatar = agentPath ? ' onclick="
|
|
516
|
+
var _clickAvatar = agentPath ? ' onclick="startPrivateChatFromGroup(\'' + escapeHtml(agentPath) + '\')" style="background:' + agentColor + ';color:#fff;cursor:pointer" title="点击私聊 ' + escapeHtml(agentName) + '"' : ' style="background:' + agentColor + ';color:#fff"';
|
|
504
517
|
// [v1.23.82] 流式输出时显示闪烁光标
|
|
505
518
|
var _streamingCursor = msg._streaming ? '<span class="streaming-cursor" style="display:inline-block;width:2px;height:1em;background:var(--accent);margin-left:2px;animation:blink 1s step-end infinite;vertical-align:text-bottom"></span>' : '';
|
|
506
519
|
html += '<div class="group-msg-row">'
|
|
@@ -520,7 +533,7 @@ function _renderGroupMessagesInner() {
|
|
|
520
533
|
var rEmoji = r.agent_emoji || r.sender_avatar || r.avatar || '🤖';
|
|
521
534
|
var rColor = r.agent_color || r.color || 'var(--accent)';
|
|
522
535
|
var rPath = r.agent_path || '';
|
|
523
|
-
var _rClickAvatar = rPath ? ' onclick="
|
|
536
|
+
var _rClickAvatar = rPath ? ' onclick="startPrivateChatFromGroup(\'' + escapeHtml(rPath) + '\')" style="background:' + rColor + ';color:#fff;cursor:pointer" title="点击私聊 ' + escapeHtml(rName) + '"' : ' style="background:' + rColor + ';color:#fff"';
|
|
524
537
|
html += '<div class="group-msg-row">'
|
|
525
538
|
+ '<div class="group-msg-avatar"' + _rClickAvatar + '>' + rEmoji + '</div>'
|
|
526
539
|
+ '<div>'
|