Refactoring StreamProcessor -> ReasoningHandler
| @@ -269,7 +269,7 @@ import { initSettingsSearch } from './scripts/setting-search.js'; | |||
| 269 | import { initBulkEdit } from './scripts/bulk-edit.js'; | 269 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 270 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; | 270 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 271 | import { getContext } from './scripts/st-context.js'; | 271 | import { getContext } from './scripts/st-context.js'; |
| 272 | import { extractReasoningFromData, initReasoning, isHiddenReasoningModel, PromptReasoning, updateReasoningUI } from './scripts/reasoning.js'; | 272 | import { extractReasoningFromData, initReasoning, PromptReasoning, ReasoningHandler, updateReasoningUI } from './scripts/reasoning.js'; |
| 273 | 273 | ||
| 274 | // API OBJECT FOR EXTERNAL WIRING | 274 | // API OBJECT FOR EXTERNAL WIRING |
| 275 | globalThis.SillyTavern = { | 275 | globalThis.SillyTavern = { |
| @@ -3128,10 +3128,6 @@ class StreamingProcessor { | |||
| 3128 | this.messageTimerDom = null; | 3128 | this.messageTimerDom = null; |
| 3129 | /** @type {HTMLElement} */ | 3129 | /** @type {HTMLElement} */ |
| 3130 | this.messageTokenCounterDom = null; | 3130 | this.messageTokenCounterDom = null; |
| 3131 | /** @type {HTMLElement} */ | ||
| 3132 | this.messageReasoningDom = null; | ||
| 3133 | /** @type {HTMLElement} */ | ||
| 3134 | this.messageReasoningHeaderDom = null; | ||
| 3135 | /** @type {HTMLTextAreaElement} */ | 3131 | /** @type {HTMLTextAreaElement} */ |
| 3136 | this.sendTextarea = document.querySelector('#send_textarea'); | 3132 | this.sendTextarea = document.querySelector('#send_textarea'); |
| 3137 | this.type = type; | 3133 | this.type = type; |
| @@ -3147,20 +3143,8 @@ class StreamingProcessor { | |||
| 3147 | /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */ | 3143 | /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */ |
| 3148 | this.messageLogprobs = []; | 3144 | this.messageLogprobs = []; |
| 3149 | this.toolCalls = []; | 3145 | this.toolCalls = []; |
| 3150 | this.reasoning = ''; | 3146 | // Initialize reasoning in its own handler |
| 3151 | /** @type {Date} */ | 3147 | this.reasoningHandler = new ReasoningHandler(type, timeStarted); |
| 3152 | this.reasoningStartTime = null; | ||
| 3153 | /** @type {Date} */ | ||
| 3154 | this.reasoningEndTime = null; | ||
| 3155 | this.isHiddenReasoning = isHiddenReasoningModel(); | ||
| 3156 | } | ||
| 3157 | |||
| 3158 | /** @type {() => number} Reasoning duration in milliseconds */ | ||
| 3159 | #reasoningDuration() { | ||
| 3160 | if (this.reasoningStartTime && this.reasoningEndTime) { | ||
| 3161 | return (this.reasoningEndTime.getTime() - this.reasoningStartTime.getTime()); | ||
| 3162 | } | ||
| 3163 | return null; | ||
| 3164 | } | 3148 | } |
| 3165 | 3149 | ||
| 3166 | #checkDomElements(messageId) { | 3150 | #checkDomElements(messageId) { |
| @@ -3169,11 +3153,8 @@ class StreamingProcessor { | |||
| 3169 | this.messageTextDom = this.messageDom?.querySelector('.mes_text'); | 3153 | this.messageTextDom = this.messageDom?.querySelector('.mes_text'); |
| 3170 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); | 3154 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); |
| 3171 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); | 3155 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); |
| 3172 | this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning'); | ||
| 3173 | this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title'); | ||
| 3174 | } | 3156 | } |
| 3175 | 3157 | this.reasoningHandler.checkDomElements(messageId); | |
| 3176 | this.messageDom.classList.toggle('reasoning_hidden', this.isHiddenReasoning); | ||
| 3177 | } | 3158 | } |
| 3178 | 3159 | ||
| 3179 | #updateMessageBlockVisibility() { | 3160 | #updateMessageBlockVisibility() { |
| @@ -3184,19 +3165,11 @@ class StreamingProcessor { | |||
| 3184 | } | 3165 | } |
| 3185 | 3166 | ||
| 3186 | showMessageButtons(messageId) { | 3167 | showMessageButtons(messageId) { |
| 3187 | if (messageId == -1) { | ||
| 3188 | return; | ||
| 3189 | } | ||
| 3190 | |||
| 3191 | showStopButton(); | 3168 | showStopButton(); |
| 3192 | $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'none' }); | 3169 | $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'none' }); |
| 3193 | } | 3170 | } |
| 3194 | 3171 | ||
| 3195 | hideMessageButtons(messageId) { | 3172 | hideMessageButtons(messageId) { |
| 3196 | if (messageId == -1) { | ||
| 3197 | return; | ||
| 3198 | } | ||
| 3199 | |||
| 3200 | hideStopButton(); | 3173 | hideStopButton(); |
| 3201 | $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'flex' }); | 3174 | $(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'flex' }); |
| 3202 | } | 3175 | } |
| @@ -3207,14 +3180,12 @@ class StreamingProcessor { | |||
| 3207 | if (this.type == 'impersonate') { | 3180 | if (this.type == 'impersonate') { |
| 3208 | this.sendTextarea.value = ''; | 3181 | this.sendTextarea.value = ''; |
| 3209 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); | 3182 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); |
| 3210 | } | 3183 | } else { |
| 3211 | else { | ||
| 3212 | await saveReply(this.type, text, true, '', [], ''); | 3184 | await saveReply(this.type, text, true, '', [], ''); |
| 3213 | messageId = chat.length - 1; | 3185 | messageId = chat.length - 1; |
| 3214 | this.#checkDomElements(messageId); | 3186 | this.#checkDomElements(messageId); |
| 3215 | this.showMessageButtons(messageId); | 3187 | this.showMessageButtons(messageId); |
| 3216 | } | 3188 | } |
| 3217 | |||
| 3218 | hideSwipeButtons(); | 3189 | hideSwipeButtons(); |
| 3219 | scrollChatToBottom(); | 3190 | scrollChatToBottom(); |
| 3220 | return messageId; | 3191 | return messageId; |
| @@ -3232,11 +3203,9 @@ class StreamingProcessor { | |||
| 3232 | 3203 | ||
| 3233 | let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal, this.stoppingStrings); | 3204 | let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal, this.stoppingStrings); |
| 3234 | 3205 | ||
| 3235 | // Predict unbalanced asterisks / quotes during streaming | ||
| 3236 | const charsToBalance = ['*', '"', '```']; | 3206 | const charsToBalance = ['*', '"', '```']; |
| 3237 | for (const char of charsToBalance) { | 3207 | for (const char of charsToBalance) { |
| 3238 | if (!isFinal && isOdd(countOccurrences(processedText, char))) { | 3208 | if (!isFinal && isOdd(countOccurrences(processedText, char))) { |
| 3239 | // Add character at the end to balance it | ||
| 3240 | const separator = char.length > 1 ? '\n' : ''; | 3209 | const separator = char.length > 1 ? '\n' : ''; |
| 3241 | processedText = processedText.trimEnd() + separator + char; | 3210 | processedText = processedText.trimEnd() + separator + char; |
| 3242 | } | 3211 | } |
| @@ -3245,48 +3214,24 @@ class StreamingProcessor { | |||
| 3245 | if (isImpersonate) { | 3214 | if (isImpersonate) { |
| 3246 | this.sendTextarea.value = processedText; | 3215 | this.sendTextarea.value = processedText; |
| 3247 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); | 3216 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); |
| 3248 | } | 3217 | } else { |
| 3249 | else { | ||
| 3250 | const mesChanged = chat[messageId]['mes'] !== processedText; | 3218 | const mesChanged = chat[messageId]['mes'] !== processedText; |
| 3251 | |||
| 3252 | this.#checkDomElements(messageId); | 3219 | this.#checkDomElements(messageId); |
| 3253 | this.#updateMessageBlockVisibility(); | 3220 | this.#updateMessageBlockVisibility(); |
| 3254 | const currentTime = new Date(); | 3221 | const currentTime = new Date(); |
| 3255 | chat[messageId]['mes'] = processedText; | 3222 | chat[messageId]['mes'] = processedText; |
| 3256 | chat[messageId]['gen_started'] = this.timeStarted; | 3223 | chat[messageId]['gen_started'] = this.timeStarted; |
| 3257 | chat[messageId]['gen_finished'] = currentTime; | 3224 | chat[messageId]['gen_finished'] = currentTime; |
| 3258 | |||
| 3259 | if (!chat[messageId]['extra']) { | 3225 | if (!chat[messageId]['extra']) { |
| 3260 | chat[messageId]['extra'] = {}; | 3226 | chat[messageId]['extra'] = {}; |
| 3261 | } | 3227 | } |
| 3262 | 3228 | ||
| 3263 | if (this.reasoning || this.isHiddenReasoning) { | 3229 | // Update reasoning |
| 3264 | const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | 3230 | await this.reasoningHandler.process(messageId, mesChanged, currentTime); |
| 3265 | const reasoningChanged = chat[messageId]['extra']['reasoning'] !== reasoning; | ||
| 3266 | chat[messageId]['extra']['reasoning'] = reasoning; | ||
| 3267 | |||
| 3268 | if ((this.isHiddenReasoning || reasoningChanged) && this.reasoningStartTime === null) { | ||
| 3269 | this.reasoningStartTime = this.timeStarted; | ||
| 3270 | } | ||
| 3271 | if ((this.isHiddenReasoning || !reasoningChanged) && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) { | ||
| 3272 | this.reasoningEndTime = currentTime; | ||
| 3273 | await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.#reasoningDuration); | ||
| 3274 | } | ||
| 3275 | await this.#updateReasoningTime(messageId); | ||
| 3276 | |||
| 3277 | if (this.messageReasoningDom instanceof HTMLElement) { | ||
| 3278 | const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true); | ||
| 3279 | this.messageReasoningDom.innerHTML = formattedReasoning; | ||
| 3280 | } | ||
| 3281 | if (this.messageDom instanceof HTMLElement) { | ||
| 3282 | this.messageDom.classList.add('reasoning'); | ||
| 3283 | } | ||
| 3284 | } | ||
| 3285 | 3231 | ||
| 3286 | // Don't waste time calculating token count for streaming | 3232 | // Token count update. |
| 3287 | const tokenCountText = (this.reasoning || '') + processedText; | 3233 | const tokenCountText = this.reasoningHandler.reasoning + processedText; |
| 3288 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(tokenCountText, 0) : 0; | 3234 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(tokenCountText, 0) : 0; |
| 3289 | |||
| 3290 | if (currentTokenCount) { | 3235 | if (currentTokenCount) { |
| 3291 | chat[messageId]['extra']['token_count'] = currentTokenCount; | 3236 | chat[messageId]['extra']['token_count'] = currentTokenCount; |
| 3292 | if (this.messageTokenCounterDom instanceof HTMLElement) { | 3237 | if (this.messageTokenCounterDom instanceof HTMLElement) { |
| @@ -3312,7 +3257,7 @@ class StreamingProcessor { | |||
| 3312 | this.messageTextDom.innerHTML = formattedText; | 3257 | this.messageTextDom.innerHTML = formattedText; |
| 3313 | } | 3258 | } |
| 3314 | 3259 | ||
| 3315 | const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount, this.#reasoningDuration()); | 3260 | const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount, this.reasoningHandler.getDuration()); |
| 3316 | if (this.messageTimerDom instanceof HTMLElement) { | 3261 | if (this.messageTimerDom instanceof HTMLElement) { |
| 3317 | this.messageTimerDom.textContent = timePassed.timerValue; | 3262 | this.messageTimerDom.textContent = timePassed.timerValue; |
| 3318 | this.messageTimerDom.title = timePassed.timerTitle; | 3263 | this.messageTimerDom.title = timePassed.timerTitle; |
| @@ -3326,23 +3271,12 @@ class StreamingProcessor { | |||
| 3326 | } | 3271 | } |
| 3327 | } | 3272 | } |
| 3328 | 3273 | ||
| 3329 | async #updateReasoningTime(messageId, { forceEnd = false } = {}) { | ||
| 3330 | const duration = this.#reasoningDuration(); | ||
| 3331 | chat[messageId]['extra']['reasoning_duration'] = duration; | ||
| 3332 | updateReasoningUI(this.messageDom, this.reasoning, duration, { forceEnd: forceEnd }); | ||
| 3333 | } | ||
| 3334 | |||
| 3335 | async onFinishStreaming(messageId, text) { | 3274 | async onFinishStreaming(messageId, text) { |
| 3336 | this.hideMessageButtons(this.messageId); | 3275 | this.hideMessageButtons(this.messageId); |
| 3337 | await this.onProgressStreaming(messageId, text, true); | 3276 | await this.onProgressStreaming(messageId, text, true); |
| 3338 | addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`)); | 3277 | addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`)); |
| 3339 | 3278 | ||
| 3340 | // Ensure reasoning finish time is recorded if not already | 3279 | await this.reasoningHandler.finish(messageId); |
| 3341 | if (this.reasoningStartTime !== null && this.reasoningEndTime === null) { | ||
| 3342 | this.reasoningEndTime = new Date(); | ||
| 3343 | await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.#reasoningDuration); | ||
| 3344 | await this.#updateReasoningTime(messageId, { forceEnd: true }); | ||
| 3345 | } | ||
| 3346 | 3280 | ||
| 3347 | if (Array.isArray(this.swipes) && this.swipes.length > 0) { | 3281 | if (Array.isArray(this.swipes) && this.swipes.length > 0) { |
| 3348 | const message = chat[messageId]; | 3282 | const message = chat[messageId]; |
| @@ -3443,7 +3377,8 @@ class StreamingProcessor { | |||
| 3443 | if (logprobs) { | 3377 | if (logprobs) { |
| 3444 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); | 3378 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); |
| 3445 | } | 3379 | } |
| 3446 | this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING); | 3380 | // Get the updated reasoning string into the handler |
| 3381 | this.reasoningHandler.updateReasoning(state?.reasoning ?? ''); | ||
| 3447 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); | 3382 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3448 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); | 3383 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3449 | } | 3384 | } |
| @@ -151,7 +151,7 @@ export function updateReasoningUI(messageIdOrElement, reasoning = null, reasonin | |||
| 151 | * @param {object} [options={}] Options for the function | 151 | * @param {object} [options={}] Options for the function |
| 152 | * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists | 152 | * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists |
| 153 | */ | 153 | */ |
| 154 | export function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { | 154 | function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { |
| 155 | if (duration) { | 155 | if (duration) { |
| 156 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); | 156 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); |
| 157 | const secondsStr = moment.duration(duration).asSeconds(); | 157 | const secondsStr = moment.duration(duration).asSeconds(); |
| @@ -163,6 +163,160 @@ export function updateReasoningTimeUI(element, duration, { forceEnd = false } = | |||
| 163 | } | 163 | } |
| 164 | } | 164 | } |
| 165 | 165 | ||
| 166 | /** @enum {string} */ | ||
| 167 | export const ReasoningState = { | ||
| 168 | None: 'none', | ||
| 169 | Thinking: 'thinking', | ||
| 170 | Done: 'done', | ||
| 171 | Hidden: 'hidden', | ||
| 172 | }; | ||
| 173 | |||
| 174 | /** | ||
| 175 | * Handles reasoning-specific logic and DOM updates for messages. | ||
| 176 | * Used inside the @see {StreamingProcessor} | ||
| 177 | */ | ||
| 178 | export class ReasoningHandler { | ||
| 179 | #isHidden; | ||
| 180 | |||
| 181 | /** | ||
| 182 | * @param {string} type - The streaming type | ||
| 183 | * @param {Date} timeStarted - When the generation started | ||
| 184 | */ | ||
| 185 | constructor(type, timeStarted) { | ||
| 186 | /** @type {ReasoningState} The current state of the reasoning process */ | ||
| 187 | this.state = ReasoningState.None; | ||
| 188 | /** @type {string} The reasoning output */ | ||
| 189 | this.reasoning = ''; | ||
| 190 | /** @type {Date} When the reasoning started */ | ||
| 191 | this.startTime = null; | ||
| 192 | /** @type {Date} When the reasoning ended */ | ||
| 193 | this.endTime = null; | ||
| 194 | |||
| 195 | /** @type {string} Generation type (normal, continue, impersonation, etc) */ | ||
| 196 | this.type = type; | ||
| 197 | /** @type {Date} Initial starting time of the generation */ | ||
| 198 | this.initialTime = timeStarted; | ||
| 199 | |||
| 200 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ | ||
| 201 | this.#isHidden = isHiddenReasoningModel(); | ||
| 202 | |||
| 203 | // Cached DOM elements for reasoning | ||
| 204 | /** @type {HTMLElement} Main message DOM element `.mes` */ | ||
| 205 | this.messageDom = null; | ||
| 206 | /** @type {HTMLElement} Reasoning details DOM element `.mes_reasoning_details` */ | ||
| 207 | this.messageReasoningDetailsDom = null; | ||
| 208 | /** @type {HTMLElement} Reasoning content DOM element `.mes_reasoning_content` */ | ||
| 209 | this.messageReasoningContentDom = null; | ||
| 210 | /** @type {HTMLElement} Reasoning header DOM element `.mes_reasoning_header` */ | ||
| 211 | this.messageReasoningHeaderDom = null; | ||
| 212 | } | ||
| 213 | |||
| 214 | /** | ||
| 215 | * Gets the duration of the reasoning in milliseconds. | ||
| 216 | * @returns {number|null} The duration in milliseconds, or null if the start or end time is not set. | ||
| 217 | */ | ||
| 218 | getDuration() { | ||
| 219 | if (this.startTime && this.endTime) { | ||
| 220 | return this.endTime.getTime() - this.startTime.getTime(); | ||
| 221 | } | ||
| 222 | return null; | ||
| 223 | } | ||
| 224 | |||
| 225 | /** | ||
| 226 | * Finds and caches reasoning-related DOM elements for the given message. | ||
| 227 | * @param {number} messageId The message ID | ||
| 228 | */ | ||
| 229 | checkDomElements(messageId) { | ||
| 230 | // Make sure we reset dom elements if we are checking for a different message (shouldn't happen, but be sure) | ||
| 231 | if (this.messageDom !== null && this.messageDom.getAttribute('mesid') !== messageId.toString()) { | ||
| 232 | this.messageDom = null; | ||
| 233 | } | ||
| 234 | |||
| 235 | // Cache the DOM elements once | ||
| 236 | if (this.messageDom === null) { | ||
| 237 | this.messageDom = document.querySelector(`#chat .mes[mesid="${messageId}"]`); | ||
| 238 | this.messageReasoningDetailsDom = this.messageDom.querySelector('.mes_reasoning_details'); | ||
| 239 | this.messageReasoningContentDom = this.messageDom.querySelector('.mes_reasoning'); | ||
| 240 | this.messageReasoningHeaderDom = this.messageDom.querySelector('.mes_reasoning_header_title'); | ||
| 241 | // Update the DOM with the current reasoning state. | ||
| 242 | this.messageDom.dataset.state = this.state; | ||
| 243 | this.messageDom.classList.toggle('reasoning_hidden', this.#isHidden); | ||
| 244 | } | ||
| 245 | |||
| 246 | // Update main DOM state | ||
| 247 | this.#updateDomState(); | ||
| 248 | } | ||
| 249 | |||
| 250 | #updateDomState() { | ||
| 251 | this.messageDom.dataset.state = this.state; | ||
| 252 | this.messageDom.classList.toggle('reasoning_hidden', this.#isHidden); | ||
| 253 | } | ||
| 254 | |||
| 255 | updateReasoning(reasoning = null) { | ||
| 256 | reasoning = reasoning ?? this.reasoning; | ||
| 257 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); | ||
| 258 | } | ||
| 259 | |||
| 260 | /** | ||
| 261 | * Processes and updates reasoning info for the message. | ||
| 262 | * @param {number} messageId - The ID of the message. | ||
| 263 | * @param {boolean} mesChanged - True if the message text changed. | ||
| 264 | * @param {Date} currentTime - The current time. | ||
| 265 | */ | ||
| 266 | async process(messageId, mesChanged, currentTime) { | ||
| 267 | if (!this.reasoning && !this.#isHidden) return; | ||
| 268 | |||
| 269 | this.updateReasoning(); | ||
| 270 | |||
| 271 | // Ensure the chat extra exists. | ||
| 272 | if (!chat[messageId]['extra']) { | ||
| 273 | chat[messageId]['extra'] = {}; | ||
| 274 | } | ||
| 275 | const extra = chat[messageId]['extra']; | ||
| 276 | const finalReasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | ||
| 277 | const reasoningChanged = extra['reasoning'] !== finalReasoning; | ||
| 278 | extra['reasoning'] = finalReasoning; | ||
| 279 | |||
| 280 | if ((this.#isHidden || reasoningChanged) && this.startTime === null) { | ||
| 281 | this.startTime = this.initialTime; | ||
| 282 | } | ||
| 283 | if ((this.#isHidden || !reasoningChanged) && mesChanged && this.startTime !== null && this.endTime === null) { | ||
| 284 | this.endTime = currentTime; | ||
| 285 | await eventSource.emit(event_types.STREAM_REASONING_DONE, finalReasoning, () => this.getDuration()); | ||
| 286 | } | ||
| 287 | await this.updateTime(messageId); | ||
| 288 | if (this.messageReasoningContentDom instanceof HTMLElement) { | ||
| 289 | const formattedReasoning = messageFormatting(finalReasoning, '', false, false, messageId, {}, true); | ||
| 290 | this.messageReasoningContentDom.innerHTML = formattedReasoning; | ||
| 291 | } | ||
| 292 | if (this.messageDom instanceof HTMLElement) { | ||
| 293 | this.messageDom.classList.add('reasoning'); | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | async finish(messageId) { | ||
| 298 | // Make sure the finish time is recorded if a reasoning was in process and it wasn't ended correctly during streaming | ||
| 299 | if (this.startTime !== null && this.endTime === null) { | ||
| 300 | this.endTime = new Date(); | ||
| 301 | const finalReasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | ||
| 302 | await eventSource.emit(event_types.STREAM_REASONING_DONE, finalReasoning, () => this.getDuration()); | ||
| 303 | await this.updateTime(messageId); | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | /** | ||
| 308 | * Updates the reasoning duration in the UI. | ||
| 309 | * @param {number} messageId - The ID of the message | ||
| 310 | * @param {object} [options={}] - Optional argument | ||
| 311 | * @param {boolean} [options.forceEnd=false] - If true, there will be no "Thinking..." when no duration exists | ||
| 312 | */ | ||
| 313 | async updateTime(messageId, { forceEnd = false } = {}) { | ||
| 314 | const duration = this.getDuration(); | ||
| 315 | chat[messageId]['extra']['reasoning_duration'] = duration; | ||
| 316 | updateReasoningUI(this.messageDom, this.reasoning, duration, { forceEnd }); | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 166 | /** | 320 | /** |
| 167 | * Helper class for adding reasoning to messages. | 321 | * Helper class for adding reasoning to messages. |
| 168 | * Keeps track of the number of reasoning additions. | 322 | * Keeps track of the number of reasoning additions. |