Merge pull request #3700 from SillyTavern/reasoning-template Reasoning templates
Signed| @@ -786,5 +786,13 @@ | ||
| 786 | 786 | { |
| 787 | 787 | "filename": "presets/context/DeepSeek-V2.5.json", |
| 788 | 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 | 3917 | <summary data-i18n="Reasoning Formatting"> |
| 3918 | 3918 | Reasoning Formatting |
| 3919 | 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 | 3933 | <div class="flex-container"> |
| 3921 | 3934 | <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix"> |
| 3922 | 3935 | <small data-i18n="Prefix">Prefix</small> |
| @@ -39,6 +39,7 @@ const CC_COMMANDS = [ | ||
| 39 | 39 | 'proxy', |
| 40 | 40 | 'stop-strings', |
| 41 | 41 | 'start-reply-with', |
| 42 | + 'reasoning-template', | |
| 42 | 43 | ]; |
| 43 | 44 | |
| 44 | 45 | const TC_COMMANDS = [ |
| @@ -54,6 +55,7 @@ const TC_COMMANDS = [ | ||
| 54 | 55 | 'tokenizer', |
| 55 | 56 | 'stop-strings', |
| 56 | 57 | 'start-reply-with', |
| 58 | + 'reasoning-template', | |
| 57 | 59 | ]; |
| 58 | 60 | |
| 59 | 61 | const FANCY_NAMES = { |
| @@ -70,6 +72,7 @@ const FANCY_NAMES = { | ||
| 70 | 72 | 'tokenizer': 'Tokenizer', |
| 71 | 73 | 'stop-strings': 'Custom Stopping Strings', |
| 72 | 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 | 157 | * @property {string} [tokenizer] Tokenizer |
| 155 | 158 | * @property {string} [stop-strings] Custom Stopping Strings |
| 156 | 159 | * @property {string} [start-reply-with] Start Reply With |
| 160 | + * @property {string} [reasoning-template] Reasoning Template | |
| 157 | 161 | * @property {string[]} [exclude] Commands to exclude |
| 158 | 162 | */ |
| 159 | 163 | |
| @@ -55,6 +55,7 @@ import { POPUP_TYPE, callGenericPopup } from './popup.js'; | ||
| 55 | 55 | import { loadSystemPrompts } from './sysprompt.js'; |
| 56 | 56 | import { fuzzySearchCategories } from './filters.js'; |
| 57 | 57 | import { accountStorage } from './util/AccountStorage.js'; |
| 58 | +import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js'; | |
| 58 | 59 | |
| 59 | 60 | export { |
| 60 | 61 | loadPowerUserSettings, |
| @@ -257,6 +258,7 @@ let power_user = { | ||
| 257 | 258 | }, |
| 258 | 259 | |
| 259 | 260 | reasoning: { |
| 261 | + name: DEFAULT_REASONING_TEMPLATE, | |
| 260 | 262 | auto_parse: false, |
| 261 | 263 | add_to_prompts: false, |
| 262 | 264 | auto_expand: false, |
| @@ -1624,6 +1626,7 @@ async function loadPowerUserSettings(settings, data) { | ||
| 1624 | 1626 | await loadInstructMode(data); |
| 1625 | 1627 | await loadContextSettings(); |
| 1626 | 1628 | await loadSystemPrompts(data); |
| 1629 | + await loadReasoningTemplates(data); | |
| 1627 | 1630 | loadMaxContextUnlocked(); |
| 1628 | 1631 | switchWaifuMode(); |
| 1629 | 1632 | switchSpoilerMode(); |
| @@ -21,7 +21,7 @@ import { groups, selected_group } from './group-chats.js'; | ||
| 21 | 21 | import { instruct_presets } from './instruct-mode.js'; |
| 22 | 22 | import { kai_settings } from './kai-settings.js'; |
| 23 | 23 | import { convertNovelPreset } from './nai-settings.js'; |
| 24 | 24 | import { openai_settings, openai_setting_names, oai_settings } from './openai.js'; |
| 25 | 25 | import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 26 | 26 | import { context_presets, getContextSettings, power_user } from './power-user.js'; |
| 27 | 27 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -38,6 +38,7 @@ import { | ||
| 38 | 38 | } from './textgen-settings.js'; |
| 39 | 39 | import { download, parseJsonFile, waitUntilCondition } from './utils.js'; |
| 40 | 40 | import { t } from './i18n.js'; |
| 41 | +import { reasoning_templates } from './reasoning.js'; | |
| 41 | 42 | |
| 42 | 43 | const presetManagers = {}; |
| 43 | 44 | |
| @@ -168,6 +169,20 @@ class PresetManager { | ||
| 168 | 169 | }, |
| 169 | 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 | 188 | static isPossiblyInstructData(data) { |
| @@ -190,6 +205,11 @@ class PresetManager { | ||
| 190 | 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 | 214 | * Imports master settings from JSON data. |
| 195 | 215 | * @param {object} data Data to import |
| @@ -227,6 +247,12 @@ class PresetManager { | ||
| 227 | 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 | 256 | const validSections = []; |
| 231 | 257 | for (const [key, section] of Object.entries(this.masterSections)) { |
| 232 | 258 | if (key in data && section.isValid(data[key])) { |
| @@ -478,6 +504,10 @@ class PresetManager { | ||
| 478 | 504 | presets = system_prompts; |
| 479 | 505 | preset_names = system_prompts.map(x => x.name); |
| 480 | 506 | break; |
| 507 | + case 'reasoning': | |
| 508 | + presets = reasoning_templates; | |
| 509 | + preset_names = reasoning_templates.map(x => x.name); | |
| 510 | + break; | |
| 481 | 511 | default: |
| 482 | 512 | console.warn(`Unknown API ID ${api}`); |
| 483 | 513 | } |
| @@ -490,7 +520,7 @@ class PresetManager { | ||
| 490 | 520 | } |
| 491 | 521 | |
| 492 | 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 | 526 | updateList(name, preset) { |
| @@ -553,6 +583,11 @@ class PresetManager { | ||
| 553 | 583 | sysprompt_preset['name'] = name || power_user.sysprompt.preset; |
| 554 | 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 | 591 | default: |
| 557 | 592 | console.warn(`Unknown API ID ${apiId}`); |
| 558 | 593 | return {}; |
| @@ -599,6 +634,13 @@ class PresetManager { | ||
| 599 | 634 | 'include_reasoning', |
| 600 | 635 | 'global_banned_tokens', |
| 601 | 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 | 645 | const settings = Object.assign({}, getSettingsByApiId(this.apiId)); |
| 604 | 646 | |
| @@ -7,14 +7,46 @@ import { getCurrentLocale, t, translate } from './i18n.js'; | ||
| 7 | 7 | import { MacrosParser } from './macros.js'; |
| 8 | 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; |
| 9 | 9 | import { Popup } from './popup.js'; |
| 10 | 10 | import { performFuzzySearch, power_user } from './power-user.js'; |
| 11 | +import { getPresetManager } from './preset-manager.js'; | |
| 11 | 12 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 12 | 13 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| 13 | 14 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 14 | 15 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; |
| 15 | 16 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 16 | 17 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 17 | 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 | 52 | * Enum representing the type of the reasoning for a message (where it came from) |
| @@ -61,7 +93,7 @@ export function extractReasoningFromData(data, { | ||
| 61 | 93 | mainApi = null, |
| 62 | 94 | ignoreShowThoughts = false, |
| 63 | 95 | textGenType = null, |
| 64 | 96 | chatCompletionSource = null, |
| 65 | 97 | } = {}) { |
| 66 | 98 | switch (mainApi ?? main_api) { |
| 67 | 99 | case 'textgenerationwebui': |
| @@ -669,57 +701,102 @@ export class PromptReasoning { | ||
| 669 | 701 | } |
| 670 | 702 | |
| 671 | 703 | function loadReasoningSettings() { |
| 672 | 704 | UI.$('#reasoning_add_to_prompts')addToPrompts.prop('checked', power_user.reasoning.add_to_prompts); |
| 673 | 705 | UI.$('#reasoning_add_to_prompts')addToPrompts.on('change', function () { |
| 674 | 706 | power_user.reasoning.add_to_prompts = !!$(this).prop('checked'); |
| 675 | 707 | saveSettingsDebounced(); |
| 676 | 708 | }); |
| 677 | 709 | |
| 678 | 710 | UI.$('#reasoning_prefix')prefix.val(power_user.reasoning.prefix); |
| 679 | 711 | UI.$('#reasoning_prefix')prefix.on('input', function () { |
| 680 | 712 | power_user.reasoning.prefix = String($(this).val()); |
| 681 | 713 | saveSettingsDebounced(); |
| 682 | 714 | }); |
| 683 | 715 | |
| 684 | 716 | UI.$('#reasoning_suffix')suffix.val(power_user.reasoning.suffix); |
| 685 | 717 | UI.$('#reasoning_suffix')suffix.on('input', function () { |
| 686 | 718 | power_user.reasoning.suffix = String($(this).val()); |
| 687 | 719 | saveSettingsDebounced(); |
| 688 | 720 | }); |
| 689 | 721 | |
| 690 | 722 | UI.$('#reasoning_separator')separator.val(power_user.reasoning.separator); |
| 691 | 723 | UI.$('#reasoning_separator')separator.on('input', function () { |
| 692 | 724 | power_user.reasoning.separator = String($(this).val()); |
| 693 | 725 | saveSettingsDebounced(); |
| 694 | 726 | }); |
| 695 | 727 | |
| 696 | 728 | UI.$('#reasoning_max_additions')maxAdditions.val(power_user.reasoning.max_additions); |
| 697 | 729 | UI.$('#reasoning_max_additions')maxAdditions.on('input', function () { |
| 698 | 730 | power_user.reasoning.max_additions = Number($(this).val()); |
| 699 | 731 | saveSettingsDebounced(); |
| 700 | 732 | }); |
| 701 | 733 | |
| 702 | 734 | UI.$('#reasoning_auto_parse')autoParse.prop('checked', power_user.reasoning.auto_parse); |
| 703 | 735 | UI.$('#reasoning_auto_parse')autoParse.on('change', function () { |
| 704 | 736 | power_user.reasoning.auto_parse = !!$(this).prop('checked'); |
| 705 | 737 | saveSettingsDebounced(); |
| 706 | 738 | }); |
| 707 | 739 | |
| 708 | 740 | UI.$('#reasoning_auto_expand')autoExpand.prop('checked', power_user.reasoning.auto_expand); |
| 709 | 741 | UI.$('#reasoning_auto_expand')autoExpand.on('change', function () { |
| 710 | 742 | power_user.reasoning.auto_expand = !!$(this).prop('checked'); |
| 711 | 743 | toggleReasoningAutoExpand(); |
| 712 | 744 | saveSettingsDebounced(); |
| 713 | 745 | }); |
| 714 | 746 | toggleReasoningAutoExpand(); |
| 715 | 747 | |
| 716 | 748 | UI.$('#reasoning_show_hidden')showHidden.prop('checked', power_user.reasoning.show_hidden); |
| 717 | 749 | UI.$('#reasoning_show_hidden')showHidden.on('change', function () { |
| 718 | 750 | power_user.reasoning.show_hidden = !!$(this).prop('checked'); |
| 719 | 751 | $('#chat').attr('data-show-hidden-reasoning', power_user.reasoning.show_hidden ? 'true' : null); |
| 720 | 752 | saveSettingsDebounced(); |
| 721 | 753 | }); |
| 722 | 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 | 802 | function registerReasoningSlashCommands() { |
| @@ -853,6 +930,42 @@ function registerReasoningSlashCommands() { | ||
| 853 | 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 | 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 | 1375 | export function initReasoning() { |
| 1216 | 1376 | loadReasoningSettings(); |
| 1217 | 1377 | setReasoningEventHandlers(); |
| @@ -43,6 +43,7 @@ export const USER_DIRECTORY_TEMPLATE = Object.freeze({ | ||
| 43 | 43 | vectors: 'vectors', |
| 44 | 44 | backups: 'backups', |
| 45 | 45 | sysprompt: 'sysprompt', |
| 46 | + reasoning: 'reasoning', | |
| 46 | 47 | }); |
| 47 | 48 | |
| 48 | 49 | /** |
| @@ -48,6 +48,7 @@ export const CONTENT_TYPES = { | ||
| 48 | 48 | MOVING_UI: 'moving_ui', |
| 49 | 49 | QUICK_REPLIES: 'quick_replies', |
| 50 | 50 | SYSPROMPT: 'sysprompt', |
| 51 | + REASONING: 'reasoning', | |
| 51 | 52 | }; |
| 52 | 53 | |
| 53 | 54 | /** |
| @@ -61,7 +62,7 @@ export function getDefaultPresets(directories) { | ||
| 61 | 62 | const presets = []; |
| 62 | 63 | |
| 63 | 64 | for (const contentItem of contentIndex) { |
| 64 | 65 | if (contentItem.type.endsWith('_preset') || contentItem.type === ['instruct' || contentItem.type ===, 'context', ||'sysprompt', 'reasoning'].includes(contentItem.type === 'sysprompt')) { |
| 65 | 66 | contentItem.name = path.parse(contentItem.filename).name; |
| 66 | 67 | contentItem.folder = getTargetByType(contentItem.type, directories); |
| 67 | 68 | presets.push(contentItem); |
| @@ -299,6 +300,8 @@ function getTargetByType(type, directories) { | ||
| 299 | 300 | return directories.quickreplies; |
| 300 | 301 | case CONTENT_TYPES.SYSPROMPT: |
| 301 | 302 | return directories.sysprompt; |
| 303 | + case CONTENT_TYPES.REASONING: | |
| 304 | + return directories.reasoning; | |
| 302 | 305 | default: |
| 303 | 306 | return null; |
| 304 | 307 | } |
| @@ -30,6 +30,8 @@ function getPresetSettingsByAPI(apiId, directories) { | ||
| 30 | 30 | return { folder: directories.context, extension: '.json' }; |
| 31 | 31 | case 'sysprompt': |
| 32 | 32 | return { folder: directories.sysprompt, extension: '.json' }; |
| 33 | + case 'reasoning': | |
| 34 | + return { folder: directories.reasoning, extension: '.json' }; | |
| 33 | 35 | default: |
| 34 | 36 | return { folder: null, extension: null }; |
| 35 | 37 | } |
| @@ -254,6 +254,7 @@ router.post('/get', (request, response) => { | ||
| 254 | 254 | const instruct = readAndParseFromDirectory(request.user.directories.instruct); |
| 255 | 255 | const context = readAndParseFromDirectory(request.user.directories.context); |
| 256 | 256 | const sysprompt = readAndParseFromDirectory(request.user.directories.sysprompt); |
| 257 | + const reasoning = readAndParseFromDirectory(request.user.directories.reasoning); | |
| 257 | 258 | |
| 258 | 259 | response.send({ |
| 259 | 260 | settings, |
| @@ -272,6 +273,7 @@ router.post('/get', (request, response) => { | ||
| 272 | 273 | instruct, |
| 273 | 274 | context, |
| 274 | 275 | sysprompt, |
| 276 | + reasoning, | |
| 275 | 277 | enable_extensions: ENABLE_EXTENSIONS, |
| 276 | 278 | enable_extensions_auto_update: ENABLE_EXTENSIONS_AUTO_UPDATE, |
| 277 | 279 | enable_accounts: ENABLE_ACCOUNTS, |
| @@ -95,6 +95,7 @@ const STORAGE_KEYS = { | ||
| 95 | 95 | * @property {string} vectors - The directory where the vectors are stored |
| 96 | 96 | * @property {string} backups - The directory where the backups are stored |
| 97 | 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 | /** |