ec-web 0.0.4 → 0.0.6
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 +63 -6
- package/dist/sources/msg.js +371 -14
- package/dist/sources/session-codex.js +115 -35
- package/dist/sources/session-origin.js +195 -0
- package/dist/sources/session.js +292 -41
- package/dist/sources/transcript-window.js +35 -0
- package/dist/static/app.js +456 -62
- package/dist/static/index.html +4 -5
- package/dist/static/style.css +153 -63
- package/dist/static/vendor/echarts-5.6.0.min.js +45 -0
- package/package.json +2 -2
package/dist/static/app.js
CHANGED
|
@@ -248,6 +248,7 @@ const translations = {
|
|
|
248
248
|
'messages.empty.noAgents': '暂无 Agent',
|
|
249
249
|
'messages.empty.noPeers': '暂无聊天对端',
|
|
250
250
|
'messages.empty.noMessages': '暂无消息',
|
|
251
|
+
'messages.loadOlder': '加载更早消息',
|
|
251
252
|
'messages.tag.group': '群聊',
|
|
252
253
|
'messages.tag.groupShort': '群',
|
|
253
254
|
'messages.tag.main': '主',
|
|
@@ -288,6 +289,8 @@ const translations = {
|
|
|
288
289
|
'sessions.empty.noMatch': '无匹配会话',
|
|
289
290
|
'sessions.empty.noSessions': '该项目暂无会话',
|
|
290
291
|
'sessions.empty.noContent': '该会话暂无内容',
|
|
292
|
+
'sessions.loadOlder': '加载更早内容',
|
|
293
|
+
'sessions.loadFullResult': '加载完整结果',
|
|
291
294
|
'sessions.header.project': '项目',
|
|
292
295
|
'sessions.header.session': '会话',
|
|
293
296
|
'sessions.stat.context': '📐 {tokens} ctx',
|
|
@@ -296,10 +299,19 @@ const translations = {
|
|
|
296
299
|
'sessions.turnType.toolUse': '工具使用',
|
|
297
300
|
'sessions.turnType.toolResult': '工具结果',
|
|
298
301
|
'sessions.turnType.msgSend': '发送消息',
|
|
299
|
-
'sessions.count': '{
|
|
302
|
+
'sessions.count': '{count} 个会话',
|
|
300
303
|
'sessions.filter.valid': '有效',
|
|
301
304
|
'sessions.filter.validTitle': '只显示有效会话(≥1 条用户消息)',
|
|
305
|
+
'sessions.filter.all': '全部',
|
|
306
|
+
'sessions.filter.allTitle': '显示全部会话(包括无用户消息的会话)',
|
|
307
|
+
'sessions.filter.sourceTitle': '按会话类型筛选',
|
|
308
|
+
'sessions.filter.sourceAll': '全部类型',
|
|
302
309
|
'sessions.messageCountTitle': '用户输入 {user} 条 / 共 {total} 条消息',
|
|
310
|
+
'sessions.source.trigger': 'Trigger',
|
|
311
|
+
'sessions.source.agent': 'Agent',
|
|
312
|
+
'sessions.source.cli': 'CLI',
|
|
313
|
+
'sessions.source.user': '用户',
|
|
314
|
+
'sessions.source.unknown': 'Unknown',
|
|
303
315
|
'sessions.selectLog': '选择会话查看 CC 日志',
|
|
304
316
|
'sessions.chatView': '💬 对话视图',
|
|
305
317
|
'sessions.fullView': '📜 完整视图',
|
|
@@ -309,10 +321,21 @@ const translations = {
|
|
|
309
321
|
'sessions.empty.noDialogue': '该会话没有用户对话消息',
|
|
310
322
|
'sessions.turnType.userInput': '用户输入',
|
|
311
323
|
'sessions.turnType.toolCall': '工具调用',
|
|
324
|
+
'sessions.turnType.subtask': '子任务',
|
|
312
325
|
'sessions.turnType.system': '系统',
|
|
326
|
+
'sessions.subtask.status.running': '运行中',
|
|
327
|
+
'sessions.subtask.status.completed': '已完成',
|
|
328
|
+
'sessions.subtask.status.failed': '失败',
|
|
329
|
+
'sessions.subtask.status.stopped': '已停止',
|
|
330
|
+
'sessions.subtask.status.killed': '已终止',
|
|
331
|
+
'sessions.subtask.tools': '{count} 次工具',
|
|
332
|
+
'sessions.subtask.tokens': '{count} tokens',
|
|
333
|
+
'sessions.subtask.reason': '原因:{reason}',
|
|
313
334
|
'sessions.block.thinking': '思考',
|
|
314
335
|
'sessions.block.result': '结果',
|
|
315
336
|
'sessions.block.resultError': '结果',
|
|
337
|
+
'sessions.content.expand': '展开内容',
|
|
338
|
+
'sessions.content.collapse': '收起内容',
|
|
316
339
|
'sessions.contextTitle': '最后一轮喂给模型的完整上下文大小',
|
|
317
340
|
'sessions.costTitle': '累计费用(按模型定价估算)',
|
|
318
341
|
'sessions.msgUnit': '条',
|
|
@@ -639,6 +662,7 @@ const translations = {
|
|
|
639
662
|
'triggers.feedback.defaultFailure': '默认:触发器失败:{{error.message}}',
|
|
640
663
|
'triggers.field.model': '模型',
|
|
641
664
|
'triggers.field.effort': '推理强度',
|
|
665
|
+
'triggers.baseagent.mismatch': 'Trigger 固定:{trigger},Agent 当前:{agent}',
|
|
642
666
|
'triggers.field.permission': '权限模式',
|
|
643
667
|
'triggers.delivery.on': '启用',
|
|
644
668
|
'triggers.delivery.off': '禁用',
|
|
@@ -1104,6 +1128,7 @@ const translations = {
|
|
|
1104
1128
|
'messages.empty.noAgents': 'No agents',
|
|
1105
1129
|
'messages.empty.noPeers': 'No chats',
|
|
1106
1130
|
'messages.empty.noMessages': 'No messages',
|
|
1131
|
+
'messages.loadOlder': 'Load older messages',
|
|
1107
1132
|
'messages.tag.group': 'Group',
|
|
1108
1133
|
'messages.tag.groupShort': 'G',
|
|
1109
1134
|
'messages.tag.main': 'Main',
|
|
@@ -1144,6 +1169,8 @@ const translations = {
|
|
|
1144
1169
|
'sessions.empty.noMatch': 'No matching sessions',
|
|
1145
1170
|
'sessions.empty.noSessions': 'No sessions in this project',
|
|
1146
1171
|
'sessions.empty.noContent': 'No content in this session',
|
|
1172
|
+
'sessions.loadOlder': 'Load older content',
|
|
1173
|
+
'sessions.loadFullResult': 'Load full result',
|
|
1147
1174
|
'sessions.header.project': 'Project',
|
|
1148
1175
|
'sessions.header.session': 'Session',
|
|
1149
1176
|
'sessions.stat.context': '📐 {tokens} ctx',
|
|
@@ -1152,10 +1179,19 @@ const translations = {
|
|
|
1152
1179
|
'sessions.turnType.toolUse': 'Tool Use',
|
|
1153
1180
|
'sessions.turnType.toolResult': 'Tool Result',
|
|
1154
1181
|
'sessions.turnType.msgSend': 'Send Message',
|
|
1155
|
-
'sessions.count': '{
|
|
1182
|
+
'sessions.count': '{count} sessions',
|
|
1156
1183
|
'sessions.filter.valid': 'Valid',
|
|
1157
1184
|
'sessions.filter.validTitle': 'Only show valid sessions (at least 1 user message)',
|
|
1185
|
+
'sessions.filter.all': 'All',
|
|
1186
|
+
'sessions.filter.allTitle': 'Show all sessions, including sessions without user messages',
|
|
1187
|
+
'sessions.filter.sourceTitle': 'Filter by session type',
|
|
1188
|
+
'sessions.filter.sourceAll': 'All types',
|
|
1158
1189
|
'sessions.messageCountTitle': '{user} user messages / {total} total messages',
|
|
1190
|
+
'sessions.source.trigger': 'Trigger',
|
|
1191
|
+
'sessions.source.agent': 'Agent',
|
|
1192
|
+
'sessions.source.cli': 'CLI',
|
|
1193
|
+
'sessions.source.user': 'User',
|
|
1194
|
+
'sessions.source.unknown': 'Unknown',
|
|
1159
1195
|
'sessions.selectLog': 'Select a session to view the CC log',
|
|
1160
1196
|
'sessions.chatView': '💬 Chat View',
|
|
1161
1197
|
'sessions.fullView': '📜 Full View',
|
|
@@ -1165,10 +1201,21 @@ const translations = {
|
|
|
1165
1201
|
'sessions.empty.noDialogue': 'No user dialogue messages in this session',
|
|
1166
1202
|
'sessions.turnType.userInput': 'User Input',
|
|
1167
1203
|
'sessions.turnType.toolCall': 'Tool Call',
|
|
1204
|
+
'sessions.turnType.subtask': 'Subtask',
|
|
1168
1205
|
'sessions.turnType.system': 'System',
|
|
1206
|
+
'sessions.subtask.status.running': 'Running',
|
|
1207
|
+
'sessions.subtask.status.completed': 'Completed',
|
|
1208
|
+
'sessions.subtask.status.failed': 'Failed',
|
|
1209
|
+
'sessions.subtask.status.stopped': 'Stopped',
|
|
1210
|
+
'sessions.subtask.status.killed': 'Killed',
|
|
1211
|
+
'sessions.subtask.tools': '{count} tool uses',
|
|
1212
|
+
'sessions.subtask.tokens': '{count} tokens',
|
|
1213
|
+
'sessions.subtask.reason': 'Reason: {reason}',
|
|
1169
1214
|
'sessions.block.thinking': 'Thinking',
|
|
1170
1215
|
'sessions.block.result': 'Result',
|
|
1171
1216
|
'sessions.block.resultError': 'Result',
|
|
1217
|
+
'sessions.content.expand': 'Expand content',
|
|
1218
|
+
'sessions.content.collapse': 'Collapse content',
|
|
1172
1219
|
'sessions.contextTitle': 'Full context size sent to the model in the last turn',
|
|
1173
1220
|
'sessions.costTitle': 'Estimated cumulative cost by model pricing',
|
|
1174
1221
|
'sessions.msgUnit': 'msgs',
|
|
@@ -1495,6 +1542,7 @@ const translations = {
|
|
|
1495
1542
|
'triggers.feedback.defaultFailure': 'Default: 触发器失败:{{error.message}}',
|
|
1496
1543
|
'triggers.field.model': 'Model',
|
|
1497
1544
|
'triggers.field.effort': 'Effort',
|
|
1545
|
+
'triggers.baseagent.mismatch': 'Trigger fixed: {trigger}; current Agent: {agent}',
|
|
1498
1546
|
'triggers.field.permission': 'Permission Mode',
|
|
1499
1547
|
'triggers.delivery.on': 'Enabled',
|
|
1500
1548
|
'triggers.delivery.off': 'Disabled',
|
|
@@ -1896,7 +1944,7 @@ function connect() {
|
|
|
1896
1944
|
console.log('[ecweb] Available baseagents:', availableBaseagents, 'Selected:', sessSel.baseagent);
|
|
1897
1945
|
// 重新订阅当前视图(带上正确的参数)
|
|
1898
1946
|
if (currentView === 'session') {
|
|
1899
|
-
subscribe('session',
|
|
1947
|
+
subscribe('session', sessSel);
|
|
1900
1948
|
} else if (currentView === 'roleManagement') {
|
|
1901
1949
|
subscribeRoleManagementSubview();
|
|
1902
1950
|
} else if (currentView === 'system' && systemContentMode === 'monitor') {
|
|
@@ -1931,6 +1979,11 @@ function connect() {
|
|
|
1931
1979
|
if (pend) { delete _ipcPending[msg.requestId]; pend.resolve(msg.data); }
|
|
1932
1980
|
return;
|
|
1933
1981
|
}
|
|
1982
|
+
if (msg.type === 'session.result') {
|
|
1983
|
+
const pending = _sessionResultPending[msg.requestId];
|
|
1984
|
+
if (pending) { delete _sessionResultPending[msg.requestId]; pending.resolve(msg.data); }
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1934
1987
|
if (msg.type === 'snapshot' || msg.type === 'delta') {
|
|
1935
1988
|
console.log('[ecweb] Received', msg.type, 'for view:', msg.view, 'currentView:', currentView);
|
|
1936
1989
|
// 系统基础快照不主动执行健康检查;保留用户点击按钮后得到的 check/upgrade 结果。
|
|
@@ -1940,6 +1993,24 @@ function connect() {
|
|
|
1940
1993
|
check: msg.data.check ?? state.system.check,
|
|
1941
1994
|
upgrade: state.system.upgrade ?? msg.data.upgrade,
|
|
1942
1995
|
};
|
|
1996
|
+
} else if (msg.view === 'msg'
|
|
1997
|
+
&& msg.data?.messagePage?.before != null
|
|
1998
|
+
&& state.msg?.scope === msg.data.scope
|
|
1999
|
+
&& state.msg?.peer === msg.data.peer) {
|
|
2000
|
+
const existing = state.msg.messages || [];
|
|
2001
|
+
const incoming = msg.data.messages || [];
|
|
2002
|
+
const incomingKeys = new Set(incoming.map(item => item.msgId || `${item.ts}:${item.dir}:${item.content}`));
|
|
2003
|
+
const merged = incoming.concat(existing.filter(item => !incomingKeys.has(item.msgId || `${item.ts}:${item.dir}:${item.content}`)));
|
|
2004
|
+
state.msg = { ...msg.data, messages: merged };
|
|
2005
|
+
} else if (msg.view === 'session'
|
|
2006
|
+
&& msg.data?.turnPage?.before != null
|
|
2007
|
+
&& state.session?.sessionId === msg.data.sessionId
|
|
2008
|
+
&& state.session?.project === msg.data.project) {
|
|
2009
|
+
const existing = state.session.turns || [];
|
|
2010
|
+
const incoming = msg.data.turns || [];
|
|
2011
|
+
const incomingKeys = new Set(incoming.map(turn => turn.uuid || `${turn.ts}:${turn.category}:${JSON.stringify(turn.blocks || [])}`));
|
|
2012
|
+
const merged = incoming.concat(existing.filter(turn => !incomingKeys.has(turn.uuid || `${turn.ts}:${turn.category}:${JSON.stringify(turn.blocks || [])}`)));
|
|
2013
|
+
state.session = { ...msg.data, turns: merged };
|
|
1943
2014
|
} else {
|
|
1944
2015
|
state[msg.view] = msg.data;
|
|
1945
2016
|
}
|
|
@@ -2002,6 +2073,8 @@ function menuSend(payload) {
|
|
|
2002
2073
|
|
|
2003
2074
|
const _ipcPending = {};
|
|
2004
2075
|
let _ipcSeq = 0;
|
|
2076
|
+
const _sessionResultPending = {};
|
|
2077
|
+
let _sessionResultSeq = 0;
|
|
2005
2078
|
function ipcSend(payload, timeoutMs) {
|
|
2006
2079
|
return new Promise((resolve, reject) => {
|
|
2007
2080
|
if (!ws || ws.readyState !== WebSocket.OPEN) { reject(new Error(t('common.notConnected'))); return; }
|
|
@@ -2015,14 +2088,26 @@ function ipcSend(payload, timeoutMs) {
|
|
|
2015
2088
|
});
|
|
2016
2089
|
}
|
|
2017
2090
|
|
|
2091
|
+
function sessionResultSend(params) {
|
|
2092
|
+
return new Promise((resolve, reject) => {
|
|
2093
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) { reject(new Error(t('common.notConnected'))); return; }
|
|
2094
|
+
const requestId = 'ecw-session-result-' + (++_sessionResultSeq);
|
|
2095
|
+
_sessionResultPending[requestId] = { resolve, reject };
|
|
2096
|
+
setTimeout(() => {
|
|
2097
|
+
if (_sessionResultPending[requestId]) { delete _sessionResultPending[requestId]; reject(new Error('timeout')); }
|
|
2098
|
+
}, 10000);
|
|
2099
|
+
ws.send(JSON.stringify({ type: 'session.result', requestId, params }));
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2018
2103
|
// 心跳
|
|
2019
2104
|
setInterval(() => {
|
|
2020
2105
|
if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'ping' }));
|
|
2021
2106
|
}, 20000);
|
|
2022
2107
|
|
|
2023
2108
|
// ── Tab 切换 ──
|
|
2024
|
-
let msgSel = { aid: null, peer: null };
|
|
2025
|
-
let sessSel = { sessionId: null, project: null, baseagent: null };
|
|
2109
|
+
let msgSel = { aid: null, peer: null, before: null };
|
|
2110
|
+
let sessSel = { sessionId: null, project: null, baseagent: null, turnBefore: null };
|
|
2026
2111
|
let trigSel = { agent: null };
|
|
2027
2112
|
let trigAgentsCollapsed = false;
|
|
2028
2113
|
const _trigOps = new Map(); // Map<triggerId, string>
|
|
@@ -2030,7 +2115,8 @@ let _triggerEdit = null;
|
|
|
2030
2115
|
let _triggerHistory = null;
|
|
2031
2116
|
let _triggerTest = null;
|
|
2032
2117
|
let sessSearch = '';
|
|
2033
|
-
let
|
|
2118
|
+
let sessShowAll = false; // false=只显示有效会话;true=显示全部
|
|
2119
|
+
let sessSourceFilter = 'all'; // all / trigger / agent / cli / user / unknown
|
|
2034
2120
|
let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
|
|
2035
2121
|
let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
|
|
2036
2122
|
let logSel = { source: null, level: 'all', module: 'all', search: '', limit: 500, follow: true };
|
|
@@ -2087,8 +2173,8 @@ function switchView(view) {
|
|
|
2087
2173
|
document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.view === view));
|
|
2088
2174
|
document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === 'view-' + view));
|
|
2089
2175
|
// 切换时按当前选择恢复订阅
|
|
2090
|
-
if (view === 'msg') subscribe('msg',
|
|
2091
|
-
else if (view === 'session') subscribe('session',
|
|
2176
|
+
if (view === 'msg') subscribe('msg', msgSel);
|
|
2177
|
+
else if (view === 'session') subscribe('session', sessSel);
|
|
2092
2178
|
else if (view === 'cache') subscribe('cache', {});
|
|
2093
2179
|
else if (view === 'system' && systemContentMode === 'monitor') subscribe('monitor', { range: monRange });
|
|
2094
2180
|
else if (view === 'system') subscribe('system', {});
|
|
@@ -2414,8 +2500,13 @@ const MSG_TYPE_META = {
|
|
|
2414
2500
|
file: { icon: '📎', label: () => t('messages.msgType.file') },
|
|
2415
2501
|
command: { icon: '⌘', label: () => t('messages.msgType.command') }
|
|
2416
2502
|
};
|
|
2417
|
-
|
|
2418
|
-
|
|
2503
|
+
// 渠道标:AUN 是默认渠道,不标;其余渠道标出来,免得混排后分不清来路
|
|
2504
|
+
function channelTagHtml(channel, cls) {
|
|
2505
|
+
if (!channel || channel === 'aun') return '';
|
|
2506
|
+
return `<span class="${cls}">${esc(channel)}</span>`;
|
|
2507
|
+
}
|
|
2508
|
+
function msgTagsHtml(kind, encrypt, chatmode, dir, channel) {
|
|
2509
|
+
let h = channelTagHtml(channel, 'mtag mtag-channel');
|
|
2419
2510
|
// 'send' 仅出向才是「回复」;入向是用户输入,不打回复标记
|
|
2420
2511
|
const km = (kind === 'send' && dir === 'in') ? null : MSG_KIND_META[kind];
|
|
2421
2512
|
if (km) h += `<span class="mtag${kind === 'send' ? ' mtag-reply' : ''}">${km.icon}${km.label()}</span>`;
|
|
@@ -2425,24 +2516,39 @@ function msgTagsHtml(kind, encrypt, chatmode, dir) {
|
|
|
2425
2516
|
}
|
|
2426
2517
|
|
|
2427
2518
|
// 消息行:方向箭头 + 标记 + 对端 + 文字
|
|
2519
|
+
function latestAgentPreviewMessage(s) {
|
|
2520
|
+
const recent = Array.isArray(s.recentMessages) ? s.recentMessages[0] : null;
|
|
2521
|
+
if (recent?.text) return recent;
|
|
2522
|
+
const recvTs = s.lastReceivedAt || 0;
|
|
2523
|
+
const sentTs = s.lastSentAt || 0;
|
|
2524
|
+
if (sentTs > recvTs && s.lastSentText) {
|
|
2525
|
+
return {
|
|
2526
|
+
dir: 'out', peer: s.lastSentTo, text: s.lastSentText, kind: s.lastSentKind,
|
|
2527
|
+
encrypt: s.lastSentEncrypt, chatmode: s.lastSentChatmode, channel: s.lastSentChannel,
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
if (s.lastReceivedText) {
|
|
2531
|
+
return {
|
|
2532
|
+
dir: 'in', peer: s.lastReceivedFrom, text: s.lastReceivedText, kind: s.lastReceivedKind,
|
|
2533
|
+
encrypt: s.lastReceivedEncrypt, chatmode: s.lastReceivedChatmode, channel: s.lastReceivedChannel,
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
return null;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2428
2539
|
function agentPreviewHtml(s) {
|
|
2429
2540
|
const clip = (t) => esc(String(t).replace(/\n/g, ' ').slice(0, 80));
|
|
2430
|
-
const line = (dir, peer, text, kind, encrypt, chatmode) => {
|
|
2541
|
+
const line = (dir, peer, text, kind, encrypt, chatmode, channel) => {
|
|
2431
2542
|
const arrow = dir === 'in' ? '<span class="arrow-in">↓</span>' : '<span class="arrow-out">↑</span>';
|
|
2432
|
-
const tags = msgTagsHtml(kind, encrypt, chatmode, dir);
|
|
2543
|
+
const tags = msgTagsHtml(kind, encrypt, chatmode, dir, channel);
|
|
2433
2544
|
const peerHtml = peer ? `<span class="peer">${esc(shortAid(peer))}</span>: ` : '';
|
|
2434
2545
|
const textCls = dir === 'in' ? 'text-in' : 'text-out';
|
|
2435
2546
|
return `${arrow}${tags ? ' ' + tags + ' ' : ' '}${peerHtml}<span class="${textCls}">${clip(text)}</span>`;
|
|
2436
2547
|
};
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
if (sentTs > recvTs && s.lastSentText)
|
|
2442
|
-
return line('out', s.lastSentTo, s.lastSentText, s.lastSentKind, s.lastSentEncrypt, s.lastSentChatmode);
|
|
2443
|
-
if (s.lastReceivedText)
|
|
2444
|
-
return line('in', s.lastReceivedFrom, s.lastReceivedText, s.lastReceivedKind, s.lastReceivedEncrypt, s.lastReceivedChatmode);
|
|
2445
|
-
return '';
|
|
2548
|
+
const latest = latestAgentPreviewMessage(s);
|
|
2549
|
+
return latest
|
|
2550
|
+
? line(latest.dir, latest.peer, latest.text, latest.kind, latest.encrypt, latest.chatmode, latest.channel)
|
|
2551
|
+
: '';
|
|
2446
2552
|
}
|
|
2447
2553
|
|
|
2448
2554
|
// HTML tooltip(最近 N 轮):时间 + 彩色箭头 + 方式 + 对端 + 文字
|
|
@@ -2458,10 +2564,11 @@ function recentMsgTooltipHtml(recent) {
|
|
|
2458
2564
|
const kh = km ? `<span class="tip-kind${m.kind === 'send' ? ' tip-kind-reply' : ''}">${km.icon}${km.label()}</span>` : '';
|
|
2459
2565
|
const enc = m.encrypt != null ? `<span class="tip-flag">${m.encrypt ? t('messages.tag.encrypted') : t('messages.tag.plain')}</span>` : '';
|
|
2460
2566
|
const mode = m.chatmode ? `<span class="tip-flag">${m.chatmode === 'proactive' ? t('messages.tag.proactive') : (m.chatmode === 'inject' ? t('messages.tag.inject') : t('messages.tag.responsive'))}</span>` : '';
|
|
2567
|
+
const chan = channelTagHtml(m.channel, 'tip-flag tip-flag-channel');
|
|
2461
2568
|
const peer = m.peer ? esc(shortAid(m.peer)) : '';
|
|
2462
2569
|
const text = esc(String(m.text).replace(/\n/g, ' ').slice(0, 140));
|
|
2463
2570
|
const time = m.ts ? `<span class="tip-time">${fmtTime(m.ts)}</span>` : '';
|
|
2464
|
-
h += `<div class="tip-row ${rcls}">${time}${arrow}${kh}${enc}${mode} <b>${peer}</b> ${text}</div>`;
|
|
2571
|
+
h += `<div class="tip-row ${rcls}">${time}${arrow}${chan}${kh}${enc}${mode} <b>${peer}</b> ${text}</div>`;
|
|
2465
2572
|
}
|
|
2466
2573
|
return h + '</div>';
|
|
2467
2574
|
}
|
|
@@ -2857,6 +2964,14 @@ function card(label, value, valCls, sub) {
|
|
|
2857
2964
|
}
|
|
2858
2965
|
|
|
2859
2966
|
// ── Messages 视图 ──
|
|
2967
|
+
function msgChannelTone(channelType) {
|
|
2968
|
+
const normalized = String(channelType || '').trim().toLowerCase();
|
|
2969
|
+
if (normalized === 'qq') return 'qqbot';
|
|
2970
|
+
return ['aun', 'wechat', 'wecom', 'feishu', 'dingtalk', 'qqbot', 'daemon'].includes(normalized)
|
|
2971
|
+
? normalized
|
|
2972
|
+
: 'other';
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2860
2975
|
function renderMsg(data) {
|
|
2861
2976
|
if (!data) return;
|
|
2862
2977
|
const aids = data.scopes || data.aids || [];
|
|
@@ -2867,7 +2982,7 @@ function renderMsg(data) {
|
|
|
2867
2982
|
if (!selectedAidExists) {
|
|
2868
2983
|
const nextAid = aids[0]?.aid || null;
|
|
2869
2984
|
const selectionChanged = msgSel.aid !== nextAid || msgSel.peer !== null;
|
|
2870
|
-
msgSel = { aid: nextAid, peer: null };
|
|
2985
|
+
msgSel = { aid: nextAid, peer: null, before: null };
|
|
2871
2986
|
if (selectionChanged && nextAid) subscribe('msg', msgSel);
|
|
2872
2987
|
}
|
|
2873
2988
|
const scopeMatches = data.scope === msgSel.aid;
|
|
@@ -2877,7 +2992,7 @@ function renderMsg(data) {
|
|
|
2877
2992
|
if (!selectedPeerExists) {
|
|
2878
2993
|
const nextPeer = peers[0]?.peerId || null;
|
|
2879
2994
|
const selectionChanged = msgSel.peer !== nextPeer;
|
|
2880
|
-
msgSel = { aid: msgSel.aid, peer: nextPeer };
|
|
2995
|
+
msgSel = { aid: msgSel.aid, peer: nextPeer, before: null };
|
|
2881
2996
|
if (selectionChanged && nextPeer) subscribe('msg', msgSel);
|
|
2882
2997
|
}
|
|
2883
2998
|
}
|
|
@@ -2889,7 +3004,7 @@ function renderMsg(data) {
|
|
|
2889
3004
|
let aidsHtml = `<div class="col-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
|
|
2890
3005
|
for (const a of aids) {
|
|
2891
3006
|
const sel = a.aid === msgSel.aid ? ' sel' : '';
|
|
2892
|
-
const name = a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown';
|
|
3007
|
+
const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
|
|
2893
3008
|
const privateCount = Math.max(0, (a.peerCount || 0) - (a.groupCount || 0));
|
|
2894
3009
|
const privateBit = tf('messages.privateCount', { count: privateCount });
|
|
2895
3010
|
const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
|
|
@@ -2902,7 +3017,7 @@ function renderMsg(data) {
|
|
|
2902
3017
|
$('#msg-aids').querySelectorAll('.list-item').forEach(item => {
|
|
2903
3018
|
item.onclick = () => {
|
|
2904
3019
|
if (item.dataset.aid === msgSel.aid) return;
|
|
2905
|
-
msgSel = { aid: item.dataset.aid, peer: null };
|
|
3020
|
+
msgSel = { aid: item.dataset.aid, peer: null, before: null };
|
|
2906
3021
|
subscribe('msg', msgSel);
|
|
2907
3022
|
};
|
|
2908
3023
|
});
|
|
@@ -2933,7 +3048,9 @@ function renderMsg(data) {
|
|
|
2933
3048
|
? t('messages.tag.topic')
|
|
2934
3049
|
: p.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
|
|
2935
3050
|
const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(p.partition || 'main')}">${esc(partitionLabel)}</span>`;
|
|
2936
|
-
const channelTag = channelLabel
|
|
3051
|
+
const channelTag = channelLabel
|
|
3052
|
+
? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(p.channelType)}">${esc(channelLabel)}</span>`
|
|
3053
|
+
: '';
|
|
2937
3054
|
peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
|
|
2938
3055
|
`<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
|
|
2939
3056
|
`<div class="sub">${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
|
|
@@ -2943,7 +3060,7 @@ function renderMsg(data) {
|
|
|
2943
3060
|
$('#msg-peers').querySelectorAll('.list-item').forEach(item => {
|
|
2944
3061
|
item.onclick = () => {
|
|
2945
3062
|
if (item.dataset.peer === msgSel.peer) return;
|
|
2946
|
-
msgSel = { aid: msgSel.aid, peer: item.dataset.peer };
|
|
3063
|
+
msgSel = { aid: msgSel.aid, peer: item.dataset.peer, before: null };
|
|
2947
3064
|
subscribe('msg', msgSel);
|
|
2948
3065
|
};
|
|
2949
3066
|
});
|
|
@@ -2961,6 +3078,9 @@ function renderMsg(data) {
|
|
|
2961
3078
|
}
|
|
2962
3079
|
const atBottom = stream.scrollHeight - stream.scrollTop - stream.clientHeight < 60;
|
|
2963
3080
|
let msgHtml = '<div class="msg-stream-body">';
|
|
3081
|
+
if (data.messagePage?.hasMore && data.messagePage.nextBefore != null) {
|
|
3082
|
+
msgHtml += `<button class="ctrl-btn msg-load-more" type="button" data-before="${data.messagePage.nextBefore}">${t('messages.loadOlder') || '加载更早消息'}</button>`;
|
|
3083
|
+
}
|
|
2964
3084
|
for (const m of messages) {
|
|
2965
3085
|
const cls = m.dir === 'in' ? 'in' : 'out';
|
|
2966
3086
|
const arrow = m.dir === 'in' ? '↓' : '↑';
|
|
@@ -2983,6 +3103,11 @@ function renderMsg(data) {
|
|
|
2983
3103
|
}
|
|
2984
3104
|
msgHtml += messages.length ? '</div>' : `<div class="empty">${t('messages.empty.noMessages')}</div></div>`;
|
|
2985
3105
|
stream.innerHTML = renderMsgSessionHeader(sessionContext) + msgHtml;
|
|
3106
|
+
const loadMore = stream.querySelector('.msg-load-more');
|
|
3107
|
+
if (loadMore) loadMore.onclick = () => {
|
|
3108
|
+
msgSel = { aid: msgSel.aid, peer: msgSel.peer, before: Number(loadMore.dataset.before) };
|
|
3109
|
+
subscribe('msg', msgSel);
|
|
3110
|
+
};
|
|
2986
3111
|
if (atBottom) stream.scrollTop = stream.scrollHeight;
|
|
2987
3112
|
}
|
|
2988
3113
|
|
|
@@ -3020,6 +3145,20 @@ function renderMsgSessionHeader(context) {
|
|
|
3020
3145
|
}
|
|
3021
3146
|
|
|
3022
3147
|
// ── Sessions 视图 ──
|
|
3148
|
+
function filterSessionTranscripts(transcripts, showAll, sourceType = 'all') {
|
|
3149
|
+
return (transcripts || []).filter(session => {
|
|
3150
|
+
const valid = showAll || (session.userMsgs || 0) >= 1;
|
|
3151
|
+
const sourceMatches = sourceType === 'all' || (session.sourceType || 'unknown') === sourceType;
|
|
3152
|
+
return valid && sourceMatches;
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
function renderSessionSourceBadge(sourceType) {
|
|
3157
|
+
const type = ['trigger', 'agent', 'cli', 'user'].includes(sourceType) ? sourceType : 'unknown';
|
|
3158
|
+
const label = t(`sessions.source.${type}`);
|
|
3159
|
+
return `<span class="session-source-badge source-${type}" title="${esc(label)}">${esc(label)}</span>`;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3023
3162
|
function renderSession(data) {
|
|
3024
3163
|
if (!data) return;
|
|
3025
3164
|
const projects = data.projects || [];
|
|
@@ -3034,8 +3173,7 @@ function renderSession(data) {
|
|
|
3034
3173
|
}
|
|
3035
3174
|
|
|
3036
3175
|
// 有效筛选
|
|
3037
|
-
const filtered = transcripts
|
|
3038
|
-
.filter(t => !sessFilterNormal || (t.userMsgs || 0) >= 1);
|
|
3176
|
+
const filtered = filterSessionTranscripts(transcripts, sessShowAll, sessSourceFilter);
|
|
3039
3177
|
|
|
3040
3178
|
// 左栏:过滤条 + 列表
|
|
3041
3179
|
const projOpts = projects.map(p =>
|
|
@@ -3050,18 +3188,25 @@ function renderSession(data) {
|
|
|
3050
3188
|
if (availableBaseagents.codex) {
|
|
3051
3189
|
baseagentOpts += `<option value="codex"${sessSel.baseagent === 'codex' ? ' selected' : ''}>Codex</option>`;
|
|
3052
3190
|
}
|
|
3191
|
+
const sourceTypes = ['all', 'user', 'agent', 'trigger', 'cli', 'unknown'];
|
|
3192
|
+
const sourceOpts = sourceTypes.map(type => {
|
|
3193
|
+
const label = type === 'all' ? t('sessions.filter.sourceAll') : t(`sessions.source.${type}`);
|
|
3194
|
+
return `<option value="${type}"${sessSourceFilter === type ? ' selected' : ''}>${esc(label)}</option>`;
|
|
3195
|
+
}).join('');
|
|
3053
3196
|
|
|
3054
|
-
const
|
|
3197
|
+
const filterLabelKey = sessShowAll ? 'sessions.filter.all' : 'sessions.filter.valid';
|
|
3198
|
+
const filterTitleKey = sessShowAll ? 'sessions.filter.allTitle' : 'sessions.filter.validTitle';
|
|
3055
3199
|
let listHtml = '<div class="sess-filter">' +
|
|
3056
3200
|
'<div class="sess-select-row">' +
|
|
3057
3201
|
`<select id="sess-baseagent" title="Base Agent">${baseagentOpts}</select>` +
|
|
3058
3202
|
`<select id="sess-project" title="${t('sessions.header.project')}">${projOpts}</select>` +
|
|
3203
|
+
`<select id="sess-source-type" title="${t('sessions.filter.sourceTitle')}">${sourceOpts}</select>` +
|
|
3059
3204
|
'</div>' +
|
|
3060
3205
|
'<div class="sess-filter-row">' +
|
|
3061
|
-
`<div class="sess-count">${tf('sessions.count', {
|
|
3062
|
-
`<label class="sess-switch" title="${t(
|
|
3063
|
-
`<span>${t(
|
|
3064
|
-
`<input id="sess-filter-toggle" type="checkbox"${
|
|
3206
|
+
`<div class="sess-count">${tf('sessions.count', { count: filtered.length })}</div>` +
|
|
3207
|
+
`<label class="sess-switch" title="${t(filterTitleKey)}">` +
|
|
3208
|
+
`<span>${t(filterLabelKey)}</span>` +
|
|
3209
|
+
`<input id="sess-filter-toggle" type="checkbox"${sessShowAll ? ' checked' : ''}>` +
|
|
3065
3210
|
'<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>' +
|
|
3066
3211
|
'</label>' +
|
|
3067
3212
|
'</div></div>' +
|
|
@@ -3076,12 +3221,13 @@ function renderSession(data) {
|
|
|
3076
3221
|
let badge = '';
|
|
3077
3222
|
if (t.bound) {
|
|
3078
3223
|
const dot = t.online ? '<span class="dot on"></span>' : '<span class="dot idle"></span>';
|
|
3079
|
-
badge = `<span class="bind-badge">${dot}${esc(t.boundChannel || '')}·${esc(shortAid(t.
|
|
3224
|
+
badge = `<span class="bind-badge">${dot}${esc(t.boundChannel || '')}·${esc(shortAid(t.boundSelf || ''))}</span>`;
|
|
3080
3225
|
}
|
|
3226
|
+
const sourceBadge = renderSessionSourceBadge(t.sourceType);
|
|
3081
3227
|
const msgs = `<span class="msg-count" title="${tf('sessions.messageCountTitle', { user: t.userMsgs || 0, total: t.totalMsgs || 0 })}">💬 ${t.userMsgs || 0}/${t.totalMsgs || 0}</span>`;
|
|
3082
3228
|
listHtml += `<div class="list-item${sel}" data-sid="${esc(t.id)}">` +
|
|
3083
3229
|
`<div class="name">${esc(title)}</div>` +
|
|
3084
|
-
`<div class="sub">${fmtAgo(t.lastActivity)} · ${msgs}
|
|
3230
|
+
`<div class="sub">${fmtAgo(t.lastActivity)} · ${msgs} · ${sourceBadge}${badge}</div>` +
|
|
3085
3231
|
'</div>';
|
|
3086
3232
|
}
|
|
3087
3233
|
listHtml += '</div>';
|
|
@@ -3091,19 +3237,24 @@ function renderSession(data) {
|
|
|
3091
3237
|
const baseagentSel = $('#sess-baseagent');
|
|
3092
3238
|
if (baseagentSel) baseagentSel.onchange = () => {
|
|
3093
3239
|
console.log('[ecweb] Baseagent changed to:', baseagentSel.value);
|
|
3094
|
-
sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value };
|
|
3240
|
+
sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value, turnBefore: null };
|
|
3095
3241
|
console.log('[ecweb] Subscribing to session with baseagent:', sessSel.baseagent);
|
|
3096
3242
|
subscribe('session', { baseagent: sessSel.baseagent });
|
|
3097
3243
|
};
|
|
3098
3244
|
const projSel = $('#sess-project');
|
|
3099
3245
|
if (projSel) projSel.onchange = () => {
|
|
3100
|
-
sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent };
|
|
3246
|
+
sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent, turnBefore: null };
|
|
3101
3247
|
subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
|
|
3102
3248
|
};
|
|
3103
3249
|
const filterToggle = $('#sess-filter-toggle');
|
|
3104
|
-
if (filterToggle) filterToggle.onchange = () => {
|
|
3250
|
+
if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; renderSession(state.session); };
|
|
3251
|
+
const sourceTypeSelect = $('#sess-source-type');
|
|
3252
|
+
if (sourceTypeSelect) sourceTypeSelect.onchange = () => {
|
|
3253
|
+
sessSourceFilter = sourceTypeSelect.value;
|
|
3254
|
+
renderSession(state.session);
|
|
3255
|
+
};
|
|
3105
3256
|
$('#sess-list').querySelectorAll('.list-item').forEach(item => {
|
|
3106
|
-
item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent }; subscribe('session', sessSel); };
|
|
3257
|
+
item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent, turnBefore: null }; subscribe('session', sessSel); };
|
|
3107
3258
|
});
|
|
3108
3259
|
|
|
3109
3260
|
// 右:transcript 详情
|
|
@@ -3113,11 +3264,22 @@ function renderSession(data) {
|
|
|
3113
3264
|
const h = data.header || {};
|
|
3114
3265
|
const atBottom = detail.scrollHeight - detail.scrollTop - detail.clientHeight < 60;
|
|
3115
3266
|
let html = renderSessHeader(h, data.baseagent || sessSel.baseagent);
|
|
3267
|
+
if (data.turnPage?.hasMore && data.turnPage.nextBefore != null) {
|
|
3268
|
+
html += `<button class="ctrl-btn sess-load-more" type="button" data-before="${data.turnPage.nextBefore}">${t('sessions.loadOlder') || '加载更早内容'}</button>`;
|
|
3269
|
+
}
|
|
3116
3270
|
html += '<div class="turn-list">' + (sessChatMode ? renderChatView(turns) : renderFullView(turns)) + '</div>';
|
|
3117
3271
|
detail.innerHTML = html;
|
|
3118
3272
|
|
|
3273
|
+
const loadMore = detail.querySelector('.sess-load-more');
|
|
3274
|
+
if (loadMore) loadMore.onclick = () => {
|
|
3275
|
+
sessSel = { ...sessSel, turnBefore: Number(loadMore.dataset.before) };
|
|
3276
|
+
subscribe('session', sessSel);
|
|
3277
|
+
};
|
|
3278
|
+
|
|
3119
3279
|
const toggle = $('#chat-toggle');
|
|
3120
3280
|
if (toggle) toggle.onclick = () => { sessChatMode = !sessChatMode; renderSession(state.session); };
|
|
3281
|
+
setupContentCollapses(detail);
|
|
3282
|
+
setupTruncatedSessionResults(detail, data);
|
|
3121
3283
|
if (atBottom) detail.scrollTop = detail.scrollHeight;
|
|
3122
3284
|
}
|
|
3123
3285
|
|
|
@@ -3126,13 +3288,19 @@ function renderFullView(turns) {
|
|
|
3126
3288
|
let html = '';
|
|
3127
3289
|
for (const t of turns) {
|
|
3128
3290
|
const cat = t.category || t.role;
|
|
3291
|
+
const blocks = t.blocks || [];
|
|
3292
|
+
// Hide already-cached empty assistant turns until their transcript is reparsed.
|
|
3293
|
+
if (cat === 'model_output' && !blocks.length) continue;
|
|
3129
3294
|
const c = CAT_META[cat] || CAT_META.system;
|
|
3130
3295
|
const usage = (t.inputTokens || t.outputTokens)
|
|
3131
3296
|
? `<span class="turn-usage">${esc(t.model || '')} · in ${t.inputTokens || 0} / out ${t.outputTokens || 0}</span>` : '';
|
|
3132
3297
|
html += `<div class="turn cat-${cat}">` +
|
|
3133
3298
|
`<div class="turn-head"><span class="turn-role">${c.icon} ${c.label()}</span>` +
|
|
3134
3299
|
`<span class="turn-time">${t.ts ? fmtTime(t.ts) : ''}</span>${usage}</div>` +
|
|
3135
|
-
`<div class="turn-blocks">${renderBlocks(
|
|
3300
|
+
`<div class="turn-blocks">${renderBlocks(blocks, {
|
|
3301
|
+
collapseText: cat === 'model_output' || cat === 'user_input',
|
|
3302
|
+
collapseKind: cat === 'user_input' ? 'user-input' : 'model-output',
|
|
3303
|
+
})}</div></div>`;
|
|
3136
3304
|
}
|
|
3137
3305
|
return html;
|
|
3138
3306
|
}
|
|
@@ -3179,7 +3347,9 @@ function renderChatView(turns) {
|
|
|
3179
3347
|
while (next < items.length && items[next].type === 'proc') { group.push(items[next]); next++; }
|
|
3180
3348
|
const peer = it.peer ? shortAid(it.peer) : '';
|
|
3181
3349
|
const time = `${it.ts ? fmtTime(it.ts) : ''}${peer ? ' → ' + esc(peer) : ''}`;
|
|
3182
|
-
|
|
3350
|
+
const kind = it.type === 'out' ? 'model-output' : 'user-input';
|
|
3351
|
+
const bubble = renderCollapsibleText(it.text, `chat-bubble ${kind}-content`, kind);
|
|
3352
|
+
html += `<div class="chat-row ${it.type}">${bubble}` +
|
|
3183
3353
|
'<div class="chat-meta-row">' +
|
|
3184
3354
|
(group.length ? renderProcGroup(group, true, time) : `<div class="chat-time">${time}</div>`) +
|
|
3185
3355
|
'</div></div>';
|
|
@@ -3201,7 +3371,10 @@ function renderProcGroup(group, inline = false, time = '') {
|
|
|
3201
3371
|
const c = CAT_META[g.cat] || CAT_META.system;
|
|
3202
3372
|
inner += `<div class="turn cat-${g.cat}"><div class="turn-head"><span class="turn-role">${c.icon} ${c.label()}</span>` +
|
|
3203
3373
|
`<span class="turn-time">${g.ts ? fmtTime(g.ts) : ''}</span></div>` +
|
|
3204
|
-
`<div class="turn-blocks">${renderBlocks(g.blocks
|
|
3374
|
+
`<div class="turn-blocks">${renderBlocks(g.blocks, {
|
|
3375
|
+
collapseText: g.cat === 'model_output' || g.cat === 'user_input',
|
|
3376
|
+
collapseKind: g.cat === 'user_input' ? 'user-input' : 'model-output',
|
|
3377
|
+
})}</div></div>`;
|
|
3205
3378
|
}
|
|
3206
3379
|
const summary = tf('sessions.procSummary', { count: group.length });
|
|
3207
3380
|
if (inline) {
|
|
@@ -3220,6 +3393,7 @@ const CAT_META = {
|
|
|
3220
3393
|
tool_call: { label: () => t('sessions.turnType.toolCall'), icon: '🟣' },
|
|
3221
3394
|
tool_result: { label: () => t('sessions.turnType.toolResult'), icon: '🟠' },
|
|
3222
3395
|
msg_send: { label: () => t('sessions.turnType.msgSend'), icon: '📤' },
|
|
3396
|
+
subtask: { label: () => t('sessions.turnType.subtask'), icon: '◆' },
|
|
3223
3397
|
system: { label: () => t('sessions.turnType.system'), icon: '⚪' },
|
|
3224
3398
|
};
|
|
3225
3399
|
|
|
@@ -3236,7 +3410,7 @@ function renderSessHeader(h, baseagent) {
|
|
|
3236
3410
|
let bind = '';
|
|
3237
3411
|
if (h.bound) {
|
|
3238
3412
|
const dot = h.online ? `<span class="dot on"></span>${t('common.online')}` : `<span class="dot idle"></span>${t('common.offline')}`;
|
|
3239
|
-
bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.
|
|
3413
|
+
bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.boundSelf || ''))} ${dot}</span>`;
|
|
3240
3414
|
}
|
|
3241
3415
|
return '<div class="sess-header">' +
|
|
3242
3416
|
'<div class="sh-title-row">' +
|
|
@@ -3269,6 +3443,7 @@ function renderCatBar(counts) {
|
|
|
3269
3443
|
['tool_call', counts.toolCall],
|
|
3270
3444
|
['tool_result', counts.toolResult],
|
|
3271
3445
|
['msg_send', counts.msgSend],
|
|
3446
|
+
['subtask', counts.subtask],
|
|
3272
3447
|
];
|
|
3273
3448
|
let s = '<div class="sh-cats">';
|
|
3274
3449
|
for (const [cat, n] of items) {
|
|
@@ -3295,11 +3470,50 @@ function toolParamValue(value) {
|
|
|
3295
3470
|
try { return JSON.stringify(value, null, 2); } catch { return String(value); }
|
|
3296
3471
|
}
|
|
3297
3472
|
|
|
3298
|
-
function
|
|
3473
|
+
function setupContentCollapses(container) {
|
|
3474
|
+
const contents = container.querySelectorAll('.collapse-content');
|
|
3475
|
+
contents.forEach(content => {
|
|
3476
|
+
const body = content.querySelector('.collapse-body');
|
|
3477
|
+
const arrowToggle = content.querySelector('.collapse-arrow-toggle');
|
|
3478
|
+
if (!body || !arrowToggle) return;
|
|
3479
|
+
|
|
3480
|
+
body.classList.remove('is-collapsed');
|
|
3481
|
+
content.classList.remove('is-collapsed');
|
|
3482
|
+
body.style.maxHeight = '';
|
|
3483
|
+
arrowToggle.hidden = true;
|
|
3484
|
+
|
|
3485
|
+
const styles = getComputedStyle(body);
|
|
3486
|
+
const previewLines = Number.parseInt(styles.getPropertyValue('--collapse-preview-lines'), 10) || 12;
|
|
3487
|
+
const lineHeight = Number.parseFloat(styles.lineHeight) || 17;
|
|
3488
|
+
const padding = Number.parseFloat(styles.paddingTop) + Number.parseFloat(styles.paddingBottom);
|
|
3489
|
+
const previewHeight = previewLines * lineHeight + padding;
|
|
3490
|
+
if (body.scrollHeight <= previewHeight + 1) return;
|
|
3491
|
+
|
|
3492
|
+
const setExpanded = expanded => {
|
|
3493
|
+
body.classList.toggle('is-collapsed', !expanded);
|
|
3494
|
+
content.classList.toggle('is-collapsed', !expanded);
|
|
3495
|
+
body.style.maxHeight = `${expanded ? body.scrollHeight : previewHeight}px`;
|
|
3496
|
+
arrowToggle.setAttribute('aria-expanded', String(expanded));
|
|
3497
|
+
arrowToggle.setAttribute('aria-label', t(expanded ? 'sessions.content.collapse' : 'sessions.content.expand'));
|
|
3498
|
+
};
|
|
3499
|
+
setExpanded(false);
|
|
3500
|
+
arrowToggle.hidden = false;
|
|
3501
|
+
arrowToggle.onclick = () => setExpanded(body.classList.contains('is-collapsed'));
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
function renderCollapsibleText(text, classes, kind = 'model-output') {
|
|
3506
|
+
return `<div class="${classes} collapse-content"><div class="collapse-body ${kind}-body">${esc(text)}</div>` +
|
|
3507
|
+
'<button class="collapse-arrow-toggle" type="button" hidden aria-expanded="false"><span class="collapse-arrow-icon" aria-hidden="true"></span></button></div>';
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
function renderBlocks(blocks, options = {}) {
|
|
3299
3511
|
let out = '';
|
|
3300
3512
|
for (const b of blocks) {
|
|
3301
3513
|
if (b.kind === 'text') {
|
|
3302
|
-
out +=
|
|
3514
|
+
out += options.collapseText
|
|
3515
|
+
? renderCollapsibleText(b.text, `blk blk-text ${(options.collapseKind || 'model-output')}-content`, options.collapseKind)
|
|
3516
|
+
: `<div class="blk blk-text">${esc(b.text)}</div>`;
|
|
3303
3517
|
} else if (b.kind === 'thinking') {
|
|
3304
3518
|
out += `<details class="blk blk-thinking" open><summary>💭 ${t('sessions.block.thinking')}</summary><div class="blk-thinking-body">${esc(b.text)}</div></details>`;
|
|
3305
3519
|
} else if (b.kind === 'tool_use') {
|
|
@@ -3312,15 +3526,73 @@ function renderBlocks(blocks) {
|
|
|
3312
3526
|
for (const p of entries) {
|
|
3313
3527
|
params += `<div class="tool-param"><span class="pk">${esc(p.k)}</span><code class="pv">${esc(p.v)}</code></div>`;
|
|
3314
3528
|
}
|
|
3315
|
-
|
|
3529
|
+
const toolParams = params
|
|
3530
|
+
? `<div class="tool-params collapse-content"><div class="tool-params-body collapse-body">${params}</div>` +
|
|
3531
|
+
'<button class="collapse-arrow-toggle" type="button" hidden aria-expanded="false"><span class="collapse-arrow-icon" aria-hidden="true"></span></button></div>'
|
|
3532
|
+
: '';
|
|
3533
|
+
out += `<details class="blk blk-tool" open><summary class="tool-head">${icon} <span class="tool-name">${esc(tool)}</span></summary>${toolParams}</details>`;
|
|
3316
3534
|
} else if (b.kind === 'tool_result') {
|
|
3317
3535
|
const cls = b.isError ? 'blk-result err' : 'blk-result';
|
|
3318
|
-
|
|
3536
|
+
const loadFull = b.truncated && b.resultKey
|
|
3537
|
+
? `<button class="ctrl-btn result-load-full" type="button" data-result-key="${esc(b.resultKey)}">${t('sessions.loadFullResult') || '加载完整结果'}</button>`
|
|
3538
|
+
: '';
|
|
3539
|
+
out += `<details class="blk ${cls}" open><summary>${b.isError ? '✗ ' + t('sessions.block.resultError') : '↳ ' + t('sessions.block.result')}</summary>` +
|
|
3540
|
+
`<div class="result-content collapse-content"><pre class="result-body collapse-body">${esc(b.text)}</pre>${loadFull}<button class="collapse-arrow-toggle" type="button" hidden aria-expanded="false"><span class="collapse-arrow-icon" aria-hidden="true"></span></button></div></details>`;
|
|
3541
|
+
} else if (b.kind === 'subtask') {
|
|
3542
|
+
const knownStatuses = ['running', 'completed', 'failed', 'stopped', 'killed'];
|
|
3543
|
+
const status = knownStatuses.includes(String(b.status)) ? String(b.status) : 'running';
|
|
3544
|
+
const statusLabel = t(`sessions.subtask.status.${status}`);
|
|
3545
|
+
const meta = [];
|
|
3546
|
+
if (b.subagentType) meta.push(esc(b.subagentType));
|
|
3547
|
+
if (b.toolUses) meta.push(esc(tf('sessions.subtask.tools', { count: fmtNum(b.toolUses) })));
|
|
3548
|
+
if (b.totalTokens) meta.push(esc(tf('sessions.subtask.tokens', { count: fmtNum(b.totalTokens) })));
|
|
3549
|
+
if (b.durationMs) meta.push(esc(fmtDur(b.durationMs / 1000)));
|
|
3550
|
+
if (b.terminalReason) meta.push(esc(tf('sessions.subtask.reason', { reason: b.terminalReason })));
|
|
3551
|
+
const progress = b.progress && b.progress !== b.description
|
|
3552
|
+
? `<div class="subtask-progress">${esc(b.progress)}</div>`
|
|
3553
|
+
: '';
|
|
3554
|
+
out += `<div class="blk blk-subtask status-${status}">` +
|
|
3555
|
+
'<div class="subtask-main">' +
|
|
3556
|
+
`<span class="subtask-status-dot" aria-hidden="true"></span>` +
|
|
3557
|
+
`<span class="subtask-status">${esc(statusLabel)}</span>` +
|
|
3558
|
+
`<span class="subtask-title">${esc(b.description || b.taskId || '')}</span>` +
|
|
3559
|
+
'</div>' +
|
|
3560
|
+
(meta.length ? `<div class="subtask-meta">${meta.join('<span>·</span>')}</div>` : '') +
|
|
3561
|
+
progress +
|
|
3562
|
+
'</div>';
|
|
3319
3563
|
}
|
|
3320
3564
|
}
|
|
3321
3565
|
return out;
|
|
3322
3566
|
}
|
|
3323
3567
|
|
|
3568
|
+
function setupTruncatedSessionResults(container, data) {
|
|
3569
|
+
container.querySelectorAll('.result-load-full').forEach(button => {
|
|
3570
|
+
button.onclick = async () => {
|
|
3571
|
+
const key = button.dataset.resultKey;
|
|
3572
|
+
if (!key || button.disabled) return;
|
|
3573
|
+
button.disabled = true;
|
|
3574
|
+
button.textContent = t('common.loading');
|
|
3575
|
+
try {
|
|
3576
|
+
const result = await sessionResultSend({
|
|
3577
|
+
baseagent: data.baseagent || sessSel.baseagent,
|
|
3578
|
+
project: data.project || sessSel.project,
|
|
3579
|
+
sessionId: data.sessionId || sessSel.sessionId,
|
|
3580
|
+
resultKey: key,
|
|
3581
|
+
});
|
|
3582
|
+
const body = button.parentElement?.querySelector('.result-body');
|
|
3583
|
+
if (!body || !result?.text) throw new Error('not found');
|
|
3584
|
+
body.textContent = result.text;
|
|
3585
|
+
body.classList.remove('is-collapsed');
|
|
3586
|
+
button.parentElement?.classList.remove('is-collapsed');
|
|
3587
|
+
button.remove();
|
|
3588
|
+
} catch {
|
|
3589
|
+
button.disabled = false;
|
|
3590
|
+
button.textContent = t('sessions.loadFullResult') || '加载完整结果';
|
|
3591
|
+
}
|
|
3592
|
+
};
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3324
3596
|
// ── 通用 Menu 协议辅助(mResp / toast,供 Agents / System / Triggers 复用)──
|
|
3325
3597
|
|
|
3326
3598
|
// 提取 menu.response 的 data/error
|
|
@@ -5474,12 +5746,18 @@ function triggerOriginalEditState(t) {
|
|
|
5474
5746
|
const id = triggerIdOf(t);
|
|
5475
5747
|
const agentAid = triggerAgentOf(t);
|
|
5476
5748
|
const rawFeedbackStrategy = String(feedback.strategy ?? t?.feedbackStrategy ?? 'target');
|
|
5749
|
+
const executionType = String(execution.type ?? t?.executionType ?? 'target_session');
|
|
5750
|
+
const agentBaseagent = String(triggerAgentInfo(agentAid)?.baseagent ?? triggerActiveBaseagentForAgent(agentAid));
|
|
5751
|
+
const persistedBaseagent = typeof execution.baseagent === 'string' && execution.baseagent.trim()
|
|
5752
|
+
? execution.baseagent.trim()
|
|
5753
|
+
: (typeof t?.baseagent === 'string' && t.baseagent.trim() ? t.baseagent.trim() : '');
|
|
5754
|
+
const baseagent = persistedBaseagent || agentBaseagent;
|
|
5477
5755
|
return {
|
|
5478
5756
|
id,
|
|
5479
5757
|
agentAid,
|
|
5480
5758
|
name: String(definition?.name ?? t?.name ?? t?.label ?? ''),
|
|
5481
5759
|
enabled: triggerIsEnabled(t),
|
|
5482
|
-
executionType
|
|
5760
|
+
executionType,
|
|
5483
5761
|
scriptPath: String(execution.script?.path ?? ''),
|
|
5484
5762
|
scriptRuntime: String(execution.script?.runtime ?? 'node'),
|
|
5485
5763
|
scriptTimeoutMs: Number(execution.script?.timeoutMs ?? 30_000),
|
|
@@ -5487,7 +5765,11 @@ function triggerOriginalEditState(t) {
|
|
|
5487
5765
|
scheduleValue: source.scheduleValue,
|
|
5488
5766
|
eventFilter: definition?.source?.type === 'event' ? (definition.source.filter ?? null) : null,
|
|
5489
5767
|
timezone: String(source.timezone ?? ''),
|
|
5490
|
-
baseagent
|
|
5768
|
+
baseagent,
|
|
5769
|
+
agentBaseagent,
|
|
5770
|
+
baseagentMismatch: executionType === 'trigger_session'
|
|
5771
|
+
&& Boolean(persistedBaseagent)
|
|
5772
|
+
&& persistedBaseagent !== agentBaseagent,
|
|
5491
5773
|
prompt: execution.type === 'script' ? '' : triggerPrompt(definition, t),
|
|
5492
5774
|
model: String(execution.model ?? t?.model ?? ''),
|
|
5493
5775
|
effort: String(execution.effort ?? t?.effort ?? ''),
|
|
@@ -6710,6 +6992,7 @@ function openTriggerEditDrawer(id, agent) {
|
|
|
6710
6992
|
`<label class="ag-edit-field trig-script-field"><span>${t('triggers.field.scriptRuntime')}</span><select id="trig-edit-script-runtime">${triggerOptionHtml(['node', 'python', 'bash'], original.scriptRuntime, null)}</select></label>` +
|
|
6711
6993
|
`<label class="ag-edit-field trig-script-field trig-script-path-field"><span>${t('triggers.field.scriptPath')}</span><input id="trig-edit-script-path" type="text" autocomplete="off" spellcheck="false" value="${esc(original.scriptPath)}" placeholder="${t('triggers.placeholder.scriptPath')}"></label>` +
|
|
6712
6994
|
`</div>` +
|
|
6995
|
+
(original.baseagentMismatch ? `<div class="trig-baseagent-note">${esc(tf('triggers.baseagent.mismatch', { trigger: original.baseagent, agent: original.agentBaseagent }))}</div>` : '') +
|
|
6713
6996
|
`<div class="trig-script-feedback">` +
|
|
6714
6997
|
`<div class="trig-feedback-templates-title">${t('triggers.field.outputTemplate')}</div>` +
|
|
6715
6998
|
`<div class="trig-feedback-templates">` +
|
|
@@ -6990,6 +7273,13 @@ function updateThemeButton() {
|
|
|
6990
7273
|
}
|
|
6991
7274
|
|
|
6992
7275
|
function disposeWindowChart(key) {
|
|
7276
|
+
const observerKey = '__ecwebChartResizeObserver_' + key;
|
|
7277
|
+
const observer = window[observerKey];
|
|
7278
|
+
if (observer) {
|
|
7279
|
+
observer.disconnect();
|
|
7280
|
+
window[observerKey] = null;
|
|
7281
|
+
}
|
|
7282
|
+
|
|
6993
7283
|
const chart = window[key];
|
|
6994
7284
|
if (chart && typeof chart.dispose === 'function') {
|
|
6995
7285
|
chart.dispose();
|
|
@@ -7048,6 +7338,28 @@ function initTheme() {
|
|
|
7048
7338
|
}
|
|
7049
7339
|
}
|
|
7050
7340
|
|
|
7341
|
+
function chartThemeValue(name, fallback) {
|
|
7342
|
+
var value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
7343
|
+
return value || fallback;
|
|
7344
|
+
}
|
|
7345
|
+
|
|
7346
|
+
function chartTooltipOptions(isDark) {
|
|
7347
|
+
return {
|
|
7348
|
+
backgroundColor: chartThemeValue('--bg2', isDark ? '#161b22' : '#ffffff'),
|
|
7349
|
+
borderColor: chartThemeValue('--border', isDark ? '#30363d' : '#e2e6ed'),
|
|
7350
|
+
borderWidth: 1,
|
|
7351
|
+
padding: [10, 12],
|
|
7352
|
+
textStyle: {
|
|
7353
|
+
color: chartThemeValue('--fg', isDark ? '#e6edf3' : '#1a202c'),
|
|
7354
|
+
fontSize: 12,
|
|
7355
|
+
lineHeight: 20,
|
|
7356
|
+
},
|
|
7357
|
+
extraCssText: 'border-radius:8px;box-shadow:' + (isDark
|
|
7358
|
+
? '0 14px 34px rgba(0,0,0,0.42)'
|
|
7359
|
+
: '0 14px 34px rgba(31,41,55,0.16)') + ';',
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
|
|
7051
7363
|
// ── Usage 相关函数 ──
|
|
7052
7364
|
function fmtTokens(n) {
|
|
7053
7365
|
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
@@ -7255,12 +7567,19 @@ function formatDatetimeLocal(date) {
|
|
|
7255
7567
|
}
|
|
7256
7568
|
|
|
7257
7569
|
// 模型访问明细查询
|
|
7570
|
+
var _detailInited = false;
|
|
7571
|
+
|
|
7258
7572
|
function initDetailQuery() {
|
|
7259
7573
|
// 填充Agent选择器
|
|
7260
7574
|
loadDetailAgentList();
|
|
7261
7575
|
// 填充Model选择器(按上面总览的时间范围)
|
|
7262
7576
|
loadDetailModelList();
|
|
7263
7577
|
|
|
7578
|
+
// 事件只绑一次:initOverviewFilters 会在每次切回 Overview 子 Tab 时重复调用,
|
|
7579
|
+
// 重复绑定会让一次 change 触发 N 次并发查询,慢的旧响应可能覆盖新响应(表现为“切换无效”)。
|
|
7580
|
+
if (_detailInited) return;
|
|
7581
|
+
_detailInited = true;
|
|
7582
|
+
|
|
7264
7583
|
// 绑定分页大小变化
|
|
7265
7584
|
const pageSizeEl = $('#detail-page-size');
|
|
7266
7585
|
if (pageSizeEl) {
|
|
@@ -7347,10 +7666,8 @@ async function loadDetailAgentList() {
|
|
|
7347
7666
|
option.textContent = a.agent_name || a.agent_aid.split('.')[0];
|
|
7348
7667
|
selectEl.appendChild(option);
|
|
7349
7668
|
});
|
|
7350
|
-
//
|
|
7351
|
-
|
|
7352
|
-
selectEl.value = agents[0].agent_aid;
|
|
7353
|
-
}
|
|
7669
|
+
// 默认查询全部智能体(首项 value=""),不要自动切到第一个 Agent。
|
|
7670
|
+
selectEl.value = '';
|
|
7354
7671
|
// 加载完成后自动查询一次
|
|
7355
7672
|
queryDetailUsage();
|
|
7356
7673
|
}
|
|
@@ -7394,7 +7711,11 @@ async function loadDetailModelList() {
|
|
|
7394
7711
|
} catch {}
|
|
7395
7712
|
}
|
|
7396
7713
|
|
|
7714
|
+
var _detailQuerySeq = 0;
|
|
7715
|
+
|
|
7397
7716
|
async function queryDetailUsage() {
|
|
7717
|
+
// 请求序号:并发查询时只认最后一次的响应,避免慢的旧响应覆盖新结果
|
|
7718
|
+
const seq = ++_detailQuerySeq;
|
|
7398
7719
|
// 使用总览的时间范围
|
|
7399
7720
|
const timeRange = window._currentOverviewTimeRange || {};
|
|
7400
7721
|
const fromTs = timeRange.fromTs;
|
|
@@ -7421,13 +7742,16 @@ async function queryDetailUsage() {
|
|
|
7421
7742
|
const resp = await fetch(authApiUrl('api/stats/detail?' + params.toString()), {
|
|
7422
7743
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
7423
7744
|
});
|
|
7745
|
+
if (seq !== _detailQuerySeq) return; // 已有更新的查询发出,丢弃本次结果
|
|
7424
7746
|
if (!resp.ok) {
|
|
7425
7747
|
showDetailError(t('usage.detail.error'));
|
|
7426
7748
|
return;
|
|
7427
7749
|
}
|
|
7428
7750
|
const result = await resp.json();
|
|
7751
|
+
if (seq !== _detailQuerySeq) return;
|
|
7429
7752
|
renderDetailTable(result.data, result.total, page, pageSize);
|
|
7430
7753
|
} catch {
|
|
7754
|
+
if (seq !== _detailQuerySeq) return;
|
|
7431
7755
|
showDetailError(t('usage.detail.error'));
|
|
7432
7756
|
}
|
|
7433
7757
|
}
|
|
@@ -7627,9 +7951,11 @@ function initExplorer() {
|
|
|
7627
7951
|
// 初始化时间范围选择
|
|
7628
7952
|
initExplorerTimeFilters();
|
|
7629
7953
|
|
|
7630
|
-
//
|
|
7631
|
-
var
|
|
7632
|
-
if (
|
|
7954
|
+
// 模型、粒度下拉框变更后立即查询,不再需要额外的查询按钮。
|
|
7955
|
+
var modelFilter = $('#exp-model');
|
|
7956
|
+
if (modelFilter) modelFilter.addEventListener('change', runExplorerQuery);
|
|
7957
|
+
var granularityFilter = $('#exp-granularity');
|
|
7958
|
+
if (granularityFilter) granularityFilter.addEventListener('change', runExplorerQuery);
|
|
7633
7959
|
|
|
7634
7960
|
// Load sidebar lists
|
|
7635
7961
|
loadExplorerSidebar();
|
|
@@ -8076,7 +8402,10 @@ async function runExplorerQuery() {
|
|
|
8076
8402
|
_explorerChart = echarts.init(chartEl, isDark ? 'dark' : null);
|
|
8077
8403
|
var periods = data.map(function(r) { return r.period; });
|
|
8078
8404
|
_explorerChart.setOption({
|
|
8079
|
-
tooltip: {
|
|
8405
|
+
tooltip: {
|
|
8406
|
+
trigger: 'axis',
|
|
8407
|
+
...chartTooltipOptions(isDark),
|
|
8408
|
+
},
|
|
8080
8409
|
legend: { data: [t('usage.card.input'), t('usage.card.output')], top: 0, textStyle: { fontSize: 11 } },
|
|
8081
8410
|
grid: { top: 30, bottom: 30, left: 60, right: 16 },
|
|
8082
8411
|
xAxis: { type: 'category', data: periods, axisLabel: { fontSize: 10, rotate: 30 } },
|
|
@@ -8122,6 +8451,52 @@ function disposeMonitorCharts() {
|
|
|
8122
8451
|
['_monCpu', '_monMem', '_monDisk', '_monNet'].forEach(disposeWindowChart);
|
|
8123
8452
|
}
|
|
8124
8453
|
|
|
8454
|
+
function scheduleAfterLayout(callback) {
|
|
8455
|
+
if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(callback);
|
|
8456
|
+
return setTimeout(callback, 16);
|
|
8457
|
+
}
|
|
8458
|
+
|
|
8459
|
+
function resizeChartAfterLayout(chart) {
|
|
8460
|
+
scheduleAfterLayout(function () {
|
|
8461
|
+
if (!chart || (typeof chart.isDisposed === 'function' && chart.isDisposed())) return;
|
|
8462
|
+
chart.resize();
|
|
8463
|
+
});
|
|
8464
|
+
}
|
|
8465
|
+
|
|
8466
|
+
function observeMonitorChartSize(el, varKey, chart) {
|
|
8467
|
+
const observerKey = '__ecwebChartResizeObserver_' + varKey;
|
|
8468
|
+
const previous = window[observerKey];
|
|
8469
|
+
if (previous) previous.disconnect();
|
|
8470
|
+
|
|
8471
|
+
if (typeof ResizeObserver === 'function') {
|
|
8472
|
+
const observer = new ResizeObserver(function () {
|
|
8473
|
+
if (window[varKey] !== chart || (typeof chart.isDisposed === 'function' && chart.isDisposed())) {
|
|
8474
|
+
observer.disconnect();
|
|
8475
|
+
return;
|
|
8476
|
+
}
|
|
8477
|
+
chart.resize();
|
|
8478
|
+
});
|
|
8479
|
+
observer.observe(el);
|
|
8480
|
+
window[observerKey] = observer;
|
|
8481
|
+
}
|
|
8482
|
+
|
|
8483
|
+
resizeChartAfterLayout(chart);
|
|
8484
|
+
}
|
|
8485
|
+
|
|
8486
|
+
function deferMonitorChartUntilVisible(el, render) {
|
|
8487
|
+
let attempts = 0;
|
|
8488
|
+
const retry = function () {
|
|
8489
|
+
if (!el.isConnected) return;
|
|
8490
|
+
if (el.clientWidth > 0 && el.clientHeight > 0) {
|
|
8491
|
+
render();
|
|
8492
|
+
return;
|
|
8493
|
+
}
|
|
8494
|
+
// The AUN proxy can deliver the first snapshot before its page layout settles.
|
|
8495
|
+
if (++attempts < 30) scheduleAfterLayout(retry);
|
|
8496
|
+
};
|
|
8497
|
+
scheduleAfterLayout(retry);
|
|
8498
|
+
}
|
|
8499
|
+
|
|
8125
8500
|
function bindMonRangeTabs(wrap) {
|
|
8126
8501
|
var tabs = wrap.querySelectorAll('.mon-range');
|
|
8127
8502
|
if (!tabs.length) return;
|
|
@@ -8289,8 +8664,18 @@ function renderMonitor(data) {
|
|
|
8289
8664
|
function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
|
|
8290
8665
|
var el = $('#' + elId);
|
|
8291
8666
|
if (!el) return;
|
|
8292
|
-
|
|
8293
|
-
|
|
8667
|
+
const render = function () {
|
|
8668
|
+
var chart = window[varKey];
|
|
8669
|
+
if (chart && typeof chart.getDom === 'function' && chart.getDom() !== el) {
|
|
8670
|
+
disposeWindowChart(varKey);
|
|
8671
|
+
chart = null;
|
|
8672
|
+
}
|
|
8673
|
+
if (!chart) {
|
|
8674
|
+
chart = echarts.init(el, isDark ? 'dark' : null);
|
|
8675
|
+
window[varKey] = chart;
|
|
8676
|
+
observeMonitorChartSize(el, varKey, chart);
|
|
8677
|
+
}
|
|
8678
|
+
chart.setOption({
|
|
8294
8679
|
title: { text: title, left: 'center', top: 4, textStyle: { fontSize: 12, color: isDark ? '#e6edf3' : '#1a202c' } },
|
|
8295
8680
|
legend: {
|
|
8296
8681
|
show: series.length > 0,
|
|
@@ -8303,6 +8688,7 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
|
|
|
8303
8688
|
},
|
|
8304
8689
|
tooltip: {
|
|
8305
8690
|
trigger: 'axis',
|
|
8691
|
+
...chartTooltipOptions(isDark),
|
|
8306
8692
|
formatter: function (params) {
|
|
8307
8693
|
var lines = [params[0].axisValue];
|
|
8308
8694
|
params.forEach(function (pt) {
|
|
@@ -8330,7 +8716,15 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
|
|
|
8330
8716
|
};
|
|
8331
8717
|
}),
|
|
8332
8718
|
animation: false,
|
|
8333
|
-
|
|
8719
|
+
});
|
|
8720
|
+
resizeChartAfterLayout(chart);
|
|
8721
|
+
};
|
|
8722
|
+
|
|
8723
|
+
if (el.clientWidth <= 0 || el.clientHeight <= 0) {
|
|
8724
|
+
deferMonitorChartUntilVisible(el, render);
|
|
8725
|
+
return;
|
|
8726
|
+
}
|
|
8727
|
+
render();
|
|
8334
8728
|
}
|
|
8335
8729
|
|
|
8336
8730
|
function renderRoles(data) {
|