More coding on ReasoningHandler, fixing stuff
| @@ -2223,8 +2223,6 @@ function getMessageFromTemplate({ | |||
| 2223 | isUser, | 2223 | isUser, |
| 2224 | avatarImg, | 2224 | avatarImg, |
| 2225 | bias, | 2225 | bias, |
| 2226 | reasoning, | ||
| 2227 | reasoningDuration, | ||
| 2228 | isSystem, | 2226 | isSystem, |
| 2229 | title, | 2227 | title, |
| 2230 | timerValue, | 2228 | timerValue, |
| @@ -2256,7 +2254,7 @@ function getMessageFromTemplate({ | |||
| 2256 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); | 2254 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); |
| 2257 | bookmarkLink && updateBookmarkDisplay(mes); | 2255 | bookmarkLink && updateBookmarkDisplay(mes); |
| 2258 | 2256 | ||
| 2259 | updateReasoningUI(mes, reasoning, reasoningDuration, { forceEnd: true }); | 2257 | updateReasoningUI(mes); |
| 2260 | 2258 | ||
| 2261 | if (power_user.timestamp_model_icon && extra?.api) { | 2259 | if (power_user.timestamp_model_icon && extra?.api) { |
| 2262 | insertSVGIcon(mes, extra); | 2260 | insertSVGIcon(mes, extra); |
| @@ -2279,7 +2277,7 @@ export function updateMessageBlock(messageId, message, { rerenderMessage = true | |||
| 2279 | messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false)); | 2277 | messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false)); |
| 2280 | } | 2278 | } |
| 2281 | 2279 | ||
| 2282 | updateReasoningUI(messageElement, message?.extra?.reasoning, message?.extra?.reasoning_duration, { forceEnd: true }); | 2280 | updateReasoningUI(messageElement); |
| 2283 | 2281 | ||
| 2284 | addCopyToCodeBlocks(messageElement); | 2282 | addCopyToCodeBlocks(messageElement); |
| 2285 | appendMediaToMessage(message, messageElement); | 2283 | appendMediaToMessage(message, messageElement); |
| @@ -2440,7 +2438,6 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2440 | false, | 2438 | false, |
| 2441 | ); | 2439 | ); |
| 2442 | const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1, {}, false); | 2440 | const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1, {}, false); |
| 2443 | const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, chat.indexOf(mes), {}, true); | ||
| 2444 | let bookmarkLink = mes?.extra?.bookmark_link ?? ''; | 2441 | let bookmarkLink = mes?.extra?.bookmark_link ?? ''; |
| 2445 | 2442 | ||
| 2446 | let params = { | 2443 | let params = { |
| @@ -2450,8 +2447,6 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2450 | isUser: mes.is_user, | 2447 | isUser: mes.is_user, |
| 2451 | avatarImg: avatarImg, | 2448 | avatarImg: avatarImg, |
| 2452 | bias: bias, | 2449 | bias: bias, |
| 2453 | reasoning: reasoning, | ||
| 2454 | reasoningDuration: mes.extra?.reasoning_duration, | ||
| 2455 | isSystem: isSystem, | 2450 | isSystem: isSystem, |
| 2456 | title: title, | 2451 | title: title, |
| 2457 | bookmarkLink: bookmarkLink, | 2452 | bookmarkLink: bookmarkLink, |
| @@ -3144,7 +3139,7 @@ class StreamingProcessor { | |||
| 3144 | this.messageLogprobs = []; | 3139 | this.messageLogprobs = []; |
| 3145 | this.toolCalls = []; | 3140 | this.toolCalls = []; |
| 3146 | // Initialize reasoning in its own handler | 3141 | // Initialize reasoning in its own handler |
| 3147 | this.reasoningHandler = new ReasoningHandler(type, timeStarted); | 3142 | this.reasoningHandler = new ReasoningHandler(timeStarted); |
| 3148 | } | 3143 | } |
| 3149 | 3144 | ||
| 3150 | #checkDomElements(messageId) { | 3145 | #checkDomElements(messageId) { |
| @@ -3154,7 +3149,7 @@ class StreamingProcessor { | |||
| 3154 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); | 3149 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); |
| 3155 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); | 3150 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); |
| 3156 | } | 3151 | } |
| 3157 | this.reasoningHandler.checkDomElements(messageId); | 3152 | this.reasoningHandler.updateDom(messageId); |
| 3158 | } | 3153 | } |
| 3159 | 3154 | ||
| 3160 | #updateMessageBlockVisibility() { | 3155 | #updateMessageBlockVisibility() { |
| @@ -3378,7 +3373,7 @@ class StreamingProcessor { | |||
| 3378 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); | 3373 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); |
| 3379 | } | 3374 | } |
| 3380 | // Get the updated reasoning string into the handler | 3375 | // Get the updated reasoning string into the handler |
| 3381 | this.reasoningHandler.updateReasoning(state?.reasoning ?? ''); | 3376 | this.reasoningHandler.updateReasoning(this.messageId, state?.reasoning ?? ''); |
| 3382 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); | 3377 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3383 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); | 3378 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3384 | } | 3379 | } |
| @@ -8727,7 +8722,7 @@ const swipe_right = () => { | |||
| 8727 | // resets the timer | 8722 | // resets the timer |
| 8728 | swipeMessage.find('.mes_timer').html(''); | 8723 | swipeMessage.find('.mes_timer').html(''); |
| 8729 | swipeMessage.find('.tokenCounterDisplay').text(''); | 8724 | swipeMessage.find('.tokenCounterDisplay').text(''); |
| 8730 | updateReasoningUI(swipeMessage, null); | 8725 | updateReasoningUI(swipeMessage); |
| 8731 | } else { | 8726 | } else { |
| 8732 | //console.log('showing previously generated swipe candidate, or "..."'); | 8727 | //console.log('showing previously generated swipe candidate, or "..."'); |
| 8733 | //console.log('onclick right swipe calling addOneMessage'); | 8728 | //console.log('onclick right swipe calling addOneMessage'); |
| @@ -117,53 +117,20 @@ export function isHiddenReasoningModel() { | |||
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | /** | 119 | /** |
| 120 | * Updates the Reasoning UI. | 120 | * Updates the Reasoning UI for a specific message |
| 121 | * @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement The message ID or the message element. | 121 | * @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement The message ID or the message element |
| 122 | * @param {string|null} [reasoning=null] The reasoning content. | ||
| 123 | * @param {number|null} [reasoningDuration=null] The duration of the reasoning in milliseconds. | ||
| 124 | * @param {object} [options={}] Options for the function. | ||
| 125 | * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists. | ||
| 126 | */ | 122 | */ |
| 127 | export function updateReasoningUI(messageIdOrElement, reasoning = null, reasoningDuration = null, { forceEnd = false } = {}) { | 123 | export function updateReasoningUI(messageIdOrElement) { |
| 128 | const messageElement = typeof messageIdOrElement === 'number' | 124 | const handler = new ReasoningHandler(); |
| 129 | ? $(`#chat [mesid="${messageIdOrElement}"]`) | 125 | handler.initHandleMessage(messageIdOrElement); |
| 130 | : $(messageIdOrElement); | ||
| 131 | const mesReasoningElement = messageElement.find('.mes_reasoning'); | ||
| 132 | const mesReasoningHeaderTitle = messageElement.find('.mes_reasoning_header_title'); | ||
| 133 | const mesId = Number(messageElement.attr('mesid')); | ||
| 134 | |||
| 135 | mesReasoningElement.html(messageFormatting(reasoning ?? '', '', false, false, mesId, {}, true)); | ||
| 136 | const reasoningText = mesReasoningElement.text().trim(); | ||
| 137 | |||
| 138 | const hasReasoningText = !!reasoningText; | ||
| 139 | const isReasoningHidden = (!!reasoningDuration && !hasReasoningText) || (!forceEnd && isHiddenReasoningModel()); | ||
| 140 | const isReasoning = hasReasoningText || isReasoningHidden; | ||
| 141 | |||
| 142 | messageElement.toggleClass('reasoning', isReasoning); | ||
| 143 | messageElement.toggleClass('reasoning_hidden', isReasoningHidden); | ||
| 144 | updateReasoningTimeUI(mesReasoningHeaderTitle[0], reasoningDuration, { forceEnd }); | ||
| 145 | } | 126 | } |
| 146 | 127 | ||
| 128 | |||
| 147 | /** | 129 | /** |
| 148 | * Updates the Reasoning controls | 130 | * Enum for representing the state of reasoning |
| 149 | * @param {HTMLElement} element The element to update | 131 | * @enum {string} |
| 150 | * @param {number?} duration The duration of the reasoning in milliseconds | 132 | * @readonly |
| 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 | ||
| 153 | */ | 133 | */ |
| 154 | function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { | ||
| 155 | if (duration) { | ||
| 156 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); | ||
| 157 | const secondsStr = moment.duration(duration).asSeconds(); | ||
| 158 | element.innerHTML = t`Thought for <span title="${secondsStr} seconds">${durationStr}</span>`; | ||
| 159 | } else if (forceEnd) { | ||
| 160 | element.textContent = t`Thought for some time`; | ||
| 161 | } else { | ||
| 162 | element.textContent = t`Thinking...`; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | /** @enum {string} */ | ||
| 167 | export const ReasoningState = { | 134 | export const ReasoningState = { |
| 168 | None: 'none', | 135 | None: 'none', |
| 169 | Thinking: 'thinking', | 136 | Thinking: 'thinking', |
| @@ -173,16 +140,15 @@ export const ReasoningState = { | |||
| 173 | 140 | ||
| 174 | /** | 141 | /** |
| 175 | * Handles reasoning-specific logic and DOM updates for messages. | 142 | * Handles reasoning-specific logic and DOM updates for messages. |
| 176 | * Used inside the @see {StreamingProcessor} | 143 | * This class is used inside the {@link StreamingProcessor} to manage reasoning states and UI updates. |
| 177 | */ | 144 | */ |
| 178 | export class ReasoningHandler { | 145 | export class ReasoningHandler { |
| 179 | #isHidden; | 146 | #isHiddenReasoningModel; |
| 180 | 147 | ||
| 181 | /** | 148 | /** |
| 182 | * @param {string} type - The streaming type | 149 | * @param {Date?} [timeStarted=null] - When the generation started |
| 183 | * @param {Date} timeStarted - When the generation started | ||
| 184 | */ | 150 | */ |
| 185 | constructor(type, timeStarted) { | 151 | constructor(timeStarted = null) { |
| 186 | /** @type {ReasoningState} The current state of the reasoning process */ | 152 | /** @type {ReasoningState} The current state of the reasoning process */ |
| 187 | this.state = ReasoningState.None; | 153 | this.state = ReasoningState.None; |
| 188 | /** @type {string} The reasoning output */ | 154 | /** @type {string} The reasoning output */ |
| @@ -192,28 +158,69 @@ export class ReasoningHandler { | |||
| 192 | /** @type {Date} When the reasoning ended */ | 158 | /** @type {Date} When the reasoning ended */ |
| 193 | this.endTime = null; | 159 | this.endTime = null; |
| 194 | 160 | ||
| 195 | /** @type {string} Generation type (normal, continue, impersonation, etc) */ | ||
| 196 | this.type = type; | ||
| 197 | /** @type {Date} Initial starting time of the generation */ | 161 | /** @type {Date} Initial starting time of the generation */ |
| 198 | this.initialTime = timeStarted; | 162 | this.initialTime = timeStarted ?? new Date(); |
| 199 | 163 | ||
| 200 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ | 164 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ |
| 201 | this.#isHidden = isHiddenReasoningModel(); | 165 | this.#isHiddenReasoningModel = isHiddenReasoningModel(); |
| 202 | 166 | ||
| 203 | // Cached DOM elements for reasoning | 167 | // Cached DOM elements for reasoning |
| 204 | /** @type {HTMLElement} Main message DOM element `.mes` */ | 168 | /** @type {HTMLElement} Main message DOM element `.mes` */ |
| 205 | this.messageDom = null; | 169 | this.messageDom = null; |
| 206 | /** @type {HTMLElement} Reasoning details DOM element `.mes_reasoning_details` */ | 170 | /** @type {HTMLElement} Reasoning details DOM element `.mes_reasoning_details` */ |
| 207 | this.messageReasoningDetailsDom = null; | 171 | this.messageReasoningDetailsDom = null; |
| 208 | /** @type {HTMLElement} Reasoning content DOM element `.mes_reasoning_content` */ | 172 | /** @type {HTMLElement} Reasoning content DOM element `.mes_reasoning` */ |
| 209 | this.messageReasoningContentDom = null; | 173 | this.messageReasoningContentDom = null; |
| 210 | /** @type {HTMLElement} Reasoning header DOM element `.mes_reasoning_header` */ | 174 | /** @type {HTMLElement} Reasoning header DOM element `.mes_reasoning_header_title` */ |
| 211 | this.messageReasoningHeaderDom = null; | 175 | this.messageReasoningHeaderDom = null; |
| 212 | } | 176 | } |
| 213 | 177 | ||
| 214 | /** | 178 | /** |
| 179 | * Initializes the reasoning handler for a specific message. | ||
| 180 | * | ||
| 181 | * Can be used to update the DOM elements or read other reasoning states. | ||
| 182 | * It will internally take the message-saved data and write the states back into the handler, as if during streaming of the message. | ||
| 183 | * The state will always be either done/hidden or none. | ||
| 184 | * | ||
| 185 | * @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement - The message ID or the message element | ||
| 186 | */ | ||
| 187 | initHandleMessage(messageIdOrElement) { | ||
| 188 | /** @type {HTMLElement} */ | ||
| 189 | const messageElement = typeof messageIdOrElement === 'number' | ||
| 190 | ? document.querySelector(`#chat [mesid="${messageIdOrElement}"]`) | ||
| 191 | : messageIdOrElement instanceof HTMLElement | ||
| 192 | ? messageIdOrElement | ||
| 193 | : $(messageIdOrElement)[0]; | ||
| 194 | const messageId = Number(messageElement.getAttribute('mesid')); | ||
| 195 | |||
| 196 | if (isNaN(messageId)) return; | ||
| 197 | |||
| 198 | const extra = chat[messageId]['extra']; | ||
| 199 | |||
| 200 | if (extra.reasoning) { | ||
| 201 | this.state = ReasoningState.Done; | ||
| 202 | } else if (extra.reasoning_duration) { | ||
| 203 | this.state = ReasoningState.Hidden; | ||
| 204 | } | ||
| 205 | |||
| 206 | this.reasoning = extra?.reasoning ?? ''; | ||
| 207 | |||
| 208 | if (this.state !== ReasoningState.None) { | ||
| 209 | this.initialTime = new Date(chat[messageId].gen_started); | ||
| 210 | this.startTime = this.initialTime; | ||
| 211 | this.endTime = new Date(this.startTime.getTime() + (extra?.reasoning_duration ?? 0)); | ||
| 212 | } | ||
| 213 | |||
| 214 | // Prefill main dom element, as message might not have been rendered yet | ||
| 215 | this.messageDom = messageElement; | ||
| 216 | |||
| 217 | this.updateDom(messageId); | ||
| 218 | } | ||
| 219 | |||
| 220 | /** | ||
| 215 | * Gets the duration of the reasoning in milliseconds. | 221 | * 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. | 222 | * |
| 223 | * @returns {number?} The duration in milliseconds, or null if the start or end time is not set | ||
| 217 | */ | 224 | */ |
| 218 | getDuration() { | 225 | getDuration() { |
| 219 | if (this.startTime && this.endTime) { | 226 | if (this.startTime && this.endTime) { |
| @@ -223,97 +230,157 @@ export class ReasoningHandler { | |||
| 223 | } | 230 | } |
| 224 | 231 | ||
| 225 | /** | 232 | /** |
| 226 | * Finds and caches reasoning-related DOM elements for the given message. | 233 | * Updates the reasoning text/string for a message. |
| 227 | * @param {number} messageId The message ID | 234 | * |
| 235 | * @param {number} messageId - The ID of the message to update | ||
| 236 | * @param {string?} [reasoning=null] - The reasoning text to update - If null, uses the current reasoning | ||
| 237 | * @param {Object} [options={}] - Optional arguments | ||
| 238 | * @param {boolean} [options.persist=false] - Whether to persist the reasoning to the message object | ||
| 239 | * @returns {boolean} - Returns true if the reasoning was changed, otherwise false | ||
| 228 | */ | 240 | */ |
| 229 | checkDomElements(messageId) { | 241 | updateReasoning(messageId, reasoning = null, { persist = false } = {}) { |
| 230 | // Make sure we reset dom elements if we are checking for a different message (shouldn't happen, but be sure) | 242 | reasoning = reasoning ?? this.reasoning; |
| 231 | if (this.messageDom !== null && this.messageDom.getAttribute('mesid') !== messageId.toString()) { | 243 | const reasoningChanged = this.reasoning !== reasoning; |
| 232 | this.messageDom = null; | 244 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); |
| 233 | } | ||
| 234 | 245 | ||
| 235 | // Cache the DOM elements once | 246 | if (persist) { |
| 236 | if (this.messageDom === null) { | 247 | // Ensure the chat extra exists |
| 237 | this.messageDom = document.querySelector(`#chat .mes[mesid="${messageId}"]`); | 248 | if (!chat[messageId]['extra']) { |
| 238 | this.messageReasoningDetailsDom = this.messageDom.querySelector('.mes_reasoning_details'); | 249 | chat[messageId]['extra'] = {}; |
| 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 | } | 250 | } |
| 245 | 251 | ||
| 246 | // Update main DOM state | 252 | // Build and save the reasoning data to message extras |
| 247 | this.#updateDomState(); | 253 | const extra = chat[messageId]['extra']; |
| 254 | extra['reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | ||
| 255 | extra['reasoning_duration'] = this.getDuration(); | ||
| 248 | } | 256 | } |
| 249 | 257 | ||
| 250 | #updateDomState() { | 258 | return reasoningChanged; |
| 251 | this.messageDom.dataset.state = this.state; | ||
| 252 | this.messageDom.classList.toggle('reasoning_hidden', this.#isHidden); | ||
| 253 | } | 259 | } |
| 254 | 260 | ||
| 255 | updateReasoning(reasoning = null) { | ||
| 256 | reasoning = reasoning ?? this.reasoning; | ||
| 257 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); | ||
| 258 | } | ||
| 259 | 261 | ||
| 260 | /** | 262 | /** |
| 261 | * Processes and updates reasoning info for the message. | 263 | * Handles processing of reasoning for a message. |
| 262 | * @param {number} messageId - The ID of the message. | 264 | * |
| 263 | * @param {boolean} mesChanged - True if the message text changed. | 265 | * This is usually called by the message processor when a message is changed. |
| 264 | * @param {Date} currentTime - The current time. | 266 | * |
| 267 | * @param {number} messageId - The ID of the message to process | ||
| 268 | * @param {boolean} mesChanged - Whether the message has changed | ||
| 269 | * @returns {Promise<void>} | ||
| 265 | */ | 270 | */ |
| 266 | async process(messageId, mesChanged, currentTime) { | 271 | async process(messageId, mesChanged) { |
| 267 | if (!this.reasoning && !this.#isHidden) return; | 272 | if (!this.reasoning && !this.#isHiddenReasoningModel) return; |
| 268 | 273 | ||
| 269 | this.updateReasoning(); | 274 | // Ensure reasoning string is updated and regexes are applied correctly |
| 275 | const reasoningChanged = this.updateReasoning(messageId, null, { persist: true }); | ||
| 270 | 276 | ||
| 271 | // Ensure the chat extra exists. | 277 | if ((this.#isHiddenReasoningModel || reasoningChanged) && this.state === ReasoningState.None) { |
| 272 | if (!chat[messageId]['extra']) { | 278 | this.state = ReasoningState.Thinking; |
| 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; | 279 | this.startTime = this.initialTime; |
| 282 | } | 280 | } |
| 283 | if ((this.#isHidden || !reasoningChanged) && mesChanged && this.startTime !== null && this.endTime === null) { | 281 | if ((this.#isHiddenReasoningModel || !reasoningChanged) && mesChanged && this.state === ReasoningState.Thinking) { |
| 284 | this.endTime = currentTime; | 282 | this.endTime = new Date(); |
| 285 | await eventSource.emit(event_types.STREAM_REASONING_DONE, finalReasoning, () => this.getDuration()); | 283 | await this.finish(messageId); |
| 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 | } | 284 | } |
| 295 | } | 285 | } |
| 296 | 286 | ||
| 287 | /** | ||
| 288 | * Completes the reasoning process for a message. | ||
| 289 | * | ||
| 290 | * Records the finish time if it was not set during streaming and updates the reasoning state. | ||
| 291 | * Emits an event to signal the completion of reasoning and updates the DOM elements accordingly. | ||
| 292 | * | ||
| 293 | * @param {number} messageId - The ID of the message to complete reasoning for | ||
| 294 | * @returns {Promise<void>} | ||
| 295 | */ | ||
| 297 | async finish(messageId) { | 296 | async finish(messageId) { |
| 297 | if (this.state === ReasoningState.None) return; | ||
| 298 | |||
| 298 | // Make sure the finish time is recorded if a reasoning was in process and it wasn't ended correctly during streaming | 299 | // 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 | if (this.startTime !== null && this.endTime === null) { |
| 300 | this.endTime = new Date(); | 301 | this.endTime = new Date(); |
| 301 | const finalReasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | 302 | } |
| 302 | await eventSource.emit(event_types.STREAM_REASONING_DONE, finalReasoning, () => this.getDuration()); | 303 | |
| 303 | await this.updateTime(messageId); | 304 | if (this.state === ReasoningState.Thinking) { |
| 305 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Hidden : ReasoningState.Done; | ||
| 306 | this.updateReasoning(messageId, null, { persist: true }); | ||
| 307 | await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.getDuration(), messageId, this.state); | ||
| 308 | } | ||
| 309 | |||
| 310 | this.updateDom(messageId); | ||
| 311 | } | ||
| 312 | |||
| 313 | /** | ||
| 314 | * Updates the reasoning UI elements for a message. | ||
| 315 | * | ||
| 316 | * Toggles the CSS class, updates states, reasoning message, and duration. | ||
| 317 | * | ||
| 318 | * @param {number} messageId - The ID of the message to update | ||
| 319 | */ | ||
| 320 | updateDom(messageId) { | ||
| 321 | this.#checkDomElements(messageId); | ||
| 322 | |||
| 323 | // Main CSS class to show this message includes reasoning | ||
| 324 | this.messageDom.classList.toggle('reasoning', this.state !== ReasoningState.None); | ||
| 325 | |||
| 326 | // Update states to the relevant DOM elements | ||
| 327 | this.messageDom.dataset.state = this.state !== ReasoningState.None ? this.state : null; | ||
| 328 | this.messageReasoningDetailsDom.dataset.state = this.state; | ||
| 329 | |||
| 330 | // Update the reasoning message | ||
| 331 | const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | ||
| 332 | const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true); | ||
| 333 | this.messageReasoningContentDom.innerHTML = displayReasoning; | ||
| 334 | |||
| 335 | // Update the reasoning duration in the UI | ||
| 336 | this.#updateReasoningTimeUI(); | ||
| 337 | } | ||
| 338 | |||
| 339 | /** | ||
| 340 | * Finds and caches reasoning-related DOM elements for the given message. | ||
| 341 | * | ||
| 342 | * @param {number} messageId - The ID of the message to cache the DOM elements for | ||
| 343 | */ | ||
| 344 | #checkDomElements(messageId) { | ||
| 345 | // Make sure we reset dom elements if we are checking for a different message (shouldn't happen, but be sure) | ||
| 346 | if (this.messageDom !== null && this.messageDom.getAttribute('mesid') !== messageId.toString()) { | ||
| 347 | this.messageDom = null; | ||
| 348 | } | ||
| 349 | |||
| 350 | // Cache the DOM elements once | ||
| 351 | if (this.messageDom === null) { | ||
| 352 | this.messageDom = document.querySelector(`#chat .mes[mesid="${messageId}"]`); | ||
| 353 | if (this.messageDom === null) throw new Error('message dom does not exist'); | ||
| 354 | } | ||
| 355 | if (this.messageReasoningDetailsDom === null) { | ||
| 356 | this.messageReasoningDetailsDom = this.messageDom.querySelector('.mes_reasoning_details'); | ||
| 357 | } | ||
| 358 | if (this.messageReasoningContentDom === null) { | ||
| 359 | this.messageReasoningContentDom = this.messageDom.querySelector('.mes_reasoning'); | ||
| 360 | } | ||
| 361 | if (this.messageReasoningHeaderDom === null) { | ||
| 362 | this.messageReasoningHeaderDom = this.messageDom.querySelector('.mes_reasoning_header_title'); | ||
| 304 | } | 363 | } |
| 305 | } | 364 | } |
| 306 | 365 | ||
| 307 | /** | 366 | /** |
| 308 | * Updates the reasoning duration in the UI. | 367 | * Updates the reasoning time display in the UI. |
| 309 | * @param {number} messageId - The ID of the message | 368 | * |
| 310 | * @param {object} [options={}] - Optional argument | 369 | * Shows the duration in a human-readable format with a tooltip for exact seconds. |
| 311 | * @param {boolean} [options.forceEnd=false] - If true, there will be no "Thinking..." when no duration exists | 370 | * Displays "Thinking..." if still processing, or a generic message otherwise. |
| 312 | */ | 371 | */ |
| 313 | async updateTime(messageId, { forceEnd = false } = {}) { | 372 | #updateReasoningTimeUI() { |
| 373 | const element = this.messageReasoningHeaderDom; | ||
| 314 | const duration = this.getDuration(); | 374 | const duration = this.getDuration(); |
| 315 | chat[messageId]['extra']['reasoning_duration'] = duration; | 375 | if (duration) { |
| 316 | updateReasoningUI(this.messageDom, this.reasoning, duration, { forceEnd }); | 376 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); |
| 377 | const secondsStr = moment.duration(duration).asSeconds(); | ||
| 378 | element.innerHTML = t`Thought for <span title="${secondsStr} seconds">${durationStr}</span>`; | ||
| 379 | } else if (this.state === ReasoningState.Thinking) { | ||
| 380 | element.textContent = t`Thinking...`; | ||
| 381 | } else { | ||
| 382 | element.textContent = t`Thought for some time`; | ||
| 383 | } | ||
| 317 | } | 384 | } |
| 318 | } | 385 | } |
| 319 | 386 | ||