clean up implementation
This commit is contained in:
1 parent
e9c985a487
commit
8ff368fd80
3 files changed
+25
-13
No files matched your search
+14
-9
@@ -524,6 +524,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
const _STREAM_FADE_MS=140;
|
||||
const _STREAM_FADE_WAVE_MS=320;
|
||||
const _STREAM_FADE_MAX_STAGGER_MS=520;
|
||||
const _streamFadeEnabledForStream=window._fadeTextEffect===true;
|
||||
|
||||
// rAF-throttled rendering: buffer tokens, render at most once per frame
|
||||
let _renderPending=false;
|
||||
@@ -703,7 +704,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
_renderPending=false;
|
||||
}
|
||||
function _shouldUseStreamFade(){
|
||||
return window._fadeTextEffect===true;
|
||||
return _streamFadeEnabledForStream;
|
||||
}
|
||||
function _streamFadeSkipNode(node){
|
||||
if(!node||node.nodeType!==1) return false;
|
||||
@@ -718,8 +719,9 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
targetText=String(targetText||'');
|
||||
const now=performance.now();
|
||||
if(!targetText){
|
||||
const hadVisible=!!_streamFadeVisibleText;
|
||||
_resetStreamFadeState();
|
||||
return {text:'', caughtUp:true};
|
||||
return {text:'', caughtUp:true, changed:hadVisible};
|
||||
}
|
||||
if(!_streamFadeVisibleText||!targetText.startsWith(_streamFadeVisibleText)){
|
||||
// Markdown/tool stripping can rewrite the visible prefix. Reset safely rather than
|
||||
@@ -730,7 +732,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
_streamFadeLastTickMs=now;
|
||||
_streamFadeStartedAt=now;
|
||||
}
|
||||
if(_streamFadeVisibleText===targetText) return {text:_streamFadeVisibleText,caughtUp:true};
|
||||
if(_streamFadeVisibleText===targetText) return {text:_streamFadeVisibleText,caughtUp:true,changed:false};
|
||||
|
||||
const remaining=targetText.slice(_streamFadeVisibleText.length);
|
||||
const backlogWords=_streamFadeWordCountOf(remaining);
|
||||
@@ -778,7 +780,7 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
// fade for these waves so fast output stays visibly animated.
|
||||
const burstFloor=backlogWords>=120?24:backlogWords>=60?18:backlogWords>=30?12:wordsPerSecond>=300?8:wordsPerSecond>=220?6:0;
|
||||
if(burstFloor>0) wordsToReveal=Math.max(wordsToReveal, Math.min(burstFloor, backlogWords));
|
||||
if(wordsToReveal<1){_streamFadeLastRevealCount=0;return {text:_streamFadeVisibleText,caughtUp:false};}
|
||||
if(wordsToReveal<1){_streamFadeLastRevealCount=0;return {text:_streamFadeVisibleText,caughtUp:false,changed:false};}
|
||||
_streamFadeLastRevealCount=Math.min(wordsToReveal, backlogWords);
|
||||
_streamFadeWordCarry=Math.max(0,_streamFadeWordCarry-wordsToReveal);
|
||||
|
||||
@@ -792,11 +794,12 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
if(cut<=0) cut=Math.min(remaining.length,4);
|
||||
_streamFadeVisibleText+=remaining.slice(0,cut);
|
||||
if(_streamFadeVisibleText.length>targetText.length) _streamFadeVisibleText=targetText;
|
||||
return {text:_streamFadeVisibleText,caughtUp:_streamFadeVisibleText===targetText};
|
||||
return {text:_streamFadeVisibleText,caughtUp:_streamFadeVisibleText===targetText,changed:true};
|
||||
}
|
||||
function _renderStreamingFadeMarkdown(displayText){
|
||||
if(!assistantBody) return true;
|
||||
const next=_streamFadeNextText(displayText);
|
||||
if(!next.changed) return next.caughtUp;
|
||||
const html=renderMd ? renderMd(next.text||'') : esc(next.text||'');
|
||||
assistantBody.innerHTML=html;
|
||||
assistantBody.classList.add('stream-fade-active');
|
||||
@@ -835,7 +838,6 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
while((match=wordRe.exec(value))){
|
||||
if(match.index>last) frag.appendChild(document.createTextNode(value.slice(last,match.index)));
|
||||
wordIndex+=1;
|
||||
const span=document.createElement('span');
|
||||
if(!_streamFadeWordBornAt[wordIndex]){
|
||||
const newWordOffset=Math.max(wordIndex-_streamFadeWordCount-1,0);
|
||||
// High-speed output should feel like a continuous animated sweep, not
|
||||
@@ -847,14 +849,17 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
|
||||
_streamFadeLatestAnimationEndAt=Math.max(_streamFadeLatestAnimationEndAt,_streamFadeWordBornAt[wordIndex]+fadeMs);
|
||||
const ageMs=now-_streamFadeWordBornAt[wordIndex];
|
||||
const isAnimating=ageMs<fadeMs;
|
||||
span.className=isAnimating?'stream-fade-word is-new':'stream-fade-word';
|
||||
if(isAnimating){
|
||||
const span=document.createElement('span');
|
||||
span.className='stream-fade-word is-new';
|
||||
const delayMs=Math.max(-fadeMs, -ageMs);
|
||||
span.style.animationDelay=delayMs+'ms';
|
||||
span.style.setProperty('--stream-fade-ms',fadeMs+'ms');
|
||||
span.textContent=match[1];
|
||||
frag.appendChild(span);
|
||||
}else{
|
||||
frag.appendChild(document.createTextNode(match[1]));
|
||||
}
|
||||
span.textContent=match[1];
|
||||
frag.appendChild(span);
|
||||
if(match[2]) frag.appendChild(document.createTextNode(match[2]));
|
||||
last=match.index+match[0].length;
|
||||
changed=true;
|
||||
|
||||
+3
-2
@@ -3745,8 +3745,9 @@ main.main.showing-logs > #mainLogs{display:flex;}
|
||||
|
||||
/* OpenWebUI-style streaming word fade (opt-in via Settings → Preferences).
|
||||
Opacity-only fade; high-speed streams use a longer JS-driven wave duration. */
|
||||
.stream-fade-active .stream-fade-word{display:inline;will-change:opacity;}
|
||||
.stream-fade-word.is-new{animation:stream-fade-word-in var(--stream-fade-ms,140ms) ease-out both;}
|
||||
.stream-fade-active .stream-fade-word{display:inline;}
|
||||
.stream-fade-word.is-new{animation:stream-fade-word-in var(--stream-fade-ms,140ms) cubic-bezier(.2,.7,.2,1) both;will-change:opacity;}
|
||||
@keyframes stream-fade-word-in{from{opacity:0;}to{opacity:1;}}
|
||||
@media (prefers-reduced-motion: reduce){.stream-fade-word.is-new{animation:none;will-change:auto;}}
|
||||
[data-live-assistant="1"]:last-child .msg-body.stream-fade-active > :last-child::after,
|
||||
[data-live-assistant="1"]:last-child .msg-body.stream-fade-active:not(:has(> *))::after{display:none;content:none;}
|
||||
@@ -84,6 +84,8 @@ let _streamFadeLastArrivalMs=0;
|
||||
let _streamFadeArrivalWps=0;
|
||||
let _streamFadeLatestAnimationEndAt=0;
|
||||
let _streamFadeLastRevealCount=0;
|
||||
let _streamFadeWordCount=0;
|
||||
let _streamFadeWordBornAt=[];
|
||||
const _STREAM_FADE_MS=140;
|
||||
const _STREAM_FADE_WAVE_MS=320;
|
||||
const _STREAM_FADE_MAX_STAGGER_MS=520;
|
||||
@@ -193,6 +195,7 @@ def test_fade_renderer_uses_playout_buffer_and_markdown_rerender():
|
||||
render_block,
|
||||
[
|
||||
"_streamFadeNextText(displayText)",
|
||||
"if(!next.changed) return next.caughtUp",
|
||||
"renderMd ? renderMd(next.text||'')",
|
||||
"stream-fade-active",
|
||||
"_wrapStreamingFadeWords(assistantBody)",
|
||||
@@ -210,6 +213,7 @@ def test_fade_animation_state_survives_markdown_rerenders():
|
||||
"ageMs",
|
||||
"animationDelay",
|
||||
"--stream-fade-ms",
|
||||
"span.className='stream-fade-word is-new'",
|
||||
"_streamFadeLatestAnimationEndAt",
|
||||
"_streamFadeWordBornAt.length=wordIndex+1",
|
||||
],
|
||||
@@ -244,13 +248,15 @@ def test_fade_css_animates_words_and_hides_live_cursor():
|
||||
[
|
||||
"@keyframes stream-fade-word-in",
|
||||
".stream-fade-word.is-new",
|
||||
"var(--stream-fade-ms,140ms) ease-out",
|
||||
"var(--stream-fade-ms,140ms) cubic-bezier(.2,.7,.2,1)",
|
||||
"prefers-reduced-motion: reduce",
|
||||
".msg-body.stream-fade-active > :last-child::after",
|
||||
"display:none",
|
||||
"content:none",
|
||||
],
|
||||
)
|
||||
assert "prefers-reduced-motion: reduce" not in fade_css
|
||||
assert ".stream-fade-active .stream-fade-word{display:inline;}" in fade_css
|
||||
assert ".stream-fade-active .stream-fade-word{display:inline;will-change:opacity;}" not in fade_css
|
||||
|
||||
|
||||
def test_stream_fade_next_text_executes_and_advances_playout():
|
||||
|
||||
Reference in new issue
Block a user