Merge pull request #3606 from SillyTavern/reasoning-continue-fix Fix continue duplicating reasoning block
Signed| @@ -3135,8 +3135,9 @@ class StreamingProcessor { | |||
| 3135 | * @param {boolean} forceName2 If true, force the use of name2 | 3135 | * @param {boolean} forceName2 If true, force the use of name2 |
| 3136 | * @param {Date} timeStarted Date when generation was started | 3136 | * @param {Date} timeStarted Date when generation was started |
| 3137 | * @param {string} continueMessage Previous message if the type is 'continue' | 3137 | * @param {string} continueMessage Previous message if the type is 'continue' |
| 3138 | * @param {PromptReasoning} promptReasoning Prompt reasoning instance | ||
| 3138 | */ | 3139 | */ |
| 3139 | constructor(type, forceName2, timeStarted, continueMessage) { | 3140 | constructor(type, forceName2, timeStarted, continueMessage, promptReasoning) { |
| 3140 | this.result = ''; | 3141 | this.result = ''; |
| 3141 | this.messageId = -1; | 3142 | this.messageId = -1; |
| 3142 | /** @type {HTMLElement} */ | 3143 | /** @type {HTMLElement} */ |
| @@ -3164,6 +3165,8 @@ class StreamingProcessor { | |||
| 3164 | this.toolCalls = []; | 3165 | this.toolCalls = []; |
| 3165 | // Initialize reasoning in its own handler | 3166 | // Initialize reasoning in its own handler |
| 3166 | this.reasoningHandler = new ReasoningHandler(timeStarted); | 3167 | this.reasoningHandler = new ReasoningHandler(timeStarted); |
| 3168 | /** @type {PromptReasoning} */ | ||
| 3169 | this.promptReasoning = promptReasoning; | ||
| 3167 | } | 3170 | } |
| 3168 | 3171 | ||
| 3169 | #checkDomElements(messageId) { | 3172 | #checkDomElements(messageId) { |
| @@ -3192,6 +3195,10 @@ class StreamingProcessor { | |||
| 3192 | } | 3195 | } |
| 3193 | 3196 | ||
| 3194 | async onStartStreaming(text) { | 3197 | async onStartStreaming(text) { |
| 3198 | if (this.type === 'continue' && this.promptReasoning.prefixReasoning) { | ||
| 3199 | this.reasoningHandler.initContinue(this.promptReasoning); | ||
| 3200 | } | ||
| 3201 | |||
| 3195 | let messageId = -1; | 3202 | let messageId = -1; |
| 3196 | 3203 | ||
| 3197 | if (this.type == 'impersonate') { | 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 | const reasoning = new PromptReasoning(); | 3892 | const promptReasoning = new PromptReasoning(); |
| 3886 | for (let i = coreChat.length - 1; i >= 0; i--) { | 3893 | for (let i = coreChat.length - 1; i >= 0; i--) { |
| 3887 | const depth = coreChat.length - i - 1; | 3894 | const depth = coreChat.length - i - 1; |
| 3888 | const isPrefix = isContinue && i === coreChat.length - 1; | 3895 | const isPrefix = isContinue && i === coreChat.length - 1; |
| 3889 | coreChat[i] = { | 3896 | coreChat[i] = { |
| 3890 | ...coreChat[i], | 3897 | ...coreChat[i], |
| 3891 | mes: reasoning.addToMessage( | 3898 | mes: promptReasoning.addToMessage( |
| 3892 | coreChat[i].mes, | 3899 | coreChat[i].mes, |
| 3893 | getRegexedString( | 3900 | getRegexedString( |
| 3894 | String(coreChat[i].extra?.reasoning ?? ''), | 3901 | String(coreChat[i].extra?.reasoning ?? ''), |
| @@ -3896,9 +3903,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 3896 | { isPrompt: true, depth: depth }, | 3903 | { isPrompt: true, depth: depth }, |
| 3897 | ), | 3904 | ), |
| 3898 | isPrefix, | 3905 | isPrefix, |
| 3906 | coreChat[i].extra?.reasoning_duration, | ||
| 3899 | ), | 3907 | ), |
| 3900 | }; | 3908 | }; |
| 3901 | if (reasoning.isLimitReached()) { | 3909 | if (promptReasoning.isLimitReached()) { |
| 3902 | break; | 3910 | break; |
| 3903 | } | 3911 | } |
| 3904 | } | 3912 | } |
| @@ -4723,7 +4731,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4723 | console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`); | 4731 | console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`); |
| 4724 | 4732 | ||
| 4725 | if (isStreamingEnabled() && type !== 'quiet') { | 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 | if (isContinue) { | 4736 | if (isContinue) { |
| 4728 | // Save reply does add cycle text to the prompt, so it's not needed here | 4737 | // Save reply does add cycle text to the prompt, so it's not needed here |
| 4729 | streamingProcessor.firstMessageText = ''; | 4738 | streamingProcessor.firstMessageText = ''; |
| @@ -4824,6 +4833,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4824 | } | 4833 | } |
| 4825 | 4834 | ||
| 4826 | if (isContinue) { | 4835 | if (isContinue) { |
| 4836 | continue_mag = promptReasoning.removePrefix(continue_mag); | ||
| 4827 | getMessage = continue_mag + getMessage; | 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 | * Initializes the reasoning handler for a specific message. | 203 | * Initializes the reasoning handler for a specific message. |
| 193 | * | 204 | * |
| 194 | * Can be used to update the DOM elements or read other reasoning states. | 205 | * Can be used to update the DOM elements or read other reasoning states. |
| @@ -514,6 +525,9 @@ export class PromptReasoning { | |||
| 514 | 525 | ||
| 515 | constructor() { | 526 | constructor() { |
| 516 | this.counter = 0; | 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 | * @param {string} content Message content | 547 | * @param {string} content Message content |
| 534 | * @param {string} reasoning Message reasoning | 548 | * @param {string} reasoning Message reasoning |
| 535 | * @param {boolean} isPrefix Whether this is the last message prefix | 549 | * @param {boolean} isPrefix Whether this is the last message prefix |
| 550 | * @param {number?} duration Duration of the reasoning | ||
| 536 | * @returns {string} Message content with reasoning | 551 | * @returns {string} Message content with reasoning |
| 537 | */ | 552 | */ |
| 538 | addToMessage(content, reasoning, isPrefix) { | 553 | addToMessage(content, reasoning, isPrefix, duration) { |
| 539 | // Disabled or reached limit of additions | 554 | // Disabled or reached limit of additions |
| 540 | if (!isPrefix && (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions)) { | 555 | if (!isPrefix && (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions)) { |
| 541 | return content; | 556 | return content; |
| @@ -556,11 +571,35 @@ export class PromptReasoning { | |||
| 556 | 571 | ||
| 557 | // Combine parts with reasoning only | 572 | // Combine parts with reasoning only |
| 558 | if (isPrefix && !content) { | 573 | if (isPrefix && !content) { |
| 559 | return `${prefix}${reasoning}`; | 574 | const 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 | // Combine parts with reasoning and content | 583 | // Combine parts with reasoning and content |
| 563 | return `${prefix}${reasoning}${suffix}${separator}${content}`; | 584 | const formattedReasoning = `${prefix}${reasoning}${suffix}${separator}`; |
| 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 | ||