diff --git a/public/script.js b/public/script.js index 037ff7bc3..43245e7a3 100644 --- a/public/script.js +++ b/public/script.js @@ -238,7 +238,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set import { hideLoader, showLoader } from './scripts/loader.js'; import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js'; -import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, PromptReasoning } from './scripts/chats.js'; +import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat } from './scripts/chats.js'; import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; import { currentUser, setUserControls } from './scripts/user.js'; @@ -267,6 +267,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'; // API OBJECT FOR EXTERNAL WIRING globalThis.SillyTavern = { @@ -982,6 +983,7 @@ async function firstLoadInit() { initServerHistory(); initSettingsSearch(); initBulkEdit(); + initReasoning(); await initScrapers(); doDailyExtensionUpdatesCheck(); await hideLoader(); diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 8d6ec6040..94ad882bb 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -23,9 +23,6 @@ import { neutralCharacterName, updateChatMetadata, system_message_types, - updateMessageBlock, - closeMessageEditor, - substituteParams, } from '../script.js'; import { selected_group } from './group-chats.js'; import { power_user } from './power-user.js'; @@ -40,7 +37,6 @@ import { saveBase64AsFile, extractTextFromOffice, download, - copyText, } from './utils.js'; import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; @@ -1420,72 +1416,7 @@ export function registerFileConverter(mimeType, converter) { converters[mimeType] = converter; } -/** - * Helper class for adding reasoning to messages. - * Keeps track of the number of reasoning additions. - */ -export class PromptReasoning { - static REASONING_PLACEHOLDER = '\u200B'; - - constructor() { - this.counter = 0; - } - - /** - * Checks if the limit of reasoning additions has been reached. - * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise. - */ - isLimitReached() { - if (!power_user.reasoning.add_to_prompts) { - return true; - } - - return this.counter >= power_user.reasoning.max_additions; - } - - /** - * Add reasoning to a message according to the power user settings. - * @param {string} content Message content - * @param {string} reasoning Message reasoning - * @returns {string} Message content with reasoning - */ - addToMessage(content, reasoning) { - // Disabled or reached limit of additions - if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) { - return content; - } - - // No reasoning provided or a placeholder - if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) { - return content; - } - - // Increment the counter - this.counter++; - - // Substitute macros in variable parts - const prefix = substituteParams(power_user.reasoning.prefix || ''); - const separator = substituteParams(power_user.reasoning.separator || ''); - const suffix = substituteParams(power_user.reasoning.suffix || ''); - - // Combine parts with reasoning and content - return `${prefix}${reasoning}${suffix}${separator}${content}`; - } -} - jQuery(function () { - /** - * Gets a message from a jQuery element. - * @param {Element} element - * @returns {{messageId: number, message: object, messageBlock: JQuery}} - */ - const getMessageFromJquery = function (element) { - const messageBlock = $(element).closest('.mes'); - const messageId = Number(messageBlock.attr('mesid')); - const message = chat[messageId]; - return { messageId: messageId, message, messageBlock }; - }; - $(document).on('click', '.mes_hide', async function () { const messageBlock = $(this).closest('.mes'); const messageId = Number(messageBlock.attr('mesid')); @@ -1636,125 +1567,6 @@ jQuery(function () { $(document).on('click', '.mes_img_enlarge', enlargeMessageImage); $(document).on('click', '.mes_img_delete', deleteMessageImage); - $(document).on('click', '.mes_reasoning_copy', (e) => { - e.stopPropagation(); - e.preventDefault(); - }); - - $(document).on('click', '.mes_reasoning_edit', function (e) { - e.stopPropagation(); - e.preventDefault(); - const { message, messageBlock } = getMessageFromJquery(this); - if (!message?.extra) { - return; - } - - const reasoning = message?.extra?.reasoning; - const chatElement = document.getElementById('chat'); - const textarea = document.createElement('textarea'); - const reasoningBlock = messageBlock.find('.mes_reasoning'); - textarea.classList.add('reasoning_edit_textarea'); - textarea.value = reasoning === PromptReasoning.REASONING_PLACEHOLDER ? '' : reasoning; - $(textarea).insertBefore(reasoningBlock); - - if (!CSS.supports('field-sizing', 'content')) { - const resetHeight = function () { - const scrollTop = chatElement.scrollTop; - textarea.style.height = '0px'; - textarea.style.height = `${textarea.scrollHeight}px`; - chatElement.scrollTop = scrollTop; - }; - - textarea.addEventListener('input', resetHeight); - resetHeight(); - } - - textarea.focus(); - textarea.setSelectionRange(textarea.value.length, textarea.value.length); - - const textareaRect = textarea.getBoundingClientRect(); - const chatRect = chatElement.getBoundingClientRect(); - - // Scroll if textarea bottom is below visible area - if (textareaRect.bottom > chatRect.bottom) { - const scrollOffset = textareaRect.bottom - chatRect.bottom; - chatElement.scrollTop += scrollOffset; - } - }); - - $(document).on('click', '.mes_reasoning_edit_done', async function (e) { - e.stopPropagation(); - e.preventDefault(); - const { message, messageId, messageBlock } = getMessageFromJquery(this); - if (!message?.extra) { - return; - } - - const textarea = messageBlock.find('.reasoning_edit_textarea'); - const reasoning = String(textarea.val()); - message.extra.reasoning = reasoning; - await saveChatConditional(); - updateMessageBlock(messageId, message); - textarea.remove(); - }); - - $(document).on('click', '.mes_reasoning_edit_cancel', function (e) { - e.stopPropagation(); - e.preventDefault(); - - const { messageBlock } = getMessageFromJquery(this); - const textarea = messageBlock.find('.reasoning_edit_textarea'); - textarea.remove(); - }); - - $(document).on('click', '.mes_edit_add_reasoning', async function () { - const { message, messageId } = getMessageFromJquery(this); - if (!message?.extra) { - return; - } - - if (message.extra.reasoning) { - toastr.info(t`Reasoning already exists.`, t`Edit Message`); - return; - } - - message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER; - await saveChatConditional(); - closeMessageEditor(); - updateMessageBlock(messageId, message); - }); - - $(document).on('click', '.mes_reasoning_delete', async function (e) { - e.stopPropagation(); - e.preventDefault(); - - const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`); - - if (!confirm) { - return; - } - - const { message, messageId } = getMessageFromJquery(this); - if (!message?.extra) { - return; - } - message.extra.reasoning = ''; - await saveChatConditional(); - updateMessageBlock(messageId, message); - }); - - $(document).on('pointerup', '.mes_reasoning_copy', async function () { - const { message } = getMessageFromJquery(this); - const reasoning = message?.extra?.reasoning; - - if (!reasoning) { - return; - } - - await copyText(reasoning); - toastr.info(t`Copied!`, '', { timeOut: 2000 }); - }); - $('#file_form_input').on('change', async () => { const fileInput = document.getElementById('file_form_input'); if (!(fileInput instanceof HTMLInputElement)) return; diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 4859c0c71..08840c7bc 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -1621,7 +1621,6 @@ async function loadPowerUserSettings(settings, data) { loadMovingUIState(); loadCharListState(); toggleMDHotkeyIconDisplay(); - loadReasoningSettings(); } function toggleMDHotkeyIconDisplay() { @@ -1638,38 +1637,6 @@ function loadCharListState() { document.body.classList.toggle('charListGrid', power_user.charListGrid); } -function loadReasoningSettings() { - $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts); - $('#reasoning_add_to_prompts').on('change', function () { - power_user.reasoning.add_to_prompts = !!$(this).prop('checked'); - saveSettingsDebounced(); - }); - - $('#reasoning_prefix').val(power_user.reasoning.prefix); - $('#reasoning_prefix').on('input', function () { - power_user.reasoning.prefix = String($(this).val()); - saveSettingsDebounced(); - }); - - $('#reasoning_suffix').val(power_user.reasoning.suffix); - $('#reasoning_suffix').on('input', function () { - power_user.reasoning.suffix = String($(this).val()); - saveSettingsDebounced(); - }); - - $('#reasoning_separator').val(power_user.reasoning.separator); - $('#reasoning_separator').on('input', function () { - power_user.reasoning.separator = String($(this).val()); - saveSettingsDebounced(); - }); - - $('#reasoning_max_additions').val(power_user.reasoning.max_additions); - $('#reasoning_max_additions').on('input', function () { - power_user.reasoning.max_additions = Number($(this).val()); - saveSettingsDebounced(); - }); -} - function loadMovingUIState() { if (!isMobile() && power_user.movingUIState diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js new file mode 100644 index 000000000..0d2011b12 --- /dev/null +++ b/public/scripts/reasoning.js @@ -0,0 +1,228 @@ +import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; +import { t } from './i18n.js'; +import { Popup } from './popup.js'; +import { power_user } from './power-user.js'; +import { copyText } from './utils.js'; + +/** + * Gets a message from a jQuery element. + * @param {Element} element + * @returns {{messageId: number, message: object, messageBlock: JQuery}} + */ +function getMessageFromJquery(element) { + const messageBlock = $(element).closest('.mes'); + const messageId = Number(messageBlock.attr('mesid')); + const message = chat[messageId]; + return { messageId: messageId, message, messageBlock }; +} + +/** + * Helper class for adding reasoning to messages. + * Keeps track of the number of reasoning additions. + */ +export class PromptReasoning { + static REASONING_PLACEHOLDER = '\u200B'; + + constructor() { + this.counter = 0; + } + + /** + * Checks if the limit of reasoning additions has been reached. + * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise. + */ + isLimitReached() { + if (!power_user.reasoning.add_to_prompts) { + return true; + } + + return this.counter >= power_user.reasoning.max_additions; + } + + /** + * Add reasoning to a message according to the power user settings. + * @param {string} content Message content + * @param {string} reasoning Message reasoning + * @returns {string} Message content with reasoning + */ + addToMessage(content, reasoning) { + // Disabled or reached limit of additions + if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) { + return content; + } + + // No reasoning provided or a placeholder + if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) { + return content; + } + + // Increment the counter + this.counter++; + + // Substitute macros in variable parts + const prefix = substituteParams(power_user.reasoning.prefix || ''); + const separator = substituteParams(power_user.reasoning.separator || ''); + const suffix = substituteParams(power_user.reasoning.suffix || ''); + + // Combine parts with reasoning and content + return `${prefix}${reasoning}${suffix}${separator}${content}`; + } +} + +function loadReasoningSettings() { + $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts); + $('#reasoning_add_to_prompts').on('change', function () { + power_user.reasoning.add_to_prompts = !!$(this).prop('checked'); + saveSettingsDebounced(); + }); + + $('#reasoning_prefix').val(power_user.reasoning.prefix); + $('#reasoning_prefix').on('input', function () { + power_user.reasoning.prefix = String($(this).val()); + saveSettingsDebounced(); + }); + + $('#reasoning_suffix').val(power_user.reasoning.suffix); + $('#reasoning_suffix').on('input', function () { + power_user.reasoning.suffix = String($(this).val()); + saveSettingsDebounced(); + }); + + $('#reasoning_separator').val(power_user.reasoning.separator); + $('#reasoning_separator').on('input', function () { + power_user.reasoning.separator = String($(this).val()); + saveSettingsDebounced(); + }); + + $('#reasoning_max_additions').val(power_user.reasoning.max_additions); + $('#reasoning_max_additions').on('input', function () { + power_user.reasoning.max_additions = Number($(this).val()); + saveSettingsDebounced(); + }); +} + +function setReasoningEventHandlers(){ + $(document).on('click', '.mes_reasoning_copy', (e) => { + e.stopPropagation(); + e.preventDefault(); + }); + + $(document).on('click', '.mes_reasoning_edit', function (e) { + e.stopPropagation(); + e.preventDefault(); + const { message, messageBlock } = getMessageFromJquery(this); + if (!message?.extra) { + return; + } + + const reasoning = message?.extra?.reasoning; + const chatElement = document.getElementById('chat'); + const textarea = document.createElement('textarea'); + const reasoningBlock = messageBlock.find('.mes_reasoning'); + textarea.classList.add('reasoning_edit_textarea'); + textarea.value = reasoning === PromptReasoning.REASONING_PLACEHOLDER ? '' : reasoning; + $(textarea).insertBefore(reasoningBlock); + + if (!CSS.supports('field-sizing', 'content')) { + const resetHeight = function () { + const scrollTop = chatElement.scrollTop; + textarea.style.height = '0px'; + textarea.style.height = `${textarea.scrollHeight}px`; + chatElement.scrollTop = scrollTop; + }; + + textarea.addEventListener('input', resetHeight); + resetHeight(); + } + + textarea.focus(); + textarea.setSelectionRange(textarea.value.length, textarea.value.length); + + const textareaRect = textarea.getBoundingClientRect(); + const chatRect = chatElement.getBoundingClientRect(); + + // Scroll if textarea bottom is below visible area + if (textareaRect.bottom > chatRect.bottom) { + const scrollOffset = textareaRect.bottom - chatRect.bottom; + chatElement.scrollTop += scrollOffset; + } + }); + + $(document).on('click', '.mes_reasoning_edit_done', async function (e) { + e.stopPropagation(); + e.preventDefault(); + const { message, messageId, messageBlock } = getMessageFromJquery(this); + if (!message?.extra) { + return; + } + + const textarea = messageBlock.find('.reasoning_edit_textarea'); + const reasoning = String(textarea.val()); + message.extra.reasoning = reasoning; + await saveChatConditional(); + updateMessageBlock(messageId, message); + textarea.remove(); + }); + + $(document).on('click', '.mes_reasoning_edit_cancel', function (e) { + e.stopPropagation(); + e.preventDefault(); + + const { messageBlock } = getMessageFromJquery(this); + const textarea = messageBlock.find('.reasoning_edit_textarea'); + textarea.remove(); + }); + + $(document).on('click', '.mes_edit_add_reasoning', async function () { + const { message, messageId } = getMessageFromJquery(this); + if (!message?.extra) { + return; + } + + if (message.extra.reasoning) { + toastr.info(t`Reasoning already exists.`, t`Edit Message`); + return; + } + + message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER; + await saveChatConditional(); + closeMessageEditor(); + updateMessageBlock(messageId, message); + }); + + $(document).on('click', '.mes_reasoning_delete', async function (e) { + e.stopPropagation(); + e.preventDefault(); + + const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`); + + if (!confirm) { + return; + } + + const { message, messageId } = getMessageFromJquery(this); + if (!message?.extra) { + return; + } + message.extra.reasoning = ''; + await saveChatConditional(); + updateMessageBlock(messageId, message); + }); + + $(document).on('pointerup', '.mes_reasoning_copy', async function () { + const { message } = getMessageFromJquery(this); + const reasoning = message?.extra?.reasoning; + + if (!reasoning) { + return; + } + + await copyText(reasoning); + toastr.info(t`Copied!`, '', { timeOut: 2000 }); + }); +} + +export function initReasoning() { + loadReasoningSettings(); + setReasoningEventHandlers(); +}