Feat: Add toggle to exclude think/reason blocks from smooth streaming (#4849)

* Feat: Add toggle to exclude think/reason blocks from smooth streaming

* Fix: Adjust new setting name to properly reflect what it does

* Sync data-i18n with text content

* Add reasoning stream flags for Mistral, Claude and Google models

* Update layout

* Fix: Enhance reasoning handling in parseStreamData function

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
Kristian Schlikow
2025-12-02 23:20:23 +01:00
committed by GitHub
parent f145296a58
commit 6c8eb4d9ac
4 changed files with 61 additions and 16 deletions
+2 -5
View File
@@ -477,14 +477,11 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
display: none !important;
}
#smooth_streaming:not(:checked)~#smooth_streaming_speed_control {
#smooth_streaming_control:has(#smooth_streaming:not(:checked))~#smooth_streaming_speed_control,
#smooth_streaming_control:has(#smooth_streaming:not(:checked))~#smooth_streaming_no_think_control {
display: none;
}
#smooth_streaming:checked~#smooth_streaming_speed_control {
display: block;
}
.mdhotkey_icon {
opacity: 0.6;
}
+15 -9
View File
@@ -5029,22 +5029,28 @@
<small data-i18n="Clean-Up">Clean-Up</small>
</div>
</div>
<label class="checkbox_label flexWrap" for="smooth_streaming">
<label id="smooth_streaming_control" class="checkbox_label" for="smooth_streaming">
<input id="smooth_streaming" type="checkbox" />
<div class="flex-container alignItemsBaseline">
<small data-i18n="Smooth Streaming">
Smooth Streaming
</small>
</div>
<div id="smooth_streaming_speed_control" class="flexBasis100p wide100p">
<input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50">
<div class="slider_hint">
<span data-i18n="Slow">Slow</span>
<span></span>
<span data-i18n="Fast">Fast</span>
</div>
</div>
</label>
<label id="smooth_streaming_no_think_control" class="checkbox_label" for="smooth_streaming_no_think" title="Bypass smooth streaming in reasoning blocks." data-i18n="[title]Bypass smooth streaming in reasoning blocks.">
<input id="smooth_streaming_no_think" type="checkbox" />
<small data-i18n="Exclude 'Thinking...'">
Exclude 'Thinking...'
</small>
</label>
<div id="smooth_streaming_speed_control" class="wide100p">
<input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50">
<div class="slider_hint">
<span data-i18n="Slow">Slow</span>
<span></span>
<span data-i18n="Fast">Fast</span>
</div>
</div>
<label class="checkbox_label" for="stream_fade_in" title="Fade in streamed text when it appears, instead of it just popping in." data-i18n="[title]Fade in streamed text when it appears, instead of it just popping in">
<input id="stream_fade_in" type="checkbox" />
<small data-i18n="Stream Fade-In">Stream Fade-In</small>
+7
View File
@@ -139,6 +139,7 @@ export const power_user = {
chat_truncation: 100,
streaming_fps: 30,
smooth_streaming: false,
smooth_streaming_no_think: false,
smooth_streaming_speed: 50,
stream_fade_in: false,
@@ -1745,6 +1746,7 @@ export async function loadPowerUserSettings(settings, data) {
$('#streaming_fps_counter').val(power_user.streaming_fps);
$('#smooth_streaming').prop('checked', power_user.smooth_streaming);
$('#smooth_streaming_no_think').prop('checked', power_user.smooth_streaming_no_think);
$('#smooth_streaming_speed').val(power_user.smooth_streaming_speed);
$('#stream_fade_in').prop('checked', power_user.stream_fade_in);
@@ -3550,6 +3552,11 @@ jQuery(() => {
saveSettingsDebounced();
});
$('#smooth_streaming_no_think').on('input', function () {
power_user.smooth_streaming_no_think = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#smooth_streaming_speed').on('input', function () {
power_user.smooth_streaming_speed = Number($('#smooth_streaming_speed').val());
saveSettingsDebounced();
+37 -2
View File
@@ -105,7 +105,7 @@ function getDelay(s) {
/**
* Parses the stream data and returns the parsed data and the chunk to be sent.
* @param {object} json The JSON data.
* @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.
* @returns {AsyncGenerator<{data: object, chunk: string, reasoning?: boolean}>} The parsed data and the chunk to be sent.
*/
async function* parseStreamData(json) {
// Cohere
@@ -133,6 +133,19 @@ async function* parseStreamData(json) {
}
return;
}
else if (typeof json.delta === 'object' && typeof json.delta.thinking === 'string') {
if (json.delta.thinking.length > 0) {
for (let i = 0; i < json.delta.thinking.length; i++) {
const str = json.delta.thinking[i];
yield {
data: { ...json, delta: { thinking: str } },
chunk: str,
reasoning: true,
};
}
}
return;
}
// MakerSuite
else if (Array.isArray(json.candidates)) {
for (let i = 0; i < json.candidates.length; i++) {
@@ -159,9 +172,11 @@ async function* parseStreamData(json) {
candidateClone.content.parts[j].text = str;
candidateClone.content.parts = [candidateClone.content.parts[j]];
const candidates = [candidateClone];
const reasoning = json.candidates[i].content.parts[j].thought ?? false;
yield {
data: { ...json, candidates },
chunk: str,
reasoning,
};
}
}
@@ -237,6 +252,7 @@ async function* parseStreamData(json) {
yield {
data: { ...json, choices },
chunk: str,
reasoning: true,
};
}
return;
@@ -252,6 +268,7 @@ async function* parseStreamData(json) {
yield {
data: { ...json, choices },
chunk: str,
reasoning: true,
};
}
return;
@@ -269,6 +286,24 @@ async function* parseStreamData(json) {
}
return;
}
else if (Array.isArray(json.choices[0].delta.content) && json.choices[0].delta.content.length > 0) {
if (Array.isArray(json.choices[0].delta.content[0].thinking) && json.choices[0].delta.content[0].thinking.length > 0) {
if (typeof json.choices[0].delta.content[0].thinking[0].text === 'string' && json.choices[0].delta.content[0].thinking[0].text.length > 0) {
for (let j = 0; j < json.choices[0].delta.content[0].thinking[0].text.length; j++) {
const str = json.choices[0].delta.content[0].thinking[0].text[j];
const choiceClone = structuredClone(json.choices[0]);
choiceClone.delta.content[0].thinking[0].text = str;
const choices = [choiceClone];
yield {
data: { ...json, choices },
chunk: str,
reasoning: true,
};
}
return;
}
}
}
}
else if (typeof json.choices[0].message === 'object') {
if (typeof json.choices[0].message.content === 'string' && json.choices[0].message.content.length > 0) {
@@ -317,7 +352,7 @@ export class SmoothEventSourceStream extends EventSourceStream {
}
for await (const parsed of parseStreamData(json)) {
hasFocus && await delay(getDelay(lastStr));
!(power_user.smooth_streaming_no_think && parsed.reasoning) && hasFocus && await delay(getDelay(lastStr));
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) }));
lastStr = parsed.chunk;
}