More coding on ReasoningHandler, fixing stuff
| @@ -2223,8 +2223,6 @@ function getMessageFromTemplate({ | ||
| 2223 | 2223 | isUser, |
| 2224 | 2224 | avatarImg, |
| 2225 | 2225 | bias, |
| 2226 | - reasoning, | |
| 2227 | - reasoningDuration, | |
| 2228 | 2226 | isSystem, |
| 2229 | 2227 | title, |
| 2230 | 2228 | timerValue, |
| @@ -2256,7 +2254,7 @@ function getMessageFromTemplate({ | ||
| 2256 | 2254 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); |
| 2257 | 2255 | bookmarkLink && updateBookmarkDisplay(mes); |
| 2258 | 2256 | |
| 2259 | - updateReasoningUI(mes, reasoning, reasoningDuration, { forceEnd: true }); | |
| 2257 | + updateReasoningUI(mes); | |
| 2260 | 2258 | |
| 2261 | 2259 | if (power_user.timestamp_model_icon && extra?.api) { |
| 2262 | 2260 | insertSVGIcon(mes, extra); |
| @@ -2279,7 +2277,7 @@ export function updateMessageBlock(messageId, message, { rerenderMessage = true | ||
| 2279 | 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 | 2282 | addCopyToCodeBlocks(messageElement); |
| 2285 | 2283 | appendMediaToMessage(message, messageElement); |
| @@ -2440,7 +2438,6 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2440 | 2438 | false, |
| 2441 | 2439 | ); |
| 2442 | 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 | 2441 | let bookmarkLink = mes?.extra?.bookmark_link ?? ''; |
| 2445 | 2442 | |
| 2446 | 2443 | let params = { |
| @@ -2450,8 +2447,6 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2450 | 2447 | isUser: mes.is_user, |
| 2451 | 2448 | avatarImg: avatarImg, |
| 2452 | 2449 | bias: bias, |
| 2453 | - reasoning: reasoning, | |
| 2454 | - reasoningDuration: mes.extra?.reasoning_duration, | |
| 2455 | 2450 | isSystem: isSystem, |
| 2456 | 2451 | title: title, |
| 2457 | 2452 | bookmarkLink: bookmarkLink, |
| @@ -3144,7 +3139,7 @@ class StreamingProcessor { | ||
| 3144 | 3139 | this.messageLogprobs = []; |
| 3145 | 3140 | this.toolCalls = []; |
| 3146 | 3141 | // Initialize reasoning in its own handler |
| 3147 | 3142 | this.reasoningHandler = new ReasoningHandler(type, timeStarted); |
| 3148 | 3143 | } |
| 3149 | 3144 | |
| 3150 | 3145 | #checkDomElements(messageId) { |
| @@ -3154,7 +3149,7 @@ class StreamingProcessor { | ||
| 3154 | 3149 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); |
| 3155 | 3150 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); |
| 3156 | 3151 | } |
| 3157 | 3152 | this.reasoningHandler.checkDomElementsupdateDom(messageId); |
| 3158 | 3153 | } |
| 3159 | 3154 | |
| 3160 | 3155 | #updateMessageBlockVisibility() { |
| @@ -3378,7 +3373,7 @@ class StreamingProcessor { | ||
| 3378 | 3373 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); |
| 3379 | 3374 | } |
| 3380 | 3375 | // Get the updated reasoning string into the handler |
| 3381 | 3376 | this.reasoningHandler.updateReasoning(this.messageId, state?.reasoning ?? ''); |
| 3382 | 3377 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3383 | 3378 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3384 | 3379 | } |
| @@ -8727,7 +8722,7 @@ const swipe_right = () => { | ||
| 8727 | 8722 | // resets the timer |
| 8728 | 8723 | swipeMessage.find('.mes_timer').html(''); |
| 8729 | 8724 | swipeMessage.find('.tokenCounterDisplay').text(''); |
| 8730 | 8725 | updateReasoningUI(swipeMessage, null); |
| 8731 | 8726 | } else { |
| 8732 | 8727 | //console.log('showing previously generated swipe candidate, or "..."'); |
| 8733 | 8728 | //console.log('onclick right swipe calling addOneMessage'); |
| @@ -117,53 +117,20 @@ export function isHiddenReasoningModel() { | ||
| 117 | 117 | } |
| 118 | 118 | |
| 119 | 119 | /** |
| 120 | 120 | * Updates the Reasoning UI. for a specific message |
| 121 | 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 | 130 | * UpdatesEnum for representing the Reasoningstate controlsof reasoning |
| 149 | 131 | * @paramenum {HTMLElementstring} element The element to update |
| 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 | 134 | export const ReasoningState = { |
| 168 | 135 | None: 'none', |
| 169 | 136 | Thinking: 'thinking', |
| @@ -173,16 +140,15 @@ export const ReasoningState = { | ||
| 173 | 140 | |
| 174 | 141 | /** |
| 175 | 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 | 145 | export class ReasoningHandler { |
| 179 | 146 | #isHiddenisHiddenReasoningModel; |
| 180 | 147 | |
| 181 | 148 | /** |
| 182 | 149 | * @param {stringDate?} type[timeStarted=null] - TheWhen streamingthe typegeneration started |
| 183 | - * @param {Date} timeStarted - When the generation started | |
| 184 | 150 | */ |
| 185 | 151 | constructor(type, timeStarted = null) { |
| 186 | 152 | /** @type {ReasoningState} The current state of the reasoning process */ |
| 187 | 153 | this.state = ReasoningState.None; |
| 188 | 154 | /** @type {string} The reasoning output */ |
| @@ -192,28 +158,69 @@ export class ReasoningHandler { | ||
| 192 | 158 | /** @type {Date} When the reasoning ended */ |
| 193 | 159 | this.endTime = null; |
| 194 | 160 | |
| 195 | - /** @type {string} Generation type (normal, continue, impersonation, etc) */ | |
| 196 | - this.type = type; | |
| 197 | 161 | /** @type {Date} Initial starting time of the generation */ |
| 198 | 162 | this.initialTime = timeStarted ?? new Date(); |
| 199 | 163 | |
| 200 | 164 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ |
| 201 | 165 | this.#isHiddenisHiddenReasoningModel = isHiddenReasoningModel(); |
| 202 | 166 | |
| 203 | 167 | // Cached DOM elements for reasoning |
| 204 | 168 | /** @type {HTMLElement} Main message DOM element `.mes` */ |
| 205 | 169 | this.messageDom = null; |
| 206 | 170 | /** @type {HTMLElement} Reasoning details DOM element `.mes_reasoning_details` */ |
| 207 | 171 | this.messageReasoningDetailsDom = null; |
| 208 | 172 | /** @type {HTMLElement} Reasoning content DOM element `.mes_reasoning_contentmes_reasoning` */ |
| 209 | 173 | this.messageReasoningContentDom = null; |
| 210 | 174 | /** @type {HTMLElement} Reasoning header DOM element `.mes_reasoning_headermes_reasoning_header_title` */ |
| 211 | 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 | 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 | 225 | getDuration() { |
| 219 | 226 | if (this.startTime && this.endTime) { |
| @@ -223,97 +230,157 @@ export class ReasoningHandler { | ||
| 223 | 230 | } |
| 224 | 231 | |
| 225 | 232 | /** |
| 226 | 233 | * Finds andUpdates cachesthe reasoning-related DOM elementstext/string for the givena 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 | 263 | * ProcessesHandles andprocessing updatesof reasoning info for thea message. |
| 262 | - * @param {number} messageId - The ID of the message. | |
| 264 | + * | |
| 263 | 265 | * @param {boolean}This mesChangedis -usually Truecalled ifby the message textprocessor 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 | 271 | async process(messageId, mesChanged, currentTime) { |
| 267 | 272 | if (!this.reasoning && !this.#isHiddenisHiddenReasoningModel) 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 | 279 | this.startTime = this.initialTime; |
| 282 | 280 | } |
| 283 | 281 | if ((this.#isHiddenisHiddenReasoningModel || !reasoningChanged) && mesChanged && this.startTime !== null && this.endTimestate === nullReasoningState.Thinking) { |
| 284 | 282 | this.endTime = currentTimenew 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 | 296 | async finish(messageId) { |
| 297 | + if (this.state === ReasoningState.None) return; | |
| 298 | + | |
| 298 | 299 | // Make sure the finish time is recorded if a reasoning was in process and it wasn't ended correctly during streaming |
| 299 | 300 | if (this.startTime !== null && this.endTime === null) { |
| 300 | 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 | 367 | * Updates the reasoning durationtime 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 | 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 | |