Stage 376: PR #2450

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
nesquena-hermes committed 2026-05-17 16:42:10 +00:00
commit 54297bdda7
3 files changed
+28

No files matched your search

+1
View File
@@ -9,6 +9,7 @@
- Compact live Thinking cards now reuse the same timeline card across sequential tool calls, preventing repeated Thinking cards from stacking during one multi-tool turn.
- Refresh context-window metadata when a session's resolved model changes during load or when the user switches models, so high-context models do not stay stuck on a stale prior window and trigger premature compression. Fixes #2442.
- **PR #2445** by @Michaelyklam (fixes #2443) — `/api/models` now fingerprints model-catalog inputs as part of its persisted cache metadata, so server-side catalog additions and Codex local catalog changes invalidate `models_cache.json` immediately instead of waiting for the 24-hour TTL or manual cache deletion.
- **PR #2450** by @Michaelyklam (fixes #2447) — Cap the optional streaming word-fade drain after the final `done` SSE event so very large or bursty completed responses are rendered from the canonical session promptly instead of keeping the chat in a live/working state until Stop is pressed.
## [v0.51.82] — 2026-05-17 — Release BF (stage-375 — 2-PR batch — table renderer pipe protection + Catppuccin appearance skin)
+12
View File
@@ -697,6 +697,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
const _STREAM_FADE_MAX_MS=350;
const _STREAM_FADE_STAGGER_MS=16;
const _STREAM_FADE_DONE_MAX_MS=320;
const _STREAM_FADE_DONE_DRAIN_MAX_MS=900;
const _streamFadeEnabledForStream=window._fadeTextEffect===true;
// rAF-throttled rendering: buffer tokens, render at most once per frame
@@ -1086,6 +1087,8 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
: _stripXmlToolCalls(assistantText.slice(segmentStart));
}
function _drainStreamFadeBeforeDone(onDone){
const drainStartedAt=performance.now();
let forcedDone=false;
const step=()=>{
if(!assistantBody){onDone();return;}
const target=_streamFadeCurrentDisplayText();
@@ -1101,6 +1104,15 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
setTimeout(onDone, Math.min(remainingAnimationMs, _STREAM_FADE_DONE_MAX_MS));
return;
}
// Final SSE `done` means the canonical completed session is available.
// The optional word-fade playout must not keep that completed answer
// hidden behind the live Thinking state for large/bursty responses.
if(!forcedDone&&performance.now()-drainStartedAt>=_STREAM_FADE_DONE_DRAIN_MAX_MS){
forcedDone=true;
if(_smdParser) _smdEndParser();
onDone();
return;
}
setTimeout(()=>requestAnimationFrame(step), 33);
};
step();
+15
View File
@@ -82,6 +82,7 @@ const _STREAM_FADE_MS=200;
const _STREAM_FADE_MAX_MS=350;
const _STREAM_FADE_STAGGER_MS=16;
const _STREAM_FADE_DONE_MAX_MS=320;
const _STREAM_FADE_DONE_DRAIN_MAX_MS=900;
const performance={performance_stub};
{helpers}
"""
@@ -178,6 +179,20 @@ def test_stream_fade_uses_incremental_renderer_without_changing_default_path():
assert "_wrapStreamingFadeWords" not in MESSAGES_JS
def test_stream_fade_done_drain_has_hard_cap_for_large_buffered_responses():
drain_block = function_block(MESSAGES_JS, "_drainStreamFadeBeforeDone")
assert "const _STREAM_FADE_DONE_DRAIN_MAX_MS=900" in MESSAGES_JS
assert_contains_all(
drain_block,
[
"const drainStartedAt=performance.now();",
"performance.now()-drainStartedAt>=_STREAM_FADE_DONE_DRAIN_MAX_MS",
"if(_smdParser) _smdEndParser();",
"onDone();",
],
)
def test_stream_fade_css_is_opacity_only_and_hides_live_cursor():
fade_css = STYLE_CSS[STYLE_CSS.index("OpenWebUI-style streaming word fade") :]
assert "filter:" not in STYLE_CSS[STYLE_CSS.index("OpenWebUI-style streaming word fade") :].split(