Merge pull request #3606 from SillyTavern/reasoning-continue-fix Fix continue duplicating reasoning block
Signed| @@ -3135,8 +3135,9 @@ class StreamingProcessor { | ||
| 3135 | 3135 | * @param {boolean} forceName2 If true, force the use of name2 |
| 3136 | 3136 | * @param {Date} timeStarted Date when generation was started |
| 3137 | 3137 | * @param {string} continueMessage Previous message if the type is 'continue' |
| 3138 | + * @param {PromptReasoning} promptReasoning Prompt reasoning instance | |
| 3138 | 3139 | */ |
| 3139 | 3140 | constructor(type, forceName2, timeStarted, continueMessage, promptReasoning) { |
| 3140 | 3141 | this.result = ''; |
| 3141 | 3142 | this.messageId = -1; |
| 3142 | 3143 | /** @type {HTMLElement} */ |
| @@ -3164,6 +3165,8 @@ class StreamingProcessor { | ||
| 3164 | 3165 | this.toolCalls = []; |
| 3165 | 3166 | // Initialize reasoning in its own handler |
| 3166 | 3167 | this.reasoningHandler = new ReasoningHandler(timeStarted); |
| 3168 | + /** @type {PromptReasoning} */ | |
| 3169 | + this.promptReasoning = promptReasoning; | |
| 3167 | 3170 | } |
| 3168 | 3171 | |
| 3169 | 3172 | #checkDomElements(messageId) { |
| @@ -3192,6 +3195,10 @@ class StreamingProcessor { | ||
| 3192 | 3195 | } |
| 3193 | 3196 | |
| 3194 | 3197 | async onStartStreaming(text) { |
| 3198 | + if (this.type === 'continue' && this.promptReasoning.prefixReasoning) { | |
| 3199 | + this.reasoningHandler.initContinue(this.promptReasoning); | |
| 3200 | + } | |
| 3201 | + | |
| 3195 | 3202 | let messageId = -1; |
| 3196 | 3203 | |
| 3197 | 3204 | if (this.type == 'impersonate') { |
| @@ -3882,13 +3889,13 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3882 | 3889 | }; |
| 3883 | 3890 | })); |
| 3884 | 3891 | |
| 3885 | 3892 | const reasoningpromptReasoning = new PromptReasoning(); |
| 3886 | 3893 | for (let i = coreChat.length - 1; i >= 0; i--) { |
| 3887 | 3894 | const depth = coreChat.length - i - 1; |
| 3888 | 3895 | const isPrefix = isContinue && i === coreChat.length - 1; |
| 3889 | 3896 | coreChat[i] = { |
| 3890 | 3897 | ...coreChat[i], |
| 3891 | 3898 | mes: reasoningpromptReasoning.addToMessage( |
| 3892 | 3899 | coreChat[i].mes, |
| 3893 | 3900 | getRegexedString( |
| 3894 | 3901 | String(coreChat[i].extra?.reasoning ?? ''), |
| @@ -3896,9 +3903,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3896 | 3903 | { isPrompt: true, depth: depth }, |
| 3897 | 3904 | ), |
| 3898 | 3905 | isPrefix, |
| 3906 | + coreChat[i].extra?.reasoning_duration, | |
| 3899 | 3907 | ), |
| 3900 | 3908 | }; |
| 3901 | 3909 | if (reasoningpromptReasoning.isLimitReached()) { |
| 3902 | 3910 | break; |
| 3903 | 3911 | } |
| 3904 | 3912 | } |
| @@ -4723,7 +4731,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4723 | 4731 | console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`); |
| 4724 | 4732 | |
| 4725 | 4733 | if (isStreamingEnabled() && type !== 'quiet') { |
| 4726 | - streamingProcessor = new StreamingProcessor(type, force_name2, generation_started, continue_mag); | |
| 4734 | + continue_mag = promptReasoning.removePrefix(continue_mag); | |
| 4735 | + streamingProcessor = new StreamingProcessor(type, force_name2, generation_started, continue_mag, promptReasoning); | |
| 4727 | 4736 | if (isContinue) { |
| 4728 | 4737 | // Save reply does add cycle text to the prompt, so it's not needed here |
| 4729 | 4738 | streamingProcessor.firstMessageText = ''; |
| @@ -4824,6 +4833,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4824 | 4833 | } |
| 4825 | 4834 | |
| 4826 | 4835 | if (isContinue) { |
| 4836 | + continue_mag = promptReasoning.removePrefix(continue_mag); | |
| 4827 | 4837 | getMessage = continue_mag + getMessage; |
| 4828 | 4838 | } |
| 4829 | 4839 | |
| @@ -189,6 +189,17 @@ export class ReasoningHandler { | ||
| 189 | 189 | } |
| 190 | 190 | |
| 191 | 191 | /** |
| 192 | + * Sets the reasoning state when continuing a prompt. | |
| 193 | + * @param {PromptReasoning} promptReasoning Prompt reasoning object | |
| 194 | + */ | |
| 195 | + initContinue(promptReasoning) { | |
| 196 | + this.reasoning = promptReasoning.prefixReasoning; | |
| 197 | + this.state = ReasoningState.Done; | |
| 198 | + this.startTime = this.initialTime; | |
| 199 | + this.endTime = promptReasoning.prefixDuration ? new Date(this.initialTime.getTime() + promptReasoning.prefixDuration) : null; | |
| 200 | + } | |
| 201 | + | |
| 202 | + /** | |
| 192 | 203 | * Initializes the reasoning handler for a specific message. |
| 193 | 204 | * |
| 194 | 205 | * Can be used to update the DOM elements or read other reasoning states. |
| @@ -514,6 +525,9 @@ export class PromptReasoning { | ||
| 514 | 525 | |
| 515 | 526 | constructor() { |
| 516 | 527 | this.counter = 0; |
| 528 | + this.prefixLength = -1; | |
| 529 | + this.prefixReasoning = ''; | |
| 530 | + this.prefixDuration = null; | |
| 517 | 531 | } |
| 518 | 532 | |
| 519 | 533 | /** |
| @@ -533,9 +547,10 @@ export class PromptReasoning { | ||
| 533 | 547 | * @param {string} content Message content |
| 534 | 548 | * @param {string} reasoning Message reasoning |
| 535 | 549 | * @param {boolean} isPrefix Whether this is the last message prefix |
| 550 | + * @param {number?} duration Duration of the reasoning | |
| 536 | 551 | * @returns {string} Message content with reasoning |
| 537 | 552 | */ |
| 538 | 553 | addToMessage(content, reasoning, isPrefix, duration) { |
| 539 | 554 | // Disabled or reached limit of additions |
| 540 | 555 | if (!isPrefix && (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions)) { |
| 541 | 556 | return content; |
| @@ -556,11 +571,35 @@ export class PromptReasoning { | ||
| 556 | 571 | |
| 557 | 572 | // Combine parts with reasoning only |
| 558 | 573 | if (isPrefix && !content) { |
| 559 | 574 | returnconst formattedReasoning = `${prefix}${reasoning}`; |
| 575 | + if (isPrefix) { | |
| 576 | + this.prefixReasoning = reasoning; | |
| 577 | + this.prefixLength = formattedReasoning.length; | |
| 578 | + this.prefixDuration = duration; | |
| 579 | + } | |
| 580 | + return formattedReasoning; | |
| 560 | 581 | } |
| 561 | 582 | |
| 562 | 583 | // Combine parts with reasoning and content |
| 563 | 584 | returnconst formattedReasoning = `${prefix}${reasoning}${suffix}${separator}${content}`; |
| 585 | + if (isPrefix) { | |
| 586 | + this.prefixReasoning = reasoning; | |
| 587 | + this.prefixLength = formattedReasoning.length; | |
| 588 | + this.prefixDuration = duration; | |
| 589 | + } | |
| 590 | + return `${formattedReasoning}${content}`; | |
| 591 | + } | |
| 592 | + | |
| 593 | + /** | |
| 594 | + * Removes the reasoning prefix from the content. | |
| 595 | + * @param {string} content Content with the reasoning prefix | |
| 596 | + * @returns {string} Content without the reasoning prefix | |
| 597 | + */ | |
| 598 | + removePrefix(content) { | |
| 599 | + if (this.prefixLength > 0) { | |
| 600 | + return content.slice(this.prefixLength); | |
| 601 | + } | |
| 602 | + return content; | |
| 564 | 603 | } |
| 565 | 604 | } |
| 566 | 605 | |