Merge pull request #3700 from SillyTavern/reasoning-template Reasoning templates

03d590415b9f600082965b1f52ab39bacfa3f5b4

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

Signed
13 files changed, +273 -22Ignore whitespace
default/content/index.json+8 -0
@@ -786,5 +786,13 @@
786 {786 {
787 "filename": "presets/context/DeepSeek-V2.5.json",787 "filename": "presets/context/DeepSeek-V2.5.json",
788 "type": "context"788 "type": "context"
789 },
790 {
791 "filename": "presets/reasoning/DeepSeek.json",
792 "type": "reasoning"
793 },
794 {
795 "filename": "presets/reasoning/Blank.json",
796 "type": "reasoning"
789 }797 }
790]798]
default/content/presets/reasoning/Blank.json+6 -0
@@ -0,0 +1,6 @@
1{
2 "name": "Blank",
3 "prefix": "",
4 "suffix": "",
5 "separator": ""
6}
default/content/presets/reasoning/DeepSeek.json+6 -0
@@ -0,0 +1,6 @@
1{
2 "name": "DeepSeek",
3 "prefix": "<think>\n",
4 "suffix": "\n</think>",
5 "separator": "\n\n"
6}
public/index.html+13 -0
@@ -3917,6 +3917,19 @@
3917 <summary data-i18n="Reasoning Formatting">3917 <summary data-i18n="Reasoning Formatting">
3918 Reasoning Formatting3918 Reasoning Formatting
3919 </summary>3919 </summary>
3920 <div class="flex-container" title="Select your current Reasoning Template" data-i18n="[title]Select your current Reasoning Template">
3921 <select id="reasoning_select" data-preset-manager-for="reasoning" class="flex1 text_pole"></select>
3922 <div class="flex-container margin0 justifyCenter gap3px">
3923 <input type="file" hidden data-preset-manager-file="reasoning" accept=".json, .settings">
3924 <i data-preset-manager-update="reasoning" class="menu_button fa-solid fa-save" title="Update current template" data-i18n="[title]Update current template"></i>
3925 <i data-preset-manager-rename="reasoning" class="menu_button fa-pencil fa-solid" title="Rename current template" data-i18n="[title]Rename current template"></i>
3926 <i data-preset-manager-new="reasoning" class="menu_button fa-solid fa-file-circle-plus" title="Save template as" data-i18n="[title]Save template as"></i>
3927 <i data-preset-manager-import="reasoning" class="displayNone menu_button fa-solid fa-file-import" title="Import template" data-i18n="[title]Import template"></i>
3928 <i data-preset-manager-export="reasoning" class="displayNone menu_button fa-solid fa-file-export" title="Export template" data-i18n="[title]Export template"></i>
3929 <i data-preset-manager-restore="reasoning" class="menu_button fa-solid fa-recycle" title="Restore current template" data-i18n="[title]Restore current template"></i>
3930 <i data-preset-manager-delete="reasoning" class="menu_button fa-solid fa-trash-can" title="Delete template" data-i18n="[title]Delete template"></i>
3931 </div>
3932 </div>
3920 <div class="flex-container">3933 <div class="flex-container">
3921 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">3934 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
3922 <small data-i18n="Prefix">Prefix</small>3935 <small data-i18n="Prefix">Prefix</small>
public/scripts/extensions/connection-manager/index.js+4 -0
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
39 'proxy',39 'proxy',
40 'stop-strings',40 'stop-strings',
41 'start-reply-with',41 'start-reply-with',
42 'reasoning-template',
42];43];
4344
44const TC_COMMANDS = [45const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
54 'tokenizer',55 'tokenizer',
55 'stop-strings',56 'stop-strings',
56 'start-reply-with',57 'start-reply-with',
58 'reasoning-template',
57];59];
5860
59const FANCY_NAMES = {61const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
70 'tokenizer': 'Tokenizer',72 'tokenizer': 'Tokenizer',
71 'stop-strings': 'Custom Stopping Strings',73 'stop-strings': 'Custom Stopping Strings',
72 'start-reply-with': 'Start Reply With',74 'start-reply-with': 'Start Reply With',
75 'reasoning-template': 'Reasoning Template',
73};76};
7477
75/**78/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
154 * @property {string} [tokenizer] Tokenizer157 * @property {string} [tokenizer] Tokenizer
155 * @property {string} [stop-strings] Custom Stopping Strings158 * @property {string} [stop-strings] Custom Stopping Strings
156 * @property {string} [start-reply-with] Start Reply With159 * @property {string} [start-reply-with] Start Reply With
160 * @property {string} [reasoning-template] Reasoning Template
157 * @property {string[]} [exclude] Commands to exclude161 * @property {string[]} [exclude] Commands to exclude
158 */162 */
159163
public/scripts/power-user.js+3 -0
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
55import { loadSystemPrompts } from './sysprompt.js';55import { loadSystemPrompts } from './sysprompt.js';
56import { fuzzySearchCategories } from './filters.js';56import { fuzzySearchCategories } from './filters.js';
57import { accountStorage } from './util/AccountStorage.js';57import { accountStorage } from './util/AccountStorage.js';
58import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
5859
59export {60export {
60 loadPowerUserSettings,61 loadPowerUserSettings,
@@ -257,6 +258,7 @@ let power_user = {
257 },258 },
258259
259 reasoning: {260 reasoning: {
261 name: DEFAULT_REASONING_TEMPLATE,
260 auto_parse: false,262 auto_parse: false,
261 add_to_prompts: false,263 add_to_prompts: false,
262 auto_expand: false,264 auto_expand: false,
@@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
1624 await loadInstructMode(data);1626 await loadInstructMode(data);
1625 await loadContextSettings();1627 await loadContextSettings();
1626 await loadSystemPrompts(data);1628 await loadSystemPrompts(data);
1629 await loadReasoningTemplates(data);
1627 loadMaxContextUnlocked();1630 loadMaxContextUnlocked();
1628 switchWaifuMode();1631 switchWaifuMode();
1629 switchSpoilerMode();1632 switchSpoilerMode();
public/scripts/preset-manager.js+44 -2
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
21import { instruct_presets } from './instruct-mode.js';21import { instruct_presets } from './instruct-mode.js';
22import { kai_settings } from './kai-settings.js';22import { kai_settings } from './kai-settings.js';
23import { convertNovelPreset } from './nai-settings.js';23import { convertNovelPreset } from './nai-settings.js';
24import { openai_settings, openai_setting_names, oai_settings } from './openai.js';24import { openai_settings, openai_setting_names } from './openai.js';
25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';25import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
26import { context_presets, getContextSettings, power_user } from './power-user.js';26import { context_presets, getContextSettings, power_user } from './power-user.js';
27import { SlashCommand } from './slash-commands/SlashCommand.js';27import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
38} from './textgen-settings.js';38} from './textgen-settings.js';
39import { download, parseJsonFile, waitUntilCondition } from './utils.js';39import { download, parseJsonFile, waitUntilCondition } from './utils.js';
40import { t } from './i18n.js';40import { t } from './i18n.js';
41import { reasoning_templates } from './reasoning.js';
4142
42const presetManagers = {};43const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168 },169 },
169 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170 },171 },
172 'reasoning': {
173 name: 'Reasoning Formatting',
174 getData: () => {
175 const manager = getPresetManager('reasoning');
176 const name = manager.getSelectedPresetName();
177 return manager.getPresetSettings(name);
178 },
179 setData: (data) => {
180 const manager = getPresetManager('reasoning');
181 const name = data.name;
182 return manager.savePreset(name, data);
183 },
184 isValid: (data) => PresetManager.isPossiblyReasoningData(data),
185 },
171 };186 };
172187
173 static isPossiblyInstructData(data) {188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191 }206 }
192207
208 static isPossiblyReasoningData(data) {
209 const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
210 return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
211 }
212
193 /**213 /**
194 * Imports master settings from JSON data.214 * Imports master settings from JSON data.
195 * @param {object} data Data to import215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228 }248 }
229249
250 // 5. Reasoning Template
251 if (this.isPossiblyReasoningData(data)) {
252 toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
253 return await getPresetManager('reasoning').savePreset(data.name, data);
254 }
255
230 const validSections = [];256 const validSections = [];
231 for (const [key, section] of Object.entries(this.masterSections)) {257 for (const [key, section] of Object.entries(this.masterSections)) {
232 if (key in data && section.isValid(data[key])) {258 if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
478 presets = system_prompts;504 presets = system_prompts;
479 preset_names = system_prompts.map(x => x.name);505 preset_names = system_prompts.map(x => x.name);
480 break;506 break;
507 case 'reasoning':
508 presets = reasoning_templates;
509 preset_names = reasoning_templates.map(x => x.name);
510 break;
481 default:511 default:
482 console.warn(`Unknown API ID ${api}`);512 console.warn(`Unknown API ID ${api}`);
483 }513 }
@@ -490,7 +520,7 @@ class PresetManager {
490 }520 }
491521
492 isAdvancedFormatting() {522 isAdvancedFormatting() {
493 return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';523 return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494 }524 }
495525
496 updateList(name, preset) {526 updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
553 sysprompt_preset['name'] = name || power_user.sysprompt.preset;583 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554 return sysprompt_preset;584 return sysprompt_preset;
555 }585 }
586 case 'reasoning': {
587 const reasoning_preset = structuredClone(power_user.reasoning);
588 reasoning_preset['name'] = name || power_user.reasoning.preset;
589 return reasoning_preset;
590 }
556 default:591 default:
557 console.warn(`Unknown API ID ${apiId}`);592 console.warn(`Unknown API ID ${apiId}`);
558 return {};593 return {};
@@ -599,6 +634,13 @@ class PresetManager {
599 'include_reasoning',634 'include_reasoning',
600 'global_banned_tokens',635 'global_banned_tokens',
601 'send_banned_tokens',636 'send_banned_tokens',
637
638 // Reasoning exclusions
639 'auto_parse',
640 'add_to_prompts',
641 'auto_expand',
642 'show_hidden',
643 'max_additions',
602 ];644 ];
603 const settings = Object.assign({}, getSettingsByApiId(this.apiId));645 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
604646
public/scripts/reasoning.js+179 -19
@@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
7import { MacrosParser } from './macros.js';7import { MacrosParser } from './macros.js';
8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
9import { Popup } from './popup.js';9import { Popup } from './popup.js';
10import { power_user } from './power-user.js';10import { performFuzzySearch, power_user } from './power-user.js';
11import { getPresetManager } from './preset-manager.js';
11import { SlashCommand } from './slash-commands/SlashCommand.js';12import { SlashCommand } from './slash-commands/SlashCommand.js';
12import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';13import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
13import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';14import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
14import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';15import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
15import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';16import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
16import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';17import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
17import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';18import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
19
20/**
21 * @typedef {object} ReasoningTemplate
22 * @property {string} name - The name of the template
23 * @property {string} prefix - Reasoning prefix
24 * @property {string} suffix - Reasoning suffix
25 * @property {string} separator - Reasoning separator
26 */
27
28/**
29 * @type {ReasoningTemplate[]} List of reasoning templates
30 */
31export const reasoning_templates = [];
32
33export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
34
35/**
36 * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
37 * @readonly
38 */
39const UI = {
40 $select: $('#reasoning_select'),
41 $suffix: $('#reasoning_suffix'),
42 $prefix: $('#reasoning_prefix'),
43 $separator: $('#reasoning_separator'),
44 $autoParse: $('#reasoning_auto_parse'),
45 $autoExpand: $('#reasoning_auto_expand'),
46 $showHidden: $('#reasoning_show_hidden'),
47 $addToPrompts: $('#reasoning_add_to_prompts'),
48 $maxAdditions: $('#reasoning_max_additions'),
49};
1850
19/**51/**
20 * Enum representing the type of the reasoning for a message (where it came from)52 * Enum representing the type of the reasoning for a message (where it came from)
@@ -61,7 +93,7 @@ export function extractReasoningFromData(data, {
61 mainApi = null,93 mainApi = null,
62 ignoreShowThoughts = false,94 ignoreShowThoughts = false,
63 textGenType = null,95 textGenType = null,
64 chatCompletionSource = null96 chatCompletionSource = null,
65} = {}) {97} = {}) {
66 switch (mainApi ?? main_api) {98 switch (mainApi ?? main_api) {
67 case 'textgenerationwebui':99 case 'textgenerationwebui':
@@ -669,57 +701,102 @@ export class PromptReasoning {
669}701}
670702
671function loadReasoningSettings() {703function loadReasoningSettings() {
672 $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);704 UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
673 $('#reasoning_add_to_prompts').on('change', function () {705 UI.$addToPrompts.on('change', function () {
674 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');706 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
675 saveSettingsDebounced();707 saveSettingsDebounced();
676 });708 });
677709
678 $('#reasoning_prefix').val(power_user.reasoning.prefix);710 UI.$prefix.val(power_user.reasoning.prefix);
679 $('#reasoning_prefix').on('input', function () {711 UI.$prefix.on('input', function () {
680 power_user.reasoning.prefix = String($(this).val());712 power_user.reasoning.prefix = String($(this).val());
681 saveSettingsDebounced();713 saveSettingsDebounced();
682 });714 });
683715
684 $('#reasoning_suffix').val(power_user.reasoning.suffix);716 UI.$suffix.val(power_user.reasoning.suffix);
685 $('#reasoning_suffix').on('input', function () {717 UI.$suffix.on('input', function () {
686 power_user.reasoning.suffix = String($(this).val());718 power_user.reasoning.suffix = String($(this).val());
687 saveSettingsDebounced();719 saveSettingsDebounced();
688 });720 });
689721
690 $('#reasoning_separator').val(power_user.reasoning.separator);722 UI.$separator.val(power_user.reasoning.separator);
691 $('#reasoning_separator').on('input', function () {723 UI.$separator.on('input', function () {
692 power_user.reasoning.separator = String($(this).val());724 power_user.reasoning.separator = String($(this).val());
693 saveSettingsDebounced();725 saveSettingsDebounced();
694 });726 });
695727
696 $('#reasoning_max_additions').val(power_user.reasoning.max_additions);728 UI.$maxAdditions.val(power_user.reasoning.max_additions);
697 $('#reasoning_max_additions').on('input', function () {729 UI.$maxAdditions.on('input', function () {
698 power_user.reasoning.max_additions = Number($(this).val());730 power_user.reasoning.max_additions = Number($(this).val());
699 saveSettingsDebounced();731 saveSettingsDebounced();
700 });732 });
701733
702 $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);734 UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
703 $('#reasoning_auto_parse').on('change', function () {735 UI.$autoParse.on('change', function () {
704 power_user.reasoning.auto_parse = !!$(this).prop('checked');736 power_user.reasoning.auto_parse = !!$(this).prop('checked');
705 saveSettingsDebounced();737 saveSettingsDebounced();
706 });738 });
707739
708 $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);740 UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
709 $('#reasoning_auto_expand').on('change', function () {741 UI.$autoExpand.on('change', function () {
710 power_user.reasoning.auto_expand = !!$(this).prop('checked');742 power_user.reasoning.auto_expand = !!$(this).prop('checked');
711 toggleReasoningAutoExpand();743 toggleReasoningAutoExpand();
712 saveSettingsDebounced();744 saveSettingsDebounced();
713 });745 });
714 toggleReasoningAutoExpand();746 toggleReasoningAutoExpand();
715747
716 $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);748 UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
717 $('#reasoning_show_hidden').on('change', function () {749 UI.$showHidden.on('change', function () {
718 power_user.reasoning.show_hidden = !!$(this).prop('checked');750 power_user.reasoning.show_hidden = !!$(this).prop('checked');
719 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);751 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
720 saveSettingsDebounced();752 saveSettingsDebounced();
721 });753 });
722 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);754 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
755
756 UI.$select.on('change', async function () {
757 const name = String($(this).val());
758 const template = reasoning_templates.find(p => p.name === name);
759 if (!template) {
760 return;
761 }
762
763 UI.$prefix.val(template.prefix);
764 UI.$suffix.val(template.suffix);
765 UI.$separator.val(template.separator);
766
767 power_user.reasoning.name = name;
768 power_user.reasoning.prefix = template.prefix;
769 power_user.reasoning.suffix = template.suffix;
770 power_user.reasoning.separator = template.separator;
771
772 saveSettingsDebounced();
773 });
774}
775
776function selectReasoningTemplateCallback(args, name) {
777 if (!name) {
778 return power_user.reasoning.name ?? '';
779 }
780
781 const quiet = isTrueBoolean(args?.quiet);
782 const templateNames = reasoning_templates.map(preset => preset.name);
783 let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
784
785 if (!foundName) {
786 const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
787
788 if (result.length === 0) {
789 !quiet && toastr.warning(`Reasoning template "${name}" not found`);
790 return '';
791 }
792
793 foundName = result[0].item;
794 }
795
796 UI.$select.val(foundName).trigger('change');
797 !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
798 return foundName;
799
723}800}
724801
725function registerReasoningSlashCommands() {802function registerReasoningSlashCommands() {
@@ -853,6 +930,42 @@ function registerReasoningSlashCommands() {
853 : parsedReasoning.reasoning;930 : parsedReasoning.reasoning;
854 },931 },
855 }));932 }));
933 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
934 name: 'reasoning-template',
935 aliases: ['reasoning-formatting', 'reasoning-preset'],
936 callback: selectReasoningTemplateCallback,
937 returns: 'template name',
938 namedArgumentList: [
939 SlashCommandNamedArgument.fromProps({
940 name: 'quiet',
941 description: 'Suppress the toast message on template change',
942 typeList: [ARGUMENT_TYPE.BOOLEAN],
943 defaultValue: 'false',
944 enumList: commonEnumProviders.boolean('trueFalse')(),
945 }),
946 ],
947 unnamedArgumentList: [
948 SlashCommandArgument.fromProps({
949 description: 'reasoning template name',
950 typeList: [ARGUMENT_TYPE.STRING],
951 enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
952 }),
953 ],
954 helpString: `
955 <div>
956 Selects a reasoning template by name, using fuzzy search to find the closest match.
957 Gets the current template if no name is provided.
958 </div>
959 <div>
960 <strong>Example:</strong>
961 <ul>
962 <li>
963 <pre><code class="language-stscript">/reasoning-template DeepSeek</code></pre>
964 </li>
965 </ul>
966 </div>
967 `,
968 }));
856}969}
857970
858function registerReasoningMacros() {971function registerReasoningMacros() {
@@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() {
1212 }1325 }
1213}1326}
12141327
1328/**
1329 * Loads reasoning templates from the settings data.
1330 * @param {object} data Settings data
1331 * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
1332 * @returns {Promise<void>}
1333 */
1334export async function loadReasoningTemplates(data) {
1335 if (data.reasoning !== undefined) {
1336 reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
1337 }
1338
1339 for (const template of reasoning_templates) {
1340 $('<option>').val(template.name).text(template.name).appendTo(UI.$select);
1341 }
1342
1343 // No template name, need to migrate
1344 if (power_user.reasoning.name === undefined) {
1345 const defaultTemplate = reasoning_templates.find(p => p.name === DEFAULT_REASONING_TEMPLATE);
1346 if (defaultTemplate) {
1347 // If the reasoning settings were modified - migrate them to a custom template
1348 if (power_user.reasoning.prefix !== defaultTemplate.prefix || power_user.reasoning.suffix !== defaultTemplate.suffix || power_user.reasoning.separator !== defaultTemplate.separator) {
1349 /** @type {ReasoningTemplate} */
1350 const data = {
1351 name: '[Migrated] Custom',
1352 prefix: power_user.reasoning.prefix,
1353 suffix: power_user.reasoning.suffix,
1354 separator: power_user.reasoning.separator,
1355 };
1356 await getPresetManager('reasoning')?.savePreset(data.name, data);
1357 power_user.reasoning.name = data.name;
1358 } else {
1359 power_user.reasoning.name = defaultTemplate.name;
1360 }
1361 } else {
1362 // Template not found (deleted or content check skipped - leave blank)
1363 power_user.reasoning.name = '';
1364 }
1365
1366 saveSettingsDebounced();
1367 }
1368
1369 UI.$select.val(power_user.reasoning.name);
1370}
1371
1372/**
1373 * Initializes reasoning settings and event handlers.
1374 */
1215export function initReasoning() {1375export function initReasoning() {
1216 loadReasoningSettings();1376 loadReasoningSettings();
1217 setReasoningEventHandlers();1377 setReasoningEventHandlers();
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
43 vectors: 'vectors',43 vectors: 'vectors',
44 backups: 'backups',44 backups: 'backups',
45 sysprompt: 'sysprompt',45 sysprompt: 'sysprompt',
46 reasoning: 'reasoning',
46});47});
4748
48/**49/**
src/endpoints/content-manager.js+4 -1
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
48 MOVING_UI: 'moving_ui',48 MOVING_UI: 'moving_ui',
49 QUICK_REPLIES: 'quick_replies',49 QUICK_REPLIES: 'quick_replies',
50 SYSPROMPT: 'sysprompt',50 SYSPROMPT: 'sysprompt',
51 REASONING: 'reasoning',
51};52};
5253
53/**54/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
61 const presets = [];62 const presets = [];
6263
63 for (const contentItem of contentIndex) {64 for (const contentItem of contentIndex) {
64 if (contentItem.type.endsWith('_preset') || contentItem.type === 'instruct' || contentItem.type === 'context' || contentItem.type === 'sysprompt') {65 if (contentItem.type.endsWith('_preset') || ['instruct', 'context', 'sysprompt', 'reasoning'].includes(contentItem.type)) {
65 contentItem.name = path.parse(contentItem.filename).name;66 contentItem.name = path.parse(contentItem.filename).name;
66 contentItem.folder = getTargetByType(contentItem.type, directories);67 contentItem.folder = getTargetByType(contentItem.type, directories);
67 presets.push(contentItem);68 presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
299 return directories.quickreplies;300 return directories.quickreplies;
300 case CONTENT_TYPES.SYSPROMPT:301 case CONTENT_TYPES.SYSPROMPT:
301 return directories.sysprompt;302 return directories.sysprompt;
303 case CONTENT_TYPES.REASONING:
304 return directories.reasoning;
302 default:305 default:
303 return null;306 return null;
304 }307 }
src/endpoints/presets.js+2 -0
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
30 return { folder: directories.context, extension: '.json' };30 return { folder: directories.context, extension: '.json' };
31 case 'sysprompt':31 case 'sysprompt':
32 return { folder: directories.sysprompt, extension: '.json' };32 return { folder: directories.sysprompt, extension: '.json' };
33 case 'reasoning':
34 return { folder: directories.reasoning, extension: '.json' };
33 default:35 default:
34 return { folder: null, extension: null };36 return { folder: null, extension: null };
35 }37 }
src/endpoints/settings.js+2 -0
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
255 const context = readAndParseFromDirectory(request.user.directories.context);255 const context = readAndParseFromDirectory(request.user.directories.context);
256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
257 const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
257258
258 response.send({259 response.send({
259 settings,260 settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
272 instruct,273 instruct,
273 context,274 context,
274 sysprompt,275 sysprompt,
276 reasoning,
275 enable_extensions: ENABLE_EXTENSIONS,277 enable_extensions: ENABLE_EXTENSIONS,
276 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,278 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
277 enable_accounts: ENABLE_ACCOUNTS,279 enable_accounts: ENABLE_ACCOUNTS,
src/users.js+1 -0
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
95 * @property {string} vectors - The directory where the vectors are stored95 * @property {string} vectors - The directory where the vectors are stored
96 * @property {string} backups - The directory where the backups are stored96 * @property {string} backups - The directory where the backups are stored
97 * @property {string} sysprompt - The directory where the system prompt data is stored97 * @property {string} sysprompt - The directory where the system prompt data is stored
98 * @property {string} reasoning - The directory where the reasoning templates are stored
98 */99 */
99100
100/**101/**