mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	Add at-depth position for custom Prompt Manager prompts
This commit is contained in:
		| @@ -4318,23 +4318,44 @@ | ||||
|             <h3>Edit</h3> | ||||
|             <div class="completion_prompt_manager_popup_entry"> | ||||
|                 <form class="completion_prompt_manager_popup_entry_form"> | ||||
|                     <div class="completion_prompt_manager_popup_entry_form_control"> | ||||
|                         <label for="completion_prompt_manager_popup_entry_form_name"> | ||||
|                             <span>Name</span> | ||||
|                         </label> | ||||
|                         <div class="text_muted">A name for this prompt.</div> | ||||
|                         <input id="completion_prompt_manager_popup_entry_form_name" class="text_pole" type="text" name="name" /> | ||||
|                     <div class="flex-container gap10px"> | ||||
|                         <div class="completion_prompt_manager_popup_entry_form_control flex1"> | ||||
|                             <label for="completion_prompt_manager_popup_entry_form_name"> | ||||
|                                 <span>Name</span> | ||||
|                             </label> | ||||
|                             <div class="text_muted">A name for this prompt.</div> | ||||
|                             <input id="completion_prompt_manager_popup_entry_form_name" class="text_pole" type="text" name="name" /> | ||||
|                         </div> | ||||
|                         <div class="completion_prompt_manager_popup_entry_form_control flex1"> | ||||
|                             <label for="completion_prompt_manager_popup_entry_form_role"> | ||||
|                                 <span>Role</span> | ||||
|                             </label> | ||||
|                             <div class="text_muted">To whom this message will be attributed.</div> | ||||
|                             <select id="completion_prompt_manager_popup_entry_form_role" class="text_pole" name="role"> | ||||
|                                 <option value="system">System</option> | ||||
|                                 <option value="user">User</option> | ||||
|                                 <option value="assistant">AI Assistant</option> | ||||
|                             </select> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="completion_prompt_manager_popup_entry_form_control"> | ||||
|                         <label for="completion_prompt_manager_popup_entry_form_role"> | ||||
|                             <span>Role</span> | ||||
|                         </label> | ||||
|                         <div class="text_muted">To whom this message will be attributed.</div> | ||||
|                         <select id="completion_prompt_manager_popup_entry_form_role" class="text_pole" name="role"> | ||||
|                             <option value="system">System</option> | ||||
|                             <option value="user">User</option> | ||||
|                             <option value="assistant">AI Assistant</option> | ||||
|                         </select> | ||||
|                     <div class="flex-container gap10px"> | ||||
|                         <div class="completion_prompt_manager_popup_entry_form_control flex1"> | ||||
|                             <label for="completion_prompt_manager_popup_entry_form_injection_position"> | ||||
|                                 <span>Position</span> | ||||
|                             </label> | ||||
|                             <div class="text_muted">Injection position. Next to other prompts (relative) or in-chat (absolute).</div> | ||||
|                             <select id="completion_prompt_manager_popup_entry_form_injection_position" class="text_pole" name="injection_position"> | ||||
|                                 <option value="0">Relative</option> | ||||
|                                 <option value="1">Absolute</option> | ||||
|                             </select> | ||||
|                         </div> | ||||
|                         <div class="completion_prompt_manager_popup_entry_form_control flex1"> | ||||
|                             <label for="completion_prompt_manager_popup_entry_form_injection_depth"> | ||||
|                                 <span>Depth</span> | ||||
|                             </label> | ||||
|                             <div class="text_muted">Injection depth. 0 = after the last message, 1 = before the last message, etc.</div> | ||||
|                             <input id="completion_prompt_manager_popup_entry_form_injection_depth" class="text_pole" type="number" name="injection_depth" min="0" max="999" value="4" /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="completion_prompt_manager_popup_entry_form_control"> | ||||
|                         <label for="completion_prompt_manager_popup_entry_form_prompt"> | ||||
|   | ||||
| @@ -21,6 +21,16 @@ function debouncePromise(func, delay) { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| const DEFAULT_DEPTH = 4; | ||||
|  | ||||
| /** | ||||
|  * @enum {number} | ||||
|  */ | ||||
| export const INJECTION_POSITION ={ | ||||
|     RELATIVE: 0, | ||||
|     ABSOLUTE: 1, | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Register migrations for the prompt manager when settings are loaded or an Open AI preset is loaded. | ||||
|  */ | ||||
| @@ -60,7 +70,7 @@ const registerPromptManagerMigration = () => { | ||||
|  * Represents a prompt. | ||||
|  */ | ||||
| class Prompt { | ||||
|     identifier; role; content; name; system_prompt; position; | ||||
|     identifier; role; content; name; system_prompt; position; injection_position; injection_depth; | ||||
|  | ||||
|     /** | ||||
|      * Create a new Prompt instance. | ||||
| @@ -72,14 +82,18 @@ class Prompt { | ||||
|      * @param {string} param0.name - The name of the prompt. | ||||
|      * @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt. | ||||
|      * @param {string} param0.position - The position of the prompt in the prompt list. | ||||
|      * @param {number} param0.injection_position - The insert position of the prompt. | ||||
|      * @param {number} param0.injection_depth - The depth of the prompt in the chat. | ||||
|      */ | ||||
|     constructor({ identifier, role, content, name, system_prompt, position } = {}) { | ||||
|     constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position } = {}) { | ||||
|         this.identifier = identifier; | ||||
|         this.role = role; | ||||
|         this.content = content; | ||||
|         this.name = name; | ||||
|         this.system_prompt = system_prompt; | ||||
|         this.position = position; | ||||
|         this.injection_depth = injection_depth; | ||||
|         this.injection_position = injection_position; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -381,6 +395,8 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti | ||||
|         document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name; | ||||
|         document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system'; | ||||
|         document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content; | ||||
|         document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0; | ||||
|         document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH; | ||||
|     } | ||||
|  | ||||
|     // Append prompt to selected character | ||||
| @@ -673,6 +689,8 @@ PromptManagerModule.prototype.updatePromptWithPromptEditForm = function (prompt) | ||||
|     prompt.name = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value; | ||||
|     prompt.role = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value; | ||||
|     prompt.content = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value; | ||||
|     prompt.injection_position = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value); | ||||
|     prompt.injection_depth = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value); | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -1085,10 +1103,14 @@ PromptManagerModule.prototype.loadPromptIntoEditForm = function (prompt) { | ||||
|     const nameField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name'); | ||||
|     const roleField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role'); | ||||
|     const promptField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'); | ||||
|     const injectionPositionField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position'); | ||||
|     const injectionDepthField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth'); | ||||
|  | ||||
|     nameField.value = prompt.name ?? ''; | ||||
|     roleField.value = prompt.role ?? ''; | ||||
|     promptField.value = prompt.content ?? ''; | ||||
|     injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE; | ||||
|     injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH; | ||||
|  | ||||
|     const resetPromptButton = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_reset'); | ||||
|     if (true === prompt.system_prompt) { | ||||
| @@ -1152,10 +1174,14 @@ PromptManagerModule.prototype.clearEditForm = function () { | ||||
|     const nameField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name'); | ||||
|     const roleField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role'); | ||||
|     const promptField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'); | ||||
|     const injectionPositionField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position'); | ||||
|     const injectionDepthField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth'); | ||||
|  | ||||
|     nameField.value = ''; | ||||
|     roleField.selectedIndex = 0; | ||||
|     promptField.value = ''; | ||||
|     injectionPositionField.selectedIndex = 0; | ||||
|     injectionDepthField.value = DEFAULT_DEPTH; | ||||
|  | ||||
|     roleField.disabled = false; | ||||
| } | ||||
| @@ -1435,13 +1461,18 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () { | ||||
|         } | ||||
|  | ||||
|         const encodedName = escapeHtml(prompt.name); | ||||
|         const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE; | ||||
|         const isUserPrompt = !prompt.marker && !prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE; | ||||
|         const isInjectionPrompt = !prompt.marker && prompt.injection_position === INJECTION_POSITION.ABSOLUTE; | ||||
|         listItemHtml += ` | ||||
|             <li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass}" data-pm-identifier="${prompt.identifier}"> | ||||
|                 <span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}"> | ||||
|                     ${prompt.marker ? '<span class="fa-solid fa-thumb-tack" title="Marker"></span>' : ''} | ||||
|                     ${!prompt.marker && prompt.system_prompt ? '<span class="fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''} | ||||
|                     ${!prompt.marker && !prompt.system_prompt ? '<span class="fa-solid fa-user" title="User Prompt"></span>' : ''} | ||||
|                     ${isSystemPrompt ? '<span class="fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''} | ||||
|                     ${isUserPrompt ? '<span class="fa-solid fa-user" title="User Prompt"></span>' : ''} | ||||
|                     ${isInjectionPrompt ? `<span class="fa-solid fa-syringe" title="In-Chat Injection"></span>` : ''} | ||||
|                     ${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${encodedName}</a>` : encodedName} | ||||
|                     ${isInjectionPrompt ? `<small class="prompt-manager-injection-depth">@ ${prompt.injection_depth}</small>` : ''} | ||||
|                 </span> | ||||
|                 <span> | ||||
|                         <span class="prompt_manager_prompt_controls"> | ||||
|   | ||||
| @@ -31,7 +31,8 @@ import { groups, selected_group } from "./group-chats.js"; | ||||
| import { | ||||
|     promptManagerDefaultPromptOrders, | ||||
|     chatCompletionDefaultPrompts, Prompt, | ||||
|     PromptManagerModule as PromptManager | ||||
|     PromptManagerModule as PromptManager, | ||||
|     INJECTION_POSITION, | ||||
| } from "./PromptManager.js"; | ||||
|  | ||||
| import { | ||||
| @@ -321,15 +322,6 @@ function setOpenAIMessages(chat) { | ||||
|         openai_msgs[i] = { "role": role, "content": content, name: name }; | ||||
|         j++; | ||||
|     } | ||||
|  | ||||
|     // Add chat injections, 100 = maximum depth of injection. (Why would you ever need more?) | ||||
|     for (let i = MAX_INJECTION_DEPTH; i >= 0; i--) { | ||||
|         const anchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, i); | ||||
|  | ||||
|         if (anchor && anchor.length) { | ||||
|             openai_msgs.splice(i, 0, { "role": 'system', 'content': anchor.trim() }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| function setOpenAIMessageExamples(mesExamplesArray) { | ||||
| @@ -468,6 +460,34 @@ function formatWorldInfo(value) { | ||||
|     return stringFormat(oai_settings.wi_format, value); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * This function populates the injections in the conversation. | ||||
|  * | ||||
|  * @param {Prompt[]} prompts - Array containing injection prompts. | ||||
|  */ | ||||
| function populationInjectionPrompts(prompts) { | ||||
|     for (let i = MAX_INJECTION_DEPTH; i >= 0; i--) { | ||||
|         // Get prompts for current depth | ||||
|         const depthPrompts = prompts.filter(prompt => prompt.injection_depth === i && prompt.content); | ||||
|  | ||||
|         // Order of priority (most important go lower) | ||||
|         const roles = ['system', 'user', 'assistant']; | ||||
|  | ||||
|         for (const role of roles) { | ||||
|             // Get prompts for current role | ||||
|             const rolePrompts = depthPrompts.filter(prompt => prompt.role === role).map(x => x.content).join('\n'); | ||||
|             // Get extension prompt (only for system role) | ||||
|             const extensionPrompt = role === 'system' ? getExtensionPrompt(extension_prompt_types.IN_CHAT, i) : ''; | ||||
|  | ||||
|             const jointPrompt = [rolePrompts, extensionPrompt].filter(x => x).map(x => x.trim()).join('\n'); | ||||
|  | ||||
|             if (jointPrompt && jointPrompt.length) { | ||||
|                 openai_msgs.splice(i, 0, { "role": role, 'content': jointPrompt }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Populates the chat history of the conversation. | ||||
|  * | ||||
| @@ -477,7 +497,6 @@ function formatWorldInfo(value) { | ||||
|  * @param cyclePrompt | ||||
|  */ | ||||
| function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt = null) { | ||||
|     // Chat History | ||||
|     chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory')); | ||||
|  | ||||
|     let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || ''; | ||||
| @@ -646,14 +665,20 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty | ||||
|  | ||||
|     // Add ordered system and user prompts | ||||
|     const systemPrompts = ['nsfw', 'jailbreak']; | ||||
|     const userPrompts = prompts.collection | ||||
|         .filter((prompt) => false === prompt.system_prompt) | ||||
|     const userRelativePrompts = prompts.collection | ||||
|         .filter((prompt) => false === prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE) | ||||
|         .reduce((acc, prompt) => { | ||||
|             acc.push(prompt.identifier) | ||||
|             return acc; | ||||
|         }, []); | ||||
|     const userAbsolutePrompts = prompts.collection | ||||
|         .filter((prompt) => false === prompt.system_prompt && prompt.injection_position === INJECTION_POSITION.ABSOLUTE) | ||||
|         .reduce((acc, prompt) => { | ||||
|             acc.push(prompt) | ||||
|             return acc; | ||||
|         }, []); | ||||
|  | ||||
|     [...systemPrompts, ...userPrompts].forEach(identifier => addToChatCompletion(identifier)); | ||||
|     [...systemPrompts, ...userRelativePrompts].forEach(identifier => addToChatCompletion(identifier)); | ||||
|  | ||||
|     // Add enhance definition instruction | ||||
|     if (prompts.has('enhanceDefinitions')) addToChatCompletion('enhanceDefinitions'); | ||||
| @@ -697,6 +722,9 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Add in-chat injections | ||||
|     populationInjectionPrompts(userAbsolutePrompts); | ||||
|  | ||||
|     // Decide whether dialogue examples should always be added | ||||
|     if (power_user.pin_examples) { | ||||
|         populateDialogueExamples(prompts, chatCompletion); | ||||
| @@ -1714,7 +1742,7 @@ class ChatCompletion { | ||||
|      * | ||||
|      * @param {Message} message - The message to insert. | ||||
|      * @param {string} identifier - The identifier of the collection where to insert the message. | ||||
|      * @param {string} position - The position at which to insert the message ('start' or 'end'). | ||||
|      * @param {string|number} position - The position at which to insert the message ('start' or 'end'). | ||||
|      */ | ||||
|     insert(message, identifier, position = 'end') { | ||||
|         this.validateMessage(message); | ||||
| @@ -1723,7 +1751,8 @@ class ChatCompletion { | ||||
|         const index = this.findMessageIndex(identifier); | ||||
|         if (message.content) { | ||||
|             if ('start' === position) this.messages.collection[index].collection.unshift(message); | ||||
|             else if ('end' === position) this.messages.collection[index].collection.push(message); | ||||
|             else if ('end' === position) this.messages.collection[index].collection.push(message) | ||||
|             else if (typeof position === 'number') this.messages.collection[index].collection.splice(position, 0, message); | ||||
|  | ||||
|             this.decreaseTokenBudgetBy(message.getTokens()); | ||||
|  | ||||
| @@ -1731,7 +1760,6 @@ class ChatCompletion { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Remove the last item of the collection | ||||
|      * | ||||
|   | ||||
		Reference in New Issue
	
	Block a user