diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e938441..a6cbfb66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/static/messages.js b/static/messages.js index ae7c6113..98c63daf 100644 --- a/static/messages.js +++ b/static/messages.js @@ -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()); diff --git a/static/ui.js b/static/ui.js index 4fedd18e..b9bbe0a1 100644 --- a/static/ui.js +++ b/static/ui.js @@ -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){ ${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 `