Merge pull request #3442 from SillyTavern/more-reasoning-ui Smaller improvements and fixes to the reasoning UI
Signed| @@ -225,7 +225,7 @@ import { | ||
| 225 | 225 | instruct_presets, |
| 226 | 226 | selectContextPreset, |
| 227 | 227 | } from './scripts/instruct-mode.js'; |
| 228 | 228 | import { getCurrentLocale, initLocales, t } from './scripts/i18n.js'; |
| 229 | 229 | import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js'; |
| 230 | 230 | import { |
| 231 | 231 | user_avatar, |
| @@ -269,7 +269,7 @@ import { initSettingsSearch } from './scripts/setting-search.js'; | ||
| 269 | 269 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 270 | 270 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; |
| 271 | 271 | import { getContext } from './scripts/st-context.js'; |
| 272 | 272 | import { extractReasoningFromData, initReasoning, PromptReasoning, updateReasoningTimeUI } from './scripts/reasoning.js'; |
| 273 | 273 | |
| 274 | 274 | // API OBJECT FOR EXTERNAL WIRING |
| 275 | 275 | globalThis.SillyTavern = { |
| @@ -2275,11 +2275,15 @@ function getMessageFromTemplate({ | ||
| 2275 | 2275 | * Re-renders a message block with updated content. |
| 2276 | 2276 | * @param {number} messageId Message ID |
| 2277 | 2277 | * @param {object} message Message object |
| 2278 | + * @param {object} [options={}] Optional arguments | |
| 2279 | + * @param {boolean} [options.rerenderMessage=true] Whether to re-render the message content (inside <c>.mes_text</c>) | |
| 2278 | 2280 | */ |
| 2279 | 2281 | export function updateMessageBlock(messageId, message, { rerenderMessage = true } = {}) { |
| 2280 | 2282 | const messageElement = $(`#chat [mesid="${messageId}"]`); |
| 2281 | - const text = message?.extra?.display_text ?? message.mes; | |
| 2283 | + if (rerenderMessage) { | |
| 2282 | - messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false)); | |
| 2284 | + const text = message?.extra?.display_text ?? message.mes; | |
| 2285 | + messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false)); | |
| 2286 | + } | |
| 2283 | 2287 | messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true)); |
| 2284 | 2288 | messageElement.toggleClass('reasoning', !!message.extra?.reasoning); |
| 2285 | 2289 | addCopyToCodeBlocks(messageElement); |
| @@ -5764,56 +5768,6 @@ function extractMessageFromData(data) { | ||
| 5764 | 5768 | } |
| 5765 | 5769 | |
| 5766 | 5770 | /** |
| 5767 | - * Extracts the reasoning from the response data. | |
| 5768 | - * @param {object} data Response data | |
| 5769 | - * @returns {string} Extracted reasoning | |
| 5770 | - */ | |
| 5771 | -function extractReasoningFromData(data) { | |
| 5772 | - switch (main_api) { | |
| 5773 | - case 'textgenerationwebui': | |
| 5774 | - switch (textgen_settings.type) { | |
| 5775 | - case textgen_types.OPENROUTER: | |
| 5776 | - return data?.choices?.[0]?.reasoning ?? ''; | |
| 5777 | - } | |
| 5778 | - break; | |
| 5779 | - | |
| 5780 | - case 'openai': | |
| 5781 | - if (!oai_settings.show_thoughts) break; | |
| 5782 | - | |
| 5783 | - switch (oai_settings.chat_completion_source) { | |
| 5784 | - case chat_completion_sources.DEEPSEEK: | |
| 5785 | - return data?.choices?.[0]?.message?.reasoning_content ?? ''; | |
| 5786 | - case chat_completion_sources.OPENROUTER: | |
| 5787 | - return data?.choices?.[0]?.message?.reasoning ?? ''; | |
| 5788 | - case chat_completion_sources.MAKERSUITE: | |
| 5789 | - return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; | |
| 5790 | - } | |
| 5791 | - break; | |
| 5792 | - } | |
| 5793 | - | |
| 5794 | - return ''; | |
| 5795 | -} | |
| 5796 | - | |
| 5797 | -/** | |
| 5798 | - * Updates the Reasoning controls | |
| 5799 | - * @param {HTMLElement} element The element to update | |
| 5800 | - * @param {number?} duration The duration of the reasoning in milliseconds | |
| 5801 | - * @param {object} [options={}] Options for the function | |
| 5802 | - * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists | |
| 5803 | - */ | |
| 5804 | -function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { | |
| 5805 | - if (duration) { | |
| 5806 | - const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 9 }); | |
| 5807 | - element.textContent = t`Thought for ${durationStr}`; | |
| 5808 | - } else if (forceEnd) { | |
| 5809 | - element.textContent = t`Thought for some time`; | |
| 5810 | - } else { | |
| 5811 | - element.textContent = t`Thinking...`; | |
| 5812 | - } | |
| 5813 | -} | |
| 5814 | - | |
| 5815 | - | |
| 5816 | -/** | |
| 5817 | 5771 | * Extracts multiswipe swipes from the response data. |
| 5818 | 5772 | * @param {Object} data Response data |
| 5819 | 5773 | * @param {string} type Type of generation |
| @@ -10872,18 +10826,6 @@ jQuery(async function () { | ||
| 10872 | 10826 | } |
| 10873 | 10827 | }); |
| 10874 | 10828 | |
| 10875 | - $(document).on('click', '.mes_reasoning_header', function () { | |
| 10876 | - // If we are in message edit mode and reasoning area is closed, a click opens and edits it | |
| 10877 | - const mes = $(this).closest('.mes'); | |
| 10878 | - const mesEditArea = mes.find('#curEditTextarea'); | |
| 10879 | - if (mesEditArea.length) { | |
| 10880 | - const summary = $(mes).find('.mes_reasoning_summary'); | |
| 10881 | - if (!summary.attr('open')) { | |
| 10882 | - summary.find('.mes_reasoning_edit').trigger('click'); | |
| 10883 | - } | |
| 10884 | - } | |
| 10885 | - }); | |
| 10886 | - | |
| 10887 | 10829 | $(document).on('input', '#curEditTextarea', function () { |
| 10888 | 10830 | if (power_user.auto_save_msg_edits === true) { |
| 10889 | 10831 | messageEditAuto($(this)); |
| @@ -11438,17 +11380,6 @@ jQuery(async function () { | ||
| 11438 | 11380 | } |
| 11439 | 11381 | }); |
| 11440 | 11382 | |
| 11441 | - $(document).on('click', '.mes_reasoning_summary', function () { | |
| 11442 | - // If you toggle summary header while editing reasoning, yup - we just cancel it | |
| 11443 | - $(this).closest('.mes').find('.mes_reasoning_edit_cancel:visible').trigger('click'); | |
| 11444 | - }); | |
| 11445 | - | |
| 11446 | - $(document).on('click', '.mes_reasoning_details', function (e) { | |
| 11447 | - if (!e.target.closest('.mes_reasoning_actions') && !e.target.closest('.mes_reasoning_header')) { | |
| 11448 | - e.preventDefault(); | |
| 11449 | - } | |
| 11450 | - }); | |
| 11451 | - | |
| 11452 | 11383 | $(document).keyup(function (e) { |
| 11453 | 11384 | if (e.key === 'Escape') { |
| 11454 | 11385 | const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0; |
| @@ -1,13 +1,18 @@ | ||
| 1 | -import { chat, closeMessageEditor, event_types, eventSource, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; | |
| 1 | +import { | |
| 2 | + moment, | |
| 3 | +} from '../lib.js'; | |
| 4 | +import { chat, closeMessageEditor, event_types, eventSource, main_api, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; | |
| 2 | 5 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 3 | 6 | import { getCurrentLocale, t } from './i18n.js'; |
| 4 | 7 | import { MacrosParser } from './macros.js'; |
| 8 | +import { chat_completion_sources, oai_settings } from './openai.js'; | |
| 5 | 9 | import { Popup } from './popup.js'; |
| 6 | 10 | import { power_user } from './power-user.js'; |
| 7 | 11 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 8 | 12 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| 9 | 13 | import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 10 | 14 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 15 | +import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | |
| 11 | 16 | import { copyText, escapeRegex, isFalseBoolean } from './utils.js'; |
| 12 | 17 | |
| 13 | 18 | /** |
| @@ -35,6 +40,56 @@ function toggleReasoningAutoExpand() { | ||
| 35 | 40 | } |
| 36 | 41 | |
| 37 | 42 | /** |
| 43 | + * Extracts the reasoning from the response data. | |
| 44 | + * @param {object} data Response data | |
| 45 | + * @returns {string} Extracted reasoning | |
| 46 | + */ | |
| 47 | +export function extractReasoningFromData(data) { | |
| 48 | + switch (main_api) { | |
| 49 | + case 'textgenerationwebui': | |
| 50 | + switch (textgenerationwebui_settings.type) { | |
| 51 | + case textgen_types.OPENROUTER: | |
| 52 | + return data?.choices?.[0]?.reasoning ?? ''; | |
| 53 | + } | |
| 54 | + break; | |
| 55 | + | |
| 56 | + case 'openai': | |
| 57 | + if (!oai_settings.show_thoughts) break; | |
| 58 | + | |
| 59 | + switch (oai_settings.chat_completion_source) { | |
| 60 | + case chat_completion_sources.DEEPSEEK: | |
| 61 | + return data?.choices?.[0]?.message?.reasoning_content ?? ''; | |
| 62 | + case chat_completion_sources.OPENROUTER: | |
| 63 | + return data?.choices?.[0]?.message?.reasoning ?? ''; | |
| 64 | + case chat_completion_sources.MAKERSUITE: | |
| 65 | + return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; | |
| 66 | + } | |
| 67 | + break; | |
| 68 | + } | |
| 69 | + | |
| 70 | + return ''; | |
| 71 | +} | |
| 72 | + | |
| 73 | +/** | |
| 74 | + * Updates the Reasoning controls | |
| 75 | + * @param {HTMLElement} element The element to update | |
| 76 | + * @param {number?} duration The duration of the reasoning in milliseconds | |
| 77 | + * @param {object} [options={}] Options for the function | |
| 78 | + * @param {boolean} [options.forceEnd=false] If true, there will be no "Thinking..." when no duration exists | |
| 79 | + */ | |
| 80 | +export function updateReasoningTimeUI(element, duration, { forceEnd = false } = {}) { | |
| 81 | + if (duration) { | |
| 82 | + const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); | |
| 83 | + const secondsStr = moment.duration(duration).asSeconds(); | |
| 84 | + element.innerHTML = t`Thought for <span title="${secondsStr} seconds">${durationStr}</span>`; | |
| 85 | + } else if (forceEnd) { | |
| 86 | + element.textContent = t`Thought for some time`; | |
| 87 | + } else { | |
| 88 | + element.textContent = t`Thinking...`; | |
| 89 | + } | |
| 90 | +} | |
| 91 | + | |
| 92 | +/** | |
| 38 | 93 | * Helper class for adding reasoning to messages. |
| 39 | 94 | * Keeps track of the number of reasoning additions. |
| 40 | 95 | */ |
| @@ -247,6 +302,24 @@ function registerReasoningMacros() { | ||
| 247 | 302 | } |
| 248 | 303 | |
| 249 | 304 | function setReasoningEventHandlers() { |
| 305 | + $(document).on('click', '.mes_reasoning_details', function (e) { | |
| 306 | + if (!e.target.closest('.mes_reasoning_actions') && !e.target.closest('.mes_reasoning_header')) { | |
| 307 | + e.preventDefault(); | |
| 308 | + } | |
| 309 | + }); | |
| 310 | + | |
| 311 | + $(document).on('click', '.mes_reasoning_header', function () { | |
| 312 | + // If we are in message edit mode and reasoning area is closed, a click opens and edits it | |
| 313 | + const mes = $(this).closest('.mes'); | |
| 314 | + const mesEditArea = mes.find('#curEditTextarea'); | |
| 315 | + if (mesEditArea.length) { | |
| 316 | + const summary = $(mes).find('.mes_reasoning_summary'); | |
| 317 | + if (!summary.attr('open')) { | |
| 318 | + summary.find('.mes_reasoning_edit').trigger('click'); | |
| 319 | + } | |
| 320 | + } | |
| 321 | + }); | |
| 322 | + | |
| 250 | 323 | $(document).on('click', '.mes_reasoning_copy', (e) => { |
| 251 | 324 | e.stopPropagation(); |
| 252 | 325 | e.preventDefault(); |
| @@ -323,7 +396,7 @@ function setReasoningEventHandlers() { | ||
| 323 | 396 | }); |
| 324 | 397 | |
| 325 | 398 | $(document).on('click', '.mes_edit_add_reasoning', async function () { |
| 326 | 399 | const { message, messageId, messageBlock } = getMessageFromJquery(this); |
| 327 | 400 | if (!message?.extra) { |
| 328 | 401 | return; |
| 329 | 402 | } |
| @@ -334,7 +407,8 @@ function setReasoningEventHandlers() { | ||
| 334 | 407 | } |
| 335 | 408 | |
| 336 | 409 | message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER; |
| 337 | 410 | updateMessageBlock(messageId, message, { rerenderMessage: false }); |
| 411 | + messageBlock.find('.mes_reasoning_edit').trigger('click'); | |
| 338 | 412 | await saveChatConditional(); |
| 339 | 413 | }); |
| 340 | 414 | |
| @@ -348,13 +422,15 @@ function setReasoningEventHandlers() { | ||
| 348 | 422 | return; |
| 349 | 423 | } |
| 350 | 424 | |
| 351 | 425 | const { message, messageId, messageBlock } = getMessageFromJquery(this); |
| 352 | 426 | if (!message?.extra) { |
| 353 | 427 | return; |
| 354 | 428 | } |
| 355 | 429 | message.extra.reasoning = ''; |
| 356 | 430 | await saveChatConditional(); |
| 357 | 431 | updateMessageBlock(messageId, message); |
| 432 | + const textarea = messageBlock.find('.reasoning_edit_textarea'); | |
| 433 | + textarea.remove(); | |
| 358 | 434 | }); |
| 359 | 435 | |
| 360 | 436 | $(document).on('pointerup', '.mes_reasoning_copy', async function () { |
| @@ -388,7 +388,7 @@ input[type='checkbox']:focus-visible { | ||
| 388 | 388 | margin: 0.5em 2px; |
| 389 | 389 | padding: 7px 14px; |
| 390 | 390 | padding-right: calc(0.7em + 14px); |
| 391 | 391 | border-radius: 10px5px; |
| 392 | 392 | background-color: var(--grey30); |
| 393 | 393 | font-size: calc(var(--mainFontSize) * 0.9); |
| 394 | 394 | align-items: baseline; |