mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
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
This commit is contained in:
@@ -3789,6 +3789,12 @@
|
|||||||
Auto-Parse Reasoning
|
Auto-Parse Reasoning
|
||||||
</small>
|
</small>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox_label" for="reasoning_auto_expand" title="Automatically expand reasoning blocks." data-i18n="[title]reasoning_auto_expand">
|
||||||
|
<input id="reasoning_auto_expand" type="checkbox" />
|
||||||
|
<small data-i18n="Auto-Expand Reasoning">
|
||||||
|
Auto-Expand Reasoning
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
<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">
|
<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">
|
||||||
<input id="reasoning_add_to_prompts" type="checkbox" />
|
<input id="reasoning_add_to_prompts" type="checkbox" />
|
||||||
<small data-i18n="Add Reasoning to Prompts">
|
<small data-i18n="Add Reasoning to Prompts">
|
||||||
@@ -6248,14 +6254,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<details class="mes_reasoning_details">
|
<details class="mes_reasoning_details">
|
||||||
<summary class="mes_reasoning_summary">
|
<summary class="mes_reasoning_summary flex-container">
|
||||||
<span data-i18n="Reasoning">Reasoning</span>
|
<div class="mes_reasoning_header_block flex-container">
|
||||||
<div class="mes_reasoning_actions">
|
<div class="mes_reasoning_header flex-container">
|
||||||
<div class="mes_reasoning_edit_done mes_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div>
|
<span class="mes_reasoning_header_title" data-i18n="Thought for some time">Thought for some time</span>
|
||||||
<div class="mes_reasoning_edit_cancel mes_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div>
|
<div class="mes_reasoning_arrow fa-solid fa-chevron-up"></div>
|
||||||
<div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mes_reasoning_actions flex-container">
|
||||||
|
<div class="mes_reasoning_edit_done menu_button edit_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div>
|
||||||
|
<div class="mes_reasoning_delete menu_button edit_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div>
|
||||||
|
<div class="mes_reasoning_edit_cancel menu_button edit_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div>
|
||||||
<div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div>
|
<div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div>
|
||||||
<div class="mes_reasoning_delete mes_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div>
|
<div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="mes_reasoning"></div>
|
<div class="mes_reasoning"></div>
|
||||||
|
169
public/script.js
169
public/script.js
@@ -225,7 +225,7 @@ import {
|
|||||||
instruct_presets,
|
instruct_presets,
|
||||||
selectContextPreset,
|
selectContextPreset,
|
||||||
} from './scripts/instruct-mode.js';
|
} from './scripts/instruct-mode.js';
|
||||||
import { initLocales, t } from './scripts/i18n.js';
|
import { getCurrentLocale, initLocales, t } from './scripts/i18n.js';
|
||||||
import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
|
import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js';
|
||||||
import {
|
import {
|
||||||
user_avatar,
|
user_avatar,
|
||||||
@@ -495,6 +495,7 @@ export const event_types = {
|
|||||||
/** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
|
/** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */
|
||||||
SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
||||||
STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
STREAM_TOKEN_RECEIVED: 'stream_token_received',
|
||||||
|
STREAM_REASONING_DONE: 'stream_reasoning_done',
|
||||||
FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
|
FILE_ATTACHMENT_DELETED: 'file_attachment_deleted',
|
||||||
WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
|
WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate',
|
||||||
OPEN_CHARACTER_LIBRARY: 'open_character_library',
|
OPEN_CHARACTER_LIBRARY: 'open_character_library',
|
||||||
@@ -2189,26 +2190,29 @@ function insertSVGIcon(mes, extra) {
|
|||||||
modelName = extra.api;
|
modelName = extra.api;
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = new Image();
|
const insertOrReplaceSVG = (image, className, targetSelector, insertBefore) => {
|
||||||
// Add classes for styling and identification
|
image.onload = async function () {
|
||||||
image.classList.add('icon-svg', 'timestamp-icon');
|
let existingSVG = insertBefore ? mes.find(targetSelector).prev(`.${className}`) : mes.find(targetSelector).next(`.${className}`);
|
||||||
image.src = `/img/${modelName}.svg`;
|
if (existingSVG.length) {
|
||||||
image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
|
existingSVG.replaceWith(image);
|
||||||
|
} else {
|
||||||
image.onload = async function () {
|
if (insertBefore) mes.find(targetSelector).before(image);
|
||||||
// Check if an SVG already exists adjacent to the timestamp
|
else mes.find(targetSelector).after(image);
|
||||||
let existingSVG = mes.find('.timestamp').next('.timestamp-icon');
|
}
|
||||||
|
await SVGInject(image);
|
||||||
if (existingSVG.length) {
|
};
|
||||||
// Replace existing SVG
|
|
||||||
existingSVG.replaceWith(image);
|
|
||||||
} else {
|
|
||||||
// Append the new SVG if none exists
|
|
||||||
mes.find('.timestamp').after(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
await SVGInject(image);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createModelImage = (className, targetSelector, insertBefore) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.classList.add('icon-svg', className);
|
||||||
|
image.src = `/img/${modelName}.svg`;
|
||||||
|
image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
|
||||||
|
insertOrReplaceSVG(image, className, targetSelector, insertBefore);
|
||||||
|
};
|
||||||
|
|
||||||
|
createModelImage('timestamp-icon', '.timestamp');
|
||||||
|
createModelImage('thinking-icon', '.mes_reasoning_header_title', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2220,6 +2224,7 @@ function getMessageFromTemplate({
|
|||||||
avatarImg,
|
avatarImg,
|
||||||
bias,
|
bias,
|
||||||
reasoning,
|
reasoning,
|
||||||
|
reasoningDuration,
|
||||||
isSystem,
|
isSystem,
|
||||||
title,
|
title,
|
||||||
timerValue,
|
timerValue,
|
||||||
@@ -2252,6 +2257,13 @@ function getMessageFromTemplate({
|
|||||||
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
|
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
|
||||||
bookmarkLink && updateBookmarkDisplay(mes);
|
bookmarkLink && updateBookmarkDisplay(mes);
|
||||||
|
|
||||||
|
if (reasoning) {
|
||||||
|
mes.addClass('reasoning');
|
||||||
|
}
|
||||||
|
if (reasoningDuration) {
|
||||||
|
updateReasoningTimeUI(mes.find('.mes_reasoning_header_title')[0], reasoningDuration, { forceEnd: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (power_user.timestamp_model_icon && extra?.api) {
|
if (power_user.timestamp_model_icon && extra?.api) {
|
||||||
insertSVGIcon(mes, extra);
|
insertSVGIcon(mes, extra);
|
||||||
}
|
}
|
||||||
@@ -2269,6 +2281,7 @@ export function updateMessageBlock(messageId, message) {
|
|||||||
const text = message?.extra?.display_text ?? message.mes;
|
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_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.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true));
|
||||||
|
messageElement.toggleClass('reasoning', !!message.extra?.reasoning);
|
||||||
addCopyToCodeBlocks(messageElement);
|
addCopyToCodeBlocks(messageElement);
|
||||||
appendMediaToMessage(message, messageElement);
|
appendMediaToMessage(message, messageElement);
|
||||||
}
|
}
|
||||||
@@ -2439,6 +2452,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
|
|||||||
avatarImg: avatarImg,
|
avatarImg: avatarImg,
|
||||||
bias: bias,
|
bias: bias,
|
||||||
reasoning: reasoning,
|
reasoning: reasoning,
|
||||||
|
reasoningDuration: mes.extra?.reasoning_duration,
|
||||||
isSystem: isSystem,
|
isSystem: isSystem,
|
||||||
title: title,
|
title: title,
|
||||||
bookmarkLink: bookmarkLink,
|
bookmarkLink: bookmarkLink,
|
||||||
@@ -3108,8 +3122,12 @@ class StreamingProcessor {
|
|||||||
this.messageDom = null;
|
this.messageDom = null;
|
||||||
this.messageTextDom = null;
|
this.messageTextDom = null;
|
||||||
this.messageTimerDom = null;
|
this.messageTimerDom = null;
|
||||||
|
/** @type {HTMLElement} */
|
||||||
this.messageTokenCounterDom = null;
|
this.messageTokenCounterDom = null;
|
||||||
|
/** @type {HTMLElement} */
|
||||||
this.messageReasoningDom = null;
|
this.messageReasoningDom = null;
|
||||||
|
/** @type {HTMLElement} */
|
||||||
|
this.messageReasoningHeaderDom = null;
|
||||||
/** @type {HTMLTextAreaElement} */
|
/** @type {HTMLTextAreaElement} */
|
||||||
this.sendTextarea = document.querySelector('#send_textarea');
|
this.sendTextarea = document.querySelector('#send_textarea');
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -3126,6 +3144,15 @@ class StreamingProcessor {
|
|||||||
this.messageLogprobs = [];
|
this.messageLogprobs = [];
|
||||||
this.toolCalls = [];
|
this.toolCalls = [];
|
||||||
this.reasoning = '';
|
this.reasoning = '';
|
||||||
|
this.reasoningStartTime = null;
|
||||||
|
this.reasoningEndTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reasoningDuration() {
|
||||||
|
if (this.reasoningStartTime && this.reasoningEndTime) {
|
||||||
|
return (this.reasoningEndTime - this.reasoningStartTime);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#checkDomElements(messageId) {
|
#checkDomElements(messageId) {
|
||||||
@@ -3135,6 +3162,7 @@ class StreamingProcessor {
|
|||||||
this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
|
this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
|
||||||
this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
|
this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
|
||||||
this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
|
this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
|
||||||
|
this.messageReasoningHeaderDom = this.messageDom?.querySelector('.mes_reasoning_header_title');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3182,7 +3210,7 @@ class StreamingProcessor {
|
|||||||
return messageId;
|
return messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgressStreaming(messageId, text, isFinal) {
|
async onProgressStreaming(messageId, text, isFinal) {
|
||||||
const isImpersonate = this.type == 'impersonate';
|
const isImpersonate = this.type == 'impersonate';
|
||||||
const isContinue = this.type == 'continue';
|
const isContinue = this.type == 'continue';
|
||||||
|
|
||||||
@@ -3209,6 +3237,8 @@ class StreamingProcessor {
|
|||||||
this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
|
this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const mesChanged = chat[messageId]['mes'] !== processedText;
|
||||||
|
|
||||||
this.#checkDomElements(messageId);
|
this.#checkDomElements(messageId);
|
||||||
this.#updateMessageBlockVisibility();
|
this.#updateMessageBlockVisibility();
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
@@ -3221,11 +3251,25 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.reasoning) {
|
if (this.reasoning) {
|
||||||
chat[messageId]['extra']['reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
|
const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
|
||||||
|
const reasoningChanged = chat[messageId]['extra']['reasoning'] !== reasoning;
|
||||||
|
chat[messageId]['extra']['reasoning'] = reasoning;
|
||||||
|
|
||||||
|
if (reasoningChanged && this.reasoningStartTime === null) {
|
||||||
|
this.reasoningStartTime = Date.now();
|
||||||
|
}
|
||||||
|
if (!reasoningChanged && mesChanged && this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||||
|
this.reasoningEndTime = Date.now();
|
||||||
|
}
|
||||||
|
await this.#updateReasoningTime(messageId);
|
||||||
|
|
||||||
if (this.messageReasoningDom instanceof HTMLElement) {
|
if (this.messageReasoningDom instanceof HTMLElement) {
|
||||||
const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
|
const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
|
||||||
this.messageReasoningDom.innerHTML = formattedReasoning;
|
this.messageReasoningDom.innerHTML = formattedReasoning;
|
||||||
}
|
}
|
||||||
|
if (this.messageDom instanceof HTMLElement) {
|
||||||
|
this.messageDom.classList.add('reasoning');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't waste time calculating token count for streaming
|
// Don't waste time calculating token count for streaming
|
||||||
@@ -3271,11 +3315,24 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async #updateReasoningTime(messageId, { forceEnd = false } = {}) {
|
||||||
|
const duration = this.#reasoningDuration();
|
||||||
|
chat[messageId]['extra']['reasoning_duration'] = duration;
|
||||||
|
updateReasoningTimeUI(this.messageReasoningHeaderDom, duration, { forceEnd: forceEnd });
|
||||||
|
await eventSource.emit(event_types.STREAM_REASONING_DONE, this.reasoning, duration);
|
||||||
|
}
|
||||||
|
|
||||||
async onFinishStreaming(messageId, text) {
|
async onFinishStreaming(messageId, text) {
|
||||||
this.hideMessageButtons(this.messageId);
|
this.hideMessageButtons(this.messageId);
|
||||||
this.onProgressStreaming(messageId, text, true);
|
await this.onProgressStreaming(messageId, text, true);
|
||||||
addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
|
addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
|
||||||
|
|
||||||
|
// Ensure reasoning finish time is recorded if not already
|
||||||
|
if (this.reasoningStartTime !== null && this.reasoningEndTime === null) {
|
||||||
|
this.reasoningEndTime = Date.now();
|
||||||
|
await this.#updateReasoningTime(messageId, { forceEnd: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(this.swipes) && this.swipes.length > 0) {
|
if (Array.isArray(this.swipes) && this.swipes.length > 0) {
|
||||||
const message = chat[messageId];
|
const message = chat[messageId];
|
||||||
const swipeInfo = {
|
const swipeInfo = {
|
||||||
@@ -3377,7 +3434,7 @@ class StreamingProcessor {
|
|||||||
}
|
}
|
||||||
this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
|
this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
|
||||||
await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
|
await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
|
||||||
await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
|
await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text));
|
||||||
}
|
}
|
||||||
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
|
const seconds = (timestamps[timestamps.length - 1] - timestamps[0]) / 1000;
|
||||||
console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
|
console.warn(`Stream stats: ${timestamps.length} tokens, ${seconds.toFixed(2)} seconds, rate: ${Number(timestamps.length / seconds).toFixed(2)} TPS`);
|
||||||
@@ -5737,6 +5794,25 @@ function extractReasoningFromData(data) {
|
|||||||
return '';
|
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.
|
* Extracts multiswipe swipes from the response data.
|
||||||
* @param {Object} data Response data
|
* @param {Object} data Response data
|
||||||
@@ -7118,8 +7194,10 @@ export function setGenerationParamsFromPreset(preset) {
|
|||||||
// Common code for message editor done and auto-save
|
// Common code for message editor done and auto-save
|
||||||
function updateMessage(div) {
|
function updateMessage(div) {
|
||||||
const mesBlock = div.closest('.mes_block');
|
const mesBlock = div.closest('.mes_block');
|
||||||
let text = mesBlock.find('.edit_textarea').val();
|
let text = mesBlock.find('.edit_textarea').val()
|
||||||
const mes = chat[this_edit_mes_id];
|
?? mesBlock.find('.mes_text').text();
|
||||||
|
const mesElement = div.closest('.mes');
|
||||||
|
const mes = chat[mesElement.attr('mesid')];
|
||||||
|
|
||||||
let regexPlacement;
|
let regexPlacement;
|
||||||
if (mes.is_user) {
|
if (mes.is_user) {
|
||||||
@@ -7238,6 +7316,11 @@ async function messageEditDone(div) {
|
|||||||
appendMediaToMessage(mes, div.closest('.mes'));
|
appendMediaToMessage(mes, div.closest('.mes'));
|
||||||
addCopyToCodeBlocks(div.closest('.mes'));
|
addCopyToCodeBlocks(div.closest('.mes'));
|
||||||
|
|
||||||
|
const reasoningEditDone = mesBlock.find('.mes_reasoning_edit_done:visible');
|
||||||
|
if (reasoningEditDone.length > 0) {
|
||||||
|
reasoningEditDone.trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
|
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
|
||||||
this_edit_mes_id = undefined;
|
this_edit_mes_id = undefined;
|
||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
@@ -10745,6 +10828,12 @@ jQuery(async function () {
|
|||||||
var edit_mes_id = $(this).closest('.mes').attr('mesid');
|
var edit_mes_id = $(this).closest('.mes').attr('mesid');
|
||||||
this_edit_mes_id = edit_mes_id;
|
this_edit_mes_id = edit_mes_id;
|
||||||
|
|
||||||
|
// Also edit reasoning, if it exists
|
||||||
|
const reasoningEdit = $(this).closest('.mes_block').find('.mes_reasoning_edit:visible');
|
||||||
|
if (reasoningEdit.length > 0) {
|
||||||
|
reasoningEdit.trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
var text = chat[edit_mes_id]['mes'];
|
var text = chat[edit_mes_id]['mes'];
|
||||||
if (chat[edit_mes_id]['is_user']) {
|
if (chat[edit_mes_id]['is_user']) {
|
||||||
this_edit_mes_chname = name1;
|
this_edit_mes_chname = name1;
|
||||||
@@ -10783,6 +10872,18 @@ 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 () {
|
$(document).on('input', '#curEditTextarea', function () {
|
||||||
if (power_user.auto_save_msg_edits === true) {
|
if (power_user.auto_save_msg_edits === true) {
|
||||||
messageEditAuto($(this));
|
messageEditAuto($(this));
|
||||||
@@ -10878,6 +10979,11 @@ jQuery(async function () {
|
|||||||
appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
|
appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
|
||||||
addCopyToCodeBlocks($(this).closest('.mes'));
|
addCopyToCodeBlocks($(this).closest('.mes'));
|
||||||
|
|
||||||
|
const reasoningEditDone = $(this).closest('.mes_block').find('.mes_reasoning_edit_cancel:visible');
|
||||||
|
if (reasoningEditDone.length > 0) {
|
||||||
|
reasoningEditDone.trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
|
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
|
||||||
this_edit_mes_id = undefined;
|
this_edit_mes_id = undefined;
|
||||||
});
|
});
|
||||||
@@ -11332,6 +11438,17 @@ 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) {
|
$(document).keyup(function (e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0;
|
const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0;
|
||||||
|
@@ -1063,12 +1063,19 @@ export function initRossMods() {
|
|||||||
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
|
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
|
||||||
if (event.ctrlKey && event.key == 'Enter') {
|
if (event.ctrlKey && event.key == 'Enter') {
|
||||||
const editMesDone = $('.mes_edit_done:visible');
|
const editMesDone = $('.mes_edit_done:visible');
|
||||||
|
const reasoningMesDone = $('.mes_reasoning_edit_done:visible');
|
||||||
if (editMesDone.length > 0) {
|
if (editMesDone.length > 0) {
|
||||||
console.debug('Accepting edits with Ctrl+Enter');
|
console.debug('Accepting edits with Ctrl+Enter');
|
||||||
$('#send_textarea').focus();
|
$('#send_textarea').trigger('focus');
|
||||||
editMesDone.trigger('click');
|
editMesDone.trigger('click');
|
||||||
return;
|
return;
|
||||||
} else if (is_send_press == false) {
|
} else if (reasoningMesDone.length > 0) {
|
||||||
|
console.debug('Accepting edits with Ctrl+Enter');
|
||||||
|
$('#send_textarea').trigger('focus');
|
||||||
|
reasoningMesDone.trigger('click');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (is_send_press == false) {
|
||||||
const skipConfirmKey = 'RegenerateWithCtrlEnter';
|
const skipConfirmKey = 'RegenerateWithCtrlEnter';
|
||||||
const skipConfirm = LoadLocalBool(skipConfirmKey);
|
const skipConfirm = LoadLocalBool(skipConfirmKey);
|
||||||
function doRegenerate() {
|
function doRegenerate() {
|
||||||
@@ -1082,7 +1089,9 @@ export function initRossMods() {
|
|||||||
let regenerateWithCtrlEnter = false;
|
let regenerateWithCtrlEnter = false;
|
||||||
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
|
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
|
||||||
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
|
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
|
||||||
onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,
|
onClose: (popup) => {
|
||||||
|
regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
|
@@ -256,6 +256,7 @@ let power_user = {
|
|||||||
reasoning: {
|
reasoning: {
|
||||||
auto_parse: false,
|
auto_parse: false,
|
||||||
add_to_prompts: false,
|
add_to_prompts: false,
|
||||||
|
auto_expand: false,
|
||||||
prefix: '<think>\n',
|
prefix: '<think>\n',
|
||||||
suffix: '\n</think>',
|
suffix: '\n</think>',
|
||||||
separator: '\n\n',
|
separator: '\n\n',
|
||||||
|
@@ -22,6 +22,18 @@ function getMessageFromJquery(element) {
|
|||||||
return { messageId: messageId, message, messageBlock };
|
return { messageId: messageId, message, messageBlock };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the auto-expand state of reasoning blocks.
|
||||||
|
*/
|
||||||
|
function toggleReasoningAutoExpand() {
|
||||||
|
const reasoningBlocks = document.querySelectorAll('details.mes_reasoning_details');
|
||||||
|
reasoningBlocks.forEach((block) => {
|
||||||
|
if (block instanceof HTMLDetailsElement) {
|
||||||
|
block.open = power_user.reasoning.auto_expand;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for adding reasoning to messages.
|
* Helper class for adding reasoning to messages.
|
||||||
* Keeps track of the number of reasoning additions.
|
* Keeps track of the number of reasoning additions.
|
||||||
@@ -118,6 +130,13 @@ function loadReasoningSettings() {
|
|||||||
power_user.reasoning.auto_parse = !!$(this).prop('checked');
|
power_user.reasoning.auto_parse = !!$(this).prop('checked');
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);
|
||||||
|
$('#reasoning_auto_expand').on('change', function () {
|
||||||
|
power_user.reasoning.auto_expand = !!$(this).prop('checked');
|
||||||
|
toggleReasoningAutoExpand();
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerReasoningSlashCommands() {
|
function registerReasoningSlashCommands() {
|
||||||
@@ -288,6 +307,8 @@ function setReasoningEventHandlers() {
|
|||||||
await saveChatConditional();
|
await saveChatConditional();
|
||||||
updateMessageBlock(messageId, message);
|
updateMessageBlock(messageId, message);
|
||||||
textarea.remove();
|
textarea.remove();
|
||||||
|
|
||||||
|
messageBlock.find('.mes_edit_done:visible').trigger('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
|
$(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
|
||||||
@@ -297,6 +318,8 @@ function setReasoningEventHandlers() {
|
|||||||
const { messageBlock } = getMessageFromJquery(this);
|
const { messageBlock } = getMessageFromJquery(this);
|
||||||
const textarea = messageBlock.find('.reasoning_edit_textarea');
|
const textarea = messageBlock.find('.reasoning_edit_textarea');
|
||||||
textarea.remove();
|
textarea.remove();
|
||||||
|
|
||||||
|
messageBlock.find('.mes_reasoning_edit_cancel:visible').trigger('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.mes_edit_add_reasoning', async function () {
|
$(document).on('click', '.mes_edit_add_reasoning', async function () {
|
||||||
@@ -311,9 +334,8 @@ function setReasoningEventHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
|
message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
|
||||||
await saveChatConditional();
|
|
||||||
closeMessageEditor();
|
|
||||||
updateMessageBlock(messageId, message);
|
updateMessageBlock(messageId, message);
|
||||||
|
await saveChatConditional();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.mes_reasoning_delete', async function (e) {
|
$(document).on('click', '.mes_reasoning_delete', async function (e) {
|
||||||
@@ -441,6 +463,7 @@ function registerReasoningAppEvents() {
|
|||||||
|
|
||||||
export function initReasoning() {
|
export function initReasoning() {
|
||||||
loadReasoningSettings();
|
loadReasoningSettings();
|
||||||
|
toggleReasoningAutoExpand();
|
||||||
setReasoningEventHandlers();
|
setReasoningEventHandlers();
|
||||||
registerReasoningSlashCommands();
|
registerReasoningSlashCommands();
|
||||||
registerReasoningMacros();
|
registerReasoningMacros();
|
||||||
|
112
public/style.css
112
public/style.css
@@ -106,6 +106,8 @@
|
|||||||
--tool-cool-color-picker-btn-bg: transparent;
|
--tool-cool-color-picker-btn-bg: transparent;
|
||||||
--tool-cool-color-picker-btn-border-color: transparent;
|
--tool-cool-color-picker-btn-border-color: transparent;
|
||||||
|
|
||||||
|
--mes-right-spacing: 30px;
|
||||||
|
|
||||||
--avatar-base-height: 50px;
|
--avatar-base-height: 50px;
|
||||||
--avatar-base-width: 50px;
|
--avatar-base-width: 50px;
|
||||||
--avatar-base-border-radius: 2px;
|
--avatar-base-border-radius: 2px;
|
||||||
@@ -342,18 +344,59 @@ input[type='checkbox']:focus-visible {
|
|||||||
|
|
||||||
.mes_reasoning {
|
.mes_reasoning {
|
||||||
display: block;
|
display: block;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
border-left: 2px solid var(--SmartThemeEmColor);
|
||||||
background-color: var(--black30a);
|
border-radius: 2px;
|
||||||
border-radius: 5px;
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin: 5px 0;
|
padding-left: 14px;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
color: var(--SmartThemeEmColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mes_reasoning_summary {
|
.mes_reasoning_details {
|
||||||
|
margin-right: var(--mes-right-spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_reasoning_details .mes_reasoning_summary {
|
||||||
|
margin-right: calc(var(--mes-right-spacing) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_reasoning *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_reasoning em,
|
||||||
|
.mes_reasoning i,
|
||||||
|
.mes_reasoning u,
|
||||||
|
.mes_reasoning q,
|
||||||
|
.mes_reasoning blockquote {
|
||||||
|
filter: saturate(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_reasoning_details .mes_reasoning em {
|
||||||
|
color: color-mix(in srgb, var(--SmartThemeEmColor) 67%, var(--SmartThemeBlurTintColor) 33%)
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_reasoning_header_block {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_reasoning_header {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 2px;
|
user-select: none;
|
||||||
|
margin: 0.5em 2px;
|
||||||
|
padding: 7px 14px;
|
||||||
|
padding-right: calc(0.7em + 14px);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--grey30);
|
||||||
|
font-size: calc(var(--mainFontSize) * 0.9);
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TWIMC: Remove with custom CSS to show the icon */
|
||||||
|
.mes_reasoning_header > .icon-svg {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports not selector(:has(*)) {
|
@supports not selector(:has(*)) {
|
||||||
@@ -365,27 +408,27 @@ input[type='checkbox']:focus-visible {
|
|||||||
.mes_bias:empty,
|
.mes_bias:empty,
|
||||||
.mes_reasoning:empty,
|
.mes_reasoning:empty,
|
||||||
.mes_reasoning_details:has(.mes_reasoning:empty),
|
.mes_reasoning_details:has(.mes_reasoning:empty),
|
||||||
.mes_block:has(.edit_textarea) .mes_reasoning_details,
|
|
||||||
.mes_reasoning_details:not([open]) .mes_reasoning_actions,
|
.mes_reasoning_details:not([open]) .mes_reasoning_actions,
|
||||||
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning,
|
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning,
|
||||||
.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_done,
|
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_header,
|
||||||
.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_cancel,
|
.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .edit_button,
|
||||||
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.mes_reasoning_edit_done, .mes_reasoning_edit_cancel) {
|
.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.edit_button),
|
||||||
|
.mes_block:has(.edit_textarea):has(.reasoning_edit_textarea) .mes_reasoning_actions {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mes_reasoning_actions {
|
.mes_reasoning_details .mes_reasoning_arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
top: 50%;
|
||||||
top: 0;
|
right: 7px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: calc(var(--mainFontSize) * 0.7);
|
||||||
|
width: calc(var(--mainFontSize) * 0.7);
|
||||||
|
height: calc(var(--mainFontSize) * 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
display: flex;
|
.mes_reasoning_details:not([open]) .mes_reasoning_arrow {
|
||||||
gap: 4px;
|
transform: translateY(-50%) rotate(180deg);
|
||||||
flex-wrap: nowrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
transition: all 200ms;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mes_reasoning_summary>span {
|
.mes_reasoning_summary>span {
|
||||||
@@ -1100,13 +1143,8 @@ body .panelControlBar {
|
|||||||
/*only affects bubblechat to make it sit nicely at the bottom*/
|
/*only affects bubblechat to make it sit nicely at the bottom*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.last_mes:has(.mes_text:empty):has(.mes_reasoning_details[open]) .mes_reasoning:not(:empty) {
|
.last_mes:has(.mes_text:empty):has(.mes_reasoning_details) .mes_reasoning:not(:empty) {
|
||||||
margin-bottom: 30px;
|
margin-bottom: var(--mes-right-spacing);
|
||||||
}
|
|
||||||
|
|
||||||
.last_mes .mes_reasoning,
|
|
||||||
.last_mes .mes_text {
|
|
||||||
padding-right: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SWIPE RELATED STYLES*/
|
/* SWIPE RELATED STYLES*/
|
||||||
@@ -1330,6 +1368,7 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
|
padding-right: var(--mes-right-spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
br {
|
br {
|
||||||
@@ -3000,6 +3039,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
|||||||
.mes_block .ch_name {
|
.mes_block .ch_name {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-height: 22px;
|
min-height: 22px;
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*applies to both groups and solos chars in the char list*/
|
/*applies to both groups and solos chars in the char list*/
|
||||||
@@ -4224,7 +4264,13 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
transition: 0.3s ease-in-out;
|
transition: 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mes_edit_buttons .menu_button {
|
.mes_reasoning_actions {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mes_edit_buttons .menu_button,
|
||||||
|
.mes_reasoning_actions .edit_button {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -4237,6 +4283,12 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mes_reasoning_actions .edit_button {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
opacity: 1;
|
||||||
|
filter: brightness(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
.mes_reasoning_edit_cancel,
|
.mes_reasoning_edit_cancel,
|
||||||
.mes_edit_cancel.menu_button {
|
.mes_edit_cancel.menu_button {
|
||||||
background-color: var(--crimson70a);
|
background-color: var(--crimson70a);
|
||||||
@@ -4263,6 +4315,10 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
field-sizing: content;
|
field-sizing: content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mes.reasoning .mes_edit_add_reasoning {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#anchor_order {
|
#anchor_order {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user