| 1 | import { Fuse } from '../lib.js'; |
| 2 | |
| 3 | import { saveSettingsDebounced } from '../script.js'; |
| 4 | import { callGenericPopup, POPUP_TYPE } from './popup.js'; |
| 5 | import { power_user } from './power-user.js'; |
| 6 | import { getPresetManager } from './preset-manager.js'; |
| 7 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 8 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; |
| 9 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 10 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; |
| 11 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 12 | import { renderTemplateAsync } from './templates.js'; |
| 13 | import { isTrueBoolean, resetScrollHeight } from './utils.js'; |
| 14 | |
| 15 | export let system_prompts = []; |
| 16 | |
| 17 | const $enabled = $('#sysprompt_enabled'); |
| 18 | const $select = $('#sysprompt_select'); |
| 19 | const $content = $('#sysprompt_content'); |
| 20 | const $postHistory = $('#sysprompt_post_history'); |
| 21 | const $contentBlock = $('#SystemPromptBlock'); |
| 22 | |
| 23 | async function migrateSystemPromptFromInstructMode() { |
| 24 | if ('system_prompt' in power_user.instruct) { |
| 25 | const prompt = String(power_user.instruct.system_prompt); |
| 26 | delete power_user.instruct.system_prompt; |
| 27 | power_user.sysprompt.enabled = power_user.instruct.enabled; |
| 28 | power_user.sysprompt.content = prompt; |
| 29 | power_user.sysprompt.post_history = ''; |
| 30 | |
| 31 | const existingPromptName = system_prompts.find(x => x.content === prompt)?.name; |
| 32 | |
| 33 | if (existingPromptName) { |
| 34 | power_user.sysprompt.name = existingPromptName; |
| 35 | } else { |
| 36 | const data = { name: `[Migrated] ${power_user.instruct.preset}`, content: prompt }; |
| 37 | await getPresetManager('sysprompt')?.savePreset(data.name, data); |
| 38 | power_user.sysprompt.name = data.name; |
| 39 | } |
| 40 | |
| 41 | saveSettingsDebounced(); |
| 42 | toastr.info('System prompt settings have been moved from the Instruct Mode.', 'Migration notice', { timeOut: 5000 }); |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | /** |
| 47 | * Loads sysprompt settings from the given data object. |
| 48 | * @param {object} data Settings data object. |
| 49 | */ |
| 50 | export async function loadSystemPrompts(data) { |
| 51 | if (data.sysprompt !== undefined) { |
| 52 | system_prompts = data.sysprompt; |
| 53 | } |
| 54 | |
| 55 | await migrateSystemPromptFromInstructMode(); |
| 56 | toggleSystemPromptDisabledControls(); |
| 57 | |
| 58 | for (const prompt of system_prompts) { |
| 59 | $('<option>').val(prompt.name).text(prompt.name).appendTo($select); |
| 60 | } |
| 61 | |
| 62 | $enabled.prop('checked', power_user.sysprompt.enabled); |
| 63 | $select.val(power_user.sysprompt.name); |
| 64 | $content.val(power_user.sysprompt.content || ''); |
| 65 | $postHistory.val(power_user.sysprompt.post_history || ''); |
| 66 | if (!CSS.supports('field-sizing', 'content')) { |
| 67 | await resetScrollHeight($content); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * Checks if the instruct template has a system prompt and prompts the user to save it as a system prompt. |
| 73 | * @param {string} name Name of the instruct template |
| 74 | * @param {object} template Instruct template object |
| 75 | */ |
| 76 | export async function checkForSystemPromptInInstructTemplate(name, template) { |
| 77 | if (!template || !name || typeof name !== 'string' || typeof template !== 'object') { |
| 78 | return; |
| 79 | } |
| 80 | if ('system_prompt' in template && template.system_prompt) { |
| 81 | const existingName = system_prompts.find(x => x.content === template.system_prompt)?.name; |
| 82 | const html = await renderTemplateAsync('migrateInstructPrompt', { prompt: template.system_prompt, existing: existingName }); |
| 83 | const confirm = await callGenericPopup(html, POPUP_TYPE.CONFIRM); |
| 84 | if (confirm) { |
| 85 | const migratedName = `[Migrated] ${name}`; |
| 86 | const prompt = { name: migratedName, content: template.system_prompt }; |
| 87 | const presetManager = getPresetManager('sysprompt'); |
| 88 | await presetManager.savePreset(migratedName, prompt); |
| 89 | toastr.success(`System prompt "${migratedName}" has been saved.`); |
| 90 | } else { |
| 91 | toastr.info('System prompt has been discarded.'); |
| 92 | } |
| 93 | |
| 94 | delete template.system_prompt; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | function toggleSystemPromptDisabledControls() { |
| 99 | $enabled.parent().find('i').toggleClass('toggleEnabled', !!power_user.sysprompt.enabled); |
| 100 | $contentBlock.toggleClass('disabled', !power_user.sysprompt.enabled); |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Sets the system prompt state. |
| 105 | * @param {boolean} state System prompt state |
| 106 | * @returns {string} Empty string |
| 107 | */ |
| 108 | function setSystemPromptStateCallback(state) { |
| 109 | power_user.sysprompt.enabled = state; |
| 110 | $enabled.prop('checked', state); |
| 111 | toggleSystemPromptDisabledControls(); |
| 112 | saveSettingsDebounced(); |
| 113 | return ''; |
| 114 | } |
| 115 | |
| 116 | function toggleSystemPromptCallback(_args, state) { |
| 117 | if (!state || typeof state !== 'string') { |
| 118 | return String(power_user.sysprompt.enabled); |
| 119 | } |
| 120 | |
| 121 | const newState = isTrueBoolean(state); |
| 122 | setSystemPromptStateCallback(newState); |
| 123 | return String(power_user.sysprompt.enabled); |
| 124 | } |
| 125 | |
| 126 | function selectSystemPromptCallback(args, name) { |
| 127 | if (!power_user.sysprompt.enabled && !isTrueBoolean(args.forceGet)) { |
| 128 | return ''; |
| 129 | } |
| 130 | |
| 131 | if (!name) { |
| 132 | return power_user.sysprompt.name ?? ''; |
| 133 | } |
| 134 | |
| 135 | const quiet = isTrueBoolean(args?.quiet); |
| 136 | const systemPromptNames = system_prompts.map(preset => preset.name); |
| 137 | let foundName = systemPromptNames.find(x => x.toLowerCase() === name.toLowerCase()); |
| 138 | |
| 139 | if (!foundName) { |
| 140 | const fuse = new Fuse(systemPromptNames); |
| 141 | const result = fuse.search(name); |
| 142 | |
| 143 | if (result.length === 0) { |
| 144 | !quiet && toastr.warning(`System prompt "${name}" not found`); |
| 145 | return ''; |
| 146 | } |
| 147 | |
| 148 | foundName = result[0].item; |
| 149 | } |
| 150 | |
| 151 | $select.val(foundName).trigger('change'); |
| 152 | !quiet && toastr.success(`System prompt "${foundName}" selected`); |
| 153 | return foundName; |
| 154 | } |
| 155 | |
| 156 | export function initSystemPrompts() { |
| 157 | $enabled.on('input', function () { |
| 158 | power_user.sysprompt.enabled = !!$(this).prop('checked'); |
| 159 | toggleSystemPromptDisabledControls(); |
| 160 | saveSettingsDebounced(); |
| 161 | }); |
| 162 | |
| 163 | $select.on('change', async function () { |
| 164 | if (!power_user.sysprompt.enabled) { |
| 165 | $enabled.prop('checked', true).trigger('input'); |
| 166 | } |
| 167 | |
| 168 | const name = String($(this).val()); |
| 169 | const prompt = system_prompts.find(p => p.name === name); |
| 170 | if (prompt) { |
| 171 | $content.val(prompt.content || ''); |
| 172 | $postHistory.val(prompt.post_history || ''); |
| 173 | |
| 174 | if (!CSS.supports('field-sizing', 'content')) { |
| 175 | await resetScrollHeight($content); |
| 176 | await resetScrollHeight($postHistory); |
| 177 | } |
| 178 | |
| 179 | power_user.sysprompt.name = name; |
| 180 | power_user.sysprompt.content = prompt.content || ''; |
| 181 | power_user.sysprompt.post_history = prompt.post_history || ''; |
| 182 | } |
| 183 | saveSettingsDebounced(); |
| 184 | }); |
| 185 | |
| 186 | $content.on('input', function () { |
| 187 | power_user.sysprompt.content = String($(this).val()); |
| 188 | saveSettingsDebounced(); |
| 189 | }); |
| 190 | |
| 191 | $postHistory.on('input', function () { |
| 192 | power_user.sysprompt.post_history = String($(this).val()); |
| 193 | saveSettingsDebounced(); |
| 194 | }); |
| 195 | |
| 196 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 197 | name: 'sysprompt', |
| 198 | aliases: ['system-prompt'], |
| 199 | callback: selectSystemPromptCallback, |
| 200 | returns: 'current prompt name', |
| 201 | namedArgumentList: [ |
| 202 | SlashCommandNamedArgument.fromProps({ |
| 203 | name: 'quiet', |
| 204 | description: 'Suppress the toast message on prompt change', |
| 205 | typeList: [ARGUMENT_TYPE.BOOLEAN], |
| 206 | defaultValue: 'false', |
| 207 | enumList: commonEnumProviders.boolean('trueFalse')(), |
| 208 | }), |
| 209 | SlashCommandNamedArgument.fromProps({ |
| 210 | name: 'forceGet', |
| 211 | description: 'Force getting a name even if system prompt is disabled', |
| 212 | typeList: [ARGUMENT_TYPE.BOOLEAN], |
| 213 | defaultValue: 'false', |
| 214 | enumList: commonEnumProviders.boolean('trueFalse')(), |
| 215 | }), |
| 216 | ], |
| 217 | unnamedArgumentList: [ |
| 218 | SlashCommandArgument.fromProps({ |
| 219 | description: 'system prompt name', |
| 220 | typeList: [ARGUMENT_TYPE.STRING], |
| 221 | enumProvider: () => system_prompts.map(x => new SlashCommandEnumValue(x.name, null, enumTypes.enum, enumIcons.preset)), |
| 222 | }), |
| 223 | ], |
| 224 | helpString: ` |
| 225 | <div> |
| 226 | Selects a system prompt by name, using fuzzy search to find the closest match. |
| 227 | Gets the current system prompt if no name is provided and sysprompt is enabled or <code>forceGet=true</code> is passed. |
| 228 | </div> |
| 229 | <div> |
| 230 | <strong>Example:</strong> |
| 231 | <ul> |
| 232 | <li> |
| 233 | <pre><code class="language-stscript">/sysprompt </code></pre> |
| 234 | </li> |
| 235 | </ul> |
| 236 | </div> |
| 237 | `, |
| 238 | })); |
| 239 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 240 | name: 'sysprompt-on', |
| 241 | aliases: ['sysprompt-enable'], |
| 242 | callback: () => setSystemPromptStateCallback(true), |
| 243 | helpString: 'Enables system prompt.', |
| 244 | })); |
| 245 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 246 | name: 'sysprompt-off', |
| 247 | aliases: ['sysprompt-disable'], |
| 248 | callback: () => setSystemPromptStateCallback(false), |
| 249 | helpString: 'Disables system prompt', |
| 250 | })); |
| 251 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 252 | name: 'sysprompt-state', |
| 253 | aliases: ['sysprompt-toggle'], |
| 254 | helpString: 'Gets the current system prompt state. If an argument is provided, it will set the system prompt state.', |
| 255 | unnamedArgumentList: [ |
| 256 | SlashCommandArgument.fromProps({ |
| 257 | description: 'system prompt state', |
| 258 | typeList: [ARGUMENT_TYPE.BOOLEAN], |
| 259 | enumList: commonEnumProviders.boolean('trueFalse')(), |
| 260 | }), |
| 261 | ], |
| 262 | callback: toggleSystemPromptCallback, |
| 263 | })); |
| 264 | } |