Blame Raw
· · · 264 lines (9.9 KB)
0 contributors
1import { Fuse } from '../lib.js';
2
3import { saveSettingsDebounced } from '../script.js';
4import { callGenericPopup, POPUP_TYPE } from './popup.js';
5import { power_user } from './power-user.js';
6import { getPresetManager } from './preset-manager.js';
7import { SlashCommand } from './slash-commands/SlashCommand.js';
8import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
9import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
10import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
11import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
12import { renderTemplateAsync } from './templates.js';
13import { isTrueBoolean, resetScrollHeight } from './utils.js';
14
15export let system_prompts = [];
16
17const $enabled = $('#sysprompt_enabled');
18const $select = $('#sysprompt_select');
19const $content = $('#sysprompt_content');
20const $postHistory = $('#sysprompt_post_history');
21const $contentBlock = $('#SystemPromptBlock');
22
23async 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 */
50export 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 */
76export 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
98function 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 */
108function setSystemPromptStateCallback(state) {
109 power_user.sysprompt.enabled = state;
110 $enabled.prop('checked', state);
111 toggleSystemPromptDisabledControls();
112 saveSettingsDebounced();
113 return '';
114}
115
116function 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
126function 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
156export 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}