Merge pull request #3413 from SillyTavern/thinking-is-stylish Thinking is stylish - if you are not cool, I don't know how to help you

5494e89fdb71d0037c7024e57764e03a4cbadada

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
6 files changed, +282 -65Ignore whitespace
public/index.html+18 -7
@@ -3789,6 +3789,12 @@
3789 Auto-Parse Reasoning3789 Auto-Parse Reasoning
3790 </small>3790 </small>
3791 </label>3791 </label>
3792 <label class="checkbox_label" for="reasoning_auto_expand" title="Automatically expand reasoning blocks." data-i18n="[title]reasoning_auto_expand">
3793 <input id="reasoning_auto_expand" type="checkbox" />
3794 <small data-i18n="Auto-Expand Reasoning">
3795 Auto-Expand Reasoning
3796 </small>
3797 </label>
3792 <label class="checkbox_label" for="reasoning_add_to_prompts" title="Add existing reasoning blocks to prompts. To add a new reasoning block, use the message edit menu." data-i18n="[title]reasoning_add_to_prompts">3798 <label class="checkbox_label" for="reasoning_add_to_prompts" title="Add existing reasoning blocks to prompts. To add a new reasoning block, use the message edit menu." data-i18n="[title]reasoning_add_to_prompts">
3793 <input id="reasoning_add_to_prompts" type="checkbox" />3799 <input id="reasoning_add_to_prompts" type="checkbox" />
3794 <small data-i18n="Add Reasoning to Prompts">3800 <small data-i18n="Add Reasoning to Prompts">
@@ -6248,14 +6254,19 @@
6248 </div>6254 </div>
6249 </div>6255 </div>
6250 <details class="mes_reasoning_details">6256 <details class="mes_reasoning_details">
6251 <summary class="mes_reasoning_summary">6257 <summary class="mes_reasoning_summary flex-container">
6252 <span data-i18n="Reasoning">Reasoning</span>6258 <div class="mes_reasoning_header_block flex-container">
6253 <div class="mes_reasoning_actions">6259 <div class="mes_reasoning_header flex-container">
6254 <div class="mes_reasoning_edit_done mes_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div>6260 <span class="mes_reasoning_header_title" data-i18n="Thought for some time">Thought for some time</span>
6255 <div class="mes_reasoning_edit_cancel mes_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div>6261 <div class="mes_reasoning_arrow fa-solid fa-chevron-up"></div>
6256 <div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div>6262 </div>
6263 </div>
6264 <div class="mes_reasoning_actions flex-container">
6265 <div class="mes_reasoning_edit_done menu_button edit_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div>
6266 <div class="mes_reasoning_delete menu_button edit_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div>
6267 <div class="mes_reasoning_edit_cancel menu_button edit_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div>
6257 <div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div>6268 <div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div>
6258 <div class="mes_reasoning_delete mes_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div>6269 <div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div>
6259 </div>6270 </div>
6260 </summary>6271 </summary>
6261 <div class="mes_reasoning"></div>6272 <div class="mes_reasoning"></div>
public/script.js+142 -25
@@ -225,7 +225,7 @@ import {
225 instruct_presets,225 instruct_presets,
226 selectContextPreset,226 selectContextPreset,
227} from './scripts/instruct-mode.js';227} from './scripts/instruct-mode.js';
228import { initLocales, t } from './scripts/i18n.js';228import { getCurrentLocale, initLocales, t } from './scripts/i18n.js';
229import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';229import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
230import {230import {
231 user_avatar,231 user_avatar,
@@ -495,6 +495,7 @@ export const event_types = {
495 /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */495 /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
496 SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',496 SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
497 STREAM_TOKEN_RECEIVED: 'stream_token_received',497 STREAM_TOKEN_RECEIVED: 'stream_token_received',
498 STREAM_REASONING_DONE: 'stream_reasoning_done',
498 FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',499 FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
499 WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',500 WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
500 OPEN_CHARACTER_LIBRARY: 'open_character_library',501 OPEN_CHARACTER_LIBRARY: 'open_character_library',
@@ -2189,26 +2190,29 @@ function insertSVGIcon(mes, extra) {
2189 modelName = extra.api;2190 modelName = extra.api;
2190 }2191 }
21912192
2192 const image = new Image();2193 const insertOrReplaceSVG = (image, className, targetSelector, insertBefore) => {
2193 // Add classes for styling and identification2194 image.onload = async function () {
2194 image.classList.add('icon-svg', 'timestamp-icon');2195 let existingSVG = insertBefore ? mes.find(targetSelector).prev(`.${className}`) : mes.find(targetSelector).next(`.${className}`);
2195 image.src = `/img/${modelName}.svg`;2196 if (existingSVG.length) {
2196 image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;2197 existingSVG.replaceWith(image);
21972198 } else {
2198 image.onload = async function () {2199 if (insertBefore) mes.find(targetSelector).before(image);
2199 // Check if an SVG already exists adjacent to the timestamp2200 else mes.find(targetSelector).after(image);
2200 let existingSVG = mes.find('.timestamp').next('.timestamp-icon');2201 }
22012202 await SVGInject(image);
2202 if (existingSVG.length) {2203 };
2203 // Replace existing SVG2204 };
2204 existingSVG.replaceWith(image);
2205 } else {
2206 // Append the new SVG if none exists
2207 mes.find('.timestamp').after(image);
2208 }
22092205
2210 await SVGInject(image);2206 const createModelImage = (className, targetSelector, insertBefore) => {
2207 const image = new Image();
2208 image.classList.add('icon-svg', className);
2209 image.src = `/img/${modelName}.svg`;
2210 image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
2211 insertOrReplaceSVG(image, className, targetSelector, insertBefore);
2211 };2212 };
2213
2214 createModelImage('timestamp-icon', '.timestamp');
2215 createModelImage('thinking-icon', '.mes_reasoning_header_title', true);
2212}2216}
22132217
22142218
@@ -2220,6 +2224,7 @@ function getMessageFromTemplate({
2220 avatarImg,2224 avatarImg,
2221 bias,2225 bias,
2222 reasoning,2226 reasoning,
2227 reasoningDuration,
2223 isSystem,2228 isSystem,
2224 title,2229 title,
2225 timerValue,2230 timerValue,
@@ -2252,6 +2257,13 @@ function getMessageFromTemplate({
2252 timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);2257 timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
2253 bookmarkLink && updateBookmarkDisplay(mes);2258 bookmarkLink && updateBookmarkDisplay(mes);
22542259
2260 if (reasoning) {
2261 mes.addClass('reasoning');
2262 }
2263 if (reasoningDuration) {
2264 updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true });
2265 }
2266
2255 if (power_user.timestamp_model_icon && extra?.api) {2267 if (power_user.timestamp_model_icon && extra?.api) {
2256 insertSVGIcon(mes, extra);2268 insertSVGIcon(mes, extra);
2257 }2269 }
@@ -2269,6 +2281,7 @@ export function updateMessageBlock(messageId, message) {
2269 const text = message?.extra?.display_text ?? message.mes;2281 const text = message?.extra?.display_text ?? message.mes;
2270 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false));2282 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false));
2271 messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true));2283 messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true));
2284 messageElement.toggleClass('reasoning', !!message.extra?.reasoning);
2272 addCopyToCodeBlocks(messageElement);2285 addCopyToCodeBlocks(messageElement);
2273 appendMediaToMessage(message, messageElement);2286 appendMediaToMessage(message, messageElement);
2274}2287}
@@ -2439,6 +2452,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2439 avatarImg: avatarImg,2452 avatarImg: avatarImg,
2440 bias: bias,2453 bias: bias,
2441 reasoning: reasoning,2454 reasoning: reasoning,
2455 reasoningDuration: mes.extra?.reasoning_duration,
2442 isSystem: isSystem,2456 isSystem: isSystem,
2443 title: title,2457 title: title,
2444 bookmarkLink: bookmarkLink,2458 bookmarkLink: bookmarkLink,
@@ -3108,8 +3122,12 @@ class StreamingProcessor {
3108 this.messageDom = null;3122 this.messageDom = null;
3109 this.messageTextDom = null;3123 this.messageTextDom = null;
3110 this.messageTimerDom = null;3124 this.messageTimerDom = null;
3125 /** @type {HTMLElement} */
3111 this.messageTokenCounterDom = null;3126 this.messageTokenCounterDom = null;
3127 /** @type {HTMLElement} */
3112 this.messageReasoningDom = null;3128 this.messageReasoningDom = null;
3129 /** @type {HTMLElement} */
3130 this.messageReasoningHeaderDom = null;
3113 /** @type {HTMLTextAreaElement} */3131 /** @type {HTMLTextAreaElement} */
3114 this.sendTextarea = document.querySelector('#send_textarea');3132 this.sendTextarea = document.querySelector('#send_textarea');
3115 this.type = type;3133 this.type = type;
@@ -3126,6 +3144,15 @@ class StreamingProcessor {
3126 this.messageLogprobs = [];3144 this.messageLogprobs = [];
3127 this.toolCalls = [];3145 this.toolCalls = [];
3128 this.reasoning = '';3146 this.reasoning = '';
3147 this.reasoningStartTime = null;
3148 this.reasoningEndTime = null;
3149 }
3150
3151 #reasoningDuration() {
3152 if (this.reasoningStartTime && this.reasoningEndTime) {
3153 return (this.reasoningEndTime - this.reasoningStartTime);
3154 }
3155 return null;
3129 }3156 }
31303157
3131 #checkDomElements(messageId) {3158 #checkDomElements(messageId) {
@@ -3135,6 +3162,7 @@ class StreamingProcessor {
3135 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');3162 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
3136 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');3163 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
3137 this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');3164 this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
3165 this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title');
3138 }3166 }
3139 }3167 }
31403168
@@ -3182,7 +3210,7 @@ class StreamingProcessor {
3182 return messageId;3210 return messageId;
3183 }3211 }
31843212
3185 onProgressStreaming(messageId, text, isFinal) {3213 async onProgressStreaming(messageId, text, isFinal) {
3186 const isImpersonate = this.type == 'impersonate';3214 const isImpersonate = this.type == 'impersonate';
3187 const isContinue = this.type == 'continue';3215 const isContinue = this.type == 'continue';
31883216
@@ -3209,6 +3237,8 @@ class StreamingProcessor {
3209 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));3237 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
3210 }3238 }
3211 else {3239 else {
3240 const mesChanged = chat[messageId]['mes'] !== processedText;
3241
3212 this.#checkDomElements(messageId);3242 this.#checkDomElements(messageId);
3213 this.#updateMessageBlockVisibility();3243 this.#updateMessageBlockVisibility();
3214 const currentTime = new Date();3244 const currentTime = new Date();
@@ -3221,11 +3251,25 @@ class StreamingProcessor {
3221 }3251 }
32223252
3223 if (this.reasoning) {3253 if (this.reasoning) {
3224 chat[messageId]['extra']['reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;3254 const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
3255 const reasoningChanged = chat[messageId]['extra']['reasoning'] !== reasoning;
3256 chat[messageId]['extra']['reasoning'] = reasoning;
3257
3258 if (reasoningChanged && this.reasoningStartTime === null) {
3259 this.reasoningStartTime = Date.now();
3260 }
3261 if (!reasoningChanged && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) {
3262 this.reasoningEndTime = Date.now();
3263 }
3264 await this.#updateReasoningTime(messageId);
3265
3225 if (this.messageReasoningDom instanceof HTMLElement) {3266 if (this.messageReasoningDom instanceof HTMLElement) {
3226 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);3267 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
3227 this.messageReasoningDom.innerHTML = formattedReasoning;3268 this.messageReasoningDom.innerHTML = formattedReasoning;
3228 }3269 }
3270 if (this.messageDom instanceof HTMLElement) {
3271 this.messageDom.classList.add('reasoning');
3272 }
3229 }3273 }
32303274
3231 // Don't waste time calculating token count for streaming3275 // Don't waste time calculating token count for streaming
@@ -3271,11 +3315,24 @@ class StreamingProcessor {
3271 }3315 }
3272 }3316 }
32733317
3318 async #updateReasoningTime(messageId, { forceEnd = false } = {}) {
3319 const duration = this.#reasoningDuration();
3320 chat[messageId]['extra']['reasoning_duration'] = duration;
3321 updateReasoningTimeUI(this.messageReasoningHeaderDom, duration, { forceEnd: forceEnd });
3322 await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, duration);
3323 }
3324
3274 async onFinishStreaming(messageId, text) {3325 async onFinishStreaming(messageId, text) {
3275 this.hideMessageButtons(this.messageId);3326 this.hideMessageButtons(this.messageId);
3276 this.onProgressStreaming(messageId, text, true);3327 await this.onProgressStreaming(messageId, text, true);
3277 addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));3328 addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
32783329
3330 // Ensure reasoning finish time is recorded if not already
3331 if (this.reasoningStartTime !== null && this.reasoningEndTime === null) {
3332 this.reasoningEndTime = Date.now();
3333 await this.#updateReasoningTime(messageId, { forceEnd: true });
3334 }
3335
3279 if (Array.isArray(this.swipes) && this.swipes.length > 0) {3336 if (Array.isArray(this.swipes) && this.swipes.length > 0) {
3280 const message = chat[messageId];3337 const message = chat[messageId];
3281 const swipeInfo = {3338 const swipeInfo = {
@@ -3377,7 +3434,7 @@ class StreamingProcessor {
3377 }3434 }
3378 this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);3435 this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
3379 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);3436 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
3380 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));3437 await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text));
3381 }3438 }
3382 const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;3439 const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
3383 console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);3440 console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
@@ -5738,6 +5795,25 @@ function extractReasoningFromData(data) {
5738}5795}
57395796
5740/**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 */
5804function 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/**
5741 * Extracts multiswipe swipes from the response data.5817 * Extracts multiswipe swipes from the response data.
5742 * @param {Object} data Response data5818 * @param {Object} data Response data
5743 * @param {string} type Type of generation5819 * @param {string} type Type of generation
@@ -7118,8 +7194,10 @@ export function setGenerationParamsFromPreset(preset) {
7118// Common code for message editor done and auto-save7194// Common code for message editor done and auto-save
7119function updateMessage(div) {7195function updateMessage(div) {
7120 const mesBlock = div.closest('.mes_block');7196 const mesBlock = div.closest('.mes_block');
7121 let text = mesBlock.find('.edit_textarea').val();7197 let text = mesBlock.find('.edit_textarea').val()
7122 const mes = chat[this_edit_mes_id];7198 ?? mesBlock.find('.mes_text').text();
7199 const mesElement = div.closest('.mes');
7200 const mes = chat[mesElement.attr('mesid')];
71237201
7124 let regexPlacement;7202 let regexPlacement;
7125 if (mes.is_user) {7203 if (mes.is_user) {
@@ -7238,6 +7316,11 @@ async function messageEditDone(div) {
7238 appendMediaToMessage(mes, div.closest('.mes'));7316 appendMediaToMessage(mes, div.closest('.mes'));
7239 addCopyToCodeBlocks(div.closest('.mes'));7317 addCopyToCodeBlocks(div.closest('.mes'));
72407318
7319 const reasoningEditDone = mesBlock.find('.mes_reasoning_edit_done:visible');
7320 if (reasoningEditDone.length > 0) {
7321 reasoningEditDone.trigger('click');
7322 }
7323
7241 await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);7324 await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
7242 this_edit_mes_id = undefined;7325 this_edit_mes_id = undefined;
7243 await saveChatConditional();7326 await saveChatConditional();
@@ -10745,6 +10828,12 @@ jQuery(async function () {
10745 var edit_mes_id = $(this).closest('.mes').attr('mesid');10828 var edit_mes_id = $(this).closest('.mes').attr('mesid');
10746 this_edit_mes_id = edit_mes_id;10829 this_edit_mes_id = edit_mes_id;
1074710830
10831 // Also edit reasoning, if it exists
10832 const reasoningEdit = $(this).closest('.mes_block').find('.mes_reasoning_edit:visible');
10833 if (reasoningEdit.length > 0) {
10834 reasoningEdit.trigger('click');
10835 }
10836
10748 var text = chat[edit_mes_id]['mes'];10837 var text = chat[edit_mes_id]['mes'];
10749 if (chat[edit_mes_id]['is_user']) {10838 if (chat[edit_mes_id]['is_user']) {
10750 this_edit_mes_chname = name1;10839 this_edit_mes_chname = name1;
@@ -10783,6 +10872,18 @@ jQuery(async function () {
10783 }10872 }
10784 });10873 });
1078510874
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
10786 $(document).on('input', '#curEditTextarea', function () {10887 $(document).on('input', '#curEditTextarea', function () {
10787 if (power_user.auto_save_msg_edits === true) {10888 if (power_user.auto_save_msg_edits === true) {
10788 messageEditAuto($(this));10889 messageEditAuto($(this));
@@ -10878,6 +10979,11 @@ jQuery(async function () {
10878 appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));10979 appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
10879 addCopyToCodeBlocks($(this).closest('.mes'));10980 addCopyToCodeBlocks($(this).closest('.mes'));
1088010981
10982 const reasoningEditDone = $(this).closest('.mes_block').find('.mes_reasoning_edit_cancel:visible');
10983 if (reasoningEditDone.length > 0) {
10984 reasoningEditDone.trigger('click');
10985 }
10986
10881 await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);10987 await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
10882 this_edit_mes_id = undefined;10988 this_edit_mes_id = undefined;
10883 });10989 });
@@ -11332,6 +11438,17 @@ jQuery(async function () {
11332 }11438 }
11333 });11439 });
1133411440
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
11335 $(document).keyup(function (e) {11452 $(document).keyup(function (e) {
11336 if (e.key === 'Escape') {11453 if (e.key === 'Escape') {
11337 const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0;11454 const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0;
public/scripts/RossAscends-mods.js+12 -3
@@ -1063,12 +1063,19 @@ export function initRossMods() {
1063 // Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead1063 // Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
1064 if (event.ctrlKey && event.key == 'Enter') {1064 if (event.ctrlKey && event.key == 'Enter') {
1065 const editMesDone = $('.mes_edit_done:visible');1065 const editMesDone = $('.mes_edit_done:visible');
1066 const reasoningMesDone = $('.mes_reasoning_edit_done:visible');
1066 if (editMesDone.length > 0) {1067 if (editMesDone.length > 0) {
1067 console.debug('Accepting edits with Ctrl+Enter');1068 console.debug('Accepting edits with Ctrl+Enter');
1068 $('#send_textarea').focus();1069 $('#send_textarea').trigger('focus');
1069 editMesDone.trigger('click');1070 editMesDone.trigger('click');
1070 return;1071 return;
1071 } else if (is_send_press == false) {1072 } else if (reasoningMesDone.length > 0) {
1073 console.debug('Accepting edits with Ctrl+Enter');
1074 $('#send_textarea').trigger('focus');
1075 reasoningMesDone.trigger('click');
1076 return;
1077 }
1078 else if (is_send_press == false) {
1072 const skipConfirmKey = 'RegenerateWithCtrlEnter';1079 const skipConfirmKey = 'RegenerateWithCtrlEnter';
1073 const skipConfirm = LoadLocalBool(skipConfirmKey);1080 const skipConfirm = LoadLocalBool(skipConfirmKey);
1074 function doRegenerate() {1081 function doRegenerate() {
@@ -1082,7 +1089,9 @@ export function initRossMods() {
1082 let regenerateWithCtrlEnter = false;1089 let regenerateWithCtrlEnter = false;
1083 const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {1090 const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
1084 customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],1091 customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
1085 onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,1092 onClose: (popup) => {
1093 regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false;
1094 },
1086 });1095 });
1087 if (!result) {1096 if (!result) {
1088 return;1097 return;
public/scripts/power-user.js+1 -0
@@ -256,6 +256,7 @@ let power_user = {
256 reasoning: {256 reasoning: {
257 auto_parse: false,257 auto_parse: false,
258 add_to_prompts: false,258 add_to_prompts: false,
259 auto_expand: false,
259 prefix: '<think>\n',260 prefix: '<think>\n',
260 suffix: '\n</think>',261 suffix: '\n</think>',
261 separator: '\n\n',262 separator: '\n\n',
public/scripts/reasoning.js+25 -2
@@ -23,6 +23,18 @@ function getMessageFromJquery(element) {
23}23}
2424
25/**25/**
26 * Toggles the auto-expand state of reasoning blocks.
27 */
28function toggleReasoningAutoExpand() {
29 const reasoningBlocks = document.querySelectorAll('details.mes_reasoning_details');
30 reasoningBlocks.forEach((block) => {
31 if (block instanceof HTMLDetailsElement) {
32 block.open = power_user.reasoning.auto_expand;
33 }
34 });
35}
36
37/**
26 * Helper class for adding reasoning to messages.38 * Helper class for adding reasoning to messages.
27 * Keeps track of the number of reasoning additions.39 * Keeps track of the number of reasoning additions.
28 */40 */
@@ -118,6 +130,13 @@ function loadReasoningSettings() {
118 power_user.reasoning.auto_parse = !!$(this).prop('checked');130 power_user.reasoning.auto_parse = !!$(this).prop('checked');
119 saveSettingsDebounced();131 saveSettingsDebounced();
120 });132 });
133
134 $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);
135 $('#reasoning_auto_expand').on('change', function () {
136 power_user.reasoning.auto_expand = !!$(this).prop('checked');
137 toggleReasoningAutoExpand();
138 saveSettingsDebounced();
139 });
121}140}
122141
123function registerReasoningSlashCommands() {142function registerReasoningSlashCommands() {
@@ -288,6 +307,8 @@ function setReasoningEventHandlers() {
288 await saveChatConditional();307 await saveChatConditional();
289 updateMessageBlock(messageId, message);308 updateMessageBlock(messageId, message);
290 textarea.remove();309 textarea.remove();
310
311 messageBlock.find('.mes_edit_done:visible').trigger('click');
291 });312 });
292313
293 $(document).on('click', '.mes_reasoning_edit_cancel', function (e) {314 $(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
@@ -297,6 +318,8 @@ function setReasoningEventHandlers() {
297 const { messageBlock } = getMessageFromJquery(this);318 const { messageBlock } = getMessageFromJquery(this);
298 const textarea = messageBlock.find('.reasoning_edit_textarea');319 const textarea = messageBlock.find('.reasoning_edit_textarea');
299 textarea.remove();320 textarea.remove();
321
322 messageBlock.find('.mes_reasoning_edit_cancel:visible').trigger('click');
300 });323 });
301324
302 $(document).on('click', '.mes_edit_add_reasoning', async function () {325 $(document).on('click', '.mes_edit_add_reasoning', async function () {
@@ -311,9 +334,8 @@ function setReasoningEventHandlers() {
311 }334 }
312335
313 message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;336 message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
314 await saveChatConditional();
315 closeMessageEditor();
316 updateMessageBlock(messageId, message);337 updateMessageBlock(messageId, message);
338 await saveChatConditional();
317 });339 });
318340
319 $(document).on('click', '.mes_reasoning_delete', async function (e) {341 $(document).on('click', '.mes_reasoning_delete', async function (e) {
@@ -441,6 +463,7 @@ function registerReasoningAppEvents() {
441463
442export function initReasoning() {464export function initReasoning() {
443 loadReasoningSettings();465 loadReasoningSettings();
466 toggleReasoningAutoExpand();
444 setReasoningEventHandlers();467 setReasoningEventHandlers();
445 registerReasoningSlashCommands();468 registerReasoningSlashCommands();
446 registerReasoningMacros();469 registerReasoningMacros();
public/style.css+84 -28
@@ -106,6 +106,8 @@
106 --tool-cool-color-picker-btn-bg: transparent;106 --tool-cool-color-picker-btn-bg: transparent;
107 --tool-cool-color-picker-btn-border-color: transparent;107 --tool-cool-color-picker-btn-border-color: transparent;
108108
109 --mes-right-spacing: 30px;
110
109 --avatar-base-height: 50px;111 --avatar-base-height: 50px;
110 --avatar-base-width: 50px;112 --avatar-base-width: 50px;
111 --avatar-base-border-radius: 2px;113 --avatar-base-border-radius: 2px;
@@ -342,18 +344,59 @@ input[type='checkbox']:focus-visible {
342344
343.mes_reasoning {345.mes_reasoning {
344 display: block;346 display: block;
345 border: 1px solid var(--SmartThemeBorderColor);347 border-left: 2px solid var(--SmartThemeEmColor);
346 background-color: var(--black30a);348 border-radius: 2px;
347 border-radius: 5px;
348 padding: 5px;349 padding: 5px;
349 margin: 5px 0;350 padding-left: 14px;
351 margin-bottom: 0.5em;
350 overflow-y: auto;352 overflow-y: auto;
353 color: var(--SmartThemeEmColor);
354}
355
356.mes_reasoning_details {
357 margin-right: var(--mes-right-spacing);
358}
359
360.mes_reasoning_details .mes_reasoning_summary {
361 margin-right: calc(var(--mes-right-spacing) * -1);
362}
363
364.mes_reasoning *:last-child {
365 margin-bottom: 0;
366}
367
368.mes_reasoning em,
369.mes_reasoning i,
370.mes_reasoning u,
371.mes_reasoning q,
372.mes_reasoning blockquote {
373 filter: saturate(0.5);
374}
375
376.mes_reasoning_details .mes_reasoning em {
377 color: color-mix(in srgb, var(--SmartThemeEmColor) 67%, var(--SmartThemeBlurTintColor) 33%)
378}
379
380.mes_reasoning_header_block {
381 flex-grow: 1;
351}382}
352383
353.mes_reasoning_summary {384.mes_reasoning_header {
354 cursor: pointer;385 cursor: pointer;
355 position: relative;386 position: relative;
356 margin: 2px;387 user-select: none;
388 margin: 0.5em 2px;
389 padding: 7px 14px;
390 padding-right: calc(0.7em + 14px);
391 border-radius: 10px;
392 background-color: var(--grey30);
393 font-size: calc(var(--mainFontSize) * 0.9);
394 align-items: baseline;
395}
396
397/* TWIMC: Remove with custom CSS to show the icon */
398.mes_reasoning_header > .icon-svg {
399 display: none;
357}400}
358401
359@supports not selector(:has(*)) {402@supports not selector(:has(*)) {
@@ -365,27 +408,27 @@ input[type='checkbox']:focus-visible {
365.mes_bias:empty,408.mes_bias:empty,
366.mes_reasoning:empty,409.mes_reasoning:empty,
367.mes_reasoning_details:has(.mes_reasoning:empty),410.mes_reasoning_details:has(.mes_reasoning:empty),
368.mes_block:has(.edit_textarea) .mes_reasoning_details,
369.mes_reasoning_details:not([open]) .mes_reasoning_actions,411.mes_reasoning_details:not([open]) .mes_reasoning_actions,
370.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning,412.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning,
371.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_done,413.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_header,
372.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_cancel,414.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .edit_button,
373.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.mes_reasoning_edit_done, .mes_reasoning_edit_cancel) {415.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.edit_button),
416.mes_block:has(.edit_textarea):has(.reasoning_edit_textarea) .mes_reasoning_actions {
374 display: none;417 display: none;
375}418}
376419
377.mes_reasoning_actions {420.mes_reasoning_details .mes_reasoning_arrow {
378 position: absolute;421 position: absolute;
379 right: 0;422 top: 50%;
380 top: 0;423 right: 7px;
424 transform: translateY(-50%);
425 font-size: calc(var(--mainFontSize) * 0.7);
426 width: calc(var(--mainFontSize) * 0.7);
427 height: calc(var(--mainFontSize) * 0.7);
428}
381429
382 display: flex;430.mes_reasoning_details:not([open]) .mes_reasoning_arrow {
383 gap: 4px;431 transform: translateY(-50%) rotate(180deg);
384 flex-wrap: nowrap;
385 justify-content: flex-end;
386 transition: all 200ms;
387 overflow-x: hidden;
388 padding: 1px;
389}432}
390433
391.mes_reasoning_summary>span {434.mes_reasoning_summary>span {
@@ -1100,13 +1143,8 @@ body .panelControlBar {
1100 /*only affects bubblechat to make it sit nicely at the bottom*/1143 /*only affects bubblechat to make it sit nicely at the bottom*/
1101}1144}
11021145
1103.last_mes:has(.mes_text:empty):has(.mes_reasoning_details[open]) .mes_reasoning:not(:empty) {1146.last_mes:has(.mes_text:empty):has(.mes_reasoning_details) .mes_reasoning:not(:empty) {
1104 margin-bottom: 30px;1147 margin-bottom: var(--mes-right-spacing);
1105}
1106
1107.last_mes .mes_reasoning,
1108.last_mes .mes_text {
1109 padding-right: 30px;
1110}1148}
11111149
1112/* SWIPE RELATED STYLES*/1150/* SWIPE RELATED STYLES*/
@@ -1330,6 +1368,7 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
1330 padding-left: 0;1368 padding-left: 0;
1331 padding-top: 5px;1369 padding-top: 5px;
1332 padding-bottom: 5px;1370 padding-bottom: 5px;
1371 padding-right: var(--mes-right-spacing);
1333}1372}
13341373
1335br {1374br {
@@ -3000,6 +3039,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
3000.mes_block .ch_name {3039.mes_block .ch_name {
3001 max-width: 100%;3040 max-width: 100%;
3002 min-height: 22px;3041 min-height: 22px;
3042 align-items: flex-start;
3003}3043}
30043044
3005/*applies to both groups and solos chars in the char list*/3045/*applies to both groups and solos chars in the char list*/
@@ -4224,7 +4264,13 @@ input[type="range"]::-webkit-slider-thumb {
4224 transition: 0.3s ease-in-out;4264 transition: 0.3s ease-in-out;
4225}4265}
42264266
4227.mes_edit_buttons .menu_button {4267.mes_reasoning_actions {
4268 margin: 0;
4269 margin-top: 0.5em;
4270}
4271
4272.mes_edit_buttons .menu_button,
4273.mes_reasoning_actions .edit_button {
4228 opacity: 0.5;4274 opacity: 0.5;
4229 padding: 0px;4275 padding: 0px;
4230 font-size: 1rem;4276 font-size: 1rem;
@@ -4237,6 +4283,12 @@ input[type="range"]::-webkit-slider-thumb {
4237 align-items: center;4283 align-items: center;
4238}4284}
42394285
4286.mes_reasoning_actions .edit_button {
4287 margin-bottom: 0.5em;
4288 opacity: 1;
4289 filter: brightness(0.7);
4290}
4291
4240.mes_reasoning_edit_cancel,4292.mes_reasoning_edit_cancel,
4241.mes_edit_cancel.menu_button {4293.mes_edit_cancel.menu_button {
4242 background-color: var(--crimson70a);4294 background-color: var(--crimson70a);
@@ -4263,6 +4315,10 @@ input[type="range"]::-webkit-slider-thumb {
4263 field-sizing: content;4315 field-sizing: content;
4264}4316}
42654317
4318.mes.reasoning .mes_edit_add_reasoning {
4319 display: none;
4320}
4321
4266#anchor_order {4322#anchor_order {
4267 margin-bottom: 15px;4323 margin-bottom: 15px;
4268}4324}