myagent-ai 1.23.28 → 1.23.29
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/main_agent.py +4 -0
- package/core/tool_dispatcher.py +25 -0
- package/package.json +1 -1
- package/scripts/cli.py +32 -0
- package/web/api_server.py +112 -17
- package/web/ui/chat/chat.css +56 -0
- package/web/ui/chat/chat_main.js +20 -0
- package/web/ui/chat/groupchat.js +152 -1
package/agents/main_agent.py
CHANGED
|
@@ -134,6 +134,9 @@ class MainAgent(BaseAgent):
|
|
|
134
134
|
【记忆】
|
|
135
135
|
- 搜索记忆: myagent-ai memory [--keyword 关键词] [--limit N]
|
|
136
136
|
|
|
137
|
+
【Agent间通信】
|
|
138
|
+
- 向其他Agent发送消息: myagent-ai chat --agent <Agent路径> --message "消息内容"
|
|
139
|
+
|
|
137
140
|
【媒体播放】
|
|
138
141
|
- 播放音频: myagent-ai playaudio --url 音频URL [--title 标题] 或 myagent-ai playaudio --file 本地路径
|
|
139
142
|
- 播放视频: myagent-ai playvideo --url 视频URL [--title 标题] 或 myagent-ai playvideo --file 本地路径
|
|
@@ -149,6 +152,7 @@ class MainAgent(BaseAgent):
|
|
|
149
152
|
<tool><toolname>command</toolname><parms>{"command": "myagent-ai ppt-create -s '{JSON幻灯片}'"}</parms><timeout>30</timeout></tool>
|
|
150
153
|
<tool><toolname>command</toolname><parms>{"command": "myagent-ai playaudio --url https://music.163.com/song?id=123 --title 歌曲名"}</parms><timeout>10</timeout></tool>
|
|
151
154
|
<tool><toolname>command</toolname><parms>{"command": "myagent-ai playvideo --url https://www.bilibili.com/video/BV123 --title 视频名"}</parms><timeout>10</timeout></tool>
|
|
155
|
+
<tool><toolname>command</toolname><parms>{"command": "myagent-ai chat --agent default/coder --message \"请帮我分析这段代码的时间复杂度\""}</parms><timeout>10</timeout></tool>
|
|
152
156
|
|
|
153
157
|
多个命令可用 && 连接一次执行(强烈推荐,减少LLM回调次数):
|
|
154
158
|
<tool><toolname>command</toolname><parms>{"command": "myagent-ai search xxx && myagent-ai read-url https://..."}</parms><timeout>30</timeout></tool>
|
package/core/tool_dispatcher.py
CHANGED
|
@@ -246,6 +246,31 @@ class ToolDispatcher:
|
|
|
246
246
|
if not media_result.get("success"):
|
|
247
247
|
result["output"] += f"\n[视频播放失败: {media_result.get('error', '')}]"
|
|
248
248
|
|
|
249
|
+
# [v1.23.29] 检测 __CHAT_AGENT__ 标记 — CLI chat 命令输出此标记
|
|
250
|
+
# 格式: __CHAT_AGENT__agent_path|agent_name|message__END__
|
|
251
|
+
chat_markers = _re.findall(r'__CHAT_AGENT__(.+?)\|(.+?)\|(.+?)__END__', clean_output)
|
|
252
|
+
if chat_markers:
|
|
253
|
+
clean_output = _re.sub(r'__CHAT_AGENT__.+?__END__\n?', '', clean_output).strip()
|
|
254
|
+
result["output"] = clean_output
|
|
255
|
+
for chat_agent_path, chat_agent_name, chat_msg in chat_markers:
|
|
256
|
+
# Store as a chat agent event in sent_files for persistence
|
|
257
|
+
if sent_files is not None:
|
|
258
|
+
sent_files.append({
|
|
259
|
+
"_type": "chat_agent",
|
|
260
|
+
"target_agent": chat_agent_path.strip(),
|
|
261
|
+
"target_name": chat_agent_name.strip(),
|
|
262
|
+
"message": chat_msg.strip(),
|
|
263
|
+
})
|
|
264
|
+
# Emit SSE event for frontend display
|
|
265
|
+
try:
|
|
266
|
+
await self._emit_sse("v2_chat_agent", {
|
|
267
|
+
"target_agent": chat_agent_path.strip(),
|
|
268
|
+
"target_name": chat_agent_name.strip(),
|
|
269
|
+
"message": chat_msg.strip(),
|
|
270
|
+
}, stream_callback)
|
|
271
|
+
except Exception:
|
|
272
|
+
pass
|
|
273
|
+
|
|
249
274
|
return result
|
|
250
275
|
|
|
251
276
|
async def _exec_recall_memory(self, params: Dict, task_id: str) -> Dict:
|
package/package.json
CHANGED
package/scripts/cli.py
CHANGED
|
@@ -18,6 +18,7 @@ scripts/cli.py - MyAgent CLI 工具集
|
|
|
18
18
|
GUI: screenshot, mouse-click, mouse-drag, type-text, hotkey,
|
|
19
19
|
window-list, window-focus
|
|
20
20
|
记忆: memory
|
|
21
|
+
通信: chat
|
|
21
22
|
帮助: help, -h, --help
|
|
22
23
|
"""
|
|
23
24
|
from __future__ import annotations
|
|
@@ -135,6 +136,8 @@ async def _run():
|
|
|
135
136
|
# 媒体播放
|
|
136
137
|
"playaudio": cmd_playaudio,
|
|
137
138
|
"playvideo": cmd_playvideo,
|
|
139
|
+
# 通信
|
|
140
|
+
"chat": cmd_chat,
|
|
138
141
|
# 帮助
|
|
139
142
|
"help": cmd_help,
|
|
140
143
|
"-h": cmd_help,
|
|
@@ -990,6 +993,32 @@ async def cmd_playvideo(args):
|
|
|
990
993
|
print(f"__EMBED_VIDEO__{source}|{title}__END__")
|
|
991
994
|
|
|
992
995
|
|
|
996
|
+
# =============================================================================
|
|
997
|
+
# 通信命令
|
|
998
|
+
# =============================================================================
|
|
999
|
+
|
|
1000
|
+
async def cmd_chat(args):
|
|
1001
|
+
"""Agent间通信 — 向群内其他Agent发送消息"""
|
|
1002
|
+
import argparse
|
|
1003
|
+
p = argparse.ArgumentParser(prog="myagent-ai chat", description="向群内其他Agent发送消息,消息会出现在群聊记录中")
|
|
1004
|
+
p.add_argument("--agent", "-a", required=True, help="目标Agent路径,如 default/coder")
|
|
1005
|
+
p.add_argument("--message", "-m", required=True, help="要发送的消息内容")
|
|
1006
|
+
p.add_argument("--group", "-g", help="群ID(可选,默认使用最近活跃的群)")
|
|
1007
|
+
a = p.parse_args(args)
|
|
1008
|
+
|
|
1009
|
+
# Find agent config
|
|
1010
|
+
agent_dir = Path(__file__).parent.parent / "data" / "agents" / a.agent
|
|
1011
|
+
cfg_file = agent_dir / "config.json"
|
|
1012
|
+
if not cfg_file.exists():
|
|
1013
|
+
print(f"错误: Agent 不存在: {a.agent}", file=sys.stderr)
|
|
1014
|
+
sys.exit(1)
|
|
1015
|
+
cfg = json.loads(cfg_file.read_text(encoding="utf-8"))
|
|
1016
|
+
agent_name = cfg.get("name", a.agent)
|
|
1017
|
+
|
|
1018
|
+
# Output chat marker (similar to __SEND_FILE__ pattern)
|
|
1019
|
+
print(f"__CHAT_AGENT__{a.agent}|{agent_name}|{a.message}__END__")
|
|
1020
|
+
|
|
1021
|
+
|
|
993
1022
|
# =============================================================================
|
|
994
1023
|
# 帮助命令
|
|
995
1024
|
# =============================================================================
|
|
@@ -1058,6 +1087,9 @@ GUI (仅 Windows/macOS):
|
|
|
1058
1087
|
记忆:
|
|
1059
1088
|
myagent-ai memory [--keyword 关键词] [--limit N] 搜索历史记忆
|
|
1060
1089
|
|
|
1090
|
+
通信:
|
|
1091
|
+
myagent-ai chat --agent <路径> --message "消息" 向其他Agent发送消息
|
|
1092
|
+
|
|
1061
1093
|
媒体播放:
|
|
1062
1094
|
myagent-ai playaudio --url <URL> [--title] 播放在线音频
|
|
1063
1095
|
myagent-ai playaudio --file <路径> [--title] 播放本地音频
|
package/web/api_server.py
CHANGED
|
@@ -7630,6 +7630,9 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7630
7630
|
|
|
7631
7631
|
async def handle_send_group_message(self, request):
|
|
7632
7632
|
"""POST /api/groups/{gid}/messages - 发送群消息(广播到所有成员agent)"""
|
|
7633
|
+
import re as _re
|
|
7634
|
+
import time as _time
|
|
7635
|
+
|
|
7633
7636
|
gid = request.match_info["gid"]
|
|
7634
7637
|
data = await request.json()
|
|
7635
7638
|
content = data.get("message", "").strip()
|
|
@@ -7666,12 +7669,39 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7666
7669
|
metadata={"source": "group_chat", "group_name": group.name},
|
|
7667
7670
|
)
|
|
7668
7671
|
|
|
7669
|
-
# 3.
|
|
7672
|
+
# 3. [v1.23.29] 解析 @提及
|
|
7673
|
+
at_pattern = _re.compile(r'@(\S+)')
|
|
7674
|
+
at_targets = at_pattern.findall(content)
|
|
7675
|
+
mentioned_agents = set()
|
|
7676
|
+
mentioned_all = False
|
|
7677
|
+
|
|
7678
|
+
for target in at_targets:
|
|
7679
|
+
target_lower = target.lower()
|
|
7680
|
+
if target_lower in ('all', '所有人', '大家'):
|
|
7681
|
+
mentioned_all = True
|
|
7682
|
+
break
|
|
7683
|
+
for m in group.members:
|
|
7684
|
+
agent_cfg = self._read_agent_config(m.agent_path)
|
|
7685
|
+
agent_name = agent_cfg.get("name", "") if agent_cfg else ""
|
|
7686
|
+
if (target_lower == m.agent_path.lower()
|
|
7687
|
+
or target_lower == agent_name.lower()
|
|
7688
|
+
or (m.nickname and target_lower == m.nickname.lower())):
|
|
7689
|
+
mentioned_agents.add(m.agent_path)
|
|
7690
|
+
|
|
7691
|
+
# 4. 广播到所有非禁言成员agent,并行处理
|
|
7670
7692
|
active_members = [m for m in group.members if not m.muted]
|
|
7671
7693
|
|
|
7694
|
+
# [v1.23.29] 确定哪些 Agent 应该回复
|
|
7695
|
+
if mentioned_all:
|
|
7696
|
+
target_members = active_members
|
|
7697
|
+
elif mentioned_agents:
|
|
7698
|
+
target_members = [m for m in active_members if m.agent_path in mentioned_agents]
|
|
7699
|
+
else:
|
|
7700
|
+
target_members = active_members # 无@=广播给所有(向后兼容)
|
|
7701
|
+
|
|
7672
7702
|
import asyncio
|
|
7673
7703
|
# Build a member_order map for deterministic sorting after gather
|
|
7674
|
-
member_order = {m.agent_path: i for i, m in enumerate(
|
|
7704
|
+
member_order = {m.agent_path: i for i, m in enumerate(target_members)}
|
|
7675
7705
|
|
|
7676
7706
|
async def process_agent_member(member):
|
|
7677
7707
|
"""Process a single member's response (DO NOT save messages here)"""
|
|
@@ -7704,19 +7734,64 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7704
7734
|
my_role = {"owner": "群主", "admin": "管理员"}.get(member.role, "成员")
|
|
7705
7735
|
my_desc = agent_cfg.get("description", "") if agent_cfg else ""
|
|
7706
7736
|
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
f"
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7737
|
+
# [v1.23.29] 构建近期群聊记录
|
|
7738
|
+
recent_msgs = mgr.get_messages(gid, limit=10)
|
|
7739
|
+
history_lines = []
|
|
7740
|
+
for rm in recent_msgs:
|
|
7741
|
+
rm_name = rm.sender_name or ("用户" if rm.sender == "user" else rm.agent_path)
|
|
7742
|
+
rm_time_str = _time.strftime("%H:%M", _time.localtime(rm.timestamp)) if rm.timestamp else ""
|
|
7743
|
+
history_lines.append(f"[{rm_time_str}] {rm_name}: {rm.content[:200]}")
|
|
7744
|
+
chat_history = "\n".join(reversed(history_lines))
|
|
7745
|
+
|
|
7746
|
+
# [v1.23.29] 确定 @提及信息
|
|
7747
|
+
is_mentioned = mentioned_all or member.agent_path in mentioned_agents
|
|
7748
|
+
at_info = ""
|
|
7749
|
+
if mentioned_all:
|
|
7750
|
+
at_info = "- 本次消息使用了 @所有人,请务必回复。\n"
|
|
7751
|
+
elif mentioned_agents:
|
|
7752
|
+
if is_mentioned:
|
|
7753
|
+
at_info = f"- 本次消息使用了 @{my_name},请务必回复。\n"
|
|
7754
|
+
else:
|
|
7755
|
+
at_info = "- 本次消息使用了 @其他成员,与你无关,请不要回复。\n"
|
|
7756
|
+
else:
|
|
7757
|
+
at_info = "- 本次消息没有 @任何人(广播消息),你可以根据内容决定是否回复。\n"
|
|
7758
|
+
|
|
7759
|
+
group_context = f"""## 群聊上下文
|
|
7760
|
+
|
|
7761
|
+
你正在参与一个群聊,以下是群的详细信息:
|
|
7762
|
+
|
|
7763
|
+
### 群信息
|
|
7764
|
+
- 群名称: {group.name}
|
|
7765
|
+
- 群描述: {group.description or '无'}
|
|
7766
|
+
- 当前发言者: 用户(群聊中的真人用户)
|
|
7767
|
+
|
|
7768
|
+
### 你的身份
|
|
7769
|
+
- 名称: {my_name}
|
|
7770
|
+
- 路径: {agent_path}
|
|
7771
|
+
- 角色: {my_role}{"(已禁言,但仍可接收消息)" if member.muted else ""}
|
|
7772
|
+
|
|
7773
|
+
### 群成员列表(共{len(group.members)}人)
|
|
7774
|
+
{chr(10).join(member_lines)}
|
|
7775
|
+
|
|
7776
|
+
### 沟通规则(重要)
|
|
7777
|
+
1. **@提及机制**: 用户发送消息时可以使用 @名称 来指定回复者
|
|
7778
|
+
- @某个Agent: 只有被@的Agent需要回复
|
|
7779
|
+
- @所有人 / @all: 所有成员都需要回复
|
|
7780
|
+
- 不@任何人(广播): 你自行判断是否需要回复
|
|
7781
|
+
2. **跨Agent沟通**: 你可以使用 `myagent-ai chat --agent <路径> --message "消息"` 命令向群内其他Agent发送消息。消息会出现在群聊中,对方下次被@时会看到。
|
|
7782
|
+
3. **回复方式**:
|
|
7783
|
+
- 直接回复用户的消息
|
|
7784
|
+
- 需要其他Agent协助时,使用 chat 命令沟通,不要假装代替其他Agent回答
|
|
7785
|
+
- 如需协调多个Agent,建议用户使用 @所有人
|
|
7786
|
+
{at_info}
|
|
7787
|
+
### 近期群聊记录(最近10条)
|
|
7788
|
+
{chat_history if chat_history else '(暂无历史消息)'}
|
|
7789
|
+
|
|
7790
|
+
### 重要提醒
|
|
7791
|
+
- 你只代表你自己发言,使用第一人称
|
|
7792
|
+
- 不要假装是其他Agent或代替其他Agent回答
|
|
7793
|
+
- 如果问题超出你的能力范围,建议用户@相关专家Agent
|
|
7794
|
+
- 如果需要其他Agent的信息,使用 `myagent-ai chat` 命令沟通"""
|
|
7720
7795
|
|
|
7721
7796
|
# 将群聊上下文追加到 agent_system_prompt
|
|
7722
7797
|
if agent_system_prompt:
|
|
@@ -7766,8 +7841,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7766
7841
|
"response": f"处理失败: {str(e)}",
|
|
7767
7842
|
}
|
|
7768
7843
|
|
|
7769
|
-
#
|
|
7770
|
-
tasks = [process_agent_member(m) for m in
|
|
7844
|
+
# 并行调用目标成员agent
|
|
7845
|
+
tasks = [process_agent_member(m) for m in target_members]
|
|
7771
7846
|
try:
|
|
7772
7847
|
gather_results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
7773
7848
|
except Exception as e:
|
|
@@ -7793,6 +7868,26 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7793
7868
|
key=lambda r: member_order.get(r.get("agent_path", ""), 999999)
|
|
7794
7869
|
)
|
|
7795
7870
|
|
|
7871
|
+
# [v1.23.29] 从 Agent 响应中提取 __CHAT_AGENT__ 标记,清理并保存跨Agent通信消息
|
|
7872
|
+
_chat_msg_pattern = _re.compile(r'__CHAT_AGENT__(.+?)\|(.+?)\|(.+?)__END__')
|
|
7873
|
+
for resp in final_responses:
|
|
7874
|
+
resp_text = resp.get("response", "")
|
|
7875
|
+
chat_matches = _chat_msg_pattern.findall(resp_text)
|
|
7876
|
+
if chat_matches:
|
|
7877
|
+
# 清理标记文本
|
|
7878
|
+
resp["response"] = _chat_msg_pattern.sub('', resp_text).strip()
|
|
7879
|
+
# 保存跨Agent通信消息到群聊
|
|
7880
|
+
for c_path, c_name, c_msg in chat_matches:
|
|
7881
|
+
chat_sys_msg = GroupMessage(
|
|
7882
|
+
group_id=gid,
|
|
7883
|
+
sender=resp.get("agent_path", ""),
|
|
7884
|
+
sender_name=resp.get("name", ""),
|
|
7885
|
+
sender_avatar=resp.get("avatar", "🤖"),
|
|
7886
|
+
content=f"💬 对 {c_name.strip()} 说: {c_msg.strip()}",
|
|
7887
|
+
msg_type="text",
|
|
7888
|
+
)
|
|
7889
|
+
mgr.add_message(chat_sys_msg)
|
|
7890
|
+
|
|
7796
7891
|
# Save agent messages sequentially in sorted order
|
|
7797
7892
|
for resp in final_responses:
|
|
7798
7893
|
agent_msg = GroupMessage(
|
package/web/ui/chat/chat.css
CHANGED
|
@@ -2671,3 +2671,59 @@ body.popout-mode #popoutBtn{display:none !important}
|
|
|
2671
2671
|
.md-table-wrapper td{padding:7px 12px;border:1px solid var(--border-light);color:var(--text);vertical-align:top;word-break:break-word}
|
|
2672
2672
|
.md-table-wrapper tr:nth-child(even){background:var(--bg3)}
|
|
2673
2673
|
.md-table-wrapper tr:hover{background:var(--accent-light)}
|
|
2674
|
+
|
|
2675
|
+
/* ══════════════════════════════════════════════════════
|
|
2676
|
+
── @ Mention Panel (v1.23.29) ──
|
|
2677
|
+
══════════════════════════════════════════════════════ */
|
|
2678
|
+
#atMentionPanel{
|
|
2679
|
+
background:var(--bg-secondary,var(--bg2));
|
|
2680
|
+
border:1px solid var(--border-color,var(--border));
|
|
2681
|
+
border-radius:8px;
|
|
2682
|
+
box-shadow:0 4px 16px rgba(0,0,0,.4);
|
|
2683
|
+
min-width:200px;
|
|
2684
|
+
max-width:320px;
|
|
2685
|
+
font-size:13px;
|
|
2686
|
+
padding:4px 0;
|
|
2687
|
+
}
|
|
2688
|
+
.at-mention-item{
|
|
2689
|
+
display:flex;
|
|
2690
|
+
align-items:center;
|
|
2691
|
+
gap:8px;
|
|
2692
|
+
padding:8px 12px;
|
|
2693
|
+
cursor:pointer;
|
|
2694
|
+
transition:background .15s;
|
|
2695
|
+
}
|
|
2696
|
+
.at-mention-item:hover,.at-mention-item.at-item-active{
|
|
2697
|
+
background:var(--bg-hover,rgba(255,255,255,.08));
|
|
2698
|
+
}
|
|
2699
|
+
.at-emoji{
|
|
2700
|
+
font-size:16px;
|
|
2701
|
+
width:24px;
|
|
2702
|
+
text-align:center;
|
|
2703
|
+
flex-shrink:0;
|
|
2704
|
+
}
|
|
2705
|
+
.at-name{
|
|
2706
|
+
font-weight:500;
|
|
2707
|
+
color:var(--text-primary,var(--text));
|
|
2708
|
+
}
|
|
2709
|
+
.at-path{
|
|
2710
|
+
font-size:11px;
|
|
2711
|
+
color:var(--text-secondary,var(--text3));
|
|
2712
|
+
margin-left:auto;
|
|
2713
|
+
}
|
|
2714
|
+
.at-all-btn{
|
|
2715
|
+
background:transparent;
|
|
2716
|
+
border:1px solid var(--accent);
|
|
2717
|
+
color:var(--accent);
|
|
2718
|
+
border-radius:6px;
|
|
2719
|
+
padding:4px 10px;
|
|
2720
|
+
font-size:12px;
|
|
2721
|
+
cursor:pointer;
|
|
2722
|
+
white-space:nowrap;
|
|
2723
|
+
transition:all .2s;
|
|
2724
|
+
flex-shrink:0;
|
|
2725
|
+
}
|
|
2726
|
+
.at-all-btn:hover{
|
|
2727
|
+
background:var(--accent);
|
|
2728
|
+
color:#fff;
|
|
2729
|
+
}
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -391,6 +391,26 @@ async function initChat() {
|
|
|
391
391
|
document.getElementById('sendBtn').disabled = !this.value.trim();
|
|
392
392
|
saveDraft();
|
|
393
393
|
});
|
|
394
|
+
// [v1.23.29] Initialize @ mention panel for group chat
|
|
395
|
+
if (typeof initAtMention === 'function') initAtMention();
|
|
396
|
+
// [v1.23.29] Create @all button (hidden by default, shown in group chat)
|
|
397
|
+
var sendBtn = document.getElementById('sendBtn');
|
|
398
|
+
if (sendBtn && sendBtn.parentElement && !document.getElementById('atAllBtn')) {
|
|
399
|
+
var atAllBtn = document.createElement('button');
|
|
400
|
+
atAllBtn.id = 'atAllBtn';
|
|
401
|
+
atAllBtn.className = 'at-all-btn';
|
|
402
|
+
atAllBtn.textContent = '@所有人';
|
|
403
|
+
atAllBtn.style.display = 'none';
|
|
404
|
+
atAllBtn.onclick = function() {
|
|
405
|
+
var input = document.getElementById('userInput');
|
|
406
|
+
if (input) {
|
|
407
|
+
input.value = input.value + '@所有人 ';
|
|
408
|
+
input.focus();
|
|
409
|
+
document.getElementById('sendBtn').disabled = false;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
sendBtn.parentElement.insertBefore(atAllBtn, sendBtn);
|
|
413
|
+
}
|
|
394
414
|
// Load task plan if in exec mode (panel stays hidden until tasks exist)
|
|
395
415
|
if (state.chatMode === 'exec') {
|
|
396
416
|
// Don't show task panel by default - it will appear when the LLM creates a task list
|
package/web/ui/chat/groupchat.js
CHANGED
|
@@ -154,6 +154,10 @@ async function selectGroup(gid) {
|
|
|
154
154
|
document.getElementById('activeAgentBadge').style.display = 'none';
|
|
155
155
|
document.getElementById('groupBackBtn').style.display = '';
|
|
156
156
|
document.getElementById('groupSettingsBtn').style.display = '';
|
|
157
|
+
|
|
158
|
+
// Show @all button for group chat
|
|
159
|
+
var atAllBtn = document.getElementById('atAllBtn');
|
|
160
|
+
if (atAllBtn) atAllBtn.style.display = '';
|
|
157
161
|
var _ccb = document.getElementById('clearChatBtn'); if (_ccb) _ccb.style.display = 'none';
|
|
158
162
|
var dot = document.querySelector('.main-title .dot');
|
|
159
163
|
if (dot) dot.style.display = 'none';
|
|
@@ -199,6 +203,8 @@ function exitGroupChat() {
|
|
|
199
203
|
document.getElementById('activeAgentBadge').style.display = '';
|
|
200
204
|
document.getElementById('groupBackBtn').style.display = 'none';
|
|
201
205
|
document.getElementById('groupSettingsBtn').style.display = 'none';
|
|
206
|
+
var atAllBtn = document.getElementById('atAllBtn');
|
|
207
|
+
if (atAllBtn) atAllBtn.style.display = 'none';
|
|
202
208
|
var _ccb2 = document.getElementById('clearChatBtn'); if (_ccb2) _ccb2.style.display = '';
|
|
203
209
|
var dot = document.querySelector('.main-title .dot');
|
|
204
210
|
if (dot) dot.style.display = '';
|
|
@@ -239,7 +245,8 @@ function _renderGroupMessagesInner() {
|
|
|
239
245
|
html = '<div class="welcome-card">'
|
|
240
246
|
+ '<h2><span class="emoji">' + escapeHtml((group && (group.avatar_emoji || group.emoji)) || '👥') + '</span>'
|
|
241
247
|
+ ' <span>群聊: ' + escapeHtml((group && group.name) || '') + '</span></h2>'
|
|
242
|
-
+ '<p class="subtitle"
|
|
248
|
+
+ '<p class="subtitle">输入 @ 选择要回复的 Agent,或直接发送消息广播给所有人</p>'
|
|
249
|
+
+ '<p class="subtitle" style="font-size:12px;color:var(--text-secondary)">支持 @Agent名 指定回复 | @所有人 全部回复 | myagent-ai chat 跨Agent沟通</p>'
|
|
243
250
|
+ '</div>';
|
|
244
251
|
container.innerHTML = html;
|
|
245
252
|
return;
|
|
@@ -593,6 +600,150 @@ async function confirmAddMembers() {
|
|
|
593
600
|
}
|
|
594
601
|
}
|
|
595
602
|
|
|
603
|
+
// ══════════════════════════════════════════════════════
|
|
604
|
+
// ── @ Mention Panel (@提及面板) ──
|
|
605
|
+
// ══════════════════════════════════════════════════════
|
|
606
|
+
|
|
607
|
+
var _atPanelVisible = false;
|
|
608
|
+
var _atPanelFiltered = [];
|
|
609
|
+
|
|
610
|
+
function initAtMention() {
|
|
611
|
+
var input = document.getElementById('userInput');
|
|
612
|
+
if (!input) return;
|
|
613
|
+
input.addEventListener('input', _onInputChangeForAt);
|
|
614
|
+
input.addEventListener('keydown', _onInputKeydownForAt);
|
|
615
|
+
// Close panel on click outside
|
|
616
|
+
document.addEventListener('click', function(e) {
|
|
617
|
+
if (!e.target.closest('#atMentionPanel') && !e.target.closest('#userInput')) {
|
|
618
|
+
_hideAtPanel();
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function _onInputChangeForAt(e) {
|
|
624
|
+
if (currentView !== 'group') { _hideAtPanel(); return; }
|
|
625
|
+
var val = e.target.value;
|
|
626
|
+
var pos = val.lastIndexOf('@');
|
|
627
|
+
if (pos < 0 || (pos > 0 && val[pos - 1] !== ' ' && val[pos - 1] !== '\n')) {
|
|
628
|
+
_hideAtPanel();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
var query = val.substring(pos + 1).toLowerCase().split(' ')[0];
|
|
632
|
+
if (query.length > 20) { _hideAtPanel(); return; }
|
|
633
|
+
|
|
634
|
+
var group = groups.find(function(g) { return g.id === currentGroupId; });
|
|
635
|
+
if (!group || !group.members) { _hideAtPanel(); return; }
|
|
636
|
+
|
|
637
|
+
_atPanelFiltered = [];
|
|
638
|
+
// Add "所有人" option
|
|
639
|
+
var allMatch = !query || 'all'.indexOf(query) >= 0 || '所有人'.indexOf(query) >= 0 || '大家'.indexOf(query) >= 0;
|
|
640
|
+
if (allMatch) {
|
|
641
|
+
_atPanelFiltered.push({name: '所有人', path: '@all', emoji: '📢', isAll: true});
|
|
642
|
+
}
|
|
643
|
+
// Add members
|
|
644
|
+
for (var i = 0; i < group.members.length; i++) {
|
|
645
|
+
var m = group.members[i];
|
|
646
|
+
var mc = (state.agentsFlat || []).find(function(a) { return a.path === m.agent_path; });
|
|
647
|
+
var mName = (mc && mc.name) || m.agent_path;
|
|
648
|
+
var mEmoji = (mc && mc.avatar_emoji) || '🤖';
|
|
649
|
+
if (!query || mName.toLowerCase().indexOf(query) >= 0 || m.agent_path.toLowerCase().indexOf(query) >= 0) {
|
|
650
|
+
_atPanelFiltered.push({name: mName, path: m.agent_path, emoji: mEmoji});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (_atPanelFiltered.length === 0) { _hideAtPanel(); return; }
|
|
655
|
+
_showAtPanel(pos);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function _showAtPanel(atPos) {
|
|
659
|
+
var existing = document.getElementById('atMentionPanel');
|
|
660
|
+
if (!existing) {
|
|
661
|
+
existing = document.createElement('div');
|
|
662
|
+
existing.id = 'atMentionPanel';
|
|
663
|
+
document.body.appendChild(existing);
|
|
664
|
+
}
|
|
665
|
+
var html = '';
|
|
666
|
+
for (var i = 0; i < _atPanelFiltered.length; i++) {
|
|
667
|
+
var item = _atPanelFiltered[i];
|
|
668
|
+
var cls = i === 0 ? ' at-item-active' : '';
|
|
669
|
+
html += '<div class="at-mention-item' + cls + '" data-index="' + i + '" data-name="' + escapeHtml(item.name) + '" data-path="' + escapeHtml(item.path) + '">'
|
|
670
|
+
+ '<span class="at-emoji">' + item.emoji + '</span>'
|
|
671
|
+
+ '<span class="at-name">' + escapeHtml(item.name) + '</span>'
|
|
672
|
+
+ (item.isAll ? '' : '<span class="at-path">' + escapeHtml(item.path) + '</span>')
|
|
673
|
+
+ '</div>';
|
|
674
|
+
}
|
|
675
|
+
existing.innerHTML = html;
|
|
676
|
+
existing.style.display = 'block';
|
|
677
|
+
existing._atIndex = 0;
|
|
678
|
+
|
|
679
|
+
// Position near input
|
|
680
|
+
var input = document.getElementById('userInput');
|
|
681
|
+
var rect = input.getBoundingClientRect();
|
|
682
|
+
existing.style.position = 'fixed';
|
|
683
|
+
existing.style.left = Math.max(8, rect.left) + 'px';
|
|
684
|
+
existing.style.bottom = (window.innerHeight - rect.top + 4) + 'px';
|
|
685
|
+
existing.style.maxHeight = '200px';
|
|
686
|
+
existing.style.overflowY = 'auto';
|
|
687
|
+
existing.style.zIndex = '99999';
|
|
688
|
+
|
|
689
|
+
// Click handlers
|
|
690
|
+
var items = existing.querySelectorAll('.at-mention-item');
|
|
691
|
+
items.forEach(function(el) {
|
|
692
|
+
el.addEventListener('mousedown', function(e) {
|
|
693
|
+
e.preventDefault();
|
|
694
|
+
_selectAtItem(parseInt(el.dataset.index));
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
_atPanelVisible = true;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function _hideAtPanel() {
|
|
701
|
+
var panel = document.getElementById('atMentionPanel');
|
|
702
|
+
if (panel) panel.style.display = 'none';
|
|
703
|
+
_atPanelVisible = false;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function _selectAtItem(index) {
|
|
707
|
+
var item = _atPanelFiltered[index];
|
|
708
|
+
if (!item) return;
|
|
709
|
+
var input = document.getElementById('userInput');
|
|
710
|
+
var val = input.value;
|
|
711
|
+
var pos = val.lastIndexOf('@');
|
|
712
|
+
var mention = item.isAll ? '@所有人 ' : '@' + item.name + ' ';
|
|
713
|
+
input.value = val.substring(0, pos) + mention + val.substring(pos + 1 + (val.substring(pos + 1).split(' ')[0].length));
|
|
714
|
+
input.focus();
|
|
715
|
+
_hideAtPanel();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function _onInputKeydownForAt(e) {
|
|
719
|
+
if (!_atPanelVisible || !_atPanelFiltered.length) return;
|
|
720
|
+
var panel = document.getElementById('atMentionPanel');
|
|
721
|
+
if (e.key === 'ArrowDown') {
|
|
722
|
+
e.preventDefault();
|
|
723
|
+
panel._atIndex = Math.min(panel._atIndex + 1, _atPanelFiltered.length - 1);
|
|
724
|
+
_highlightAtItem(panel);
|
|
725
|
+
} else if (e.key === 'ArrowUp') {
|
|
726
|
+
e.preventDefault();
|
|
727
|
+
panel._atIndex = Math.max(panel._atIndex - 1, 0);
|
|
728
|
+
_highlightAtItem(panel);
|
|
729
|
+
} else if (e.key === 'Enter' || e.key === 'Tab') {
|
|
730
|
+
e.preventDefault();
|
|
731
|
+
_selectAtItem(panel._atIndex);
|
|
732
|
+
} else if (e.key === 'Escape') {
|
|
733
|
+
_hideAtPanel();
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function _highlightAtItem(panel) {
|
|
738
|
+
var items = panel.querySelectorAll('.at-mention-item');
|
|
739
|
+
items.forEach(function(el, i) {
|
|
740
|
+
el.classList.toggle('at-item-active', i === panel._atIndex);
|
|
741
|
+
});
|
|
742
|
+
// Scroll into view
|
|
743
|
+
var active = panel.querySelector('.at-item-active');
|
|
744
|
+
if (active) active.scrollIntoView({block: 'nearest'});
|
|
745
|
+
}
|
|
746
|
+
|
|
596
747
|
// ══════════════════════════════════════════════════════
|
|
597
748
|
// ── Group Chat Send (群聊消息发送) ──
|
|
598
749
|
// ══════════════════════════════════════════════════════
|