diff --git a/public/script.js b/public/script.js index 61d250ea8..753262ac9 100644 --- a/public/script.js +++ b/public/script.js @@ -225,7 +225,7 @@ import { instruct_presets, selectContextPreset, } from './scripts/instruct-mode.js'; -import { getCurrentLocale, initLocales, t } from './scripts/i18n.js'; +import { initLocales, t } from './scripts/i18n.js'; import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js'; import { user_avatar, @@ -269,7 +269,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 { initReasoning, PromptReasoning } from './scripts/reasoning.js'; +import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI } from './scripts/reasoning.js'; // API OBJECT FOR EXTERNAL WIRING globalThis.SillyTavern = { @@ -2275,11 +2275,15 @@ function getMessageFromTemplate({ * Re-renders a message block with updated content. * @param {number} messageId Message ID * @param {object} message Message object + * @param {object} [options={}] Optional arguments + * @param {boolean} [options.rerenderMessage=true] Whether to re-render the message content (inside .mes_text) */ -export function updateMessageBlock(messageId, message) { +export function updateMessageBlock(messageId, message, { rerenderMessage = true } = {}) { const messageElement = $(`#chat [mesid="${messageId}"]`); - 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)); + if (rerenderMessage) { + 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); addCopyToCodeBlocks(messageElement); @@ -5763,56 +5767,6 @@ function extractMessageFromData(data) { } } -/** - * Extracts the reasoning from the response data. - * @param {object} data Response data - * @returns {string} Extracted reasoning - */ -function extractReasoningFromData(data) { - switch (main_api) { - case 'textgenerationwebui': - switch (textgen_settings.type) { - case textgen_types.OPENROUTER: - return data?.choices?.[0]?.reasoning ?? ''; - } - break; - - case 'openai': - if (!oai_settings.show_thoughts) break; - - switch (oai_settings.chat_completion_source) { - case chat_completion_sources.DEEPSEEK: - return data?.choices?.[0]?.message?.reasoning_content ?? ''; - case chat_completion_sources.OPENROUTER: - return data?.choices?.[0]?.message?.reasoning ?? ''; - case chat_completion_sources.MAKERSUITE: - return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; - } - break; - } - - 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) { - const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 9 }); - element.textContent = t`Thought for ${durationStr}`; - } else if (forceEnd) { - element.textContent = t`Thought for some time`; - } else { - element.textContent = t`Thinking...`; - } -} - - /** * Extracts multiswipe swipes from the response data. * @param {Object} data Response data @@ -10872,18 +10826,6 @@ jQuery(async function () { } }); - $(document).on('click', '.mes_reasoning_header', function () { - // If we are in message edit mode and reasoning area is closed, a click opens and edits it - const mes = $(this).closest('.mes'); - const mesEditArea = mes.find('#curEditTextarea'); - if (mesEditArea.length) { - const summary = $(mes).find('.mes_reasoning_summary'); - if (!summary.attr('open')) { - summary.find('.mes_reasoning_edit').trigger('click'); - } - } - }); - $(document).on('input', '#curEditTextarea', function () { if (power_user.auto_save_msg_edits === true) { messageEditAuto($(this)); @@ -11438,17 +11380,6 @@ jQuery(async function () { } }); - $(document).on('click', '.mes_reasoning_summary', function () { - // If you toggle summary header while editing reasoning, yup - we just cancel it - $(this).closest('.mes').find('.mes_reasoning_edit_cancel:visible').trigger('click'); - }); - - $(document).on('click', '.mes_reasoning_details', function (e) { - if (!e.target.closest('.mes_reasoning_actions') && !e.target.closest('.mes_reasoning_header')) { - e.preventDefault(); - } - }); - $(document).keyup(function (e) { if (e.key === 'Escape') { const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0; diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js index 6b9344313..3535401e5 100644 --- a/public/scripts/reasoning.js +++ b/public/scripts/reasoning.js @@ -1,13 +1,18 @@ -import { chat, closeMessageEditor, event_types, eventSource, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; +import { + moment, +} from '../lib.js'; +import { chat, closeMessageEditor, event_types, eventSource, main_api, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; -import { t } from './i18n.js'; +import { getCurrentLocale, t } from './i18n.js'; import { MacrosParser } from './macros.js'; +import { chat_completion_sources, oai_settings } from './openai.js'; import { Popup } from './popup.js'; import { power_user } from './power-user.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; +import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; import { copyText, escapeRegex, isFalseBoolean } from './utils.js'; /** @@ -34,6 +39,56 @@ function toggleReasoningAutoExpand() { }); } +/** + * Extracts the reasoning from the response data. + * @param {object} data Response data + * @returns {string} Extracted reasoning + */ +export function extractReasoningFromData(data) { + switch (main_api) { + case 'textgenerationwebui': + switch (textgenerationwebui_settings.type) { + case textgen_types.OPENROUTER: + return data?.choices?.[0]?.reasoning ?? ''; + } + break; + + case 'openai': + if (!oai_settings.show_thoughts) break; + + switch (oai_settings.chat_completion_source) { + case chat_completion_sources.DEEPSEEK: + return data?.choices?.[0]?.message?.reasoning_content ?? ''; + case chat_completion_sources.OPENROUTER: + return data?.choices?.[0]?.message?.reasoning ?? ''; + case chat_completion_sources.MAKERSUITE: + return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; + } + break; + } + + 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 + */ +export function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { + if (duration) { + const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); + const secondsStr = moment.duration(duration).asSeconds(); + element.innerHTML = t`Thought for ${durationStr}`; + } else if (forceEnd) { + element.textContent = t`Thought for some time`; + } else { + element.textContent = t`Thinking...`; + } +} + /** * Helper class for adding reasoning to messages. * Keeps track of the number of reasoning additions. @@ -247,6 +302,24 @@ function registerReasoningMacros() { } function setReasoningEventHandlers() { + $(document).on('click', '.mes_reasoning_details', function (e) { + if (!e.target.closest('.mes_reasoning_actions') && !e.target.closest('.mes_reasoning_header')) { + e.preventDefault(); + } + }); + + $(document).on('click', '.mes_reasoning_header', function () { + // If we are in message edit mode and reasoning area is closed, a click opens and edits it + const mes = $(this).closest('.mes'); + const mesEditArea = mes.find('#curEditTextarea'); + if (mesEditArea.length) { + const summary = $(mes).find('.mes_reasoning_summary'); + if (!summary.attr('open')) { + summary.find('.mes_reasoning_edit').trigger('click'); + } + } + }); + $(document).on('click', '.mes_reasoning_copy', (e) => { e.stopPropagation(); e.preventDefault(); @@ -323,7 +396,7 @@ function setReasoningEventHandlers() { }); $(document).on('click', '.mes_edit_add_reasoning', async function () { - const { message, messageId } = getMessageFromJquery(this); + const { message, messageId, messageBlock } = getMessageFromJquery(this); if (!message?.extra) { return; } @@ -334,7 +407,8 @@ function setReasoningEventHandlers() { } message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER; - updateMessageBlock(messageId, message); + updateMessageBlock(messageId, message, { rerenderMessage: false }); + messageBlock.find('.mes_reasoning_edit').trigger('click'); await saveChatConditional(); }); @@ -348,13 +422,15 @@ function setReasoningEventHandlers() { return; } - const { message, messageId } = getMessageFromJquery(this); + const { message, messageId, messageBlock } = getMessageFromJquery(this); if (!message?.extra) { return; } message.extra.reasoning = ''; await saveChatConditional(); updateMessageBlock(messageId, message); + const textarea = messageBlock.find('.reasoning_edit_textarea'); + textarea.remove(); }); $(document).on('pointerup', '.mes_reasoning_copy', async function () { diff --git a/public/style.css b/public/style.css index d854c48cc..e026b73de 100644 --- a/public/style.css +++ b/public/style.css @@ -388,7 +388,7 @@ input[type='checkbox']:focus-visible { margin: 0.5em 2px; padding: 7px 14px; padding-right: calc(0.7em + 14px); - border-radius: 10px; + border-radius: 5px; background-color: var(--grey30); font-size: calc(var(--mainFontSize) * 0.9); align-items: baseline;