ec-web 0.0.27 → 0.0.29-test.0
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/dist/server.js +21 -3
- package/dist/sources/basic-settings.js +4 -3
- package/dist/sources/msg.js +9 -5
- package/dist/sources/relation-roles.js +5 -0
- package/dist/sources/role-definitions.js +7 -0
- package/dist/sources/session-origin.js +2 -2
- package/dist/sources/triggers.js +2 -12
- package/dist/static/app.js +732 -146
- package/dist/static/basic-settings.js +10 -9
- package/dist/static/index.html +33 -12
- package/dist/static/style.css +613 -38
- package/package.json +1 -1
package/dist/static/app.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* EC Web — 前端 WS 客户端 + 多视图渲染 */
|
|
2
2
|
|
|
3
3
|
const $ = (sel) => document.querySelector(sel);
|
|
4
|
-
const TOKEN_KEY = 'ecWatchToken';
|
|
5
4
|
const LANG_KEY = 'ecWatchLang';
|
|
6
5
|
const VIEW_KEY = 'ecWatchCurrentView';
|
|
7
6
|
const ROLE_SUBVIEW_KEY = 'ecWatchRoleSubview';
|
|
@@ -84,6 +83,12 @@ const translations = {
|
|
|
84
83
|
'common.loadingSuffix': '加载中',
|
|
85
84
|
'common.noChanges': '没有需要保存的改动',
|
|
86
85
|
'common.close': '关闭',
|
|
86
|
+
'common.back': '返回',
|
|
87
|
+
'common.openDetail': '打开详情',
|
|
88
|
+
'navigation.open': '打开导航',
|
|
89
|
+
'navigation.close': '关闭导航',
|
|
90
|
+
'navigation.pages': '页面导航',
|
|
91
|
+
'navigation.moreFilters': '更多筛选',
|
|
87
92
|
'common.rawJsonPreview': '原始 JSON 预览',
|
|
88
93
|
'common.inherited': '继承',
|
|
89
94
|
'common.noLimit': '无限制',
|
|
@@ -117,6 +122,7 @@ const translations = {
|
|
|
117
122
|
'theme.light': '浅色模式',
|
|
118
123
|
'theme.dark': '深色模式',
|
|
119
124
|
'theme.system': '跟随系统',
|
|
125
|
+
'theme.drawerLabel': '主题:{mode}',
|
|
120
126
|
'theme.buttonTitle': '主题:{mode};点击切换',
|
|
121
127
|
'language.buttonTitle': '切换语言',
|
|
122
128
|
|
|
@@ -134,6 +140,7 @@ const translations = {
|
|
|
134
140
|
'logs.lines.1000': '1000 行',
|
|
135
141
|
'logs.lines.2000': '2000 行',
|
|
136
142
|
'logs.follow': '跟随尾部',
|
|
143
|
+
'logs.moreFilters': '更多筛选',
|
|
137
144
|
'logs.live': '自动刷新',
|
|
138
145
|
'logs.meta': '{files} 个文件 · {size} · 更新于 {time}',
|
|
139
146
|
'logs.filtered': '筛选结果 {visible} 行(已加载 {total} 行)',
|
|
@@ -278,10 +285,13 @@ const translations = {
|
|
|
278
285
|
// Messages view
|
|
279
286
|
'messages.colTitle.aid': 'Agent',
|
|
280
287
|
'messages.colTitle.peers': 'Chats',
|
|
288
|
+
'messages.mobile.selection': '消息选择',
|
|
289
|
+
'messages.mobile.aid': 'Agent',
|
|
290
|
+
'messages.mobile.peer': 'Chats',
|
|
281
291
|
'messages.empty.selectAid': '← 选择一个 Agent',
|
|
282
292
|
'messages.empty.selectToView': '选择 Agent 查看消息',
|
|
283
293
|
'messages.empty.noAgents': '暂无 Agent',
|
|
284
|
-
'messages.empty.noPeers': '
|
|
294
|
+
'messages.empty.noPeers': '暂无 Chats',
|
|
285
295
|
'messages.empty.noMessages': '暂无消息',
|
|
286
296
|
'messages.loadOlder': '加载更早消息',
|
|
287
297
|
'messages.tag.group': '群聊',
|
|
@@ -305,7 +315,7 @@ const translations = {
|
|
|
305
315
|
'messages.msgType.command': '命令',
|
|
306
316
|
'messages.groupCount': '群 {count}',
|
|
307
317
|
'messages.privateCount': '单 {count}',
|
|
308
|
-
'messages.session.field.sessionId': '
|
|
318
|
+
'messages.session.field.sessionId': 'EC会话ID',
|
|
309
319
|
'messages.session.field.baseagent': 'Baseagent',
|
|
310
320
|
'messages.session.field.chatMode': '模式',
|
|
311
321
|
'messages.session.field.permissionMode': '权限',
|
|
@@ -316,6 +326,8 @@ const translations = {
|
|
|
316
326
|
'messages.session.status.idle': '空闲',
|
|
317
327
|
'messages.session.mode.interactive': '响应',
|
|
318
328
|
'messages.session.mode.proactive': '自主',
|
|
329
|
+
'messages.session.details.expand': '展开会话状态',
|
|
330
|
+
'messages.session.details.collapse': '收起会话状态',
|
|
319
331
|
|
|
320
332
|
// Sessions view
|
|
321
333
|
'sessions.filter.normal': '🔍 仅有效',
|
|
@@ -359,6 +371,8 @@ const translations = {
|
|
|
359
371
|
'sessions.fullView': '📜 完整视图',
|
|
360
372
|
'sessions.chatHint': '只看用户与 Agent 的对话,处理过程已折叠',
|
|
361
373
|
'sessions.fullHint': '显示全部消息',
|
|
374
|
+
'sessions.details.expand': '展开会话信息',
|
|
375
|
+
'sessions.details.collapse': '收起会话信息',
|
|
362
376
|
'sessions.procSummary': '⋯ {count} 条处理过程(思考·工具·结果)',
|
|
363
377
|
'sessions.empty.noDialogue': '该会话没有用户对话消息',
|
|
364
378
|
'sessions.turnType.userInput': '用户输入',
|
|
@@ -750,10 +764,13 @@ const translations = {
|
|
|
750
764
|
'triggers.lastUnrecorded': '未记录',
|
|
751
765
|
'triggers.collapse.expand': '展开 Agent 列表',
|
|
752
766
|
'triggers.collapse.collapse': '收起 Agent 列表',
|
|
767
|
+
'triggers.agent.daemon': 'DAEMON',
|
|
753
768
|
'triggers.empty.noAgents': '暂无 Agent',
|
|
754
769
|
'triggers.empty.noTriggers': '暂无触发器',
|
|
755
770
|
'triggers.empty.noAgentTriggers': '该 Agent 暂无触发器',
|
|
756
771
|
'triggers.th.status': '状态',
|
|
772
|
+
'triggers.th.agent': 'Agent',
|
|
773
|
+
'triggers.th.id': 'ID',
|
|
757
774
|
'triggers.th.subscription': '订阅',
|
|
758
775
|
'triggers.th.name': '名称',
|
|
759
776
|
'triggers.th.type': '类型',
|
|
@@ -1024,6 +1041,12 @@ const translations = {
|
|
|
1024
1041
|
'common.loadingSuffix': 'loading',
|
|
1025
1042
|
'common.noChanges': 'No changes to save',
|
|
1026
1043
|
'common.close': 'Close',
|
|
1044
|
+
'common.back': 'Back',
|
|
1045
|
+
'common.openDetail': 'Open details',
|
|
1046
|
+
'navigation.open': 'Open navigation',
|
|
1047
|
+
'navigation.close': 'Close navigation',
|
|
1048
|
+
'navigation.pages': 'Page navigation',
|
|
1049
|
+
'navigation.moreFilters': 'More filters',
|
|
1027
1050
|
'common.rawJsonPreview': 'Raw JSON Preview',
|
|
1028
1051
|
'common.inherited': 'Inherited',
|
|
1029
1052
|
'common.noLimit': 'No limit',
|
|
@@ -1057,6 +1080,7 @@ const translations = {
|
|
|
1057
1080
|
'theme.light': 'Light',
|
|
1058
1081
|
'theme.dark': 'Dark',
|
|
1059
1082
|
'theme.system': 'System',
|
|
1083
|
+
'theme.drawerLabel': 'Theme: {mode}',
|
|
1060
1084
|
'theme.buttonTitle': 'Theme: {mode}; click to switch',
|
|
1061
1085
|
'language.buttonTitle': 'Switch language',
|
|
1062
1086
|
|
|
@@ -1074,6 +1098,7 @@ const translations = {
|
|
|
1074
1098
|
'logs.lines.1000': '1000 Lines',
|
|
1075
1099
|
'logs.lines.2000': '2000 Lines',
|
|
1076
1100
|
'logs.follow': 'Follow tail',
|
|
1101
|
+
'logs.moreFilters': 'More filters',
|
|
1077
1102
|
'logs.live': 'Auto refresh',
|
|
1078
1103
|
'logs.meta': '{files} files · {size} · updated {time}',
|
|
1079
1104
|
'logs.filtered': '{visible} filtered ({total} loaded)',
|
|
@@ -1218,6 +1243,9 @@ const translations = {
|
|
|
1218
1243
|
// Messages view
|
|
1219
1244
|
'messages.colTitle.aid': 'Agent',
|
|
1220
1245
|
'messages.colTitle.peers': 'Chats',
|
|
1246
|
+
'messages.mobile.selection': 'Message selection',
|
|
1247
|
+
'messages.mobile.aid': 'Agent',
|
|
1248
|
+
'messages.mobile.peer': 'Chats',
|
|
1221
1249
|
'messages.empty.selectAid': '← Select Agent',
|
|
1222
1250
|
'messages.empty.selectToView': 'Select Agent to view messages',
|
|
1223
1251
|
'messages.empty.noAgents': 'No agents',
|
|
@@ -1245,7 +1273,7 @@ const translations = {
|
|
|
1245
1273
|
'messages.msgType.command': 'Command',
|
|
1246
1274
|
'messages.groupCount': '{count} groups',
|
|
1247
1275
|
'messages.privateCount': '{count} private',
|
|
1248
|
-
'messages.session.field.sessionId': '
|
|
1276
|
+
'messages.session.field.sessionId': 'EC Session ID',
|
|
1249
1277
|
'messages.session.field.baseagent': 'Baseagent',
|
|
1250
1278
|
'messages.session.field.chatMode': 'Mode',
|
|
1251
1279
|
'messages.session.field.permissionMode': 'Perm',
|
|
@@ -1256,6 +1284,8 @@ const translations = {
|
|
|
1256
1284
|
'messages.session.status.idle': 'Idle',
|
|
1257
1285
|
'messages.session.mode.interactive': 'Interactive',
|
|
1258
1286
|
'messages.session.mode.proactive': 'Proactive',
|
|
1287
|
+
'messages.session.details.expand': 'Expand session status',
|
|
1288
|
+
'messages.session.details.collapse': 'Collapse session status',
|
|
1259
1289
|
|
|
1260
1290
|
// Sessions view
|
|
1261
1291
|
'sessions.filter.normal': '🔍 Valid Only',
|
|
@@ -1299,6 +1329,8 @@ const translations = {
|
|
|
1299
1329
|
'sessions.fullView': '📜 Full View',
|
|
1300
1330
|
'sessions.chatHint': 'Only user and Agent messages; processing is collapsed',
|
|
1301
1331
|
'sessions.fullHint': 'Show all messages',
|
|
1332
|
+
'sessions.details.expand': 'Expand session details',
|
|
1333
|
+
'sessions.details.collapse': 'Collapse session details',
|
|
1302
1334
|
'sessions.procSummary': '⋯ {count} processing steps (thinking · tools · results)',
|
|
1303
1335
|
'sessions.empty.noDialogue': 'No user dialogue messages in this session',
|
|
1304
1336
|
'sessions.turnType.userInput': 'User Input',
|
|
@@ -1690,10 +1722,13 @@ const translations = {
|
|
|
1690
1722
|
'triggers.lastUnrecorded': 'Not recorded',
|
|
1691
1723
|
'triggers.collapse.expand': 'Expand Agent List',
|
|
1692
1724
|
'triggers.collapse.collapse': 'Collapse Agent List',
|
|
1725
|
+
'triggers.agent.daemon': 'DAEMON',
|
|
1693
1726
|
'triggers.empty.noAgents': 'No agents',
|
|
1694
1727
|
'triggers.empty.noTriggers': 'No triggers',
|
|
1695
1728
|
'triggers.empty.noAgentTriggers': 'No triggers for this agent',
|
|
1696
1729
|
'triggers.th.status': 'Status',
|
|
1730
|
+
'triggers.th.agent': 'Agent',
|
|
1731
|
+
'triggers.th.id': 'ID',
|
|
1697
1732
|
'triggers.th.subscription': 'Subscription',
|
|
1698
1733
|
'triggers.th.name': 'Name',
|
|
1699
1734
|
'triggers.th.type': 'Type',
|
|
@@ -1922,9 +1957,15 @@ function updateI18n() {
|
|
|
1922
1957
|
const key = el.getAttribute('data-i18n-title');
|
|
1923
1958
|
el.title = t(key);
|
|
1924
1959
|
});
|
|
1960
|
+
document.querySelectorAll('[data-i18n-aria-label]').forEach(el => {
|
|
1961
|
+
const key = el.getAttribute('data-i18n-aria-label');
|
|
1962
|
+
el.setAttribute('aria-label', t(key));
|
|
1963
|
+
});
|
|
1925
1964
|
// 更新 html lang 属性
|
|
1926
1965
|
document.documentElement.lang = currentLang;
|
|
1927
1966
|
updateThemeButton();
|
|
1967
|
+
updateMobilePageTitle();
|
|
1968
|
+
renderMobileNavigation();
|
|
1928
1969
|
window.refreshBasicSettingsI18n?.();
|
|
1929
1970
|
}
|
|
1930
1971
|
|
|
@@ -1939,7 +1980,7 @@ function toggleLang() {
|
|
|
1939
1980
|
}
|
|
1940
1981
|
|
|
1941
1982
|
// ── 基础路径 ──
|
|
1942
|
-
// 本地直连时页面在 "/",经 AUN Service Proxy 时页面在 "
|
|
1983
|
+
// 本地直连时页面在 "/",经 AUN Service Proxy 时页面在 "/<host>/ecweb/"。
|
|
1943
1984
|
// 取当前页面所在目录(含尾斜杠)作为所有 API/WS 的前缀,使绝对路径在两种
|
|
1944
1985
|
// 部署下都正确(proxy-server 用首段路径选服务,前缀不能丢)。
|
|
1945
1986
|
const BASE = (() => {
|
|
@@ -1952,6 +1993,9 @@ const BASE = (() => {
|
|
|
1952
1993
|
}
|
|
1953
1994
|
return location.pathname.replace(/[^/]*$/, '');
|
|
1954
1995
|
})();
|
|
1996
|
+
// 同源的不同主机共享 localStorage,token 必须按完整代理路径隔离。
|
|
1997
|
+
// 旧 ecWatchToken 无法确定所属主机,不迁移;远程访问需重新配对一次。
|
|
1998
|
+
const TOKEN_KEY = `ecWatchToken:${BASE}`;
|
|
1955
1999
|
const apiUrl = (p) => BASE + p.replace(/^\/+/, '');
|
|
1956
2000
|
const authApiUrl = (p) => {
|
|
1957
2001
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
@@ -2280,7 +2324,13 @@ setInterval(() => {
|
|
|
2280
2324
|
|
|
2281
2325
|
// ── Tab 切换 ──
|
|
2282
2326
|
let msgSel = { aid: null, peer: null, before: null };
|
|
2327
|
+
let msgCollapsedAids = new Set();
|
|
2328
|
+
// 消息详情扩展信息只在当前页面生命周期内记忆,不写入 sessionStorage。
|
|
2329
|
+
// 以 Agent/Chats/EC 会话 ID 区分,切换会话时仍默认展开。
|
|
2330
|
+
const msgSessionDetailsExpanded = new Map();
|
|
2283
2331
|
let sessSel = { sessionId: null, project: null, baseagent: null, turnBefore: null };
|
|
2332
|
+
// 会话详情顶部信息只在当前页面生命周期内记忆展开/收起状态。
|
|
2333
|
+
const sessHeaderExpanded = new Map();
|
|
2284
2334
|
let trigSel = { agent: null };
|
|
2285
2335
|
let trigAgentsCollapsed = false;
|
|
2286
2336
|
const _trigOps = new Map(); // Map<triggerId, string>
|
|
@@ -2290,9 +2340,98 @@ let _triggerTest = null;
|
|
|
2290
2340
|
let sessSearch = '';
|
|
2291
2341
|
let sessShowAll = false; // false=只显示有效会话;true=显示全部
|
|
2292
2342
|
let sessSourceFilter = 'all'; // all / trigger / agent / cli / user / unknown
|
|
2343
|
+
let sessListScroll = { layout: 0, items: 0 };
|
|
2293
2344
|
let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
|
|
2294
2345
|
let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
|
|
2295
2346
|
let logSel = { source: null, level: 'all', module: 'all', search: '', limit: 500, follow: true };
|
|
2347
|
+
const SESSION_UI_KEY = 'ecWebUiState';
|
|
2348
|
+
let mobileNavOpen = false;
|
|
2349
|
+
let detailViewState = { msg: false, session: false };
|
|
2350
|
+
let historySyncing = false;
|
|
2351
|
+
let sessScrollSaveFrame = null;
|
|
2352
|
+
|
|
2353
|
+
function readSessionUiState() {
|
|
2354
|
+
try {
|
|
2355
|
+
const raw = sessionStorage.getItem(SESSION_UI_KEY);
|
|
2356
|
+
if (!raw) return;
|
|
2357
|
+
const saved = JSON.parse(raw);
|
|
2358
|
+
if (saved.msg && typeof saved.msg === 'object') msgSel = { ...msgSel, ...saved.msg, before: null };
|
|
2359
|
+
if (saved.session && typeof saved.session === 'object') sessSel = { ...sessSel, ...saved.session, turnBefore: null };
|
|
2360
|
+
if (saved.trigger && typeof saved.trigger === 'object') trigSel = { ...trigSel, ...saved.trigger };
|
|
2361
|
+
if (typeof saved.sessShowAll === 'boolean') sessShowAll = saved.sessShowAll;
|
|
2362
|
+
if (typeof saved.sessSourceFilter === 'string') sessSourceFilter = saved.sessSourceFilter;
|
|
2363
|
+
if (saved.sessionListScroll && typeof saved.sessionListScroll === 'object') {
|
|
2364
|
+
sessListScroll = {
|
|
2365
|
+
layout: Math.max(0, Number(saved.sessionListScroll.layout) || 0),
|
|
2366
|
+
items: Math.max(0, Number(saved.sessionListScroll.items) || 0),
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
if (saved.log && typeof saved.log === 'object') logSel = { ...logSel, ...saved.log };
|
|
2370
|
+
if (saved.detail && typeof saved.detail === 'object') detailViewState = { ...detailViewState, ...saved.detail };
|
|
2371
|
+
} catch {
|
|
2372
|
+
// sessionStorage may be unavailable in private or embedded contexts.
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
function saveSessionUiState() {
|
|
2377
|
+
try {
|
|
2378
|
+
captureSessionListScroll();
|
|
2379
|
+
sessionStorage.setItem(SESSION_UI_KEY, JSON.stringify({
|
|
2380
|
+
msg: { aid: msgSel.aid, peer: msgSel.peer },
|
|
2381
|
+
session: { ...sessSel, turnBefore: null },
|
|
2382
|
+
trigger: { agent: trigSel.agent },
|
|
2383
|
+
sessShowAll,
|
|
2384
|
+
sessSourceFilter,
|
|
2385
|
+
sessionListScroll: sessListScroll,
|
|
2386
|
+
log: { ...logSel },
|
|
2387
|
+
detail: { ...detailViewState },
|
|
2388
|
+
}));
|
|
2389
|
+
} catch {
|
|
2390
|
+
// Keep the in-memory state when storage is unavailable.
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
function captureSessionListScroll() {
|
|
2395
|
+
const layout = document.querySelector('.sess-layout');
|
|
2396
|
+
const items = document.querySelector('#sess-list .sess-items');
|
|
2397
|
+
if (layout) sessListScroll.layout = Math.max(0, layout.scrollTop || 0);
|
|
2398
|
+
if (items) sessListScroll.items = Math.max(0, items.scrollTop || 0);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
function restoreSessionListScroll() {
|
|
2402
|
+
const layout = document.querySelector('.sess-layout');
|
|
2403
|
+
const items = document.querySelector('#sess-list .sess-items');
|
|
2404
|
+
if (layout) layout.scrollTop = sessListScroll.layout;
|
|
2405
|
+
if (items) items.scrollTop = sessListScroll.items;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
function bindSessionListScroll() {
|
|
2409
|
+
const layout = document.querySelector('.sess-layout');
|
|
2410
|
+
if (layout && layout.dataset.scrollBound !== '1') {
|
|
2411
|
+
layout.dataset.scrollBound = '1';
|
|
2412
|
+
layout.addEventListener('scroll', () => {
|
|
2413
|
+
captureSessionListScroll();
|
|
2414
|
+
scheduleSessionListStateSave();
|
|
2415
|
+
}, { passive: true });
|
|
2416
|
+
}
|
|
2417
|
+
const items = document.querySelector('#sess-list .sess-items');
|
|
2418
|
+
if (items) items.addEventListener('scroll', () => {
|
|
2419
|
+
captureSessionListScroll();
|
|
2420
|
+
scheduleSessionListStateSave();
|
|
2421
|
+
}, { passive: true });
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
function scheduleSessionListStateSave() {
|
|
2425
|
+
if (sessScrollSaveFrame != null) return;
|
|
2426
|
+
const flush = () => {
|
|
2427
|
+
sessScrollSaveFrame = null;
|
|
2428
|
+
saveSessionUiState();
|
|
2429
|
+
};
|
|
2430
|
+
if (typeof window.requestAnimationFrame === 'function') sessScrollSaveFrame = window.requestAnimationFrame(flush);
|
|
2431
|
+
else sessScrollSaveFrame = window.setTimeout(flush, 0);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
readSessionUiState();
|
|
2296
2435
|
// ECWeb 界面的 base agent 展示名统一走这里的 i18n 键,而不是父包别名表:
|
|
2297
2436
|
// 别名表面向 CLI/日志(claude → "Claude Code"、ecagent → "ecagent"),
|
|
2298
2437
|
// 面板里应保持简洁一致(Claude / Codex / Gemini / EC Agent)。
|
|
@@ -2369,7 +2508,133 @@ function setRoleManagementSubview(subview, options = {}) {
|
|
|
2369
2508
|
if (options.render !== false) renderRoleManagementSubview();
|
|
2370
2509
|
}
|
|
2371
2510
|
|
|
2372
|
-
|
|
2511
|
+
const VIEW_LABEL_KEYS = {
|
|
2512
|
+
agents: 'tab.agents', msg: 'tab.messages', session: 'tab.sessions', triggers: 'tab.triggers',
|
|
2513
|
+
cache: 'tab.cache', system: 'tab.system', logs: 'tab.logs', usage: 'tab.usage', roleManagement: 'tab.roleManagement',
|
|
2514
|
+
};
|
|
2515
|
+
|
|
2516
|
+
function normalizedView(view) {
|
|
2517
|
+
if (view === 'monitor') return 'system';
|
|
2518
|
+
if (view === 'roles' || view === 'roleDefinitions') return 'roleManagement';
|
|
2519
|
+
return ['agents', 'msg', 'session', 'triggers', 'cache', 'system', 'logs', 'usage', 'roleManagement'].includes(view)
|
|
2520
|
+
? view
|
|
2521
|
+
: 'agents';
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
function historySnapshot() {
|
|
2525
|
+
return {
|
|
2526
|
+
ecweb: true,
|
|
2527
|
+
view: currentView,
|
|
2528
|
+
detail: { ...detailViewState },
|
|
2529
|
+
roleManagementSubview,
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
function updateMobilePageTitle() {
|
|
2534
|
+
const title = $('#mobile-page-title');
|
|
2535
|
+
if (title) title.textContent = t(VIEW_LABEL_KEYS[currentView] || 'tab.agents');
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
function applyDetailClasses() {
|
|
2539
|
+
['msg', 'session'].forEach(view => {
|
|
2540
|
+
const el = $('#view-' + view);
|
|
2541
|
+
if (!el) return;
|
|
2542
|
+
el.classList.toggle('detail-fullscreen', currentView === view && detailViewState[view] === true);
|
|
2543
|
+
});
|
|
2544
|
+
saveSessionUiState();
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function enterDetail(view, options = {}) {
|
|
2548
|
+
if (!['msg', 'session'].includes(view) || currentView !== view) return;
|
|
2549
|
+
if (detailViewState[view]) return;
|
|
2550
|
+
detailViewState[view] = true;
|
|
2551
|
+
applyDetailClasses();
|
|
2552
|
+
if (options.history !== false && !historySyncing) history.pushState(historySnapshot(), '', location.href);
|
|
2553
|
+
renderView(view);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
function leaveDetail(view, options = {}) {
|
|
2557
|
+
if (!['msg', 'session'].includes(view) || !detailViewState[view]) return;
|
|
2558
|
+
if (options.history !== false && !historySyncing && history.state?.ecweb && history.state.detail?.[view]) {
|
|
2559
|
+
history.back();
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
detailViewState[view] = false;
|
|
2563
|
+
applyDetailClasses();
|
|
2564
|
+
renderView(view);
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
function restoreHistoryState(snapshot) {
|
|
2568
|
+
const next = snapshot && snapshot.ecweb ? snapshot : { ecweb: true, view: currentView, detail: {} };
|
|
2569
|
+
historySyncing = true;
|
|
2570
|
+
if (next.roleManagementSubview) roleManagementSubview = next.roleManagementSubview;
|
|
2571
|
+
detailViewState = { msg: false, session: false };
|
|
2572
|
+
switchView(next.view || 'agents', { history: false });
|
|
2573
|
+
detailViewState = { msg: false, session: false, ...(next.detail || {}) };
|
|
2574
|
+
historySyncing = false;
|
|
2575
|
+
applyDetailClasses();
|
|
2576
|
+
if (currentView === 'msg' || currentView === 'session') renderView(currentView);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
function initHistory() {
|
|
2580
|
+
const initial = history.state && history.state.ecweb ? history.state : historySnapshot();
|
|
2581
|
+
history.replaceState(initial, '', location.href);
|
|
2582
|
+
window.addEventListener('popstate', event => restoreHistoryState(event.state));
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
function renderMobileNavigation() {
|
|
2586
|
+
const list = $('.mobile-nav-list');
|
|
2587
|
+
if (!list) return;
|
|
2588
|
+
list.innerHTML = [...document.querySelectorAll('.tabs .tab')].map(tab => {
|
|
2589
|
+
const view = tab.dataset.view;
|
|
2590
|
+
const label = t(tab.dataset.i18n || VIEW_LABEL_KEYS[view] || view);
|
|
2591
|
+
return `<button type="button" class="mobile-nav-item${view === currentView ? ' active' : ''}" data-mobile-view="${esc(view)}">${esc(label)}</button>`;
|
|
2592
|
+
}).join('');
|
|
2593
|
+
list.querySelectorAll('[data-mobile-view]').forEach(button => {
|
|
2594
|
+
button.onclick = () => {
|
|
2595
|
+
closeMobileNavigation();
|
|
2596
|
+
switchView(button.dataset.mobileView);
|
|
2597
|
+
};
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
function setMobileNavigation(open) {
|
|
2602
|
+
mobileNavOpen = Boolean(open);
|
|
2603
|
+
const drawer = $('#mobile-nav-drawer');
|
|
2604
|
+
const backdrop = $('#mobile-nav-backdrop');
|
|
2605
|
+
const menu = $('#mobile-menu-btn');
|
|
2606
|
+
if (drawer) {
|
|
2607
|
+
drawer.classList.toggle('open', mobileNavOpen);
|
|
2608
|
+
drawer.setAttribute('aria-hidden', String(!mobileNavOpen));
|
|
2609
|
+
}
|
|
2610
|
+
if (backdrop) {
|
|
2611
|
+
backdrop.hidden = !mobileNavOpen;
|
|
2612
|
+
backdrop.classList.toggle('open', mobileNavOpen);
|
|
2613
|
+
}
|
|
2614
|
+
if (menu) menu.setAttribute('aria-expanded', String(mobileNavOpen));
|
|
2615
|
+
document.body.classList.toggle('mobile-nav-open', mobileNavOpen);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
function closeMobileNavigation() { setMobileNavigation(false); }
|
|
2619
|
+
|
|
2620
|
+
function initMobileNavigation() {
|
|
2621
|
+
renderMobileNavigation();
|
|
2622
|
+
$('#mobile-menu-btn')?.addEventListener('click', () => setMobileNavigation(!mobileNavOpen));
|
|
2623
|
+
$('#mobile-nav-close')?.addEventListener('click', closeMobileNavigation);
|
|
2624
|
+
$('#mobile-nav-backdrop')?.addEventListener('click', closeMobileNavigation);
|
|
2625
|
+
document.querySelectorAll('[data-mobile-tool]').forEach(button => {
|
|
2626
|
+
button.addEventListener('click', () => {
|
|
2627
|
+
const tool = button.dataset.mobileTool;
|
|
2628
|
+
closeMobileNavigation();
|
|
2629
|
+
if (tool === 'lang') toggleLang();
|
|
2630
|
+
else if (tool === 'theme') $('#theme-btn')?.click();
|
|
2631
|
+
else if (tool === 'settings') $('#basic-settings-btn')?.click();
|
|
2632
|
+
else if (tool === 'logout') $('#logout-btn')?.click();
|
|
2633
|
+
});
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
function switchView(view, options = {}) {
|
|
2373
2638
|
if (view === 'monitor') {
|
|
2374
2639
|
systemContentMode = 'monitor';
|
|
2375
2640
|
view = 'system';
|
|
@@ -2378,10 +2643,19 @@ function switchView(view) {
|
|
|
2378
2643
|
roleManagementSubview = view === 'roles' ? 'assignments' : 'definitions';
|
|
2379
2644
|
view = 'roleManagement';
|
|
2380
2645
|
}
|
|
2646
|
+
view = normalizedView(view);
|
|
2647
|
+
if (currentView !== view) {
|
|
2648
|
+
detailViewState = { msg: false, session: false };
|
|
2649
|
+
}
|
|
2381
2650
|
currentView = view;
|
|
2382
2651
|
localStorage.setItem(VIEW_KEY, view);
|
|
2383
2652
|
document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.view === view));
|
|
2384
2653
|
document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === 'view-' + view));
|
|
2654
|
+
updateMobilePageTitle();
|
|
2655
|
+
renderMobileNavigation();
|
|
2656
|
+
applyDetailClasses();
|
|
2657
|
+
if (options.replaceHistory) history.replaceState(historySnapshot(), '', location.href);
|
|
2658
|
+
else if (options.history !== false && !historySyncing && options.fromHistory !== true) history.pushState(historySnapshot(), '', location.href);
|
|
2385
2659
|
// 切换时按当前选择恢复订阅
|
|
2386
2660
|
if (view === 'msg') subscribe('msg', msgSel);
|
|
2387
2661
|
else if (view === 'session') subscribe('session', sessSel);
|
|
@@ -2413,6 +2687,7 @@ function initTabs() {
|
|
|
2413
2687
|
document.querySelector(`[data-role-subview="${next}"]`)?.focus();
|
|
2414
2688
|
};
|
|
2415
2689
|
});
|
|
2690
|
+
initMobileNavigation();
|
|
2416
2691
|
}
|
|
2417
2692
|
|
|
2418
2693
|
function renderView(view) {
|
|
@@ -2516,37 +2791,49 @@ function bindLogControls() {
|
|
|
2516
2791
|
const search = $('#log-search');
|
|
2517
2792
|
const limit = $('#log-limit');
|
|
2518
2793
|
const follow = $('#log-follow');
|
|
2794
|
+
const moreFilters = $('#log-more-filters');
|
|
2519
2795
|
if (!source || source.dataset.bound === '1') return;
|
|
2520
2796
|
source.dataset.bound = '1';
|
|
2521
2797
|
source.addEventListener('change', () => {
|
|
2522
2798
|
logSel.source = source.value || null;
|
|
2799
|
+
saveSessionUiState();
|
|
2523
2800
|
$('#log-stream').innerHTML = `<div class="empty">${t('common.loading')}</div>`;
|
|
2524
2801
|
subscribe('logs', { source: logSel.source, limit: logSel.limit });
|
|
2525
2802
|
});
|
|
2526
2803
|
level.addEventListener('change', () => {
|
|
2527
2804
|
logSel.level = level.value || 'all';
|
|
2805
|
+
saveSessionUiState();
|
|
2528
2806
|
renderLogs(state.logs);
|
|
2529
2807
|
});
|
|
2530
2808
|
module.addEventListener('change', () => {
|
|
2531
2809
|
logSel.module = module.value || 'all';
|
|
2810
|
+
saveSessionUiState();
|
|
2532
2811
|
renderLogs(state.logs);
|
|
2533
2812
|
});
|
|
2534
2813
|
search.addEventListener('input', () => {
|
|
2535
2814
|
logSel.search = search.value || '';
|
|
2815
|
+
saveSessionUiState();
|
|
2536
2816
|
renderLogs(state.logs);
|
|
2537
2817
|
});
|
|
2538
2818
|
limit.addEventListener('change', () => {
|
|
2539
2819
|
logSel.limit = Number(limit.value) || 500;
|
|
2820
|
+
saveSessionUiState();
|
|
2540
2821
|
$('#log-stream').innerHTML = `<div class="empty">${t('common.loading')}</div>`;
|
|
2541
2822
|
subscribe('logs', { source: logSel.source, limit: logSel.limit });
|
|
2542
2823
|
});
|
|
2543
2824
|
follow.addEventListener('change', () => {
|
|
2544
2825
|
logSel.follow = follow.checked;
|
|
2826
|
+
saveSessionUiState();
|
|
2545
2827
|
if (logSel.follow) {
|
|
2546
2828
|
const stream = $('#log-stream');
|
|
2547
2829
|
stream.scrollTop = stream.scrollHeight;
|
|
2548
2830
|
}
|
|
2549
2831
|
});
|
|
2832
|
+
if (moreFilters) moreFilters.addEventListener('click', () => {
|
|
2833
|
+
const toolbar = moreFilters.closest('.log-toolbar');
|
|
2834
|
+
const expanded = toolbar?.classList.toggle('filters-expanded') || false;
|
|
2835
|
+
moreFilters.setAttribute('aria-expanded', String(expanded));
|
|
2836
|
+
});
|
|
2550
2837
|
}
|
|
2551
2838
|
|
|
2552
2839
|
function renderLogs(data) {
|
|
@@ -2556,6 +2843,7 @@ function renderLogs(data) {
|
|
|
2556
2843
|
const search = $('#log-search');
|
|
2557
2844
|
const limit = $('#log-limit');
|
|
2558
2845
|
const follow = $('#log-follow');
|
|
2846
|
+
const moreFilters = $('#log-more-filters');
|
|
2559
2847
|
const meta = $('#log-meta-info');
|
|
2560
2848
|
const filterResult = $('#log-filter-result');
|
|
2561
2849
|
const stream = $('#log-stream');
|
|
@@ -2566,6 +2854,7 @@ function renderLogs(data) {
|
|
|
2566
2854
|
search.value = logSel.search;
|
|
2567
2855
|
limit.value = String(logSel.limit);
|
|
2568
2856
|
follow.checked = logSel.follow;
|
|
2857
|
+
if (moreFilters) moreFilters.textContent = t('logs.moreFilters');
|
|
2569
2858
|
if (!data) {
|
|
2570
2859
|
stream.innerHTML = `<div class="empty">${t('common.loading')}</div>`;
|
|
2571
2860
|
return;
|
|
@@ -2889,6 +3178,13 @@ function initMsgTipFloat() {
|
|
|
2889
3178
|
if (to && (wrap.contains(to) || (floatEl && floatEl.contains(to)))) return;
|
|
2890
3179
|
scheduleHide();
|
|
2891
3180
|
});
|
|
3181
|
+
document.addEventListener('click', (e) => {
|
|
3182
|
+
if (!floatEl || !floatEl.classList.contains('show')) return;
|
|
3183
|
+
if (floatEl.contains(e.target)) return;
|
|
3184
|
+
const wrap = e.target.closest && e.target.closest('.ag-msg-wrap');
|
|
3185
|
+
if (wrap && wrap === curWrap) return;
|
|
3186
|
+
hideNow();
|
|
3187
|
+
}, true);
|
|
2892
3188
|
// 滚动时隐藏,避免浮层与行脱节
|
|
2893
3189
|
window.addEventListener('scroll', hideNow, true);
|
|
2894
3190
|
}
|
|
@@ -3138,7 +3434,9 @@ function renderAgents(data) {
|
|
|
3138
3434
|
if (!disabledAgents.length) {
|
|
3139
3435
|
html += `<div class="empty">${t('agents.empty.disabled')}</div>`;
|
|
3140
3436
|
} else {
|
|
3141
|
-
html +=
|
|
3437
|
+
html += '<table class="agents-table agents-table-disabled"><thead><tr>' +
|
|
3438
|
+
`<th>${t('agents.th.agent')}</th><th>${t('agents.th.operations')}</th>` +
|
|
3439
|
+
'</tr></thead><tbody>';
|
|
3142
3440
|
for (const ag of disabledAgents) {
|
|
3143
3441
|
const busy = _agentOps.has(ag.aid);
|
|
3144
3442
|
const ops = busy
|
|
@@ -3146,7 +3444,6 @@ function renderAgents(data) {
|
|
|
3146
3444
|
: agentDisabledOpsHtml(ag.aid);
|
|
3147
3445
|
html += `<tr class="ag-main">` +
|
|
3148
3446
|
`<td><div class="ag-id"><span class="dot off"></span><span class="ag-id-text"><span class="ag-name">${esc(ag.displayName || shortAid(ag.aid))}${agentLifecycleBadge(ag.lifecycle)}</span><span class="ag-aid">${esc(ag.aid)}</span></span></div></td>` +
|
|
3149
|
-
`<td style="font-size:11px;font-family:monospace">${esc(ag.projectPath || '—')}</td>` +
|
|
3150
3447
|
`<td class="agent-ops-cell">${ops}</td></tr>`;
|
|
3151
3448
|
}
|
|
3152
3449
|
html += '</tbody></table>';
|
|
@@ -3171,7 +3468,7 @@ function renderAgents(data) {
|
|
|
3171
3468
|
return;
|
|
3172
3469
|
}
|
|
3173
3470
|
|
|
3174
|
-
html += '<table><thead><tr>' +
|
|
3471
|
+
html += '<table class="agents-table agents-table-enabled"><thead><tr>' +
|
|
3175
3472
|
`<th>${t('agents.th.aid')}</th><th>${t('agents.th.work')}</th><th>${t('agents.th.queue')}</th><th>${t('agents.th.model')}</th><th>${t('agents.th.runtime')}</th>` +
|
|
3176
3473
|
`<th>${t('agents.th.received')}</th><th>${t('agents.th.sent')}</th><th>${t('agents.th.errors')}</th><th>${t('agents.th.interrupts')}</th><th>${t('agents.th.completed')}</th>` +
|
|
3177
3474
|
`<th>${t('agents.th.lastActivity')}</th><th>${t('agents.th.operations')}</th>` +
|
|
@@ -3212,10 +3509,11 @@ function renderAgents(data) {
|
|
|
3212
3509
|
const recent = (s.recentMessages || []);
|
|
3213
3510
|
const tipHtml = recentMsgTooltipHtml(recent);
|
|
3214
3511
|
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3512
|
+
if (preview) {
|
|
3513
|
+
html += `<tr class="ag-sub"><td colspan="12"><div class="ag-info">` +
|
|
3514
|
+
`<div class="ag-msg-wrap">${tipHtml}<div class="ag-msg">${preview}</div></div>` +
|
|
3515
|
+
'</div></td></tr>';
|
|
3516
|
+
}
|
|
3219
3517
|
}
|
|
3220
3518
|
html += '</tbody></table>';
|
|
3221
3519
|
if (data.daemonRunning) {
|
|
@@ -3358,6 +3656,83 @@ function msgChannelTone(channelType) {
|
|
|
3358
3656
|
: 'other';
|
|
3359
3657
|
}
|
|
3360
3658
|
|
|
3659
|
+
function renderMsgMobileControls(aids, peers, scopeMatches) {
|
|
3660
|
+
const el = $('#msg-mobile-controls');
|
|
3661
|
+
if (!el) return;
|
|
3662
|
+
const aidOptions = aids.map(item => {
|
|
3663
|
+
const name = item.displayName || (item.selfAID && item.selfAID !== 'unknown' ? shortAid(item.selfAID) : 'unknown');
|
|
3664
|
+
return `<option value="${esc(item.aid)}"${item.aid === msgSel.aid ? ' selected' : ''}>${esc(name)}</option>`;
|
|
3665
|
+
}).join('');
|
|
3666
|
+
const peerOptions = peers.map(item => {
|
|
3667
|
+
const name = item.chatType === 'group'
|
|
3668
|
+
? (item.groupName || item.peerName || item.groupId || item.peerId)
|
|
3669
|
+
: (item.peerName || item.peerId);
|
|
3670
|
+
return `<option value="${esc(item.peerId)}"${item.peerId === msgSel.peer ? ' selected' : ''}>${esc(shortId(name))}</option>`;
|
|
3671
|
+
}).join('');
|
|
3672
|
+
el.innerHTML = `<label><span>${esc(t('messages.mobile.aid'))}</span>` +
|
|
3673
|
+
`<select id="msg-mobile-aid"${aids.length ? '' : ' disabled'}>${aidOptions || '<option value="">—</option>'}</select>` +
|
|
3674
|
+
`</label><label><span>${esc(t('messages.mobile.peer'))}</span>` +
|
|
3675
|
+
`<select id="msg-mobile-peer"${scopeMatches && peers.length ? '' : ' disabled'}>${peerOptions || '<option value="">—</option>'}</select>` +
|
|
3676
|
+
'</label>';
|
|
3677
|
+
const aidSelect = $('#msg-mobile-aid');
|
|
3678
|
+
if (aidSelect) aidSelect.onchange = () => {
|
|
3679
|
+
msgSel = { aid: aidSelect.value || null, peer: null, before: null };
|
|
3680
|
+
detailViewState.msg = false;
|
|
3681
|
+
saveSessionUiState();
|
|
3682
|
+
subscribe('msg', msgSel);
|
|
3683
|
+
};
|
|
3684
|
+
const peerSelect = $('#msg-mobile-peer');
|
|
3685
|
+
if (peerSelect) peerSelect.onchange = () => {
|
|
3686
|
+
msgSel = { aid: msgSel.aid, peer: peerSelect.value || null, before: null };
|
|
3687
|
+
saveSessionUiState();
|
|
3688
|
+
subscribe('msg', msgSel);
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
function msgAgentIsExpanded(aid) {
|
|
3693
|
+
return aid === msgSel.aid && !msgCollapsedAids.has(aid);
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
function renderMsgPeerItem(peer, extraClass = '') {
|
|
3697
|
+
const sel = peer.peerId === msgSel.peer ? ' sel' : '';
|
|
3698
|
+
const isGroup = peer.chatType === 'group';
|
|
3699
|
+
const displayName = peer.chatType === 'group'
|
|
3700
|
+
? (peer.groupName || peer.peerName || peer.groupId || peer.peerId)
|
|
3701
|
+
: (peer.peerName || peer.peerId);
|
|
3702
|
+
const chatTitle = peer.partition === 'thread'
|
|
3703
|
+
? (peer.threadName || peer.threadId || displayName)
|
|
3704
|
+
: peer.partition === 'legacy'
|
|
3705
|
+
? t('messages.legacy.title')
|
|
3706
|
+
: displayName;
|
|
3707
|
+
const channelLabel = peer.channelName && peer.channelName !== 'main'
|
|
3708
|
+
? `${peer.channelType}/${peer.channelName}`
|
|
3709
|
+
: (peer.channelType || '');
|
|
3710
|
+
const groupMark = isGroup ? `<span class="msg-group-mark">${esc(t('messages.tag.groupShort'))}</span>` : '';
|
|
3711
|
+
const partitionLabel = peer.partition === 'thread'
|
|
3712
|
+
? t('messages.tag.topic')
|
|
3713
|
+
: peer.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
|
|
3714
|
+
const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(peer.partition || 'main')}">${esc(partitionLabel)}</span>`;
|
|
3715
|
+
const channelTag = channelLabel
|
|
3716
|
+
? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(peer.channelType)}">${esc(channelLabel)}</span>`
|
|
3717
|
+
: '';
|
|
3718
|
+
const topicStatus = ({ pending: '待讨论', in_progress: '进行中', completed: '已完成', archived: '已归档' })[peer.topicStatus] || '待讨论';
|
|
3719
|
+
const sub = peer.partition === 'thread' ? `${esc(topicStatus)} · ` : '';
|
|
3720
|
+
const label = peer.label ? `${esc(peer.label)} · ` : '';
|
|
3721
|
+
const threadPeer = peer.partition === 'thread' ? `${esc(shortId(displayName))} · ` : '';
|
|
3722
|
+
return `<div class="list-item msg-peer-item ${extraClass}${sel}" data-peer="${esc(peer.peerId)}">` +
|
|
3723
|
+
`<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
|
|
3724
|
+
`<div class="sub">${sub}${label}${threadPeer}↓${peer.inbound} ↑${peer.outbound} · ${fmtAgo(peer.lastAt)}</div></div>`;
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3727
|
+
function bindDetailPreview(container, view) {
|
|
3728
|
+
if (!container) return;
|
|
3729
|
+
container.onclick = event => {
|
|
3730
|
+
if (event.target.closest('button, a, input, select, textarea, summary, details')) return;
|
|
3731
|
+
if (detailViewState[view]) leaveDetail(view);
|
|
3732
|
+
else enterDetail(view);
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3361
3736
|
function renderMsg(data) {
|
|
3362
3737
|
if (!data) return;
|
|
3363
3738
|
const aids = data.scopes || data.aids || [];
|
|
@@ -3369,6 +3744,7 @@ function renderMsg(data) {
|
|
|
3369
3744
|
const nextAid = aids[0]?.aid || null;
|
|
3370
3745
|
const selectionChanged = msgSel.aid !== nextAid || msgSel.peer !== null;
|
|
3371
3746
|
msgSel = { aid: nextAid, peer: null, before: null };
|
|
3747
|
+
saveSessionUiState();
|
|
3372
3748
|
if (selectionChanged && nextAid) subscribe('msg', msgSel);
|
|
3373
3749
|
}
|
|
3374
3750
|
const scopeMatches = data.scope === msgSel.aid;
|
|
@@ -3379,6 +3755,7 @@ function renderMsg(data) {
|
|
|
3379
3755
|
const nextPeer = peers[0]?.peerId || null;
|
|
3380
3756
|
const selectionChanged = msgSel.peer !== nextPeer;
|
|
3381
3757
|
msgSel = { aid: msgSel.aid, peer: nextPeer, before: null };
|
|
3758
|
+
saveSessionUiState();
|
|
3382
3759
|
if (selectionChanged && nextPeer) subscribe('msg', msgSel);
|
|
3383
3760
|
}
|
|
3384
3761
|
}
|
|
@@ -3386,24 +3763,78 @@ function renderMsg(data) {
|
|
|
3386
3763
|
const selectedAid = aids.find(item => item.aid === msgSel.aid);
|
|
3387
3764
|
const peerTotal = scopeMatches ? peers.length : (selectedAid?.peerCount || 0);
|
|
3388
3765
|
|
|
3389
|
-
//
|
|
3390
|
-
let
|
|
3766
|
+
// 左:桌面端 AID 列表;双列布局使用同一栏里的 Agent → Chats 树。
|
|
3767
|
+
let aidListHtml = `<div class="col-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
|
|
3391
3768
|
for (const a of aids) {
|
|
3392
3769
|
const sel = a.aid === msgSel.aid ? ' sel' : '';
|
|
3393
3770
|
const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
|
|
3394
3771
|
const privateCount = Math.max(0, (a.peerCount || 0) - (a.groupCount || 0));
|
|
3395
3772
|
const privateBit = tf('messages.privateCount', { count: privateCount });
|
|
3396
3773
|
const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
|
|
3397
|
-
|
|
3774
|
+
aidListHtml += `<div class="list-item msg-aid-item${sel}" data-aid="${esc(a.aid)}">` +
|
|
3398
3775
|
`<div class="name">${esc(name)}</div>` +
|
|
3399
3776
|
`<div class="sub">↓${a.totalIn} ↑${a.totalOut} · ${privateBit}${groupBit} · ${fmtAgo(a.lastAt)}</div></div>`;
|
|
3400
3777
|
}
|
|
3401
|
-
if (!aids.length)
|
|
3402
|
-
|
|
3403
|
-
|
|
3778
|
+
if (!aids.length) aidListHtml += `<div class="empty">${t('messages.empty.noAgents')}</div>`;
|
|
3779
|
+
|
|
3780
|
+
let aidTreeHtml = `<div class="msg-tree-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
|
|
3781
|
+
for (const a of aids) {
|
|
3782
|
+
const selected = a.aid === msgSel.aid;
|
|
3783
|
+
const expanded = msgAgentIsExpanded(a.aid);
|
|
3784
|
+
const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
|
|
3785
|
+
const peerCount = a.peerCount || 0;
|
|
3786
|
+
const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
|
|
3787
|
+
let chatsHtml = '';
|
|
3788
|
+
if (expanded) {
|
|
3789
|
+
if (!scopeMatches) chatsHtml = `<div class="msg-tree-empty">${t('common.loading')}</div>`;
|
|
3790
|
+
else if (!peers.length) chatsHtml = `<div class="msg-tree-empty">${t('messages.empty.noPeers')}</div>`;
|
|
3791
|
+
else chatsHtml = peers.map(peer => renderMsgPeerItem(peer, 'msg-tree-chat')).join('');
|
|
3792
|
+
}
|
|
3793
|
+
aidTreeHtml += `<div class="msg-tree-agent${selected ? ' sel' : ''}${expanded ? ' expanded' : ''}">` +
|
|
3794
|
+
`<button type="button" class="msg-tree-agent-toggle" data-aid="${esc(a.aid)}" aria-expanded="${expanded ? 'true' : 'false'}">` +
|
|
3795
|
+
`<span class="msg-tree-chevron" aria-hidden="true">${expanded ? '▾' : '▸'}</span>` +
|
|
3796
|
+
`<span class="msg-tree-agent-main"><span class="name">${esc(name)}</span>` +
|
|
3797
|
+
`<span class="sub">${t('messages.colTitle.peers')} ${peerCount} · ↓${a.totalIn} ↑${a.totalOut}${groupBit ? `${groupBit}` : ''} · ${fmtAgo(a.lastAt)}</span>` +
|
|
3798
|
+
`</span>` +
|
|
3799
|
+
`</button>` +
|
|
3800
|
+
`<div class="msg-tree-chats"${expanded ? '' : ' hidden'}>${chatsHtml}</div>` +
|
|
3801
|
+
`</div>`;
|
|
3802
|
+
}
|
|
3803
|
+
if (!aids.length) aidTreeHtml += `<div class="empty">${t('messages.empty.noAgents')}</div>`;
|
|
3804
|
+
$('#msg-aids').innerHTML = `<div class="msg-aids-list">${aidListHtml}</div><div class="msg-tree-view">${aidTreeHtml}</div>`;
|
|
3805
|
+
$('#msg-aids').querySelectorAll('.msg-aid-item').forEach(item => {
|
|
3404
3806
|
item.onclick = () => {
|
|
3405
3807
|
if (item.dataset.aid === msgSel.aid) return;
|
|
3808
|
+
msgCollapsedAids.delete(item.dataset.aid);
|
|
3406
3809
|
msgSel = { aid: item.dataset.aid, peer: null, before: null };
|
|
3810
|
+
detailViewState.msg = false;
|
|
3811
|
+
saveSessionUiState();
|
|
3812
|
+
subscribe('msg', msgSel);
|
|
3813
|
+
};
|
|
3814
|
+
});
|
|
3815
|
+
|
|
3816
|
+
$('#msg-aids').querySelectorAll('.msg-tree-agent-toggle').forEach(item => {
|
|
3817
|
+
item.onclick = () => {
|
|
3818
|
+
const aid = item.dataset.aid;
|
|
3819
|
+
if (aid !== msgSel.aid) {
|
|
3820
|
+
msgCollapsedAids.delete(aid);
|
|
3821
|
+
msgSel = { aid, peer: null, before: null };
|
|
3822
|
+
detailViewState.msg = false;
|
|
3823
|
+
saveSessionUiState();
|
|
3824
|
+
subscribe('msg', msgSel);
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
if (msgCollapsedAids.has(aid)) msgCollapsedAids.delete(aid);
|
|
3828
|
+
else msgCollapsedAids.add(aid);
|
|
3829
|
+
renderMsg(state.msg);
|
|
3830
|
+
};
|
|
3831
|
+
});
|
|
3832
|
+
$('#msg-aids').querySelectorAll('.msg-tree-chat').forEach(item => {
|
|
3833
|
+
item.onclick = event => {
|
|
3834
|
+
event.stopPropagation();
|
|
3835
|
+
if (item.dataset.peer === msgSel.peer) return;
|
|
3836
|
+
msgSel = { aid: msgSel.aid, peer: item.dataset.peer, before: null };
|
|
3837
|
+
saveSessionUiState();
|
|
3407
3838
|
subscribe('msg', msgSel);
|
|
3408
3839
|
};
|
|
3409
3840
|
});
|
|
@@ -3417,40 +3848,20 @@ function renderMsg(data) {
|
|
|
3417
3848
|
} else if (!peers.length) {
|
|
3418
3849
|
peersHtml += `<div class="empty">${t('messages.empty.noPeers')}</div>`;
|
|
3419
3850
|
} else {
|
|
3420
|
-
for (const p of peers)
|
|
3421
|
-
const sel = p.peerId === msgSel.peer ? ' sel' : '';
|
|
3422
|
-
const isGroup = p.chatType === 'group';
|
|
3423
|
-
const displayName = p.chatType === 'group'
|
|
3424
|
-
? (p.groupName || p.peerName || p.groupId || p.peerId)
|
|
3425
|
-
: (p.peerName || p.peerId);
|
|
3426
|
-
const chatTitle = p.partition === 'thread'
|
|
3427
|
-
? (p.threadName || p.threadId || displayName)
|
|
3428
|
-
: p.partition === 'legacy'
|
|
3429
|
-
? t('messages.legacy.title')
|
|
3430
|
-
: displayName;
|
|
3431
|
-
const channelLabel = p.channelName && p.channelName !== 'main' ? `${p.channelType}/${p.channelName}` : (p.channelType || '');
|
|
3432
|
-
const groupMark = isGroup ? `<span class="msg-group-mark">${esc(t('messages.tag.groupShort'))}</span>` : '';
|
|
3433
|
-
const partitionLabel = p.partition === 'thread'
|
|
3434
|
-
? t('messages.tag.topic')
|
|
3435
|
-
: p.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
|
|
3436
|
-
const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(p.partition || 'main')}">${esc(partitionLabel)}</span>`;
|
|
3437
|
-
const channelTag = channelLabel
|
|
3438
|
-
? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(p.channelType)}">${esc(channelLabel)}</span>`
|
|
3439
|
-
: '';
|
|
3440
|
-
peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
|
|
3441
|
-
`<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
|
|
3442
|
-
`<div class="sub">${p.lable ? `${esc(p.lable)} · ` : ''}${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
|
|
3443
|
-
}
|
|
3851
|
+
for (const p of peers) peersHtml += renderMsgPeerItem(p);
|
|
3444
3852
|
}
|
|
3445
3853
|
$('#msg-peers').innerHTML = peersHtml;
|
|
3446
|
-
$('#msg-peers').querySelectorAll('.
|
|
3854
|
+
$('#msg-peers').querySelectorAll('.msg-peer-item').forEach(item => {
|
|
3447
3855
|
item.onclick = () => {
|
|
3448
3856
|
if (item.dataset.peer === msgSel.peer) return;
|
|
3449
3857
|
msgSel = { aid: msgSel.aid, peer: item.dataset.peer, before: null };
|
|
3858
|
+
saveSessionUiState();
|
|
3450
3859
|
subscribe('msg', msgSel);
|
|
3451
3860
|
};
|
|
3452
3861
|
});
|
|
3453
3862
|
|
|
3863
|
+
renderMsgMobileControls(aids, peers, scopeMatches);
|
|
3864
|
+
|
|
3454
3865
|
// 右:消息流
|
|
3455
3866
|
const stream = $('#msg-stream');
|
|
3456
3867
|
if (!msgSel.aid) { stream.innerHTML = `<div class="empty">${t('messages.empty.selectToView')}</div>`; return; }
|
|
@@ -3489,16 +3900,83 @@ function renderMsg(data) {
|
|
|
3489
3900
|
}
|
|
3490
3901
|
msgHtml += messages.length ? '</div>' : `<div class="empty">${t('messages.empty.noMessages')}</div></div>`;
|
|
3491
3902
|
stream.innerHTML = renderMsgSessionHeader(sessionContext) + msgHtml;
|
|
3903
|
+
bindDetailPreview(stream, 'msg');
|
|
3904
|
+
const sessionDetailsRow = stream.querySelector('.msg-session-id-row');
|
|
3905
|
+
const sessionDetails = stream.querySelector('.msg-session-grid');
|
|
3906
|
+
if (sessionDetailsRow && sessionDetails) {
|
|
3907
|
+
const detailsKey = [msgSel.aid || '', msgSel.peer || '', sessionContext?.sessionId || ''].join('\u001f');
|
|
3908
|
+
const expanded = msgSessionDetailsExpanded.has(detailsKey)
|
|
3909
|
+
? msgSessionDetailsExpanded.get(detailsKey) !== false
|
|
3910
|
+
: true;
|
|
3911
|
+
sessionDetails.hidden = !expanded;
|
|
3912
|
+
sessionDetailsRow.setAttribute('aria-expanded', String(expanded));
|
|
3913
|
+
sessionDetails.setAttribute('aria-expanded', String(expanded));
|
|
3914
|
+
const initialLabel = t(`messages.session.details.${expanded ? 'collapse' : 'expand'}`);
|
|
3915
|
+
sessionDetailsRow.setAttribute('aria-label', initialLabel);
|
|
3916
|
+
sessionDetailsRow.title = initialLabel;
|
|
3917
|
+
sessionDetails.setAttribute('aria-label', initialLabel);
|
|
3918
|
+
const initialIcon = sessionDetailsRow.querySelector('.msg-session-toggle-icon');
|
|
3919
|
+
if (initialIcon) initialIcon.textContent = expanded ? '▾' : '▸';
|
|
3920
|
+
const setSessionDetailsExpanded = () => {
|
|
3921
|
+
const nextExpanded = sessionDetails.hidden;
|
|
3922
|
+
sessionDetails.hidden = !nextExpanded;
|
|
3923
|
+
msgSessionDetailsExpanded.set(detailsKey, nextExpanded);
|
|
3924
|
+
sessionDetailsRow.setAttribute('aria-expanded', String(nextExpanded));
|
|
3925
|
+
sessionDetails.setAttribute('aria-expanded', String(nextExpanded));
|
|
3926
|
+
const label = t(`messages.session.details.${nextExpanded ? 'collapse' : 'expand'}`);
|
|
3927
|
+
sessionDetailsRow.setAttribute('aria-label', label);
|
|
3928
|
+
sessionDetailsRow.title = label;
|
|
3929
|
+
sessionDetails.setAttribute('aria-label', label);
|
|
3930
|
+
const icon = sessionDetailsRow.querySelector('.msg-session-toggle-icon');
|
|
3931
|
+
if (icon) icon.textContent = nextExpanded ? '▾' : '▸';
|
|
3932
|
+
};
|
|
3933
|
+
sessionDetailsRow.onclick = event => {
|
|
3934
|
+
event.stopPropagation();
|
|
3935
|
+
setSessionDetailsExpanded();
|
|
3936
|
+
};
|
|
3937
|
+
sessionDetailsRow.onkeydown = event => {
|
|
3938
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
3939
|
+
event.preventDefault();
|
|
3940
|
+
event.stopPropagation();
|
|
3941
|
+
setSessionDetailsExpanded();
|
|
3942
|
+
};
|
|
3943
|
+
sessionDetails.onclick = event => {
|
|
3944
|
+
event.stopPropagation();
|
|
3945
|
+
setSessionDetailsExpanded();
|
|
3946
|
+
};
|
|
3947
|
+
sessionDetails.onkeydown = event => {
|
|
3948
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
3949
|
+
event.preventDefault();
|
|
3950
|
+
event.stopPropagation();
|
|
3951
|
+
setSessionDetailsExpanded();
|
|
3952
|
+
};
|
|
3953
|
+
}
|
|
3492
3954
|
const loadMore = stream.querySelector('.msg-load-more');
|
|
3493
3955
|
if (loadMore) loadMore.onclick = () => {
|
|
3494
3956
|
msgSel = { aid: msgSel.aid, peer: msgSel.peer, before: Number(loadMore.dataset.before) };
|
|
3957
|
+
saveSessionUiState();
|
|
3495
3958
|
subscribe('msg', msgSel);
|
|
3496
3959
|
};
|
|
3497
3960
|
if (atBottom) stream.scrollTop = stream.scrollHeight;
|
|
3498
3961
|
}
|
|
3499
3962
|
|
|
3963
|
+
function renderDetailToolbar(view, title = '') {
|
|
3964
|
+
return title ? `<div class="detail-toolbar"><span class="detail-toolbar-title">${esc(title)}</span></div>` : '';
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
function renderSessionDetailToolbar() {
|
|
3968
|
+
return '<div class="detail-toolbar sess-detail-toolbar">' +
|
|
3969
|
+
`<span class="detail-toolbar-title">${esc(t('tab.sessions'))}</span>` +
|
|
3970
|
+
'<div class="sess-view-control">' +
|
|
3971
|
+
`<button class="view-toggle${sessChatMode ? ' active' : ''}" id="chat-toggle">` +
|
|
3972
|
+
`${sessChatMode ? t('sessions.chatView') : t('sessions.fullView')}</button>` +
|
|
3973
|
+
`<span class="toolbar-hint">${sessChatMode ? t('sessions.chatHint') : t('sessions.fullHint')}</span>` +
|
|
3974
|
+
'</div>' +
|
|
3975
|
+
'</div>';
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3500
3978
|
function renderMsgSessionHeader(context) {
|
|
3501
|
-
if (!context) return '';
|
|
3979
|
+
if (!context) return renderDetailToolbar('msg');
|
|
3502
3980
|
const status = context.status === 'processing' ? 'processing' : 'idle';
|
|
3503
3981
|
const statusLabel = t(`messages.session.status.${status}`);
|
|
3504
3982
|
const modeKey = context.chatMode === 'proactive' ? 'proactive' : (context.chatMode === 'interactive' ? 'interactive' : '');
|
|
@@ -3519,13 +3997,16 @@ function renderMsgSessionHeader(context) {
|
|
|
3519
3997
|
`${esc(value)}</span>` +
|
|
3520
3998
|
'</span>'
|
|
3521
3999
|
).join('');
|
|
3522
|
-
|
|
4000
|
+
const detailsLabel = t('messages.session.details.collapse');
|
|
4001
|
+
return renderDetailToolbar('msg') +
|
|
4002
|
+
'<div class="sess-header msg-session-header">' +
|
|
3523
4003
|
'<div class="msg-session-context-row">' +
|
|
3524
|
-
|
|
4004
|
+
`<div class="msg-session-id-row" role="button" tabindex="0" aria-expanded="false" aria-label="${esc(detailsLabel)}" title="${esc(detailsLabel)}">` +
|
|
3525
4005
|
`<span class="msg-session-label">${t('messages.session.field.sessionId')}</span>` +
|
|
3526
4006
|
`<span class="msg-session-id" title="${esc(context.sessionId || '—')}">${esc(context.sessionId || '—')}</span>` +
|
|
4007
|
+
'<span class="msg-session-toggle" aria-hidden="true"><span class="msg-session-toggle-icon">▸</span></span>' +
|
|
3527
4008
|
'</div>' +
|
|
3528
|
-
`<div class="msg-session-grid">${stats}</div>` +
|
|
4009
|
+
`<div class="msg-session-grid" role="button" tabindex="0" hidden>${stats}</div>` +
|
|
3529
4010
|
'</div>' +
|
|
3530
4011
|
'</div>';
|
|
3531
4012
|
}
|
|
@@ -3663,7 +4144,7 @@ function renderSession(data) {
|
|
|
3663
4144
|
// 有效筛选
|
|
3664
4145
|
const filtered = filterSessionTranscripts(transcripts, sessShowAll, sessSourceFilter);
|
|
3665
4146
|
|
|
3666
|
-
//
|
|
4147
|
+
// 会话列表顶部:过滤条 + 标题/计数;详情保持右侧第二列。
|
|
3667
4148
|
const projOpts = projects.map(p =>
|
|
3668
4149
|
`<option value="${esc(p.encoded)}"${p.encoded === sessSel.project ? ' selected' : ''}>${esc(p.label)} (${p.count})</option>`
|
|
3669
4150
|
).join('');
|
|
@@ -3678,7 +4159,7 @@ function renderSession(data) {
|
|
|
3678
4159
|
|
|
3679
4160
|
const filterLabelKey = sessShowAll ? 'sessions.filter.all' : 'sessions.filter.valid';
|
|
3680
4161
|
const filterTitleKey = sessShowAll ? 'sessions.filter.allTitle' : 'sessions.filter.validTitle';
|
|
3681
|
-
|
|
4162
|
+
const contextHtml = '<div class="sess-filter">' +
|
|
3682
4163
|
'<div class="sess-select-row">' +
|
|
3683
4164
|
`<select id="sess-baseagent" title="Base Agent">${baseagentOpts}</select>` +
|
|
3684
4165
|
`<select id="sess-project" title="${t('sessions.header.project')}">${projOpts}</select>` +
|
|
@@ -3691,8 +4172,9 @@ function renderSession(data) {
|
|
|
3691
4172
|
`<input id="sess-filter-toggle" type="checkbox"${sessShowAll ? ' checked' : ''}>` +
|
|
3692
4173
|
'<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>' +
|
|
3693
4174
|
'</label>' +
|
|
3694
|
-
'</div></div>'
|
|
3695
|
-
|
|
4175
|
+
'</div></div>';
|
|
4176
|
+
|
|
4177
|
+
let listHtml = contextHtml + '<div class="sess-items">';
|
|
3696
4178
|
|
|
3697
4179
|
if (!filtered.length) {
|
|
3698
4180
|
const emptyText = data.noConfiguredBaseagent
|
|
@@ -3717,47 +4199,72 @@ function renderSession(data) {
|
|
|
3717
4199
|
}
|
|
3718
4200
|
listHtml += '</div>';
|
|
3719
4201
|
$('#sess-list').innerHTML = listHtml;
|
|
4202
|
+
bindSessionListScroll();
|
|
4203
|
+
restoreSessionListScroll();
|
|
4204
|
+
window.requestAnimationFrame?.(restoreSessionListScroll);
|
|
3720
4205
|
|
|
3721
4206
|
// 绑定交互
|
|
3722
4207
|
const baseagentSel = $('#sess-baseagent');
|
|
3723
4208
|
if (baseagentSel) baseagentSel.onchange = () => {
|
|
3724
4209
|
console.log('[ecweb] Baseagent changed to:', baseagentSel.value);
|
|
3725
4210
|
sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value, turnBefore: null };
|
|
4211
|
+
detailViewState.session = false;
|
|
4212
|
+
saveSessionUiState();
|
|
3726
4213
|
console.log('[ecweb] Subscribing to session with baseagent:', sessSel.baseagent);
|
|
3727
4214
|
subscribe('session', { baseagent: sessSel.baseagent });
|
|
3728
4215
|
};
|
|
3729
4216
|
const projSel = $('#sess-project');
|
|
3730
4217
|
if (projSel) projSel.onchange = () => {
|
|
3731
4218
|
sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent, turnBefore: null };
|
|
4219
|
+
detailViewState.session = false;
|
|
4220
|
+
saveSessionUiState();
|
|
3732
4221
|
subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
|
|
3733
4222
|
};
|
|
3734
4223
|
const filterToggle = $('#sess-filter-toggle');
|
|
3735
|
-
if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; renderSession(state.session); };
|
|
4224
|
+
if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; saveSessionUiState(); renderSession(state.session); };
|
|
3736
4225
|
const sourceTypeSelect = $('#sess-source-type');
|
|
3737
4226
|
if (sourceTypeSelect) sourceTypeSelect.onchange = () => {
|
|
3738
4227
|
sessSourceFilter = sourceTypeSelect.value;
|
|
4228
|
+
saveSessionUiState();
|
|
3739
4229
|
renderSession(state.session);
|
|
3740
4230
|
};
|
|
3741
4231
|
$('#sess-list').querySelectorAll('.list-item').forEach(item => {
|
|
3742
|
-
item.onclick = () => {
|
|
4232
|
+
item.onclick = () => {
|
|
4233
|
+
sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent, turnBefore: null };
|
|
4234
|
+
saveSessionUiState();
|
|
4235
|
+
if (window.matchMedia?.('(max-width: 479px)').matches) enterDetail('session');
|
|
4236
|
+
subscribe('session', sessSel);
|
|
4237
|
+
};
|
|
3743
4238
|
});
|
|
3744
4239
|
|
|
3745
4240
|
// 右:transcript 详情
|
|
3746
4241
|
const detail = $('#sess-detail');
|
|
3747
|
-
if (!sessSel.sessionId) {
|
|
3748
|
-
|
|
4242
|
+
if (!sessSel.sessionId) {
|
|
4243
|
+
detail.innerHTML = `<div class="detail-toolbar"><span class="detail-placeholder">${t('sessions.selectLog')}</span></div><div class="empty">${t('sessions.selectLog')}</div>`;
|
|
4244
|
+
bindDetailPreview(detail, 'session');
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
if (!turns.length) {
|
|
4248
|
+
detail.innerHTML = renderDetailToolbar('session') + `<div class="empty">${t('sessions.empty.noContent')}</div>`;
|
|
4249
|
+
bindDetailPreview(detail, 'session');
|
|
4250
|
+
return;
|
|
4251
|
+
}
|
|
3749
4252
|
const h = data.header || {};
|
|
3750
4253
|
const atBottom = detail.scrollHeight - detail.scrollTop - detail.clientHeight < 60;
|
|
3751
|
-
let html =
|
|
4254
|
+
let html = renderSessionDetailToolbar();
|
|
4255
|
+
html += renderSessHeader(h, data.baseagent || sessSel.baseagent);
|
|
3752
4256
|
if (data.turnPage?.hasMore && data.turnPage.nextBefore != null) {
|
|
3753
4257
|
html += `<button class="ctrl-btn sess-load-more" type="button" data-before="${data.turnPage.nextBefore}">${t('sessions.loadOlder') || '加载更早内容'}</button>`;
|
|
3754
4258
|
}
|
|
3755
4259
|
html += '<div class="turn-list">' + (sessChatMode ? renderChatView(turns) : renderFullView(turns)) + '</div>';
|
|
3756
4260
|
detail.innerHTML = html;
|
|
4261
|
+
bindDetailPreview(detail, 'session');
|
|
4262
|
+
bindSessionHeaderToggle(detail, [sessSel.project || '', data.baseagent || sessSel.baseagent || '', h.sessionId || ''].join('\u001f'));
|
|
3757
4263
|
|
|
3758
4264
|
const loadMore = detail.querySelector('.sess-load-more');
|
|
3759
4265
|
if (loadMore) loadMore.onclick = () => {
|
|
3760
4266
|
sessSel = { ...sessSel, turnBefore: Number(loadMore.dataset.before) };
|
|
4267
|
+
saveSessionUiState();
|
|
3761
4268
|
subscribe('session', sessSel);
|
|
3762
4269
|
};
|
|
3763
4270
|
|
|
@@ -3768,6 +4275,41 @@ function renderSession(data) {
|
|
|
3768
4275
|
if (atBottom) detail.scrollTop = detail.scrollHeight;
|
|
3769
4276
|
}
|
|
3770
4277
|
|
|
4278
|
+
function bindSessionHeaderToggle(container, key) {
|
|
4279
|
+
const toggleRow = container?.querySelector('.sess-header-toggle-row');
|
|
4280
|
+
const details = container?.querySelector('.sess-header-details');
|
|
4281
|
+
if (!toggleRow || !details) return;
|
|
4282
|
+
const remembered = sessHeaderExpanded.get(key);
|
|
4283
|
+
const initialExpanded = remembered == null ? true : remembered !== false;
|
|
4284
|
+
const apply = expanded => {
|
|
4285
|
+
details.hidden = !expanded;
|
|
4286
|
+
const label = t(`sessions.details.${expanded ? 'collapse' : 'expand'}`);
|
|
4287
|
+
toggleRow.setAttribute('aria-expanded', String(expanded));
|
|
4288
|
+
toggleRow.setAttribute('aria-label', label);
|
|
4289
|
+
toggleRow.title = label;
|
|
4290
|
+
details.setAttribute('aria-expanded', String(expanded));
|
|
4291
|
+
details.setAttribute('aria-label', label);
|
|
4292
|
+
const icon = toggleRow.querySelector('.sess-header-toggle-icon');
|
|
4293
|
+
if (icon) icon.textContent = expanded ? '▾' : '▸';
|
|
4294
|
+
};
|
|
4295
|
+
const toggle = event => {
|
|
4296
|
+
event.preventDefault();
|
|
4297
|
+
event.stopPropagation();
|
|
4298
|
+
const expanded = details.hidden;
|
|
4299
|
+
sessHeaderExpanded.set(key, expanded);
|
|
4300
|
+
apply(expanded);
|
|
4301
|
+
};
|
|
4302
|
+
toggleRow.onclick = toggle;
|
|
4303
|
+
details.onclick = toggle;
|
|
4304
|
+
toggleRow.onkeydown = event => {
|
|
4305
|
+
if (event.key === 'Enter' || event.key === ' ') toggle(event);
|
|
4306
|
+
};
|
|
4307
|
+
details.onkeydown = event => {
|
|
4308
|
+
if (event.key === 'Enter' || event.key === ' ') toggle(event);
|
|
4309
|
+
};
|
|
4310
|
+
apply(initialExpanded);
|
|
4311
|
+
}
|
|
4312
|
+
|
|
3771
4313
|
// 完整视图:所有轮次按 4 类渲染
|
|
3772
4314
|
function renderFullView(turns) {
|
|
3773
4315
|
let html = '';
|
|
@@ -3897,24 +4439,23 @@ function renderSessHeader(h, baseagent) {
|
|
|
3897
4439
|
const dot = h.online ? `<span class="dot on"></span>${t('common.online')}` : `<span class="dot idle"></span>${t('common.offline')}`;
|
|
3898
4440
|
bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.boundSelf || ''))} ${dot}</span>`;
|
|
3899
4441
|
}
|
|
3900
|
-
|
|
3901
|
-
|
|
4442
|
+
const detailsLabel = t('sessions.details.collapse');
|
|
4443
|
+
return '<div class="sess-header sess-header-collapsible">' +
|
|
4444
|
+
`<div class="sess-header-toggle-row" role="button" tabindex="0" aria-expanded="true" aria-label="${esc(detailsLabel)}" title="${esc(detailsLabel)}">` +
|
|
3902
4445
|
`<div class="sh-title" title="${esc(title)}">${esc(title)}</div>` +
|
|
4446
|
+
'<span class="sess-header-toggle" aria-hidden="true"><span class="sess-header-toggle-icon">▾</span></span>' +
|
|
3903
4447
|
'</div>' +
|
|
3904
|
-
'<div class="
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
`<button class="view-toggle${sessChatMode ? ' active' : ''}" id="chat-toggle">` +
|
|
3916
|
-
`${sessChatMode ? t('sessions.chatView') : t('sessions.fullView')}</button>` +
|
|
3917
|
-
`<span class="toolbar-hint">${sessChatMode ? t('sessions.chatHint') : t('sessions.fullHint')}</span>` +
|
|
4448
|
+
'<div class="sess-header-details" role="button" tabindex="0" aria-expanded="true" aria-label="' + esc(detailsLabel) + '">' +
|
|
4449
|
+
'<div class="sh-stats">' +
|
|
4450
|
+
`<span class="sh-stat" title="${tf('sessions.messageCountTitle', { user: h.userMsgs || 0, total: h.totalMsgs || 0 })}">💬 ${h.userMsgs || 0}/${h.totalMsgs || 0} ${t('sessions.msgUnit')}</span>` +
|
|
4451
|
+
(h.model ? `<span class="sh-stat">🤖 ${esc(h.model)}</span>` : '') +
|
|
4452
|
+
tok + ctx + cost +
|
|
4453
|
+
(h.gitBranch ? `<span class="sh-stat">🌿 ${esc(h.gitBranch)}</span>` : '') +
|
|
4454
|
+
(h.version ? `<span class="sh-stat">cc ${esc(h.version)}</span>` : '') +
|
|
4455
|
+
bind +
|
|
4456
|
+
'</div>' +
|
|
4457
|
+
'<div class="sh-footer-row">' +
|
|
4458
|
+
renderCatBar(h.counts) +
|
|
3918
4459
|
'</div>' +
|
|
3919
4460
|
'</div>' +
|
|
3920
4461
|
'</div>';
|
|
@@ -5344,7 +5885,7 @@ function openAgentConfigDrawer(aid, data) {
|
|
|
5344
5885
|
const fullaccessState = enabled => currentLang === 'zh-CN'
|
|
5345
5886
|
? (enabled ? '开启' : '关闭')
|
|
5346
5887
|
: (enabled ? 'Enabled' : 'Disabled');
|
|
5347
|
-
const fullaccessSection =
|
|
5888
|
+
const fullaccessSection =
|
|
5348
5889
|
`<section class="ag-edit-section">` +
|
|
5349
5890
|
`<div class="ag-edit-fullaccess-row">` +
|
|
5350
5891
|
`<span class="ag-edit-field-label">Owner Fullaccess<span class="basic-settings-tip" tabindex="0" role="img" aria-label="${esc(fullaccessHelpLabel)}" data-tooltip="${esc(fullaccessHelp)}">?</span></span>` +
|
|
@@ -5354,7 +5895,7 @@ function openAgentConfigDrawer(aid, data) {
|
|
|
5354
5895
|
`<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>` +
|
|
5355
5896
|
`</label>` +
|
|
5356
5897
|
`</div>` +
|
|
5357
|
-
`</section
|
|
5898
|
+
`</section>`;
|
|
5358
5899
|
|
|
5359
5900
|
const html = `<div class="ag-edit-backdrop" id="ag-edit-backdrop">` +
|
|
5360
5901
|
`<aside class="ag-edit-drawer" role="dialog" aria-modal="true" aria-labelledby="ag-edit-title">` +
|
|
@@ -5437,7 +5978,6 @@ function openAgentConfigDrawer(aid, data) {
|
|
|
5437
5978
|
document.addEventListener('keydown', ctx.onKeydown);
|
|
5438
5979
|
syncAgentBaseagentFields(ctx);
|
|
5439
5980
|
syncAgentBaseagentOptions(ctx);
|
|
5440
|
-
setTimeout(() => backdrop.querySelector('#ag-edit-owners')?.focus(), 0);
|
|
5441
5981
|
}
|
|
5442
5982
|
|
|
5443
5983
|
async function agentOpEdit(aid) {
|
|
@@ -5787,8 +6327,7 @@ const TRIGGER_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
|
5787
6327
|
const TRIGGER_PERMISSION_MODES = ['readonly', 'auto', 'request', 'bypass'];
|
|
5788
6328
|
function triggerPermissionModes(currentMode = '') {
|
|
5789
6329
|
const modes = [...TRIGGER_PERMISSION_MODES];
|
|
5790
|
-
if (
|
|
5791
|
-
&& state.triggers?.canApprovePersistentFullAccess === true)
|
|
6330
|
+
if (state.triggers?.canApprovePersistentFullAccess === true
|
|
5792
6331
|
|| currentMode === 'fullaccess') modes.push('fullaccess');
|
|
5793
6332
|
return modes;
|
|
5794
6333
|
}
|
|
@@ -6330,6 +6869,18 @@ function triggerAgentInfo(agentAid) {
|
|
|
6330
6869
|
return (state.triggers?.agents || []).find(item => item.aid === agentAid || item.value === agentAid) || null;
|
|
6331
6870
|
}
|
|
6332
6871
|
|
|
6872
|
+
function isDaemonTriggerAgent(aid, label, channels) {
|
|
6873
|
+
const normalizedAid = String(aid || '').trim().toLowerCase();
|
|
6874
|
+
const normalizedLabel = String(label || '').trim().toLowerCase();
|
|
6875
|
+
return normalizedAid === 'daemon'
|
|
6876
|
+
|| normalizedAid === '__daemon__'
|
|
6877
|
+
|| normalizedAid.endsWith('#daemon')
|
|
6878
|
+
|| normalizedAid.endsWith('.daemon')
|
|
6879
|
+
|| normalizedLabel === 'daemon'
|
|
6880
|
+
|| (Array.isArray(channels) && channels.some(channel => String(channel).trim().toLowerCase() === 'daemon'))
|
|
6881
|
+
|| /\(daemon\)$/i.test(normalizedLabel);
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6333
6884
|
function triggerChannelOptions(agentAid, current) {
|
|
6334
6885
|
const channels = [...(triggerAgentInfo(agentAid)?.channels || [])].map(String).filter(Boolean);
|
|
6335
6886
|
if (current && !channels.includes(current)) channels.push(current);
|
|
@@ -6466,31 +7017,22 @@ function triggerRunJson(value) {
|
|
|
6466
7017
|
try { return JSON.stringify(value, null, 2); } catch { return String(value); }
|
|
6467
7018
|
}
|
|
6468
7019
|
|
|
6469
|
-
function
|
|
6470
|
-
return String(
|
|
6471
|
-
run?.error?.message
|
|
6472
|
-
|| run?.reply?.textPreview
|
|
6473
|
-
|| run?.script?.result?.text
|
|
6474
|
-
|| run?.script?.stderrPreview
|
|
6475
|
-
|| run?.script?.stdoutPreview
|
|
6476
|
-
|| run?.reason
|
|
6477
|
-
|| '',
|
|
6478
|
-
);
|
|
6479
|
-
}
|
|
6480
|
-
|
|
6481
|
-
function triggerHistoryListHtml(runs, selectedRunId) {
|
|
7020
|
+
function triggerHistoryListHtml(runs, selectedRunId, inlineDetails, renderDetail) {
|
|
6482
7021
|
if (!runs.length) return `<div class="trig-history-empty">${t('triggers.history.empty')}</div>`;
|
|
6483
7022
|
return runs.map(run => {
|
|
6484
7023
|
const runId = String(run.runId || '');
|
|
7024
|
+
const selected = runId === selectedRunId;
|
|
7025
|
+
const expanded = inlineDetails && selected;
|
|
6485
7026
|
const duration = Number(run.finishedAt || 0) - Number(run.startedAt || 0);
|
|
6486
7027
|
const source = run.source?.eventName || run.source?.type || '—';
|
|
6487
7028
|
const execution = run.processing?.mode || run.definition?.name || '—';
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
`<div class="trig-history-item-head"><span>${run.startedAt ? fmtTime(run.startedAt) : '—'}</span>${triggerRunStatusBadge(run.status)}
|
|
7029
|
+
return `<article class="trig-history-entry${expanded ? ' expanded' : ''}">` +
|
|
7030
|
+
`<button type="button" class="trig-history-item${selected ? ' selected' : ''}" data-runid="${esc(runId)}" aria-expanded="${expanded}">` +
|
|
7031
|
+
`<div class="trig-history-item-head"><span>${run.startedAt ? fmtTime(run.startedAt) : '—'}</span>${triggerRunStatusBadge(run.status)}<span class="trig-history-chevron" aria-hidden="true">›</span></div>` +
|
|
6491
7032
|
`<div class="trig-history-item-meta"><span>${esc(source)}</span><span>${esc(execution)}</span><span>${esc(triggerRunDurationLabel(duration))}</span></div>` +
|
|
6492
|
-
|
|
6493
|
-
|
|
7033
|
+
`</button>` +
|
|
7034
|
+
(expanded ? `<div class="trig-history-inline-detail">${renderDetail(run)}</div>` : '') +
|
|
7035
|
+
`</article>`;
|
|
6494
7036
|
}).join('');
|
|
6495
7037
|
}
|
|
6496
7038
|
|
|
@@ -6841,29 +7383,38 @@ function triggerHistoryDetailHtml(run, definition) {
|
|
|
6841
7383
|
function renderTriggerHistoryDrawer(ctx) {
|
|
6842
7384
|
if (_triggerHistory !== ctx) return;
|
|
6843
7385
|
const list = ctx.backdrop.querySelector('#trig-history-list');
|
|
6844
|
-
const detail = ctx.backdrop.querySelector('#trig-history-detail');
|
|
6845
7386
|
const count = ctx.backdrop.querySelector('#trig-history-count');
|
|
7387
|
+
const detail = ctx.backdrop.querySelector('#trig-history-detail');
|
|
7388
|
+
const inlineDetails = ctx.layoutQuery.matches;
|
|
6846
7389
|
if (count) count.textContent = tf('triggers.history.runCount', { count: ctx.runs.length });
|
|
7390
|
+
const renderDetail = run => {
|
|
7391
|
+
const revision = String(run?.definition?.revision || '');
|
|
7392
|
+
const historicalDefinition = ctx.definitionsByRevision.get(revision)
|
|
7393
|
+
|| (revision && revision === String(ctx.currentRevision || '') ? ctx.currentDefinition : undefined);
|
|
7394
|
+
return triggerHistoryDetailHtml(run, historicalDefinition);
|
|
7395
|
+
};
|
|
6847
7396
|
if (list) {
|
|
6848
|
-
list.innerHTML = triggerHistoryListHtml(ctx.runs, ctx.selectedRunId);
|
|
7397
|
+
list.innerHTML = triggerHistoryListHtml(ctx.runs, ctx.selectedRunId, inlineDetails, renderDetail);
|
|
6849
7398
|
list.querySelectorAll('.trig-history-item').forEach(item => {
|
|
6850
7399
|
item.addEventListener('click', () => {
|
|
6851
|
-
|
|
7400
|
+
const runId = item.dataset.runid || '';
|
|
7401
|
+
ctx.selectedRunId = inlineDetails && ctx.selectedRunId === runId ? '' : runId;
|
|
6852
7402
|
renderTriggerHistoryDrawer(ctx);
|
|
6853
7403
|
});
|
|
6854
7404
|
});
|
|
6855
7405
|
}
|
|
6856
|
-
const
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
if (detail) detail.innerHTML = triggerHistoryDetailHtml(selected, historicalDefinition);
|
|
7406
|
+
const selectedRun = ctx.runs.find(run => String(run.runId || '') === ctx.selectedRunId) || null;
|
|
7407
|
+
if (detail) detail.innerHTML = !inlineDetails && selectedRun
|
|
7408
|
+
? `<div class="trig-history-detail">${renderDetail(selectedRun)}</div>`
|
|
7409
|
+
: `<div class="trig-history-detail-empty">${t('triggers.history.selectRun')}</div>`;
|
|
6861
7410
|
}
|
|
6862
7411
|
|
|
6863
7412
|
function closeTriggerHistoryDrawer() {
|
|
6864
7413
|
const ctx = _triggerHistory;
|
|
6865
7414
|
if (!ctx) return;
|
|
6866
7415
|
document.removeEventListener('keydown', ctx.onKeydown);
|
|
7416
|
+
if (ctx.layoutQuery?.removeEventListener) ctx.layoutQuery.removeEventListener('change', ctx.onLayoutChange);
|
|
7417
|
+
else if (ctx.layoutQuery?.removeListener) ctx.layoutQuery.removeListener(ctx.onLayoutChange);
|
|
6867
7418
|
try { ctx.backdrop.remove(); } catch {}
|
|
6868
7419
|
_triggerHistory = null;
|
|
6869
7420
|
}
|
|
@@ -6882,10 +7433,10 @@ async function openTriggerHistoryDrawer(id, agent) {
|
|
|
6882
7433
|
`<button class="ag-edit-close" id="trig-history-close" type="button" aria-label="${t('common.close')}">×</button>` +
|
|
6883
7434
|
`</div>` +
|
|
6884
7435
|
`<div class="trig-history-content">` +
|
|
6885
|
-
`<div class="trig-history-list-pane"><div id="trig-history-
|
|
6886
|
-
`<div id="trig-history-detail" class="trig-history-detail"><div class="trig-history-detail-empty">${t('triggers.history.selectRun')}</div></div>` +
|
|
7436
|
+
`<div class="trig-history-list-pane"><div id="trig-history-list" class="trig-history-list"><div class="trig-history-empty">${t('triggers.history.loading')}</div></div></div>` +
|
|
7437
|
+
`<div id="trig-history-detail" class="trig-history-detail-pane"><div class="trig-history-detail-empty">${t('triggers.history.selectRun')}</div></div>` +
|
|
6887
7438
|
`</div>` +
|
|
6888
|
-
`<div class="ag-edit-actions"><button class="ctrl-btn" id="trig-history-done" type="button">${t('common.close')}</button></div>` +
|
|
7439
|
+
`<div class="ag-edit-actions trig-history-actions"><span id="trig-history-count" class="trig-history-count" aria-live="polite">${t('triggers.history.loading')}</span><button class="ctrl-btn" id="trig-history-done" type="button">${t('common.close')}</button></div>` +
|
|
6889
7440
|
`</aside>` +
|
|
6890
7441
|
`</div>`;
|
|
6891
7442
|
const wrap = document.createElement('div');
|
|
@@ -6896,16 +7447,20 @@ async function openTriggerHistoryDrawer(id, agent) {
|
|
|
6896
7447
|
backdrop,
|
|
6897
7448
|
runs: [],
|
|
6898
7449
|
selectedRunId: '',
|
|
7450
|
+
layoutQuery: window.matchMedia('(orientation: portrait)'),
|
|
6899
7451
|
currentDefinition: item.definition,
|
|
6900
7452
|
currentRevision: item.revision,
|
|
6901
7453
|
definitionsByRevision: new Map(),
|
|
6902
7454
|
onKeydown: e => { if (e.key === 'Escape') closeTriggerHistoryDrawer(); },
|
|
7455
|
+
onLayoutChange: () => renderTriggerHistoryDrawer(ctx),
|
|
6903
7456
|
};
|
|
6904
7457
|
_triggerHistory = ctx;
|
|
6905
7458
|
backdrop.addEventListener('click', e => { if (e.target === backdrop) closeTriggerHistoryDrawer(); });
|
|
6906
7459
|
backdrop.querySelector('#trig-history-close').addEventListener('click', closeTriggerHistoryDrawer);
|
|
6907
7460
|
backdrop.querySelector('#trig-history-done').addEventListener('click', closeTriggerHistoryDrawer);
|
|
6908
7461
|
document.addEventListener('keydown', ctx.onKeydown);
|
|
7462
|
+
if (ctx.layoutQuery.addEventListener) ctx.layoutQuery.addEventListener('change', ctx.onLayoutChange);
|
|
7463
|
+
else if (ctx.layoutQuery.addListener) ctx.layoutQuery.addListener(ctx.onLayoutChange);
|
|
6909
7464
|
|
|
6910
7465
|
try {
|
|
6911
7466
|
const response = await ipcSend({ type: 'trigger.history', agentAid: agent, triggerId: id, limit: 500 }, 10000);
|
|
@@ -7758,7 +8313,6 @@ function openTriggerEditDrawer(id, agent) {
|
|
|
7758
8313
|
if (_triggerEdit === ctx) toast(e.message || String(e), true);
|
|
7759
8314
|
});
|
|
7760
8315
|
if (triggerUsesModelOptions(ctx)) syncTriggerOptionFields(ctx, original);
|
|
7761
|
-
setTimeout(() => backdrop.querySelector('#trig-edit-name')?.focus(), 0);
|
|
7762
8316
|
}
|
|
7763
8317
|
|
|
7764
8318
|
function renderTriggers(data) {
|
|
@@ -7769,7 +8323,7 @@ function renderTriggers(data) {
|
|
|
7769
8323
|
|
|
7770
8324
|
// 左列:agent 列表(仿 msg list-item 风格)
|
|
7771
8325
|
let aHtml = '<div class="col-title trig-title">' +
|
|
7772
|
-
'
|
|
8326
|
+
`<span>${t('triggers.th.agent')}</span>` +
|
|
7773
8327
|
`<button id="trig-agents-toggle" class="trig-collapse-btn" title="${trigAgentsCollapsed ? t('triggers.collapse.expand') : t('triggers.collapse.collapse')}">${trigAgentsCollapsed ? '›' : '‹'}</button>` +
|
|
7774
8328
|
'</div>';
|
|
7775
8329
|
if (!trigAgentsCollapsed) {
|
|
@@ -7777,9 +8331,12 @@ function renderTriggers(data) {
|
|
|
7777
8331
|
for (const ag of agents) {
|
|
7778
8332
|
const aid = ag.aid || ag.value || '';
|
|
7779
8333
|
const sel = aid === selAid ? ' sel' : '';
|
|
8334
|
+
const isDaemonAgent = isDaemonTriggerAgent(aid, ag.label, ag.channels);
|
|
8335
|
+
const label = isDaemonAgent ? t('triggers.agent.daemon') : ag.label;
|
|
7780
8336
|
aHtml += `<div class="list-item${sel}" data-aid="${esc(aid)}">` +
|
|
7781
|
-
`<div class="name">${esc(
|
|
7782
|
-
`<div class="sub">${esc(aid)}</div
|
|
8337
|
+
`<div class="name">${esc(label)}</div>` +
|
|
8338
|
+
(isDaemonAgent ? '' : `<div class="sub">${esc(aid)}</div>`) +
|
|
8339
|
+
'</div>';
|
|
7783
8340
|
}
|
|
7784
8341
|
}
|
|
7785
8342
|
const agentCol = $('#trig-agents');
|
|
@@ -7797,11 +8354,13 @@ function renderTriggers(data) {
|
|
|
7797
8354
|
if (trigAgentsCollapsed) return;
|
|
7798
8355
|
if (!trigSel.agent) return;
|
|
7799
8356
|
trigSel.agent = null;
|
|
8357
|
+
saveSessionUiState();
|
|
7800
8358
|
subscribe('triggers', {});
|
|
7801
8359
|
};
|
|
7802
8360
|
agentCol.querySelectorAll('.list-item').forEach(item => {
|
|
7803
8361
|
item.onclick = () => {
|
|
7804
8362
|
trigSel.agent = item.dataset.aid;
|
|
8363
|
+
saveSessionUiState();
|
|
7805
8364
|
subscribe('triggers', { agent: trigSel.agent });
|
|
7806
8365
|
};
|
|
7807
8366
|
});
|
|
@@ -7809,14 +8368,14 @@ function renderTriggers(data) {
|
|
|
7809
8368
|
// 右列:table,每字段一列
|
|
7810
8369
|
const el = $('#trig-table');
|
|
7811
8370
|
if (!triggers.length) {
|
|
7812
|
-
el.innerHTML = `<div class="empty" style="padding:16px">${selAid ? t('triggers.empty.noAgentTriggers') : t('triggers.empty.noTriggers')}</div>`;
|
|
8371
|
+
el.innerHTML = `<div class="trig-table-empty empty" style="padding:16px">${selAid ? t('triggers.empty.noAgentTriggers') : t('triggers.empty.noTriggers')}</div>`;
|
|
7813
8372
|
return;
|
|
7814
8373
|
}
|
|
7815
8374
|
|
|
7816
8375
|
let html = '<table><thead><tr>' +
|
|
7817
|
-
|
|
8376
|
+
`<th class="trig-agent-col">${t('triggers.th.agent')}</th><th>${t('triggers.th.status')}</th><th class="trig-subscription-col">${t('triggers.th.subscription')}</th><th>${t('triggers.th.name')}</th><th class="trig-id-col">${t('triggers.th.id')}</th><th>${t('triggers.th.type')}</th><th>${t('triggers.th.expression')}</th>` +
|
|
7818
8377
|
`<th>${t('triggers.th.lastFire')}</th><th>${t('triggers.th.nextFire')}</th><th>${t('triggers.th.fireCount')}</th><th>${t('triggers.th.failCount')}</th><th>${t('triggers.th.lastResult')}</th><th>${t('triggers.field.executionType')}</th>` +
|
|
7819
|
-
`<th>${t('triggers.th.targetChannel')}</th><th>${t('triggers.field.channelId')}</th><th>${t('triggers.th.channelType')}</th>` +
|
|
8378
|
+
`<th class="trig-target-channel-col">${t('triggers.th.targetChannel')}</th><th class="trig-channel-id-col">${t('triggers.field.channelId')}</th><th class="trig-channel-type-col">${t('triggers.th.channelType')}</th>` +
|
|
7820
8379
|
`<th>${t('triggers.th.createdBy')}</th><th>${t('triggers.th.createdChannel')}</th><th>${t('triggers.th.createdAt')}</th><th class="trig-ops-col">${t('triggers.th.operations')}</th>` +
|
|
7821
8380
|
'</tr></thead><tbody>';
|
|
7822
8381
|
for (const trigger of triggers) {
|
|
@@ -7825,42 +8384,62 @@ function renderTriggers(data) {
|
|
|
7825
8384
|
const lastFireTime = trigger.lastFiredAt || trigger.lastScheduledAt;
|
|
7826
8385
|
const lastResult = trigger.lastResult || (trigger.lastScheduledAt ? t('triggers.lastUnrecorded') : '');
|
|
7827
8386
|
const agentAid = trigger.agentAid || trigger.schedulerAid || selAid || '';
|
|
7828
|
-
const
|
|
8387
|
+
const agentInfo = triggerAgentInfo(agentAid);
|
|
8388
|
+
const agentLabel = isDaemonTriggerAgent(agentAid, trigger.agentLabel, agentInfo?.channels)
|
|
8389
|
+
? t('triggers.agent.daemon')
|
|
8390
|
+
: (trigger.agentLabel || (agentAid ? shortAid(agentAid) : ''));
|
|
7829
8391
|
const sourceType = triggerSourceType(trigger);
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
`<td>${
|
|
7833
|
-
`<td
|
|
7834
|
-
`<td>${
|
|
7835
|
-
`<td>${esc(trigger.
|
|
7836
|
-
`<td>${esc(
|
|
7837
|
-
`<td>${esc(
|
|
7838
|
-
`<td
|
|
7839
|
-
`<td>${
|
|
7840
|
-
`<td>${trigger.
|
|
7841
|
-
`<td
|
|
7842
|
-
`<td>${
|
|
7843
|
-
`<td
|
|
7844
|
-
`<td>${esc(trigger.
|
|
7845
|
-
`<td>${esc(trigger.
|
|
7846
|
-
`<td>${esc(trigger.
|
|
7847
|
-
`<td>${esc(trigger.
|
|
7848
|
-
`<td>${esc(trigger.
|
|
7849
|
-
`<td>${
|
|
7850
|
-
`<td
|
|
8392
|
+
const mobileLabel = key => ` data-mobile-label="${esc(t(key))}"`;
|
|
8393
|
+
html += `<tr class="trig-row${active ? '' : ' trig-disabled'}" tabindex="0" aria-expanded="false">` +
|
|
8394
|
+
`<td class="trig-agent-col"${mobileLabel('triggers.th.agent')} title="${esc(agentAid)}">${esc(agentLabel)}</td>` +
|
|
8395
|
+
`<td${mobileLabel('triggers.th.status')}>${trigStatusBadge(status)}</td>` +
|
|
8396
|
+
`<td class="trig-subscription-col"${mobileLabel('triggers.th.subscription')}>${trigSubscriptionBadge(trigger.subscription, sourceType)}</td>` +
|
|
8397
|
+
`<td${mobileLabel('triggers.th.name')}>${esc(trigger.name ?? trigger.label ?? '')}</td>` +
|
|
8398
|
+
`<td class="trig-id-col"${mobileLabel('triggers.th.id')}>${esc(trigger.id ?? trigger.value ?? '')}</td>` +
|
|
8399
|
+
`<td${mobileLabel('triggers.th.type')}>${esc(triggerScheduleLabel(sourceType || trigger.scheduleType || ''))}</td>` +
|
|
8400
|
+
`<td${mobileLabel('triggers.th.expression')}>${esc(triggerScheduleValueLabel(trigger.scheduleType, trigger.scheduleValue))}</td>` +
|
|
8401
|
+
`<td${mobileLabel('triggers.th.lastFire')}>${lastFireTime ? fmtTime(lastFireTime) : '—'}</td>` +
|
|
8402
|
+
`<td${mobileLabel('triggers.th.nextFire')}>${trigger.nextFireAt ? fmtTime(trigger.nextFireAt) : '—'}</td>` +
|
|
8403
|
+
`<td${mobileLabel('triggers.th.fireCount')}>${trigger.fireCount ?? 0}</td>` +
|
|
8404
|
+
`<td${mobileLabel('triggers.th.failCount')}>${trigger.failCount ? `<span style="color:var(--red)">${trigger.failCount}</span>` : '0'}</td>` +
|
|
8405
|
+
`<td${mobileLabel('triggers.th.lastResult')}>${lastResult ? esc(lastResult) : '—'}</td>` +
|
|
8406
|
+
`<td${mobileLabel('triggers.field.executionType')}>${esc(triggerExecutionTypeLabel(trigger.definition?.execution?.type ?? trigger.executionType ?? trigger.execution?.type))}</td>` +
|
|
8407
|
+
`<td class="trig-target-channel-col"${mobileLabel('triggers.th.targetChannel')}>${esc(trigger.targetChannel ?? '')}</td>` +
|
|
8408
|
+
`<td class="trig-channel-id-col"${mobileLabel('triggers.field.channelId')}>${esc(trigger.targetChannelId ?? '')}</td>` +
|
|
8409
|
+
`<td class="trig-channel-type-col"${mobileLabel('triggers.th.channelType')}>${esc(trigger.targetChannelType ?? '')}</td>` +
|
|
8410
|
+
`<td${mobileLabel('triggers.th.createdBy')}>${esc(trigger.createdByPeerId ?? '')}</td>` +
|
|
8411
|
+
`<td${mobileLabel('triggers.th.createdChannel')}>${esc(trigger.createdByChannel ?? '')}</td>` +
|
|
8412
|
+
`<td${mobileLabel('triggers.th.createdAt')}>${trigger.createdAt ? fmtTime(trigger.createdAt) : '—'}</td>` +
|
|
8413
|
+
`<td class="trig-ops-col"${mobileLabel('triggers.th.operations')}>${triggerOpsHtml(trigger)}</td>` +
|
|
7851
8414
|
'</tr>';
|
|
7852
8415
|
}
|
|
7853
8416
|
html += '</tbody></table>';
|
|
7854
|
-
el.innerHTML = html
|
|
8417
|
+
el.innerHTML = `<div class="trig-table-wide">${html}</div>`;
|
|
7855
8418
|
ensureOpsOutsideClose();
|
|
8419
|
+
bindTriggerOps(el);
|
|
8420
|
+
el.querySelectorAll('.trig-row').forEach(row => {
|
|
8421
|
+
const toggle = event => {
|
|
8422
|
+
if (event.target.closest('button, a, input, select, textarea, summary, details')) return;
|
|
8423
|
+
const expanded = row.classList.toggle('trig-expanded');
|
|
8424
|
+
row.setAttribute('aria-expanded', String(expanded));
|
|
8425
|
+
};
|
|
8426
|
+
row.addEventListener('click', toggle);
|
|
8427
|
+
row.addEventListener('keydown', event => {
|
|
8428
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
8429
|
+
event.preventDefault();
|
|
8430
|
+
toggle(event);
|
|
8431
|
+
});
|
|
8432
|
+
});
|
|
8433
|
+
}
|
|
7856
8434
|
|
|
7857
|
-
|
|
8435
|
+
function bindTriggerOps(container) {
|
|
8436
|
+
container.querySelectorAll('.trig-ops button[data-trig-op]').forEach(btn => {
|
|
7858
8437
|
btn.addEventListener('click', async (e) => {
|
|
7859
8438
|
const op = btn.dataset.trigOp;
|
|
7860
8439
|
if (op === 'more') {
|
|
7861
8440
|
const more = btn.closest('.ops-more');
|
|
7862
8441
|
const wasOpen = more?.classList.contains('open');
|
|
7863
|
-
document.querySelectorAll('.ops-more.open').forEach(
|
|
8442
|
+
document.querySelectorAll('.ops-more.open').forEach(item => item.classList.remove('open'));
|
|
7864
8443
|
if (more && !wasOpen) more.classList.add('open');
|
|
7865
8444
|
e.stopPropagation();
|
|
7866
8445
|
return;
|
|
@@ -7881,8 +8460,9 @@ function startApp() {
|
|
|
7881
8460
|
initTabs();
|
|
7882
8461
|
initRolesTab();
|
|
7883
8462
|
initRoleDefinitionsTab();
|
|
8463
|
+
initHistory();
|
|
7884
8464
|
// 恢复保存的 tab 视图
|
|
7885
|
-
switchView(currentView);
|
|
8465
|
+
switchView(currentView, { history: false });
|
|
7886
8466
|
connect();
|
|
7887
8467
|
$('#logout-btn').onclick = () => {
|
|
7888
8468
|
localStorage.removeItem(TOKEN_KEY);
|
|
@@ -7910,13 +8490,17 @@ function resolveTheme(preference) {
|
|
|
7910
8490
|
|
|
7911
8491
|
function updateThemeButton() {
|
|
7912
8492
|
const btn = $('#theme-btn');
|
|
7913
|
-
if (!btn) return;
|
|
7914
8493
|
const icons = { light: '☀️', dark: '🌙', system: '🖥️' };
|
|
7915
|
-
const
|
|
7916
|
-
|
|
7917
|
-
btn
|
|
7918
|
-
|
|
7919
|
-
|
|
8494
|
+
const modeLabel = t('theme.' + themePreference);
|
|
8495
|
+
const label = tf('theme.buttonTitle', { mode: modeLabel });
|
|
8496
|
+
if (btn) {
|
|
8497
|
+
btn.textContent = icons[themePreference] || icons.light;
|
|
8498
|
+
btn.title = label;
|
|
8499
|
+
btn.setAttribute('aria-label', label);
|
|
8500
|
+
btn.dataset.themeMode = themePreference;
|
|
8501
|
+
}
|
|
8502
|
+
const mobileLabel = $('[data-mobile-theme-label]');
|
|
8503
|
+
if (mobileLabel) mobileLabel.textContent = tf('theme.drawerLabel', { mode: modeLabel });
|
|
7920
8504
|
}
|
|
7921
8505
|
|
|
7922
8506
|
function disposeWindowChart(key) {
|
|
@@ -10037,6 +10621,7 @@ const COMMAND_PERMISSION_CATEGORY_ORDER = ['read', 'diagnose', 'write-own', 'wri
|
|
|
10037
10621
|
const COMMAND_PERMISSION_BOOLEAN_CONSTRAINTS = [
|
|
10038
10622
|
'ownPeerOnly',
|
|
10039
10623
|
'ownAgentOnly',
|
|
10624
|
+
'currentRelationOnly',
|
|
10040
10625
|
'privateOnly',
|
|
10041
10626
|
'groupOnly',
|
|
10042
10627
|
'requireDaemonOwner',
|
|
@@ -10104,6 +10689,7 @@ function commandConstraintLabel(key) {
|
|
|
10104
10689
|
const labels = {
|
|
10105
10690
|
ownPeerOnly: currentLang === 'zh-CN' ? '仅本人 Peer' : 'Own peer only',
|
|
10106
10691
|
ownAgentOnly: currentLang === 'zh-CN' ? '仅当前 Agent' : 'Own agent only',
|
|
10692
|
+
currentRelationOnly: currentLang === 'zh-CN' ? '仅当前关系' : 'Current relation only',
|
|
10107
10693
|
privateOnly: currentLang === 'zh-CN' ? '仅私聊' : 'Private only',
|
|
10108
10694
|
groupOnly: currentLang === 'zh-CN' ? '仅群聊' : 'Group only',
|
|
10109
10695
|
requireDaemonOwner: currentLang === 'zh-CN' ? '需要 daemon owner' : 'Require daemon owner',
|