Fix auto-parsing of continue from reasoning Continues #3606
| @@ -3287,7 +3287,7 @@ class StreamingProcessor { | ||
| 3287 | 3287 | chat[messageId]['extra']['time_to_first_token'] = this.timeToFirstToken; |
| 3288 | 3288 | |
| 3289 | 3289 | // Update reasoning |
| 3290 | 3290 | await this.reasoningHandler.process(messageId, mesChanged, this.promptReasoning); |
| 3291 | 3291 | processedText = chat[messageId]['mes']; |
| 3292 | 3292 | |
| 3293 | 3293 | // Token count update. |
| @@ -5953,7 +5953,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc | ||
| 5953 | 5953 | getMessage = trimToEndSentence(getMessage); |
| 5954 | 5954 | } |
| 5955 | 5955 | |
| 5956 | 5956 | if (power_user.trim_spaces && !PromptReasoning.getLatestPrefix()) { |
| 5957 | 5957 | getMessage = getMessage.trim(); |
| 5958 | 5958 | } |
| 5959 | 5959 | |
| @@ -6147,13 +6147,17 @@ export function syncMesToSwipe(messageId = null) { | ||
| 6147 | 6147 | } |
| 6148 | 6148 | |
| 6149 | 6149 | const targetMessageId = messageId ?? chat.length - 1; |
| 6150 | 6150 | if (chat.lengthtargetMessageId >= targetMessageIdchat.length || targetMessageId < 0) { |
| 6151 | 6151 | console.warn(`[syncMesToSwipe] Invalid message ID: ${messageId}`); |
| 6152 | 6152 | return false; |
| 6153 | 6153 | } |
| 6154 | 6154 | |
| 6155 | 6155 | const targetMessage = chat[targetMessageId]; |
| 6156 | 6156 | |
| 6157 | + if (!targetMessage) { | |
| 6158 | + return false; | |
| 6159 | + } | |
| 6160 | + | |
| 6157 | 6161 | // No swipe data there yet, exit out |
| 6158 | 6162 | if (typeof targetMessage.swipe_id !== 'number') { |
| 6159 | 6163 | return false; |
| @@ -196,7 +196,7 @@ export class ReasoningHandler { | ||
| 196 | 196 | */ |
| 197 | 197 | initContinue(promptReasoning) { |
| 198 | 198 | this.reasoning = promptReasoning.prefixReasoning; |
| 199 | 199 | this.state = promptReasoning.prefixIncomplete ? ReasoningState.None : ReasoningState.Done; |
| 200 | 200 | this.startTime = this.initialTime; |
| 201 | 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 | 325 | * @param {number} messageId - The ID of the message to process |
| 326 | 326 | * @param {boolean} mesChanged - Whether the message has changed |
| 327 | + * @param {PromptReasoning} promptReasoning - Prompt reasoning object | |
| 327 | 328 | * @returns {Promise<void>} |
| 328 | 329 | */ |
| 329 | 330 | async process(messageId, mesChanged, promptReasoning) { |
| 330 | 331 | mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged, promptReasoning); |
| 331 | 332 | |
| 332 | 333 | if (!this.reasoning && !this.#isHiddenReasoningModel) |
| 333 | 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 | 357 | if (!power_user.reasoning.auto_parse) |
| 350 | 358 | return; |
| 351 | 359 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) |
| @@ -355,15 +363,17 @@ export class ReasoningHandler { | ||
| 355 | 363 | const message = chat[messageId]; |
| 356 | 364 | if (!message) return mesChanged; |
| 357 | 365 | |
| 366 | + const parseTarget = promptReasoning?.prefixIncomplete ? (promptReasoning.prefixReasoningFormatted + message.mes) : message.mes; | |
| 367 | + | |
| 358 | 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 | 369 | if (this.#parsingReasoningMesStartIndex) { |
| 360 | 370 | message.mes = trimSpaces(message.mesparseTarget.slice(this.#parsingReasoningMesStartIndex)); |
| 361 | 371 | return mesChanged; |
| 362 | 372 | } |
| 363 | 373 | |
| 364 | 374 | if (this.state === ReasoningState.None || this.#isHiddenReasoningModel) { |
| 365 | 375 | // If streamed message starts with the opening, cut it out and put all inside reasoning |
| 366 | 376 | if (message.mesparseTarget.startsWith(power_user.reasoning.prefix) && message.mesparseTarget.length > power_user.reasoning.prefix.length) { |
| 367 | 377 | this.#isParsingReasoning = true; |
| 368 | 378 | |
| 369 | 379 | // Manually set starting state here, as we might already have received the ending suffix |
| @@ -377,15 +387,14 @@ export class ReasoningHandler { | ||
| 377 | 387 | return mesChanged; |
| 378 | 388 | |
| 379 | 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 | 391 | message.mes = ''; |
| 383 | 392 | |
| 384 | 393 | // If the reasoning contains the ending suffix, we cut that off and continue as message streaming |
| 385 | 394 | if (this.reasoning.includes(power_user.reasoning.suffix)) { |
| 386 | 395 | this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix)); |
| 387 | 396 | this.#parsingReasoningMesStartIndex = originalMesparseTarget.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length; |
| 388 | 397 | message.mes = trimSpaces(originalMesparseTarget.slice(this.#parsingReasoningMesStartIndex)); |
| 389 | 398 | this.#isParsingReasoning = false; |
| 390 | 399 | } |
| 391 | 400 | |
| @@ -525,13 +534,56 @@ export class ReasoningHandler { | ||
| 525 | 534 | * Keeps track of the number of reasoning additions. |
| 526 | 535 | */ |
| 527 | 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 | 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 | 572 | constructor() { |
| 573 | + PromptReasoning.#LATEST = this; | |
| 574 | + | |
| 575 | + /** @type {number} */ | |
| 531 | 576 | this.counter = 0; |
| 577 | + /** @type {number} */ | |
| 532 | 578 | this.prefixLength = -1; |
| 579 | + /** @type {string} */ | |
| 533 | 580 | this.prefixReasoning = ''; |
| 581 | + /** @type {string} */ | |
| 582 | + this.prefixReasoningFormatted = ''; | |
| 583 | + /** @type {number?} */ | |
| 534 | 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 | 630 | const formattedReasoning = `${prefix}${reasoning}`; |
| 579 | 631 | if (isPrefix) { |
| 580 | 632 | this.prefixReasoning = reasoning; |
| 633 | + this.prefixReasoningFormatted = formattedReasoning; | |
| 581 | 634 | this.prefixLength = formattedReasoning.length; |
| 582 | 635 | this.prefixDuration = duration; |
| 636 | + this.prefixIncomplete = true; | |
| 583 | 637 | } |
| 584 | 638 | return formattedReasoning; |
| 585 | 639 | } |
| @@ -588,8 +642,10 @@ export class PromptReasoning { | ||
| 588 | 642 | const formattedReasoning = `${prefix}${reasoning}${suffix}${separator}`; |
| 589 | 643 | if (isPrefix) { |
| 590 | 644 | this.prefixReasoning = reasoning; |
| 645 | + this.prefixReasoningFormatted = formattedReasoning; | |
| 591 | 646 | this.prefixLength = formattedReasoning.length; |
| 592 | 647 | this.prefixDuration = duration; |
| 648 | + this.prefixIncomplete = false; | |
| 593 | 649 | } |
| 594 | 650 | return `${formattedReasoning}${content}`; |
| 595 | 651 | } |
| @@ -1049,12 +1105,13 @@ function parseReasoningFromString(str, { strict = true } = {}) { | ||
| 1049 | 1105 | } |
| 1050 | 1106 | |
| 1051 | 1107 | function registerReasoningAppEvents() { |
| 1052 | 1108 | const eventHandler = (/** @type {string} */ type, /** @type {number} */ idx) => { |
| 1053 | 1109 | if (!power_user.reasoning.auto_parse) { |
| 1054 | 1110 | return; |
| 1055 | 1111 | } |
| 1056 | 1112 | |
| 1057 | 1113 | console.debug('[Reasoning] Auto-parsing reasoning block for message', idx); |
| 1114 | + const prefix = type === event_types.MESSAGE_RECEIVED ? PromptReasoning.getLatestPrefix() : ''; | |
| 1058 | 1115 | const message = chat[idx]; |
| 1059 | 1116 | |
| 1060 | 1117 | if (!message) { |
| @@ -1067,12 +1124,12 @@ function registerReasoningAppEvents() { | ||
| 1067 | 1124 | return null; |
| 1068 | 1125 | } |
| 1069 | 1126 | |
| 1070 | 1127 | if (message.extra?.reasoning && !prefix) { |
| 1071 | 1128 | console.debug('[Reasoning] Message already has reasoning', idx); |
| 1072 | 1129 | return null; |
| 1073 | 1130 | } |
| 1074 | 1131 | |
| 1075 | 1132 | const parsedReasoning = parseReasoningFromString(prefix + message.mes); |
| 1076 | 1133 | |
| 1077 | 1134 | // No reasoning block found |
| 1078 | 1135 | if (!parsedReasoning) { |
| @@ -1111,7 +1168,11 @@ function registerReasoningAppEvents() { | ||
| 1111 | 1168 | }; |
| 1112 | 1169 | |
| 1113 | 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 | |