Add reasoning time duration - Add reasoning time calculation, saved in message metadata - Add event when reasoning streaming is finished
| @@ -495,6 +495,7 @@ export const event_types = { | ||
| 495 | 495 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ |
| 496 | 496 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| 497 | 497 | STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| 498 | + STREAM_REASONING_DONE: 'stream_reasoning_done', | |
| 498 | 499 | FILE_ATTACHMENT_DELETED: 'file_attachment_deleted', |
| 499 | 500 | WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate', |
| 500 | 501 | OPEN_CHARACTER_LIBRARY: 'open_character_library', |
| @@ -2223,6 +2224,7 @@ function getMessageFromTemplate({ | ||
| 2223 | 2224 | avatarImg, |
| 2224 | 2225 | bias, |
| 2225 | 2226 | reasoning, |
| 2227 | + reasoningDuration, | |
| 2226 | 2228 | isSystem, |
| 2227 | 2229 | title, |
| 2228 | 2230 | timerValue, |
| @@ -2255,6 +2257,10 @@ function getMessageFromTemplate({ | ||
| 2255 | 2257 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); |
| 2256 | 2258 | bookmarkLink && updateBookmarkDisplay(mes); |
| 2257 | 2259 | |
| 2260 | + if (reasoningDuration) { | |
| 2261 | + updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true }); | |
| 2262 | + } | |
| 2263 | + | |
| 2258 | 2264 | if (power_user.timestamp_model_icon && extra?.api) { |
| 2259 | 2265 | insertSVGIcon(mes, extra); |
| 2260 | 2266 | } |
| @@ -2442,6 +2448,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2442 | 2448 | avatarImg: avatarImg, |
| 2443 | 2449 | bias: bias, |
| 2444 | 2450 | reasoning: reasoning, |
| 2451 | + reasoningDuration: mes.extra?.reasoning_duration, | |
| 2445 | 2452 | isSystem: isSystem, |
| 2446 | 2453 | title: title, |
| 2447 | 2454 | bookmarkLink: bookmarkLink, |
| @@ -3111,8 +3118,12 @@ class StreamingProcessor { | ||
| 3111 | 3118 | this.messageDom = null; |
| 3112 | 3119 | this.messageTextDom = null; |
| 3113 | 3120 | this.messageTimerDom = null; |
| 3121 | + /** @type {HTMLElement} */ | |
| 3114 | 3122 | this.messageTokenCounterDom = null; |
| 3123 | + /** @type {HTMLElement} */ | |
| 3115 | 3124 | this.messageReasoningDom = null; |
| 3125 | + /** @type {HTMLElement} */ | |
| 3126 | + this.messageReasoningHeaderDom = null; | |
| 3116 | 3127 | /** @type {HTMLTextAreaElement} */ |
| 3117 | 3128 | this.sendTextarea = document.querySelector('#send_textarea'); |
| 3118 | 3129 | this.type = type; |
| @@ -3129,6 +3140,15 @@ class StreamingProcessor { | ||
| 3129 | 3140 | this.messageLogprobs = []; |
| 3130 | 3141 | this.toolCalls = []; |
| 3131 | 3142 | this.reasoning = ''; |
| 3143 | + this.reasoningStartTime = null; | |
| 3144 | + this.reasoningEndTime = null; | |
| 3145 | + } | |
| 3146 | + | |
| 3147 | + #reasoningDuration() { | |
| 3148 | + if (this.reasoningStartTime && this.reasoningEndTime) { | |
| 3149 | + return (this.reasoningEndTime - this.reasoningStartTime); | |
| 3150 | + } | |
| 3151 | + return null; | |
| 3132 | 3152 | } |
| 3133 | 3153 | |
| 3134 | 3154 | #checkDomElements(messageId) { |
| @@ -3138,6 +3158,7 @@ class StreamingProcessor { | ||
| 3138 | 3158 | this.messageTimerDom = this.messageDom?.querySelector('.mes_timer'); |
| 3139 | 3159 | this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay'); |
| 3140 | 3160 | this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning'); |
| 3161 | + this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title'); | |
| 3141 | 3162 | } |
| 3142 | 3163 | } |
| 3143 | 3164 | |
| @@ -3185,7 +3206,7 @@ class StreamingProcessor { | ||
| 3185 | 3206 | return messageId; |
| 3186 | 3207 | } |
| 3187 | 3208 | |
| 3188 | 3209 | async onProgressStreaming(messageId, text, isFinal) { |
| 3189 | 3210 | const isImpersonate = this.type == 'impersonate'; |
| 3190 | 3211 | const isContinue = this.type == 'continue'; |
| 3191 | 3212 | |
| @@ -3212,6 +3233,8 @@ class StreamingProcessor { | ||
| 3212 | 3233 | this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true })); |
| 3213 | 3234 | } |
| 3214 | 3235 | else { |
| 3236 | + const mesChanged = chat[messageId]['mes'] !== processedText; | |
| 3237 | + | |
| 3215 | 3238 | this.#checkDomElements(messageId); |
| 3216 | 3239 | this.#updateMessageBlockVisibility(); |
| 3217 | 3240 | const currentTime = new Date(); |
| @@ -3224,7 +3247,18 @@ class StreamingProcessor { | ||
| 3224 | 3247 | } |
| 3225 | 3248 | |
| 3226 | 3249 | if (this.reasoning) { |
| 3227 | 3250 | chat[messageId]['extra']['const reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; |
| 3251 | + const reasoningChanged = chat[messageId]['extra']['reasoning'] !== reasoning; | |
| 3252 | + chat[messageId]['extra']['reasoning'] = reasoning; | |
| 3253 | + | |
| 3254 | + if (reasoningChanged && this.reasoningStartTime === null) { | |
| 3255 | + this.reasoningStartTime = Date.now(); | |
| 3256 | + } | |
| 3257 | + if (!reasoningChanged && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) { | |
| 3258 | + this.reasoningEndTime = Date.now(); | |
| 3259 | + } | |
| 3260 | + await this.#updateReasoningTime(messageId); | |
| 3261 | + | |
| 3228 | 3262 | if (this.messageReasoningDom instanceof HTMLElement) { |
| 3229 | 3263 | const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true); |
| 3230 | 3264 | this.messageReasoningDom.innerHTML = formattedReasoning; |
| @@ -3274,11 +3308,24 @@ class StreamingProcessor { | ||
| 3274 | 3308 | } |
| 3275 | 3309 | } |
| 3276 | 3310 | |
| 3311 | + async #updateReasoningTime(messageId, { forceEnd = false } = {}) { | |
| 3312 | + const duration = this.#reasoningDuration(); | |
| 3313 | + chat[messageId]['extra']['reasoning_duration'] = duration; | |
| 3314 | + updateReasoningTimeUI(this.messageReasoningHeaderDom, duration, { forceEnd: forceEnd }); | |
| 3315 | + await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, duration); | |
| 3316 | + } | |
| 3317 | + | |
| 3277 | 3318 | async onFinishStreaming(messageId, text) { |
| 3278 | 3319 | this.hideMessageButtons(this.messageId); |
| 3279 | 3320 | await this.onProgressStreaming(messageId, text, true); |
| 3280 | 3321 | addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`)); |
| 3281 | 3322 | |
| 3323 | + // Ensure reasoning finish time is recorded if not already | |
| 3324 | + if (this.reasoningStartTime !== null && this.reasoningEndTime === null) { | |
| 3325 | + this.reasoningEndTime = Date.now(); | |
| 3326 | + await this.#updateReasoningTime(messageId, { forceEnd: true }); | |
| 3327 | + } | |
| 3328 | + | |
| 3282 | 3329 | if (Array.isArray(this.swipes) && this.swipes.length > 0) { |
| 3283 | 3330 | const message = chat[messageId]; |
| 3284 | 3331 | const swipeInfo = { |
| @@ -3380,7 +3427,7 @@ class StreamingProcessor { | ||
| 3380 | 3427 | } |
| 3381 | 3428 | this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING); |
| 3382 | 3429 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3383 | 3430 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3384 | 3431 | } |
| 3385 | 3432 | const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000; |
| 3386 | 3433 | console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`); |
| @@ -5741,6 +5788,24 @@ function extractReasoningFromData(data) { | ||
| 5741 | 5788 | } |
| 5742 | 5789 | |
| 5743 | 5790 | /** |
| 5791 | + * Updates the Reasoning controls | |
| 5792 | + * @param {HTMLElement} element The element to update | |
| 5793 | + * @param {number?} duration The duration of the reasoning in milliseconds | |
| 5794 | + * @param {object} [options={}] Options for the function | |
| 5795 | + * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists | |
| 5796 | + */ | |
| 5797 | +function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { | |
| 5798 | + if (duration) { | |
| 5799 | + element.textContent = t`Thought for ${moment.duration(duration).humanize({ s: 50, ss: 9 })}`; | |
| 5800 | + } else if (forceEnd) { | |
| 5801 | + element.textContent = t`Thought for some time`; | |
| 5802 | + } else { | |
| 5803 | + element.textContent = t`Thinking...`; | |
| 5804 | + } | |
| 5805 | +} | |
| 5806 | + | |
| 5807 | + | |
| 5808 | +/** | |
| 5744 | 5809 | * Extracts multiswipe swipes from the response data. |
| 5745 | 5810 | * @param {Object} data Response data |
| 5746 | 5811 | * @param {string} type Type of generation |