Merge pull request #3700 from SillyTavern/reasoning-template Reasoning templates
Signed| @@ -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 | ] |
| @@ -0,0 +1,6 @@ | |||
| 1 | { | ||
| 2 | "name": "Blank", | ||
| 3 | "prefix": "", | ||
| 4 | "suffix": "", | ||
| 5 | "separator": "" | ||
| 6 | } | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | { | ||
| 2 | "name": "DeepSeek", | ||
| 3 | "prefix": "<think>\n", | ||
| 4 | "suffix": "\n</think>", | ||
| 5 | "separator": "\n\n" | ||
| 6 | } | ||
| @@ -3917,6 +3917,19 @@ | |||
| 3917 | <summary data-i18n="Reasoning Formatting"> | 3917 | <summary data-i18n="Reasoning Formatting"> |
| 3918 | Reasoning Formatting | 3918 | 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> |
| @@ -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 | ]; |
| 43 | 44 | ||
| 44 | const TC_COMMANDS = [ | 45 | const 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 | ]; |
| 58 | 60 | ||
| 59 | const FANCY_NAMES = { | 61 | const 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 | }; |
| 74 | 77 | ||
| 75 | /** | 78 | /** |
| @@ -154,6 +157,7 @@ const profilesProvider = () => [ | |||
| 154 | * @property {string} [tokenizer] Tokenizer | 157 | * @property {string} [tokenizer] Tokenizer |
| 155 | * @property {string} [stop-strings] Custom Stopping Strings | 158 | * @property {string} [stop-strings] Custom Stopping Strings |
| 156 | * @property {string} [start-reply-with] Start Reply With | 159 | * @property {string} [start-reply-with] Start Reply With |
| 160 | * @property {string} [reasoning-template] Reasoning Template | ||
| 157 | * @property {string[]} [exclude] Commands to exclude | 161 | * @property {string[]} [exclude] Commands to exclude |
| 158 | */ | 162 | */ |
| 159 | 163 | ||
| @@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js'; | |||
| 55 | import { loadSystemPrompts } from './sysprompt.js'; | 55 | import { loadSystemPrompts } from './sysprompt.js'; |
| 56 | import { fuzzySearchCategories } from './filters.js'; | 56 | import { fuzzySearchCategories } from './filters.js'; |
| 57 | import { accountStorage } from './util/AccountStorage.js'; | 57 | import { accountStorage } from './util/AccountStorage.js'; |
| 58 | import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js'; | ||
| 58 | 59 | ||
| 59 | export { | 60 | export { |
| 60 | loadPowerUserSettings, | 61 | loadPowerUserSettings, |
| @@ -257,6 +258,7 @@ let power_user = { | |||
| 257 | }, | 258 | }, |
| 258 | 259 | ||
| 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(); |
| @@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js'; | |||
| 21 | import { instruct_presets } from './instruct-mode.js'; | 21 | import { instruct_presets } from './instruct-mode.js'; |
| 22 | import { kai_settings } from './kai-settings.js'; | 22 | import { kai_settings } from './kai-settings.js'; |
| 23 | import { convertNovelPreset } from './nai-settings.js'; | 23 | import { convertNovelPreset } from './nai-settings.js'; |
| 24 | import { openai_settings, openai_setting_names, oai_settings } from './openai.js'; | 24 | import { openai_settings, openai_setting_names } from './openai.js'; |
| 25 | import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; | 25 | import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 26 | import { context_presets, getContextSettings, power_user } from './power-user.js'; | 26 | import { context_presets, getContextSettings, power_user } from './power-user.js'; |
| 27 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 27 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -38,6 +38,7 @@ import { | |||
| 38 | } from './textgen-settings.js'; | 38 | } from './textgen-settings.js'; |
| 39 | import { download, parseJsonFile, waitUntilCondition } from './utils.js'; | 39 | import { download, parseJsonFile, waitUntilCondition } from './utils.js'; |
| 40 | import { t } from './i18n.js'; | 40 | import { t } from './i18n.js'; |
| 41 | import { reasoning_templates } from './reasoning.js'; | ||
| 41 | 42 | ||
| 42 | const presetManagers = {}; | 43 | const presetManagers = {}; |
| 43 | 44 | ||
| @@ -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 | }; |
| 172 | 187 | ||
| 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 | } |
| 192 | 207 | ||
| 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 import | 215 | * @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 | } |
| 229 | 249 | ||
| 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 | } |
| 491 | 521 | ||
| 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 | } |
| 495 | 525 | ||
| 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)); |
| 604 | 646 | ||
| @@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js'; | |||
| 7 | import { MacrosParser } from './macros.js'; | 7 | import { MacrosParser } from './macros.js'; |
| 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; | 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; |
| 9 | import { Popup } from './popup.js'; | 9 | import { Popup } from './popup.js'; |
| 10 | import { power_user } from './power-user.js'; | 10 | import { performFuzzySearch, power_user } from './power-user.js'; |
| 11 | import { getPresetManager } from './preset-manager.js'; | ||
| 11 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 12 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 12 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | 13 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| 13 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 14 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 14 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; | 15 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; |
| 15 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 16 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 16 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | 17 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 17 | import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js'; | 18 | import { 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 | export const DEFAULT_REASONING_TEMPLATE = 'DeepSeek'; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * @type {Record<string, JQuery<HTMLElement>>} List of UI elements for reasoning settings | ||
| 37 | * @readonly | ||
| 38 | */ | ||
| 39 | const 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 | }; | ||
| 18 | 50 | ||
| 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 = null | 96 | 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 | } |
| 670 | 702 | ||
| 671 | function loadReasoningSettings() { | 703 | function 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 | }); |
| 677 | 709 | ||
| 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 | }); |
| 683 | 715 | ||
| 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 | }); |
| 689 | 721 | ||
| 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 | }); |
| 695 | 727 | ||
| 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 | }); |
| 701 | 733 | ||
| 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 | }); |
| 707 | 739 | ||
| 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(); |
| 715 | 747 | ||
| 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 | |||
| 776 | function 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 | } |
| 724 | 801 | ||
| 725 | function registerReasoningSlashCommands() { | 802 | function 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 | } |
| 857 | 970 | ||
| 858 | function registerReasoningMacros() { | 971 | function registerReasoningMacros() { |
| @@ -1212,6 +1325,53 @@ function registerReasoningAppEvents() { | |||
| 1212 | } | 1325 | } |
| 1213 | } | 1326 | } |
| 1214 | 1327 | ||
| 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 | */ | ||
| 1334 | export 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 | */ | ||
| 1215 | export function initReasoning() { | 1375 | export function initReasoning() { |
| 1216 | loadReasoningSettings(); | 1376 | loadReasoningSettings(); |
| 1217 | setReasoningEventHandlers(); | 1377 | setReasoningEventHandlers(); |
| @@ -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 | }); |
| 47 | 48 | ||
| 48 | /** | 49 | /** |
| @@ -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 | }; |
| 52 | 53 | ||
| 53 | /** | 54 | /** |
| @@ -61,7 +62,7 @@ export function getDefaultPresets(directories) { | |||
| 61 | const presets = []; | 62 | const presets = []; |
| 62 | 63 | ||
| 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 | } |
| @@ -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 | } |
| @@ -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); | ||
| 257 | 258 | ||
| 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, |
| @@ -95,6 +95,7 @@ const STORAGE_KEYS = { | |||
| 95 | * @property {string} vectors - The directory where the vectors are stored | 95 | * @property {string} vectors - The directory where the vectors are stored |
| 96 | * @property {string} backups - The directory where the backups are stored | 96 | * @property {string} backups - The directory where the backups are stored |
| 97 | * @property {string} sysprompt - The directory where the system prompt data is stored | 97 | * @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 | */ |
| 99 | 100 | ||
| 100 | /** | 101 | /** |