mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Add reasoning time duration
- Add reasoning time calculation, saved in message metadata - Add event when reasoning streaming is finished
This commit is contained in:
@@ -495,6 +495,7 @@ export const event_types = {
|
|||||||
/** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
|
/** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
|
||||||
SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
||||||
STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
||||||
|
STREAM_REASONING_DONE: 'stream_reasoning_done',
|
||||||
FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
|
FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
|
||||||
WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
|
WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
|
||||||
OPEN_CHARACTER_LIBRARY: 'open_character_library',
|
OPEN_CHARACTER_LIBRARY: 'open_character_library',
|
||||||
@@ -2223,6 +2224,7 @@ function getMessageFromTemplate({
|
|||||||
avatarImg,
|
avatarImg,
|
||||||
bias,
|
bias,
|
||||||
reasoning,
|
reasoning,
|
||||||
|
reasoningDuration,
|
||||||
isSystem,
|
isSystem,
|
||||||
title,
|
title,
|
||||||
timerValue,
|
timerValue,
|
||||||
@@ -2255,6 +2257,10 @@ function getMessageFromTemplate({
|
|||||||
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
|
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
|
||||||
bookmarkLink && updateBookmarkDisplay(mes);
|
bookmarkLink && updateBookmarkDisplay(mes);
|
||||||
|
|
||||||
|
if (reasoningDuration) {
|
||||||
|
updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (power_user.timestamp_model_icon && extra?.api) {
|
if (power_user.timestamp_model_icon && extra?.api) {
|
||||||
insertSVGIcon(mes, extra);
|
insertSVGIcon(mes, extra);
|
||||||
}
|
}
|
||||||
@@ -2442,6 +2448,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
|||||||
avatarImg: avatarImg,
|
avatarImg: avatarImg,
|
||||||
bias: bias,
|
bias: bias,
|
||||||
reasoning: reasoning,
|
reasoning: reasoning,
|
||||||
|
reasoningDuration: mes.extra?.reasoning_duration,
|
||||||
isSystem: isSystem,
|
isSystem: isSystem,
|
||||||
title: title,
|
title: title,
|
||||||
bookmarkLink: bookmarkLink,
|
bookmarkLink: bookmarkLink,
|
||||||
@@ -3111,8 +3118,12 @@ class StreamingProcessor {
|
|||||||
this.messageDom = null;
|
this.messageDom = null;
|
||||||
this.messageTextDom = null;
|
this.messageTextDom = null;
|
||||||
this.messageTimerDom = null;
|
this.messageTimerDom = null;
|
||||||
|
/** @type {HTMLElement} */
|
||||||
this.messageTokenCounterDom = null;
|
this.messageTokenCounterDom = null;
|
||||||
|
/** @type {HTMLElement} */
|
||||||
this.messageReasoningDom = null;
|
this.messageReasoningDom = null;
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
this.messageReasoningHeaderDom = null;
|
||||||
/** @type {HTMLTextAreaElement} */
|
/** @type {HTMLTextAreaElement} */
|
||||||
this.sendTextarea = document.querySelector('#send_textarea');
|
this.sendTextarea = document.querySelector('#send_textarea');
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -3129,6 +3140,15 @@ class StreamingProcessor {
|
|||||||
this.messageLogprobs = [];
|
this.messageLogprobs = [];
|
||||||
this.toolCalls = [];
|
this.toolCalls = [];
|
||||||
this.reasoning = '';
|
this.reasoning = '';
|
||||||
|
this.reasoningStartTime = null;
|
||||||
|
this.reasoningEndTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reasoningDuration() {
|
||||||
|
if (this.reasoningStartTime && this.reasoningEndTime) {
|
||||||
|
return (this.reasoningEndTime - this.reasoningStartTime);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#checkDomElements(messageId) {
|
#checkDomElements(messageId) {
|
||||||
@@ -3138,6 +3158,7 @@ class StreamingProcessor {
|
|||||||
this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
|
this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
|
||||||
this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
|
this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
|
||||||
this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
|
this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
|
||||||
|
this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3185,7 +3206,7 @@ class StreamingProcessor {
|
|||||||
return messageId;
|
return messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgressStreaming(messageId, text, isFinal) {
|
async onProgressStreaming(messageId, text, isFinal) {
|
||||||
const isImpersonate = this.type == 'impersonate';
|
const isImpersonate = this.type == 'impersonate';
|
||||||
const isContinue = this.type == 'continue';
|
const isContinue = this.type == 'continue';
|
||||||
|
|
||||||
@@ -3212,6 +3233,8 @@ class StreamingProcessor {
|
|||||||
this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
|
this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const mesChanged = chat[messageId]['mes'] !== processedText;
|
||||||
|
|
||||||
this.#checkDomElements(messageId);
|
this.#checkDomElements(messageId);
|
||||||
this.#updateMessageBlockVisibility();
|
this.#updateMessageBlockVisibility();
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
@@ -3224,7 +3247,18 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.reasoning) {
|
if (this.reasoning) {
|
||||||
chat[messageId]['extra']['reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
|
const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
|
||||||
|
const reasoningChanged = chat[messageId]['extra']['reasoning'] !== reasoning;
|
||||||
|
chat[messageId]['extra']['reasoning'] = reasoning;
|
||||||
|
|
||||||
|
if (reasoningChanged && this.reasoningStartTime === null) {
|
||||||
|
this.reasoningStartTime = Date.now();
|
||||||
|
}
|
||||||
|
if (!reasoningChanged && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||||
|
this.reasoningEndTime = Date.now();
|
||||||
|
}
|
||||||
|
await this.#updateReasoningTime(messageId);
|
||||||
|
|
||||||
if (this.messageReasoningDom instanceof HTMLElement) {
|
if (this.messageReasoningDom instanceof HTMLElement) {
|
||||||
const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
|
const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
|
||||||
this.messageReasoningDom.innerHTML = formattedReasoning;
|
this.messageReasoningDom.innerHTML = formattedReasoning;
|
||||||
@@ -3274,11 +3308,24 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #updateReasoningTime(messageId, { forceEnd = false } = {}) {
|
||||||
|
const duration = this.#reasoningDuration();
|
||||||
|
chat[messageId]['extra']['reasoning_duration'] = duration;
|
||||||
|
updateReasoningTimeUI(this.messageReasoningHeaderDom, duration, { forceEnd: forceEnd });
|
||||||
|
await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, duration);
|
||||||
|
}
|
||||||
|
|
||||||
async onFinishStreaming(messageId, text) {
|
async onFinishStreaming(messageId, text) {
|
||||||
this.hideMessageButtons(this.messageId);
|
this.hideMessageButtons(this.messageId);
|
||||||
this.onProgressStreaming(messageId, text, true);
|
await this.onProgressStreaming(messageId, text, true);
|
||||||
addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
|
addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
|
||||||
|
|
||||||
|
// Ensure reasoning finish time is recorded if not already
|
||||||
|
if (this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||||
|
this.reasoningEndTime = Date.now();
|
||||||
|
await this.#updateReasoningTime(messageId, { forceEnd: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(this.swipes) && this.swipes.length > 0) {
|
if (Array.isArray(this.swipes) && this.swipes.length > 0) {
|
||||||
const message = chat[messageId];
|
const message = chat[messageId];
|
||||||
const swipeInfo = {
|
const swipeInfo = {
|
||||||
@@ -3380,7 +3427,7 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
|
this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
|
||||||
await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
|
await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
|
||||||
await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
|
await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text));
|
||||||
}
|
}
|
||||||
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
|
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
|
||||||
console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
|
console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
|
||||||
@@ -5740,6 +5787,24 @@ function extractReasoningFromData(data) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the Reasoning controls
|
||||||
|
* @param {HTMLElement} element The element to update
|
||||||
|
* @param {number?} duration The duration of the reasoning in milliseconds
|
||||||
|
* @param {object} [options={}] Options for the function
|
||||||
|
* @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists
|
||||||
|
*/
|
||||||
|
function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) {
|
||||||
|
if (duration) {
|
||||||
|
element.textContent = t`Thought for ${moment.duration(duration).humanize({ s: 50, ss: 9 })}`;
|
||||||
|
} else if (forceEnd) {
|
||||||
|
element.textContent = t`Thought for some time`;
|
||||||
|
} else {
|
||||||
|
element.textContent = t`Thinking...`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts multiswipe swipes from the response data.
|
* Extracts multiswipe swipes from the response data.
|
||||||
* @param {Object} data Response data
|
* @param {Object} data Response data
|
||||||
|
Reference in New Issue
Block a user