CC/PM: Add generation type trigger prompt filter (#4281) * CC/PM: Add generation type trigger prompt filter Closes #4276 * Fix element cast type * Narrower element types * Change trigger to multiple select * Extract class scope function * Fix control name * Add 'enabled' property to Prompt class and update type annotations in PromptManager methods * Clarify placeholder of multiselect

d9f4028952f1eadf6dfb322c87bb0de96bcf244e

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
7 files changed, +292 -108Ignore whitespace
public/css/promptmanager.css+4 -0
@@ -112,6 +112,10 @@
112112 flex-direction: column;
113113}
114114
115+.completion_prompt_manager_popup_entry_form .select2-container {
116+ margin: 5px 0;
117+}
118+
115119#completion_prompt_manager_popup .completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt) {
116120 flex: 1;
117121 display: flex;
public/index.html+16 -0
@@ -6720,6 +6720,22 @@
67206720 </select>
67216721 <div class="text_muted" data-i18n="To whom this message will be attributed.">To whom this message will be attributed.</div>
67226722 </div>
6723+ <div class="completion_prompt_manager_popup_entry_form_control flex1">
6724+ <label for="completion_prompt_manager_popup_entry_form_injection_trigger">
6725+ <span data-i18n="Triggers">Triggers</span>
6726+ </label>
6727+ <select id="completion_prompt_manager_popup_entry_form_injection_trigger" class="text_pole" name="injection_trigger" multiple>
6728+ <option data-i18n="Normal" value="normal">Normal</option>
6729+ <option data-i18n="Continue" value="continue">Continue</option>
6730+ <option data-i18n="Impersonate" value="impersonate">Impersonate</option>
6731+ <option data-i18n="Swipe" value="swipe">Swipe</option>
6732+ <option data-i18n="Regenerate" value="regenerate">Regenerate</option>
6733+ <option data-i18n="Quiet" value="quiet">Quiet</option>
6734+ </select>
6735+ <div class="text_muted" data-i18n="Filter to specific generation types.">
6736+ Filter to specific generation types.
6737+ </div>
6738+ </div>
67236739 </div>
67246740 <div class="flex-container gap10px">
67256741 <div class="completion_prompt_manager_popup_entry_form_control flex1">
public/script.js+0 -2
@@ -3540,7 +3540,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
35403540 'impersonate',
35413541 'quiet',
35423542 'continue',
3543- 'ask_command',
35443543 ];
35453544 //for normal messages sent from user..
35463545 if ((textareaText != '' || (hasPendingFileAttachment() && !noAttachTypes.includes(type))) && !automatic_trigger && type !== 'quiet' && !dryRun) {
@@ -4380,7 +4379,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
43804379 cyclePrompt: cyclePrompt,
43814380 systemPromptOverride: system,
43824381 jailbreakPromptOverride: jailbreak,
4383- personaDescription: persona,
43844382 messages: oaiMessages,
43854383 messageExamples: oaiMessageExamples,
43864384 }, dryRun);
public/scripts/PromptManager.js+259 -96
@@ -6,7 +6,7 @@ import { event_types, eventSource, is_send_press, main_api, substituteParams } f
66import { is_group_generating } from './group-chats.js';
77import { Message, MessageCollection, TokenHandler } from './openai.js';
88import { power_user } from './power-user.js';
99import { debounce, waitUntilCondition, escapeHtml, uuidv4 } from './utils.js';
1010import { debounce_timeout } from './constants.js';
1111import { renderTemplateAsync } from './templates.js';
1212import { Popup } from './popup.js';
@@ -29,6 +29,7 @@ function debouncePromise(func, delay) {
2929}
3030
3131const DEFAULT_DEPTH = 4;
32+const DEFAULT_ORDER = 100;
3233
3334/**
3435 * @enum {number}
@@ -77,25 +78,108 @@ const registerPromptManagerMigration = () => {
7778 * Represents a prompt.
7879 */
7980class Prompt {
80- identifier; role; content; name; system_prompt; position; injection_position; injection_depth; injection_order; forbid_overrides; extension;
81+ /**
82+ * Indicates if the prompt is enabled.
83+ * @type {boolean}
84+ */
85+ enabled;
86+
87+ /**
88+ * Unique identifier for the prompt.
89+ * @type {string}
90+ */
91+ identifier;
92+
93+ /**
94+ * Role of the prompt, e.g., 'system', 'user', etc.
95+ * @type {string}
96+ */
97+ role;
98+
99+ /**
100+ * Content of the prompt.
101+ * @type {string}
102+ */
103+ content;
104+
105+ /**
106+ * Display name of the prompt.
107+ * @type {string}
108+ */
109+ name;
110+
111+ /**
112+ * Indicates if the prompt is a system prompt.
113+ * @type {boolean}
114+ */
115+ system_prompt;
116+
117+ /**
118+ * Position of the prompt in the prompt list.
119+ * @type {string|number}
120+ */
121+ position;
122+
123+ /**
124+ * Inject position of the prompt (relative = 0 or in-chat = 1)
125+ * @type {number}
126+ */
127+ injection_position;
128+
129+ /**
130+ * Depth of the prompt in the chat.
131+ * @type {number}
132+ */
133+ injection_depth;
134+
135+ /**
136+ * Order of the prompt in the chat.
137+ * @type {number}
138+ */
139+ injection_order;
140+
141+ /**
142+ * Indicates if the prompt should not be overridden.
143+ * @type {boolean}
144+ */
145+ forbid_overrides;
146+
147+ /**
148+ * Prompt is added by an extension.
149+ * @type {boolean}
150+ */
151+ extension;
152+
153+ /**
154+ * A list of generation type triggers for the prompt injection.
155+ * @type {string[]}
156+ */
157+ injection_trigger;
158+
159+ /**
160+ * Indicates if the prompt is a marker prompt.
161+ * @type {boolean}
162+ */
163+ marker;
81164
82165 /**
83166 * Create a new Prompt instance.
84167 *
85168 * @param {Object} [param0] - Object containing the properties of the prompt.
86169 * @param {string} [param0.identifier] - The unique identifier of the prompt.
87170 * @param {string} [param0.role] - The role associated with the prompt.
88171 * @param {string} [param0.content] - The content of the prompt.
89172 * @param {string} [param0.name] - The name of the prompt.
90173 * @param {boolean} [param0.system_prompt] - Indicates if the prompt is a system prompt.
91174 * @param {string|number} [param0.position] - The position of the prompt in the prompt list.
92175 * @param {number} [param0.injection_position] - The insert position of the prompt.
93176 * @param {number} [param0.injection_depth] - The depth of the prompt in the chat.
94177 * @param {number} [param0.injection_order] - The order of the prompt in the chat.
178+ * @param {string[]} [param0.injection_trigger] - The generation type trigger for the prompt injection.
95179 * @param {boolean} [param0.forbid_overrides] - Indicates if the prompt should not be overridden.
96180 * @param {boolean} [param0.extension] - Prompt is added by an extension.
97181 */
98182 constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides, extension, injection_order, injection_trigger } = {}) {
99183 this.identifier = identifier;
100184 this.role = role;
101185 this.content = content;
@@ -106,7 +190,8 @@ class Prompt {
106190 this.injection_position = injection_position;
107191 this.forbid_overrides = forbid_overrides;
108192 this.extension = extension ?? false;
109193 this.injection_order = injection_order ?? 100DEFAULT_ORDER;
194+ this.injection_trigger = injection_trigger ?? [];
110195 }
111196}
112197
@@ -114,7 +199,16 @@ class Prompt {
114199 * Representing a collection of prompts.
115200 */
116201export class PromptCollection {
202+ /**
203+ * List of Prompts in the collection.
204+ * @type {Prompt[]}
205+ */
117206 collection = [];
207+
208+ /**
209+ * List of identifiers of prompts that have been overridden.
210+ * @type {string[]}
211+ */
118212 overriddenPrompts = [];
119213
120214 /**
@@ -129,7 +223,7 @@ export class PromptCollection {
129223 /**
130224 * Checks if the provided instances are of the Prompt class.
131225 *
132226 * @param {...anyPrompt} prompts - Instances to check.
133227 * @throws Will throw an error if one or more instances are not of the Prompt class.
134228 */
135229 checkPromptInstance(...prompts) {
@@ -191,6 +285,12 @@ export class PromptCollection {
191285 return this.index(identifier) !== -1;
192286 }
193287
288+ /**
289+ * Overrides a prompt at a specific position in the collection.
290+ *
291+ * @param {Prompt} prompt - The Prompt instance to override.
292+ * @param {number} position - The position in the collection to override the Prompt instance.
293+ */
194294 override(prompt, position) {
195295 this.set(prompt, position);
196296 this.overriddenPrompts.push(prompt.identifier);
@@ -274,7 +374,7 @@ class PromptManager {
274374 this.tryGenerate = async () => { };
275375
276376 /** Called to persist the configuration, must return a promise */
277377 this.saveServiceSettings = () => { return Promise.resolve(); };
278378
279379 /** Toggle prompt button click */
280380 this.handleToggle = () => { };
@@ -444,32 +544,51 @@ class PromptManager {
444544 break;
445545 }
446546
447547 const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name);
448548 const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system');
449549 const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content ?? '');
450550 const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0);
451551 const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH);
452552 const injectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order').value = prompt.injection_order ?? 100);
453- document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
553+ const injectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_trigger'));
454- document.getElementById(this.configuration.prefix + 'prompt_manager_order_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
554+ const depthBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'));
455- document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;
555+ const orderBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'));
456- document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';
556+ const forbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides'));
457- document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').disabled = prompt.marker ?? false;
557+ const forbidOverridesBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block'));
458- document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block').style.display = isPulledPrompt ? '' : 'none';
558+ const entrySourceBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block'));
559+ const entrySource = /** @type {HTMLSpanElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'));
560+
561+ nameField.value = prompt.name;
562+ roleField.value = 'system';
563+ promptField.value = prompt.content ?? '';
564+ injectionPositionField.value = (prompt.injection_position ?? 0).toString();
565+ injectionDepthField.value = (prompt.injection_depth ?? DEFAULT_DEPTH).toString();
566+ injectionOrderField.value = (prompt.injection_order ?? DEFAULT_ORDER).toString();
567+ Array.from(injectionTriggerField.options).forEach(option => {
568+ option.selected = false;
569+ });
570+ injectionTriggerField.dispatchEvent(new Event('change', { bubbles: true }));
571+ depthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
572+ orderBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
573+ forbidOverridesField.checked = prompt.forbid_overrides ?? false;
574+ forbidOverridesBlock.style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';
575+ promptField.disabled = prompt.marker ?? false;
576+ entrySourceBlock.style.display = isPulledPrompt ? '' : 'none';
459577
460578 if (isPulledPrompt) {
461579 const sourceName = this.promptSources[promptId];
462- document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source').textContent = sourceName;
580+ entrySource.textContent = sourceName;
463581 }
464582
465583 if (!this.systemPrompts.includes(promptId)) {
466584 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position')injectionPositionField.removeAttribute('disabled');
467585 }
468586 };
469587
470588 // Append prompt to selected character
471589 this.handleAppendPrompt = (event) => {
472590 const promptIDappendPromptFooter = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value);
591+ const promptID = appendPromptFooter.value;
473592 const prompt = this.getPromptById(promptID);
474593
475594 if (prompt) {
@@ -483,7 +602,8 @@ class PromptManager {
483602 this.handleDeletePrompt = async (event) => {
484603 Popup.show.confirm(t`Are you sure you want to delete this prompt?`, null).then((userChoice) => {
485604 if (!userChoice) return;
486605 const promptIDappendPromptFooter = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value);
606+ const promptID = appendPromptFooter.value;
487607 const prompt = this.getPromptById(promptID);
488608
489609 if (prompt && true === this.isPromptDeletionAllowed(prompt)) {
@@ -566,6 +686,7 @@ class PromptManager {
566686 fileOpener.accept = '.json';
567687
568688 fileOpener.addEventListener('change', (event) => {
689+ if (!(event.target instanceof HTMLInputElement)) return;
569690 const file = event.target.files[0];
570691 if (!file) return;
571692
@@ -575,7 +696,7 @@ class PromptManager {
575696 const fileContent = event.target.result;
576697
577698 try {
578699 const data = JSON.parse(fileContent.toString());
579700 this.import(data);
580701 } catch (err) {
581702 toastr.error(t`An error occurred while importing prompts. More info available in console.`);
@@ -615,7 +736,7 @@ class PromptManager {
615736
616737 // Update edit form if present
617738 // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
618739 const popupEditFormPrompt = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'));
619740 if (popupEditFormPrompt.offsetParent) {
620741 popupEditFormPrompt.value = prompt.content;
621742 }
@@ -673,6 +794,7 @@ class PromptManager {
673794
674795 // Trigger re-render when token settings are changed
675796 document.getElementById('openai_max_context').addEventListener('change', (event) => {
797+ if (!(event.target instanceof HTMLInputElement)) return;
676798 this.serviceSettings.openai_max_context = event.target.value;
677799 if (this.activeCharacter) this.renderDebounced();
678800 });
@@ -778,23 +900,33 @@ class PromptManager {
778900
779901 /**
780902 * Update a prompt with the values from the HTML form.
781903 * @param {objectPartial<Prompt>} prompt - The prompt to be updated.
782904 * @returns {void}
783905 */
784906 updatePromptWithPromptEditForm(prompt) {
785907 prompt.nameconst nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value);
786908 prompt.roleconst roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value);
787909 prompt.contentconst promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value);
788910 prompt.injection_positionconst injectionPositionField = Number/** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value);
789911 prompt.injection_depthconst injectionDepthField = Number/** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value);
790912 prompt.injection_orderconst injectionOrderField = Number/** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order').value);
791913 prompt.forbid_overridesconst injectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overridesprompt_manager_popup_entry_form_injection_trigger').checked);
914+ const forbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides'));
915+
916+ prompt.name = nameField.value;
917+ prompt.role = roleField.value;
918+ prompt.content = promptField.value;
919+ prompt.injection_position = Number(injectionPositionField.value);
920+ prompt.injection_depth = Number(injectionDepthField.value);
921+ prompt.injection_order = Number(injectionOrderField.value);
922+ prompt.injection_trigger = Array.from(injectionTriggerField.selectedOptions).map(option => option.value);
923+ prompt.forbid_overrides = forbidOverridesField.checked;
792924 }
793925
794926 /**
795927 * Find a prompt by its identifier and update it with the provided object.
796928 * @param {string} identifier - The identifier of the prompt.
797929 * @param {objectPrompt} updatePrompt - An object with properties to be updated in the prompt.
798930 * @returns {void}
799931 */
800932 updatePromptByIdentifier(identifier, updatePrompt) {
@@ -804,7 +936,7 @@ class PromptManager {
804936
805937 /**
806938 * Iterate over an array of prompts, find each one by its identifier, and update them with the provided data.
807939 * @param {objectPrompt[]} prompts - An array of prompt updates.
808940 * @returns {void}
809941 */
810942 updatePrompts(prompts) {
@@ -826,7 +958,7 @@ class PromptManager {
826958
827959 /**
828960 * Add a prompt to the current character's prompt list.
829961 * @param {objectPrompt} prompt - The prompt to be added.
830962 * @param {object} character - The character whose prompt list will be updated.
831963 * @returns {void}
832964 */
@@ -839,7 +971,7 @@ class PromptManager {
839971
840972 /**
841973 * Remove a prompt from the current character's prompt list.
842974 * @param {objectPrompt} prompt - The prompt to be removed.
843975 * @param {object} character - The character whose prompt list will be updated.
844976 * @returns {void}
845977 */
@@ -853,7 +985,7 @@ class PromptManager {
853985
854986 /**
855987 * Create a new prompt and add it to the list of prompts.
856988 * @param {objectPartial<Prompt>} prompt - The prompt to be added.
857989 * @param {string} identifier - The identifier for the new prompt.
858990 * @returns {void}
859991 */
@@ -931,7 +1063,7 @@ class PromptManager {
9311063
9321064 /**
9331065 * Check whether a prompt can be inspected.
9341066 * @param {objectPrompt} prompt - The prompt to check.
9351067 * @returns {boolean} True if the prompt is a marker, false otherwise.
9361068 */
9371069 isPromptInspectionAllowed(prompt) {
@@ -940,7 +1072,7 @@ class PromptManager {
9401072
9411073 /**
9421074 * Check whether a prompt can be deleted. System prompts cannot be deleted.
9431075 * @param {objectPrompt} prompt - The prompt to check.
9441076 * @returns {boolean} True if the prompt can be deleted, false otherwise.
9451077 */
9461078 isPromptDeletionAllowed(prompt) {
@@ -949,7 +1081,7 @@ class PromptManager {
9491081
9501082 /**
9511083 * Check whether a prompt can be edited.
9521084 * @param {objectPrompt} prompt - The prompt to check.
9531085 * @returns {boolean} True if the prompt can be edited, false otherwise.
9541086 */
9551087 isPromptEditAllowed(prompt) {
@@ -966,7 +1098,7 @@ class PromptManager {
9661098
9671099 /**
9681100 * Check whether a prompt can be toggled on or off.
9691101 * @param {objectPrompt} prompt - The prompt to check.
9701102 * @returns {boolean} True if the prompt can be deleted, false otherwise.
9711103 */
9721104 isPromptToggleAllowed(prompt) {
@@ -1062,7 +1194,7 @@ class PromptManager {
10621194
10631195 /**
10641196 * Get the prompts for a specific character. Can be filtered to only include enabled prompts.
10651197 * @returns {objectPrompt[]} The prompts for the character.
10661198 * @param character
10671199 * @param onlyEnabled
10681200 */
@@ -1075,7 +1207,7 @@ class PromptManager {
10751207 /**
10761208 * Get the order of prompts for a specific character. If no character is specified or the character doesn't have a prompt list, an empty array is returned.
10771209 * @param {object|null} character - The character to get the prompt list for.
10781210 * @returns {objectPartial<Prompt>[]} The prompt list for the character, or an empty array.
10791211 */
10801212 getPromptOrderForCharacter(character) {
10811213 return !character ? [] : (this.serviceSettings.prompt_order.find(list => String(list.character_id) === String(character.id))?.order ?? []);
@@ -1083,7 +1215,7 @@ class PromptManager {
10831215
10841216 /**
10851217 * Set the prompts for the manager.
10861218 * @param {objectPartial<Prompt>[]} prompts - The prompts to be set.
10871219 * @returns {void}
10881220 */
10891221 setPrompts(prompts) {
@@ -1125,7 +1257,7 @@ class PromptManager {
11251257 /**
11261258 * Finds and returns a prompt by its identifier.
11271259 * @param {string} identifier - Identifier of the prompt
11281260 * @returns {ObjectPrompt|null} The prompt object, or null if not found
11291261 */
11301262 getPromptById(identifier) {
11311263 return this.serviceSettings.prompts.find(item => item && item.identifier === identifier) ?? null;
@@ -1143,9 +1275,9 @@ class PromptManager {
11431275 /**
11441276 * Enriches a generic object, creating a new prompt object in the process
11451277 *
11461278 * @param {ObjectPartial<Prompt>} prompt - Prompt object
11471279 * @param original
11481280 * @returns {ObjectPrompt} An object with "role" and "content" properties
11491281 */
11501282 preparePrompt(prompt, original = null) {
11511283 const groupMembers = this.getActiveGroupCharacters();
@@ -1184,7 +1316,7 @@ class PromptManager {
11841316
11851317 const debouncedSaveServiceSettings = debouncePromise(() => this.saveServiceSettings(), 300);
11861318
11871319 const textarea = /** @type {HTMLTextAreaElement} */(document.getElementById(textareaIdentifier));
11881320 textarea.addEventListener('blur', () => {
11891321 prompt.content = textarea.value;
11901322 this.updatePromptByIdentifier(identifier, prompt);
@@ -1193,9 +1325,15 @@ class PromptManager {
11931325
11941326 }
11951327
1328+ /**
1329+ * Updates the quick edit textarea for a specific prompt.
1330+ * @param {string} identifier - The identifier of the prompt.
1331+ * @param {Prompt} prompt - The updated prompt object.
1332+ * @returns {string} The ID of the updated textarea element.
1333+ */
11961334 updateQuickEdit(identifier, prompt) {
11971335 const elementId = `${identifier}_prompt_quick_edit_textarea`;
11981336 const textarea = /** @type {HTMLTextAreaElement} */(document.getElementById(elementId));
11991337 textarea.value = prompt.content;
12001338
12011339 return elementId;
@@ -1220,30 +1358,35 @@ class PromptManager {
12201358
12211359 /**
12221360 * Loads a given prompt into the edit form fields.
12231361 * @param {ObjectPartial<Prompt>} prompt - Prompt object with properties 'name', 'role', 'content', and 'system_prompt'
12241362 */
12251363 loadPromptIntoEditForm(prompt) {
12261364 const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name'));
12271365 const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role'));
12281366 const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'));
12291367 const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position'));
12301368 const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth'));
12311369 const injectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order'));
12321370 const injectionDepthBlockinjectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_depth_blockprompt_manager_popup_entry_form_injection_trigger'));
12331371 const injectionOrderBlockinjectionDepthBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_order_blockprompt_manager_depth_block'));
12341372 const forbidOverridesFieldinjectionOrderBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overridesprompt_manager_order_block'));
12351373 const forbidOverridesBlockforbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_blockprompt_manager_popup_entry_form_forbid_overrides'));
12361374 const entrySourceBlockforbidOverridesBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_blockprompt_manager_forbid_overrides_block'));
12371375 const entrySourceentrySourceBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_sourceprompt_manager_popup_entry_source_block'));
1376+ const entrySource = /** @type {HTMLSpanElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'));
12381377 const isPulledPrompt = Object.keys(this.promptSources).includes(prompt.identifier);
12391378
12401379 nameField.value = prompt.name ?? '';
12411380 roleField.value = prompt.role || 'system';
12421381 promptField.value = prompt.content ?? '';
12431382 promptField.disabled = prompt.marker ?? false;
12441383 injectionPositionField.value = (prompt.injection_position ?? INJECTION_POSITION.RELATIVE).toString();
12451384 injectionDepthField.value = (prompt.injection_depth ?? DEFAULT_DEPTH).toString();
12461385 injectionOrderField.value = (prompt.injection_order ?? 100DEFAULT_ORDER).toString();
1386+ Array.from(injectionTriggerField.options).forEach(option => {
1387+ option.selected = Array.isArray(prompt.injection_trigger) && prompt.injection_trigger.includes(option.value);
1388+ });
1389+ injectionTriggerField.dispatchEvent(new Event('change', { bubbles: true }));
12471390 injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
12481391 injectionOrderBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
12491392 injectionPositionField.removeAttribute('disabled');
@@ -1335,17 +1478,19 @@ class PromptManager {
13351478 const editArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_edit');
13361479 editArea.style.display = 'none';
13371480
13381481 const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name'));
13391482 const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role'));
13401483 const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'));
13411484 const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position'));
13421485 const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth'));
13431486 const injectionDepthBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'));
13441487 const injectionOrderBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'));
13451488 const forbidOverridesFieldinjectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overridesprompt_manager_popup_entry_form_injection_order'));
13461489 const forbidOverridesBlockinjectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_blockprompt_manager_popup_entry_form_injection_trigger'));
13471490 const entrySourceBlockforbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_blockprompt_manager_popup_entry_form_forbid_overrides'));
13481491 const entrySourceforbidOverridesBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_sourceprompt_manager_forbid_overrides_block'));
1492+ const entrySourceBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block'));
1493+ const entrySource = /** @type {HTMLSpanElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'));
13491494
13501495 nameField.value = '';
13511496 roleField.selectedIndex = 0;
@@ -1353,7 +1498,9 @@ class PromptManager {
13531498 promptField.disabled = false;
13541499 injectionPositionField.selectedIndex = 0;
13551500 injectionPositionField.removeAttribute('disabled');
13561501 injectionDepthField.value = DEFAULT_DEPTH.toString();
1502+ injectionOrderField.value = DEFAULT_ORDER.toString();
1503+ injectionTriggerField.value = '';
13571504 injectionDepthBlock.style.visibility = 'unset';
13581505 injectionOrderBlock.style.visibility = 'unset';
13591506 forbidOverridesBlock.style.visibility = 'unset';
@@ -1373,22 +1520,30 @@ class PromptManager {
13731520
13741521 /**
13751522 * Returns a full list of prompts whose content markers have been substituted.
1523+ * @param {string} generationType - The type of generation, e.g., 'continue' or 'quiet'.
13761524 * @returns {PromptCollection} A PromptCollection object
13771525 */
13781526 getPromptCollection(generationType) {
1527+ generationType = String(generationType || 'normal').toLowerCase().trim();
1528+ const promptCollection = new PromptCollection();
13791529 const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
13801530
1381- const promptCollection = new PromptCollection();
13821531 promptOrder.forEach(entry => {
13831532 ifconst (trueprompt === this.getPromptById(entry.enabledidentifier) {;
13841533 const promptallowedTrigger = entry.enabled && this.getPromptByIdshouldTrigger(entry.identifierprompt, generationType);
1385- if (prompt) promptCollection.add(this.preparePrompt(prompt));
1534+
1386- } else if (!entry.enabled && entry.identifier === 'main') {
1535+ if (!prompt) {
1536+ return;
1537+ }
1538+
1539+ if (allowedTrigger) {
1540+ promptCollection.add(this.preparePrompt(prompt));
1541+ } else if (entry.identifier === 'main') {
13871542 // Some extensions require main prompt to be present for relative inserts.
13881543 // So we make a GMO-free vegan replacement.
13891544 const promptreplacementPrompt = structuredClone(this.getPromptById(entry.identifier)prompt);
13901545 promptreplacementPrompt.content = '';
13911546 if (prompt) promptCollection.add(this.preparePrompt(promptreplacementPrompt));
13921547 }
13931548 });
13941549
@@ -1396,6 +1551,18 @@ class PromptManager {
13961551 }
13971552
13981553 /**
1554+ * Checks if a prompt should be triggered based on its injection triggers.
1555+ * @param {Prompt} prompt - The prompt to check.
1556+ * @param {string} generationType - The type of generation to check against.
1557+ * @returns {boolean} True if the prompt should be triggered, false otherwise.
1558+ */
1559+ shouldTrigger(prompt, generationType) {
1560+ if (!Array.isArray(prompt?.injection_trigger)) return true;
1561+ if (!prompt.injection_trigger.length) return true;
1562+ return prompt.injection_trigger.includes(generationType);
1563+ }
1564+
1565+ /**
13991566 * Setter for messages property
14001567 *
14011568 * @param {import('./openai.js').MessageCollection} messages
@@ -1807,11 +1974,7 @@ class PromptManager {
18071974 * @returns {string} A string representation of an uuid4
18081975 */
18091976 getUuidv4() {
1810- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
1977+ return uuidv4();
1811- let r = Math.random() * 16 | 0,
1812- v = c === 'x' ? r : (r & 0x3 | 0x8);
1813- return v.toString(16);
1814- });
18151978 }
18161979
18171980 /**
public/scripts/group-chats.js+1 -1
@@ -921,7 +921,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
921921 for (const chId of activatedMembers) {
922922 throwIfAborted();
923923 deactivateSendButtons();
924- const generateType = type == 'swipe' || type == 'impersonate' || type == 'quiet' || type == 'continue' ? type : 'group_chat';
925924 setCharacterId(chId);
926925 setCharacterName(characters[chId].name);
927926 if (power_user.show_group_chat_queue) {
@@ -930,6 +929,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
930929 await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId);
931930
932931 // Wait for generation to finish
932+ const generateType = ['swipe', 'impersonate', 'quiet', 'continue'].includes(type) ? type : 'normal';
933933 textResult = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
934934 let messageChunk = textResult?.messageChunk;
935935
public/scripts/openai.js+11 -8
@@ -1255,12 +1255,12 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
12551255 * @param {string} options.quietPrompt - The quiet prompt to be used in the conversation.
12561256 * @param {string} options.bias - The bias to be added in the conversation.
12571257 * @param {Object} options.extensionPrompts - An object containing additional prompts.
12581258 * @param {string} options.systemPromptOverride - Character card override of the main prompt
12591259 * @param {string} options.jailbreakPromptOverride - Character card override of the PHI
12601260 * @param {string} options.personaDescriptiontype - The type of generation that triggered the prompt
12611261 * @returns {Promise<Object>} prompts - The prepared and merged system and user-defined prompts.
12621262 */
12631263async function preparePromptsForChatCompletion({ scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescriptiontype }) {
12641264 const scenarioText = scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : (scenario || '');
12651265 const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : (charPersonality || '');
12661266 const groupNudge = substituteParams(oai_settings.group_nudge_prompt);
@@ -1363,7 +1363,7 @@ async function preparePromptsForChatCompletion({ scenario, charPersonality, name
13631363 }
13641364
13651365 // This is the prompt order defined by the user
13661366 const prompts = promptManager.getPromptCollection(type);
13671367
13681368 // Merge system prompts with prompt manager prompts
13691369 systemPrompts.forEach(prompt => {
@@ -1429,7 +1429,6 @@ async function preparePromptsForChatCompletion({ scenario, charPersonality, name
14291429 * @param {string} content.cyclePrompt - The last prompt used for chat message continuation.
14301430 * @param {string} content.systemPromptOverride - The system prompt override.
14311431 * @param {string} content.jailbreakPromptOverride - The jailbreak prompt override.
1432- * @param {string} content.personaDescription - The persona description.
14331432 * @param {object} content.extensionPrompts - An array of additional prompts.
14341433 * @param {object[]} content.messages - An array of messages to be used as chat history.
14351434 * @param {string[]} content.messageExamples - An array of messages to be used as dialogue examples.
@@ -1451,7 +1450,6 @@ export async function prepareOpenAIMessages({
14511450 cyclePrompt,
14521451 systemPromptOverride,
14531452 jailbreakPromptOverride,
1454- personaDescription,
14551453 messages,
14561454 messageExamples,
14571455}, dryRun) {
@@ -1478,7 +1476,7 @@ export async function prepareOpenAIMessages({
14781476 extensionPrompts,
14791477 systemPromptOverride,
14801478 jailbreakPromptOverride,
14811479 personaDescriptiontype,
14821480 });
14831481
14841482 // Fill the chat completion with as much context as the budget allows
@@ -6429,6 +6427,11 @@ export function initOpenAI() {
64296427 width: '100%',
64306428 templateResult: getAimlapiModelTemplate,
64316429 });
6430+ $('#completion_prompt_manager_popup_entry_form_injection_trigger').select2({
6431+ placeholder: t`All types (default)`,
6432+ width: '100%',
6433+ closeOnSelect: false,
6434+ });
64326435 }
64336436
64346437 $('#openrouter_providers_chat').on('change', function () {
public/scripts/slash-commands.js+1 -1
@@ -3947,7 +3947,7 @@ async function askCharacter(args, text) {
39473947 try {
39483948 eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter);
39493949 toastr.info(`Asking ${name} something...`);
39503950 askResult = await Generate('ask_commandnormal');
39513951 } catch (error) {
39523952 restoreCharacter();
39533953 console.error('Error running /ask command', error);