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