myagent-ai 1.23.33 → 1.23.35
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 +3 -3
- package/core/output_parser.py +0 -7
- package/core/tool_dispatcher.py +5 -5
- package/package.json +1 -1
- package/web/api_server.py +22 -36
- package/web/ui/chat/chat.css +1 -0
- package/web/ui/chat/chat_main.js +25 -4
- package/web/ui/chat/flow_engine.js +6 -6
- package/web/ui/chat/groupchat.js +62 -2
package/agents/main_agent.py
CHANGED
|
@@ -48,7 +48,7 @@ class MainAgent(BaseAgent):
|
|
|
48
48
|
<mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
|
|
49
49
|
<usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
|
|
50
50
|
<response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接、表格等。内容上,针对用户问题,直接回应问题;针对任务,开始的时候,告诉用户,为完成任务,准备如何开展工作;执行过程中,根据工具调用结果,简单展示任务进展;任务完成后的详细最终总结。注意:这是给用户展示信息的最重要标签,尽量不要跟上次回复重复,执行过程展示内容尽量简洁,执行总结可以丰富一点。</reply><toolstocal>
|
|
51
|
-
<tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout
|
|
51
|
+
<tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>最多给它执行多久(秒),工具调用超过这个时限会立即回调大语言模型,方便调整工具使用</timeout></tool>
|
|
52
52
|
</toolstocal>
|
|
53
53
|
</response>
|
|
54
54
|
<task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记,排序按已完成在前。</task_plan>
|
|
@@ -63,7 +63,7 @@ class MainAgent(BaseAgent):
|
|
|
63
63
|
</output>
|
|
64
64
|
|
|
65
65
|
注意事项:
|
|
66
|
-
1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool"工具调用只要按顺序重复堆叠tool
|
|
66
|
+
1. toolstocal标签: 尽量一次性列出所有需执行工具调用的,多个"tool"工具调用只要按顺序重复堆叠tool标签即可,解析器会按顺序执行工具调用,最终全部执行完后,会连同所有结果,回调大语言模型。如果某个工具执行超过预估的时限,也会回调大模型,让大模型分析为什么超时,改用其他工具。要求每个工具调用尽快合并多个命令行。注意: docx/xlsx/ppt/pdf-create 命令执行后会自动发送文件给用户,不需要再拼接 send-file。仅在需要发送其他已存在的独立文件时才需要手动调用 send-file。多个独立查询或系统命令才需要用 && 拼接。
|
|
67
67
|
2. 上下文中的记忆系统说明
|
|
68
68
|
- <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
|
|
69
69
|
- <recall_memory>: 你在上一轮通过 <recall> 指定的记忆搜索结果。系统根据你提供的关键字和时间点搜索了 top5 相关记忆。
|
|
@@ -184,7 +184,7 @@ class MainAgent(BaseAgent):
|
|
|
184
184
|
<mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
|
|
185
185
|
<usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
|
|
186
186
|
<response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接、表格等。内容上,针对用户问题,直接回应问题;针对任务,开始的时候,告诉用户,为完成任务,准备如何开展工作;执行过程中,根据工具调用结果,简单展示任务进展;任务完成后的详细最终总结。注意:这是给用户展示信息的最重要标签,尽量不要跟上次回复重复,执行过程展示内容尽量简洁,执行总结可以丰富一点。</reply><toolstocal>
|
|
187
|
-
<tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout
|
|
187
|
+
<tool><beforecalltext>展示给用户的简单工具调用信息。格式:先使用"接下来、下一步、接着、现在、然后、最后、等下"等连接词➕调用"工具名"。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>最多给它执行多久(秒),工具调用超过这个时限会立即回调大语言模型,方便调整工具使用。</timeout></tool>
|
|
188
188
|
</toolstocal>
|
|
189
189
|
</response>
|
|
190
190
|
<task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记,排序按已完成在前。</task_plan>
|
package/core/output_parser.py
CHANGED
|
@@ -22,7 +22,6 @@ Expected XML schema produced by the LLM::
|
|
|
22
22
|
<toolname>工具名</toolname>
|
|
23
23
|
<parms>参数JSON或描述</parms>
|
|
24
24
|
<timeout>预估超时时限(秒)</timeout>
|
|
25
|
-
<callback>true/false</callback>
|
|
26
25
|
</tool>
|
|
27
26
|
</toolstocal>
|
|
28
27
|
</response>
|
|
@@ -62,7 +61,6 @@ logger = get_logger("myagent.output_parser")
|
|
|
62
61
|
# ---------------------------------------------------------------------------
|
|
63
62
|
|
|
64
63
|
_DEFAULT_TIMEOUT: int = 120
|
|
65
|
-
_DEFAULT_CALLBACK: bool = True
|
|
66
64
|
|
|
67
65
|
# All top-level tags we recognise inside <output>.
|
|
68
66
|
KNOWN_TOP_LEVEL_TAGS = [
|
|
@@ -89,7 +87,6 @@ TOOL_INNER_TAGS = [
|
|
|
89
87
|
"toolname",
|
|
90
88
|
"parms",
|
|
91
89
|
"timeout",
|
|
92
|
-
"callback",
|
|
93
90
|
]
|
|
94
91
|
|
|
95
92
|
# Inner tags inside <remember>.
|
|
@@ -590,10 +587,6 @@ def _parse_toolstocal(toolstocal_content: str, *, conservative: bool = False) ->
|
|
|
590
587
|
_extract_tag_content(block, "timeout", TOOL_INNER_TAGS, conservative=conservative),
|
|
591
588
|
_DEFAULT_TIMEOUT,
|
|
592
589
|
),
|
|
593
|
-
"callback": _parse_bool(
|
|
594
|
-
_extract_tag_content(block, "callback", TOOL_INNER_TAGS, conservative=conservative),
|
|
595
|
-
_DEFAULT_CALLBACK,
|
|
596
|
-
),
|
|
597
590
|
}
|
|
598
591
|
# Only add if toolname is present
|
|
599
592
|
if tool["toolname"]:
|
package/core/tool_dispatcher.py
CHANGED
|
@@ -300,16 +300,15 @@ class ToolDispatcher:
|
|
|
300
300
|
) -> Dict:
|
|
301
301
|
"""发送文件给用户 — 后端推送 v2_file SSE 事件 + 持久化到聊天记录"""
|
|
302
302
|
try:
|
|
303
|
-
from skills.file_send import
|
|
304
|
-
fskill = FileSendSkill()
|
|
303
|
+
from skills.file_send import UPLOADS_DIR
|
|
305
304
|
fpath = params.get("file_path", "")
|
|
306
305
|
fdesc = params.get("description", "")
|
|
307
306
|
if not fpath:
|
|
308
307
|
logger.warning(f"[{task_id}] file_send: 缺少 file_path 参数")
|
|
309
308
|
return {"success": False, "error": "缺少 file_path 参数,请提供要发送的文件路径"}
|
|
310
309
|
logger.info(f"[{task_id}] file_send: 发送文件 {fpath}")
|
|
311
|
-
|
|
312
|
-
# [v1.23.
|
|
310
|
+
|
|
311
|
+
# [v1.23.35] 先复制文件(不依赖 file_send.execute 的 SSE 发送)
|
|
313
312
|
from pathlib import Path as _P
|
|
314
313
|
import shutil, uuid as _uuid, time as _time
|
|
315
314
|
fpath_resolved = _P(fpath.strip().strip("'\"")).expanduser()
|
|
@@ -319,7 +318,8 @@ class ToolDispatcher:
|
|
|
319
318
|
return {"success": False, "error": f"不是文件: {fpath}"}
|
|
320
319
|
|
|
321
320
|
file_id = str(_uuid.uuid4())[:12]
|
|
322
|
-
|
|
321
|
+
# [v1.23.35] 直接使用模块级 UPLOADS_DIR,不依赖 FileSendSkill 实例属性
|
|
322
|
+
date_dir = UPLOADS_DIR / _time.strftime("%Y-%m")
|
|
323
323
|
date_dir.mkdir(parents=True, exist_ok=True)
|
|
324
324
|
stored_name = f"{file_id}_{fpath_resolved.name}"
|
|
325
325
|
stored_path = date_dir / stored_name
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -1810,12 +1810,9 @@ window.addEventListener('beforeunload', function() {{
|
|
|
1810
1810
|
agent_name = data.get("agent_name", "default") or "default"
|
|
1811
1811
|
# 支持 path 格式 (如 "coder/python-expert")
|
|
1812
1812
|
agent_path = data.get("agent_path", agent_name)
|
|
1813
|
-
|
|
1814
|
-
#
|
|
1815
|
-
|
|
1816
|
-
session_id = raw_session_id
|
|
1817
|
-
else:
|
|
1818
|
-
session_id = f"{agent_path}_{raw_session_id}"
|
|
1813
|
+
# [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
|
|
1814
|
+
# 新格式(sess_uuid)和旧格式(agent_path_web_xxx)均兼容
|
|
1815
|
+
session_id = data.get("session_id", "") or "web_default"
|
|
1819
1816
|
chat_mode = data.get("mode", "") # "exec" = 执行模式
|
|
1820
1817
|
escalated = data.get("escalated", False) # 临时提权到 local
|
|
1821
1818
|
|
|
@@ -1970,12 +1967,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
1970
1967
|
return web.Response(text="data: " + json.dumps({"error": "message is required"}) + "\n\n", content_type="text/event-stream")
|
|
1971
1968
|
|
|
1972
1969
|
agent_path = data.get("agent_path", data.get("agent_name", "default")) or "default"
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
if raw_session_id.startswith(f"{agent_path}_"):
|
|
1976
|
-
session_id = raw_session_id
|
|
1977
|
-
else:
|
|
1978
|
-
session_id = f"{agent_path}_{raw_session_id}"
|
|
1970
|
+
# [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
|
|
1971
|
+
session_id = data.get("session_id", "") or "web_default"
|
|
1979
1972
|
chat_mode = data.get("mode", "")
|
|
1980
1973
|
escalated = data.get("escalated", False)
|
|
1981
1974
|
voice_text = data.get("voice_text", "").strip() # 语音转文字原始文本(用于 usersays_correct)
|
|
@@ -2255,12 +2248,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
2255
2248
|
return web.json_response({"error": "message is required"}, status=400)
|
|
2256
2249
|
|
|
2257
2250
|
agent_path = data.get("agent_path", "default")
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
if raw_session_id.startswith(f"{agent_path}_"):
|
|
2261
|
-
session_id = raw_session_id
|
|
2262
|
-
else:
|
|
2263
|
-
session_id = f"{agent_path}_{raw_session_id}"
|
|
2251
|
+
# [v1.23.35] 直接使用前端传来的 session_id,不再拼接 agent_path 前缀
|
|
2252
|
+
session_id = data.get("session_id", "web_default")
|
|
2264
2253
|
choice = data.get("choice", "queue") # "continue" (插入后继续) 或 "queue" (排队)
|
|
2265
2254
|
|
|
2266
2255
|
# 检查会话是否正在运行
|
|
@@ -7807,8 +7796,10 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7807
7796
|
# 构建最终消息(用户原文不包含任何注入内容)
|
|
7808
7797
|
agent_content = content
|
|
7809
7798
|
|
|
7799
|
+
# [v1.23.34] 群聊场景直接调用 _try_model_chain_inner(绕过全局锁)
|
|
7800
|
+
# 全局锁 _model_chain_lock 在并行场景下会导致死锁/阻塞
|
|
7810
7801
|
if model_chain and self.core.llm:
|
|
7811
|
-
response = await self.
|
|
7802
|
+
response = await self._try_model_chain_inner(
|
|
7812
7803
|
model_chain, agent_content, session_id,
|
|
7813
7804
|
agent_path=agent_path, agent_system_prompt=agent_system_prompt,
|
|
7814
7805
|
)
|
|
@@ -7841,26 +7832,21 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7841
7832
|
"response": f"处理失败: {str(e)}",
|
|
7842
7833
|
}
|
|
7843
7834
|
|
|
7844
|
-
#
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
gather_results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
7848
|
-
except Exception as e:
|
|
7849
|
-
logger.error(f"群消息广播异常: {e}")
|
|
7850
|
-
tp.update_task_status(task_id, "failed", last_message=f"广播异常: {str(e)}")
|
|
7851
|
-
return web.json_response({"error": f"群消息广播异常: {str(e)}"}, status=500)
|
|
7852
|
-
|
|
7853
|
-
# 处理异常结果并按原始成员顺序排序
|
|
7835
|
+
# [v1.23.34] 串行调用目标成员agent(避免并行时 LLM 全局状态互相覆盖导致卡死)
|
|
7836
|
+
# 原因: _try_model_chain_inner 会修改 self.core.llm 的 provider/model/api_key 等全局属性
|
|
7837
|
+
# 并行执行会导致 Agent 之间互相覆盖模型配置,甚至死锁
|
|
7854
7838
|
raw_responses = []
|
|
7855
|
-
for
|
|
7856
|
-
|
|
7839
|
+
for member in target_members:
|
|
7840
|
+
try:
|
|
7841
|
+
result = await process_agent_member(member)
|
|
7842
|
+
raw_responses.append(result)
|
|
7843
|
+
except Exception as e:
|
|
7844
|
+
logger.error(f"群消息处理异常 ({member.agent_path}): {e}")
|
|
7857
7845
|
raw_responses.append({
|
|
7858
|
-
"ok": False, "agent_path":
|
|
7859
|
-
"name":
|
|
7860
|
-
"response": f"异常: {str(
|
|
7846
|
+
"ok": False, "agent_path": member.agent_path,
|
|
7847
|
+
"name": member.agent_path, "avatar": "❌",
|
|
7848
|
+
"response": f"异常: {str(e)}",
|
|
7861
7849
|
})
|
|
7862
|
-
else:
|
|
7863
|
-
raw_responses.append(r)
|
|
7864
7850
|
|
|
7865
7851
|
# Sort by original member order to ensure deterministic message ordering
|
|
7866
7852
|
final_responses = sorted(
|
package/web/ui/chat/chat.css
CHANGED
|
@@ -1797,6 +1797,7 @@ input,textarea,select{font:inherit}
|
|
|
1797
1797
|
border:1px solid var(--border-light);border-bottom-left-radius:4px;
|
|
1798
1798
|
border-left:3px solid var(--accent);
|
|
1799
1799
|
overflow-wrap:break-word;overflow:hidden;position:relative;
|
|
1800
|
+
user-select:text;-webkit-user-select:text;
|
|
1800
1801
|
}
|
|
1801
1802
|
.group-msg-bubble p{margin-bottom:8px}
|
|
1802
1803
|
.group-msg-bubble p:last-child{margin-bottom:0}
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -328,6 +328,14 @@ const StatePersistence = {
|
|
|
328
328
|
if (savedSessionId) {
|
|
329
329
|
state._pendingSessionRestore = savedSessionId;
|
|
330
330
|
}
|
|
331
|
+
// [v1.23.34] 恢复群聊状态
|
|
332
|
+
var savedView = StatePersistence.load('currentView', 'chat');
|
|
333
|
+
var savedGroupId = StatePersistence.load('currentGroupId', null);
|
|
334
|
+
if (savedView === 'group' && savedGroupId) {
|
|
335
|
+
currentView = 'group';
|
|
336
|
+
currentGroupId = savedGroupId;
|
|
337
|
+
state._pendingGroupRestore = savedGroupId;
|
|
338
|
+
}
|
|
331
339
|
},
|
|
332
340
|
/** 标记 setup 已完成(避免每次刷新弹出向导) */
|
|
333
341
|
markSetupDone() { StatePersistence.save('setupDone', true); },
|
|
@@ -445,8 +453,12 @@ async function initChat() {
|
|
|
445
453
|
selectAgent(resolved);
|
|
446
454
|
}
|
|
447
455
|
} else if (urlSession) {
|
|
448
|
-
// 只有 session 没有 agent,尝试从 session ID 推断 agent
|
|
449
|
-
|
|
456
|
+
// 只有 session 没有 agent,尝试从 session ID 推断 agent(兼容旧格式 agent_web_xxx)
|
|
457
|
+
var targetAgent = 'default';
|
|
458
|
+
if (urlSession.indexOf('_web_') >= 0) {
|
|
459
|
+
targetAgent = urlSession.split('_web_')[0] || 'default';
|
|
460
|
+
}
|
|
461
|
+
// 新格式 sess_xxx 无法推断 agent,回退 default
|
|
450
462
|
var target = targetAgent;
|
|
451
463
|
if (!findAgentByPath(target)) {
|
|
452
464
|
console.warn('Session-inferred agent not found, fallback to default:', target);
|
|
@@ -465,6 +477,14 @@ async function initChat() {
|
|
|
465
477
|
}
|
|
466
478
|
// 如果 agent 一致,loadSessions() 内部已通过 _pendingSessionRestore 自动处理了
|
|
467
479
|
}
|
|
480
|
+
|
|
481
|
+
// [v1.23.34] 恢复群聊视图(在所有初始化完成后)
|
|
482
|
+
if (state._pendingGroupRestore && typeof selectGroup === 'function') {
|
|
483
|
+
var _gid = state._pendingGroupRestore;
|
|
484
|
+
state._pendingGroupRestore = null;
|
|
485
|
+
// 等待 fetchGroups 完成(initChat 中已调用),延迟恢复
|
|
486
|
+
setTimeout(function() { selectGroup(_gid); }, 300);
|
|
487
|
+
}
|
|
468
488
|
}
|
|
469
489
|
|
|
470
490
|
// Run init: if DOMContentLoaded already fired (dynamic script load), run immediately
|
|
@@ -2062,7 +2082,8 @@ function formatSessionName(id) {
|
|
|
2062
2082
|
if (!id) return '新会话';
|
|
2063
2083
|
if (id.startsWith('web_')) return id.replace('web_', '').replace(/_/g, ' ');
|
|
2064
2084
|
if (id.startsWith('cli_')) return 'CLI: ' + id.replace('cli_', '');
|
|
2065
|
-
//
|
|
2085
|
+
// [v1.23.35] 新格式 sess_xxx 直接显示"新对话",由后端自动命名
|
|
2086
|
+
if (id.startsWith('sess_')) return '新对话';
|
|
2066
2087
|
return id.length > 20 ? id.slice(0, 20) + '...' : id;
|
|
2067
2088
|
}
|
|
2068
2089
|
|
|
@@ -3053,9 +3074,9 @@ window.buildMessageHtml = function(msg, idx, agent) {
|
|
|
3053
3074
|
taskPlanHtml +
|
|
3054
3075
|
finishReasonHtml +
|
|
3055
3076
|
imageAttachmentHtml +
|
|
3056
|
-
mediaEmbedHtml +
|
|
3057
3077
|
wcHtml +
|
|
3058
3078
|
bubbleHtml +
|
|
3079
|
+
mediaEmbedHtml +
|
|
3059
3080
|
fileAttachmentHtml +
|
|
3060
3081
|
streamingIndicator +
|
|
3061
3082
|
(msg.time ? '<div class="message-time">' + formatTime(msg.time) + '</div>' : '') +
|
|
@@ -692,16 +692,16 @@ function updateStreamingMessage(msgIdx) {
|
|
|
692
692
|
}
|
|
693
693
|
|
|
694
694
|
// ── [v1.23.19] 增量渲染在线媒体嵌入播放器(v2_media 事件) ──
|
|
695
|
-
// [v1.23.
|
|
695
|
+
// [v1.23.35] 媒体容器放在 bubbleWrapper 之后(对话底部),不再放在前面
|
|
696
696
|
if (msg._media && msg._media.length > 0) {
|
|
697
697
|
var existingMedia = contentArea.querySelectorAll(':scope > .msg-attachments-media');
|
|
698
698
|
var mediaContainer = existingMedia.length > 0 ? existingMedia[0] : null;
|
|
699
699
|
if (!mediaContainer) {
|
|
700
700
|
mediaContainer = document.createElement('div');
|
|
701
701
|
mediaContainer.className = 'msg-attachments msg-attachments-media';
|
|
702
|
-
// 插入到 bubbleWrapper
|
|
702
|
+
// [v1.23.35] 插入到 bubbleWrapper 之后(对话末尾)
|
|
703
703
|
if (bubbleWrapper && bubbleWrapper.parentNode) {
|
|
704
|
-
bubbleWrapper.parentNode.insertBefore(mediaContainer, bubbleWrapper);
|
|
704
|
+
bubbleWrapper.parentNode.insertBefore(mediaContainer, bubbleWrapper.nextSibling);
|
|
705
705
|
} else {
|
|
706
706
|
contentArea.appendChild(mediaContainer);
|
|
707
707
|
}
|
|
@@ -1592,7 +1592,7 @@ async function sendMessage(opts) {
|
|
|
1592
1592
|
return;
|
|
1593
1593
|
}
|
|
1594
1594
|
|
|
1595
|
-
// Create session if needed
|
|
1595
|
+
// [v1.23.35] Create session if needed — 使用 UUID 防止 ID 重复/乱串
|
|
1596
1596
|
let sessionId = state.activeSessionId;
|
|
1597
1597
|
if (!sessionId || sessionId === '__new__') {
|
|
1598
1598
|
// 防御:如果当前已有消息(说明有活跃会话但 ID 丢失),尝试复用已有会话
|
|
@@ -1601,8 +1601,8 @@ async function sendMessage(opts) {
|
|
|
1601
1601
|
state.activeSessionId = sessionId;
|
|
1602
1602
|
console.warn('[sendMessage] activeSessionId was empty but messages exist, reusing session:', sessionId);
|
|
1603
1603
|
} else {
|
|
1604
|
-
|
|
1605
|
-
sessionId =
|
|
1604
|
+
// 使用 UUID v4 保证全局唯一性,不再暴露 agent 名明文
|
|
1605
|
+
sessionId = 'sess_' + crypto.randomUUID().replace(/-/g, '').slice(0, 20);
|
|
1606
1606
|
state.activeSessionId = sessionId;
|
|
1607
1607
|
document.getElementById('headerTitle').textContent = formatSessionName(sessionId);
|
|
1608
1608
|
// ── 立即在左侧边栏添加新会话条目(不等后端返回) ──
|
package/web/ui/chat/groupchat.js
CHANGED
|
@@ -138,6 +138,11 @@ async function selectGroup(gid) {
|
|
|
138
138
|
}
|
|
139
139
|
currentView = 'group';
|
|
140
140
|
currentGroupId = gid;
|
|
141
|
+
// [v1.23.34] 持久化群聊状态,刷新页面后恢复
|
|
142
|
+
if (typeof StatePersistence !== 'undefined') {
|
|
143
|
+
StatePersistence.save('currentView', 'group');
|
|
144
|
+
StatePersistence.save('currentGroupId', gid);
|
|
145
|
+
}
|
|
141
146
|
|
|
142
147
|
// Update sidebar
|
|
143
148
|
renderSessions();
|
|
@@ -194,6 +199,11 @@ function exitGroupChat() {
|
|
|
194
199
|
currentView = 'chat';
|
|
195
200
|
currentGroupId = null;
|
|
196
201
|
groupMessages = [];
|
|
202
|
+
// [v1.23.34] 清除持久化的群聊状态
|
|
203
|
+
if (typeof StatePersistence !== 'undefined') {
|
|
204
|
+
StatePersistence.save('currentView', 'chat');
|
|
205
|
+
StatePersistence.remove('currentGroupId');
|
|
206
|
+
}
|
|
197
207
|
// 清空残留的聊天状态,避免切回个人聊天时显示群聊消息
|
|
198
208
|
state.messages = [];
|
|
199
209
|
state.activeSessionId = null;
|
|
@@ -267,8 +277,10 @@ function _renderGroupMessagesInner() {
|
|
|
267
277
|
var agentEmoji = msg.agent_emoji || msg.sender_avatar || msg.avatar || '🤖';
|
|
268
278
|
var agentColor = msg.agent_color || msg.color || 'var(--accent)';
|
|
269
279
|
var agentRole = msg.agent_role || msg.role_detail || '';
|
|
280
|
+
var agentPath = msg.agent || '';
|
|
281
|
+
var _clickAvatar = agentPath ? ' onclick="showAgentProfile(\'' + escapeHtml(agentPath) + '\')" style="background:' + agentColor + ';color:#fff;cursor:pointer" title="点击查看简介"' : ' style="background:' + agentColor + ';color:#fff"';
|
|
270
282
|
html += '<div class="group-msg-row">'
|
|
271
|
-
+ '<div class="group-msg-avatar"
|
|
283
|
+
+ '<div class="group-msg-avatar"' + _clickAvatar + '>' + agentEmoji + '</div>'
|
|
272
284
|
+ '<div>'
|
|
273
285
|
+ '<div class="group-msg-name">' + escapeHtml(agentName)
|
|
274
286
|
+ (agentRole ? ' <span class="role-badge">' + escapeHtml(agentRole) + '</span>' : '')
|
|
@@ -283,8 +295,10 @@ function _renderGroupMessagesInner() {
|
|
|
283
295
|
var rName = r.agent_name || r.sender_name || r.name || r.agent || 'Agent';
|
|
284
296
|
var rEmoji = r.agent_emoji || r.sender_avatar || r.avatar || '🤖';
|
|
285
297
|
var rColor = r.agent_color || r.color || 'var(--accent)';
|
|
298
|
+
var rPath = r.agent_path || '';
|
|
299
|
+
var _rClickAvatar = rPath ? ' onclick="showAgentProfile(\'' + escapeHtml(rPath) + '\')" style="background:' + rColor + ';color:#fff;cursor:pointer" title="点击查看简介"' : ' style="background:' + rColor + ';color:#fff"';
|
|
286
300
|
html += '<div class="group-msg-row">'
|
|
287
|
-
+ '<div class="group-msg-avatar"
|
|
301
|
+
+ '<div class="group-msg-avatar"' + _rClickAvatar + '>' + rEmoji + '</div>'
|
|
288
302
|
+ '<div>'
|
|
289
303
|
+ '<div class="group-msg-name">' + escapeHtml(rName) + '</div>'
|
|
290
304
|
+ '<div class="group-msg-bubble" style="border-left-color:' + rColor + '">' + renderMarkdown(r.content || r.response || '') + '</div>'
|
|
@@ -294,6 +308,17 @@ function _renderGroupMessagesInner() {
|
|
|
294
308
|
}
|
|
295
309
|
|
|
296
310
|
container.innerHTML = html;
|
|
311
|
+
// [v1.23.34] 为所有群聊气泡添加双击复制功能
|
|
312
|
+
container.querySelectorAll('.group-msg-bubble').forEach(function(bubble) {
|
|
313
|
+
bubble.addEventListener('dblclick', function() {
|
|
314
|
+
var text = bubble.innerText || bubble.textContent || '';
|
|
315
|
+
if (text) {
|
|
316
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
317
|
+
if (typeof toast === 'function') toast('已复制消息', 'success');
|
|
318
|
+
}).catch(function() {});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
});
|
|
297
322
|
scrollToBottom();
|
|
298
323
|
}
|
|
299
324
|
|
|
@@ -798,3 +823,38 @@ async function sendGroupChat() {
|
|
|
798
823
|
input.focus();
|
|
799
824
|
}
|
|
800
825
|
}
|
|
826
|
+
|
|
827
|
+
// ══════════════════════════════════════════════════════
|
|
828
|
+
// ── Agent Profile Popup (点击头像显示简介) ──
|
|
829
|
+
// ══════════════════════════════════════════════════════
|
|
830
|
+
|
|
831
|
+
function showAgentProfile(agentPath) {
|
|
832
|
+
var agent = (state.agentsFlat || []).find(function(a) { return a.path === agentPath; });
|
|
833
|
+
if (!agent) {
|
|
834
|
+
if (typeof toast === 'function') toast('未找到 Agent 信息', 'error');
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
var name = agent.name || agentPath;
|
|
838
|
+
var emoji = agent.avatar_emoji || '🤖';
|
|
839
|
+
var color = agent.avatar_color || getAgentGradient(name);
|
|
840
|
+
var desc = agent.description || '暂无描述';
|
|
841
|
+
var tools = agent.tools || [];
|
|
842
|
+
var toolsStr = tools.length > 0 ? tools.join(', ') : '默认工具集';
|
|
843
|
+
|
|
844
|
+
var container = document.getElementById('groupModalContainer');
|
|
845
|
+
// [v1.23.35] 添加"对话"按钮 — 点击后退出群聊,切换到该 Agent 的个人对话
|
|
846
|
+
var chatBtnHtml = typeof selectAgent === 'function'
|
|
847
|
+
? '<button class="agent-modal-btn agent-modal-btn-primary" onclick="closeGroupModal();selectAgent(\'' + escapeHtml(agentPath) + '\')">💬 对话</button>'
|
|
848
|
+
: '';
|
|
849
|
+
container.innerHTML = '<div class="modal-overlay" onclick="if(event.target===this)closeGroupModal()"><div class="group-modal" style="max-width:380px">'
|
|
850
|
+
+ '<div style="display:flex;align-items:center;gap:14px;margin-bottom:16px">'
|
|
851
|
+
+ '<div class="group-msg-avatar" style="background:' + color + ';color:#fff;width:48px;height:48px;border-radius:12px;font-size:22px">' + emoji + '</div>'
|
|
852
|
+
+ '<div><div style="font-size:18px;font-weight:700;color:var(--text)">' + escapeHtml(name) + '</div>'
|
|
853
|
+
+ '<div style="font-size:12px;color:var(--text3)">' + escapeHtml(agentPath) + '</div></div></div>'
|
|
854
|
+
+ '<div style="font-size:14px;color:var(--text2);line-height:1.6;margin-bottom:14px;padding:12px;background:var(--bg);border-radius:var(--radius-sm);border:1px solid var(--border-light)">' + escapeHtml(desc) + '</div>'
|
|
855
|
+
+ (tools.length > 0 ? '<div style="font-size:12px;color:var(--text3);margin-bottom:14px"><span style="font-weight:600">工具:</span> ' + escapeHtml(toolsStr) + '</div>' : '')
|
|
856
|
+
+ '<div class="agent-modal-actions">'
|
|
857
|
+
+ chatBtnHtml
|
|
858
|
+
+ '<button class="agent-modal-btn agent-modal-btn-cancel" onclick="closeGroupModal()">关闭</button>'
|
|
859
|
+
+ '</div></div></div>';
|
|
860
|
+
}
|