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 = {
495495 /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
496496 SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
497497 STREAM_TOKEN_RECEIVED: 'stream_token_received',
498+ STREAM_REASONING_DONE: 'stream_reasoning_done',
498499 FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
499500 WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
500501 OPEN_CHARACTER_LIBRARY: 'open_character_library',
@@ -2223,6 +2224,7 @@ function getMessageFromTemplate({
22232224 avatarImg,
22242225 bias,
22252226 reasoning,
2227+ reasoningDuration,
22262228 isSystem,
22272229 title,
22282230 timerValue,
@@ -2255,6 +2257,10 @@ function getMessageFromTemplate({
22552257 timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
22562258 bookmarkLink && updateBookmarkDisplay(mes);
22572259
2260+ if (reasoningDuration) {
2261+ updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true });
2262+ }
2263+
22582264 if (power_user.timestamp_model_icon && extra?.api) {
22592265 insertSVGIcon(mes, extra);
22602266 }
@@ -2442,6 +2448,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24422448 avatarImg: avatarImg,
24432449 bias: bias,
24442450 reasoning: reasoning,
2451+ reasoningDuration: mes.extra?.reasoning_duration,
24452452 isSystem: isSystem,
24462453 title: title,
24472454 bookmarkLink: bookmarkLink,
@@ -3111,8 +3118,12 @@ class StreamingProcessor {
31113118 this.messageDom = null;
31123119 this.messageTextDom = null;
31133120 this.messageTimerDom = null;
3121+ /** @type {HTMLElement} */
31143122 this.messageTokenCounterDom = null;
3123+ /** @type {HTMLElement} */
31153124 this.messageReasoningDom = null;
3125+ /** @type {HTMLElement} */
3126+ this.messageReasoningHeaderDom = null;
31163127 /** @type {HTMLTextAreaElement} */
31173128 this.sendTextarea = document.querySelector('#send_textarea');
31183129 this.type = type;
@@ -3129,6 +3140,15 @@ class StreamingProcessor {
31293140 this.messageLogprobs = [];
31303141 this.toolCalls = [];
31313142 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;
31323152 }
31333153
31343154 #checkDomElements(messageId) {
@@ -3138,6 +3158,7 @@ class StreamingProcessor {
31383158 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
31393159 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
31403160 this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
3161+ this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title');
31413162 }
31423163 }
31433164
@@ -3185,7 +3206,7 @@ class StreamingProcessor {
31853206 return messageId;
31863207 }
31873208
31883209 async onProgressStreaming(messageId, text, isFinal) {
31893210 const isImpersonate = this.type == 'impersonate';
31903211 const isContinue = this.type == 'continue';
31913212
@@ -3212,6 +3233,8 @@ class StreamingProcessor {
32123233 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
32133234 }
32143235 else {
3236+ const mesChanged = chat[messageId]['mes'] !== processedText;
3237+
32153238 this.#checkDomElements(messageId);
32163239 this.#updateMessageBlockVisibility();
32173240 const currentTime = new Date();
@@ -3224,7 +3247,18 @@ class StreamingProcessor {
32243247 }
32253248
32263249 if (this.reasoning) {
32273250 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+
32283262 if (this.messageReasoningDom instanceof HTMLElement) {
32293263 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
32303264 this.messageReasoningDom.innerHTML = formattedReasoning;
@@ -3274,11 +3308,24 @@ class StreamingProcessor {
32743308 }
32753309 }
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+
32773318 async onFinishStreaming(messageId, text) {
32783319 this.hideMessageButtons(this.messageId);
32793320 await this.onProgressStreaming(messageId, text, true);
32803321 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+
32823329 if (Array.isArray(this.swipes) && this.swipes.length > 0) {
32833330 const message = chat[messageId];
32843331 const swipeInfo = {
@@ -3380,7 +3427,7 @@ class StreamingProcessor {
33803427 }
33813428 this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
33823429 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
33833430 await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text));
33843431 }
33853432 const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
33863433 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) {
57415788}
57425789
57435790/**
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+/**
57445809 * Extracts multiswipe swipes from the response data.
57455810 * @param {Object} data Response data
57465811 * @param {string} type Type of generation