Prefix
diff --git a/public/scripts/extensions/connection-manager/index.js b/public/scripts/extensions/connection-manager/index.js
index 20019e8e9..fbcf69c7f 100644
--- a/public/scripts/extensions/connection-manager/index.js
+++ b/public/scripts/extensions/connection-manager/index.js
@@ -39,6 +39,7 @@ const CC_COMMANDS = [
'proxy',
'stop-strings',
'start-reply-with',
+ 'reasoning-template',
];
const TC_COMMANDS = [
@@ -54,6 +55,7 @@ const TC_COMMANDS = [
'tokenizer',
'stop-strings',
'start-reply-with',
+ 'reasoning-template',
];
const FANCY_NAMES = {
@@ -70,6 +72,7 @@ const FANCY_NAMES = {
'tokenizer': 'Tokenizer',
'stop-strings': 'Custom Stopping Strings',
'start-reply-with': 'Start Reply With',
+ 'reasoning-template': 'Reasoning Template',
};
/**
@@ -154,6 +157,7 @@ const profilesProvider = () => [
* @property {string} [tokenizer] Tokenizer
* @property {string} [stop-strings] Custom Stopping Strings
* @property {string} [start-reply-with] Start Reply With
+ * @property {string} [reasoning-template] Reasoning Template
* @property {string[]} [exclude] Commands to exclude
*/
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 6eabf2273..d27d62c31 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { loadSystemPrompts } from './sysprompt.js';
import { fuzzySearchCategories } from './filters.js';
import { accountStorage } from './util/AccountStorage.js';
+import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js';
export {
loadPowerUserSettings,
@@ -257,6 +258,7 @@ let power_user = {
},
reasoning: {
+ name: DEFAULT_REASONING_TEMPLATE,
auto_parse: false,
add_to_prompts: false,
auto_expand: false,
@@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) {
await loadInstructMode(data);
await loadContextSettings();
await loadSystemPrompts(data);
+ await loadReasoningTemplates(data);
loadMaxContextUnlocked();
switchWaifuMode();
switchSpoilerMode();
diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js
index c582497fa..cdff9501c 100644
--- a/public/scripts/preset-manager.js
+++ b/public/scripts/preset-manager.js
@@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js';
import { instruct_presets } from './instruct-mode.js';
import { kai_settings } from './kai-settings.js';
import { convertNovelPreset } from './nai-settings.js';
-import { openai_settings, openai_setting_names, oai_settings } from './openai.js';
+import { openai_settings, openai_setting_names } from './openai.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { context_presets, getContextSettings, power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -38,6 +38,7 @@ import {
} from './textgen-settings.js';
import { download, parseJsonFile, waitUntilCondition } from './utils.js';
import { t } from './i18n.js';
+import { reasoning_templates } from './reasoning.js';
const presetManagers = {};
@@ -168,6 +169,20 @@ class PresetManager {
},
isValid: (data) => PresetManager.isPossiblyTextCompletionData(data),
},
+ 'reasoning': {
+ name: 'Reasoning Formatting',
+ getData: () => {
+ const manager = getPresetManager('reasoning');
+ const name = manager.getSelectedPresetName();
+ return manager.getPresetSettings(name);
+ },
+ setData: (data) => {
+ const manager = getPresetManager('reasoning');
+ const name = data.name;
+ return manager.savePreset(name, data);
+ },
+ isValid: (data) => PresetManager.isPossiblyReasoningData(data),
+ },
};
static isPossiblyInstructData(data) {
@@ -190,6 +205,11 @@ class PresetManager {
return data && textCompletionProps.every(prop => Object.keys(data).includes(prop));
}
+ static isPossiblyReasoningData(data) {
+ const reasoningProps = ['name', 'prefix', 'suffix', 'separator'];
+ return data && reasoningProps.every(prop => Object.keys(data).includes(prop));
+ }
+
/**
* Imports master settings from JSON data.
* @param {object} data Data to import
@@ -227,6 +247,12 @@ class PresetManager {
return await getPresetManager('textgenerationwebui').savePreset(fileName, data);
}
+ // 5. Reasoning Template
+ if (this.isPossiblyReasoningData(data)) {
+ toastr.info(t`Importing as reasoning template...`, t`Reasoning template detected`);
+ return await getPresetManager('reasoning').savePreset(data.name, data);
+ }
+
const validSections = [];
for (const [key, section] of Object.entries(this.masterSections)) {
if (key in data && section.isValid(data[key])) {
@@ -478,6 +504,10 @@ class PresetManager {
presets = system_prompts;
preset_names = system_prompts.map(x => x.name);
break;
+ case 'reasoning':
+ presets = reasoning_templates;
+ preset_names = reasoning_templates.map(x => x.name);
+ break;
default:
console.warn(`Unknown API ID ${api}`);
}
@@ -490,7 +520,7 @@ class PresetManager {
}
isAdvancedFormatting() {
- return this.apiId == 'context' || this.apiId == 'instruct' || this.apiId == 'sysprompt';
+ return ['context', 'instruct', 'sysprompt', 'reasoning'].includes(this.apiId);
}
updateList(name, preset) {
@@ -553,6 +583,11 @@ class PresetManager {
sysprompt_preset['name'] = name || power_user.sysprompt.preset;
return sysprompt_preset;
}
+ case 'reasoning': {
+ const reasoning_preset = structuredClone(power_user.reasoning);
+ reasoning_preset['name'] = name || power_user.reasoning.preset;
+ return reasoning_preset;
+ }
default:
console.warn(`Unknown API ID ${apiId}`);
return {};
@@ -599,6 +634,13 @@ class PresetManager {
'include_reasoning',
'global_banned_tokens',
'send_banned_tokens',
+
+ // Reasoning exclusions
+ 'auto_parse',
+ 'add_to_prompts',
+ 'auto_expand',
+ 'show_hidden',
+ 'max_additions',
];
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js
index 54ee800f2..b63b26f63 100644
--- a/public/scripts/reasoning.js
+++ b/public/scripts/reasoning.js
@@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js';
-import { power_user } from './power-user.js';
+import { performFuzzySearch, power_user } from './power-user.js';
+import { getPresetManager } from './preset-manager.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
-import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
+import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetProperty, trimSpaces } from './utils.js';
+
+/**
+ * @typedef {object} ReasoningTemplate
+ * @property {string} name - The name of the template
+ * @property {string} prefix - Reasoning prefix
+ * @property {string} suffix - Reasoning suffix
+ * @property {string} separator - Reasoning separator
+ */
+
+/**
+ * @type {ReasoningTemplate[]} List of reasoning templates
+ */
+export const reasoning_templates = [];
+
+export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek';
+
+/**
+ * @type {Record
>} List of UI elements for reasoning settings
+ * @readonly
+ */
+const UI = {
+ $select: $('#reasoning_select'),
+ $suffix: $('#reasoning_suffix'),
+ $prefix: $('#reasoning_prefix'),
+ $separator: $('#reasoning_separator'),
+ $autoParse: $('#reasoning_auto_parse'),
+ $autoExpand: $('#reasoning_auto_expand'),
+ $showHidden: $('#reasoning_show_hidden'),
+ $addToPrompts: $('#reasoning_add_to_prompts'),
+ $maxAdditions: $('#reasoning_max_additions'),
+};
/**
* Enum representing the type of the reasoning for a message (where it came from)
@@ -61,7 +93,7 @@ export function extractReasoningFromData(data, {
mainApi = null,
ignoreShowThoughts = false,
textGenType = null,
- chatCompletionSource = null
+ chatCompletionSource = null,
} = {}) {
switch (mainApi ?? main_api) {
case 'textgenerationwebui':
@@ -669,57 +701,102 @@ export class PromptReasoning {
}
function loadReasoningSettings() {
- $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
- $('#reasoning_add_to_prompts').on('change', function () {
+ UI.$addToPrompts.prop('checked', power_user.reasoning.add_to_prompts);
+ UI.$addToPrompts.on('change', function () {
power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
saveSettingsDebounced();
});
- $('#reasoning_prefix').val(power_user.reasoning.prefix);
- $('#reasoning_prefix').on('input', function () {
+ UI.$prefix.val(power_user.reasoning.prefix);
+ UI.$prefix.on('input', function () {
power_user.reasoning.prefix = String($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_suffix').val(power_user.reasoning.suffix);
- $('#reasoning_suffix').on('input', function () {
+ UI.$suffix.val(power_user.reasoning.suffix);
+ UI.$suffix.on('input', function () {
power_user.reasoning.suffix = String($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_separator').val(power_user.reasoning.separator);
- $('#reasoning_separator').on('input', function () {
+ UI.$separator.val(power_user.reasoning.separator);
+ UI.$separator.on('input', function () {
power_user.reasoning.separator = String($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_max_additions').val(power_user.reasoning.max_additions);
- $('#reasoning_max_additions').on('input', function () {
+ UI.$maxAdditions.val(power_user.reasoning.max_additions);
+ UI.$maxAdditions.on('input', function () {
power_user.reasoning.max_additions = Number($(this).val());
saveSettingsDebounced();
});
- $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);
- $('#reasoning_auto_parse').on('change', function () {
+ UI.$autoParse.prop('checked', power_user.reasoning.auto_parse);
+ UI.$autoParse.on('change', function () {
power_user.reasoning.auto_parse = !!$(this).prop('checked');
saveSettingsDebounced();
});
- $('#reasoning_auto_expand').prop('checked', power_user.reasoning.auto_expand);
- $('#reasoning_auto_expand').on('change', function () {
+ UI.$autoExpand.prop('checked', power_user.reasoning.auto_expand);
+ UI.$autoExpand.on('change', function () {
power_user.reasoning.auto_expand = !!$(this).prop('checked');
toggleReasoningAutoExpand();
saveSettingsDebounced();
});
toggleReasoningAutoExpand();
- $('#reasoning_show_hidden').prop('checked', power_user.reasoning.show_hidden);
- $('#reasoning_show_hidden').on('change', function () {
+ UI.$showHidden.prop('checked', power_user.reasoning.show_hidden);
+ UI.$showHidden.on('change', function () {
power_user.reasoning.show_hidden = !!$(this).prop('checked');
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
saveSettingsDebounced();
});
$('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null);
+
+ UI.$select.on('change', async function () {
+ const name = String($(this).val());
+ const template = reasoning_templates.find(p => p.name === name);
+ if (!template) {
+ return;
+ }
+
+ UI.$prefix.val(template.prefix);
+ UI.$suffix.val(template.suffix);
+ UI.$separator.val(template.separator);
+
+ power_user.reasoning.name = name;
+ power_user.reasoning.prefix = template.prefix;
+ power_user.reasoning.suffix = template.suffix;
+ power_user.reasoning.separator = template.separator;
+
+ saveSettingsDebounced();
+ });
+}
+
+function selectReasoningTemplateCallback(args, name) {
+ if (!name) {
+ return power_user.reasoning.name ?? '';
+ }
+
+ const quiet = isTrueBoolean(args?.quiet);
+ const templateNames = reasoning_templates.map(preset => preset.name);
+ let foundName = templateNames.find(x => x.toLowerCase() === name.toLowerCase());
+
+ if (!foundName) {
+ const result = performFuzzySearch('reasoning-templates', templateNames, [], name);
+
+ if (result.length === 0) {
+ !quiet && toastr.warning(`Reasoning template "${name}" not found`);
+ return '';
+ }
+
+ foundName = result[0].item;
+ }
+
+ UI.$select.val(foundName).trigger('change');
+ !quiet && toastr.success(`Reasoning template "${foundName}" selected`);
+ return foundName;
+
}
function registerReasoningSlashCommands() {
@@ -853,6 +930,42 @@ function registerReasoningSlashCommands() {
: parsedReasoning.reasoning;
},
}));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'reasoning-template',
+ aliases: ['reasoning-formatting', 'reasoning-preset'],
+ callback: selectReasoningTemplateCallback,
+ returns: 'template name',
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'quiet',
+ description: 'Suppress the toast message on template change',
+ typeList: [ARGUMENT_TYPE.BOOLEAN],
+ defaultValue: 'false',
+ enumList: commonEnumProviders.boolean('trueFalse')(),
+ }),
+ ],
+ unnamedArgumentList: [
+ SlashCommandArgument.fromProps({
+ description: 'reasoning template name',
+ typeList: [ARGUMENT_TYPE.STRING],
+ enumProvider: () => reasoning_templates.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)),
+ }),
+ ],
+ helpString: `
+
+ Selects a reasoning template by name, using fuzzy search to find the closest match.
+ Gets the current template if no name is provided.
+
+
+ `,
+ }));
}
function registerReasoningMacros() {
@@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() {
}
}
+/**
+ * Loads reasoning templates from the settings data.
+ * @param {object} data Settings data
+ * @param {ReasoningTemplate[]} data.reasoning Reasoning templates
+ * @returns {Promise}
+ */
+export async function loadReasoningTemplates(data) {
+ if (data.reasoning !== undefined) {
+ reasoning_templates.splice(0, reasoning_templates.length, ...data.reasoning);
+ }
+
+ for (const template of reasoning_templates) {
+ $('