mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-24 18:50:15 +00:00
fix: show auto-compression elapsed time
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user