Add reasoning time duration - Add reasoning time calculation, saved in message metadata - Add event when reasoning streaming is finished

3818290e4d28a37538fcb53c37a2b4e5504fb307

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +69 -4Showing whitespace changes
public/script.js+69 -4
@@ -495,6 +495,7 @@ export const event_types = {
495 /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */495 /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
496 SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',496 SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
497 STREAM_TOKEN_RECEIVED: 'stream_token_received',497 STREAM_TOKEN_RECEIVED: 'stream_token_received',
498 STREAM_REASONING_DONE: 'stream_reasoning_done',
498 FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',499 FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
499 WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',500 WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
500 OPEN_CHARACTER_LIBRARY: 'open_character_library',501 OPEN_CHARACTER_LIBRARY: 'open_character_library',
@@ -2223,6 +2224,7 @@ function getMessageFromTemplate({
2223 avatarImg,2224 avatarImg,
2224 bias,2225 bias,
2225 reasoning,2226 reasoning,
2227 reasoningDuration,
2226 isSystem,2228 isSystem,
2227 title,2229 title,
2228 timerValue,2230 timerValue,
@@ -2255,6 +2257,10 @@ function getMessageFromTemplate({
2255 timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);2257 timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
2256 bookmarkLink && updateBookmarkDisplay(mes);2258 bookmarkLink && updateBookmarkDisplay(mes);
22572259
2260 if (reasoningDuration) {
2261 updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true });
2262 }
2263
2258 if (power_user.timestamp_model_icon && extra?.api) {2264 if (power_user.timestamp_model_icon && extra?.api) {
2259 insertSVGIcon(mes, extra);2265 insertSVGIcon(mes, extra);
2260 }2266 }
@@ -2442,6 +2448,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2442 avatarImg: avatarImg,2448 avatarImg: avatarImg,
2443 bias: bias,2449 bias: bias,
2444 reasoning: reasoning,2450 reasoning: reasoning,
2451 reasoningDuration: mes.extra?.reasoning_duration,
2445 isSystem: isSystem,2452 isSystem: isSystem,
2446 title: title,2453 title: title,
2447 bookmarkLink: bookmarkLink,2454 bookmarkLink: bookmarkLink,
@@ -3111,8 +3118,12 @@ class StreamingProcessor {
3111 this.messageDom = null;3118 this.messageDom = null;
3112 this.messageTextDom = null;3119 this.messageTextDom = null;
3113 this.messageTimerDom = null;3120 this.messageTimerDom = null;
3121 /** @type {HTMLElement} */
3114 this.messageTokenCounterDom = null;3122 this.messageTokenCounterDom = null;
3123 /** @type {HTMLElement} */
3115 this.messageReasoningDom = null;3124 this.messageReasoningDom = null;
3125 /** @type {HTMLElement} */
3126 this.messageReasoningHeaderDom = null;
3116 /** @type {HTMLTextAreaElement} */3127 /** @type {HTMLTextAreaElement} */
3117 this.sendTextarea = document.querySelector('#send_textarea');3128 this.sendTextarea = document.querySelector('#send_textarea');
3118 this.type = type;3129 this.type = type;
@@ -3129,6 +3140,15 @@ class StreamingProcessor {
3129 this.messageLogprobs = [];3140 this.messageLogprobs = [];
3130 this.toolCalls = [];3141 this.toolCalls = [];
3131 this.reasoning = '';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 }
31333153
3134 #checkDomElements(messageId) {3154 #checkDomElements(messageId) {
@@ -3138,6 +3158,7 @@ class StreamingProcessor {
3138 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');3158 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
3139 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');3159 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
3140 this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');3160 this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
3161 this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title');
3141 }3162 }
3142 }3163 }
31433164
@@ -3185,7 +3206,7 @@ class StreamingProcessor {
3185 return messageId;3206 return messageId;
3186 }3207 }
31873208
3188 onProgressStreaming(messageId, text, isFinal) {3209 async onProgressStreaming(messageId, text, isFinal) {
3189 const isImpersonate = this.type == 'impersonate';3210 const isImpersonate = this.type == 'impersonate';
3190 const isContinue = this.type == 'continue';3211 const isContinue = this.type == 'continue';
31913212
@@ -3212,6 +3233,8 @@ class StreamingProcessor {
3212 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));3233 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
3213 }3234 }
3214 else {3235 else {
3236 const mesChanged = chat[messageId]['mes'] !== processedText;
3237
3215 this.#checkDomElements(messageId);3238 this.#checkDomElements(messageId);
3216 this.#updateMessageBlockVisibility();3239 this.#updateMessageBlockVisibility();
3217 const currentTime = new Date();3240 const currentTime = new Date();
@@ -3224,7 +3247,18 @@ class StreamingProcessor {
3224 }3247 }
32253248
3226 if (this.reasoning) {3249 if (this.reasoning) {
3227 chat[messageId]['extra']['reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;3250 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 if (this.messageReasoningDom instanceof HTMLElement) {3262 if (this.messageReasoningDom instanceof HTMLElement) {
3229 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);3263 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
3230 this.messageReasoningDom.innerHTML = formattedReasoning;3264 this.messageReasoningDom.innerHTML = formattedReasoning;
@@ -3274,11 +3308,24 @@ class StreamingProcessor {
3274 }3308 }
3275 }3309 }
32763310
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 async onFinishStreaming(messageId, text) {3318 async onFinishStreaming(messageId, text) {
3278 this.hideMessageButtons(this.messageId);3319 this.hideMessageButtons(this.messageId);
3279 this.onProgressStreaming(messageId, text, true);3320 await this.onProgressStreaming(messageId, text, true);
3280 addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));3321 addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
32813322
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 if (Array.isArray(this.swipes) && this.swipes.length > 0) {3329 if (Array.isArray(this.swipes) && this.swipes.length > 0) {
3283 const message = chat[messageId];3330 const message = chat[messageId];
3284 const swipeInfo = {3331 const swipeInfo = {
@@ -3380,7 +3427,7 @@ class StreamingProcessor {
3380 }3427 }
3381 this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);3428 this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
3382 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);3429 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
3383 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));3430 await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text));
3384 }3431 }
3385 const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;3432 const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
3386 console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);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}
57425789
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 */
5797function 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 * Extracts multiswipe swipes from the response data.5809 * Extracts multiswipe swipes from the response data.
5745 * @param {Object} data Response data5810 * @param {Object} data Response data
5746 * @param {string} type Type of generation5811 * @param {string} type Type of generation