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>
Signed| @@ -477,14 +477,11 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint { | ||
| 477 | 477 | display: none !important; |
| 478 | 478 | } |
| 479 | 479 | |
| 480 | 480 | #smooth_streaming_control:has(#smooth_streaming:not(:checked))~#smooth_streaming_speed_control {, |
| 481 | +#smooth_streaming_control:has(#smooth_streaming:not(:checked))~#smooth_streaming_no_think_control { | |
| 481 | 482 | display: none; |
| 482 | 483 | } |
| 483 | 484 | |
| 484 | -#smooth_streaming:checked~#smooth_streaming_speed_control { | |
| 485 | - display: block; | |
| 486 | -} | |
| 487 | - | |
| 488 | 485 | .mdhotkey_icon { |
| 489 | 486 | opacity: 0.6; |
| 490 | 487 | } |
| @@ -5029,14 +5029,21 @@ | ||
| 5029 | 5029 | <small data-i18n="Clean-Up">Clean-Up</small> |
| 5030 | 5030 | </div> |
| 5031 | 5031 | </div> |
| 5032 | 5032 | <label id="smooth_streaming_control" class="checkbox_label flexWrap" for="smooth_streaming"> |
| 5033 | 5033 | <input id="smooth_streaming" type="checkbox" /> |
| 5034 | 5034 | <div class="flex-container alignItemsBaseline"> |
| 5035 | 5035 | <small data-i18n="Smooth Streaming"> |
| 5036 | 5036 | Smooth Streaming |
| 5037 | 5037 | </small> |
| 5038 | 5038 | </div> |
| 5039 | - <div id="smooth_streaming_speed_control" class="flexBasis100p wide100p"> | |
| 5039 | + </label> | |
| 5040 | + <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."> | |
| 5041 | + <input id="smooth_streaming_no_think" type="checkbox" /> | |
| 5042 | + <small data-i18n="Exclude 'Thinking...'"> | |
| 5043 | + Exclude 'Thinking...' | |
| 5044 | + </small> | |
| 5045 | + </label> | |
| 5046 | + <div id="smooth_streaming_speed_control" class="wide100p"> | |
| 5040 | 5047 | <input type="range" id="smooth_streaming_speed" name="smooth_streaming_speed" min="0" max="100" step="10" value="50"> |
| 5041 | 5048 | <div class="slider_hint"> |
| 5042 | 5049 | <span data-i18n="Slow">Slow</span> |
| @@ -5044,7 +5051,6 @@ | ||
| 5044 | 5051 | <span data-i18n="Fast">Fast</span> |
| 5045 | 5052 | </div> |
| 5046 | 5053 | </div> |
| 5047 | - </label> | |
| 5048 | 5054 | <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"> |
| 5049 | 5055 | <input id="stream_fade_in" type="checkbox" /> |
| 5050 | 5056 | <small data-i18n="Stream Fade-In">Stream Fade-In</small> |
| @@ -139,6 +139,7 @@ export const power_user = { | ||
| 139 | 139 | chat_truncation: 100, |
| 140 | 140 | streaming_fps: 30, |
| 141 | 141 | smooth_streaming: false, |
| 142 | + smooth_streaming_no_think: false, | |
| 142 | 143 | smooth_streaming_speed: 50, |
| 143 | 144 | stream_fade_in: false, |
| 144 | 145 | |
| @@ -1745,6 +1746,7 @@ export async function loadPowerUserSettings(settings, data) { | ||
| 1745 | 1746 | $('#streaming_fps_counter').val(power_user.streaming_fps); |
| 1746 | 1747 | |
| 1747 | 1748 | $('#smooth_streaming').prop('checked', power_user.smooth_streaming); |
| 1749 | + $('#smooth_streaming_no_think').prop('checked', power_user.smooth_streaming_no_think); | |
| 1748 | 1750 | $('#smooth_streaming_speed').val(power_user.smooth_streaming_speed); |
| 1749 | 1751 | |
| 1750 | 1752 | $('#stream_fade_in').prop('checked', power_user.stream_fade_in); |
| @@ -3550,6 +3552,11 @@ jQuery(() => { | ||
| 3550 | 3552 | saveSettingsDebounced(); |
| 3551 | 3553 | }); |
| 3552 | 3554 | |
| 3555 | + $('#smooth_streaming_no_think').on('input', function () { | |
| 3556 | + power_user.smooth_streaming_no_think = !!$(this).prop('checked'); | |
| 3557 | + saveSettingsDebounced(); | |
| 3558 | + }); | |
| 3559 | + | |
| 3553 | 3560 | $('#smooth_streaming_speed').on('input', function () { |
| 3554 | 3561 | power_user.smooth_streaming_speed = Number($('#smooth_streaming_speed').val()); |
| 3555 | 3562 | saveSettingsDebounced(); |
| @@ -105,7 +105,7 @@ function getDelay(s) { | ||
| 105 | 105 | /** |
| 106 | 106 | * Parses the stream data and returns the parsed data and the chunk to be sent. |
| 107 | 107 | * @param {object} json The JSON data. |
| 108 | 108 | * @returns {AsyncGenerator<{data: object, chunk: string, reasoning?: boolean}>} The parsed data and the chunk to be sent. |
| 109 | 109 | */ |
| 110 | 110 | async function* parseStreamData(json) { |
| 111 | 111 | // Cohere |
| @@ -133,6 +133,19 @@ async function* parseStreamData(json) { | ||
| 133 | 133 | } |
| 134 | 134 | return; |
| 135 | 135 | } |
| 136 | + else if (typeof json.delta === 'object' && typeof json.delta.thinking === 'string') { | |
| 137 | + if (json.delta.thinking.length > 0) { | |
| 138 | + for (let i = 0; i < json.delta.thinking.length; i++) { | |
| 139 | + const str = json.delta.thinking[i]; | |
| 140 | + yield { | |
| 141 | + data: { ...json, delta: { thinking: str } }, | |
| 142 | + chunk: str, | |
| 143 | + reasoning: true, | |
| 144 | + }; | |
| 145 | + } | |
| 146 | + } | |
| 147 | + return; | |
| 148 | + } | |
| 136 | 149 | // MakerSuite |
| 137 | 150 | else if (Array.isArray(json.candidates)) { |
| 138 | 151 | for (let i = 0; i < json.candidates.length; i++) { |
| @@ -159,9 +172,11 @@ async function* parseStreamData(json) { | ||
| 159 | 172 | candidateClone.content.parts[j].text = str; |
| 160 | 173 | candidateClone.content.parts = [candidateClone.content.parts[j]]; |
| 161 | 174 | const candidates = [candidateClone]; |
| 175 | + const reasoning = json.candidates[i].content.parts[j].thought ?? false; | |
| 162 | 176 | yield { |
| 163 | 177 | data: { ...json, candidates }, |
| 164 | 178 | chunk: str, |
| 179 | + reasoning, | |
| 165 | 180 | }; |
| 166 | 181 | } |
| 167 | 182 | } |
| @@ -237,6 +252,7 @@ async function* parseStreamData(json) { | ||
| 237 | 252 | yield { |
| 238 | 253 | data: { ...json, choices }, |
| 239 | 254 | chunk: str, |
| 255 | + reasoning: true, | |
| 240 | 256 | }; |
| 241 | 257 | } |
| 242 | 258 | return; |
| @@ -252,6 +268,7 @@ async function* parseStreamData(json) { | ||
| 252 | 268 | yield { |
| 253 | 269 | data: { ...json, choices }, |
| 254 | 270 | chunk: str, |
| 271 | + reasoning: true, | |
| 255 | 272 | }; |
| 256 | 273 | } |
| 257 | 274 | return; |
| @@ -269,6 +286,24 @@ async function* parseStreamData(json) { | ||
| 269 | 286 | } |
| 270 | 287 | return; |
| 271 | 288 | } |
| 289 | + else if (Array.isArray(json.choices[0].delta.content) && json.choices[0].delta.content.length > 0) { | |
| 290 | + if (Array.isArray(json.choices[0].delta.content[0].thinking) && json.choices[0].delta.content[0].thinking.length > 0) { | |
| 291 | + if (typeof json.choices[0].delta.content[0].thinking[0].text === 'string' && json.choices[0].delta.content[0].thinking[0].text.length > 0) { | |
| 292 | + for (let j = 0; j < json.choices[0].delta.content[0].thinking[0].text.length; j++) { | |
| 293 | + const str = json.choices[0].delta.content[0].thinking[0].text[j]; | |
| 294 | + const choiceClone = structuredClone(json.choices[0]); | |
| 295 | + choiceClone.delta.content[0].thinking[0].text = str; | |
| 296 | + const choices = [choiceClone]; | |
| 297 | + yield { | |
| 298 | + data: { ...json, choices }, | |
| 299 | + chunk: str, | |
| 300 | + reasoning: true, | |
| 301 | + }; | |
| 302 | + } | |
| 303 | + return; | |
| 304 | + } | |
| 305 | + } | |
| 306 | + } | |
| 272 | 307 | } |
| 273 | 308 | else if (typeof json.choices[0].message === 'object') { |
| 274 | 309 | if (typeof json.choices[0].message.content === 'string' && json.choices[0].message.content.length > 0) { |
| @@ -317,7 +352,7 @@ export class SmoothEventSourceStream extends EventSourceStream { | ||
| 317 | 352 | } |
| 318 | 353 | |
| 319 | 354 | for await (const parsed of parseStreamData(json)) { |
| 320 | 355 | !(power_user.smooth_streaming_no_think && parsed.reasoning) && hasFocus && await delay(getDelay(lastStr)); |
| 321 | 356 | controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) })); |
| 322 | 357 | lastStr = parsed.chunk; |
| 323 | 358 | } |