mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-29 10:20:10 +01:00
Add thinking time for hidden reasoning models
- Streamline reasoning UI update functionality - Add helper function to identify hidden reasoning models - Fix/update reasoning time calculation to actually utilize start gen time - Fix reasoning UI update on swipe - add CSS class for hidden reasoning blocks (to make it possible to hide for users)
This commit is contained in:
parent
5886bb6b3a
commit
d94ac48b65
@ -113,6 +113,7 @@ import {
|
||||
loadProxyPresets,
|
||||
selected_proxy,
|
||||
initOpenAI,
|
||||
isHiddenReasoningModel,
|
||||
} from './scripts/openai.js';
|
||||
|
||||
import {
|
||||
@ -269,7 +270,7 @@ import { initSettingsSearch } from './scripts/setting-search.js';
|
||||
import { initBulkEdit } from './scripts/bulk-edit.js';
|
||||
import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
|
||||
import { getContext } from './scripts/st-context.js';
|
||||
import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI } from './scripts/reasoning.js';
|
||||
import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI, updateReasoningUI } from './scripts/reasoning.js';
|
||||
|
||||
// API OBJECT FOR EXTERNAL WIRING
|
||||
globalThis.SillyTavern = {
|
||||
@ -2249,7 +2250,6 @@ function getMessageFromTemplate({
|
||||
mes.find('.avatar img').attr('src', avatarImg);
|
||||
mes.find('.ch_name .name_text').text(characterName);
|
||||
mes.find('.mes_bias').html(bias);
|
||||
mes.find('.mes_reasoning').html(reasoning);
|
||||
mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
|
||||
mes.find('.mesIDDisplay').text(`#${mesId}`);
|
||||
tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
|
||||
@ -2257,12 +2257,7 @@ function getMessageFromTemplate({
|
||||
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
|
||||
bookmarkLink && updateBookmarkDisplay(mes);
|
||||
|
||||
if (reasoning) {
|
||||
mes.addClass('reasoning');
|
||||
}
|
||||
if (reasoningDuration) {
|
||||
updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true });
|
||||
}
|
||||
updateReasoningUI(mes, reasoning, reasoningDuration, { forceEnd: true });
|
||||
|
||||
if (power_user.timestamp_model_icon && extra?.api) {
|
||||
insertSVGIcon(mes, extra);
|
||||
@ -2284,8 +2279,9 @@ export function updateMessageBlock(messageId, message, { rerenderMessage = true
|
||||
const text = message?.extra?.display_text ?? message.mes;
|
||||
messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false));
|
||||
}
|
||||
messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true));
|
||||
messageElement.toggleClass('reasoning', !!message.extra?.reasoning);
|
||||
|
||||
updateReasoningUI(messageElement, message?.extra?.reasoning, message?.extra?.reasoning_duration, { forceEnd: true });
|
||||
|
||||
addCopyToCodeBlocks(messageElement);
|
||||
appendMediaToMessage(message, messageElement);
|
||||
}
|
||||
@ -3123,8 +3119,11 @@ class StreamingProcessor {
|
||||
constructor(type, forceName2, timeStarted, continueMessage) {
|
||||
this.result = '';
|
||||
this.messageId = -1;
|
||||
/** @type {HTMLElement} */
|
||||
this.messageDom = null;
|
||||
/** @type {HTMLElement} */
|
||||
this.messageTextDom = null;
|
||||
/** @type {HTMLElement} */
|
||||
this.messageTimerDom = null;
|
||||
/** @type {HTMLElement} */
|
||||
this.messageTokenCounterDom = null;
|
||||
@ -3148,13 +3147,17 @@ class StreamingProcessor {
|
||||
this.messageLogprobs = [];
|
||||
this.toolCalls = [];
|
||||
this.reasoning = '';
|
||||
/** @type {Date} */
|
||||
this.reasoningStartTime = null;
|
||||
/** @type {Date} */
|
||||
this.reasoningEndTime = null;
|
||||
this.isHiddenReasoning = isHiddenReasoningModel();
|
||||
}
|
||||
|
||||
/** @type {() => number} Reasoning duration in milliseconds */
|
||||
#reasoningDuration() {
|
||||
if (this.reasoningStartTime && this.reasoningEndTime) {
|
||||
return (this.reasoningEndTime - this.reasoningStartTime);
|
||||
return (this.reasoningEndTime.getTime() - this.reasoningStartTime.getTime());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -3168,6 +3171,8 @@ class StreamingProcessor {
|
||||
this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
|
||||
this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title');
|
||||
}
|
||||
|
||||
this.messageDom.classList.toggle('reasoning_hidden', this.isHiddenReasoning);
|
||||
}
|
||||
|
||||
#updateMessageBlockVisibility() {
|
||||
@ -3254,16 +3259,17 @@ class StreamingProcessor {
|
||||
chat[messageId]['extra'] = {};
|
||||
}
|
||||
|
||||
if (this.reasoning) {
|
||||
if (this.reasoning || this.isHiddenReasoning) {
|
||||
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 ((this.isHiddenReasoning || reasoningChanged) && this.reasoningStartTime === null) {
|
||||
this.reasoningStartTime = this.timeStarted;
|
||||
}
|
||||
if (!reasoningChanged && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||
this.reasoningEndTime = Date.now();
|
||||
if ((this.isHiddenReasoning || !reasoningChanged) && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||
this.reasoningEndTime = currentTime;
|
||||
await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.#reasoningDuration);
|
||||
}
|
||||
await this.#updateReasoningTime(messageId);
|
||||
|
||||
@ -3322,8 +3328,7 @@ 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);
|
||||
updateReasoningUI(this.messageDom, this.reasoning, duration, { forceEnd: forceEnd });
|
||||
}
|
||||
|
||||
async onFinishStreaming(messageId, text) {
|
||||
@ -3333,7 +3338,8 @@ class StreamingProcessor {
|
||||
|
||||
// Ensure reasoning finish time is recorded if not already
|
||||
if (this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||
this.reasoningEndTime = Date.now();
|
||||
this.reasoningEndTime = new Date();
|
||||
await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.#reasoningDuration);
|
||||
await this.#updateReasoningTime(messageId, { forceEnd: true });
|
||||
}
|
||||
|
||||
@ -8785,7 +8791,7 @@ const swipe_right = () => {
|
||||
// resets the timer
|
||||
swipeMessage.find('.mes_timer').html('');
|
||||
swipeMessage.find('.tokenCounterDisplay').text('');
|
||||
swipeMessage.find('.mes_reasoning').html('');
|
||||
updateReasoningUI(swipeMessage, null);
|
||||
} else {
|
||||
//console.log('showing previously generated swipe candidate, or "..."');
|
||||
//console.log('onclick right swipe calling addOneMessage');
|
||||
|
@ -4995,6 +4995,53 @@ export function isImageInliningSupported() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if the model supports reasoning, but does not send back the reasoning
|
||||
* @returns {boolean} True if the model supports reasoning
|
||||
*/
|
||||
export function isHiddenReasoningModel() {
|
||||
if (main_api !== 'openai') {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @typedef {Object.<chat_completion_sources, { currentModel: string; models: ({ name: string; startsWith: boolean?; matchingFunc: (model: string) => boolean?; }|string)[]; }>} */
|
||||
const hiddenReasoningModels = {
|
||||
[chat_completion_sources.OPENAI]: {
|
||||
currentModel: oai_settings.openai_model,
|
||||
models: [
|
||||
{ name: 'o1', startsWith: true },
|
||||
{ name: 'o3', startsWith: true },
|
||||
],
|
||||
},
|
||||
[chat_completion_sources.MAKERSUITE]: {
|
||||
currentModel: oai_settings.google_model,
|
||||
models: [
|
||||
{ name: 'gemini-2.0-flash-thinking-exp', startsWith: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const sourceConfig = hiddenReasoningModels[oai_settings.chat_completion_source];
|
||||
if (!sourceConfig) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sourceConfig.models.some(model => {
|
||||
if (typeof model === 'string') {
|
||||
return sourceConfig.currentModel === model;
|
||||
}
|
||||
if (model.startsWith) {
|
||||
return (sourceConfig.currentModel).startsWith(model.name);
|
||||
}
|
||||
if (model.matchingFunc) {
|
||||
return model.matchingFunc(sourceConfig.currentModel);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy stuff
|
||||
*/
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {
|
||||
moment,
|
||||
} from '../lib.js';
|
||||
import { chat, closeMessageEditor, event_types, eventSource, main_api, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
|
||||
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
|
||||
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
|
||||
import { getCurrentLocale, t } from './i18n.js';
|
||||
import { MacrosParser } from './macros.js';
|
||||
import { chat_completion_sources, oai_settings } from './openai.js';
|
||||
import { chat_completion_sources, isHiddenReasoningModel, oai_settings } from './openai.js';
|
||||
import { Popup } from './popup.js';
|
||||
import { power_user } from './power-user.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
@ -70,6 +70,34 @@ export function extractReasoningFromData(data) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Reasoning UI.
|
||||
* @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement The message ID or the message element.
|
||||
* @param {string|null} [reasoning=null] The reasoning content.
|
||||
* @param {number|null} [reasoningDuration=null] 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.
|
||||
*/
|
||||
export function updateReasoningUI(messageIdOrElement, reasoning = null, reasoningDuration = null, { forceEnd = false } = {}) {
|
||||
const messageElement = typeof messageIdOrElement === 'number'
|
||||
? $(`#chat [mesid="${messageIdOrElement}"]`)
|
||||
: $(messageIdOrElement);
|
||||
const mesReasoningElement = messageElement.find('.mes_reasoning');
|
||||
const mesReasoningHeaderTitle = messageElement.find('.mes_reasoning_header_title');
|
||||
const mesId = Number(messageElement.attr('mesid'));
|
||||
|
||||
mesReasoningElement.html(messageFormatting(reasoning ?? '', '', false, false, mesId, {}, true));
|
||||
const reasoningText = mesReasoningElement.text().trim();
|
||||
|
||||
const hasReasoningText = !!reasoningText;
|
||||
const isReasoningHidden = (!!reasoningDuration && !hasReasoningText) || (!forceEnd && isHiddenReasoningModel());
|
||||
const isReasoning = hasReasoningText || isReasoningHidden;
|
||||
|
||||
messageElement.toggleClass('reasoning', isReasoning);
|
||||
messageElement.toggleClass('reasoning_hidden', isReasoningHidden);
|
||||
updateReasoningTimeUI(mesReasoningHeaderTitle[0], reasoningDuration, { forceEnd });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Reasoning controls
|
||||
* @param {HTMLElement} element The element to update
|
||||
|
@ -413,10 +413,20 @@ input[type='checkbox']:focus-visible {
|
||||
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_header,
|
||||
.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .edit_button,
|
||||
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.edit_button),
|
||||
.mes_block:has(.edit_textarea):has(.reasoning_edit_textarea) .mes_reasoning_actions {
|
||||
.mes_block:has(.edit_textarea):has(.reasoning_edit_textarea) .mes_reasoning_actions,
|
||||
.mes.reasoning:not(.reasoning_hidden) .mes_edit_add_reasoning,
|
||||
.mes.reasoning_hidden .mes_reasoning_arrow,
|
||||
.mes.reasoning_hidden .mes_reasoning_actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mes.reasoning_hidden .mes_reasoning_details {
|
||||
display: block;
|
||||
}
|
||||
.mes.reasoning_hidden .mes_reasoning_header {
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.mes_reasoning_details .mes_reasoning_arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -4315,10 +4325,6 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
field-sizing: content;
|
||||
}
|
||||
|
||||
.mes.reasoning .mes_edit_add_reasoning {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#anchor_order {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user