Fix auto-parsing of continue from reasoning Continues #3606
| @@ -3287,7 +3287,7 @@ class StreamingProcessor { | |||
| 3287 | chat[messageId]['extra']['time_to_first_token'] = this.timeToFirstToken; | 3287 | chat[messageId]['extra']['time_to_first_token'] = this.timeToFirstToken; |
| 3288 | 3288 | ||
| 3289 | // Update reasoning | 3289 | // Update reasoning |
| 3290 | await this.reasoningHandler.process(messageId, mesChanged); | 3290 | await this.reasoningHandler.process(messageId, mesChanged, this.promptReasoning); |
| 3291 | processedText = chat[messageId]['mes']; | 3291 | processedText = chat[messageId]['mes']; |
| 3292 | 3292 | ||
| 3293 | // Token count update. | 3293 | // Token count update. |
| @@ -5953,7 +5953,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc | |||
| 5953 | getMessage = trimToEndSentence(getMessage); | 5953 | getMessage = trimToEndSentence(getMessage); |
| 5954 | } | 5954 | } |
| 5955 | 5955 | ||
| 5956 | if (power_user.trim_spaces) { | 5956 | if (power_user.trim_spaces && !PromptReasoning.getLatestPrefix()) { |
| 5957 | getMessage = getMessage.trim(); | 5957 | getMessage = getMessage.trim(); |
| 5958 | } | 5958 | } |
| 5959 | 5959 | ||
| @@ -6147,13 +6147,17 @@ export function syncMesToSwipe(messageId = null) { | |||
| 6147 | } | 6147 | } |
| 6148 | 6148 | ||
| 6149 | const targetMessageId = messageId ?? chat.length - 1; | 6149 | const targetMessageId = messageId ?? chat.length - 1; |
| 6150 | if (chat.length > targetMessageId || targetMessageId < 0) { | 6150 | if (targetMessageId >= chat.length || targetMessageId < 0) { |
| 6151 | console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`); | 6151 | console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`); |
| 6152 | return false; | 6152 | return false; |
| 6153 | } | 6153 | } |
| 6154 | 6154 | ||
| 6155 | const targetMessage = chat[targetMessageId]; | 6155 | const targetMessage = chat[targetMessageId]; |
| 6156 | 6156 | ||
| 6157 | if (!targetMessage) { | ||
| 6158 | return false; | ||
| 6159 | } | ||
| 6160 | |||
| 6157 | // No swipe data there yet, exit out | 6161 | // No swipe data there yet, exit out |
| 6158 | if (typeof targetMessage.swipe_id !== 'number') { | 6162 | if (typeof targetMessage.swipe_id !== 'number') { |
| 6159 | return false; | 6163 | return false; |
| @@ -196,7 +196,7 @@ export class ReasoningHandler { | |||
| 196 | */ | 196 | */ |
| 197 | initContinue(promptReasoning) { | 197 | initContinue(promptReasoning) { |
| 198 | this.reasoning = promptReasoning.prefixReasoning; | 198 | this.reasoning = promptReasoning.prefixReasoning; |
| 199 | this.state = ReasoningState.Done; | 199 | this.state = promptReasoning.prefixIncomplete ? ReasoningState.None : ReasoningState.Done; |
| 200 | this.startTime = this.initialTime; | 200 | this.startTime = this.initialTime; |
| 201 | this.endTime = promptReasoning.prefixDuration ? new Date(this.initialTime.getTime() + promptReasoning.prefixDuration) : null; | 201 | this.endTime = promptReasoning.prefixDuration ? new Date(this.initialTime.getTime() + promptReasoning.prefixDuration) : null; |
| 202 | } | 202 | } |
| @@ -324,10 +324,11 @@ export class ReasoningHandler { | |||
| 324 | * | 324 | * |
| 325 | * @param {number} messageId - The ID of the message to process | 325 | * @param {number} messageId - The ID of the message to process |
| 326 | * @param {boolean} mesChanged - Whether the message has changed | 326 | * @param {boolean} mesChanged - Whether the message has changed |
| 327 | * @param {PromptReasoning} promptReasoning - Prompt reasoning object | ||
| 327 | * @returns {Promise<void>} | 328 | * @returns {Promise<void>} |
| 328 | */ | 329 | */ |
| 329 | async process(messageId, mesChanged) { | 330 | async process(messageId, mesChanged, promptReasoning) { |
| 330 | mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged); | 331 | mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged, promptReasoning); |
| 331 | 332 | ||
| 332 | if (!this.reasoning && !this.#isHiddenReasoningModel) | 333 | if (!this.reasoning && !this.#isHiddenReasoningModel) |
| 333 | return; | 334 | return; |
| @@ -345,7 +346,14 @@ export class ReasoningHandler { | |||
| 345 | } | 346 | } |
| 346 | } | 347 | } |
| 347 | 348 | ||
| 348 | #autoParseReasoningFromMessage(messageId, mesChanged) { | 349 | /** |
| 350 | * Parse reasoning from a message during streaming. | ||
| 351 | * @param {number} messageId Message ID | ||
| 352 | * @param {boolean} mesChanged Whether the message has changed before reasoning parsing | ||
| 353 | * @param {PromptReasoning} promptReasoning Prompt reasoning object | ||
| 354 | * @returns {boolean} Whether the message has changed after reasoning parsing | ||
| 355 | */ | ||
| 356 | #autoParseReasoningFromMessage(messageId, mesChanged, promptReasoning) { | ||
| 349 | if (!power_user.reasoning.auto_parse) | 357 | if (!power_user.reasoning.auto_parse) |
| 350 | return; | 358 | return; |
| 351 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) | 359 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) |
| @@ -355,15 +363,17 @@ export class ReasoningHandler { | |||
| 355 | const message = chat[messageId]; | 363 | const message = chat[messageId]; |
| 356 | if (!message) return mesChanged; | 364 | if (!message) return mesChanged; |
| 357 | 365 | ||
| 366 | const parseTarget = promptReasoning?.prefixIncomplete ? (promptReasoning.prefixReasoningFormatted + message.mes) : message.mes; | ||
| 367 | |||
| 358 | // If we are done with reasoning parse, we just split the message correctly so the reasoning doesn't show up inside of it. | 368 | // If we are done with reasoning parse, we just split the message correctly so the reasoning doesn't show up inside of it. |
| 359 | if (this.#parsingReasoningMesStartIndex) { | 369 | if (this.#parsingReasoningMesStartIndex) { |
| 360 | message.mes = trimSpaces(message.mes.slice(this.#parsingReasoningMesStartIndex)); | 370 | message.mes = trimSpaces(parseTarget.slice(this.#parsingReasoningMesStartIndex)); |
| 361 | return mesChanged; | 371 | return mesChanged; |
| 362 | } | 372 | } |
| 363 | 373 | ||
| 364 | if (this.state === ReasoningState.None || this.#isHiddenReasoningModel) { | 374 | if (this.state === ReasoningState.None || this.#isHiddenReasoningModel) { |
| 365 | // If streamed message starts with the opening, cut it out and put all inside reasoning | 375 | // If streamed message starts with the opening, cut it out and put all inside reasoning |
| 366 | if (message.mes.startsWith(power_user.reasoning.prefix) && message.mes.length > power_user.reasoning.prefix.length) { | 376 | if (parseTarget.startsWith(power_user.reasoning.prefix) && parseTarget.length > power_user.reasoning.prefix.length) { |
| 367 | this.#isParsingReasoning = true; | 377 | this.#isParsingReasoning = true; |
| 368 | 378 | ||
| 369 | // Manually set starting state here, as we might already have received the ending suffix | 379 | // Manually set starting state here, as we might already have received the ending suffix |
| @@ -377,15 +387,14 @@ export class ReasoningHandler { | |||
| 377 | return mesChanged; | 387 | return mesChanged; |
| 378 | 388 | ||
| 379 | // If we are in manual parsing mode, all currently streaming mes tokens will go the the reasoning block | 389 | // If we are in manual parsing mode, all currently streaming mes tokens will go the the reasoning block |
| 380 | const originalMes = message.mes; | 390 | this.reasoning = parseTarget.slice(power_user.reasoning.prefix.length); |
| 381 | this.reasoning = originalMes.slice(power_user.reasoning.prefix.length); | ||
| 382 | message.mes = ''; | 391 | message.mes = ''; |
| 383 | 392 | ||
| 384 | // If the reasoning contains the ending suffix, we cut that off and continue as message streaming | 393 | // If the reasoning contains the ending suffix, we cut that off and continue as message streaming |
| 385 | if (this.reasoning.includes(power_user.reasoning.suffix)) { | 394 | if (this.reasoning.includes(power_user.reasoning.suffix)) { |
| 386 | this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix)); | 395 | this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix)); |
| 387 | this.#parsingReasoningMesStartIndex = originalMes.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length; | 396 | this.#parsingReasoningMesStartIndex = parseTarget.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length; |
| 388 | message.mes = trimSpaces(originalMes.slice(this.#parsingReasoningMesStartIndex)); | 397 | message.mes = trimSpaces(parseTarget.slice(this.#parsingReasoningMesStartIndex)); |
| 389 | this.#isParsingReasoning = false; | 398 | this.#isParsingReasoning = false; |
| 390 | } | 399 | } |
| 391 | 400 | ||
| @@ -525,13 +534,56 @@ export class ReasoningHandler { | |||
| 525 | * Keeps track of the number of reasoning additions. | 534 | * Keeps track of the number of reasoning additions. |
| 526 | */ | 535 | */ |
| 527 | export class PromptReasoning { | 536 | export class PromptReasoning { |
| 537 | /** | ||
| 538 | * An instance initiated during the latest prompt processing. | ||
| 539 | * @type {PromptReasoning} | ||
| 540 | * */ | ||
| 541 | static #LATEST = null; | ||
| 542 | /** | ||
| 543 | * @readonly Zero-width space character used as a placeholder for reasoning. | ||
| 544 | * @type {string} | ||
| 545 | */ | ||
| 528 | static REASONING_PLACEHOLDER = '\u200B'; | 546 | static REASONING_PLACEHOLDER = '\u200B'; |
| 529 | 547 | ||
| 548 | /** | ||
| 549 | * Returns the latest formatted reasoning prefix if the prefix is incomplete. | ||
| 550 | * @returns {string} Formatted reasoning prefix | ||
| 551 | */ | ||
| 552 | static getLatestPrefix() { | ||
| 553 | if (!PromptReasoning.#LATEST) { | ||
| 554 | return ''; | ||
| 555 | } | ||
| 556 | |||
| 557 | if (!PromptReasoning.#LATEST.prefixIncomplete) { | ||
| 558 | return ''; | ||
| 559 | } | ||
| 560 | |||
| 561 | return PromptReasoning.#LATEST.prefixReasoningFormatted; | ||
| 562 | } | ||
| 563 | |||
| 564 | /** | ||
| 565 | * Free the latest reasoning instance. | ||
| 566 | * To be called when the generation has ended or stopped. | ||
| 567 | */ | ||
| 568 | static clearLatest() { | ||
| 569 | PromptReasoning.#LATEST = null; | ||
| 570 | } | ||
| 571 | |||
| 530 | constructor() { | 572 | constructor() { |
| 573 | PromptReasoning.#LATEST = this; | ||
| 574 | |||
| 575 | /** @type {number} */ | ||
| 531 | this.counter = 0; | 576 | this.counter = 0; |
| 577 | /** @type {number} */ | ||
| 532 | this.prefixLength = -1; | 578 | this.prefixLength = -1; |
| 579 | /** @type {string} */ | ||
| 533 | this.prefixReasoning = ''; | 580 | this.prefixReasoning = ''; |
| 581 | /** @type {string} */ | ||
| 582 | this.prefixReasoningFormatted = ''; | ||
| 583 | /** @type {number?} */ | ||
| 534 | this.prefixDuration = null; | 584 | this.prefixDuration = null; |
| 585 | /** @type {boolean} */ | ||
| 586 | this.prefixIncomplete = false; | ||
| 535 | } | 587 | } |
| 536 | 588 | ||
| 537 | /** | 589 | /** |
| @@ -578,8 +630,10 @@ export class PromptReasoning { | |||
| 578 | const formattedReasoning = `${prefix}${reasoning}`; | 630 | const formattedReasoning = `${prefix}${reasoning}`; |
| 579 | if (isPrefix) { | 631 | if (isPrefix) { |
| 580 | this.prefixReasoning = reasoning; | 632 | this.prefixReasoning = reasoning; |
| 633 | this.prefixReasoningFormatted = formattedReasoning; | ||
| 581 | this.prefixLength = formattedReasoning.length; | 634 | this.prefixLength = formattedReasoning.length; |
| 582 | this.prefixDuration = duration; | 635 | this.prefixDuration = duration; |
| 636 | this.prefixIncomplete = true; | ||
| 583 | } | 637 | } |
| 584 | return formattedReasoning; | 638 | return formattedReasoning; |
| 585 | } | 639 | } |
| @@ -588,8 +642,10 @@ export class PromptReasoning { | |||
| 588 | const formattedReasoning = `${prefix}${reasoning}${suffix}${separator}`; | 642 | const formattedReasoning = `${prefix}${reasoning}${suffix}${separator}`; |
| 589 | if (isPrefix) { | 643 | if (isPrefix) { |
| 590 | this.prefixReasoning = reasoning; | 644 | this.prefixReasoning = reasoning; |
| 645 | this.prefixReasoningFormatted = formattedReasoning; | ||
| 591 | this.prefixLength = formattedReasoning.length; | 646 | this.prefixLength = formattedReasoning.length; |
| 592 | this.prefixDuration = duration; | 647 | this.prefixDuration = duration; |
| 648 | this.prefixIncomplete = false; | ||
| 593 | } | 649 | } |
| 594 | return `${formattedReasoning}${content}`; | 650 | return `${formattedReasoning}${content}`; |
| 595 | } | 651 | } |
| @@ -1049,12 +1105,13 @@ function parseReasoningFromString(str, { strict = true } = {}) { | |||
| 1049 | } | 1105 | } |
| 1050 | 1106 | ||
| 1051 | function registerReasoningAppEvents() { | 1107 | function registerReasoningAppEvents() { |
| 1052 | const eventHandler = (/** @type {number} */ idx) => { | 1108 | const eventHandler = (/** @type {string} */ type, /** @type {number} */ idx) => { |
| 1053 | if (!power_user.reasoning.auto_parse) { | 1109 | if (!power_user.reasoning.auto_parse) { |
| 1054 | return; | 1110 | return; |
| 1055 | } | 1111 | } |
| 1056 | 1112 | ||
| 1057 | console.debug('[Reasoning] Auto-parsing reasoning block for message', idx); | 1113 | console.debug('[Reasoning] Auto-parsing reasoning block for message', idx); |
| 1114 | const prefix = type === event_types.MESSAGE_RECEIVED ? PromptReasoning.getLatestPrefix() : ''; | ||
| 1058 | const message = chat[idx]; | 1115 | const message = chat[idx]; |
| 1059 | 1116 | ||
| 1060 | if (!message) { | 1117 | if (!message) { |
| @@ -1067,12 +1124,12 @@ function registerReasoningAppEvents() { | |||
| 1067 | return null; | 1124 | return null; |
| 1068 | } | 1125 | } |
| 1069 | 1126 | ||
| 1070 | if (message.extra?.reasoning) { | 1127 | if (message.extra?.reasoning && !prefix) { |
| 1071 | console.debug('[Reasoning] Message already has reasoning', idx); | 1128 | console.debug('[Reasoning] Message already has reasoning', idx); |
| 1072 | return null; | 1129 | return null; |
| 1073 | } | 1130 | } |
| 1074 | 1131 | ||
| 1075 | const parsedReasoning = parseReasoningFromString(message.mes); | 1132 | const parsedReasoning = parseReasoningFromString(prefix + message.mes); |
| 1076 | 1133 | ||
| 1077 | // No reasoning block found | 1134 | // No reasoning block found |
| 1078 | if (!parsedReasoning) { | 1135 | if (!parsedReasoning) { |
| @@ -1111,7 +1168,11 @@ function registerReasoningAppEvents() { | |||
| 1111 | }; | 1168 | }; |
| 1112 | 1169 | ||
| 1113 | for (const event of [event_types.MESSAGE_RECEIVED, event_types.MESSAGE_UPDATED]) { | 1170 | for (const event of [event_types.MESSAGE_RECEIVED, event_types.MESSAGE_UPDATED]) { |
| 1114 | eventSource.on(event, eventHandler); | 1171 | eventSource.on(event, (/** @type {number} */ idx) => eventHandler(event, idx)); |
| 1172 | } | ||
| 1173 | |||
| 1174 | for (const event of [event_types.GENERATION_STOPPED, event_types.GENERATION_ENDED, event_types.CHAT_CHANGED]) { | ||
| 1175 | eventSource.on(event, () => PromptReasoning.clearLatest()); | ||
| 1115 | } | 1176 | } |
| 1116 | } | 1177 | } |
| 1117 | 1178 | ||