From 62342b35e27253512352f8be5142dbb0c7063d47 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 16 Mar 2025 15:01:31 +0200 Subject: [PATCH 1/5] Reasoning template --- default/content/index.json | 4 + .../presets/reasoning/DeepSeek R1.json | 6 + public/index.html | 13 ++ public/scripts/power-user.js | 3 + public/scripts/preset-manager.js | 46 ++++- public/scripts/reasoning.js | 171 ++++++++++++++++-- src/constants.js | 1 + src/endpoints/content-manager.js | 5 +- src/endpoints/presets.js | 2 + src/endpoints/settings.js | 2 + src/users.js | 1 + 11 files changed, 234 insertions(+), 20 deletions(-) create mode 100644 default/content/presets/reasoning/DeepSeek R1.json diff --git a/default/content/index.json b/default/content/index.json index 4caa21c14..3a09e8e84 100644 --- a/default/content/index.json +++ b/default/content/index.json @@ -786,5 +786,9 @@ { "filename": "presets/context/DeepSeek-V2.5.json", "type": "context" + }, + { + "filename": "presets/reasoning/DeepSeek R1.json", + "type": "reasoning" } ] diff --git a/default/content/presets/reasoning/DeepSeek R1.json b/default/content/presets/reasoning/DeepSeek R1.json new file mode 100644 index 000000000..503c45171 --- /dev/null +++ b/default/content/presets/reasoning/DeepSeek R1.json @@ -0,0 +1,6 @@ +{ + "name": "DeepSeek R1", + "prefix": "\n", + "suffix": "\n", + "separator": "\n\n" +} diff --git a/public/index.html b/public/index.html index 0753e3134..a072aaf47 100644 --- a/public/index.html +++ b/public/index.html @@ -3917,6 +3917,19 @@ Reasoning Formatting +
+ +
+ + + + + + + + +
+
Prefix diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 572be339b..ba5dead91 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 { loadReasoningTemplates } from './reasoning.js'; export { loadPowerUserSettings, @@ -255,6 +256,7 @@ let power_user = { }, reasoning: { + name: 'DeepSeek R1', auto_parse: false, add_to_prompts: false, auto_expand: false, @@ -1622,6 +1624,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 2a8e9f73e..a4f663ff3 100644 --- a/public/scripts/reasoning.js +++ b/public/scripts/reasoning.js @@ -1,4 +1,5 @@ import { + Fuse, moment, } from '../lib.js'; import { 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 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 = []; + +/** + * @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) @@ -664,57 +694,103 @@ 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 fuse = new Fuse(templateNames); + const result = fuse.search(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() { @@ -848,6 +924,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. +
+
+ Example: +
    +
  • +
    /reasoning-template DeepSeek R1
    +
  • +
+
+ `, + })); } function registerReasoningMacros() { @@ -1207,6 +1319,31 @@ 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) { + $('
From b1346910a451cebf70257f6fb7bbd230f876d354 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 18 Mar 2025 00:47:20 +0200 Subject: [PATCH 5/5] Adjust reasoning template migration procedure --- public/scripts/power-user.js | 4 ++-- public/scripts/reasoning.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 13bda052f..81d5bbf94 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -55,7 +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 { loadReasoningTemplates } from './reasoning.js'; +import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js'; export { loadPowerUserSettings, @@ -258,7 +258,7 @@ let power_user = { }, reasoning: { - name: 'DeepSeek', + name: DEFAULT_REASONING_TEMPLATE, auto_parse: false, add_to_prompts: false, auto_expand: false, diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js index 39ce8885e..b63b26f63 100644 --- a/public/scripts/reasoning.js +++ b/public/scripts/reasoning.js @@ -8,6 +8,7 @@ import { MacrosParser } from './macros.js'; import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; import { Popup } from './popup.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'; @@ -29,6 +30,8 @@ import { copyText, escapeRegex, isFalseBoolean, isTrueBoolean, setDatasetPropert */ export const reasoning_templates = []; +export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek'; + /** * @type {Record>} List of UI elements for reasoning settings * @readonly @@ -1337,8 +1340,30 @@ export async function loadReasoningTemplates(data) { $('