Reasoning template

62342b35e27253512352f8be5142dbb0c7063d47

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

11 files changed, +234 -20Ignore whitespace
default/content/index.json+4 -0
@@ -786,5 +786,9 @@
786786 {
787787 "filename": "presets/context/DeepSeek-V2.5.json",
788788 "type": "context"
789+ },
790+ {
791+ "filename": "presets/reasoning/DeepSeek R1.json",
792+ "type": "reasoning"
789793 }
790794]
default/content/presets/reasoning/DeepSeek R1.json+6 -0
@@ -0,0 +1,6 @@
1+{
2+ "name": "DeepSeek R1",
3+ "prefix": "<think>\n",
4+ "suffix": "\n</think>",
5+ "separator": "\n\n"
6+}
public/index.html+13 -0
@@ -3917,6 +3917,19 @@
39173917 <summary data-i18n="Reasoning Formatting">
39183918 Reasoning Formatting
39193919 </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>
39203933 <div class="flex-container">
39213934 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
39223935 <small data-i18n="Prefix">Prefix</small>
public/scripts/power-user.js+3 -0
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
5555import { loadSystemPrompts } from './sysprompt.js';
5656import { fuzzySearchCategories } from './filters.js';
5757import { accountStorage } from './util/AccountStorage.js';
58+import { loadReasoningTemplates } from './reasoning.js';
5859
5960export {
6061 loadPowerUserSettings,
@@ -255,6 +256,7 @@ let power_user = {
255256 },
256257
257258 reasoning: {
259+ name: 'DeepSeek R1',
258260 auto_parse: false,
259261 add_to_prompts: false,
260262 auto_expand: false,
@@ -1622,6 +1624,7 @@ async function loadPowerUserSettings(settings, data) {
16221624 await loadInstructMode(data);
16231625 await loadContextSettings();
16241626 await loadSystemPrompts(data);
1627+ await loadReasoningTemplates(data);
16251628 loadMaxContextUnlocked();
16261629 switchWaifuMode();
16271630 switchSpoilerMode();
public/scripts/preset-manager.js+44 -2
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
2121import { instruct_presets } from './instruct-mode.js';
2222import { kai_settings } from './kai-settings.js';
2323import { convertNovelPreset } from './nai-settings.js';
2424import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
2525import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
2626import { context_presets, getContextSettings, power_user } from './power-user.js';
2727import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
3838} from './textgen-settings.js';
3939import { download, parseJsonFile, waitUntilCondition } from './utils.js';
4040import { t } from './i18n.js';
41+import { reasoning_templates } from './reasoning.js';
4142
4243const presetManagers = {};
4344
@@ -168,6 +169,20 @@ class PresetManager {
168169 },
169170 isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
170171 },
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+ },
171186 };
172187
173188 static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
190205 return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
191206 }
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+
193213 /**
194214 * Imports master settings from JSON data.
195215 * @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
227247 return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
228248 }
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+
230256 const validSections = [];
231257 for (const [key, section] of Object.entries(this.masterSections)) {
232258 if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
478504 presets = system_prompts;
479505 preset_names = system_prompts.map(x => x.name);
480506 break;
507+ case 'reasoning':
508+ presets = reasoning_templates;
509+ preset_names = reasoning_templates.map(x => x.name);
510+ break;
481511 default:
482512 console.warn(`Unknown API ID ${api}`);
483513 }
@@ -490,7 +520,7 @@ class PresetManager {
490520 }
491521
492522 isAdvancedFormatting() {
493- return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
523+ return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
494524 }
495525
496526 updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
553583 sysprompt_preset['name'] = name || power_user.sysprompt.preset;
554584 return sysprompt_preset;
555585 }
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+ }
556591 default:
557592 console.warn(`Unknown API ID ${apiId}`);
558593 return {};
@@ -599,6 +634,13 @@ class PresetManager {
599634 'include_reasoning',
600635 'global_banned_tokens',
601636 'send_banned_tokens',
637+
638+ // Reasoning exclusions
639+ 'auto_parse',
640+ 'add_to_prompts',
641+ 'auto_expand',
642+ 'show_hidden',
643+ 'max_additions',
602644 ];
603645 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
604646
public/scripts/reasoning.js+154 -17
@@ -1,4 +1,5 @@
11import {
2+ Fuse,
23 moment,
34} from '../lib.js';
45import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveChatDebounced, saveSettingsDebounced, substituteParams, syncMesToSwipe, updateMessageBlock } from '../script.js';
@@ -14,7 +15,36 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
1415import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
1516import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
1617import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
1718import { 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+ */
31+export const reasoning_templates = [];
32+
33+/**
34+ * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings
35+ * @readonly
36+ */
37+const UI = {
38+ $select: $('#reasoning_select'),
39+ $suffix: $('#reasoning_suffix'),
40+ $prefix: $('#reasoning_prefix'),
41+ $separator: $('#reasoning_separator'),
42+ $autoParse: $('#reasoning_auto_parse'),
43+ $autoExpand: $('#reasoning_auto_expand'),
44+ $showHidden: $('#reasoning_show_hidden'),
45+ $addToPrompts: $('#reasoning_add_to_prompts'),
46+ $maxAdditions: $('#reasoning_max_additions'),
47+};
1848
1949/**
2050 * Enum representing the type of the reasoning for a message (where it came from)
@@ -664,57 +694,103 @@ export class PromptReasoning {
664694}
665695
666696function loadReasoningSettings() {
667697 UI.$('#reasoning_add_to_prompts')addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
668698 UI.$('#reasoning_add_to_prompts')addToPrompts.on('change', function () {
669699 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
670700 saveSettingsDebounced();
671701 });
672702
673703 UI.$('#reasoning_prefix')prefix.val(power_user.reasoning.prefix);
674704 UI.$('#reasoning_prefix')prefix.on('input', function () {
675705 power_user.reasoning.prefix = String($(this).val());
676706 saveSettingsDebounced();
677707 });
678708
679709 UI.$('#reasoning_suffix')suffix.val(power_user.reasoning.suffix);
680710 UI.$('#reasoning_suffix')suffix.on('input', function () {
681711 power_user.reasoning.suffix = String($(this).val());
682712 saveSettingsDebounced();
683713 });
684714
685715 UI.$('#reasoning_separator')separator.val(power_user.reasoning.separator);
686716 UI.$('#reasoning_separator')separator.on('input', function () {
687717 power_user.reasoning.separator = String($(this).val());
688718 saveSettingsDebounced();
689719 });
690720
691721 UI.$('#reasoning_max_additions')maxAdditions.val(power_user.reasoning.max_additions);
692722 UI.$('#reasoning_max_additions')maxAdditions.on('input', function () {
693723 power_user.reasoning.max_additions = Number($(this).val());
694724 saveSettingsDebounced();
695725 });
696726
697727 UI.$('#reasoning_auto_parse')autoParse.prop('checked', power_user.reasoning.auto_parse);
698728 UI.$('#reasoning_auto_parse')autoParse.on('change', function () {
699729 power_user.reasoning.auto_parse = !!$(this).prop('checked');
700730 saveSettingsDebounced();
701731 });
702732
703733 UI.$('#reasoning_auto_expand')autoExpand.prop('checked', power_user.reasoning.auto_expand);
704734 UI.$('#reasoning_auto_expand')autoExpand.on('change', function () {
705735 power_user.reasoning.auto_expand = !!$(this).prop('checked');
706736 toggleReasoningAutoExpand();
707737 saveSettingsDebounced();
708738 });
709739 toggleReasoningAutoExpand();
710740
711741 UI.$('#reasoning_show_hidden')showHidden.prop('checked', power_user.reasoning.show_hidden);
712742 UI.$('#reasoning_show_hidden')showHidden.on('change', function () {
713743 power_user.reasoning.show_hidden = !!$(this).prop('checked');
714744 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
715745 saveSettingsDebounced();
716746 });
717747 $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
748+
749+ UI.$select.on('change', async function () {
750+ const name = String($(this).val());
751+ const template = reasoning_templates.find(p => p.name === name);
752+ if (!template) {
753+ return;
754+ }
755+
756+ UI.$prefix.val(template.prefix);
757+ UI.$suffix.val(template.suffix);
758+ UI.$separator.val(template.separator);
759+
760+ power_user.reasoning.name = name;
761+ power_user.reasoning.prefix = template.prefix;
762+ power_user.reasoning.suffix = template.suffix;
763+ power_user.reasoning.separator = template.separator;
764+
765+ saveSettingsDebounced();
766+ });
767+}
768+
769+function selectReasoningTemplateCallback(args, name) {
770+ if (!name) {
771+ return power_user.reasoning.name ?? '';
772+ }
773+
774+ const quiet = isTrueBoolean(args?.quiet);
775+ const templateNames = reasoning_templates.map(preset => preset.name);
776+ let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
777+
778+ if (!foundName) {
779+ const fuse = new Fuse(templateNames);
780+ const result = fuse.search(name);
781+
782+ if (result.length === 0) {
783+ !quiet && toastr.warning(`Reasoning template "${name}" not found`);
784+ return '';
785+ }
786+
787+ foundName = result[0].item;
788+ }
789+
790+ UI.$select.val(foundName).trigger('change');
791+ !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
792+ return foundName;
793+
718794}
719795
720796function registerReasoningSlashCommands() {
@@ -848,6 +924,42 @@ function registerReasoningSlashCommands() {
848924 : parsedReasoning.reasoning;
849925 },
850926 }));
927+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
928+ name: 'reasoning-template',
929+ aliases: ['reasoning-formatting', 'reasoning-preset'],
930+ callback: selectReasoningTemplateCallback,
931+ returns: 'template name',
932+ namedArgumentList: [
933+ SlashCommandNamedArgument.fromProps({
934+ name: 'quiet',
935+ description: 'Suppress the toast message on template change',
936+ typeList: [ARGUMENT_TYPE.BOOLEAN],
937+ defaultValue: 'false',
938+ enumList: commonEnumProviders.boolean('trueFalse')(),
939+ }),
940+ ],
941+ unnamedArgumentList: [
942+ SlashCommandArgument.fromProps({
943+ description: 'reasoning template name',
944+ typeList: [ARGUMENT_TYPE.STRING],
945+ enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
946+ }),
947+ ],
948+ helpString: `
949+ <div>
950+ Selects a reasoning template by name, using fuzzy search to find the closest match.
951+ Gets the current template if no name is provided.
952+ </div>
953+ <div>
954+ <strong>Example:</strong>
955+ <ul>
956+ <li>
957+ <pre><code class="language-stscript">/reasoning-template DeepSeek R1</code></pre>
958+ </li>
959+ </ul>
960+ </div>
961+ `,
962+ }));
851963}
852964
853965function registerReasoningMacros() {
@@ -1207,6 +1319,31 @@ function registerReasoningAppEvents() {
12071319 }
12081320}
12091321
1322+/**
1323+ * Loads reasoning templates from the settings data.
1324+ * @param {object} data Settings data
1325+ * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
1326+ * @returns {Promise<void>}
1327+ */
1328+export async function loadReasoningTemplates(data) {
1329+ if (data.reasoning !== undefined) {
1330+ reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
1331+ }
1332+
1333+ for (const template of reasoning_templates) {
1334+ $('<option>').val(template.name).text(template.name).appendTo(UI.$select);
1335+ }
1336+
1337+ if (!power_user.reasoning.name) {
1338+ power_user.reasoning.name = reasoning_templates[0]?.name ?? '';
1339+ }
1340+
1341+ UI.$select.val(power_user.reasoning.name);
1342+}
1343+
1344+/**
1345+ * Initializes reasoning settings and event handlers.
1346+ */
12101347export function initReasoning() {
12111348 loadReasoningSettings();
12121349 setReasoningEventHandlers();
src/constants.js+1 -0
@@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({
4343 vectors: 'vectors',
4444 backups: 'backups',
4545 sysprompt: 'sysprompt',
46+ reasoning: 'reasoning',
4647});
4748
4849/**
src/endpoints/content-manager.js+4 -1
@@ -48,6 +48,7 @@ export const CONTENT_TYPES = {
4848 MOVING_UI: 'moving_ui',
4949 QUICK_REPLIES: 'quick_replies',
5050 SYSPROMPT: 'sysprompt',
51+ REASONING: 'reasoning',
5152};
5253
5354/**
@@ -61,7 +62,7 @@ export function getDefaultPresets(directories) {
6162 const presets = [];
6263
6364 for (const contentItem of contentIndex) {
6465 if (contentItem.type.endsWith('_preset') || contentItem.type === ['instruct' || contentItem.type ===, 'context', ||'sysprompt', 'reasoning'].includes(contentItem.type === 'sysprompt')) {
6566 contentItem.name = path.parse(contentItem.filename).name;
6667 contentItem.folder = getTargetByType(contentItem.type, directories);
6768 presets.push(contentItem);
@@ -299,6 +300,8 @@ function getTargetByType(type, directories) {
299300 return directories.quickreplies;
300301 case CONTENT_TYPES.SYSPROMPT:
301302 return directories.sysprompt;
303+ case CONTENT_TYPES.REASONING:
304+ return directories.reasoning;
302305 default:
303306 return null;
304307 }
src/endpoints/presets.js+2 -0
@@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) {
3030 return { folder: directories.context, extension: '.json' };
3131 case 'sysprompt':
3232 return { folder: directories.sysprompt, extension: '.json' };
33+ case 'reasoning':
34+ return { folder: directories.reasoning, extension: '.json' };
3335 default:
3436 return { folder: null, extension: null };
3537 }
src/endpoints/settings.js+2 -0
@@ -254,6 +254,7 @@ router.post('/get', (request, response) => {
254254 const instruct = readAndParseFromDirectory(request.user.directories.instruct);
255255 const context = readAndParseFromDirectory(request.user.directories.context);
256256 const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt);
257+ const reasoning = readAndParseFromDirectory(request.user.directories.reasoning);
257258
258259 response.send({
259260 settings,
@@ -272,6 +273,7 @@ router.post('/get', (request, response) => {
272273 instruct,
273274 context,
274275 sysprompt,
276+ reasoning,
275277 enable_extensions: ENABLE_EXTENSIONS,
276278 enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE,
277279 enable_accounts: ENABLE_ACCOUNTS,
src/users.js+1 -0
@@ -95,6 +95,7 @@ const STORAGE_KEYS = {
9595 * @property {string} vectors - The directory where the vectors are stored
9696 * @property {string} backups - The directory where the backups are stored
9797 * @property {string} sysprompt - The directory where the system prompt data is stored
98+ * @property {string} reasoning - The directory where the reasoning templates are stored
9899 */
99100
100101/**