Move reasoning-specific code into its own module

eb798fa4f191892bffe07d40bd255b69dddf2d4f

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

4 files changed, +231 -222Ignore whitespace
public/script.js+3 -1
@@ -238,7 +238,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set
238238import { hideLoader, showLoader } from './scripts/loader.js';
239239import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js';
240240import { loadFeatherlessModels, loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadVllmModels, loadAphroditeModels, loadDreamGenModels, initTextGenModels, loadTabbyModels, loadGenericModels } from './scripts/textgen-models.js';
241241import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, PromptReasoning } from './scripts/chats.js';
242242import { getPresetManager, initPresetManager } from './scripts/preset-manager.js';
243243import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js';
244244import { currentUser, setUserControls } from './scripts/user.js';
@@ -267,6 +267,7 @@ import { initSettingsSearch } from './scripts/setting-search.js';
267267import { initBulkEdit } from './scripts/bulk-edit.js';
268268import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
269269import { getContext } from './scripts/st-context.js';
270+import { initReasoning, PromptReasoning } from './scripts/reasoning.js';
270271
271272// API OBJECT FOR EXTERNAL WIRING
272273globalThis.SillyTavern = {
@@ -982,6 +983,7 @@ async function firstLoadInit() {
982983 initServerHistory();
983984 initSettingsSearch();
984985 initBulkEdit();
986+ initReasoning();
985987 await initScrapers();
986988 doDailyExtensionUpdatesCheck();
987989 await hideLoader();
public/scripts/chats.js+0 -188
@@ -23,9 +23,6 @@ import {
2323 neutralCharacterName,
2424 updateChatMetadata,
2525 system_message_types,
26- updateMessageBlock,
27- closeMessageEditor,
28- substituteParams,
2926} from '../script.js';
3027import { selected_group } from './group-chats.js';
3128import { power_user } from './power-user.js';
@@ -40,7 +37,6 @@ import {
4037 saveBase64AsFile,
4138 extractTextFromOffice,
4239 download,
43- copyText,
4440} from './utils.js';
4541import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
4642import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
@@ -1420,72 +1416,7 @@ export function registerFileConverter(mimeType, converter) {
14201416 converters[mimeType] = converter;
14211417}
14221418
1423-/**
1424- * Helper class for adding reasoning to messages.
1425- * Keeps track of the number of reasoning additions.
1426- */
1427-export class PromptReasoning {
1428- static REASONING_PLACEHOLDER = '\u200B';
1429-
1430- constructor() {
1431- this.counter = 0;
1432- }
1433-
1434- /**
1435- * Checks if the limit of reasoning additions has been reached.
1436- * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise.
1437- */
1438- isLimitReached() {
1439- if (!power_user.reasoning.add_to_prompts) {
1440- return true;
1441- }
1442-
1443- return this.counter >= power_user.reasoning.max_additions;
1444- }
1445-
1446- /**
1447- * Add reasoning to a message according to the power user settings.
1448- * @param {string} content Message content
1449- * @param {string} reasoning Message reasoning
1450- * @returns {string} Message content with reasoning
1451- */
1452- addToMessage(content, reasoning) {
1453- // Disabled or reached limit of additions
1454- if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) {
1455- return content;
1456- }
1457-
1458- // No reasoning provided or a placeholder
1459- if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) {
1460- return content;
1461- }
1462-
1463- // Increment the counter
1464- this.counter++;
1465-
1466- // Substitute macros in variable parts
1467- const prefix = substituteParams(power_user.reasoning.prefix || '');
1468- const separator = substituteParams(power_user.reasoning.separator || '');
1469- const suffix = substituteParams(power_user.reasoning.suffix || '');
1470-
1471- // Combine parts with reasoning and content
1472- return `${prefix}${reasoning}${suffix}${separator}${content}`;
1473- }
1474-}
1475-
14761419jQuery(function () {
1477- /**
1478- * Gets a message from a jQuery element.
1479- * @param {Element} element
1480- * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}
1481- */
1482- const getMessageFromJquery = function (element) {
1483- const messageBlock = $(element).closest('.mes');
1484- const messageId = Number(messageBlock.attr('mesid'));
1485- const message = chat[messageId];
1486- return { messageId: messageId, message, messageBlock };
1487- };
1488-
14891420 $(document).on('click', '.mes_hide', async function () {
14901421 const messageBlock = $(this).closest('.mes');
14911422 const messageId = Number(messageBlock.attr('mesid'));
@@ -1636,125 +1567,6 @@ jQuery(function () {
16361567 $(document).on('click', '.mes_img_enlarge', enlargeMessageImage);
16371568 $(document).on('click', '.mes_img_delete', deleteMessageImage);
16381569
1639- $(document).on('click', '.mes_reasoning_copy', (e) => {
1640- e.stopPropagation();
1641- e.preventDefault();
1642- });
1643-
1644- $(document).on('click', '.mes_reasoning_edit', function (e) {
1645- e.stopPropagation();
1646- e.preventDefault();
1647- const { message, messageBlock } = getMessageFromJquery(this);
1648- if (!message?.extra) {
1649- return;
1650- }
1651-
1652- const reasoning = message?.extra?.reasoning;
1653- const chatElement = document.getElementById('chat');
1654- const textarea = document.createElement('textarea');
1655- const reasoningBlock = messageBlock.find('.mes_reasoning');
1656- textarea.classList.add('reasoning_edit_textarea');
1657- textarea.value = reasoning === PromptReasoning.REASONING_PLACEHOLDER ? '' : reasoning;
1658- $(textarea).insertBefore(reasoningBlock);
1659-
1660- if (!CSS.supports('field-sizing', 'content')) {
1661- const resetHeight = function () {
1662- const scrollTop = chatElement.scrollTop;
1663- textarea.style.height = '0px';
1664- textarea.style.height = `${textarea.scrollHeight}px`;
1665- chatElement.scrollTop = scrollTop;
1666- };
1667-
1668- textarea.addEventListener('input', resetHeight);
1669- resetHeight();
1670- }
1671-
1672- textarea.focus();
1673- textarea.setSelectionRange(textarea.value.length, textarea.value.length);
1674-
1675- const textareaRect = textarea.getBoundingClientRect();
1676- const chatRect = chatElement.getBoundingClientRect();
1677-
1678- // Scroll if textarea bottom is below visible area
1679- if (textareaRect.bottom > chatRect.bottom) {
1680- const scrollOffset = textareaRect.bottom - chatRect.bottom;
1681- chatElement.scrollTop += scrollOffset;
1682- }
1683- });
1684-
1685- $(document).on('click', '.mes_reasoning_edit_done', async function (e) {
1686- e.stopPropagation();
1687- e.preventDefault();
1688- const { message, messageId, messageBlock } = getMessageFromJquery(this);
1689- if (!message?.extra) {
1690- return;
1691- }
1692-
1693- const textarea = messageBlock.find('.reasoning_edit_textarea');
1694- const reasoning = String(textarea.val());
1695- message.extra.reasoning = reasoning;
1696- await saveChatConditional();
1697- updateMessageBlock(messageId, message);
1698- textarea.remove();
1699- });
1700-
1701- $(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
1702- e.stopPropagation();
1703- e.preventDefault();
1704-
1705- const { messageBlock } = getMessageFromJquery(this);
1706- const textarea = messageBlock.find('.reasoning_edit_textarea');
1707- textarea.remove();
1708- });
1709-
1710- $(document).on('click', '.mes_edit_add_reasoning', async function () {
1711- const { message, messageId } = getMessageFromJquery(this);
1712- if (!message?.extra) {
1713- return;
1714- }
1715-
1716- if (message.extra.reasoning) {
1717- toastr.info(t`Reasoning already exists.`, t`Edit Message`);
1718- return;
1719- }
1720-
1721- message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
1722- await saveChatConditional();
1723- closeMessageEditor();
1724- updateMessageBlock(messageId, message);
1725- });
1726-
1727- $(document).on('click', '.mes_reasoning_delete', async function (e) {
1728- e.stopPropagation();
1729- e.preventDefault();
1730-
1731- const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`);
1732-
1733- if (!confirm) {
1734- return;
1735- }
1736-
1737- const { message, messageId } = getMessageFromJquery(this);
1738- if (!message?.extra) {
1739- return;
1740- }
1741- message.extra.reasoning = '';
1742- await saveChatConditional();
1743- updateMessageBlock(messageId, message);
1744- });
1745-
1746- $(document).on('pointerup', '.mes_reasoning_copy', async function () {
1747- const { message } = getMessageFromJquery(this);
1748- const reasoning = message?.extra?.reasoning;
1749-
1750- if (!reasoning) {
1751- return;
1752- }
1753-
1754- await copyText(reasoning);
1755- toastr.info(t`Copied!`, '', { timeOut: 2000 });
1756- });
1757-
17581570 $('#file_form_input').on('change', async () => {
17591571 const fileInput = document.getElementById('file_form_input');
17601572 if (!(fileInput instanceof HTMLInputElement)) return;
public/scripts/power-user.js+0 -33
@@ -1621,7 +1621,6 @@ async function loadPowerUserSettings(settings, data) {
16211621 loadMovingUIState();
16221622 loadCharListState();
16231623 toggleMDHotkeyIconDisplay();
1624- loadReasoningSettings();
16251624}
16261625
16271626function toggleMDHotkeyIconDisplay() {
@@ -1638,38 +1637,6 @@ function loadCharListState() {
16381637 document.body.classList.toggle('charListGrid', power_user.charListGrid);
16391638}
16401639
1641-function loadReasoningSettings() {
1642- $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
1643- $('#reasoning_add_to_prompts').on('change', function () {
1644- power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
1645- saveSettingsDebounced();
1646- });
1647-
1648- $('#reasoning_prefix').val(power_user.reasoning.prefix);
1649- $('#reasoning_prefix').on('input', function () {
1650- power_user.reasoning.prefix = String($(this).val());
1651- saveSettingsDebounced();
1652- });
1653-
1654- $('#reasoning_suffix').val(power_user.reasoning.suffix);
1655- $('#reasoning_suffix').on('input', function () {
1656- power_user.reasoning.suffix = String($(this).val());
1657- saveSettingsDebounced();
1658- });
1659-
1660- $('#reasoning_separator').val(power_user.reasoning.separator);
1661- $('#reasoning_separator').on('input', function () {
1662- power_user.reasoning.separator = String($(this).val());
1663- saveSettingsDebounced();
1664- });
1665-
1666- $('#reasoning_max_additions').val(power_user.reasoning.max_additions);
1667- $('#reasoning_max_additions').on('input', function () {
1668- power_user.reasoning.max_additions = Number($(this).val());
1669- saveSettingsDebounced();
1670- });
1671-}
1672-
16731640function loadMovingUIState() {
16741641 if (!isMobile()
16751642 && power_user.movingUIState
public/scripts/reasoning.js+228 -0
@@ -0,0 +1,228 @@
1+import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
2+import { t } from './i18n.js';
3+import { Popup } from './popup.js';
4+import { power_user } from './power-user.js';
5+import { copyText } from './utils.js';
6+
7+/**
8+ * Gets a message from a jQuery element.
9+ * @param {Element} element
10+ * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}
11+ */
12+function getMessageFromJquery(element) {
13+ const messageBlock = $(element).closest('.mes');
14+ const messageId = Number(messageBlock.attr('mesid'));
15+ const message = chat[messageId];
16+ return { messageId: messageId, message, messageBlock };
17+}
18+
19+/**
20+ * Helper class for adding reasoning to messages.
21+ * Keeps track of the number of reasoning additions.
22+ */
23+export class PromptReasoning {
24+ static REASONING_PLACEHOLDER = '\u200B';
25+
26+ constructor() {
27+ this.counter = 0;
28+ }
29+
30+ /**
31+ * Checks if the limit of reasoning additions has been reached.
32+ * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise.
33+ */
34+ isLimitReached() {
35+ if (!power_user.reasoning.add_to_prompts) {
36+ return true;
37+ }
38+
39+ return this.counter >= power_user.reasoning.max_additions;
40+ }
41+
42+ /**
43+ * Add reasoning to a message according to the power user settings.
44+ * @param {string} content Message content
45+ * @param {string} reasoning Message reasoning
46+ * @returns {string} Message content with reasoning
47+ */
48+ addToMessage(content, reasoning) {
49+ // Disabled or reached limit of additions
50+ if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) {
51+ return content;
52+ }
53+
54+ // No reasoning provided or a placeholder
55+ if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) {
56+ return content;
57+ }
58+
59+ // Increment the counter
60+ this.counter++;
61+
62+ // Substitute macros in variable parts
63+ const prefix = substituteParams(power_user.reasoning.prefix || '');
64+ const separator = substituteParams(power_user.reasoning.separator || '');
65+ const suffix = substituteParams(power_user.reasoning.suffix || '');
66+
67+ // Combine parts with reasoning and content
68+ return `${prefix}${reasoning}${suffix}${separator}${content}`;
69+ }
70+}
71+
72+function loadReasoningSettings() {
73+ $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
74+ $('#reasoning_add_to_prompts').on('change', function () {
75+ power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
76+ saveSettingsDebounced();
77+ });
78+
79+ $('#reasoning_prefix').val(power_user.reasoning.prefix);
80+ $('#reasoning_prefix').on('input', function () {
81+ power_user.reasoning.prefix = String($(this).val());
82+ saveSettingsDebounced();
83+ });
84+
85+ $('#reasoning_suffix').val(power_user.reasoning.suffix);
86+ $('#reasoning_suffix').on('input', function () {
87+ power_user.reasoning.suffix = String($(this).val());
88+ saveSettingsDebounced();
89+ });
90+
91+ $('#reasoning_separator').val(power_user.reasoning.separator);
92+ $('#reasoning_separator').on('input', function () {
93+ power_user.reasoning.separator = String($(this).val());
94+ saveSettingsDebounced();
95+ });
96+
97+ $('#reasoning_max_additions').val(power_user.reasoning.max_additions);
98+ $('#reasoning_max_additions').on('input', function () {
99+ power_user.reasoning.max_additions = Number($(this).val());
100+ saveSettingsDebounced();
101+ });
102+}
103+
104+function setReasoningEventHandlers(){
105+ $(document).on('click', '.mes_reasoning_copy', (e) => {
106+ e.stopPropagation();
107+ e.preventDefault();
108+ });
109+
110+ $(document).on('click', '.mes_reasoning_edit', function (e) {
111+ e.stopPropagation();
112+ e.preventDefault();
113+ const { message, messageBlock } = getMessageFromJquery(this);
114+ if (!message?.extra) {
115+ return;
116+ }
117+
118+ const reasoning = message?.extra?.reasoning;
119+ const chatElement = document.getElementById('chat');
120+ const textarea = document.createElement('textarea');
121+ const reasoningBlock = messageBlock.find('.mes_reasoning');
122+ textarea.classList.add('reasoning_edit_textarea');
123+ textarea.value = reasoning === PromptReasoning.REASONING_PLACEHOLDER ? '' : reasoning;
124+ $(textarea).insertBefore(reasoningBlock);
125+
126+ if (!CSS.supports('field-sizing', 'content')) {
127+ const resetHeight = function () {
128+ const scrollTop = chatElement.scrollTop;
129+ textarea.style.height = '0px';
130+ textarea.style.height = `${textarea.scrollHeight}px`;
131+ chatElement.scrollTop = scrollTop;
132+ };
133+
134+ textarea.addEventListener('input', resetHeight);
135+ resetHeight();
136+ }
137+
138+ textarea.focus();
139+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
140+
141+ const textareaRect = textarea.getBoundingClientRect();
142+ const chatRect = chatElement.getBoundingClientRect();
143+
144+ // Scroll if textarea bottom is below visible area
145+ if (textareaRect.bottom > chatRect.bottom) {
146+ const scrollOffset = textareaRect.bottom - chatRect.bottom;
147+ chatElement.scrollTop += scrollOffset;
148+ }
149+ });
150+
151+ $(document).on('click', '.mes_reasoning_edit_done', async function (e) {
152+ e.stopPropagation();
153+ e.preventDefault();
154+ const { message, messageId, messageBlock } = getMessageFromJquery(this);
155+ if (!message?.extra) {
156+ return;
157+ }
158+
159+ const textarea = messageBlock.find('.reasoning_edit_textarea');
160+ const reasoning = String(textarea.val());
161+ message.extra.reasoning = reasoning;
162+ await saveChatConditional();
163+ updateMessageBlock(messageId, message);
164+ textarea.remove();
165+ });
166+
167+ $(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
168+ e.stopPropagation();
169+ e.preventDefault();
170+
171+ const { messageBlock } = getMessageFromJquery(this);
172+ const textarea = messageBlock.find('.reasoning_edit_textarea');
173+ textarea.remove();
174+ });
175+
176+ $(document).on('click', '.mes_edit_add_reasoning', async function () {
177+ const { message, messageId } = getMessageFromJquery(this);
178+ if (!message?.extra) {
179+ return;
180+ }
181+
182+ if (message.extra.reasoning) {
183+ toastr.info(t`Reasoning already exists.`, t`Edit Message`);
184+ return;
185+ }
186+
187+ message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
188+ await saveChatConditional();
189+ closeMessageEditor();
190+ updateMessageBlock(messageId, message);
191+ });
192+
193+ $(document).on('click', '.mes_reasoning_delete', async function (e) {
194+ e.stopPropagation();
195+ e.preventDefault();
196+
197+ const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`);
198+
199+ if (!confirm) {
200+ return;
201+ }
202+
203+ const { message, messageId } = getMessageFromJquery(this);
204+ if (!message?.extra) {
205+ return;
206+ }
207+ message.extra.reasoning = '';
208+ await saveChatConditional();
209+ updateMessageBlock(messageId, message);
210+ });
211+
212+ $(document).on('pointerup', '.mes_reasoning_copy', async function () {
213+ const { message } = getMessageFromJquery(this);
214+ const reasoning = message?.extra?.reasoning;
215+
216+ if (!reasoning) {
217+ return;
218+ }
219+
220+ await copyText(reasoning);
221+ toastr.info(t`Copied!`, '', { timeOut: 2000 });
222+ });
223+}
224+
225+export function initReasoning() {
226+ loadReasoningSettings();
227+ setReasoningEventHandlers();
228+}