ec-web 0.0.4 → 0.0.5
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 +47 -5
- package/dist/sources/msg.js +187 -5
- package/dist/sources/session-codex.js +43 -29
- package/dist/sources/session-origin.js +195 -0
- package/dist/sources/session.js +211 -33
- package/dist/static/app.js +285 -43
- package/dist/static/index.html +4 -5
- package/dist/static/style.css +55 -1
- package/dist/static/vendor/echarts-5.6.0.min.js +45 -0
- package/package.json +2 -2
package/dist/static/app.js
CHANGED
|
@@ -296,10 +296,19 @@ const translations = {
|
|
|
296
296
|
'sessions.turnType.toolUse': '工具使用',
|
|
297
297
|
'sessions.turnType.toolResult': '工具结果',
|
|
298
298
|
'sessions.turnType.msgSend': '发送消息',
|
|
299
|
-
'sessions.count': '{
|
|
299
|
+
'sessions.count': '{count} 个会话',
|
|
300
300
|
'sessions.filter.valid': '有效',
|
|
301
301
|
'sessions.filter.validTitle': '只显示有效会话(≥1 条用户消息)',
|
|
302
|
+
'sessions.filter.all': '全部',
|
|
303
|
+
'sessions.filter.allTitle': '显示全部会话(包括无用户消息的会话)',
|
|
304
|
+
'sessions.filter.sourceTitle': '按会话类型筛选',
|
|
305
|
+
'sessions.filter.sourceAll': '全部类型',
|
|
302
306
|
'sessions.messageCountTitle': '用户输入 {user} 条 / 共 {total} 条消息',
|
|
307
|
+
'sessions.source.trigger': 'Trigger',
|
|
308
|
+
'sessions.source.agent': 'Agent',
|
|
309
|
+
'sessions.source.cli': 'CLI',
|
|
310
|
+
'sessions.source.user': '用户',
|
|
311
|
+
'sessions.source.unknown': 'Unknown',
|
|
303
312
|
'sessions.selectLog': '选择会话查看 CC 日志',
|
|
304
313
|
'sessions.chatView': '💬 对话视图',
|
|
305
314
|
'sessions.fullView': '📜 完整视图',
|
|
@@ -309,7 +318,16 @@ const translations = {
|
|
|
309
318
|
'sessions.empty.noDialogue': '该会话没有用户对话消息',
|
|
310
319
|
'sessions.turnType.userInput': '用户输入',
|
|
311
320
|
'sessions.turnType.toolCall': '工具调用',
|
|
321
|
+
'sessions.turnType.subtask': '子任务',
|
|
312
322
|
'sessions.turnType.system': '系统',
|
|
323
|
+
'sessions.subtask.status.running': '运行中',
|
|
324
|
+
'sessions.subtask.status.completed': '已完成',
|
|
325
|
+
'sessions.subtask.status.failed': '失败',
|
|
326
|
+
'sessions.subtask.status.stopped': '已停止',
|
|
327
|
+
'sessions.subtask.status.killed': '已终止',
|
|
328
|
+
'sessions.subtask.tools': '{count} 次工具',
|
|
329
|
+
'sessions.subtask.tokens': '{count} tokens',
|
|
330
|
+
'sessions.subtask.reason': '原因:{reason}',
|
|
313
331
|
'sessions.block.thinking': '思考',
|
|
314
332
|
'sessions.block.result': '结果',
|
|
315
333
|
'sessions.block.resultError': '结果',
|
|
@@ -639,6 +657,7 @@ const translations = {
|
|
|
639
657
|
'triggers.feedback.defaultFailure': '默认:触发器失败:{{error.message}}',
|
|
640
658
|
'triggers.field.model': '模型',
|
|
641
659
|
'triggers.field.effort': '推理强度',
|
|
660
|
+
'triggers.baseagent.mismatch': 'Trigger 固定:{trigger},Agent 当前:{agent}',
|
|
642
661
|
'triggers.field.permission': '权限模式',
|
|
643
662
|
'triggers.delivery.on': '启用',
|
|
644
663
|
'triggers.delivery.off': '禁用',
|
|
@@ -1152,10 +1171,19 @@ const translations = {
|
|
|
1152
1171
|
'sessions.turnType.toolUse': 'Tool Use',
|
|
1153
1172
|
'sessions.turnType.toolResult': 'Tool Result',
|
|
1154
1173
|
'sessions.turnType.msgSend': 'Send Message',
|
|
1155
|
-
'sessions.count': '{
|
|
1174
|
+
'sessions.count': '{count} sessions',
|
|
1156
1175
|
'sessions.filter.valid': 'Valid',
|
|
1157
1176
|
'sessions.filter.validTitle': 'Only show valid sessions (at least 1 user message)',
|
|
1177
|
+
'sessions.filter.all': 'All',
|
|
1178
|
+
'sessions.filter.allTitle': 'Show all sessions, including sessions without user messages',
|
|
1179
|
+
'sessions.filter.sourceTitle': 'Filter by session type',
|
|
1180
|
+
'sessions.filter.sourceAll': 'All types',
|
|
1158
1181
|
'sessions.messageCountTitle': '{user} user messages / {total} total messages',
|
|
1182
|
+
'sessions.source.trigger': 'Trigger',
|
|
1183
|
+
'sessions.source.agent': 'Agent',
|
|
1184
|
+
'sessions.source.cli': 'CLI',
|
|
1185
|
+
'sessions.source.user': 'User',
|
|
1186
|
+
'sessions.source.unknown': 'Unknown',
|
|
1159
1187
|
'sessions.selectLog': 'Select a session to view the CC log',
|
|
1160
1188
|
'sessions.chatView': '💬 Chat View',
|
|
1161
1189
|
'sessions.fullView': '📜 Full View',
|
|
@@ -1165,7 +1193,16 @@ const translations = {
|
|
|
1165
1193
|
'sessions.empty.noDialogue': 'No user dialogue messages in this session',
|
|
1166
1194
|
'sessions.turnType.userInput': 'User Input',
|
|
1167
1195
|
'sessions.turnType.toolCall': 'Tool Call',
|
|
1196
|
+
'sessions.turnType.subtask': 'Subtask',
|
|
1168
1197
|
'sessions.turnType.system': 'System',
|
|
1198
|
+
'sessions.subtask.status.running': 'Running',
|
|
1199
|
+
'sessions.subtask.status.completed': 'Completed',
|
|
1200
|
+
'sessions.subtask.status.failed': 'Failed',
|
|
1201
|
+
'sessions.subtask.status.stopped': 'Stopped',
|
|
1202
|
+
'sessions.subtask.status.killed': 'Killed',
|
|
1203
|
+
'sessions.subtask.tools': '{count} tool uses',
|
|
1204
|
+
'sessions.subtask.tokens': '{count} tokens',
|
|
1205
|
+
'sessions.subtask.reason': 'Reason: {reason}',
|
|
1169
1206
|
'sessions.block.thinking': 'Thinking',
|
|
1170
1207
|
'sessions.block.result': 'Result',
|
|
1171
1208
|
'sessions.block.resultError': 'Result',
|
|
@@ -1495,6 +1532,7 @@ const translations = {
|
|
|
1495
1532
|
'triggers.feedback.defaultFailure': 'Default: 触发器失败:{{error.message}}',
|
|
1496
1533
|
'triggers.field.model': 'Model',
|
|
1497
1534
|
'triggers.field.effort': 'Effort',
|
|
1535
|
+
'triggers.baseagent.mismatch': 'Trigger fixed: {trigger}; current Agent: {agent}',
|
|
1498
1536
|
'triggers.field.permission': 'Permission Mode',
|
|
1499
1537
|
'triggers.delivery.on': 'Enabled',
|
|
1500
1538
|
'triggers.delivery.off': 'Disabled',
|
|
@@ -2030,7 +2068,8 @@ let _triggerEdit = null;
|
|
|
2030
2068
|
let _triggerHistory = null;
|
|
2031
2069
|
let _triggerTest = null;
|
|
2032
2070
|
let sessSearch = '';
|
|
2033
|
-
let
|
|
2071
|
+
let sessShowAll = false; // false=只显示有效会话;true=显示全部
|
|
2072
|
+
let sessSourceFilter = 'all'; // all / trigger / agent / cli / user / unknown
|
|
2034
2073
|
let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
|
|
2035
2074
|
let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
|
|
2036
2075
|
let logSel = { source: null, level: 'all', module: 'all', search: '', limit: 500, follow: true };
|
|
@@ -2414,8 +2453,13 @@ const MSG_TYPE_META = {
|
|
|
2414
2453
|
file: { icon: '📎', label: () => t('messages.msgType.file') },
|
|
2415
2454
|
command: { icon: '⌘', label: () => t('messages.msgType.command') }
|
|
2416
2455
|
};
|
|
2417
|
-
|
|
2418
|
-
|
|
2456
|
+
// 渠道标:AUN 是默认渠道,不标;其余渠道标出来,免得混排后分不清来路
|
|
2457
|
+
function channelTagHtml(channel, cls) {
|
|
2458
|
+
if (!channel || channel === 'aun') return '';
|
|
2459
|
+
return `<span class="${cls}">${esc(channel)}</span>`;
|
|
2460
|
+
}
|
|
2461
|
+
function msgTagsHtml(kind, encrypt, chatmode, dir, channel) {
|
|
2462
|
+
let h = channelTagHtml(channel, 'mtag mtag-channel');
|
|
2419
2463
|
// 'send' 仅出向才是「回复」;入向是用户输入,不打回复标记
|
|
2420
2464
|
const km = (kind === 'send' && dir === 'in') ? null : MSG_KIND_META[kind];
|
|
2421
2465
|
if (km) h += `<span class="mtag${kind === 'send' ? ' mtag-reply' : ''}">${km.icon}${km.label()}</span>`;
|
|
@@ -2425,24 +2469,39 @@ function msgTagsHtml(kind, encrypt, chatmode, dir) {
|
|
|
2425
2469
|
}
|
|
2426
2470
|
|
|
2427
2471
|
// 消息行:方向箭头 + 标记 + 对端 + 文字
|
|
2472
|
+
function latestAgentPreviewMessage(s) {
|
|
2473
|
+
const recent = Array.isArray(s.recentMessages) ? s.recentMessages[0] : null;
|
|
2474
|
+
if (recent?.text) return recent;
|
|
2475
|
+
const recvTs = s.lastReceivedAt || 0;
|
|
2476
|
+
const sentTs = s.lastSentAt || 0;
|
|
2477
|
+
if (sentTs > recvTs && s.lastSentText) {
|
|
2478
|
+
return {
|
|
2479
|
+
dir: 'out', peer: s.lastSentTo, text: s.lastSentText, kind: s.lastSentKind,
|
|
2480
|
+
encrypt: s.lastSentEncrypt, chatmode: s.lastSentChatmode, channel: s.lastSentChannel,
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
if (s.lastReceivedText) {
|
|
2484
|
+
return {
|
|
2485
|
+
dir: 'in', peer: s.lastReceivedFrom, text: s.lastReceivedText, kind: s.lastReceivedKind,
|
|
2486
|
+
encrypt: s.lastReceivedEncrypt, chatmode: s.lastReceivedChatmode, channel: s.lastReceivedChannel,
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
return null;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2428
2492
|
function agentPreviewHtml(s) {
|
|
2429
2493
|
const clip = (t) => esc(String(t).replace(/\n/g, ' ').slice(0, 80));
|
|
2430
|
-
const line = (dir, peer, text, kind, encrypt, chatmode) => {
|
|
2494
|
+
const line = (dir, peer, text, kind, encrypt, chatmode, channel) => {
|
|
2431
2495
|
const arrow = dir === 'in' ? '<span class="arrow-in">↓</span>' : '<span class="arrow-out">↑</span>';
|
|
2432
|
-
const tags = msgTagsHtml(kind, encrypt, chatmode, dir);
|
|
2496
|
+
const tags = msgTagsHtml(kind, encrypt, chatmode, dir, channel);
|
|
2433
2497
|
const peerHtml = peer ? `<span class="peer">${esc(shortAid(peer))}</span>: ` : '';
|
|
2434
2498
|
const textCls = dir === 'in' ? 'text-in' : 'text-out';
|
|
2435
2499
|
return `${arrow}${tags ? ' ' + tags + ' ' : ' '}${peerHtml}<span class="${textCls}">${clip(text)}</span>`;
|
|
2436
2500
|
};
|
|
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 '';
|
|
2501
|
+
const latest = latestAgentPreviewMessage(s);
|
|
2502
|
+
return latest
|
|
2503
|
+
? line(latest.dir, latest.peer, latest.text, latest.kind, latest.encrypt, latest.chatmode, latest.channel)
|
|
2504
|
+
: '';
|
|
2446
2505
|
}
|
|
2447
2506
|
|
|
2448
2507
|
// HTML tooltip(最近 N 轮):时间 + 彩色箭头 + 方式 + 对端 + 文字
|
|
@@ -2458,10 +2517,11 @@ function recentMsgTooltipHtml(recent) {
|
|
|
2458
2517
|
const kh = km ? `<span class="tip-kind${m.kind === 'send' ? ' tip-kind-reply' : ''}">${km.icon}${km.label()}</span>` : '';
|
|
2459
2518
|
const enc = m.encrypt != null ? `<span class="tip-flag">${m.encrypt ? t('messages.tag.encrypted') : t('messages.tag.plain')}</span>` : '';
|
|
2460
2519
|
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>` : '';
|
|
2520
|
+
const chan = channelTagHtml(m.channel, 'tip-flag tip-flag-channel');
|
|
2461
2521
|
const peer = m.peer ? esc(shortAid(m.peer)) : '';
|
|
2462
2522
|
const text = esc(String(m.text).replace(/\n/g, ' ').slice(0, 140));
|
|
2463
2523
|
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>`;
|
|
2524
|
+
h += `<div class="tip-row ${rcls}">${time}${arrow}${chan}${kh}${enc}${mode} <b>${peer}</b> ${text}</div>`;
|
|
2465
2525
|
}
|
|
2466
2526
|
return h + '</div>';
|
|
2467
2527
|
}
|
|
@@ -2857,6 +2917,14 @@ function card(label, value, valCls, sub) {
|
|
|
2857
2917
|
}
|
|
2858
2918
|
|
|
2859
2919
|
// ── Messages 视图 ──
|
|
2920
|
+
function msgChannelTone(channelType) {
|
|
2921
|
+
const normalized = String(channelType || '').trim().toLowerCase();
|
|
2922
|
+
if (normalized === 'qq') return 'qqbot';
|
|
2923
|
+
return ['aun', 'wechat', 'wecom', 'feishu', 'dingtalk', 'qqbot', 'daemon'].includes(normalized)
|
|
2924
|
+
? normalized
|
|
2925
|
+
: 'other';
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2860
2928
|
function renderMsg(data) {
|
|
2861
2929
|
if (!data) return;
|
|
2862
2930
|
const aids = data.scopes || data.aids || [];
|
|
@@ -2889,7 +2957,7 @@ function renderMsg(data) {
|
|
|
2889
2957
|
let aidsHtml = `<div class="col-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
|
|
2890
2958
|
for (const a of aids) {
|
|
2891
2959
|
const sel = a.aid === msgSel.aid ? ' sel' : '';
|
|
2892
|
-
const name = a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown';
|
|
2960
|
+
const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
|
|
2893
2961
|
const privateCount = Math.max(0, (a.peerCount || 0) - (a.groupCount || 0));
|
|
2894
2962
|
const privateBit = tf('messages.privateCount', { count: privateCount });
|
|
2895
2963
|
const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
|
|
@@ -2933,7 +3001,9 @@ function renderMsg(data) {
|
|
|
2933
3001
|
? t('messages.tag.topic')
|
|
2934
3002
|
: p.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
|
|
2935
3003
|
const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(p.partition || 'main')}">${esc(partitionLabel)}</span>`;
|
|
2936
|
-
const channelTag = channelLabel
|
|
3004
|
+
const channelTag = channelLabel
|
|
3005
|
+
? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(p.channelType)}">${esc(channelLabel)}</span>`
|
|
3006
|
+
: '';
|
|
2937
3007
|
peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
|
|
2938
3008
|
`<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
|
|
2939
3009
|
`<div class="sub">${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
|
|
@@ -3020,6 +3090,20 @@ function renderMsgSessionHeader(context) {
|
|
|
3020
3090
|
}
|
|
3021
3091
|
|
|
3022
3092
|
// ── Sessions 视图 ──
|
|
3093
|
+
function filterSessionTranscripts(transcripts, showAll, sourceType = 'all') {
|
|
3094
|
+
return (transcripts || []).filter(session => {
|
|
3095
|
+
const valid = showAll || (session.userMsgs || 0) >= 1;
|
|
3096
|
+
const sourceMatches = sourceType === 'all' || (session.sourceType || 'unknown') === sourceType;
|
|
3097
|
+
return valid && sourceMatches;
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
function renderSessionSourceBadge(sourceType) {
|
|
3102
|
+
const type = ['trigger', 'agent', 'cli', 'user'].includes(sourceType) ? sourceType : 'unknown';
|
|
3103
|
+
const label = t(`sessions.source.${type}`);
|
|
3104
|
+
return `<span class="session-source-badge source-${type}" title="${esc(label)}">${esc(label)}</span>`;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3023
3107
|
function renderSession(data) {
|
|
3024
3108
|
if (!data) return;
|
|
3025
3109
|
const projects = data.projects || [];
|
|
@@ -3034,8 +3118,7 @@ function renderSession(data) {
|
|
|
3034
3118
|
}
|
|
3035
3119
|
|
|
3036
3120
|
// 有效筛选
|
|
3037
|
-
const filtered = transcripts
|
|
3038
|
-
.filter(t => !sessFilterNormal || (t.userMsgs || 0) >= 1);
|
|
3121
|
+
const filtered = filterSessionTranscripts(transcripts, sessShowAll, sessSourceFilter);
|
|
3039
3122
|
|
|
3040
3123
|
// 左栏:过滤条 + 列表
|
|
3041
3124
|
const projOpts = projects.map(p =>
|
|
@@ -3050,18 +3133,25 @@ function renderSession(data) {
|
|
|
3050
3133
|
if (availableBaseagents.codex) {
|
|
3051
3134
|
baseagentOpts += `<option value="codex"${sessSel.baseagent === 'codex' ? ' selected' : ''}>Codex</option>`;
|
|
3052
3135
|
}
|
|
3136
|
+
const sourceTypes = ['all', 'user', 'agent', 'trigger', 'cli', 'unknown'];
|
|
3137
|
+
const sourceOpts = sourceTypes.map(type => {
|
|
3138
|
+
const label = type === 'all' ? t('sessions.filter.sourceAll') : t(`sessions.source.${type}`);
|
|
3139
|
+
return `<option value="${type}"${sessSourceFilter === type ? ' selected' : ''}>${esc(label)}</option>`;
|
|
3140
|
+
}).join('');
|
|
3053
3141
|
|
|
3054
|
-
const
|
|
3142
|
+
const filterLabelKey = sessShowAll ? 'sessions.filter.all' : 'sessions.filter.valid';
|
|
3143
|
+
const filterTitleKey = sessShowAll ? 'sessions.filter.allTitle' : 'sessions.filter.validTitle';
|
|
3055
3144
|
let listHtml = '<div class="sess-filter">' +
|
|
3056
3145
|
'<div class="sess-select-row">' +
|
|
3057
3146
|
`<select id="sess-baseagent" title="Base Agent">${baseagentOpts}</select>` +
|
|
3058
3147
|
`<select id="sess-project" title="${t('sessions.header.project')}">${projOpts}</select>` +
|
|
3148
|
+
`<select id="sess-source-type" title="${t('sessions.filter.sourceTitle')}">${sourceOpts}</select>` +
|
|
3059
3149
|
'</div>' +
|
|
3060
3150
|
'<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"${
|
|
3151
|
+
`<div class="sess-count">${tf('sessions.count', { count: filtered.length })}</div>` +
|
|
3152
|
+
`<label class="sess-switch" title="${t(filterTitleKey)}">` +
|
|
3153
|
+
`<span>${t(filterLabelKey)}</span>` +
|
|
3154
|
+
`<input id="sess-filter-toggle" type="checkbox"${sessShowAll ? ' checked' : ''}>` +
|
|
3065
3155
|
'<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>' +
|
|
3066
3156
|
'</label>' +
|
|
3067
3157
|
'</div></div>' +
|
|
@@ -3076,12 +3166,13 @@ function renderSession(data) {
|
|
|
3076
3166
|
let badge = '';
|
|
3077
3167
|
if (t.bound) {
|
|
3078
3168
|
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.
|
|
3169
|
+
badge = `<span class="bind-badge">${dot}${esc(t.boundChannel || '')}·${esc(shortAid(t.boundSelf || ''))}</span>`;
|
|
3080
3170
|
}
|
|
3171
|
+
const sourceBadge = renderSessionSourceBadge(t.sourceType);
|
|
3081
3172
|
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
3173
|
listHtml += `<div class="list-item${sel}" data-sid="${esc(t.id)}">` +
|
|
3083
3174
|
`<div class="name">${esc(title)}</div>` +
|
|
3084
|
-
`<div class="sub">${fmtAgo(t.lastActivity)} · ${msgs}
|
|
3175
|
+
`<div class="sub">${fmtAgo(t.lastActivity)} · ${msgs} · ${sourceBadge}${badge}</div>` +
|
|
3085
3176
|
'</div>';
|
|
3086
3177
|
}
|
|
3087
3178
|
listHtml += '</div>';
|
|
@@ -3101,7 +3192,12 @@ function renderSession(data) {
|
|
|
3101
3192
|
subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
|
|
3102
3193
|
};
|
|
3103
3194
|
const filterToggle = $('#sess-filter-toggle');
|
|
3104
|
-
if (filterToggle) filterToggle.onchange = () => {
|
|
3195
|
+
if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; renderSession(state.session); };
|
|
3196
|
+
const sourceTypeSelect = $('#sess-source-type');
|
|
3197
|
+
if (sourceTypeSelect) sourceTypeSelect.onchange = () => {
|
|
3198
|
+
sessSourceFilter = sourceTypeSelect.value;
|
|
3199
|
+
renderSession(state.session);
|
|
3200
|
+
};
|
|
3105
3201
|
$('#sess-list').querySelectorAll('.list-item').forEach(item => {
|
|
3106
3202
|
item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent }; subscribe('session', sessSel); };
|
|
3107
3203
|
});
|
|
@@ -3220,6 +3316,7 @@ const CAT_META = {
|
|
|
3220
3316
|
tool_call: { label: () => t('sessions.turnType.toolCall'), icon: '🟣' },
|
|
3221
3317
|
tool_result: { label: () => t('sessions.turnType.toolResult'), icon: '🟠' },
|
|
3222
3318
|
msg_send: { label: () => t('sessions.turnType.msgSend'), icon: '📤' },
|
|
3319
|
+
subtask: { label: () => t('sessions.turnType.subtask'), icon: '◆' },
|
|
3223
3320
|
system: { label: () => t('sessions.turnType.system'), icon: '⚪' },
|
|
3224
3321
|
};
|
|
3225
3322
|
|
|
@@ -3236,7 +3333,7 @@ function renderSessHeader(h, baseagent) {
|
|
|
3236
3333
|
let bind = '';
|
|
3237
3334
|
if (h.bound) {
|
|
3238
3335
|
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.
|
|
3336
|
+
bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.boundSelf || ''))} ${dot}</span>`;
|
|
3240
3337
|
}
|
|
3241
3338
|
return '<div class="sess-header">' +
|
|
3242
3339
|
'<div class="sh-title-row">' +
|
|
@@ -3269,6 +3366,7 @@ function renderCatBar(counts) {
|
|
|
3269
3366
|
['tool_call', counts.toolCall],
|
|
3270
3367
|
['tool_result', counts.toolResult],
|
|
3271
3368
|
['msg_send', counts.msgSend],
|
|
3369
|
+
['subtask', counts.subtask],
|
|
3272
3370
|
];
|
|
3273
3371
|
let s = '<div class="sh-cats">';
|
|
3274
3372
|
for (const [cat, n] of items) {
|
|
@@ -3316,6 +3414,28 @@ function renderBlocks(blocks) {
|
|
|
3316
3414
|
} else if (b.kind === 'tool_result') {
|
|
3317
3415
|
const cls = b.isError ? 'blk-result err' : 'blk-result';
|
|
3318
3416
|
out += `<details class="blk ${cls}" open><summary>${b.isError ? '✗ ' + t('sessions.block.resultError') : '↳ ' + t('sessions.block.result')}</summary><pre class="result-body">${esc(b.text)}</pre></details>`;
|
|
3417
|
+
} else if (b.kind === 'subtask') {
|
|
3418
|
+
const knownStatuses = ['running', 'completed', 'failed', 'stopped', 'killed'];
|
|
3419
|
+
const status = knownStatuses.includes(String(b.status)) ? String(b.status) : 'running';
|
|
3420
|
+
const statusLabel = t(`sessions.subtask.status.${status}`);
|
|
3421
|
+
const meta = [];
|
|
3422
|
+
if (b.subagentType) meta.push(esc(b.subagentType));
|
|
3423
|
+
if (b.toolUses) meta.push(esc(tf('sessions.subtask.tools', { count: fmtNum(b.toolUses) })));
|
|
3424
|
+
if (b.totalTokens) meta.push(esc(tf('sessions.subtask.tokens', { count: fmtNum(b.totalTokens) })));
|
|
3425
|
+
if (b.durationMs) meta.push(esc(fmtDur(b.durationMs / 1000)));
|
|
3426
|
+
if (b.terminalReason) meta.push(esc(tf('sessions.subtask.reason', { reason: b.terminalReason })));
|
|
3427
|
+
const progress = b.progress && b.progress !== b.description
|
|
3428
|
+
? `<div class="subtask-progress">${esc(b.progress)}</div>`
|
|
3429
|
+
: '';
|
|
3430
|
+
out += `<div class="blk blk-subtask status-${status}">` +
|
|
3431
|
+
'<div class="subtask-main">' +
|
|
3432
|
+
`<span class="subtask-status-dot" aria-hidden="true"></span>` +
|
|
3433
|
+
`<span class="subtask-status">${esc(statusLabel)}</span>` +
|
|
3434
|
+
`<span class="subtask-title">${esc(b.description || b.taskId || '')}</span>` +
|
|
3435
|
+
'</div>' +
|
|
3436
|
+
(meta.length ? `<div class="subtask-meta">${meta.join('<span>·</span>')}</div>` : '') +
|
|
3437
|
+
progress +
|
|
3438
|
+
'</div>';
|
|
3319
3439
|
}
|
|
3320
3440
|
}
|
|
3321
3441
|
return out;
|
|
@@ -5474,12 +5594,18 @@ function triggerOriginalEditState(t) {
|
|
|
5474
5594
|
const id = triggerIdOf(t);
|
|
5475
5595
|
const agentAid = triggerAgentOf(t);
|
|
5476
5596
|
const rawFeedbackStrategy = String(feedback.strategy ?? t?.feedbackStrategy ?? 'target');
|
|
5597
|
+
const executionType = String(execution.type ?? t?.executionType ?? 'target_session');
|
|
5598
|
+
const agentBaseagent = String(triggerAgentInfo(agentAid)?.baseagent ?? triggerActiveBaseagentForAgent(agentAid));
|
|
5599
|
+
const persistedBaseagent = typeof execution.baseagent === 'string' && execution.baseagent.trim()
|
|
5600
|
+
? execution.baseagent.trim()
|
|
5601
|
+
: (typeof t?.baseagent === 'string' && t.baseagent.trim() ? t.baseagent.trim() : '');
|
|
5602
|
+
const baseagent = persistedBaseagent || agentBaseagent;
|
|
5477
5603
|
return {
|
|
5478
5604
|
id,
|
|
5479
5605
|
agentAid,
|
|
5480
5606
|
name: String(definition?.name ?? t?.name ?? t?.label ?? ''),
|
|
5481
5607
|
enabled: triggerIsEnabled(t),
|
|
5482
|
-
executionType
|
|
5608
|
+
executionType,
|
|
5483
5609
|
scriptPath: String(execution.script?.path ?? ''),
|
|
5484
5610
|
scriptRuntime: String(execution.script?.runtime ?? 'node'),
|
|
5485
5611
|
scriptTimeoutMs: Number(execution.script?.timeoutMs ?? 30_000),
|
|
@@ -5487,7 +5613,11 @@ function triggerOriginalEditState(t) {
|
|
|
5487
5613
|
scheduleValue: source.scheduleValue,
|
|
5488
5614
|
eventFilter: definition?.source?.type === 'event' ? (definition.source.filter ?? null) : null,
|
|
5489
5615
|
timezone: String(source.timezone ?? ''),
|
|
5490
|
-
baseagent
|
|
5616
|
+
baseagent,
|
|
5617
|
+
agentBaseagent,
|
|
5618
|
+
baseagentMismatch: executionType === 'trigger_session'
|
|
5619
|
+
&& Boolean(persistedBaseagent)
|
|
5620
|
+
&& persistedBaseagent !== agentBaseagent,
|
|
5491
5621
|
prompt: execution.type === 'script' ? '' : triggerPrompt(definition, t),
|
|
5492
5622
|
model: String(execution.model ?? t?.model ?? ''),
|
|
5493
5623
|
effort: String(execution.effort ?? t?.effort ?? ''),
|
|
@@ -6710,6 +6840,7 @@ function openTriggerEditDrawer(id, agent) {
|
|
|
6710
6840
|
`<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
6841
|
`<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
6842
|
`</div>` +
|
|
6843
|
+
(original.baseagentMismatch ? `<div class="trig-baseagent-note">${esc(tf('triggers.baseagent.mismatch', { trigger: original.baseagent, agent: original.agentBaseagent }))}</div>` : '') +
|
|
6713
6844
|
`<div class="trig-script-feedback">` +
|
|
6714
6845
|
`<div class="trig-feedback-templates-title">${t('triggers.field.outputTemplate')}</div>` +
|
|
6715
6846
|
`<div class="trig-feedback-templates">` +
|
|
@@ -6990,6 +7121,13 @@ function updateThemeButton() {
|
|
|
6990
7121
|
}
|
|
6991
7122
|
|
|
6992
7123
|
function disposeWindowChart(key) {
|
|
7124
|
+
const observerKey = '__ecwebChartResizeObserver_' + key;
|
|
7125
|
+
const observer = window[observerKey];
|
|
7126
|
+
if (observer) {
|
|
7127
|
+
observer.disconnect();
|
|
7128
|
+
window[observerKey] = null;
|
|
7129
|
+
}
|
|
7130
|
+
|
|
6993
7131
|
const chart = window[key];
|
|
6994
7132
|
if (chart && typeof chart.dispose === 'function') {
|
|
6995
7133
|
chart.dispose();
|
|
@@ -7048,6 +7186,28 @@ function initTheme() {
|
|
|
7048
7186
|
}
|
|
7049
7187
|
}
|
|
7050
7188
|
|
|
7189
|
+
function chartThemeValue(name, fallback) {
|
|
7190
|
+
var value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
7191
|
+
return value || fallback;
|
|
7192
|
+
}
|
|
7193
|
+
|
|
7194
|
+
function chartTooltipOptions(isDark) {
|
|
7195
|
+
return {
|
|
7196
|
+
backgroundColor: chartThemeValue('--bg2', isDark ? '#161b22' : '#ffffff'),
|
|
7197
|
+
borderColor: chartThemeValue('--border', isDark ? '#30363d' : '#e2e6ed'),
|
|
7198
|
+
borderWidth: 1,
|
|
7199
|
+
padding: [10, 12],
|
|
7200
|
+
textStyle: {
|
|
7201
|
+
color: chartThemeValue('--fg', isDark ? '#e6edf3' : '#1a202c'),
|
|
7202
|
+
fontSize: 12,
|
|
7203
|
+
lineHeight: 20,
|
|
7204
|
+
},
|
|
7205
|
+
extraCssText: 'border-radius:8px;box-shadow:' + (isDark
|
|
7206
|
+
? '0 14px 34px rgba(0,0,0,0.42)'
|
|
7207
|
+
: '0 14px 34px rgba(31,41,55,0.16)') + ';',
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
7210
|
+
|
|
7051
7211
|
// ── Usage 相关函数 ──
|
|
7052
7212
|
function fmtTokens(n) {
|
|
7053
7213
|
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
@@ -7255,12 +7415,19 @@ function formatDatetimeLocal(date) {
|
|
|
7255
7415
|
}
|
|
7256
7416
|
|
|
7257
7417
|
// 模型访问明细查询
|
|
7418
|
+
var _detailInited = false;
|
|
7419
|
+
|
|
7258
7420
|
function initDetailQuery() {
|
|
7259
7421
|
// 填充Agent选择器
|
|
7260
7422
|
loadDetailAgentList();
|
|
7261
7423
|
// 填充Model选择器(按上面总览的时间范围)
|
|
7262
7424
|
loadDetailModelList();
|
|
7263
7425
|
|
|
7426
|
+
// 事件只绑一次:initOverviewFilters 会在每次切回 Overview 子 Tab 时重复调用,
|
|
7427
|
+
// 重复绑定会让一次 change 触发 N 次并发查询,慢的旧响应可能覆盖新响应(表现为“切换无效”)。
|
|
7428
|
+
if (_detailInited) return;
|
|
7429
|
+
_detailInited = true;
|
|
7430
|
+
|
|
7264
7431
|
// 绑定分页大小变化
|
|
7265
7432
|
const pageSizeEl = $('#detail-page-size');
|
|
7266
7433
|
if (pageSizeEl) {
|
|
@@ -7347,10 +7514,8 @@ async function loadDetailAgentList() {
|
|
|
7347
7514
|
option.textContent = a.agent_name || a.agent_aid.split('.')[0];
|
|
7348
7515
|
selectEl.appendChild(option);
|
|
7349
7516
|
});
|
|
7350
|
-
//
|
|
7351
|
-
|
|
7352
|
-
selectEl.value = agents[0].agent_aid;
|
|
7353
|
-
}
|
|
7517
|
+
// 默认查询全部智能体(首项 value=""),不要自动切到第一个 Agent。
|
|
7518
|
+
selectEl.value = '';
|
|
7354
7519
|
// 加载完成后自动查询一次
|
|
7355
7520
|
queryDetailUsage();
|
|
7356
7521
|
}
|
|
@@ -7394,7 +7559,11 @@ async function loadDetailModelList() {
|
|
|
7394
7559
|
} catch {}
|
|
7395
7560
|
}
|
|
7396
7561
|
|
|
7562
|
+
var _detailQuerySeq = 0;
|
|
7563
|
+
|
|
7397
7564
|
async function queryDetailUsage() {
|
|
7565
|
+
// 请求序号:并发查询时只认最后一次的响应,避免慢的旧响应覆盖新结果
|
|
7566
|
+
const seq = ++_detailQuerySeq;
|
|
7398
7567
|
// 使用总览的时间范围
|
|
7399
7568
|
const timeRange = window._currentOverviewTimeRange || {};
|
|
7400
7569
|
const fromTs = timeRange.fromTs;
|
|
@@ -7421,13 +7590,16 @@ async function queryDetailUsage() {
|
|
|
7421
7590
|
const resp = await fetch(authApiUrl('api/stats/detail?' + params.toString()), {
|
|
7422
7591
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
7423
7592
|
});
|
|
7593
|
+
if (seq !== _detailQuerySeq) return; // 已有更新的查询发出,丢弃本次结果
|
|
7424
7594
|
if (!resp.ok) {
|
|
7425
7595
|
showDetailError(t('usage.detail.error'));
|
|
7426
7596
|
return;
|
|
7427
7597
|
}
|
|
7428
7598
|
const result = await resp.json();
|
|
7599
|
+
if (seq !== _detailQuerySeq) return;
|
|
7429
7600
|
renderDetailTable(result.data, result.total, page, pageSize);
|
|
7430
7601
|
} catch {
|
|
7602
|
+
if (seq !== _detailQuerySeq) return;
|
|
7431
7603
|
showDetailError(t('usage.detail.error'));
|
|
7432
7604
|
}
|
|
7433
7605
|
}
|
|
@@ -7627,9 +7799,11 @@ function initExplorer() {
|
|
|
7627
7799
|
// 初始化时间范围选择
|
|
7628
7800
|
initExplorerTimeFilters();
|
|
7629
7801
|
|
|
7630
|
-
//
|
|
7631
|
-
var
|
|
7632
|
-
if (
|
|
7802
|
+
// 模型、粒度下拉框变更后立即查询,不再需要额外的查询按钮。
|
|
7803
|
+
var modelFilter = $('#exp-model');
|
|
7804
|
+
if (modelFilter) modelFilter.addEventListener('change', runExplorerQuery);
|
|
7805
|
+
var granularityFilter = $('#exp-granularity');
|
|
7806
|
+
if (granularityFilter) granularityFilter.addEventListener('change', runExplorerQuery);
|
|
7633
7807
|
|
|
7634
7808
|
// Load sidebar lists
|
|
7635
7809
|
loadExplorerSidebar();
|
|
@@ -8076,7 +8250,10 @@ async function runExplorerQuery() {
|
|
|
8076
8250
|
_explorerChart = echarts.init(chartEl, isDark ? 'dark' : null);
|
|
8077
8251
|
var periods = data.map(function(r) { return r.period; });
|
|
8078
8252
|
_explorerChart.setOption({
|
|
8079
|
-
tooltip: {
|
|
8253
|
+
tooltip: {
|
|
8254
|
+
trigger: 'axis',
|
|
8255
|
+
...chartTooltipOptions(isDark),
|
|
8256
|
+
},
|
|
8080
8257
|
legend: { data: [t('usage.card.input'), t('usage.card.output')], top: 0, textStyle: { fontSize: 11 } },
|
|
8081
8258
|
grid: { top: 30, bottom: 30, left: 60, right: 16 },
|
|
8082
8259
|
xAxis: { type: 'category', data: periods, axisLabel: { fontSize: 10, rotate: 30 } },
|
|
@@ -8122,6 +8299,52 @@ function disposeMonitorCharts() {
|
|
|
8122
8299
|
['_monCpu', '_monMem', '_monDisk', '_monNet'].forEach(disposeWindowChart);
|
|
8123
8300
|
}
|
|
8124
8301
|
|
|
8302
|
+
function scheduleAfterLayout(callback) {
|
|
8303
|
+
if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(callback);
|
|
8304
|
+
return setTimeout(callback, 16);
|
|
8305
|
+
}
|
|
8306
|
+
|
|
8307
|
+
function resizeChartAfterLayout(chart) {
|
|
8308
|
+
scheduleAfterLayout(function () {
|
|
8309
|
+
if (!chart || (typeof chart.isDisposed === 'function' && chart.isDisposed())) return;
|
|
8310
|
+
chart.resize();
|
|
8311
|
+
});
|
|
8312
|
+
}
|
|
8313
|
+
|
|
8314
|
+
function observeMonitorChartSize(el, varKey, chart) {
|
|
8315
|
+
const observerKey = '__ecwebChartResizeObserver_' + varKey;
|
|
8316
|
+
const previous = window[observerKey];
|
|
8317
|
+
if (previous) previous.disconnect();
|
|
8318
|
+
|
|
8319
|
+
if (typeof ResizeObserver === 'function') {
|
|
8320
|
+
const observer = new ResizeObserver(function () {
|
|
8321
|
+
if (window[varKey] !== chart || (typeof chart.isDisposed === 'function' && chart.isDisposed())) {
|
|
8322
|
+
observer.disconnect();
|
|
8323
|
+
return;
|
|
8324
|
+
}
|
|
8325
|
+
chart.resize();
|
|
8326
|
+
});
|
|
8327
|
+
observer.observe(el);
|
|
8328
|
+
window[observerKey] = observer;
|
|
8329
|
+
}
|
|
8330
|
+
|
|
8331
|
+
resizeChartAfterLayout(chart);
|
|
8332
|
+
}
|
|
8333
|
+
|
|
8334
|
+
function deferMonitorChartUntilVisible(el, render) {
|
|
8335
|
+
let attempts = 0;
|
|
8336
|
+
const retry = function () {
|
|
8337
|
+
if (!el.isConnected) return;
|
|
8338
|
+
if (el.clientWidth > 0 && el.clientHeight > 0) {
|
|
8339
|
+
render();
|
|
8340
|
+
return;
|
|
8341
|
+
}
|
|
8342
|
+
// The AUN proxy can deliver the first snapshot before its page layout settles.
|
|
8343
|
+
if (++attempts < 30) scheduleAfterLayout(retry);
|
|
8344
|
+
};
|
|
8345
|
+
scheduleAfterLayout(retry);
|
|
8346
|
+
}
|
|
8347
|
+
|
|
8125
8348
|
function bindMonRangeTabs(wrap) {
|
|
8126
8349
|
var tabs = wrap.querySelectorAll('.mon-range');
|
|
8127
8350
|
if (!tabs.length) return;
|
|
@@ -8289,8 +8512,18 @@ function renderMonitor(data) {
|
|
|
8289
8512
|
function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
|
|
8290
8513
|
var el = $('#' + elId);
|
|
8291
8514
|
if (!el) return;
|
|
8292
|
-
|
|
8293
|
-
|
|
8515
|
+
const render = function () {
|
|
8516
|
+
var chart = window[varKey];
|
|
8517
|
+
if (chart && typeof chart.getDom === 'function' && chart.getDom() !== el) {
|
|
8518
|
+
disposeWindowChart(varKey);
|
|
8519
|
+
chart = null;
|
|
8520
|
+
}
|
|
8521
|
+
if (!chart) {
|
|
8522
|
+
chart = echarts.init(el, isDark ? 'dark' : null);
|
|
8523
|
+
window[varKey] = chart;
|
|
8524
|
+
observeMonitorChartSize(el, varKey, chart);
|
|
8525
|
+
}
|
|
8526
|
+
chart.setOption({
|
|
8294
8527
|
title: { text: title, left: 'center', top: 4, textStyle: { fontSize: 12, color: isDark ? '#e6edf3' : '#1a202c' } },
|
|
8295
8528
|
legend: {
|
|
8296
8529
|
show: series.length > 0,
|
|
@@ -8303,6 +8536,7 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
|
|
|
8303
8536
|
},
|
|
8304
8537
|
tooltip: {
|
|
8305
8538
|
trigger: 'axis',
|
|
8539
|
+
...chartTooltipOptions(isDark),
|
|
8306
8540
|
formatter: function (params) {
|
|
8307
8541
|
var lines = [params[0].axisValue];
|
|
8308
8542
|
params.forEach(function (pt) {
|
|
@@ -8330,7 +8564,15 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
|
|
|
8330
8564
|
};
|
|
8331
8565
|
}),
|
|
8332
8566
|
animation: false,
|
|
8333
|
-
|
|
8567
|
+
});
|
|
8568
|
+
resizeChartAfterLayout(chart);
|
|
8569
|
+
};
|
|
8570
|
+
|
|
8571
|
+
if (el.clientWidth <= 0 || el.clientHeight <= 0) {
|
|
8572
|
+
deferMonitorChartUntilVisible(el, render);
|
|
8573
|
+
return;
|
|
8574
|
+
}
|
|
8575
|
+
render();
|
|
8334
8576
|
}
|
|
8335
8577
|
|
|
8336
8578
|
function renderRoles(data) {
|