mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-25 11:10:18 +00:00
fix(ui): prevent stuck sidebar spinner on completed sessions (closes #2066)
The spinner (.session-state-indicator.is-streaming) can remain spinning indefinitely on completed sessions when the INFLIGHT in-memory cache is not cleaned up due to abnormal stream termination (page refresh, network disconnect, gateway restart). Add a staleness guard in _isSessionLocallyStreaming: if the server reports is_streaming=false and last_message_at is older than 5 minutes, force the streaming state to false regardless of stale INFLIGHT entries.
This commit is contained in:
+24
-4
@@ -231,16 +231,32 @@ function _isSessionActivelyViewedForList(sid) {
|
||||
function _isSessionLocallyStreaming(s) {
|
||||
if (!s || !s.session_id) return false;
|
||||
const isActive = S.session && s.session_id === S.session.session_id;
|
||||
return Boolean(
|
||||
(isActive && S.busy)
|
||||
|| (typeof INFLIGHT === 'object' && INFLIGHT && INFLIGHT[s.session_id])
|
||||
);
|
||||
// For the active session, rely on S.busy to indicate an ongoing stream.
|
||||
// INFLIGHT entries for non-active sessions are artifacts of interrupted
|
||||
// streams (page refresh, network disconnect, gateway restart) where
|
||||
// `delete INFLIGHT[sid]` was never reached — they should NOT cause the
|
||||
// sidebar spinner to appear on completed sessions. (#2066)
|
||||
return isActive && Boolean(S.busy);
|
||||
}
|
||||
|
||||
function _isSessionEffectivelyStreaming(s) {
|
||||
return Boolean(s && (s.is_streaming || _isSessionLocallyStreaming(s)));
|
||||
}
|
||||
|
||||
function _purgeStaleInflightEntries() {
|
||||
// Clean up INFLIGHT entries for sessions the server confirms are NOT
|
||||
// streaming. This prevents the in-memory cache from growing unbounded
|
||||
// when streams end abnormally. (#2066)
|
||||
if (typeof INFLIGHT !== 'object' || !INFLIGHT) return;
|
||||
for (const sid of Object.keys(INFLIGHT)) {
|
||||
const s = _allSessionsById.get(sid);
|
||||
if (s && !s.is_streaming) {
|
||||
delete INFLIGHT[sid];
|
||||
if (typeof clearInflightState === 'function') clearInflightState(sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _rememberRenderedStreamingState(s, isStreaming) {
|
||||
if (!s || !s.session_id || !isStreaming) return;
|
||||
_sessionStreamingById.set(s.session_id, true);
|
||||
@@ -2257,6 +2273,10 @@ function renderSessionListFromCache(){
|
||||
// Don't re-render while user is actively renaming a session (would destroy the input)
|
||||
if(_renamingSid) return;
|
||||
closeSessionActionMenu();
|
||||
// Purge stale INFLIGHT entries for sessions the server confirms are NOT
|
||||
// streaming. This runs on every list refresh to prevent memory leaks from
|
||||
// interrupted streams. (#2066)
|
||||
_purgeStaleInflightEntries();
|
||||
const q=($('sessionSearch').value||'').toLowerCase();
|
||||
const activeSidForSidebar=_activeSessionIdForSidebar();
|
||||
const titleMatches=q?_allSessions.filter(s=>(s.title||'Untitled').toLowerCase().includes(q)):_allSessions;
|
||||
|
||||
Reference in New Issue
Block a user