fix: show auto-compression elapsed time

This commit is contained in:
Dennis Soong
2026-05-18 13:08:38 +08:00
parent e6be01c4dd
commit 516d2a588c
4 changed files with 125 additions and 9 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Show an elapsed timer on the running automatic-compression card so long WebUI context-compression pauses no longer look frozen while the browser waits for the `compressed` event.
## [v0.51.89] — 2026-05-18 — Release BM (stage-382 — 6-PR full sweep batch — runtime adapter approval/clarify seam + SOUL.md memory panel + #1855 resolve_model_provider fast-path + PWA sidebar spinner fix + /model active-provider preference + contributor contract docs index)
### Changed
+1
View File
@@ -1785,6 +1785,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
phase:'running',
automatic:true,
message:d.message||'Auto-compressing context...',
startedAt:Date.now()/1000,
};
setCompressionUi(state);
const liveAnswerStarted=!!(assistantRow||String(((_parseStreamState&&_parseStreamState())||{}).displayText||'').trim());
+78 -9
View File
@@ -1970,6 +1970,45 @@ function _formatActiveElapsedTimer(seconds){
const s=total%60;
return`${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
}
const _COMPRESSION_ELAPSED_MAX_SECONDS=5*60;
let _compressionElapsedTimer=null;
function _compressionElapsedStartedAt(state){const n=Number(state&&state.startedAt);return Number.isFinite(n)&&n>0?n:null;}
function _compressionElapsedLabel(state){
const started=_compressionElapsedStartedAt(state);
if(!started)return'';
const elapsed=Math.min(Math.max(0,(Date.now()/1000)-started),_COMPRESSION_ELAPSED_MAX_SECONDS);
return _formatActiveElapsedTimer(elapsed);
}
function _compressionElapsedExpired(state){const started=_compressionElapsedStartedAt(state);return !!(started&&((Date.now()/1000)-started)>=_COMPRESSION_ELAPSED_MAX_SECONDS);}
function _compressionLiveCardNode(){return document.querySelector('[data-live-compression-card="1"][data-compression-started-at]');}
function _compressionLiveCardState(){
const node=_compressionLiveCardNode();
const started=Number(node&&node.getAttribute('data-compression-started-at'));
if(!node||!S.session||!Number.isFinite(started)||started<=0)return null;
return {sessionId:S.session.session_id,phase:'running',automatic:true,message:node.getAttribute('data-compression-message')||'Auto-compressing context...',startedAt:started};
}
function _updateCompressionElapsedCards(state){
if(!state)return false;
const preview=_autoCompressionPreviewText(state), detail=_autoCompressionDetailText(state);
let updated=false;
document.querySelectorAll('.tool-card-compress-auto.tool-card-compress-running').forEach(card=>{
const previewEl=card.querySelector('.tool-card-preview');
const detailEl=card.querySelector('.tool-card-result pre');
if(previewEl) previewEl.textContent=preview;
if(detailEl) detailEl.textContent=detail;
updated=true;
});
return updated;
}
function _updateCompressionElapsedTimer(){
const state=_compressionStateForCurrentSession()||_compressionLiveCardState();
if(state&&state.automatic&&state.phase==='running'){
_updateCompressionElapsedCards(state);
if(_compressionElapsedExpired(state)) _clearCompressionElapsedTimer();
}else _clearCompressionElapsedTimer();
}
function _startCompressionElapsedTimer(){if(!_compressionElapsedTimer)_compressionElapsedTimer=setInterval(_updateCompressionElapsedTimer,1000);}
function _clearCompressionElapsedTimer(){if(_compressionElapsedTimer){clearInterval(_compressionElapsedTimer);_compressionElapsedTimer=null;}}
let _activityElapsedTimer=null;
let _activityElapsedTimerGroup=null;
function _activityElapsedStartedAt(group){
@@ -4875,6 +4914,7 @@ function isCompressionUiRunning(){
}
function clearCompressionUi(){
window._compressionUi=null;
_clearCompressionElapsedTimer();
_setCompressionSessionLock(null);
renderCompressionUi();
}
@@ -4883,8 +4923,14 @@ function setCompressionUi(state){
clearCompressionUi();
return;
}
window._compressionUi={...state};
if(state.sessionId) _setCompressionSessionLock(state.sessionId);
const nextState={...state};
if(nextState.automatic&&nextState.phase==='running'&&!_compressionElapsedStartedAt(nextState)){
nextState.startedAt=Date.now()/1000;
}
window._compressionUi=nextState;
if(nextState.sessionId) _setCompressionSessionLock(nextState.sessionId);
if(nextState.automatic&&nextState.phase==='running') _startCompressionElapsedTimer();
else _clearCompressionElapsedTimer();
renderCompressionUi();
}
function _compressionCardsHtml(state){
@@ -4950,21 +4996,38 @@ function _compressionCardsHtml(state){
</div>
${referenceHtml}`;
}
function _autoCompressionCardsHtml(state){
function _autoCompressionBaseDetail(state){
const fallback='Context auto-compressed to continue the conversation';
const running=state&&state.phase==='running';
const detail=running
return running
? (String(state.message||'Auto-compressing context...').trim()||'Auto-compressing context...')
: (String(state.message||fallback).trim()||fallback);
const preview=running
? detail
: (String(state.summary?.headline||detail).trim()||detail);
: (String(state&&state.message||fallback).trim()||fallback);
}
function _autoCompressionPreviewText(state){
const running=state&&state.phase==='running';
const detail=_autoCompressionBaseDetail(state);
if(!running) return (String(state&&state.summary?.headline||detail).trim()||detail);
const elapsedLabel=_compressionElapsedLabel(state);
return [detail, elapsedLabel].filter(Boolean).join(' · ');
}
function _autoCompressionDetailText(state){
const running=state&&state.phase==='running';
const detail=_autoCompressionBaseDetail(state);
const elapsedLabel=running?_compressionElapsedLabel(state):'';
return running&&elapsedLabel
? `${detail}\nElapsed: ${elapsedLabel}`
: detail;
}
function _autoCompressionCardsHtml(state){
const running=state&&state.phase==='running';
const preview=_autoCompressionPreviewText(state);
const cardDetail=_autoCompressionDetailText(state);
return `
<div class="tool-card-row compression-card-row" data-compression-card="1">
${_compressionStatusCardHtml({
statusLabel: t('auto_compress_label'),
previewText: preview,
detail,
detail: cardDetail,
icon: running ? '<span class="tool-card-running-dot"></span>' : li('check',13),
open: running,
variantClass: running
@@ -4994,6 +5057,12 @@ function appendLiveCompressionCard(state){
const node=_compressionCardsNode(state);
if(!node) return false;
node.setAttribute('data-live-compression-card','1');
if(state.automatic&&state.phase==='running'){
const started=_compressionElapsedStartedAt(state)||Date.now()/1000;
node.setAttribute('data-compression-started-at',String(started));
node.setAttribute('data-compression-message',String(state.message||'Auto-compressing context...'));
_startCompressionElapsedTimer();
}
const existing=inner.querySelector('[data-live-compression-card="1"]');
if(existing) existing.replaceWith(node);
else inner.appendChild(node);
+42
View File
@@ -67,6 +67,48 @@ def test_auto_compression_completion_transition_is_preserved_after_running_liste
assert "phase:'done'" in _compressed_listener_block()
def test_auto_compression_running_sse_stamps_elapsed_timer_start():
block = _compressing_listener_block()
assert "startedAt:Date.now()/1000" in block
assert block.index("startedAt:Date.now()/1000") < block.index("setCompressionUi(state)")
def test_auto_compression_running_card_renders_elapsed_timer_and_caps_updates():
src = _read("static/ui.js")
start = src.find("function _autoCompressionPreviewText")
assert start != -1, "auto compression preview helper not found"
end = src.find("function _compressionCardsNode", start)
assert end != -1, "compression cards node helper not found after auto helper"
helper = src[start:end]
assert "const _COMPRESSION_ELAPSED_MAX_SECONDS=5*60;" in src
assert "function _compressionElapsedLabel(state)" in src
assert "_formatActiveElapsedTimer" in src
assert "_compressionElapsedLabel(state)" in helper
assert "elapsedLabel" in helper
assert "_autoCompressionPreviewText(state)" in helper
assert "_autoCompressionDetailText(state)" in helper
assert "function _startCompressionElapsedTimer()" in src
assert "function _clearCompressionElapsedTimer()" in src
assert "function _updateCompressionElapsedCards(state)" in src
assert "_startCompressionElapsedTimer();" in src
assert "_clearCompressionElapsedTimer();" in src
def test_auto_compression_live_card_keeps_elapsed_state_for_timer_refresh():
src = _read("static/ui.js")
start = src.find("function appendLiveCompressionCard")
assert start != -1, "live compression card append helper not found"
end = src.find("function _isHandoffSummaryToolPayload", start)
assert end != -1, "handoff helper not found after live compression helper"
helper = src[start:end]
assert "data-compression-started-at" in helper
assert "data-compression-message" in helper
assert "_compressionLiveCardState" in src
def test_auto_compression_does_not_rerender_over_live_answer_text():
block = _compressing_listener_block()
src = _read("static/ui.js")