ec-web 0.0.5 → 0.0.7
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/fs-utils.js +89 -65
- package/dist/paths.js +1 -1
- package/dist/server.js +16 -1
- package/dist/sources/msg.js +218 -34
- package/dist/sources/session-codex.js +72 -6
- package/dist/sources/session.js +84 -11
- package/dist/sources/stats.js +1 -1
- package/dist/sources/transcript-window.js +35 -0
- package/dist/static/app.js +196 -21
- package/dist/static/index.html +1 -1
- package/dist/static/style.css +98 -62
- package/package.json +1 -1
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',
|
|
@@ -331,6 +334,8 @@ const translations = {
|
|
|
331
334
|
'sessions.block.thinking': '思考',
|
|
332
335
|
'sessions.block.result': '结果',
|
|
333
336
|
'sessions.block.resultError': '结果',
|
|
337
|
+
'sessions.content.expand': '展开内容',
|
|
338
|
+
'sessions.content.collapse': '收起内容',
|
|
334
339
|
'sessions.contextTitle': '最后一轮喂给模型的完整上下文大小',
|
|
335
340
|
'sessions.costTitle': '累计费用(按模型定价估算)',
|
|
336
341
|
'sessions.msgUnit': '条',
|
|
@@ -1123,6 +1128,7 @@ const translations = {
|
|
|
1123
1128
|
'messages.empty.noAgents': 'No agents',
|
|
1124
1129
|
'messages.empty.noPeers': 'No chats',
|
|
1125
1130
|
'messages.empty.noMessages': 'No messages',
|
|
1131
|
+
'messages.loadOlder': 'Load older messages',
|
|
1126
1132
|
'messages.tag.group': 'Group',
|
|
1127
1133
|
'messages.tag.groupShort': 'G',
|
|
1128
1134
|
'messages.tag.main': 'Main',
|
|
@@ -1163,6 +1169,8 @@ const translations = {
|
|
|
1163
1169
|
'sessions.empty.noMatch': 'No matching sessions',
|
|
1164
1170
|
'sessions.empty.noSessions': 'No sessions in this project',
|
|
1165
1171
|
'sessions.empty.noContent': 'No content in this session',
|
|
1172
|
+
'sessions.loadOlder': 'Load older content',
|
|
1173
|
+
'sessions.loadFullResult': 'Load full result',
|
|
1166
1174
|
'sessions.header.project': 'Project',
|
|
1167
1175
|
'sessions.header.session': 'Session',
|
|
1168
1176
|
'sessions.stat.context': '📐 {tokens} ctx',
|
|
@@ -1206,6 +1214,8 @@ const translations = {
|
|
|
1206
1214
|
'sessions.block.thinking': 'Thinking',
|
|
1207
1215
|
'sessions.block.result': 'Result',
|
|
1208
1216
|
'sessions.block.resultError': 'Result',
|
|
1217
|
+
'sessions.content.expand': 'Expand content',
|
|
1218
|
+
'sessions.content.collapse': 'Collapse content',
|
|
1209
1219
|
'sessions.contextTitle': 'Full context size sent to the model in the last turn',
|
|
1210
1220
|
'sessions.costTitle': 'Estimated cumulative cost by model pricing',
|
|
1211
1221
|
'sessions.msgUnit': 'msgs',
|
|
@@ -1934,7 +1944,7 @@ function connect() {
|
|
|
1934
1944
|
console.log('[ecweb] Available baseagents:', availableBaseagents, 'Selected:', sessSel.baseagent);
|
|
1935
1945
|
// 重新订阅当前视图(带上正确的参数)
|
|
1936
1946
|
if (currentView === 'session') {
|
|
1937
|
-
subscribe('session',
|
|
1947
|
+
subscribe('session', sessSel);
|
|
1938
1948
|
} else if (currentView === 'roleManagement') {
|
|
1939
1949
|
subscribeRoleManagementSubview();
|
|
1940
1950
|
} else if (currentView === 'system' && systemContentMode === 'monitor') {
|
|
@@ -1969,6 +1979,11 @@ function connect() {
|
|
|
1969
1979
|
if (pend) { delete _ipcPending[msg.requestId]; pend.resolve(msg.data); }
|
|
1970
1980
|
return;
|
|
1971
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
|
+
}
|
|
1972
1987
|
if (msg.type === 'snapshot' || msg.type === 'delta') {
|
|
1973
1988
|
console.log('[ecweb] Received', msg.type, 'for view:', msg.view, 'currentView:', currentView);
|
|
1974
1989
|
// 系统基础快照不主动执行健康检查;保留用户点击按钮后得到的 check/upgrade 结果。
|
|
@@ -1978,6 +1993,24 @@ function connect() {
|
|
|
1978
1993
|
check: msg.data.check ?? state.system.check,
|
|
1979
1994
|
upgrade: state.system.upgrade ?? msg.data.upgrade,
|
|
1980
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 };
|
|
1981
2014
|
} else {
|
|
1982
2015
|
state[msg.view] = msg.data;
|
|
1983
2016
|
}
|
|
@@ -2040,6 +2073,8 @@ function menuSend(payload) {
|
|
|
2040
2073
|
|
|
2041
2074
|
const _ipcPending = {};
|
|
2042
2075
|
let _ipcSeq = 0;
|
|
2076
|
+
const _sessionResultPending = {};
|
|
2077
|
+
let _sessionResultSeq = 0;
|
|
2043
2078
|
function ipcSend(payload, timeoutMs) {
|
|
2044
2079
|
return new Promise((resolve, reject) => {
|
|
2045
2080
|
if (!ws || ws.readyState !== WebSocket.OPEN) { reject(new Error(t('common.notConnected'))); return; }
|
|
@@ -2053,14 +2088,26 @@ function ipcSend(payload, timeoutMs) {
|
|
|
2053
2088
|
});
|
|
2054
2089
|
}
|
|
2055
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
|
+
|
|
2056
2103
|
// 心跳
|
|
2057
2104
|
setInterval(() => {
|
|
2058
2105
|
if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'ping' }));
|
|
2059
2106
|
}, 20000);
|
|
2060
2107
|
|
|
2061
2108
|
// ── Tab 切换 ──
|
|
2062
|
-
let msgSel = { aid: null, peer: null };
|
|
2063
|
-
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 };
|
|
2064
2111
|
let trigSel = { agent: null };
|
|
2065
2112
|
let trigAgentsCollapsed = false;
|
|
2066
2113
|
const _trigOps = new Map(); // Map<triggerId, string>
|
|
@@ -2126,8 +2173,8 @@ function switchView(view) {
|
|
|
2126
2173
|
document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.view === view));
|
|
2127
2174
|
document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === 'view-' + view));
|
|
2128
2175
|
// 切换时按当前选择恢复订阅
|
|
2129
|
-
if (view === 'msg') subscribe('msg',
|
|
2130
|
-
else if (view === 'session') subscribe('session',
|
|
2176
|
+
if (view === 'msg') subscribe('msg', msgSel);
|
|
2177
|
+
else if (view === 'session') subscribe('session', sessSel);
|
|
2131
2178
|
else if (view === 'cache') subscribe('cache', {});
|
|
2132
2179
|
else if (view === 'system' && systemContentMode === 'monitor') subscribe('monitor', { range: monRange });
|
|
2133
2180
|
else if (view === 'system') subscribe('system', {});
|
|
@@ -2935,7 +2982,7 @@ function renderMsg(data) {
|
|
|
2935
2982
|
if (!selectedAidExists) {
|
|
2936
2983
|
const nextAid = aids[0]?.aid || null;
|
|
2937
2984
|
const selectionChanged = msgSel.aid !== nextAid || msgSel.peer !== null;
|
|
2938
|
-
msgSel = { aid: nextAid, peer: null };
|
|
2985
|
+
msgSel = { aid: nextAid, peer: null, before: null };
|
|
2939
2986
|
if (selectionChanged && nextAid) subscribe('msg', msgSel);
|
|
2940
2987
|
}
|
|
2941
2988
|
const scopeMatches = data.scope === msgSel.aid;
|
|
@@ -2945,7 +2992,7 @@ function renderMsg(data) {
|
|
|
2945
2992
|
if (!selectedPeerExists) {
|
|
2946
2993
|
const nextPeer = peers[0]?.peerId || null;
|
|
2947
2994
|
const selectionChanged = msgSel.peer !== nextPeer;
|
|
2948
|
-
msgSel = { aid: msgSel.aid, peer: nextPeer };
|
|
2995
|
+
msgSel = { aid: msgSel.aid, peer: nextPeer, before: null };
|
|
2949
2996
|
if (selectionChanged && nextPeer) subscribe('msg', msgSel);
|
|
2950
2997
|
}
|
|
2951
2998
|
}
|
|
@@ -2970,7 +3017,7 @@ function renderMsg(data) {
|
|
|
2970
3017
|
$('#msg-aids').querySelectorAll('.list-item').forEach(item => {
|
|
2971
3018
|
item.onclick = () => {
|
|
2972
3019
|
if (item.dataset.aid === msgSel.aid) return;
|
|
2973
|
-
msgSel = { aid: item.dataset.aid, peer: null };
|
|
3020
|
+
msgSel = { aid: item.dataset.aid, peer: null, before: null };
|
|
2974
3021
|
subscribe('msg', msgSel);
|
|
2975
3022
|
};
|
|
2976
3023
|
});
|
|
@@ -3013,7 +3060,7 @@ function renderMsg(data) {
|
|
|
3013
3060
|
$('#msg-peers').querySelectorAll('.list-item').forEach(item => {
|
|
3014
3061
|
item.onclick = () => {
|
|
3015
3062
|
if (item.dataset.peer === msgSel.peer) return;
|
|
3016
|
-
msgSel = { aid: msgSel.aid, peer: item.dataset.peer };
|
|
3063
|
+
msgSel = { aid: msgSel.aid, peer: item.dataset.peer, before: null };
|
|
3017
3064
|
subscribe('msg', msgSel);
|
|
3018
3065
|
};
|
|
3019
3066
|
});
|
|
@@ -3031,6 +3078,9 @@ function renderMsg(data) {
|
|
|
3031
3078
|
}
|
|
3032
3079
|
const atBottom = stream.scrollHeight - stream.scrollTop - stream.clientHeight < 60;
|
|
3033
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
|
+
}
|
|
3034
3084
|
for (const m of messages) {
|
|
3035
3085
|
const cls = m.dir === 'in' ? 'in' : 'out';
|
|
3036
3086
|
const arrow = m.dir === 'in' ? '↓' : '↑';
|
|
@@ -3053,6 +3103,11 @@ function renderMsg(data) {
|
|
|
3053
3103
|
}
|
|
3054
3104
|
msgHtml += messages.length ? '</div>' : `<div class="empty">${t('messages.empty.noMessages')}</div></div>`;
|
|
3055
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
|
+
};
|
|
3056
3111
|
if (atBottom) stream.scrollTop = stream.scrollHeight;
|
|
3057
3112
|
}
|
|
3058
3113
|
|
|
@@ -3182,13 +3237,13 @@ function renderSession(data) {
|
|
|
3182
3237
|
const baseagentSel = $('#sess-baseagent');
|
|
3183
3238
|
if (baseagentSel) baseagentSel.onchange = () => {
|
|
3184
3239
|
console.log('[ecweb] Baseagent changed to:', baseagentSel.value);
|
|
3185
|
-
sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value };
|
|
3240
|
+
sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value, turnBefore: null };
|
|
3186
3241
|
console.log('[ecweb] Subscribing to session with baseagent:', sessSel.baseagent);
|
|
3187
3242
|
subscribe('session', { baseagent: sessSel.baseagent });
|
|
3188
3243
|
};
|
|
3189
3244
|
const projSel = $('#sess-project');
|
|
3190
3245
|
if (projSel) projSel.onchange = () => {
|
|
3191
|
-
sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent };
|
|
3246
|
+
sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent, turnBefore: null };
|
|
3192
3247
|
subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
|
|
3193
3248
|
};
|
|
3194
3249
|
const filterToggle = $('#sess-filter-toggle');
|
|
@@ -3199,7 +3254,7 @@ function renderSession(data) {
|
|
|
3199
3254
|
renderSession(state.session);
|
|
3200
3255
|
};
|
|
3201
3256
|
$('#sess-list').querySelectorAll('.list-item').forEach(item => {
|
|
3202
|
-
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); };
|
|
3203
3258
|
});
|
|
3204
3259
|
|
|
3205
3260
|
// 右:transcript 详情
|
|
@@ -3209,11 +3264,22 @@ function renderSession(data) {
|
|
|
3209
3264
|
const h = data.header || {};
|
|
3210
3265
|
const atBottom = detail.scrollHeight - detail.scrollTop - detail.clientHeight < 60;
|
|
3211
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
|
+
}
|
|
3212
3270
|
html += '<div class="turn-list">' + (sessChatMode ? renderChatView(turns) : renderFullView(turns)) + '</div>';
|
|
3213
3271
|
detail.innerHTML = html;
|
|
3214
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
|
+
|
|
3215
3279
|
const toggle = $('#chat-toggle');
|
|
3216
3280
|
if (toggle) toggle.onclick = () => { sessChatMode = !sessChatMode; renderSession(state.session); };
|
|
3281
|
+
setupContentCollapses(detail);
|
|
3282
|
+
setupTruncatedSessionResults(detail, data);
|
|
3217
3283
|
if (atBottom) detail.scrollTop = detail.scrollHeight;
|
|
3218
3284
|
}
|
|
3219
3285
|
|
|
@@ -3222,13 +3288,19 @@ function renderFullView(turns) {
|
|
|
3222
3288
|
let html = '';
|
|
3223
3289
|
for (const t of turns) {
|
|
3224
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;
|
|
3225
3294
|
const c = CAT_META[cat] || CAT_META.system;
|
|
3226
3295
|
const usage = (t.inputTokens || t.outputTokens)
|
|
3227
3296
|
? `<span class="turn-usage">${esc(t.model || '')} · in ${t.inputTokens || 0} / out ${t.outputTokens || 0}</span>` : '';
|
|
3228
3297
|
html += `<div class="turn cat-${cat}">` +
|
|
3229
3298
|
`<div class="turn-head"><span class="turn-role">${c.icon} ${c.label()}</span>` +
|
|
3230
3299
|
`<span class="turn-time">${t.ts ? fmtTime(t.ts) : ''}</span>${usage}</div>` +
|
|
3231
|
-
`<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>`;
|
|
3232
3304
|
}
|
|
3233
3305
|
return html;
|
|
3234
3306
|
}
|
|
@@ -3275,7 +3347,9 @@ function renderChatView(turns) {
|
|
|
3275
3347
|
while (next < items.length && items[next].type === 'proc') { group.push(items[next]); next++; }
|
|
3276
3348
|
const peer = it.peer ? shortAid(it.peer) : '';
|
|
3277
3349
|
const time = `${it.ts ? fmtTime(it.ts) : ''}${peer ? ' → ' + esc(peer) : ''}`;
|
|
3278
|
-
|
|
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}` +
|
|
3279
3353
|
'<div class="chat-meta-row">' +
|
|
3280
3354
|
(group.length ? renderProcGroup(group, true, time) : `<div class="chat-time">${time}</div>`) +
|
|
3281
3355
|
'</div></div>';
|
|
@@ -3297,7 +3371,10 @@ function renderProcGroup(group, inline = false, time = '') {
|
|
|
3297
3371
|
const c = CAT_META[g.cat] || CAT_META.system;
|
|
3298
3372
|
inner += `<div class="turn cat-${g.cat}"><div class="turn-head"><span class="turn-role">${c.icon} ${c.label()}</span>` +
|
|
3299
3373
|
`<span class="turn-time">${g.ts ? fmtTime(g.ts) : ''}</span></div>` +
|
|
3300
|
-
`<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>`;
|
|
3301
3378
|
}
|
|
3302
3379
|
const summary = tf('sessions.procSummary', { count: group.length });
|
|
3303
3380
|
if (inline) {
|
|
@@ -3393,11 +3470,50 @@ function toolParamValue(value) {
|
|
|
3393
3470
|
try { return JSON.stringify(value, null, 2); } catch { return String(value); }
|
|
3394
3471
|
}
|
|
3395
3472
|
|
|
3396
|
-
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 = {}) {
|
|
3397
3511
|
let out = '';
|
|
3398
3512
|
for (const b of blocks) {
|
|
3399
3513
|
if (b.kind === 'text') {
|
|
3400
|
-
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>`;
|
|
3401
3517
|
} else if (b.kind === 'thinking') {
|
|
3402
3518
|
out += `<details class="blk blk-thinking" open><summary>💭 ${t('sessions.block.thinking')}</summary><div class="blk-thinking-body">${esc(b.text)}</div></details>`;
|
|
3403
3519
|
} else if (b.kind === 'tool_use') {
|
|
@@ -3410,10 +3526,18 @@ function renderBlocks(blocks) {
|
|
|
3410
3526
|
for (const p of entries) {
|
|
3411
3527
|
params += `<div class="tool-param"><span class="pk">${esc(p.k)}</span><code class="pv">${esc(p.v)}</code></div>`;
|
|
3412
3528
|
}
|
|
3413
|
-
|
|
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>`;
|
|
3414
3534
|
} else if (b.kind === 'tool_result') {
|
|
3415
3535
|
const cls = b.isError ? 'blk-result err' : 'blk-result';
|
|
3416
|
-
|
|
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>`;
|
|
3417
3541
|
} else if (b.kind === 'subtask') {
|
|
3418
3542
|
const knownStatuses = ['running', 'completed', 'failed', 'stopped', 'killed'];
|
|
3419
3543
|
const status = knownStatuses.includes(String(b.status)) ? String(b.status) : 'running';
|
|
@@ -3441,6 +3565,34 @@ function renderBlocks(blocks) {
|
|
|
3441
3565
|
return out;
|
|
3442
3566
|
}
|
|
3443
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
|
+
|
|
3444
3596
|
// ── 通用 Menu 协议辅助(mResp / toast,供 Agents / System / Triggers 复用)──
|
|
3445
3597
|
|
|
3446
3598
|
// 提取 menu.response 的 data/error
|
|
@@ -5061,6 +5213,18 @@ const TRIGGER_CONCURRENCY_OPTIONS = [
|
|
|
5061
5213
|
['replace', () => t('triggers.concurrency.replace')],
|
|
5062
5214
|
['allow', () => t('triggers.concurrency.allow')],
|
|
5063
5215
|
];
|
|
5216
|
+
|
|
5217
|
+
function triggerCanonicalThread(thread) {
|
|
5218
|
+
return thread === 'per_run' || thread === 'by_run' ? 'by_run' : 'by_trigger';
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5221
|
+
function triggerConcurrencyOptions(executionType, thread) {
|
|
5222
|
+
if (executionType === 'script') return TRIGGER_CONCURRENCY_OPTIONS;
|
|
5223
|
+
if (executionType === 'trigger_session' && triggerCanonicalThread(thread) === 'by_run') {
|
|
5224
|
+
return TRIGGER_CONCURRENCY_OPTIONS;
|
|
5225
|
+
}
|
|
5226
|
+
return TRIGGER_CONCURRENCY_OPTIONS.filter(([value]) => value !== 'allow');
|
|
5227
|
+
}
|
|
5064
5228
|
const TRIGGER_MISSED_POLICY_OPTIONS = [
|
|
5065
5229
|
['skip', () => t('triggers.missed.skip')],
|
|
5066
5230
|
['run_once', () => t('triggers.missed.run_once')],
|
|
@@ -5606,6 +5770,7 @@ function triggerOriginalEditState(t) {
|
|
|
5606
5770
|
name: String(definition?.name ?? t?.name ?? t?.label ?? ''),
|
|
5607
5771
|
enabled: triggerIsEnabled(t),
|
|
5608
5772
|
executionType,
|
|
5773
|
+
triggerThread: String(execution.thread ?? ''),
|
|
5609
5774
|
scriptPath: String(execution.script?.path ?? ''),
|
|
5610
5775
|
scriptRuntime: String(execution.script?.runtime ?? 'node'),
|
|
5611
5776
|
scriptTimeoutMs: Number(execution.script?.timeoutMs ?? 30_000),
|
|
@@ -6402,6 +6567,7 @@ function syncTriggerEditFields(ctx) {
|
|
|
6402
6567
|
const valueField = root.querySelector('#trig-edit-schedule-value-field');
|
|
6403
6568
|
const eventFields = root.querySelectorAll('.trig-event-field');
|
|
6404
6569
|
const executionType = root.querySelector('#trig-edit-type')?.value || ctx.original.executionType;
|
|
6570
|
+
const concurrencySelect = root.querySelector('#trig-edit-concurrency');
|
|
6405
6571
|
const deliverySwitch = root.querySelector('#trig-edit-delivery-enabled');
|
|
6406
6572
|
const deliverySwitchField = root.querySelector('.trig-delivery-switch');
|
|
6407
6573
|
if (executionType === 'target_session') {
|
|
@@ -6481,6 +6647,15 @@ function syncTriggerEditFields(ctx) {
|
|
|
6481
6647
|
if (scriptFeedback) scriptFeedback.hidden = executionType !== 'script' || !deliveryEnabled;
|
|
6482
6648
|
if (threadField) threadField.hidden = !showTarget || targetSession !== 'thread';
|
|
6483
6649
|
if (threadInput) threadInput.disabled = ctx.saving || !showTarget || targetSession !== 'thread';
|
|
6650
|
+
if (concurrencySelect) {
|
|
6651
|
+
const options = triggerConcurrencyOptions(executionType, ctx.original.triggerThread);
|
|
6652
|
+
const current = options.some(([value]) => value === concurrencySelect.value)
|
|
6653
|
+
? concurrencySelect.value
|
|
6654
|
+
: 'forbid';
|
|
6655
|
+
concurrencySelect.innerHTML = triggerOptionHtml(options, current, null);
|
|
6656
|
+
concurrencySelect.value = current;
|
|
6657
|
+
concurrencySelect.disabled = Boolean(ctx.saving);
|
|
6658
|
+
}
|
|
6484
6659
|
}
|
|
6485
6660
|
|
|
6486
6661
|
function readTriggerEditValues(root) {
|
|
@@ -6627,7 +6802,7 @@ function triggerDefinitionFromEdit(original, values, eventFilter, feedbackStrate
|
|
|
6627
6802
|
execution.prompt = values.prompt;
|
|
6628
6803
|
delete execution.script;
|
|
6629
6804
|
if (values.executionType === 'trigger_session') {
|
|
6630
|
-
if (previousExecutionType !== 'trigger_session' || !['per_run', 'by_trigger'].includes(execution.thread)) {
|
|
6805
|
+
if (previousExecutionType !== 'trigger_session' || !['per_run', 'by_run', 'by_trigger'].includes(execution.thread)) {
|
|
6631
6806
|
execution.thread = 'by_trigger';
|
|
6632
6807
|
}
|
|
6633
6808
|
} else {
|
|
@@ -6872,7 +7047,7 @@ function openTriggerEditDrawer(id, agent) {
|
|
|
6872
7047
|
`<div class="ag-edit-grid trig-edit-grid trig-limit-grid">` +
|
|
6873
7048
|
`<label class="ag-edit-field"><span>${t('triggers.field.maxRuns')}</span><input id="trig-edit-max-runs" type="number" min="1" step="1" value="${esc(original.maxRuns)}" placeholder="${t('common.noLimit')}"></label>` +
|
|
6874
7049
|
`<label class="ag-edit-field"><span>${t('triggers.field.maxDuration')}</span><input id="trig-edit-max-duration" type="text" autocomplete="off" spellcheck="false" value="${esc(original.maxDuration)}" placeholder="${t('triggers.placeholder.maxDuration')}"></label>` +
|
|
6875
|
-
`<label class="ag-edit-field"><span>${t('triggers.field.concurrency')}</span><select id="trig-edit-concurrency">${triggerOptionHtml(
|
|
7050
|
+
`<label class="ag-edit-field"><span>${t('triggers.field.concurrency')}</span><select id="trig-edit-concurrency">${triggerOptionHtml(triggerConcurrencyOptions(original.executionType, original.triggerThread), original.concurrency, null)}</select></label>` +
|
|
6876
7051
|
`<label class="ag-edit-field"><span>${t('triggers.field.retryAttempts')}</span><input id="trig-edit-retry-attempts" type="number" min="0" max="20" step="1" value="${esc(original.retryAttempts)}"></label>` +
|
|
6877
7052
|
`<label class="ag-edit-field"><span>${t('triggers.field.retryBackoff')}</span><input id="trig-edit-retry-backoff" type="text" autocomplete="off" spellcheck="false" value="${esc(original.retryBackoff)}" placeholder="${t('triggers.placeholder.retryBackoff')}"></label>` +
|
|
6878
7053
|
`<label class="ag-edit-field"><span>${t('triggers.field.missedPolicy')}</span><select id="trig-edit-missed-policy">${triggerOptionHtml(TRIGGER_MISSED_POLICY_OPTIONS, original.missedPolicy, null)}</select></label>` +
|
package/dist/static/index.html
CHANGED
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
</nav>
|
|
50
50
|
<span style="flex:1"></span>
|
|
51
|
-
2026-
|
|
51
|
+
2026-08-03 19:27
|
|
52
52
|
<span id="conn-status" class="conn-status" data-i18n="status.connecting">连接中…</span>
|
|
53
53
|
<button id="lang-btn" class="theme-btn" data-i18n-title="language.buttonTitle" title="切换语言">🌐</button>
|
|
54
54
|
<button id="theme-btn" class="theme-btn" data-i18n-title="theme.buttonTitle" title="主题">🌗</button>
|