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)
| @@ -113,6 +113,7 @@ import { | ||
| 113 | 113 | loadProxyPresets, |
| 114 | 114 | selected_proxy, |
| 115 | 115 | initOpenAI, |
| 116 | + isHiddenReasoningModel, | |
| 116 | 117 | } from './scripts/openai.js'; |
| 117 | 118 | |
| 118 | 119 | import { |
| @@ -269,7 +270,7 @@ import { initSettingsSearch } from './scripts/setting-search.js'; | ||
| 269 | 270 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 270 | 271 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 271 | 272 | import { getContext } from './scripts/st-context.js'; |
| 272 | 273 | import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI, updateReasoningUI } from './scripts/reasoning.js'; |
| 273 | 274 | |
| 274 | 275 | // API OBJECT FOR EXTERNAL WIRING |
| 275 | 276 | globalThis.SillyTavern = { |
| @@ -2249,7 +2250,6 @@ function getMessageFromTemplate({ | ||
| 2249 | 2250 | mes.find('.avatar img').attr('src', avatarImg); |
| 2250 | 2251 | mes.find('.ch_name .name_text').text(characterName); |
| 2251 | 2252 | mes.find('.mes_bias').html(bias); |
| 2252 | - mes.find('.mes_reasoning').html(reasoning); | |
| 2253 | 2253 | mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`); |
| 2254 | 2254 | mes.find('.mesIDDisplay').text(`#${mesId}`); |
| 2255 | 2255 | tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`); |
| @@ -2257,12 +2257,7 @@ function getMessageFromTemplate({ | ||
| 2257 | 2257 | timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue); |
| 2258 | 2258 | bookmarkLink && updateBookmarkDisplay(mes); |
| 2259 | 2259 | |
| 2260 | - if (reasoning) { | |
| 2260 | + updateReasoningUI(mes, reasoning, reasoningDuration, { forceEnd: true }); | |
| 2261 | - mes.addClass('reasoning'); | |
| 2262 | - } | |
| 2263 | - if (reasoningDuration) { | |
| 2264 | - updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true }); | |
| 2265 | - } | |
| 2266 | 2261 | |
| 2267 | 2262 | if (power_user.timestamp_model_icon && extra?.api) { |
| 2268 | 2263 | insertSVGIcon(mes, extra); |
| @@ -2284,8 +2279,9 @@ export function updateMessageBlock(messageId, message, { rerenderMessage = true | ||
| 2284 | 2279 | const text = message?.extra?.display_text ?? message.mes; |
| 2285 | 2280 | messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false)); |
| 2286 | 2281 | } |
| 2287 | - messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true)); | |
| 2282 | + | |
| 2288 | - messageElement.toggleClass('reasoning', !!message.extra?.reasoning); | |
| 2283 | + updateReasoningUI(messageElement, message?.extra?.reasoning, message?.extra?.reasoning_duration, { forceEnd: true }); | |
| 2284 | + | |
| 2289 | 2285 | addCopyToCodeBlocks(messageElement); |
| 2290 | 2286 | appendMediaToMessage(message, messageElement); |
| 2291 | 2287 | } |
| @@ -3123,8 +3119,11 @@ class StreamingProcessor { | ||
| 3123 | 3119 | constructor(type, forceName2, timeStarted, continueMessage) { |
| 3124 | 3120 | this.result = ''; |
| 3125 | 3121 | this.messageId = -1; |
| 3122 | + /** @type {HTMLElement} */ | |
| 3126 | 3123 | this.messageDom = null; |
| 3124 | + /** @type {HTMLElement} */ | |
| 3127 | 3125 | this.messageTextDom = null; |
| 3126 | + /** @type {HTMLElement} */ | |
| 3128 | 3127 | this.messageTimerDom = null; |
| 3129 | 3128 | /** @type {HTMLElement} */ |
| 3130 | 3129 | this.messageTokenCounterDom = null; |
| @@ -3148,13 +3147,17 @@ class StreamingProcessor { | ||
| 3148 | 3147 | this.messageLogprobs = []; |
| 3149 | 3148 | this.toolCalls = []; |
| 3150 | 3149 | this.reasoning = ''; |
| 3150 | + /** @type {Date} */ | |
| 3151 | 3151 | this.reasoningStartTime = null; |
| 3152 | + /** @type {Date} */ | |
| 3152 | 3153 | this.reasoningEndTime = null; |
| 3154 | + this.isHiddenReasoning = isHiddenReasoningModel(); | |
| 3153 | 3155 | } |
| 3154 | 3156 | |
| 3157 | + /** @type {() => number} Reasoning duration in milliseconds */ | |
| 3155 | 3158 | #reasoningDuration() { |
| 3156 | 3159 | if (this.reasoningStartTime && this.reasoningEndTime) { |
| 3157 | 3160 | return (this.reasoningEndTime.getTime() - this.reasoningStartTime.getTime()); |
| 3158 | 3161 | } |
| 3159 | 3162 | return null; |
| 3160 | 3163 | } |
| @@ -3168,6 +3171,8 @@ class StreamingProcessor { | ||
| 3168 | 3171 | this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning'); |
| 3169 | 3172 | this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title'); |
| 3170 | 3173 | } |
| 3174 | + | |
| 3175 | + this.messageDom.classList.toggle('reasoning_hidden', this.isHiddenReasoning); | |
| 3171 | 3176 | } |
| 3172 | 3177 | |
| 3173 | 3178 | #updateMessageBlockVisibility() { |
| @@ -3254,16 +3259,17 @@ class StreamingProcessor { | ||
| 3254 | 3259 | chat[messageId]['extra'] = {}; |
| 3255 | 3260 | } |
| 3256 | 3261 | |
| 3257 | 3262 | if (this.reasoning || this.isHiddenReasoning) { |
| 3258 | 3263 | const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; |
| 3259 | 3264 | const reasoningChanged = chat[messageId]['extra']['reasoning'] !== reasoning; |
| 3260 | 3265 | chat[messageId]['extra']['reasoning'] = reasoning; |
| 3261 | 3266 | |
| 3262 | 3267 | if ((this.isHiddenReasoning || reasoningChanged) && this.reasoningStartTime === null) { |
| 3263 | 3268 | this.reasoningStartTime = Datethis.now()timeStarted; |
| 3264 | 3269 | } |
| 3265 | 3270 | if ((this.isHiddenReasoning || !reasoningChanged) && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) { |
| 3266 | 3271 | this.reasoningEndTime = Date.now()currentTime; |
| 3272 | + await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.#reasoningDuration); | |
| 3267 | 3273 | } |
| 3268 | 3274 | await this.#updateReasoningTime(messageId); |
| 3269 | 3275 | |
| @@ -3322,8 +3328,7 @@ class StreamingProcessor { | ||
| 3322 | 3328 | async #updateReasoningTime(messageId, { forceEnd = false } = {}) { |
| 3323 | 3329 | const duration = this.#reasoningDuration(); |
| 3324 | 3330 | chat[messageId]['extra']['reasoning_duration'] = duration; |
| 3325 | 3331 | updateReasoningTimeUIupdateReasoningUI(this.messageReasoningHeaderDommessageDom, this.reasoning, duration, { forceEnd: forceEnd }); |
| 3326 | - await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, duration); | |
| 3327 | 3332 | } |
| 3328 | 3333 | |
| 3329 | 3334 | async onFinishStreaming(messageId, text) { |
| @@ -3333,7 +3338,8 @@ class StreamingProcessor { | ||
| 3333 | 3338 | |
| 3334 | 3339 | // Ensure reasoning finish time is recorded if not already |
| 3335 | 3340 | if (this.reasoningStartTime !== null && this.reasoningEndTime === null) { |
| 3336 | 3341 | this.reasoningEndTime = new Date.now(); |
| 3342 | + await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, this.#reasoningDuration); | |
| 3337 | 3343 | await this.#updateReasoningTime(messageId, { forceEnd: true }); |
| 3338 | 3344 | } |
| 3339 | 3345 | |
| @@ -8785,7 +8791,7 @@ const swipe_right = () => { | ||
| 8785 | 8791 | // resets the timer |
| 8786 | 8792 | swipeMessage.find('.mes_timer').html(''); |
| 8787 | 8793 | swipeMessage.find('.tokenCounterDisplay').text(''); |
| 8788 | - swipeMessage.find('.mes_reasoning').html(''); | |
| 8794 | + updateReasoningUI(swipeMessage, null); | |
| 8789 | 8795 | } else { |
| 8790 | 8796 | //console.log('showing previously generated swipe candidate, or "..."'); |
| 8791 | 8797 | //console.log('onclick right swipe calling addOneMessage'); |
| @@ -4995,6 +4995,53 @@ export function isImageInliningSupported() { | ||
| 4995 | 4995 | } |
| 4996 | 4996 | } |
| 4997 | 4997 | |
| 4998 | + | |
| 4999 | + | |
| 5000 | +/** | |
| 5001 | + * Check if the model supports reasoning, but does not send back the reasoning | |
| 5002 | + * @returns {boolean} True if the model supports reasoning | |
| 5003 | + */ | |
| 5004 | +export function isHiddenReasoningModel() { | |
| 5005 | + if (main_api !== 'openai') { | |
| 5006 | + return false; | |
| 5007 | + } | |
| 5008 | + | |
| 5009 | + /** @typedef {Object.<chat_completion_sources, { currentModel: string; models: ({ name: string; startsWith: boolean?; matchingFunc: (model: string) => boolean?; }|string)[]; }>} */ | |
| 5010 | + const hiddenReasoningModels = { | |
| 5011 | + [chat_completion_sources.OPENAI]: { | |
| 5012 | + currentModel: oai_settings.openai_model, | |
| 5013 | + models: [ | |
| 5014 | + { name: 'o1', startsWith: true }, | |
| 5015 | + { name: 'o3', startsWith: true }, | |
| 5016 | + ], | |
| 5017 | + }, | |
| 5018 | + [chat_completion_sources.MAKERSUITE]: { | |
| 5019 | + currentModel: oai_settings.google_model, | |
| 5020 | + models: [ | |
| 5021 | + { name: 'gemini-2.0-flash-thinking-exp', startsWith: true }, | |
| 5022 | + ], | |
| 5023 | + }, | |
| 5024 | + }; | |
| 5025 | + | |
| 5026 | + const sourceConfig = hiddenReasoningModels[oai_settings.chat_completion_source]; | |
| 5027 | + if (!sourceConfig) { | |
| 5028 | + return false; | |
| 5029 | + } | |
| 5030 | + | |
| 5031 | + return sourceConfig.models.some(model => { | |
| 5032 | + if (typeof model === 'string') { | |
| 5033 | + return sourceConfig.currentModel === model; | |
| 5034 | + } | |
| 5035 | + if (model.startsWith) { | |
| 5036 | + return (sourceConfig.currentModel).startsWith(model.name); | |
| 5037 | + } | |
| 5038 | + if (model.matchingFunc) { | |
| 5039 | + return model.matchingFunc(sourceConfig.currentModel); | |
| 5040 | + } | |
| 5041 | + return false; | |
| 5042 | + }); | |
| 5043 | +} | |
| 5044 | + | |
| 4998 | 5045 | /** |
| 4999 | 5046 | * Proxy stuff |
| 5000 | 5047 | */ |
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | import { |
| 2 | 2 | moment, |
| 3 | 3 | } from '../lib.js'; |
| 4 | 4 | import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; |
| 5 | 5 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 6 | 6 | import { getCurrentLocale, t } from './i18n.js'; |
| 7 | 7 | import { MacrosParser } from './macros.js'; |
| 8 | 8 | import { chat_completion_sources, isHiddenReasoningModel, oai_settings } from './openai.js'; |
| 9 | 9 | import { Popup } from './popup.js'; |
| 10 | 10 | import { power_user } from './power-user.js'; |
| 11 | 11 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -71,6 +71,34 @@ export function extractReasoningFromData(data) { | ||
| 71 | 71 | } |
| 72 | 72 | |
| 73 | 73 | /** |
| 74 | + * Updates the Reasoning UI. | |
| 75 | + * @param {number|JQuery<HTMLElement>|HTMLElement} messageIdOrElement The message ID or the message element. | |
| 76 | + * @param {string|null} [reasoning=null] The reasoning content. | |
| 77 | + * @param {number|null} [reasoningDuration=null] The duration of the reasoning in milliseconds. | |
| 78 | + * @param {object} [options={}] Options for the function. | |
| 79 | + * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists. | |
| 80 | + */ | |
| 81 | +export function updateReasoningUI(messageIdOrElement, reasoning = null, reasoningDuration = null, { forceEnd = false } = {}) { | |
| 82 | + const messageElement = typeof messageIdOrElement === 'number' | |
| 83 | + ? $(`#chat [mesid="${messageIdOrElement}"]`) | |
| 84 | + : $(messageIdOrElement); | |
| 85 | + const mesReasoningElement = messageElement.find('.mes_reasoning'); | |
| 86 | + const mesReasoningHeaderTitle = messageElement.find('.mes_reasoning_header_title'); | |
| 87 | + const mesId = Number(messageElement.attr('mesid')); | |
| 88 | + | |
| 89 | + mesReasoningElement.html(messageFormatting(reasoning ?? '', '', false, false, mesId, {}, true)); | |
| 90 | + const reasoningText = mesReasoningElement.text().trim(); | |
| 91 | + | |
| 92 | + const hasReasoningText = !!reasoningText; | |
| 93 | + const isReasoningHidden = (!!reasoningDuration && !hasReasoningText) || (!forceEnd && isHiddenReasoningModel()); | |
| 94 | + const isReasoning = hasReasoningText || isReasoningHidden; | |
| 95 | + | |
| 96 | + messageElement.toggleClass('reasoning', isReasoning); | |
| 97 | + messageElement.toggleClass('reasoning_hidden', isReasoningHidden); | |
| 98 | + updateReasoningTimeUI(mesReasoningHeaderTitle[0], reasoningDuration, { forceEnd }); | |
| 99 | +} | |
| 100 | + | |
| 101 | +/** | |
| 74 | 102 | * Updates the Reasoning controls |
| 75 | 103 | * @param {HTMLElement} element The element to update |
| 76 | 104 | * @param {number?} duration The duration of the reasoning in milliseconds |
| @@ -413,10 +413,20 @@ input[type='checkbox']:focus-visible { | ||
| 413 | 413 | .mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_header, |
| 414 | 414 | .mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .edit_button, |
| 415 | 415 | .mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.edit_button), |
| 416 | 416 | .mes_block:has(.edit_textarea):has(.reasoning_edit_textarea) .mes_reasoning_actions {, |
| 417 | +.mes.reasoning:not(.reasoning_hidden) .mes_edit_add_reasoning, | |
| 418 | +.mes.reasoning_hidden .mes_reasoning_arrow, | |
| 419 | +.mes.reasoning_hidden .mes_reasoning_actions { | |
| 417 | 420 | display: none; |
| 418 | 421 | } |
| 419 | 422 | |
| 423 | +.mes.reasoning_hidden .mes_reasoning_details { | |
| 424 | + display: block; | |
| 425 | +} | |
| 426 | +.mes.reasoning_hidden .mes_reasoning_header { | |
| 427 | + cursor: initial; | |
| 428 | +} | |
| 429 | + | |
| 420 | 430 | .mes_reasoning_details .mes_reasoning_arrow { |
| 421 | 431 | position: absolute; |
| 422 | 432 | top: 50%; |
| @@ -4315,10 +4325,6 @@ input[type="range"]::-webkit-slider-thumb { | ||
| 4315 | 4325 | field-sizing: content; |
| 4316 | 4326 | } |
| 4317 | 4327 | |
| 4318 | -.mes.reasoning .mes_edit_add_reasoning { | |
| 4319 | - display: none; | |
| 4320 | -} | |
| 4321 | - | |
| 4322 | 4328 | #anchor_order { |
| 4323 | 4329 | margin-bottom: 15px; |
| 4324 | 4330 | } |