myagent-ai 1.23.79 → 1.23.81
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 +66 -0
- package/myagent/groups/manager.py +66 -0
- package/myagent/package.json +2 -2
- package/myagent/web/ui/admin/admin-core.js +14 -6
- package/myagent/web/ui/chat/chat.css +4 -2
- package/myagent/web/ui/chat/chat.js +1 -1
- package/myagent/web/ui/chat/chat_container.html +1 -1
- package/myagent/web/ui/chat/chat_main.js +65 -111
- package/myagent/web/ui/chat/groupchat.js +90 -34
- package/package.json +1 -1
- package/web/api_server.py +27 -0
- package/web/ui/admin/admin-core.js +14 -6
- package/web/ui/chat/chat.css +4 -2
- package/web/ui/chat/chat.js +1 -1
- package/web/ui/chat/chat_container.html +1 -1
- package/web/ui/chat/chat_main.js +50 -34
- package/web/ui/chat/groupchat.js +27 -8
- package/worklog.md +18 -0
package/groups/manager.py
CHANGED
|
@@ -258,6 +258,14 @@ class GroupManager:
|
|
|
258
258
|
db_path = self._data_dir / "groups" / "messages.db"
|
|
259
259
|
self._db_conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
|
260
260
|
self._db_conn.row_factory = sqlite3.Row
|
|
261
|
+
# [v1.23.81] 群聊 session 映射表(每个群一个纯数字 session ID)
|
|
262
|
+
self._db_conn.execute("""
|
|
263
|
+
CREATE TABLE IF NOT EXISTS group_sessions (
|
|
264
|
+
group_id TEXT PRIMARY KEY,
|
|
265
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
266
|
+
created_at REAL NOT NULL
|
|
267
|
+
)
|
|
268
|
+
""")
|
|
261
269
|
self._db_conn.execute("""
|
|
262
270
|
CREATE TABLE IF NOT EXISTS group_messages (
|
|
263
271
|
id TEXT PRIMARY KEY,
|
|
@@ -638,6 +646,64 @@ class GroupManager:
|
|
|
638
646
|
|
|
639
647
|
return group
|
|
640
648
|
|
|
649
|
+
# ==================================================================
|
|
650
|
+
# [v1.23.81] 群聊 Session 管理(纯数字 ID,嵌入 URL 用于导航恢复)
|
|
651
|
+
# ==================================================================
|
|
652
|
+
|
|
653
|
+
def get_or_create_group_session(self, group_id: str) -> str:
|
|
654
|
+
"""
|
|
655
|
+
获取或创建群聊的 session ID。
|
|
656
|
+
每个群有且仅有一个 session ID(纯数字,以 9 开头,递增)。
|
|
657
|
+
此 session ID 用于前端 URL 参数,确保从后台管理返回时能正确恢复群聊视图。
|
|
658
|
+
"""
|
|
659
|
+
if not self._db_conn:
|
|
660
|
+
return ""
|
|
661
|
+
# 查找已有 session
|
|
662
|
+
row = self._db_conn.execute(
|
|
663
|
+
"SELECT session_id FROM group_sessions WHERE group_id = ?", (group_id,)
|
|
664
|
+
).fetchone()
|
|
665
|
+
if row:
|
|
666
|
+
return row["session_id"]
|
|
667
|
+
# 生成新的纯数字 session ID(9 开头,13 位数字,时间戳后 8 位 + 随机 4 位)
|
|
668
|
+
import random
|
|
669
|
+
ts_part = str(int(time.time()))[-8:]
|
|
670
|
+
rand_part = str(random.randint(1000, 9999))
|
|
671
|
+
new_sid = "9" + ts_part + rand_part # 共 13 位纯数字
|
|
672
|
+
# 确保唯一性
|
|
673
|
+
for _ in range(10):
|
|
674
|
+
existing = self._db_conn.execute(
|
|
675
|
+
"SELECT 1 FROM group_sessions WHERE session_id = ?", (new_sid,)
|
|
676
|
+
).fetchone()
|
|
677
|
+
if not existing:
|
|
678
|
+
break
|
|
679
|
+
rand_part = str(random.randint(1000, 9999))
|
|
680
|
+
new_sid = "9" + ts_part + rand_part
|
|
681
|
+
self._db_conn.execute(
|
|
682
|
+
"INSERT INTO group_sessions (group_id, session_id, created_at) VALUES (?, ?, ?)",
|
|
683
|
+
(group_id, new_sid, time.time()),
|
|
684
|
+
)
|
|
685
|
+
self._db_conn.commit()
|
|
686
|
+
logger.info(f"群聊 session 创建: group={group_id}, session={new_sid}")
|
|
687
|
+
return new_sid
|
|
688
|
+
|
|
689
|
+
def get_group_id_by_session(self, session_id: str) -> Optional[str]:
|
|
690
|
+
"""通过 session ID 查找 group_id(用于前端 URL 恢复)"""
|
|
691
|
+
if not self._db_conn:
|
|
692
|
+
return None
|
|
693
|
+
row = self._db_conn.execute(
|
|
694
|
+
"SELECT group_id FROM group_sessions WHERE session_id = ?", (session_id,)
|
|
695
|
+
).fetchone()
|
|
696
|
+
return row["group_id"] if row else None
|
|
697
|
+
|
|
698
|
+
def get_session_by_group(self, group_id: str) -> Optional[str]:
|
|
699
|
+
"""通过 group_id 查找 session_id"""
|
|
700
|
+
if not self._db_conn:
|
|
701
|
+
return None
|
|
702
|
+
row = self._db_conn.execute(
|
|
703
|
+
"SELECT session_id FROM group_sessions WHERE group_id = ?", (group_id,)
|
|
704
|
+
).fetchone()
|
|
705
|
+
return row["session_id"] if row else None
|
|
706
|
+
|
|
641
707
|
# ==================================================================
|
|
642
708
|
# 消息管理
|
|
643
709
|
# ==================================================================
|
|
@@ -258,6 +258,14 @@ class GroupManager:
|
|
|
258
258
|
db_path = self._data_dir / "groups" / "messages.db"
|
|
259
259
|
self._db_conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
|
260
260
|
self._db_conn.row_factory = sqlite3.Row
|
|
261
|
+
# [v1.23.81] 群聊 session 映射表(每个群一个纯数字 session ID)
|
|
262
|
+
self._db_conn.execute("""
|
|
263
|
+
CREATE TABLE IF NOT EXISTS group_sessions (
|
|
264
|
+
group_id TEXT PRIMARY KEY,
|
|
265
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
266
|
+
created_at REAL NOT NULL
|
|
267
|
+
)
|
|
268
|
+
""")
|
|
261
269
|
self._db_conn.execute("""
|
|
262
270
|
CREATE TABLE IF NOT EXISTS group_messages (
|
|
263
271
|
id TEXT PRIMARY KEY,
|
|
@@ -638,6 +646,64 @@ class GroupManager:
|
|
|
638
646
|
|
|
639
647
|
return group
|
|
640
648
|
|
|
649
|
+
# ==================================================================
|
|
650
|
+
# [v1.23.81] 群聊 Session 管理(纯数字 ID,嵌入 URL 用于导航恢复)
|
|
651
|
+
# ==================================================================
|
|
652
|
+
|
|
653
|
+
def get_or_create_group_session(self, group_id: str) -> str:
|
|
654
|
+
"""
|
|
655
|
+
获取或创建群聊的 session ID。
|
|
656
|
+
每个群有且仅有一个 session ID(纯数字,以 9 开头,递增)。
|
|
657
|
+
此 session ID 用于前端 URL 参数,确保从后台管理返回时能正确恢复群聊视图。
|
|
658
|
+
"""
|
|
659
|
+
if not self._db_conn:
|
|
660
|
+
return ""
|
|
661
|
+
# 查找已有 session
|
|
662
|
+
row = self._db_conn.execute(
|
|
663
|
+
"SELECT session_id FROM group_sessions WHERE group_id = ?", (group_id,)
|
|
664
|
+
).fetchone()
|
|
665
|
+
if row:
|
|
666
|
+
return row["session_id"]
|
|
667
|
+
# 生成新的纯数字 session ID(9 开头,13 位数字,时间戳后 8 位 + 随机 4 位)
|
|
668
|
+
import random
|
|
669
|
+
ts_part = str(int(time.time()))[-8:]
|
|
670
|
+
rand_part = str(random.randint(1000, 9999))
|
|
671
|
+
new_sid = "9" + ts_part + rand_part # 共 13 位纯数字
|
|
672
|
+
# 确保唯一性
|
|
673
|
+
for _ in range(10):
|
|
674
|
+
existing = self._db_conn.execute(
|
|
675
|
+
"SELECT 1 FROM group_sessions WHERE session_id = ?", (new_sid,)
|
|
676
|
+
).fetchone()
|
|
677
|
+
if not existing:
|
|
678
|
+
break
|
|
679
|
+
rand_part = str(random.randint(1000, 9999))
|
|
680
|
+
new_sid = "9" + ts_part + rand_part
|
|
681
|
+
self._db_conn.execute(
|
|
682
|
+
"INSERT INTO group_sessions (group_id, session_id, created_at) VALUES (?, ?, ?)",
|
|
683
|
+
(group_id, new_sid, time.time()),
|
|
684
|
+
)
|
|
685
|
+
self._db_conn.commit()
|
|
686
|
+
logger.info(f"群聊 session 创建: group={group_id}, session={new_sid}")
|
|
687
|
+
return new_sid
|
|
688
|
+
|
|
689
|
+
def get_group_id_by_session(self, session_id: str) -> Optional[str]:
|
|
690
|
+
"""通过 session ID 查找 group_id(用于前端 URL 恢复)"""
|
|
691
|
+
if not self._db_conn:
|
|
692
|
+
return None
|
|
693
|
+
row = self._db_conn.execute(
|
|
694
|
+
"SELECT group_id FROM group_sessions WHERE session_id = ?", (session_id,)
|
|
695
|
+
).fetchone()
|
|
696
|
+
return row["group_id"] if row else None
|
|
697
|
+
|
|
698
|
+
def get_session_by_group(self, group_id: str) -> Optional[str]:
|
|
699
|
+
"""通过 group_id 查找 session_id"""
|
|
700
|
+
if not self._db_conn:
|
|
701
|
+
return None
|
|
702
|
+
row = self._db_conn.execute(
|
|
703
|
+
"SELECT session_id FROM group_sessions WHERE group_id = ?", (group_id,)
|
|
704
|
+
).fetchone()
|
|
705
|
+
return row["session_id"] if row else None
|
|
706
|
+
|
|
641
707
|
# ==================================================================
|
|
642
708
|
# 消息管理
|
|
643
709
|
# ==================================================================
|
package/myagent/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myagent-ai",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.81",
|
|
4
4
|
"description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
|
|
5
5
|
"main": "main.py",
|
|
6
6
|
"bin": {
|
|
@@ -43,4 +43,4 @@
|
|
|
43
43
|
"python": ">=3.10",
|
|
44
44
|
"node": ">=18"
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
}
|
|
@@ -122,7 +122,7 @@ document.getElementById('adminSidebar')?.addEventListener('click',function(e){
|
|
|
122
122
|
loadVersion();
|
|
123
123
|
setTimeout(()=>checkUpdate(false),30000);
|
|
124
124
|
|
|
125
|
-
// [v1.23.
|
|
125
|
+
// [v1.23.81] 返回聊天:通过纯数字 session ID 恢复群聊(不再传递 group_id)
|
|
126
126
|
(function(){
|
|
127
127
|
function _buildReturnUrl(){
|
|
128
128
|
var params=new URLSearchParams(window.location.search);
|
|
@@ -130,13 +130,21 @@ setTimeout(()=>checkUpdate(false),30000);
|
|
|
130
130
|
// 从 URL 参数中恢复聊天状态
|
|
131
131
|
var agent=params.get('from_agent');
|
|
132
132
|
var session=params.get('from_session');
|
|
133
|
-
|
|
133
|
+
// [v1.23.81] 群聊纯数字 session ID(优先级最高,覆盖 ?s= 个人会话参数)
|
|
134
|
+
var groupSession=params.get('from_group_session');
|
|
134
135
|
var mode=params.get('from_mode');
|
|
135
136
|
var parts=[];
|
|
136
|
-
|
|
137
|
-
if(
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
// 如果有群聊 session,用 ?g= 参数(纯数字 ID)
|
|
138
|
+
if(groupSession && /^[0-9]+$/.test(groupSession)){
|
|
139
|
+
parts.push('g='+groupSession);
|
|
140
|
+
if(agent)parts.push('a='+encodeURIComponent(agent));
|
|
141
|
+
if(mode)parts.push('mode='+encodeURIComponent(mode));
|
|
142
|
+
} else {
|
|
143
|
+
// 个人聊天模式
|
|
144
|
+
if(agent)parts.push('a='+encodeURIComponent(agent));
|
|
145
|
+
if(mode)parts.push('mode='+encodeURIComponent(mode));
|
|
146
|
+
if(session)parts.push('s='+encodeURIComponent(session));
|
|
147
|
+
}
|
|
140
148
|
if(parts.length>0)base+='?'+parts.join('&');
|
|
141
149
|
return base;
|
|
142
150
|
}
|
|
@@ -215,9 +215,10 @@ input,textarea,select{font:inherit}
|
|
|
215
215
|
.messages-container{flex:1;overflow-y:auto;padding:20px}
|
|
216
216
|
.messages-inner{max-width:900px;margin:0 auto;display:flex;flex-direction:column;gap:16px}
|
|
217
217
|
|
|
218
|
-
.message-row{display:flex;gap:10px;animation:msgIn .25s ease-out}
|
|
218
|
+
.message-row{display:flex;gap:10px;animation:msgIn .25s ease-out;align-items:flex-start}
|
|
219
219
|
@keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
220
220
|
.message-row.user{flex-direction:row-reverse}
|
|
221
|
+
.message-row>:nth-child(2){flex:1;min-width:0}
|
|
221
222
|
|
|
222
223
|
.message-avatar{
|
|
223
224
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
@@ -1786,7 +1787,8 @@ input,textarea,select{font:inherit}
|
|
|
1786
1787
|
font-size:12px;color:var(--text3);
|
|
1787
1788
|
animation:msgIn .25s ease-out;
|
|
1788
1789
|
}
|
|
1789
|
-
.group-msg-row{display:flex;gap:10px;animation:msgIn .25s ease-out}
|
|
1790
|
+
.group-msg-row{display:flex;gap:10px;animation:msgIn .25s ease-out;align-items:flex-start}
|
|
1791
|
+
.group-msg-row>:nth-child(2){flex:1;min-width:0}
|
|
1790
1792
|
.group-msg-avatar{
|
|
1791
1793
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
1792
1794
|
display:grid;place-items:center;font-size:14px;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
6
|
<title>MyAgent - AI 助手</title>
|
|
7
|
-
<link rel="stylesheet" href="chat.css?v=
|
|
7
|
+
<link rel="stylesheet" href="chat.css?v=11">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="app">
|
|
@@ -86,16 +86,19 @@ function closeMobileAgentPanel() {
|
|
|
86
86
|
if (overlay) overlay.classList.remove('active');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// [v1.23.
|
|
89
|
+
// [v1.23.81] 跳转后台管理:携带当前聊天上下文(agent、session、群聊session ID),以便"返回聊天"正确恢复
|
|
90
90
|
function goToAdmin() {
|
|
91
91
|
var params = new URLSearchParams();
|
|
92
92
|
var page = 'dashboard';
|
|
93
93
|
// 传递当前聊天状态,供"返回聊天"恢复
|
|
94
|
-
if (state.
|
|
94
|
+
if (state.activeAgent) params.set('from_agent', state.activeAgent);
|
|
95
95
|
if (state.chatMode) params.set('from_mode', state.chatMode);
|
|
96
|
-
if (state.activeSessionId) params.set('from_session', state.activeSessionId);
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
if (state.activeSessionId && currentView !== 'group') params.set('from_session', state.activeSessionId);
|
|
97
|
+
// [v1.23.81] 传递群聊纯数字 session ID(从 URL 读取),而非 JS 变量
|
|
98
|
+
if (typeof currentView !== 'undefined' && currentView === 'group') {
|
|
99
|
+
var urlP = new URLSearchParams(window.location.search);
|
|
100
|
+
var gSession = urlP.get('g');
|
|
101
|
+
if (gSession) params.set('from_group_session', gSession);
|
|
99
102
|
}
|
|
100
103
|
// 保留原始 ?page= 参数
|
|
101
104
|
var origParams = new URLSearchParams(window.location.search);
|
|
@@ -346,14 +349,10 @@ const StatePersistence = {
|
|
|
346
349
|
if (savedSessionId) {
|
|
347
350
|
state._pendingSessionRestore = savedSessionId;
|
|
348
351
|
}
|
|
349
|
-
// [v1.23.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
currentView = 'group';
|
|
354
|
-
currentGroupId = savedGroupId;
|
|
355
|
-
state._pendingGroupRestore = savedGroupId;
|
|
356
|
-
}
|
|
352
|
+
// [v1.23.81] 不再从 localStorage 恢复群聊状态(统一由 URL ?g= 参数恢复)
|
|
353
|
+
// 仅保留兼容处理:如果旧版遗留了 localStorage 群聊状态,清除它
|
|
354
|
+
StatePersistence.remove('currentView');
|
|
355
|
+
StatePersistence.remove('currentGroupId');
|
|
357
356
|
},
|
|
358
357
|
/** 标记 setup 已完成(避免每次刷新弹出向导) */
|
|
359
358
|
markSetupDone() { StatePersistence.save('setupDone', true); },
|
|
@@ -369,14 +368,17 @@ async function initChat() {
|
|
|
369
368
|
// [v1.16.12] 初始化附件上传 UI
|
|
370
369
|
initAttachmentUI();
|
|
371
370
|
|
|
372
|
-
// URL 参数处理: ?a=xxx&mode=exec&s=xxx&popout=1(agent/session 使用 UrlCodec 编码)
|
|
371
|
+
// URL 参数处理: ?a=xxx&mode=exec&s=xxx&g=xxx&popout=1(agent/session 使用 UrlCodec 编码)
|
|
373
372
|
const urlParams = new URLSearchParams(window.location.search);
|
|
374
373
|
const urlAgent = UrlCodec.decode(urlParams.get('a') || '') || UrlCodec.decode(urlParams.get('agent') || '');
|
|
375
374
|
const urlMode = urlParams.get('mode');
|
|
376
375
|
const urlSession = UrlCodec.decode(urlParams.get('s') || '') || UrlCodec.decode(urlParams.get('session') || '');
|
|
377
376
|
const isPopout = urlParams.get('popout') === '1';
|
|
378
|
-
// [v1.23.
|
|
379
|
-
const
|
|
377
|
+
// [v1.23.81] 群聊纯数字 session ID(?g= 参数,优先级最高)
|
|
378
|
+
const urlGroupSession = urlParams.get('g') || '';
|
|
379
|
+
// 兼容旧版 ?group= 参数(从后台管理返回时的旧格式)
|
|
380
|
+
const urlGroupSessionLegacy = urlParams.get('from_group_session') || '';
|
|
381
|
+
const _effectiveGroupSession = urlGroupSession || urlGroupSessionLegacy;
|
|
380
382
|
if (urlMode === 'chat' || urlMode === 'exec') {
|
|
381
383
|
state.chatMode = urlMode;
|
|
382
384
|
}
|
|
@@ -402,8 +404,7 @@ async function initChat() {
|
|
|
402
404
|
loadModels();
|
|
403
405
|
loadStatus();
|
|
404
406
|
loadChatVersion();
|
|
405
|
-
|
|
406
|
-
window._fetchGroupsPromise = fetchGroups();
|
|
407
|
+
fetchGroups();
|
|
407
408
|
fetchDepts();
|
|
408
409
|
// [v1.17.0] 初始化 VNC 远程桌面状态
|
|
409
410
|
refreshVNCStatus();
|
|
@@ -463,7 +464,38 @@ async function initChat() {
|
|
|
463
464
|
document.title = (agentObj ? agentObj.name : urlAgent || 'MyAgent') + ' - MyAgent';
|
|
464
465
|
}
|
|
465
466
|
|
|
466
|
-
|
|
467
|
+
// [v1.23.81] 群聊恢复逻辑:从 URL 的 ?g= 纯数字 session ID 恢复(优先级最高)
|
|
468
|
+
// 不再依赖 JS 状态传递(from_group),完全通过 URL session ID 恢复
|
|
469
|
+
state._pendingGroupRestore = null; // 不再使用 localStorage 中的群聊状态
|
|
470
|
+
|
|
471
|
+
async function _restoreGroupBySession(sessionId) {
|
|
472
|
+
// 通过 API 将纯数字 session ID 解析为 group_id
|
|
473
|
+
try {
|
|
474
|
+
var resolveData = await api('/api/group-session/' + encodeURIComponent(sessionId));
|
|
475
|
+
if (resolveData && resolveData.group_id) {
|
|
476
|
+
if (typeof groups !== 'undefined' && groups.length > 0) {
|
|
477
|
+
selectGroup(resolveData.group_id);
|
|
478
|
+
} else {
|
|
479
|
+
// groups 尚未加载,等待后重试
|
|
480
|
+
setTimeout(function() { _restoreGroupBySession(sessionId); }, 200);
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
console.warn('群聊 session 解析失败:', sessionId);
|
|
484
|
+
}
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.warn('解析群聊 session 失败:', e);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 如果 URL 中有 ?g= 纯数字 session ID,优先恢复群聊
|
|
491
|
+
if (_effectiveGroupSession && /^[0-9]+$/.test(_effectiveGroupSession)) {
|
|
492
|
+
// 群聊模式:如果有 from_agent 参数,更新 state.activeAgent 以便后续群聊私聊时使用
|
|
493
|
+
if (urlAgent && urlAgent !== state.activeAgent) {
|
|
494
|
+
state.activeAgent = urlAgent;
|
|
495
|
+
StatePersistence.save('activeAgent', urlAgent);
|
|
496
|
+
}
|
|
497
|
+
_restoreGroupBySession(_effectiveGroupSession);
|
|
498
|
+
} else if (urlAgent) {
|
|
467
499
|
// URL 指定了 agent,校验后切换
|
|
468
500
|
var resolved = urlAgent;
|
|
469
501
|
if (!findAgentByPath(resolved)) {
|
|
@@ -498,33 +530,6 @@ async function initChat() {
|
|
|
498
530
|
}
|
|
499
531
|
// 如果 agent 一致,loadSessions() 内部已通过 _pendingSessionRestore 自动处理了
|
|
500
532
|
}
|
|
501
|
-
|
|
502
|
-
// [v1.23.34] 恢复群聊视图(在所有初始化完成后)
|
|
503
|
-
// [v1.23.76] 等待 fetchGroups 完成后再恢复,确保 groups 数组已填充
|
|
504
|
-
var _restoreGroupView = function(gid) {
|
|
505
|
-
if (typeof selectGroup !== 'function') return;
|
|
506
|
-
// 重置滚动锁定状态,确保群聊刷新后能滚到底部
|
|
507
|
-
_userScrollLocked = false;
|
|
508
|
-
selectGroup(gid);
|
|
509
|
-
};
|
|
510
|
-
if (state._pendingGroupRestore) {
|
|
511
|
-
var _gid = state._pendingGroupRestore;
|
|
512
|
-
state._pendingGroupRestore = null;
|
|
513
|
-
// 等待 fetchGroups 完成(如果还在进行中)
|
|
514
|
-
if (window._fetchGroupsPromise) {
|
|
515
|
-
window._fetchGroupsPromise.then(function() { _restoreGroupView(_gid); }).catch(function() { _restoreGroupView(_gid); });
|
|
516
|
-
} else {
|
|
517
|
-
setTimeout(function() { _restoreGroupView(_gid); }, 300);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
// [v1.23.57] 从后台管理"返回聊天"时,URL 参数优先恢复群聊
|
|
521
|
-
if (urlGroup) {
|
|
522
|
-
if (window._fetchGroupsPromise) {
|
|
523
|
-
window._fetchGroupsPromise.then(function() { _restoreGroupView(urlGroup); }).catch(function() { _restoreGroupView(urlGroup); });
|
|
524
|
-
} else {
|
|
525
|
-
setTimeout(function() { _restoreGroupView(urlGroup); }, 300);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
533
|
}
|
|
529
534
|
|
|
530
535
|
// Run init: if DOMContentLoaded already fired (dynamic script load), run immediately
|
|
@@ -2140,77 +2145,26 @@ function formatSessionName(id) {
|
|
|
2140
2145
|
|
|
2141
2146
|
function renderSessions(filter = '') {
|
|
2142
2147
|
const list = document.getElementById('sessionList');
|
|
2143
|
-
// [v1.23.
|
|
2148
|
+
// [v1.23.51] 群聊模式下,左侧显示群聊列表而非个人会话
|
|
2144
2149
|
if (currentView === 'group' && typeof groups !== 'undefined' && groups.length > 0) {
|
|
2145
|
-
var currentGroup = null;
|
|
2146
|
-
for (var gi = 0; gi < groups.length; gi++) {
|
|
2147
|
-
if (groups[gi].id === currentGroupId) { currentGroup = groups[gi]; break; }
|
|
2148
|
-
}
|
|
2149
2150
|
var html = '';
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2151
|
+
for (var i = 0; i < groups.length; i++) {
|
|
2152
|
+
var g = groups[i];
|
|
2153
|
+
var isActive = g.id === currentGroupId;
|
|
2154
|
+
var memberCount = (g.members || []).length;
|
|
2155
|
+
html += '<div class="session-item ' + (isActive ? 'active' : '') + '" onclick="selectGroup(\'' + escapeHtml(g.id) + '\')" title="' + escapeHtml(g.name) + '">'
|
|
2156
|
+
+ '<div class="session-icon" style="font-size:16px">' + escapeHtml(g.avatar_emoji || g.emoji || '👥') + '</div>'
|
|
2155
2157
|
+ '<div class="session-info">'
|
|
2156
|
-
+ '<div class="session-name"
|
|
2157
|
-
+ '<div class="session-preview">' + (
|
|
2158
|
-
+ '</div
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
// 显示群内 Agent 成员列表,点击可直接私聊
|
|
2162
|
-
if (currentGroup && currentGroup.members && currentGroup.members.length > 0) {
|
|
2163
|
-
var members = currentGroup.members;
|
|
2164
|
-
// 按角色排序:owner > admin > member
|
|
2165
|
-
members.sort(function(a, b) {
|
|
2166
|
-
var order = {owner: 0, admin: 1, member: 2};
|
|
2167
|
-
return (order[a.role] || 2) - (order[b.role] || 2);
|
|
2168
|
-
});
|
|
2169
|
-
var roleLabel = {owner: '群主', admin: '管理员', member: ''};
|
|
2170
|
-
for (var mi = 0; mi < members.length; mi++) {
|
|
2171
|
-
var m = members[mi];
|
|
2172
|
-
var mPath = m.agent_path || m.path || '';
|
|
2173
|
-
var mName = m.agent_name || m.name || mPath;
|
|
2174
|
-
var mEmoji = m.avatar_emoji || '🤖';
|
|
2175
|
-
var mColor = m.agent_color || (typeof getAgentGradient === 'function' ? getAgentGradient(mName) : 'var(--accent)');
|
|
2176
|
-
var mRole = roleLabel[m.role] || '';
|
|
2177
|
-
// 查找 agent 完整信息
|
|
2178
|
-
var mc = (state.agentsFlat || []).find(function(a) { return a.path === mPath; });
|
|
2179
|
-
if (mc) {
|
|
2180
|
-
mEmoji = mc.avatar_emoji || mEmoji;
|
|
2181
|
-
mColor = mc.avatar_color || mColor;
|
|
2182
|
-
mName = mc.name || mName;
|
|
2183
|
-
}
|
|
2184
|
-
html += '<div class="session-item" onclick="chatWithAgent(\'' + escapeHtml(mPath) + '\')" title="点击与 ' + escapeHtml(mName) + ' 私聊">'
|
|
2185
|
-
+ '<div class="session-icon" style="background:' + escapeHtml(mColor) + ';color:#fff;font-size:14px;border-radius:10px">' + escapeHtml(mEmoji) + '</div>'
|
|
2186
|
-
+ '<div class="session-info">'
|
|
2187
|
-
+ '<div class="session-name">' + escapeHtml(mName) + (mRole ? ' <span style="font-size:11px;color:var(--text3)">' + mRole + '</span>' : '') + '</div>'
|
|
2188
|
-
+ '<div class="session-preview">' + escapeHtml(mPath) + '</div>'
|
|
2189
|
-
+ '</div></div>';
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
// 底部:其他群聊切换(折叠显示)
|
|
2194
|
-
var otherGroups = groups.filter(function(g) { return g.id !== currentGroupId; });
|
|
2195
|
-
if (otherGroups.length > 0) {
|
|
2196
|
-
html += '<div style="padding:8px 12px;font-size:11px;color:var(--text3);border-top:1px solid var(--border);margin-top:4px">其他群聊</div>';
|
|
2197
|
-
for (var oi = 0; oi < otherGroups.length; oi++) {
|
|
2198
|
-
var og = otherGroups[oi];
|
|
2199
|
-
html += '<div class="session-item" onclick="selectGroup(\'' + escapeHtml(og.id) + '\')" title="切换到 ' + escapeHtml(og.name) + '" style="opacity:0.7">'
|
|
2200
|
-
+ '<div class="session-icon" style="font-size:14px">' + escapeHtml(og.avatar_emoji || og.emoji || '👥') + '</div>'
|
|
2201
|
-
+ '<div class="session-info">'
|
|
2202
|
-
+ '<div class="session-name">' + escapeHtml(og.name) + '</div>'
|
|
2203
|
-
+ '<div class="session-preview">' + ((og.members || []).length) + ' 位成员</div>'
|
|
2204
|
-
+ '</div></div>';
|
|
2205
|
-
}
|
|
2158
|
+
+ '<div class="session-name">' + escapeHtml(g.name) + '</div>'
|
|
2159
|
+
+ '<div class="session-preview">' + escapeHtml(g.description || (memberCount + ' 位成员')) + '</div>'
|
|
2160
|
+
+ '</div>'
|
|
2161
|
+
+ '</div>';
|
|
2206
2162
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
html += '<div class="session-item" onclick="exitGroupChat()" title="返回个人聊天" style="border-top:1px solid var(--border);margin-top:4px">'
|
|
2163
|
+
// 在列表顶部添加"返回个人聊天"按钮
|
|
2164
|
+
html = '<div class="session-item" onclick="exitGroupChat()" title="返回个人聊天" style="border-bottom:1px solid var(--border);margin-bottom:4px">'
|
|
2210
2165
|
+ '<div class="session-icon" style="font-size:16px">🔙</div>'
|
|
2211
2166
|
+ '<div class="session-info"><div class="session-name">返回个人聊天</div><div class="session-preview">退出群聊视图</div></div>'
|
|
2212
|
-
+ '</div>';
|
|
2213
|
-
|
|
2167
|
+
+ '</div>' + html;
|
|
2214
2168
|
list.innerHTML = html;
|
|
2215
2169
|
return;
|
|
2216
2170
|
}
|
|
@@ -167,10 +167,21 @@ async function selectGroup(gid) {
|
|
|
167
167
|
}
|
|
168
168
|
currentView = 'group';
|
|
169
169
|
currentGroupId = gid;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
|
|
171
|
+
// [v1.23.81] 获取群聊 session ID 并写入 URL(纯数字,用于导航恢复)
|
|
172
|
+
try {
|
|
173
|
+
var sessionData = await api('/api/groups/' + encodeURIComponent(gid) + '/session');
|
|
174
|
+
if (sessionData && sessionData.session_id) {
|
|
175
|
+
var sessionId = sessionData.session_id;
|
|
176
|
+
// 用群聊专用 ?g= 参数更新 URL(不刷新页面),纯数字 session ID
|
|
177
|
+
var newUrl = '/ui/chat/chat_container.html?g=' + sessionId;
|
|
178
|
+
if (typeof state !== 'undefined' && state.activeAgent) {
|
|
179
|
+
newUrl += '&a=' + encodeURIComponent(state.activeAgent);
|
|
180
|
+
}
|
|
181
|
+
window.history.replaceState({groupSession: sessionId, groupId: gid}, '', newUrl);
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.warn('获取群聊 session 失败:', e);
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
// [v1.23.51] 群聊模式:隐藏"新对话"按钮,搜索框改为搜索群聊
|
|
@@ -202,6 +213,9 @@ async function selectGroup(gid) {
|
|
|
202
213
|
var dot = document.querySelector('.main-title .dot');
|
|
203
214
|
if (dot) dot.style.display = 'none';
|
|
204
215
|
|
|
216
|
+
// [v1.23.79] 群聊模式:左侧边栏显示群名称 + 成员图标
|
|
217
|
+
renderGroupSidebar(groupData);
|
|
218
|
+
|
|
205
219
|
// Update input placeholder
|
|
206
220
|
document.getElementById('userInput').placeholder = '发送到群聊... (Enter 发送, Shift+Enter 换行)';
|
|
207
221
|
|
|
@@ -223,6 +237,7 @@ async function selectGroup(gid) {
|
|
|
223
237
|
}
|
|
224
238
|
return m;
|
|
225
239
|
});
|
|
240
|
+
if (typeof _userScrollLocked !== 'undefined') _userScrollLocked = false;
|
|
226
241
|
renderGroupMessages();
|
|
227
242
|
} catch (e) {
|
|
228
243
|
toast('加载群聊失败: ' + e.message, 'error');
|
|
@@ -234,11 +249,19 @@ function exitGroupChat() {
|
|
|
234
249
|
currentView = 'chat';
|
|
235
250
|
currentGroupId = null;
|
|
236
251
|
groupMessages = [];
|
|
237
|
-
// [v1.23.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
252
|
+
// [v1.23.81] 清除群聊 ?g= session URL,恢复普通聊天 URL
|
|
253
|
+
var cleanUrl = '/ui/chat/chat_container.html';
|
|
254
|
+
if (typeof state !== 'undefined' && state.activeAgent && state.activeAgent !== 'default') {
|
|
255
|
+
cleanUrl += '?a=' + encodeURIComponent(state.activeAgent);
|
|
256
|
+
}
|
|
257
|
+
if (typeof state !== 'undefined' && state.activeSessionId && state.activeSessionId !== '__new__') {
|
|
258
|
+
cleanUrl += (cleanUrl.indexOf('?') >= 0 ? '&' : '?') + 's=' + encodeURIComponent(state.activeSessionId);
|
|
241
259
|
}
|
|
260
|
+
window.history.replaceState({}, '', cleanUrl);
|
|
261
|
+
// 清除群聊 localStorage 持久化状态
|
|
262
|
+
StatePersistence.remove('currentView');
|
|
263
|
+
StatePersistence.remove('currentGroupId');
|
|
264
|
+
|
|
242
265
|
// 清空残留的聊天状态,避免切回个人聊天时显示群聊消息
|
|
243
266
|
state.messages = [];
|
|
244
267
|
state.activeSessionId = null;
|
|
@@ -267,38 +290,69 @@ function exitGroupChat() {
|
|
|
267
290
|
renderSessions();
|
|
268
291
|
renderGroups();
|
|
269
292
|
|
|
293
|
+
// [v1.23.79] 恢复侧边栏指示器为当前 Agent
|
|
294
|
+
updateSidebarAgentIndicator();
|
|
295
|
+
|
|
270
296
|
// 重新加载当前 agent 的会话列表和消息
|
|
271
297
|
loadSessions();
|
|
272
298
|
}
|
|
273
299
|
|
|
274
|
-
// [v1.23.
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
300
|
+
// [v1.23.79] 群聊时左侧边栏显示群成员列表,点击可私聊
|
|
301
|
+
function renderGroupSidebar(groupData) {
|
|
302
|
+
var indicator = document.getElementById('sidebarAgentIndicator');
|
|
303
|
+
var avatar = document.getElementById('sidebarAgentAvatar');
|
|
304
|
+
var nameEl = document.getElementById('sidebarAgentName');
|
|
305
|
+
var hintEl = document.getElementById('sidebarAgentHint');
|
|
306
|
+
if (!indicator || !groupData) return;
|
|
307
|
+
|
|
308
|
+
// 顶部显示群名称
|
|
309
|
+
indicator.style.display = 'flex';
|
|
310
|
+
indicator.style.background = groupData.avatar_color ? groupData.avatar_color + '18' : 'var(--accent-light)';
|
|
311
|
+
indicator.onclick = function() { if (typeof showGroupSettingsModal === 'function') showGroupSettingsModal(); };
|
|
312
|
+
var emoji = groupData.avatar_emoji || groupData.emoji || '👥';
|
|
313
|
+
avatar.textContent = emoji;
|
|
314
|
+
avatar.style.background = groupData.avatar_color || 'var(--accent)';
|
|
315
|
+
avatar.style.color = '#fff';
|
|
316
|
+
nameEl.textContent = groupData.name || '群聊';
|
|
317
|
+
var memberCount = (groupData.members || []).length;
|
|
318
|
+
hintEl.textContent = memberCount + ' 位成员';
|
|
319
|
+
|
|
320
|
+
// 隐藏"新对话"按钮
|
|
286
321
|
var newChatBtn = document.querySelector('.new-chat-btn');
|
|
287
|
-
if (newChatBtn) newChatBtn.style.display = '';
|
|
288
|
-
var searchInput = document.getElementById('searchInput');
|
|
289
|
-
if (searchInput) { searchInput.placeholder = '搜索对话...'; searchInput.value = ''; }
|
|
290
|
-
document.getElementById('activeAgentBadge').style.display = '';
|
|
291
|
-
document.getElementById('groupBackBtn').style.display = 'none';
|
|
292
|
-
document.getElementById('groupSettingsBtn').style.display = 'none';
|
|
293
|
-
var atAllBtn = document.getElementById('atAllBtn');
|
|
294
|
-
if (atAllBtn) atAllBtn.style.display = 'none';
|
|
295
|
-
var _ccb = document.getElementById('clearChatBtn'); if (_ccb) _ccb.style.display = '';
|
|
296
|
-
var dot = document.querySelector('.main-title .dot');
|
|
297
|
-
if (dot) dot.style.display = '';
|
|
298
|
-
document.getElementById('userInput').placeholder = '输入消息... (Enter 发送, Shift+Enter 换行)';
|
|
322
|
+
if (newChatBtn) newChatBtn.style.display = 'none';
|
|
299
323
|
|
|
300
|
-
//
|
|
324
|
+
// 在会话列表区域显示群成员
|
|
325
|
+
var list = document.getElementById('sessionList');
|
|
326
|
+
if (!list) return;
|
|
327
|
+
var members = groupData.members || [];
|
|
328
|
+
var html = '<div class="session-item" onclick="exitGroupChat()" title="返回个人聊天" style="border-bottom:1px solid var(--border);margin-bottom:4px">'
|
|
329
|
+
+ '<div class="session-icon" style="font-size:16px">🔙</div>'
|
|
330
|
+
+ '<div class="session-info"><div class="session-name">返回个人聊天</div><div class="session-preview">退出群聊视图</div></div>'
|
|
331
|
+
+ '</div>';
|
|
332
|
+
|
|
333
|
+
// 显示群成员列表,点击直接私聊
|
|
334
|
+
for (var i = 0; i < members.length; i++) {
|
|
335
|
+
var m = members[i];
|
|
336
|
+
var mEmoji = m.avatar_emoji || '🤖';
|
|
337
|
+
var mName = m.agent_name || m.name || m.agent_path || 'Unknown';
|
|
338
|
+
var mPath = m.agent_path || m.path || '';
|
|
339
|
+
var mColor = m.agent_color || getAgentGradient(mName);
|
|
340
|
+
var mRole = m.role || 'member';
|
|
341
|
+
var roleLabel = {owner: '群主', admin: '管理员', member: '成员'}[mRole] || '';
|
|
342
|
+
html += '<div class="session-item" onclick="startPrivateChatFromGroup(\'' + escapeHtml(mPath) + '\')" title="私聊 ' + escapeHtml(mName) + '">'
|
|
343
|
+
+ '<div class="session-icon" style="font-size:16px;background:' + mColor + ';color:#fff;border-radius:50%;width:32px;height:32px;display:grid;place-items:center">' + escapeHtml(mEmoji) + '</div>'
|
|
344
|
+
+ '<div class="session-info">'
|
|
345
|
+
+ '<div class="session-name">' + escapeHtml(mName) + (roleLabel ? ' <span style="font-size:10px;color:var(--text3);font-weight:normal">' + roleLabel + '</span>' : '') + '</div>'
|
|
346
|
+
+ '<div class="session-preview">' + escapeHtml(mPath) + '</div>'
|
|
347
|
+
+ '</div></div>';
|
|
348
|
+
}
|
|
349
|
+
list.innerHTML = html;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// [v1.23.79] 从群聊成员列表点击,直接发起私聊
|
|
353
|
+
function startPrivateChatFromGroup(agentPath) {
|
|
301
354
|
if (typeof selectAgent === 'function') {
|
|
355
|
+
exitGroupChat();
|
|
302
356
|
selectAgent(agentPath);
|
|
303
357
|
}
|
|
304
358
|
}
|
|
@@ -392,7 +446,7 @@ function _renderGroupMessagesInner() {
|
|
|
392
446
|
}
|
|
393
447
|
});
|
|
394
448
|
});
|
|
395
|
-
scrollToBottom();
|
|
449
|
+
if (typeof scrollToBottom === 'function') scrollToBottom(true);
|
|
396
450
|
}
|
|
397
451
|
|
|
398
452
|
// ══════════════════════════════════════════════════════
|
|
@@ -853,6 +907,7 @@ async function sendGroupChat() {
|
|
|
853
907
|
|
|
854
908
|
// Add user message
|
|
855
909
|
groupMessages.push({role: 'user', content: text, time: new Date().toISOString()});
|
|
910
|
+
if (typeof _userScrollLocked !== 'undefined') _userScrollLocked = false;
|
|
856
911
|
renderGroupMessages();
|
|
857
912
|
|
|
858
913
|
// Clear input
|
|
@@ -878,6 +933,7 @@ async function sendGroupChat() {
|
|
|
878
933
|
groupMessages.push({type: 'system', content: data.system_message, time: new Date().toISOString()});
|
|
879
934
|
}
|
|
880
935
|
|
|
936
|
+
if (typeof _userScrollLocked !== 'undefined') _userScrollLocked = false;
|
|
881
937
|
renderGroupMessages();
|
|
882
938
|
fetchGroups();
|
|
883
939
|
} catch (e) {
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -474,6 +474,9 @@ class ApiServer:
|
|
|
474
474
|
r.add_get("/api/groups/{gid}/messages", self.handle_get_group_messages)
|
|
475
475
|
r.add_post("/api/groups/{gid}/messages", self.handle_send_group_message)
|
|
476
476
|
r.add_delete("/api/groups/{gid}/messages", self.handle_clear_group_messages)
|
|
477
|
+
# [v1.23.81] 群聊 session 管理(纯数字 ID,用于 URL 导航恢复)
|
|
478
|
+
r.add_get("/api/groups/{gid}/session", self.handle_get_group_session)
|
|
479
|
+
r.add_get("/api/group-session/{sid}", self.handle_resolve_group_session)
|
|
477
480
|
# [v1.23.37] Agent间私聊记录查询
|
|
478
481
|
r.add_get("/api/agent-chat/pairs", self.handle_get_agent_chat_pairs)
|
|
479
482
|
r.add_get("/api/agent-chat/messages", self.handle_get_agent_chat_messages)
|
|
@@ -7979,6 +7982,30 @@ window.addEventListener('beforeunload', function() {{
|
|
|
7979
7982
|
ok = mgr.clear_messages(gid)
|
|
7980
7983
|
return web.json_response({"ok": ok})
|
|
7981
7984
|
|
|
7985
|
+
# ── [v1.23.81] 群聊 Session API ──
|
|
7986
|
+
|
|
7987
|
+
async def handle_get_group_session(self, request):
|
|
7988
|
+
"""GET /api/groups/{gid}/session - 获取或创建群聊的 session ID(纯数字)"""
|
|
7989
|
+
gid = request.match_info["gid"]
|
|
7990
|
+
mgr = self._get_group_manager()
|
|
7991
|
+
group = mgr.get_group(gid)
|
|
7992
|
+
if not group:
|
|
7993
|
+
return web.json_response({"error": "群不存在"}, status=404)
|
|
7994
|
+
session_id = mgr.get_or_create_group_session(gid)
|
|
7995
|
+
return web.json_response({"session_id": session_id, "group_id": gid})
|
|
7996
|
+
|
|
7997
|
+
async def handle_resolve_group_session(self, request):
|
|
7998
|
+
"""GET /api/group-session/{sid} - 通过 session ID 查找 group_id(用于 URL 恢复)"""
|
|
7999
|
+
sid = request.match_info["sid"]
|
|
8000
|
+
mgr = self._get_group_manager()
|
|
8001
|
+
group_id = mgr.get_group_id_by_session(sid)
|
|
8002
|
+
if not group_id:
|
|
8003
|
+
return web.json_response({"error": "session 不存在"}, status=404)
|
|
8004
|
+
group = mgr.get_group(group_id)
|
|
8005
|
+
if not group:
|
|
8006
|
+
return web.json_response({"error": "群已不存在"}, status=404)
|
|
8007
|
+
return web.json_response({"session_id": sid, "group_id": group_id, "group": self._enrich_group_dict(group.to_dict())})
|
|
8008
|
+
|
|
7982
8009
|
# ── [v1.23.37] Agent间私聊记录 API ──
|
|
7983
8010
|
|
|
7984
8011
|
async def handle_get_agent_chat_pairs(self, request):
|
|
@@ -122,7 +122,7 @@ document.getElementById('adminSidebar')?.addEventListener('click',function(e){
|
|
|
122
122
|
loadVersion();
|
|
123
123
|
setTimeout(()=>checkUpdate(false),30000);
|
|
124
124
|
|
|
125
|
-
// [v1.23.
|
|
125
|
+
// [v1.23.81] 返回聊天:通过纯数字 session ID 恢复群聊(不再传递 group_id)
|
|
126
126
|
(function(){
|
|
127
127
|
function _buildReturnUrl(){
|
|
128
128
|
var params=new URLSearchParams(window.location.search);
|
|
@@ -130,13 +130,21 @@ setTimeout(()=>checkUpdate(false),30000);
|
|
|
130
130
|
// 从 URL 参数中恢复聊天状态
|
|
131
131
|
var agent=params.get('from_agent');
|
|
132
132
|
var session=params.get('from_session');
|
|
133
|
-
|
|
133
|
+
// [v1.23.81] 群聊纯数字 session ID(优先级最高,覆盖 ?s= 个人会话参数)
|
|
134
|
+
var groupSession=params.get('from_group_session');
|
|
134
135
|
var mode=params.get('from_mode');
|
|
135
136
|
var parts=[];
|
|
136
|
-
|
|
137
|
-
if(
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
// 如果有群聊 session,用 ?g= 参数(纯数字 ID)
|
|
138
|
+
if(groupSession && /^[0-9]+$/.test(groupSession)){
|
|
139
|
+
parts.push('g='+groupSession);
|
|
140
|
+
if(agent)parts.push('a='+encodeURIComponent(agent));
|
|
141
|
+
if(mode)parts.push('mode='+encodeURIComponent(mode));
|
|
142
|
+
} else {
|
|
143
|
+
// 个人聊天模式
|
|
144
|
+
if(agent)parts.push('a='+encodeURIComponent(agent));
|
|
145
|
+
if(mode)parts.push('mode='+encodeURIComponent(mode));
|
|
146
|
+
if(session)parts.push('s='+encodeURIComponent(session));
|
|
147
|
+
}
|
|
140
148
|
if(parts.length>0)base+='?'+parts.join('&');
|
|
141
149
|
return base;
|
|
142
150
|
}
|
package/web/ui/chat/chat.css
CHANGED
|
@@ -215,9 +215,10 @@ input,textarea,select{font:inherit}
|
|
|
215
215
|
.messages-container{flex:1;overflow-y:auto;padding:20px}
|
|
216
216
|
.messages-inner{max-width:900px;margin:0 auto;display:flex;flex-direction:column;gap:16px}
|
|
217
217
|
|
|
218
|
-
.message-row{display:flex;gap:10px;animation:msgIn .25s ease-out}
|
|
218
|
+
.message-row{display:flex;gap:10px;animation:msgIn .25s ease-out;align-items:flex-start}
|
|
219
219
|
@keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
220
220
|
.message-row.user{flex-direction:row-reverse}
|
|
221
|
+
.message-row>:nth-child(2){flex:1;min-width:0}
|
|
221
222
|
|
|
222
223
|
.message-avatar{
|
|
223
224
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
@@ -1786,7 +1787,8 @@ input,textarea,select{font:inherit}
|
|
|
1786
1787
|
font-size:12px;color:var(--text3);
|
|
1787
1788
|
animation:msgIn .25s ease-out;
|
|
1788
1789
|
}
|
|
1789
|
-
.group-msg-row{display:flex;gap:10px;animation:msgIn .25s ease-out}
|
|
1790
|
+
.group-msg-row{display:flex;gap:10px;animation:msgIn .25s ease-out;align-items:flex-start}
|
|
1791
|
+
.group-msg-row>:nth-child(2){flex:1;min-width:0}
|
|
1790
1792
|
.group-msg-avatar{
|
|
1791
1793
|
width:32px;height:32px;border-radius:8px;flex-shrink:0;
|
|
1792
1794
|
display:grid;place-items:center;font-size:14px;
|
package/web/ui/chat/chat.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
6
|
<title>MyAgent - AI 助手</title>
|
|
7
|
-
<link rel="stylesheet" href="chat.css?v=
|
|
7
|
+
<link rel="stylesheet" href="chat.css?v=11">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="app">
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -86,16 +86,19 @@ function closeMobileAgentPanel() {
|
|
|
86
86
|
if (overlay) overlay.classList.remove('active');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// [v1.23.
|
|
89
|
+
// [v1.23.81] 跳转后台管理:携带当前聊天上下文(agent、session、群聊session ID),以便"返回聊天"正确恢复
|
|
90
90
|
function goToAdmin() {
|
|
91
91
|
var params = new URLSearchParams();
|
|
92
92
|
var page = 'dashboard';
|
|
93
93
|
// 传递当前聊天状态,供"返回聊天"恢复
|
|
94
|
-
if (state.
|
|
94
|
+
if (state.activeAgent) params.set('from_agent', state.activeAgent);
|
|
95
95
|
if (state.chatMode) params.set('from_mode', state.chatMode);
|
|
96
|
-
if (state.activeSessionId) params.set('from_session', state.activeSessionId);
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
if (state.activeSessionId && currentView !== 'group') params.set('from_session', state.activeSessionId);
|
|
97
|
+
// [v1.23.81] 传递群聊纯数字 session ID(从 URL 读取),而非 JS 变量
|
|
98
|
+
if (typeof currentView !== 'undefined' && currentView === 'group') {
|
|
99
|
+
var urlP = new URLSearchParams(window.location.search);
|
|
100
|
+
var gSession = urlP.get('g');
|
|
101
|
+
if (gSession) params.set('from_group_session', gSession);
|
|
99
102
|
}
|
|
100
103
|
// 保留原始 ?page= 参数
|
|
101
104
|
var origParams = new URLSearchParams(window.location.search);
|
|
@@ -346,14 +349,10 @@ const StatePersistence = {
|
|
|
346
349
|
if (savedSessionId) {
|
|
347
350
|
state._pendingSessionRestore = savedSessionId;
|
|
348
351
|
}
|
|
349
|
-
// [v1.23.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
currentView = 'group';
|
|
354
|
-
currentGroupId = savedGroupId;
|
|
355
|
-
state._pendingGroupRestore = savedGroupId;
|
|
356
|
-
}
|
|
352
|
+
// [v1.23.81] 不再从 localStorage 恢复群聊状态(统一由 URL ?g= 参数恢复)
|
|
353
|
+
// 仅保留兼容处理:如果旧版遗留了 localStorage 群聊状态,清除它
|
|
354
|
+
StatePersistence.remove('currentView');
|
|
355
|
+
StatePersistence.remove('currentGroupId');
|
|
357
356
|
},
|
|
358
357
|
/** 标记 setup 已完成(避免每次刷新弹出向导) */
|
|
359
358
|
markSetupDone() { StatePersistence.save('setupDone', true); },
|
|
@@ -369,14 +368,17 @@ async function initChat() {
|
|
|
369
368
|
// [v1.16.12] 初始化附件上传 UI
|
|
370
369
|
initAttachmentUI();
|
|
371
370
|
|
|
372
|
-
// URL 参数处理: ?a=xxx&mode=exec&s=xxx&popout=1(agent/session 使用 UrlCodec 编码)
|
|
371
|
+
// URL 参数处理: ?a=xxx&mode=exec&s=xxx&g=xxx&popout=1(agent/session 使用 UrlCodec 编码)
|
|
373
372
|
const urlParams = new URLSearchParams(window.location.search);
|
|
374
373
|
const urlAgent = UrlCodec.decode(urlParams.get('a') || '') || UrlCodec.decode(urlParams.get('agent') || '');
|
|
375
374
|
const urlMode = urlParams.get('mode');
|
|
376
375
|
const urlSession = UrlCodec.decode(urlParams.get('s') || '') || UrlCodec.decode(urlParams.get('session') || '');
|
|
377
376
|
const isPopout = urlParams.get('popout') === '1';
|
|
378
|
-
// [v1.23.
|
|
379
|
-
const
|
|
377
|
+
// [v1.23.81] 群聊纯数字 session ID(?g= 参数,优先级最高)
|
|
378
|
+
const urlGroupSession = urlParams.get('g') || '';
|
|
379
|
+
// 兼容旧版 ?group= 参数(从后台管理返回时的旧格式)
|
|
380
|
+
const urlGroupSessionLegacy = urlParams.get('from_group_session') || '';
|
|
381
|
+
const _effectiveGroupSession = urlGroupSession || urlGroupSessionLegacy;
|
|
380
382
|
if (urlMode === 'chat' || urlMode === 'exec') {
|
|
381
383
|
state.chatMode = urlMode;
|
|
382
384
|
}
|
|
@@ -462,7 +464,38 @@ async function initChat() {
|
|
|
462
464
|
document.title = (agentObj ? agentObj.name : urlAgent || 'MyAgent') + ' - MyAgent';
|
|
463
465
|
}
|
|
464
466
|
|
|
465
|
-
|
|
467
|
+
// [v1.23.81] 群聊恢复逻辑:从 URL 的 ?g= 纯数字 session ID 恢复(优先级最高)
|
|
468
|
+
// 不再依赖 JS 状态传递(from_group),完全通过 URL session ID 恢复
|
|
469
|
+
state._pendingGroupRestore = null; // 不再使用 localStorage 中的群聊状态
|
|
470
|
+
|
|
471
|
+
async function _restoreGroupBySession(sessionId) {
|
|
472
|
+
// 通过 API 将纯数字 session ID 解析为 group_id
|
|
473
|
+
try {
|
|
474
|
+
var resolveData = await api('/api/group-session/' + encodeURIComponent(sessionId));
|
|
475
|
+
if (resolveData && resolveData.group_id) {
|
|
476
|
+
if (typeof groups !== 'undefined' && groups.length > 0) {
|
|
477
|
+
selectGroup(resolveData.group_id);
|
|
478
|
+
} else {
|
|
479
|
+
// groups 尚未加载,等待后重试
|
|
480
|
+
setTimeout(function() { _restoreGroupBySession(sessionId); }, 200);
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
console.warn('群聊 session 解析失败:', sessionId);
|
|
484
|
+
}
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.warn('解析群聊 session 失败:', e);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 如果 URL 中有 ?g= 纯数字 session ID,优先恢复群聊
|
|
491
|
+
if (_effectiveGroupSession && /^[0-9]+$/.test(_effectiveGroupSession)) {
|
|
492
|
+
// 群聊模式:如果有 from_agent 参数,更新 state.activeAgent 以便后续群聊私聊时使用
|
|
493
|
+
if (urlAgent && urlAgent !== state.activeAgent) {
|
|
494
|
+
state.activeAgent = urlAgent;
|
|
495
|
+
StatePersistence.save('activeAgent', urlAgent);
|
|
496
|
+
}
|
|
497
|
+
_restoreGroupBySession(_effectiveGroupSession);
|
|
498
|
+
} else if (urlAgent) {
|
|
466
499
|
// URL 指定了 agent,校验后切换
|
|
467
500
|
var resolved = urlAgent;
|
|
468
501
|
if (!findAgentByPath(resolved)) {
|
|
@@ -497,23 +530,6 @@ async function initChat() {
|
|
|
497
530
|
}
|
|
498
531
|
// 如果 agent 一致,loadSessions() 内部已通过 _pendingSessionRestore 自动处理了
|
|
499
532
|
}
|
|
500
|
-
|
|
501
|
-
// [v1.23.79] 群聊恢复:等待 groups 加载完毕后再 selectGroup
|
|
502
|
-
function _restoreGroupChat(gid) {
|
|
503
|
-
if (typeof groups !== 'undefined' && groups.length > 0) {
|
|
504
|
-
selectGroup(gid);
|
|
505
|
-
} else {
|
|
506
|
-
setTimeout(function() { _restoreGroupChat(gid); }, 200);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
if (state._pendingGroupRestore && typeof selectGroup === 'function') {
|
|
510
|
-
var _gid = state._pendingGroupRestore;
|
|
511
|
-
state._pendingGroupRestore = null;
|
|
512
|
-
_restoreGroupChat(_gid);
|
|
513
|
-
}
|
|
514
|
-
if (urlGroup && typeof selectGroup === 'function') {
|
|
515
|
-
_restoreGroupChat(urlGroup);
|
|
516
|
-
}
|
|
517
533
|
}
|
|
518
534
|
|
|
519
535
|
// Run init: if DOMContentLoaded already fired (dynamic script load), run immediately
|
package/web/ui/chat/groupchat.js
CHANGED
|
@@ -167,10 +167,21 @@ async function selectGroup(gid) {
|
|
|
167
167
|
}
|
|
168
168
|
currentView = 'group';
|
|
169
169
|
currentGroupId = gid;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
|
|
171
|
+
// [v1.23.81] 获取群聊 session ID 并写入 URL(纯数字,用于导航恢复)
|
|
172
|
+
try {
|
|
173
|
+
var sessionData = await api('/api/groups/' + encodeURIComponent(gid) + '/session');
|
|
174
|
+
if (sessionData && sessionData.session_id) {
|
|
175
|
+
var sessionId = sessionData.session_id;
|
|
176
|
+
// 用群聊专用 ?g= 参数更新 URL(不刷新页面),纯数字 session ID
|
|
177
|
+
var newUrl = '/ui/chat/chat_container.html?g=' + sessionId;
|
|
178
|
+
if (typeof state !== 'undefined' && state.activeAgent) {
|
|
179
|
+
newUrl += '&a=' + encodeURIComponent(state.activeAgent);
|
|
180
|
+
}
|
|
181
|
+
window.history.replaceState({groupSession: sessionId, groupId: gid}, '', newUrl);
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.warn('获取群聊 session 失败:', e);
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
// [v1.23.51] 群聊模式:隐藏"新对话"按钮,搜索框改为搜索群聊
|
|
@@ -238,11 +249,19 @@ function exitGroupChat() {
|
|
|
238
249
|
currentView = 'chat';
|
|
239
250
|
currentGroupId = null;
|
|
240
251
|
groupMessages = [];
|
|
241
|
-
// [v1.23.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
// [v1.23.81] 清除群聊 ?g= session URL,恢复普通聊天 URL
|
|
253
|
+
var cleanUrl = '/ui/chat/chat_container.html';
|
|
254
|
+
if (typeof state !== 'undefined' && state.activeAgent && state.activeAgent !== 'default') {
|
|
255
|
+
cleanUrl += '?a=' + encodeURIComponent(state.activeAgent);
|
|
256
|
+
}
|
|
257
|
+
if (typeof state !== 'undefined' && state.activeSessionId && state.activeSessionId !== '__new__') {
|
|
258
|
+
cleanUrl += (cleanUrl.indexOf('?') >= 0 ? '&' : '?') + 's=' + encodeURIComponent(state.activeSessionId);
|
|
245
259
|
}
|
|
260
|
+
window.history.replaceState({}, '', cleanUrl);
|
|
261
|
+
// 清除群聊 localStorage 持久化状态
|
|
262
|
+
StatePersistence.remove('currentView');
|
|
263
|
+
StatePersistence.remove('currentGroupId');
|
|
264
|
+
|
|
246
265
|
// 清空残留的聊天状态,避免切回个人聊天时显示群聊消息
|
|
247
266
|
state.messages = [];
|
|
248
267
|
state.activeSessionId = null;
|
package/worklog.md
CHANGED
|
@@ -19,3 +19,21 @@ Work Log:
|
|
|
19
19
|
Stage Summary:
|
|
20
20
|
- 修复文件:admin-platforms.js, admin-core.js, index.html, chat_main.js, chat_container.html, api_server.py
|
|
21
21
|
- 版本:v1.23.57 已发布到 npm
|
|
22
|
+
---
|
|
23
|
+
Task ID: 1
|
|
24
|
+
Agent: main
|
|
25
|
+
Task: 修复从后台管理返回群聊时导航状态错误 + 群聊气泡宽度不一致
|
|
26
|
+
|
|
27
|
+
Work Log:
|
|
28
|
+
- 分析了从后台管理返回群聊时导航错误的根因:initChat()中selectAgent()未被await,与selectGroup()产生竞态,导致群聊UI被覆盖
|
|
29
|
+
- 发现goToAdmin()中使用了state.currentAgent(不存在),应该是state.activeAgent
|
|
30
|
+
- 重构了initChat()的agent/session/group恢复逻辑:当有pendingGroupRestore或urlGroup时,跳过selectAgent()避免竞态
|
|
31
|
+
- 修复了群聊气泡宽度不一致问题:为.message-row和.group-msg-row添加align-items:flex-start和flex:1规则
|
|
32
|
+
- 更新CSS版本缓存chat.css?v=11,JS版本?v=12
|
|
33
|
+
- 版本号升级到1.23.80
|
|
34
|
+
- 推送到GitHub,发布npm
|
|
35
|
+
|
|
36
|
+
Stage Summary:
|
|
37
|
+
- 修复了3个bug:goToAdmin()的state.currentAgent错误、selectAgent/selectGroup竞态条件、气泡宽度不一致
|
|
38
|
+
- 已推送GitHub (commit 379098e)
|
|
39
|
+
- 已发布npm myagent-ai@1.23.80
|