myagent-ai 1.23.36 → 1.23.37
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/groups/manager.py +90 -0
- package/package.json +1 -1
- package/web/api_server.py +59 -8
- package/web/ui/admin/admin-agentchat.js +175 -0
- package/web/ui/index.html +2 -0
package/groups/manager.py
CHANGED
|
@@ -275,6 +275,27 @@ class GroupManager:
|
|
|
275
275
|
CREATE INDEX IF NOT EXISTS idx_group_messages_group_id
|
|
276
276
|
ON group_messages(group_id, timestamp)
|
|
277
277
|
""")
|
|
278
|
+
# [v1.23.37] Agent间私聊记录表(独立于群聊消息,便于查询和管理)
|
|
279
|
+
self._db_conn.execute("""
|
|
280
|
+
CREATE TABLE IF NOT EXISTS agent_chat (
|
|
281
|
+
id TEXT PRIMARY KEY,
|
|
282
|
+
group_id TEXT NOT NULL DEFAULT '',
|
|
283
|
+
from_agent TEXT NOT NULL,
|
|
284
|
+
from_name TEXT DEFAULT '',
|
|
285
|
+
to_agent TEXT NOT NULL,
|
|
286
|
+
to_name TEXT DEFAULT '',
|
|
287
|
+
content TEXT NOT NULL,
|
|
288
|
+
timestamp REAL NOT NULL
|
|
289
|
+
)
|
|
290
|
+
""")
|
|
291
|
+
self._db_conn.execute("""
|
|
292
|
+
CREATE INDEX IF NOT EXISTS idx_agent_chat_group
|
|
293
|
+
ON agent_chat(group_id, timestamp)
|
|
294
|
+
""")
|
|
295
|
+
self._db_conn.execute("""
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_agent_chat_pair
|
|
297
|
+
ON agent_chat(from_agent, to_agent, timestamp)
|
|
298
|
+
""")
|
|
278
299
|
self._db_conn.commit()
|
|
279
300
|
|
|
280
301
|
def close(self):
|
|
@@ -697,6 +718,75 @@ class GroupManager:
|
|
|
697
718
|
self._db_conn.commit()
|
|
698
719
|
return True
|
|
699
720
|
|
|
721
|
+
# ==================================================================
|
|
722
|
+
# [v1.23.37] Agent间私聊记录
|
|
723
|
+
# ==================================================================
|
|
724
|
+
|
|
725
|
+
def add_agent_chat(self, group_id: str, from_agent: str, from_name: str,
|
|
726
|
+
to_agent: str, to_name: str, content: str) -> str:
|
|
727
|
+
"""添加一条Agent间私聊记录,返回消息ID"""
|
|
728
|
+
if not self._db_conn:
|
|
729
|
+
return ""
|
|
730
|
+
msg_id = uuid.uuid4().hex[:16]
|
|
731
|
+
self._db_conn.execute(
|
|
732
|
+
"INSERT INTO agent_chat (id, group_id, from_agent, from_name, to_agent, to_name, content, timestamp) "
|
|
733
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
734
|
+
(msg_id, group_id, from_agent, from_name, to_agent, to_name, content, time.time()),
|
|
735
|
+
)
|
|
736
|
+
self._db_conn.commit()
|
|
737
|
+
return msg_id
|
|
738
|
+
|
|
739
|
+
def get_agent_chats(self, group_id: str = "", from_agent: str = "",
|
|
740
|
+
to_agent: str = "", limit: int = 200) -> list:
|
|
741
|
+
"""查询Agent间私聊记录,支持按群、发送方、接收方筛选"""
|
|
742
|
+
if not self._db_conn:
|
|
743
|
+
return []
|
|
744
|
+
conditions = []
|
|
745
|
+
params = []
|
|
746
|
+
if group_id:
|
|
747
|
+
conditions.append("group_id = ?")
|
|
748
|
+
params.append(group_id)
|
|
749
|
+
if from_agent:
|
|
750
|
+
conditions.append("(from_agent = ? OR to_agent = ?)")
|
|
751
|
+
params.extend([from_agent, from_agent])
|
|
752
|
+
if to_agent:
|
|
753
|
+
conditions.append("(from_agent = ? OR to_agent = ?)")
|
|
754
|
+
params.extend([to_agent, to_agent])
|
|
755
|
+
where = (" WHERE " + " AND ".join(conditions)) if conditions else ""
|
|
756
|
+
rows = self._db_conn.execute(
|
|
757
|
+
f"SELECT * FROM agent_chat{where} ORDER BY timestamp DESC LIMIT ?",
|
|
758
|
+
params + [limit],
|
|
759
|
+
).fetchall()
|
|
760
|
+
return [dict(r) for r in rows]
|
|
761
|
+
|
|
762
|
+
def get_agent_chat_pairs(self, group_id: str = "") -> list:
|
|
763
|
+
"""获取所有有私聊记录的Agent对(去重),返回 [{from_agent, from_name, to_agent, to_name, count, last_time}]"""
|
|
764
|
+
if not self._db_conn:
|
|
765
|
+
return []
|
|
766
|
+
where = " WHERE group_id = ?" if group_id else ""
|
|
767
|
+
params = [group_id] if group_id else []
|
|
768
|
+
rows = self._db_conn.execute(
|
|
769
|
+
f"""SELECT from_agent, from_name, to_agent, to_name,
|
|
770
|
+
COUNT(*) as cnt, MAX(timestamp) as last_ts
|
|
771
|
+
FROM agent_chat{where}
|
|
772
|
+
GROUP BY CASE WHEN from_agent < to_agent THEN from_agent ELSE to_agent END,
|
|
773
|
+
CASE WHEN from_agent < to_agent THEN to_agent ELSE from_agent END
|
|
774
|
+
ORDER BY last_ts DESC""",
|
|
775
|
+
params,
|
|
776
|
+
).fetchall()
|
|
777
|
+
return [dict(r) for r in rows]
|
|
778
|
+
|
|
779
|
+
def clear_agent_chats(self, group_id: str = "") -> int:
|
|
780
|
+
"""清空私聊记录,返回删除数量"""
|
|
781
|
+
if not self._db_conn:
|
|
782
|
+
return 0
|
|
783
|
+
if group_id:
|
|
784
|
+
cur = self._db_conn.execute("DELETE FROM agent_chat WHERE group_id = ?", (group_id,))
|
|
785
|
+
else:
|
|
786
|
+
cur = self._db_conn.execute("DELETE FROM agent_chat")
|
|
787
|
+
self._db_conn.commit()
|
|
788
|
+
return cur.rowcount
|
|
789
|
+
|
|
700
790
|
# ==================================================================
|
|
701
791
|
# 群消息统计
|
|
702
792
|
# ==================================================================
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -464,6 +464,10 @@ class ApiServer:
|
|
|
464
464
|
r.add_get("/api/groups/{gid}/messages", self.handle_get_group_messages)
|
|
465
465
|
r.add_post("/api/groups/{gid}/messages", self.handle_send_group_message)
|
|
466
466
|
r.add_delete("/api/groups/{gid}/messages", self.handle_clear_group_messages)
|
|
467
|
+
# [v1.23.37] Agent间私聊记录查询
|
|
468
|
+
r.add_get("/api/agent-chat/pairs", self.handle_get_agent_chat_pairs)
|
|
469
|
+
r.add_get("/api/agent-chat/messages", self.handle_get_agent_chat_messages)
|
|
470
|
+
r.add_delete("/api/agent-chat/messages", self.handle_clear_agent_chat_messages)
|
|
467
471
|
# ── 部门管理 ──
|
|
468
472
|
r.add_get("/api/departments", self.handle_dept_tree)
|
|
469
473
|
r.add_post("/api/departments", self.handle_create_dept)
|
|
@@ -7779,11 +7783,15 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7779
7783
|
- @某个Agent: 只有被@的Agent需要回复
|
|
7780
7784
|
- @所有人 / @all: 所有成员都需要回复
|
|
7781
7785
|
- 不@任何人(广播): 你自行判断是否需要回复
|
|
7782
|
-
2. **跨Agent
|
|
7783
|
-
|
|
7784
|
-
-
|
|
7785
|
-
|
|
7786
|
-
-
|
|
7786
|
+
2. **跨Agent私下沟通**: 你可以使用 `myagent-ai chat --agent <路径> --message "消息"` 命令向群内其他Agent发送私下消息。对方会在自己下次处理消息时收到。
|
|
7787
|
+
- 私下沟通的内容不会直接显示给用户,适合讨论细节、交换数据、协调方案
|
|
7788
|
+
- 当任务需要多个Agent协作时,应该先在群里讨论分工,然后私下沟通具体细节
|
|
7789
|
+
3. **协作分工模式**(复杂任务):
|
|
7790
|
+
- 部长/管理员应主动分析任务,在群里提出分工方案(谁负责什么)
|
|
7791
|
+
- 其他成员应积极响应,认领自己擅长的部分
|
|
7792
|
+
- 分工确定后,各自私下沟通需要协调的细节
|
|
7793
|
+
- 部长/管理员负责在群里汇总进展,向用户汇报阶段性成果和最终结果
|
|
7794
|
+
- 私下沟通的详细过程不需要在群里展示,只汇报关键进展和最终结论
|
|
7787
7795
|
{at_info}
|
|
7788
7796
|
### 近期群聊记录(最近10条)
|
|
7789
7797
|
{chat_history if chat_history else '(暂无历史消息)'}
|
|
@@ -7792,7 +7800,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7792
7800
|
- 你只代表你自己发言,使用第一人称
|
|
7793
7801
|
- 不要假装是其他Agent或代替其他Agent回答
|
|
7794
7802
|
- 如果问题超出你的能力范围,建议用户@相关专家Agent
|
|
7795
|
-
- 如果需要其他Agent的信息,使用 `myagent-ai chat`
|
|
7803
|
+
- 如果需要其他Agent的信息,使用 `myagent-ai chat` 命令私下沟通
|
|
7796
7804
|
|
|
7797
7805
|
### 能力提醒(关键)
|
|
7798
7806
|
- 你拥有完整的工具调用能力(搜索、文件操作、代码执行、图片生成等)
|
|
@@ -7872,6 +7880,7 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7872
7880
|
)
|
|
7873
7881
|
|
|
7874
7882
|
# [v1.23.29] 从 Agent 响应中提取 __CHAT_AGENT__ 标记,清理并保存跨Agent通信消息
|
|
7883
|
+
# [v1.23.37] 同时写入独立的 agent_chat 私聊记录表,便于管理后台查看
|
|
7875
7884
|
_chat_msg_pattern = _re.compile(r'__CHAT_AGENT__(.+?)\|(.+?)\|(.+?)__END__')
|
|
7876
7885
|
for resp in final_responses:
|
|
7877
7886
|
resp_text = resp.get("response", "")
|
|
@@ -7879,17 +7888,31 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7879
7888
|
if chat_matches:
|
|
7880
7889
|
# 清理标记文本
|
|
7881
7890
|
resp["response"] = _chat_msg_pattern.sub('', resp_text).strip()
|
|
7882
|
-
# 保存跨Agent
|
|
7891
|
+
# 保存跨Agent通信消息
|
|
7883
7892
|
for c_path, c_name, c_msg in chat_matches:
|
|
7893
|
+
c_path_s, c_name_s, c_msg_s = c_path.strip(), c_name.strip(), c_msg.strip()
|
|
7894
|
+
# 群聊中显示简要提示
|
|
7884
7895
|
chat_sys_msg = GroupMessage(
|
|
7885
7896
|
group_id=gid,
|
|
7886
7897
|
sender=resp.get("agent_path", ""),
|
|
7887
7898
|
sender_name=resp.get("name", ""),
|
|
7888
7899
|
sender_avatar=resp.get("avatar", "🤖"),
|
|
7889
|
-
content=f"💬
|
|
7900
|
+
content=f"💬 私下与 {c_name_s} 沟通中...",
|
|
7890
7901
|
msg_type="text",
|
|
7891
7902
|
)
|
|
7892
7903
|
mgr.add_message(chat_sys_msg)
|
|
7904
|
+
# [v1.23.37] 写入独立的私聊记录表
|
|
7905
|
+
try:
|
|
7906
|
+
mgr.add_agent_chat(
|
|
7907
|
+
group_id=gid,
|
|
7908
|
+
from_agent=resp.get("agent_path", ""),
|
|
7909
|
+
from_name=resp.get("name", ""),
|
|
7910
|
+
to_agent=c_path_s,
|
|
7911
|
+
to_name=c_name_s,
|
|
7912
|
+
content=c_msg_s,
|
|
7913
|
+
)
|
|
7914
|
+
except Exception as ce:
|
|
7915
|
+
logger.debug(f"保存私聊记录失败: {ce}")
|
|
7893
7916
|
|
|
7894
7917
|
# Save agent messages sequentially in sorted order
|
|
7895
7918
|
for resp in final_responses:
|
|
@@ -7926,6 +7949,34 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7926
7949
|
ok = mgr.clear_messages(gid)
|
|
7927
7950
|
return web.json_response({"ok": ok})
|
|
7928
7951
|
|
|
7952
|
+
# ── [v1.23.37] Agent间私聊记录 API ──
|
|
7953
|
+
|
|
7954
|
+
async def handle_get_agent_chat_pairs(self, request):
|
|
7955
|
+
"""GET /api/agent-chat/pairs - 获取所有私聊Agent对"""
|
|
7956
|
+
gid = request.query.get("group_id", "")
|
|
7957
|
+
mgr = self._get_group_manager()
|
|
7958
|
+
pairs = mgr.get_agent_chat_pairs(group_id=gid)
|
|
7959
|
+
return web.json_response(pairs)
|
|
7960
|
+
|
|
7961
|
+
async def handle_get_agent_chat_messages(self, request):
|
|
7962
|
+
"""GET /api/agent-chat/messages - 查询私聊记录详情"""
|
|
7963
|
+
gid = request.query.get("group_id", "")
|
|
7964
|
+
from_agent = request.query.get("from_agent", "")
|
|
7965
|
+
to_agent = request.query.get("to_agent", "")
|
|
7966
|
+
limit = min(int(request.query.get("limit", "200")), 500)
|
|
7967
|
+
mgr = self._get_group_manager()
|
|
7968
|
+
messages = mgr.get_agent_chats(
|
|
7969
|
+
group_id=gid, from_agent=from_agent, to_agent=to_agent, limit=limit
|
|
7970
|
+
)
|
|
7971
|
+
return web.json_response(messages)
|
|
7972
|
+
|
|
7973
|
+
async def handle_clear_agent_chat_messages(self, request):
|
|
7974
|
+
"""DELETE /api/agent-chat/messages - 清空私聊记录"""
|
|
7975
|
+
gid = request.query.get("group_id", "")
|
|
7976
|
+
mgr = self._get_group_manager()
|
|
7977
|
+
deleted = mgr.clear_agent_chats(group_id=gid)
|
|
7978
|
+
return web.json_response({"ok": True, "deleted": deleted})
|
|
7979
|
+
|
|
7929
7980
|
async def start(self, port: int = 8767, host: str = "127.0.0.1"):
|
|
7930
7981
|
# 加载禁用技能列表
|
|
7931
7982
|
self._load_disabled_skills()
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [v1.23.37] Agent间私聊记录查看模块
|
|
3
|
+
* 路径: /api/agent-chat/pairs, /api/agent-chat/messages
|
|
4
|
+
*/
|
|
5
|
+
async function renderAgentChat() {
|
|
6
|
+
const content = $('content');
|
|
7
|
+
if (!content) return;
|
|
8
|
+
|
|
9
|
+
let currentFilter = { group_id: '', from_agent: '', to_agent: '' };
|
|
10
|
+
|
|
11
|
+
function esc(s) { return (s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
12
|
+
|
|
13
|
+
async function loadGroups() {
|
|
14
|
+
try {
|
|
15
|
+
const data = await api('/api/groups');
|
|
16
|
+
return Array.isArray(data) ? data : (data.groups || data.data || []);
|
|
17
|
+
} catch { return []; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function loadPairs() {
|
|
21
|
+
const q = new URLSearchParams();
|
|
22
|
+
if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
|
|
23
|
+
const data = await api('/api/agent-chat/pairs?' + q.toString());
|
|
24
|
+
return Array.isArray(data) ? data : [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function loadMessages(fromAgent, toAgent) {
|
|
28
|
+
const q = new URLSearchParams();
|
|
29
|
+
if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
|
|
30
|
+
if (fromAgent) q.set('from_agent', fromAgent);
|
|
31
|
+
if (toAgent) q.set('to_agent', toAgent);
|
|
32
|
+
q.set('limit', '500');
|
|
33
|
+
const data = await api('/api/agent-chat/messages?' + q.toString());
|
|
34
|
+
return Array.isArray(data) ? data : [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatTime(ts) {
|
|
38
|
+
if (!ts) return '';
|
|
39
|
+
const d = new Date(ts * 1000);
|
|
40
|
+
const pad = n => String(n).padStart(2, '0');
|
|
41
|
+
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function truncate(s, len) {
|
|
45
|
+
if (!s) return '';
|
|
46
|
+
return s.length > len ? s.slice(0, len) + '...' : s;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── 主界面 ──
|
|
50
|
+
content.innerHTML = `
|
|
51
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px">
|
|
52
|
+
<div>
|
|
53
|
+
<h2 style="font-size:20px;font-weight:600;color:var(--text)">Agent 私聊记录</h2>
|
|
54
|
+
<p style="font-size:13px;color:var(--text3);margin-top:4px">查看群聊中 Agent 之间的私下沟通记录</p>
|
|
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>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
|
|
66
|
+
<select id="acGroupFilter" 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:160px">
|
|
67
|
+
<option value="">全部群聊</option>
|
|
68
|
+
</select>
|
|
69
|
+
</div>
|
|
70
|
+
<div style="display:grid;grid-template-columns:340px 1fr;gap:16px;min-height:500px" id="acLayout">
|
|
71
|
+
<div id="acPairsPanel" style="background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;display:flex;flex-direction:column">
|
|
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>
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
// ── 加载群列表 ──
|
|
87
|
+
const groups = await loadGroups();
|
|
88
|
+
const sel = $('acGroupFilter');
|
|
89
|
+
groups.forEach(g => {
|
|
90
|
+
const opt = document.createElement('option');
|
|
91
|
+
opt.value = g.id || g.group_id || '';
|
|
92
|
+
opt.textContent = g.name || g.group_name || g.id || '未命名';
|
|
93
|
+
sel.appendChild(opt);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// ── 渲染聊天对列表 ──
|
|
97
|
+
async function renderPairs() {
|
|
98
|
+
const pairs = await loadPairs();
|
|
99
|
+
const list = $('acPairsList');
|
|
100
|
+
if (!pairs.length) {
|
|
101
|
+
list.innerHTML = '<div style="text-align:center;color:var(--text3);padding:40px;font-size:13px">暂无私聊记录</div>';
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
list.innerHTML = pairs.map(p => {
|
|
105
|
+
const a = esc(p.from_name || p.from_agent);
|
|
106
|
+
const b = esc(p.to_name || p.to_agent);
|
|
107
|
+
const last = formatTime(p.last_ts);
|
|
108
|
+
const count = p.cnt || p.count || 0;
|
|
109
|
+
return `<div onclick="window._acSelectPair('${esc(p.from_agent)}','${esc(p.to_agent)}','${a}','${b}')"
|
|
110
|
+
style="padding:10px 12px;border-radius:6px;cursor:pointer;margin-bottom:4px;transition:background .15s"
|
|
111
|
+
onmouseover="this.style.background='var(--bg3)'" onmouseout="this.style.background='transparent'">
|
|
112
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
|
|
113
|
+
<span style="font-size:13px;font-weight:600;color:var(--text)">${a} ↔ ${b}</span>
|
|
114
|
+
<span style="font-size:11px;color:var(--text3)">${count}条</span>
|
|
115
|
+
</div>
|
|
116
|
+
<div style="font-size:11px;color:var(--text3)">最后沟通: ${last}</div>
|
|
117
|
+
</div>`;
|
|
118
|
+
}).join('');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── 渲染聊天记录 ──
|
|
122
|
+
async function renderMessages(fromAgent, toAgent, fromName, toName) {
|
|
123
|
+
const header = $('acChatHeader');
|
|
124
|
+
header.innerHTML = `<span style="color:var(--accent)">${esc(fromName)}</span> ↔ <span style="color:var(--accent)">${esc(toName)}</span> 的私聊记录`;
|
|
125
|
+
|
|
126
|
+
const msgs = await loadMessages(fromAgent, toAgent);
|
|
127
|
+
const container = $('acChatMessages');
|
|
128
|
+
if (!msgs.length) {
|
|
129
|
+
container.innerHTML = '<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无记录</div>';
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// 按 time 正序显示
|
|
133
|
+
const sorted = [...msgs].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
|
|
134
|
+
container.innerHTML = sorted.map(m => {
|
|
135
|
+
const isFrom = m.from_agent === fromAgent;
|
|
136
|
+
const sender = isFrom ? esc(m.from_name || m.from_agent) : esc(m.to_name || m.to_agent);
|
|
137
|
+
const bubbleBg = isFrom ? 'var(--accent-light)' : 'var(--bg3)';
|
|
138
|
+
const align = isFrom ? 'flex-end' : 'flex-start';
|
|
139
|
+
const labelColor = isFrom ? 'var(--accent)' : 'var(--text3)';
|
|
140
|
+
const content = esc(m.content || '').replace(/\n/g, '<br>');
|
|
141
|
+
const time = formatTime(m.timestamp);
|
|
142
|
+
return `<div style="display:flex;flex-direction:column;align-items:${align};margin-bottom:12px">
|
|
143
|
+
<div style="font-size:11px;color:${labelColor};margin-bottom:3px;padding:0 4px">${sender} · ${time}</div>
|
|
144
|
+
<div style="max-width:75%;padding:10px 14px;background:${bubbleBg};border-radius:12px;font-size:13px;line-height:1.6;color:var(--text);word-break:break-word">${content}</div>
|
|
145
|
+
</div>`;
|
|
146
|
+
}).join('');
|
|
147
|
+
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
|
+
};
|
|
156
|
+
window._acSelectPair = (fa, ta, fn, tn) => { renderMessages(fa, ta, fn, tn); };
|
|
157
|
+
window._acClearAll = async () => {
|
|
158
|
+
if (!confirm('确定要清空所有私聊记录吗?此操作不可恢复。')) return;
|
|
159
|
+
const q = new URLSearchParams();
|
|
160
|
+
if (currentFilter.group_id) q.set('group_id', currentFilter.group_id);
|
|
161
|
+
try {
|
|
162
|
+
const data = await api('/api/agent-chat/messages?' + q.toString(), { method: 'DELETE' });
|
|
163
|
+
toast('已清空 ' + (data.deleted || 0) + ' 条记录');
|
|
164
|
+
await renderPairs();
|
|
165
|
+
$('acChatMessages').innerHTML = '<div style="text-align:center;color:var(--text3);padding:60px;font-size:13px">暂无聊天记录</div>';
|
|
166
|
+
$('acChatHeader').textContent = '选择左侧聊天对象查看详情';
|
|
167
|
+
} catch (e) { toast('清空失败: ' + (e.message || e), 'error'); }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// 初始加载
|
|
171
|
+
await renderPairs();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
|
175
|
+
window._adminRenderers['agentchat'] = renderAgentChat;
|
package/web/ui/index.html
CHANGED
|
@@ -327,6 +327,7 @@ tr:hover{background:var(--surface2)}
|
|
|
327
327
|
<div class="nav-item" data-tooltip="聊天平台" onclick="showPage('platforms')"><span class="icon">🌐</span><span class="icon-text">聊天平台</span></div>
|
|
328
328
|
<div class="nav-item" data-tooltip="组织管理" onclick="showPage('organization')"><span class="icon">🏢</span><span class="icon-text">组织管理</span></div>
|
|
329
329
|
<div class="nav-item" data-tooltip="部门管理" onclick="showPage('departments')"><span class="icon">🏛</span><span class="icon-text">部门管理</span></div>
|
|
330
|
+
<div class="nav-item" data-tooltip="Agent私聊" onclick="showPage('agentchat')"><span class="icon">🔒</span><span class="icon-text">Agent私聊</span></div>
|
|
330
331
|
<div class="nav-item" data-tooltip="会话管理" onclick="showPage('sessions')"><span class="icon">💬</span><span class="icon-text">会话管理</span></div>
|
|
331
332
|
<div class="nav-item" data-tooltip="记忆管理" onclick="showPage('memory')"><span class="icon">🧠</span><span class="icon-text">记忆管理</span></div>
|
|
332
333
|
<div class="nav-item" data-tooltip="权限管理" onclick="showPage('permissions')"><span class="icon">🔑</span><span class="icon-text">权限管理</span></div>
|
|
@@ -368,6 +369,7 @@ tr:hover{background:var(--surface2)}
|
|
|
368
369
|
<script src="admin/admin-logs.js"></script>
|
|
369
370
|
<script src="admin/admin-tasks.js"></script>
|
|
370
371
|
<script src="admin/admin-org.js"></script>
|
|
372
|
+
<script src="admin/admin-agentchat.js"></script>
|
|
371
373
|
<script src="admin/admin-system.js"></script>
|
|
372
374
|
</body>
|
|
373
375
|
</html>
|