Spaces:
Paused
Paused
| ; | |
| import { DOMPurify } from '../lib.js'; | |
| import { event_types, eventSource, is_send_press, main_api, substituteParams } from '../script.js'; | |
| import { is_group_generating } from './group-chats.js'; | |
| import { Message, MessageCollection, TokenHandler } from './openai.js'; | |
| import { power_user } from './power-user.js'; | |
| import { debounce, waitUntilCondition, escapeHtml } from './utils.js'; | |
| import { debounce_timeout } from './constants.js'; | |
| import { renderTemplateAsync } from './templates.js'; | |
| import { Popup } from './popup.js'; | |
| import { t } from './i18n.js'; | |
| import { isMobile } from './RossAscends-mods.js'; | |
| function debouncePromise(func, delay) { | |
| let timeoutId; | |
| return (...args) => { | |
| clearTimeout(timeoutId); | |
| return new Promise((resolve) => { | |
| timeoutId = setTimeout(() => { | |
| const result = func(...args); | |
| resolve(result); | |
| }, 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. | |
| */ | |
| const registerPromptManagerMigration = () => { | |
| const migrate = (settings, savePreset = null, presetName = null) => { | |
| if ('Default' === presetName) return; | |
| if (settings.main_prompt || settings.nsfw_prompt || settings.jailbreak_prompt) { | |
| console.log('Running prompt manager configuration migration'); | |
| if (settings.prompts === undefined || settings.prompts.length === 0) settings.prompts = structuredClone(chatCompletionDefaultPrompts.prompts); | |
| const findPrompt = (identifier) => settings.prompts.find(prompt => identifier === prompt.identifier); | |
| if (settings.main_prompt) { | |
| findPrompt('main').content = settings.main_prompt; | |
| delete settings.main_prompt; | |
| } | |
| if (settings.nsfw_prompt) { | |
| findPrompt('nsfw').content = settings.nsfw_prompt; | |
| delete settings.nsfw_prompt; | |
| } | |
| if (settings.jailbreak_prompt) { | |
| findPrompt('jailbreak').content = settings.jailbreak_prompt; | |
| delete settings.jailbreak_prompt; | |
| } | |
| if (savePreset && presetName) savePreset(presetName, settings, false); | |
| } | |
| }; | |
| eventSource.on(event_types.SETTINGS_LOADED_BEFORE, settings => migrate(settings)); | |
| eventSource.on(event_types.OAI_PRESET_CHANGED_BEFORE, event => migrate(event.preset, event.savePreset, event.presetName)); | |
| }; | |
| /** | |
| * Represents a prompt. | |
| */ | |
| class Prompt { | |
| identifier; role; content; name; system_prompt; position; injection_position; injection_depth; injection_order; forbid_overrides; extension; | |
| /** | |
| * Create a new Prompt instance. | |
| * | |
| * @param {Object} param0 - Object containing the properties of the prompt. | |
| * @param {string} param0.identifier - The unique identifier of the prompt. | |
| * @param {string} param0.role - The role associated with the prompt. | |
| * @param {string} param0.content - The content of the 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. | |
| * @param {number} [param0.injection_order] - The order of the prompt in the chat. | |
| * @param {boolean} [param0.forbid_overrides] - Indicates if the prompt should not be overridden. | |
| * @param {boolean} [param0.extension] - Prompt is added by an extension. | |
| */ | |
| constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides, extension, injection_order } = {}) { | |
| 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; | |
| this.forbid_overrides = forbid_overrides; | |
| this.extension = extension ?? false; | |
| this.injection_order = injection_order ?? 100; | |
| } | |
| } | |
| /** | |
| * Representing a collection of prompts. | |
| */ | |
| export class PromptCollection { | |
| collection = []; | |
| overriddenPrompts = []; | |
| /** | |
| * Create a new PromptCollection instance. | |
| * | |
| * @param {...Prompt} prompts - An array of Prompt instances. | |
| */ | |
| constructor(...prompts) { | |
| this.add(...prompts); | |
| } | |
| /** | |
| * Checks if the provided instances are of the Prompt class. | |
| * | |
| * @param {...any} prompts - Instances to check. | |
| * @throws Will throw an error if one or more instances are not of the Prompt class. | |
| */ | |
| checkPromptInstance(...prompts) { | |
| for (let prompt of prompts) { | |
| if (!(prompt instanceof Prompt)) { | |
| throw new Error('Only Prompt instances can be added to PromptCollection'); | |
| } | |
| } | |
| } | |
| /** | |
| * Adds new Prompt instances to the collection. | |
| * | |
| * @param {...Prompt} prompts - An array of Prompt instances. | |
| */ | |
| add(...prompts) { | |
| this.checkPromptInstance(...prompts); | |
| this.collection.push(...prompts); | |
| } | |
| /** | |
| * Sets a Prompt instance at a specific position in the collection. | |
| * | |
| * @param {Prompt} prompt - The Prompt instance to set. | |
| * @param {number} position - The position in the collection to set the Prompt instance. | |
| */ | |
| set(prompt, position) { | |
| this.checkPromptInstance(prompt); | |
| this.collection[position] = prompt; | |
| } | |
| /** | |
| * Retrieves a Prompt instance from the collection by its identifier. | |
| * | |
| * @param {string} identifier - The identifier of the Prompt instance to retrieve. | |
| * @returns {Prompt} The Prompt instance with the provided identifier, or undefined if not found. | |
| */ | |
| get(identifier) { | |
| return this.collection.find(prompt => prompt.identifier === identifier); | |
| } | |
| /** | |
| * Retrieves the index of a Prompt instance in the collection by its identifier. | |
| * | |
| * @param {string} identifier - The identifier of the Prompt instance to find. | |
| * @returns {number} The index of the Prompt instance in the collection, or -1 if not found. | |
| */ | |
| index(identifier) { | |
| return this.collection.findIndex(prompt => prompt.identifier === identifier); | |
| } | |
| /** | |
| * Checks if a Prompt instance exists in the collection by its identifier. | |
| * | |
| * @param {string} identifier - The identifier of the Prompt instance to check. | |
| * @returns {boolean} true if the Prompt instance exists in the collection, false otherwise. | |
| */ | |
| has(identifier) { | |
| return this.index(identifier) !== -1; | |
| } | |
| override(prompt, position) { | |
| this.set(prompt, position); | |
| this.overriddenPrompts.push(prompt.identifier); | |
| } | |
| } | |
| class PromptManager { | |
| get promptSources() { | |
| return { | |
| charDescription: t`Character Description`, | |
| charPersonality: t`Character Personality`, | |
| scenario: t`Character Scenario`, | |
| personaDescription: t`Persona Description`, | |
| worldInfoBefore: t`World Info (↑Char)`, | |
| worldInfoAfter: t`World Info (↓Char)`, | |
| }; | |
| } | |
| constructor() { | |
| this.systemPrompts = [ | |
| 'main', | |
| 'nsfw', | |
| 'jailbreak', | |
| 'enhanceDefinitions', | |
| ]; | |
| this.overridablePrompts = [ | |
| 'main', | |
| 'jailbreak', | |
| ]; | |
| this.overriddenPrompts = []; | |
| this.configuration = { | |
| version: 1, | |
| prefix: '', | |
| containerIdentifier: '', | |
| listIdentifier: '', | |
| listItemTemplateIdentifier: '', | |
| toggleDisabled: [], | |
| promptOrder: { | |
| strategy: 'global', | |
| dummyId: 100000, | |
| }, | |
| sortableDelay: 30, | |
| warningTokenThreshold: 1500, | |
| dangerTokenThreshold: 500, | |
| defaultPrompts: { | |
| main: '', | |
| nsfw: '', | |
| jailbreak: '', | |
| enhanceDefinitions: '', | |
| }, | |
| }; | |
| // Chatcompletion configuration object | |
| this.serviceSettings = null; | |
| // DOM element containing the prompt manager | |
| this.containerElement = null; | |
| // DOM element containing the prompt list | |
| this.listElement = null; | |
| // Currently selected character | |
| this.activeCharacter = null; | |
| // Message collection of the most recent chatcompletion | |
| this.messages = null; | |
| // The current token handler instance | |
| this.tokenHandler = null; | |
| // Token usage of last dry run | |
| this.tokenUsage = 0; | |
| // Error state, contains error message. | |
| this.error = null; | |
| /** Dry-run for generate, must return a promise */ | |
| this.tryGenerate = async () => { }; | |
| /** Called to persist the configuration, must return a promise */ | |
| this.saveServiceSettings = () => { }; | |
| /** Toggle prompt button click */ | |
| this.handleToggle = () => { }; | |
| /** Prompt name click */ | |
| this.handleInspect = () => { }; | |
| /** Edit prompt button click */ | |
| this.handleEdit = () => { }; | |
| /** Detach prompt button click */ | |
| this.handleDetach = () => { }; | |
| /** Save prompt button click */ | |
| this.handleSavePrompt = () => { }; | |
| /** Reset prompt button click */ | |
| this.handleResetPrompt = () => { }; | |
| /** New prompt button click */ | |
| this.handleNewPrompt = () => { }; | |
| /** Delete prompt button click */ | |
| this.handleDeletePrompt = () => { }; | |
| /** Append prompt button click */ | |
| this.handleAppendPrompt = () => { }; | |
| /** Import button click */ | |
| this.handleImport = () => { }; | |
| /** Full export click */ | |
| this.handleFullExport = () => { }; | |
| /** Character export click */ | |
| this.handleCharacterExport = () => { }; | |
| /** Character reset button click*/ | |
| this.handleCharacterReset = () => { }; | |
| /** Debounced version of render */ | |
| this.renderDebounced = debounce(this.render.bind(this), debounce_timeout.relaxed); | |
| } | |
| /** | |
| * Initializes the PromptManager with provided configuration and service settings. | |
| * | |
| * Sets up various handlers for user interactions, event listeners and initial rendering of prompts. | |
| * It is also responsible for preparing prompt edit form buttons, managing popup form close and clear actions. | |
| * | |
| * @param {Object} moduleConfiguration - Configuration object for the PromptManager. | |
| * @param {Object} serviceSettings - Service settings object for the PromptManager. | |
| */ | |
| init(moduleConfiguration, serviceSettings) { | |
| this.configuration = Object.assign(this.configuration, moduleConfiguration); | |
| this.tokenHandler = this.tokenHandler || new TokenHandler(() => { throw new Error('Token handler not set'); }); | |
| this.serviceSettings = serviceSettings; | |
| this.containerElement = document.getElementById(this.configuration.containerIdentifier); | |
| if ('global' === this.configuration.promptOrder.strategy) this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; | |
| this.sanitizeServiceSettings(); | |
| // Enable and disable prompts | |
| this.handleToggle = (event) => { | |
| const promptID = event.target.closest('.' + this.configuration.prefix + 'prompt_manager_prompt').dataset.pmIdentifier; | |
| const promptOrderEntry = this.getPromptOrderEntry(this.activeCharacter, promptID); | |
| const counts = this.tokenHandler.getCounts(); | |
| counts[promptID] = null; | |
| promptOrderEntry.enabled = !promptOrderEntry.enabled; | |
| this.render(); | |
| this.saveServiceSettings(); | |
| }; | |
| // Open edit form and load selected prompt | |
| this.handleEdit = (event) => { | |
| this.clearEditForm(); | |
| this.clearInspectForm(); | |
| const promptID = event.target.closest('.' + this.configuration.prefix + 'prompt_manager_prompt').dataset.pmIdentifier; | |
| const prompt = this.getPromptById(promptID); | |
| this.loadPromptIntoEditForm(prompt); | |
| this.showPopup(); | |
| }; | |
| // Open edit form and load selected prompt | |
| this.handleInspect = (event) => { | |
| this.clearEditForm(); | |
| this.clearInspectForm(); | |
| const promptID = event.target.closest('.' + this.configuration.prefix + 'prompt_manager_prompt').dataset.pmIdentifier; | |
| if (true === this.messages.hasItemWithIdentifier(promptID)) { | |
| const messages = this.messages.getItemByIdentifier(promptID); | |
| this.loadMessagesIntoInspectForm(messages); | |
| this.showPopup('inspect'); | |
| } | |
| }; | |
| // Detach selected prompt from list form and close edit form | |
| this.handleDetach = (event) => { | |
| if (null === this.activeCharacter) return; | |
| const promptID = event.target.closest('.' + this.configuration.prefix + 'prompt_manager_prompt').dataset.pmIdentifier; | |
| const prompt = this.getPromptById(promptID); | |
| this.detachPrompt(prompt, this.activeCharacter); | |
| this.hidePopup(); | |
| this.clearEditForm(); | |
| this.render(); | |
| this.saveServiceSettings(); | |
| }; | |
| // Save prompt edit form to settings and close form. | |
| this.handleSavePrompt = (event) => { | |
| const promptId = event.target.dataset.pmPrompt; | |
| const prompt = this.getPromptById(promptId); | |
| if (null === prompt) { | |
| const newPrompt = {}; | |
| this.updatePromptWithPromptEditForm(newPrompt); | |
| this.addPrompt(newPrompt, promptId); | |
| } else { | |
| this.updatePromptWithPromptEditForm(prompt); | |
| } | |
| if ('main' === promptId) this.updateQuickEdit('main', prompt); | |
| if ('nsfw' === promptId) this.updateQuickEdit('nsfw', prompt); | |
| if ('jailbreak' === promptId) this.updateQuickEdit('jailbreak', prompt); | |
| this.log('Saved prompt: ' + promptId); | |
| this.hidePopup(); | |
| this.clearEditForm(); | |
| this.render(); | |
| this.saveServiceSettings(); | |
| }; | |
| // Reset prompt should it be a system prompt | |
| this.handleResetPrompt = (event) => { | |
| const promptId = event.target.dataset.pmPrompt; | |
| const prompt = this.getPromptById(promptId); | |
| const isPulledPrompt = Object.keys(this.promptSources).includes(promptId); | |
| switch (promptId) { | |
| case 'main': | |
| prompt.name = 'Main Prompt'; | |
| prompt.content = this.configuration.defaultPrompts.main; | |
| prompt.forbid_overrides = false; | |
| break; | |
| case 'nsfw': | |
| prompt.name = 'Nsfw Prompt'; | |
| prompt.content = this.configuration.defaultPrompts.nsfw; | |
| break; | |
| case 'jailbreak': | |
| prompt.name = 'Jailbreak Prompt'; | |
| prompt.content = this.configuration.defaultPrompts.jailbreak; | |
| prompt.forbid_overrides = false; | |
| break; | |
| case 'enhanceDefinitions': | |
| prompt.name = 'Enhance Definitions'; | |
| prompt.content = this.configuration.defaultPrompts.enhanceDefinitions; | |
| break; | |
| } | |
| 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; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order').value = prompt.injection_order ?? 100; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_order_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden'; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').disabled = prompt.marker ?? false; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block').style.display = isPulledPrompt ? '' : 'none'; | |
| if (isPulledPrompt) { | |
| const sourceName = this.promptSources[promptId]; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source').textContent = sourceName; | |
| } | |
| if (!this.systemPrompts.includes(promptId)) { | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled'); | |
| } | |
| }; | |
| // Append prompt to selected character | |
| this.handleAppendPrompt = (event) => { | |
| const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; | |
| const prompt = this.getPromptById(promptID); | |
| if (prompt) { | |
| this.appendPrompt(prompt, this.activeCharacter); | |
| this.render(); | |
| this.saveServiceSettings(); | |
| } | |
| }; | |
| // Delete selected prompt from list form and close edit form | |
| this.handleDeletePrompt = async (event) => { | |
| Popup.show.confirm(t`Are you sure you want to delete this prompt?`, null).then((userChoice) => { | |
| if (!userChoice) return; | |
| const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; | |
| const prompt = this.getPromptById(promptID); | |
| if (prompt && true === this.isPromptDeletionAllowed(prompt)) { | |
| const promptIndex = this.getPromptIndexById(promptID); | |
| this.serviceSettings.prompts.splice(Number(promptIndex), 1); | |
| this.log('Deleted prompt: ' + prompt.identifier); | |
| this.hidePopup(); | |
| this.clearEditForm(); | |
| this.render(); | |
| this.saveServiceSettings(); | |
| } | |
| }); | |
| }; | |
| // Create new prompt, then save it to settings and close form. | |
| this.handleNewPrompt = (event) => { | |
| const prompt = { | |
| identifier: this.getUuidv4(), | |
| name: '', | |
| role: 'system', | |
| content: '', | |
| }; | |
| this.loadPromptIntoEditForm(prompt); | |
| this.showPopup(); | |
| }; | |
| // Export all user prompts | |
| this.handleFullExport = () => { | |
| const prompts = this.serviceSettings.prompts.reduce((userPrompts, prompt) => { | |
| if (false === prompt.system_prompt && false === prompt.marker) userPrompts.push(prompt); | |
| return userPrompts; | |
| }, []); | |
| let promptOrder = []; | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId }); | |
| } else if ('character' === this.configuration.promptOrder.strategy) { | |
| promptOrder = []; | |
| } else { | |
| throw new Error('Prompt order strategy not supported.'); | |
| } | |
| const exportPrompts = { | |
| prompts: prompts, | |
| prompt_order: promptOrder, | |
| }; | |
| this.export(exportPrompts, 'full', 'st-prompts'); | |
| }; | |
| // Export user prompts and order for this character | |
| this.handleCharacterExport = () => { | |
| const characterPrompts = this.getPromptsForCharacter(this.activeCharacter).reduce((userPrompts, prompt) => { | |
| if (false === prompt.system_prompt && !prompt.marker) userPrompts.push(prompt); | |
| return userPrompts; | |
| }, []); | |
| const characterList = this.getPromptOrderForCharacter(this.activeCharacter); | |
| const exportPrompts = { | |
| prompts: characterPrompts, | |
| prompt_order: characterList, | |
| }; | |
| const name = this.activeCharacter.name + '-prompts'; | |
| this.export(exportPrompts, 'character', name); | |
| }; | |
| // Import prompts for the selected character | |
| this.handleImport = () => { | |
| Popup.show.confirm(t`Existing prompts with the same ID will be overridden. Do you want to proceed?`, null) | |
| .then(userChoice => { | |
| if (!userChoice) return; | |
| const fileOpener = document.createElement('input'); | |
| fileOpener.type = 'file'; | |
| fileOpener.accept = '.json'; | |
| fileOpener.addEventListener('change', (event) => { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| const fileContent = event.target.result; | |
| try { | |
| const data = JSON.parse(fileContent); | |
| this.import(data); | |
| } catch (err) { | |
| toastr.error(t`An error occurred while importing prompts. More info available in console.`); | |
| console.log('An error occurred while importing prompts'); | |
| console.log(err.toString()); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| }); | |
| fileOpener.click(); | |
| }); | |
| }; | |
| // Restore default state of a characters prompt order | |
| this.handleCharacterReset = () => { | |
| Popup.show.confirm(t`This will reset the prompt order for this character. You will not lose any prompts.`, null) | |
| .then(userChoice => { | |
| if (!userChoice) return; | |
| this.removePromptOrderForCharacter(this.activeCharacter); | |
| this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder); | |
| this.render(); | |
| this.saveServiceSettings(); | |
| }); | |
| }; | |
| // Fill quick edit fields for the first time | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| const handleQuickEditSave = (event) => { | |
| const promptId = event.target.dataset.pmPrompt; | |
| const prompt = this.getPromptById(promptId); | |
| prompt.content = event.target.value; | |
| // Update edit form if present | |
| // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent | |
| const popupEditFormPrompt = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'); | |
| if (popupEditFormPrompt.offsetParent) { | |
| popupEditFormPrompt.value = prompt.content; | |
| } | |
| this.log('Saved prompt: ' + promptId); | |
| this.saveServiceSettings().then(() => this.render()); | |
| }; | |
| const mainPrompt = this.getPromptById('main'); | |
| const mainElementId = this.updateQuickEdit('main', mainPrompt); | |
| document.getElementById(mainElementId).addEventListener('blur', handleQuickEditSave); | |
| const nsfwPrompt = this.getPromptById('nsfw'); | |
| const nsfwElementId = this.updateQuickEdit('nsfw', nsfwPrompt); | |
| document.getElementById(nsfwElementId).addEventListener('blur', handleQuickEditSave); | |
| const jailbreakPrompt = this.getPromptById('jailbreak'); | |
| const jailbreakElementId = this.updateQuickEdit('jailbreak', jailbreakPrompt); | |
| document.getElementById(jailbreakElementId).addEventListener('blur', handleQuickEditSave); | |
| } | |
| // Re-render when chat history changes. | |
| eventSource.on(event_types.MESSAGE_DELETED, () => this.renderDebounced()); | |
| eventSource.on(event_types.MESSAGE_EDITED, () => this.renderDebounced()); | |
| eventSource.on(event_types.MESSAGE_RECEIVED, () => this.renderDebounced()); | |
| // Re-render when chatcompletion settings change | |
| eventSource.on(event_types.CHATCOMPLETION_SOURCE_CHANGED, () => this.renderDebounced()); | |
| eventSource.on(event_types.CHATCOMPLETION_MODEL_CHANGED, () => this.renderDebounced()); | |
| // Re-render when the character changes. | |
| eventSource.on('chatLoaded', (event) => { | |
| this.handleCharacterSelected(event); | |
| this.saveServiceSettings().then(() => this.renderDebounced()); | |
| }); | |
| // Re-render when the character gets edited. | |
| eventSource.on(event_types.CHARACTER_EDITED, (event) => { | |
| this.handleCharacterUpdated(event); | |
| this.saveServiceSettings().then(() => this.renderDebounced()); | |
| }); | |
| // Re-render when the group changes. | |
| eventSource.on('groupSelected', (event) => { | |
| this.handleGroupSelected(event); | |
| this.saveServiceSettings().then(() => this.renderDebounced()); | |
| }); | |
| // Sanitize settings after character has been deleted. | |
| eventSource.on(event_types.CHARACTER_DELETED, (event) => { | |
| this.handleCharacterDeleted(event); | |
| this.saveServiceSettings().then(() => this.renderDebounced()); | |
| }); | |
| // Trigger re-render when token settings are changed | |
| document.getElementById('openai_max_context').addEventListener('change', (event) => { | |
| this.serviceSettings.openai_max_context = event.target.value; | |
| if (this.activeCharacter) this.renderDebounced(); | |
| }); | |
| document.getElementById('openai_max_tokens').addEventListener('change', (event) => { | |
| if (this.activeCharacter) this.renderDebounced(); | |
| }); | |
| // Prepare prompt edit form buttons | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_save').addEventListener('click', this.handleSavePrompt); | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_reset').addEventListener('click', this.handleResetPrompt); | |
| const closeAndClearPopup = () => { | |
| this.hidePopup(); | |
| this.clearEditForm(); | |
| this.clearInspectForm(); | |
| }; | |
| // Clear forms on closing the popup | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_close').addEventListener('click', closeAndClearPopup); | |
| document.getElementById(this.configuration.prefix + 'prompt_manager_popup_close_button').addEventListener('click', closeAndClearPopup); | |
| closeAndClearPopup(); | |
| // Re-render prompt manager on openai preset change | |
| eventSource.on(event_types.OAI_PRESET_CHANGED_AFTER, () => { | |
| this.sanitizeServiceSettings(); | |
| const mainPrompt = this.getPromptById('main'); | |
| this.updateQuickEdit('main', mainPrompt); | |
| const nsfwPrompt = this.getPromptById('nsfw'); | |
| this.updateQuickEdit('nsfw', nsfwPrompt); | |
| const jailbreakPrompt = this.getPromptById('jailbreak'); | |
| this.updateQuickEdit('jailbreak', jailbreakPrompt); | |
| this.hidePopup(); | |
| this.clearEditForm(); | |
| this.renderDebounced(); | |
| }); | |
| // Re-render prompt manager on world settings update | |
| eventSource.on(event_types.WORLDINFO_SETTINGS_UPDATED, () => this.renderDebounced()); | |
| this.log('Initialized'); | |
| } | |
| /** | |
| * Get the scroll position of the prompt manager | |
| * @returns {number} - Scroll position of the prompt manager | |
| */ | |
| #getScrollPosition() { | |
| return document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTop; | |
| } | |
| /** | |
| * Set the scroll position of the prompt manager | |
| * @param {number} scrollPosition - The scroll position to set | |
| */ | |
| #setScrollPosition(scrollPosition) { | |
| if (scrollPosition === undefined || scrollPosition === null) return; | |
| document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTo(0, scrollPosition); | |
| } | |
| /** | |
| * Main rendering function | |
| * | |
| * @param afterTryGenerate - Whether a dry run should be attempted before rendering | |
| */ | |
| render(afterTryGenerate = true) { | |
| if (main_api !== 'openai') return; | |
| if ('character' === this.configuration.promptOrder.strategy && null === this.activeCharacter) return; | |
| this.error = null; | |
| waitUntilCondition(() => !is_send_press && !is_group_generating, 1024 * 1024, 100).then(async () => { | |
| if (true === afterTryGenerate) { | |
| // Executed during dry-run for determining context composition | |
| this.profileStart('filling context'); | |
| this.tryGenerate().finally(async () => { | |
| this.profileEnd('filling context'); | |
| this.profileStart('render'); | |
| const scrollPosition = this.#getScrollPosition(); | |
| await this.renderPromptManager(); | |
| await this.renderPromptManagerListItems(); | |
| this.makeDraggable(); | |
| this.#setScrollPosition(scrollPosition); | |
| this.profileEnd('render'); | |
| }); | |
| } else { | |
| // Executed during live communication | |
| this.profileStart('render'); | |
| const scrollPosition = this.#getScrollPosition(); | |
| await this.renderPromptManager(); | |
| await this.renderPromptManagerListItems(); | |
| this.makeDraggable(); | |
| this.#setScrollPosition(scrollPosition); | |
| this.profileEnd('render'); | |
| } | |
| }).catch(() => { | |
| console.log('Timeout while waiting for send press to be false'); | |
| }); | |
| } | |
| /** | |
| * Update a prompt with the values from the HTML form. | |
| * @param {object} prompt - The prompt to be updated. | |
| * @returns {void} | |
| */ | |
| updatePromptWithPromptEditForm(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); | |
| prompt.injection_order = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order').value); | |
| prompt.forbid_overrides = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked; | |
| } | |
| /** | |
| * Find a prompt by its identifier and update it with the provided object. | |
| * @param {string} identifier - The identifier of the prompt. | |
| * @param {object} updatePrompt - An object with properties to be updated in the prompt. | |
| * @returns {void} | |
| */ | |
| updatePromptByIdentifier(identifier, updatePrompt) { | |
| let prompt = this.serviceSettings.prompts.find((item) => identifier === item.identifier); | |
| if (prompt) prompt = Object.assign(prompt, updatePrompt); | |
| } | |
| /** | |
| * Iterate over an array of prompts, find each one by its identifier, and update them with the provided data. | |
| * @param {object[]} prompts - An array of prompt updates. | |
| * @returns {void} | |
| */ | |
| updatePrompts(prompts) { | |
| prompts.forEach((update) => { | |
| let prompt = this.getPromptById(update.identifier); | |
| if (prompt) Object.assign(prompt, update); | |
| }); | |
| } | |
| getTokenHandler() { | |
| return this.tokenHandler; | |
| } | |
| isPromptDisabledForActiveCharacter(identifier) { | |
| const promptOrderEntry = this.getPromptOrderEntry(this.activeCharacter, identifier); | |
| if (promptOrderEntry) return !promptOrderEntry.enabled; | |
| return false; | |
| } | |
| /** | |
| * Add a prompt to the current character's prompt list. | |
| * @param {object} prompt - The prompt to be added. | |
| * @param {object} character - The character whose prompt list will be updated. | |
| * @returns {void} | |
| */ | |
| appendPrompt(prompt, character) { | |
| const promptOrder = this.getPromptOrderForCharacter(character); | |
| const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier); | |
| if (-1 === index) promptOrder.unshift({ identifier: prompt.identifier, enabled: false }); | |
| } | |
| /** | |
| * Remove a prompt from the current character's prompt list. | |
| * @param {object} prompt - The prompt to be removed. | |
| * @param {object} character - The character whose prompt list will be updated. | |
| * @returns {void} | |
| */ | |
| // Remove a prompt from the current characters prompt list | |
| detachPrompt(prompt, character) { | |
| const promptOrder = this.getPromptOrderForCharacter(character); | |
| const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier); | |
| if (-1 === index) return; | |
| promptOrder.splice(index, 1); | |
| } | |
| /** | |
| * Create a new prompt and add it to the list of prompts. | |
| * @param {object} prompt - The prompt to be added. | |
| * @param {string} identifier - The identifier for the new prompt. | |
| * @returns {void} | |
| */ | |
| addPrompt(prompt, identifier) { | |
| if (typeof prompt !== 'object' || prompt === null) throw new Error('Object is not a prompt'); | |
| const newPrompt = { | |
| identifier: identifier, | |
| system_prompt: false, | |
| enabled: false, | |
| marker: false, | |
| ...prompt, | |
| }; | |
| this.serviceSettings.prompts.push(newPrompt); | |
| } | |
| /** | |
| * Sanitize the service settings, ensuring each prompt has a unique identifier. | |
| * @returns {void} | |
| */ | |
| sanitizeServiceSettings() { | |
| this.serviceSettings.prompts = this.serviceSettings.prompts ?? []; | |
| this.serviceSettings.prompt_order = this.serviceSettings.prompt_order ?? []; | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| const dummyCharacter = { id: this.configuration.promptOrder.dummyId }; | |
| const promptOrder = this.getPromptOrderForCharacter(dummyCharacter); | |
| if (0 === promptOrder.length) this.addPromptOrderForCharacter(dummyCharacter, promptManagerDefaultPromptOrder); | |
| } | |
| // Check whether the referenced prompts are present. | |
| this.serviceSettings.prompts.length === 0 | |
| ? this.setPrompts(chatCompletionDefaultPrompts.prompts) | |
| : this.checkForMissingPrompts(this.serviceSettings.prompts); | |
| // Add identifiers if there are none assigned to a prompt | |
| this.serviceSettings.prompts.forEach(prompt => prompt && (prompt.identifier = prompt.identifier ?? this.getUuidv4())); | |
| if (this.activeCharacter) { | |
| const promptReferences = this.getPromptOrderForCharacter(this.activeCharacter); | |
| for (let i = promptReferences.length - 1; i >= 0; i--) { | |
| const reference = promptReferences[i]; | |
| if (reference && -1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) { | |
| promptReferences.splice(i, 1); | |
| this.log('Removed unused reference: ' + reference.identifier); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Checks whether entries of a characters prompt order are orphaned | |
| * and if all mandatory system prompts for a character are present. | |
| * | |
| * @param prompts | |
| */ | |
| checkForMissingPrompts(prompts) { | |
| const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list; }, []); | |
| const missingIdentifiers = defaultPromptIdentifiers.filter(identifier => | |
| !prompts.some(prompt => prompt.identifier === identifier), | |
| ); | |
| missingIdentifiers.forEach(identifier => { | |
| const defaultPrompt = chatCompletionDefaultPrompts.prompts.find(prompt => prompt?.identifier === identifier); | |
| if (defaultPrompt) { | |
| prompts.push(defaultPrompt); | |
| this.log(`Missing system prompt: ${defaultPrompt.identifier}. Added default.`); | |
| } | |
| }); | |
| } | |
| /** | |
| * Check whether a prompt can be inspected. | |
| * @param {object} prompt - The prompt to check. | |
| * @returns {boolean} True if the prompt is a marker, false otherwise. | |
| */ | |
| isPromptInspectionAllowed(prompt) { | |
| return true; | |
| } | |
| /** | |
| * Check whether a prompt can be deleted. System prompts cannot be deleted. | |
| * @param {object} prompt - The prompt to check. | |
| * @returns {boolean} True if the prompt can be deleted, false otherwise. | |
| */ | |
| isPromptDeletionAllowed(prompt) { | |
| return false === prompt.system_prompt; | |
| } | |
| /** | |
| * Check whether a prompt can be edited. | |
| * @param {object} prompt - The prompt to check. | |
| * @returns {boolean} True if the prompt can be edited, false otherwise. | |
| */ | |
| isPromptEditAllowed(prompt) { | |
| const forceEditPrompts = [ | |
| 'charDescription', | |
| 'charPersonality', | |
| 'scenario', | |
| 'personaDescription', | |
| 'worldInfoBefore', | |
| 'worldInfoAfter', | |
| ]; | |
| return forceEditPrompts.includes(prompt.identifier) || !prompt.marker; | |
| } | |
| /** | |
| * Check whether a prompt can be toggled on or off. | |
| * @param {object} prompt - The prompt to check. | |
| * @returns {boolean} True if the prompt can be deleted, false otherwise. | |
| */ | |
| isPromptToggleAllowed(prompt) { | |
| const forceTogglePrompts = [ | |
| 'charDescription', | |
| 'charPersonality', | |
| 'scenario', | |
| 'personaDescription', | |
| 'worldInfoBefore', | |
| 'worldInfoAfter', | |
| 'main', | |
| 'chatHistory', | |
| 'dialogueExamples', | |
| ]; | |
| return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier); | |
| } | |
| /** | |
| * Handle the deletion of a character by removing their prompt list and nullifying the active character if it was the one deleted. | |
| * @param {object} event - The event object containing the character's ID. | |
| * @returns void | |
| */ | |
| handleCharacterDeleted(event) { | |
| if ('global' === this.configuration.promptOrder.strategy) return; | |
| this.removePromptOrderForCharacter(this.activeCharacter); | |
| if (this.activeCharacter.id === event.detail.id) this.activeCharacter = null; | |
| } | |
| /** | |
| * Handle the selection of a character by setting them as the active character and setting up their prompt list if necessary. | |
| * @param {object} event - The event object containing the character's ID and character data. | |
| * @returns {void} | |
| */ | |
| handleCharacterSelected(event) { | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; | |
| } else if ('character' === this.configuration.promptOrder.strategy) { | |
| console.log('FOO'); | |
| this.activeCharacter = { id: event.detail.id, ...event.detail.character }; | |
| const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); | |
| // ToDo: These should be passed as parameter or attached to the manager as a set of default options. | |
| // Set default prompts and order for character. | |
| if (0 === promptOrder.length) this.addPromptOrderForCharacter(this.activeCharacter, promptManagerDefaultPromptOrder); | |
| } else { | |
| throw new Error('Unsupported prompt order mode.'); | |
| } | |
| } | |
| /** | |
| * Set the most recently selected character | |
| * | |
| * @param event | |
| */ | |
| handleCharacterUpdated(event) { | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; | |
| } else if ('character' === this.configuration.promptOrder.strategy) { | |
| this.activeCharacter = { id: event.detail.id, ...event.detail.character }; | |
| } else { | |
| throw new Error('Prompt order strategy not supported.'); | |
| } | |
| } | |
| /** | |
| * Set the most recently selected character group | |
| * | |
| * @param event | |
| */ | |
| handleGroupSelected(event) { | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| this.activeCharacter = { id: this.configuration.promptOrder.dummyId }; | |
| } else if ('character' === this.configuration.promptOrder.strategy) { | |
| const characterDummy = { id: event.detail.id, group: event.detail.group }; | |
| this.activeCharacter = characterDummy; | |
| const promptOrder = this.getPromptOrderForCharacter(characterDummy); | |
| if (0 === promptOrder.length) this.addPromptOrderForCharacter(characterDummy, promptManagerDefaultPromptOrder); | |
| } else { | |
| throw new Error('Prompt order strategy not supported.'); | |
| } | |
| } | |
| /** | |
| * Get a list of group characters, regardless of whether they are active or not. | |
| * | |
| * @returns {string[]} | |
| */ | |
| getActiveGroupCharacters() { | |
| // ToDo: Ideally, this should return the actual characters. | |
| return (this.activeCharacter?.group?.members || []).map(member => member && member.substring(0, member.lastIndexOf('.'))); | |
| } | |
| /** | |
| * Get the prompts for a specific character. Can be filtered to only include enabled prompts. | |
| * @returns {object[]} The prompts for the character. | |
| * @param character | |
| * @param onlyEnabled | |
| */ | |
| getPromptsForCharacter(character, onlyEnabled = false) { | |
| return this.getPromptOrderForCharacter(character) | |
| .map(item => true === onlyEnabled ? (true === item.enabled ? this.getPromptById(item.identifier) : null) : this.getPromptById(item.identifier)) | |
| .filter(prompt => null !== prompt); | |
| } | |
| /** | |
| * 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. | |
| * @param {object|null} character - The character to get the prompt list for. | |
| * @returns {object[]} The prompt list for the character, or an empty array. | |
| */ | |
| getPromptOrderForCharacter(character) { | |
| return !character ? [] : (this.serviceSettings.prompt_order.find(list => String(list.character_id) === String(character.id))?.order ?? []); | |
| } | |
| /** | |
| * Set the prompts for the manager. | |
| * @param {object[]} prompts - The prompts to be set. | |
| * @returns {void} | |
| */ | |
| setPrompts(prompts) { | |
| this.serviceSettings.prompts = prompts; | |
| } | |
| /** | |
| * Remove the prompt list for a specific character. | |
| * @param {object} character - The character whose prompt list will be removed. | |
| * @returns {void} | |
| */ | |
| removePromptOrderForCharacter(character) { | |
| const index = this.serviceSettings.prompt_order.findIndex(list => String(list.character_id) === String(character.id)); | |
| if (-1 !== index) this.serviceSettings.prompt_order.splice(index, 1); | |
| } | |
| /** | |
| * Adds a new prompt list for a specific character. | |
| * @param {Object} character - Object with at least an `id` property | |
| * @param {Array<Object>} promptOrder - Array of prompt objects | |
| */ | |
| addPromptOrderForCharacter(character, promptOrder) { | |
| this.serviceSettings.prompt_order.push({ | |
| character_id: character.id, | |
| order: JSON.parse(JSON.stringify(promptOrder)), | |
| }); | |
| } | |
| /** | |
| * Searches for a prompt list entry for a given character and identifier. | |
| * @param {Object} character - Character object | |
| * @param {string} identifier - Identifier of the prompt list entry | |
| * @returns {Object|null} The prompt list entry object, or null if not found | |
| */ | |
| getPromptOrderEntry(character, identifier) { | |
| return this.getPromptOrderForCharacter(character).find(entry => entry.identifier === identifier) ?? null; | |
| } | |
| /** | |
| * Finds and returns a prompt by its identifier. | |
| * @param {string} identifier - Identifier of the prompt | |
| * @returns {Object|null} The prompt object, or null if not found | |
| */ | |
| getPromptById(identifier) { | |
| return this.serviceSettings.prompts.find(item => item && item.identifier === identifier) ?? null; | |
| } | |
| /** | |
| * Finds and returns the index of a prompt by its identifier. | |
| * @param {string} identifier - Identifier of the prompt | |
| * @returns {number|null} Index of the prompt, or null if not found | |
| */ | |
| getPromptIndexById(identifier) { | |
| return this.serviceSettings.prompts.findIndex(item => item.identifier === identifier) ?? null; | |
| } | |
| /** | |
| * Enriches a generic object, creating a new prompt object in the process | |
| * | |
| * @param {Object} prompt - Prompt object | |
| * @param original | |
| * @returns {Object} An object with "role" and "content" properties | |
| */ | |
| preparePrompt(prompt, original = null) { | |
| const groupMembers = this.getActiveGroupCharacters(); | |
| const preparedPrompt = new Prompt(prompt); | |
| if (typeof original === 'string') { | |
| if (0 < groupMembers.length) preparedPrompt.content = substituteParams(prompt.content ?? '', null, null, original, groupMembers.join(', ')); | |
| else preparedPrompt.content = substituteParams(prompt.content, null, null, original); | |
| } else { | |
| if (0 < groupMembers.length) preparedPrompt.content = substituteParams(prompt.content ?? '', null, null, null, groupMembers.join(', ')); | |
| else preparedPrompt.content = substituteParams(prompt.content); | |
| } | |
| return preparedPrompt; | |
| } | |
| /** | |
| * Factory function for creating a QuickEdit object associated with a prompt element. | |
| * | |
| * The QuickEdit object provides methods to synchronize an input element's value with a prompt's content | |
| * and handle input events to update the prompt content. | |
| * | |
| */ | |
| createQuickEdit(identifier, title) { | |
| const prompt = this.getPromptById(identifier); | |
| const textareaIdentifier = `${identifier}_prompt_quick_edit_textarea`; | |
| const html = `<div class="range-block m-t-1"> | |
| <div class="justifyLeft" data-i18n="${title}">${title}</div> | |
| <div class="wide100p"> | |
| <textarea id="${textareaIdentifier}" class="text_pole textarea_compact" rows="6" placeholder="">${prompt.content}</textarea> | |
| </div> | |
| </div>`; | |
| const quickEditContainer = document.getElementById('quick-edit-container'); | |
| quickEditContainer.insertAdjacentHTML('afterbegin', html); | |
| const debouncedSaveServiceSettings = debouncePromise(() => this.saveServiceSettings(), 300); | |
| const textarea = document.getElementById(textareaIdentifier); | |
| textarea.addEventListener('blur', () => { | |
| prompt.content = textarea.value; | |
| this.updatePromptByIdentifier(identifier, prompt); | |
| debouncedSaveServiceSettings().then(() => this.render()); | |
| }); | |
| } | |
| updateQuickEdit(identifier, prompt) { | |
| const elementId = `${identifier}_prompt_quick_edit_textarea`; | |
| const textarea = document.getElementById(elementId); | |
| textarea.value = prompt.content; | |
| return elementId; | |
| } | |
| /** | |
| * Checks if a given name is accepted by OpenAi API | |
| * @link https://platform.openai.com/docs/api-reference/chat/create | |
| * | |
| * @param name | |
| * @returns {boolean} | |
| */ | |
| isValidName(name) { | |
| const regex = /^[a-zA-Z0-9_]{1,64}$/; | |
| return regex.test(name); | |
| } | |
| sanitizeName(name) { | |
| return name.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 64); | |
| } | |
| /** | |
| * Loads a given prompt into the edit form fields. | |
| * @param {Object} prompt - Prompt object with properties 'name', 'role', 'content', and 'system_prompt' | |
| */ | |
| loadPromptIntoEditForm(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'); | |
| const injectionOrderField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order'); | |
| const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'); | |
| const injectionOrderBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'); | |
| const forbidOverridesField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides'); | |
| const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block'); | |
| const entrySourceBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block'); | |
| const entrySource = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'); | |
| const isPulledPrompt = Object.keys(this.promptSources).includes(prompt.identifier); | |
| nameField.value = prompt.name ?? ''; | |
| roleField.value = prompt.role || 'system'; | |
| promptField.value = prompt.content ?? ''; | |
| promptField.disabled = prompt.marker ?? false; | |
| injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE; | |
| injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH; | |
| injectionOrderField.value = prompt.injection_order ?? 100; | |
| injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | |
| injectionOrderBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | |
| injectionPositionField.removeAttribute('disabled'); | |
| forbidOverridesField.checked = prompt.forbid_overrides ?? false; | |
| forbidOverridesBlock.style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden'; | |
| entrySourceBlock.style.display = isPulledPrompt ? '' : 'none'; | |
| if (isPulledPrompt) { | |
| const sourceName = this.promptSources[prompt.identifier]; | |
| entrySource.textContent = sourceName; | |
| } | |
| if (this.systemPrompts.includes(prompt.identifier)) { | |
| injectionPositionField.setAttribute('disabled', 'disabled'); | |
| } | |
| const resetPromptButton = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_reset'); | |
| if (true === prompt.system_prompt) { | |
| resetPromptButton.style.display = 'block'; | |
| resetPromptButton.dataset.pmPrompt = prompt.identifier; | |
| } else { | |
| resetPromptButton.style.display = 'none'; | |
| } | |
| injectionPositionField.removeEventListener('change', (e) => this.handleInjectionPositionChange(e)); | |
| injectionPositionField.addEventListener('change', (e) => this.handleInjectionPositionChange(e)); | |
| const savePromptButton = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_save'); | |
| savePromptButton.dataset.pmPrompt = prompt.identifier; | |
| } | |
| handleInjectionPositionChange(event) { | |
| const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'); | |
| const injectionOrderBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'); | |
| const injectionPosition = Number(event.target.value); | |
| if (injectionPosition === INJECTION_POSITION.ABSOLUTE) { | |
| injectionDepthBlock.style.visibility = 'visible'; | |
| injectionOrderBlock.style.visibility = 'visible'; | |
| } else { | |
| injectionDepthBlock.style.visibility = 'hidden'; | |
| injectionOrderBlock.style.visibility = 'hidden'; | |
| } | |
| } | |
| /** | |
| * Loads a given prompt into the inspect form | |
| * @param {MessageCollection} messages - Prompt object with properties 'name', 'role', 'content', and 'system_prompt' | |
| */ | |
| loadMessagesIntoInspectForm(messages) { | |
| if (!messages) return; | |
| const createInlineDrawer = (message) => { | |
| const truncatedTitle = message.content.length > 32 ? message.content.slice(0, 32) + '...' : message.content; | |
| const title = message.identifier || truncatedTitle; | |
| const role = message.role; | |
| const content = message.content || 'No Content'; | |
| const tokens = message.getTokens(); | |
| let drawerHTML = ` | |
| <div class="inline-drawer ${this.configuration.prefix}prompt_manager_prompt"> | |
| <div class="inline-drawer-toggle inline-drawer-header"> | |
| <span>Name: ${escapeHtml(title)}, Role: ${role}, Tokens: ${tokens}</span> | |
| <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div> | |
| </div> | |
| <div class="inline-drawer-content" style="white-space: pre-wrap;">${escapeHtml(content)}</div> | |
| </div> | |
| `; | |
| let template = document.createElement('template'); | |
| template.innerHTML = drawerHTML.trim(); | |
| return template.content.firstChild; | |
| }; | |
| const messageList = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_inspect_list'); | |
| const messagesCollection = messages instanceof Message ? [messages] : messages.getCollection(); | |
| if (0 === messagesCollection.length) messageList.innerHTML = '<span>This marker does not contain any prompts.</span>'; | |
| messagesCollection.forEach(message => { | |
| messageList.append(createInlineDrawer(message)); | |
| }); | |
| } | |
| /** | |
| * Clears all input fields in the edit form. | |
| */ | |
| clearEditForm() { | |
| const editArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_edit'); | |
| editArea.style.display = 'none'; | |
| 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'); | |
| const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'); | |
| const injectionOrderBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'); | |
| const forbidOverridesField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides'); | |
| const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block'); | |
| const entrySourceBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block'); | |
| const entrySource = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'); | |
| nameField.value = ''; | |
| roleField.selectedIndex = 0; | |
| promptField.value = ''; | |
| promptField.disabled = false; | |
| injectionPositionField.selectedIndex = 0; | |
| injectionPositionField.removeAttribute('disabled'); | |
| injectionDepthField.value = DEFAULT_DEPTH; | |
| injectionDepthBlock.style.visibility = 'unset'; | |
| injectionOrderBlock.style.visibility = 'unset'; | |
| forbidOverridesBlock.style.visibility = 'unset'; | |
| forbidOverridesField.checked = false; | |
| entrySourceBlock.style.display = 'none'; | |
| entrySource.textContent = ''; | |
| roleField.disabled = false; | |
| } | |
| clearInspectForm() { | |
| const inspectArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_inspect'); | |
| inspectArea.style.display = 'none'; | |
| const messageList = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_inspect_list'); | |
| messageList.innerHTML = ''; | |
| } | |
| /** | |
| * Returns a full list of prompts whose content markers have been substituted. | |
| * @returns {PromptCollection} A PromptCollection object | |
| */ | |
| getPromptCollection() { | |
| const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); | |
| const promptCollection = new PromptCollection(); | |
| promptOrder.forEach(entry => { | |
| if (true === entry.enabled) { | |
| const prompt = this.getPromptById(entry.identifier); | |
| if (prompt) promptCollection.add(this.preparePrompt(prompt)); | |
| } else if (!entry.enabled && entry.identifier === 'main') { | |
| // Some extensions require main prompt to be present for relative inserts. | |
| // So we make a GMO-free vegan replacement. | |
| const prompt = structuredClone(this.getPromptById(entry.identifier)); | |
| prompt.content = ''; | |
| if (prompt) promptCollection.add(this.preparePrompt(prompt)); | |
| } | |
| }); | |
| return promptCollection; | |
| } | |
| /** | |
| * Setter for messages property | |
| * | |
| * @param {import('./openai.js').MessageCollection} messages | |
| */ | |
| setMessages(messages) { | |
| this.messages = messages; | |
| } | |
| /** | |
| * Set and process a finished chat completion object | |
| * | |
| * @param {import('./openai.js').ChatCompletion} chatCompletion | |
| */ | |
| setChatCompletion(chatCompletion) { | |
| const messages = chatCompletion.getMessages(); | |
| this.setMessages(messages); | |
| this.populateTokenCounts(messages); | |
| this.overriddenPrompts = chatCompletion.getOverriddenPrompts(); | |
| } | |
| /** | |
| * Populates the token handler | |
| * | |
| * @param {import('./openai.js').MessageCollection} messages | |
| */ | |
| populateTokenCounts(messages) { | |
| this.tokenHandler.resetCounts(); | |
| const counts = this.tokenHandler.getCounts(); | |
| messages.getCollection().forEach(message => { | |
| counts[message.identifier] = message.getTokens(); | |
| }); | |
| this.tokenUsage = this.tokenHandler.getTotal(); | |
| this.log('Updated token usage with ' + this.tokenUsage); | |
| } | |
| /** | |
| * Empties, then re-assembles the container containing the prompt list. | |
| */ | |
| async renderPromptManager() { | |
| let selectedPromptIndex = 0; | |
| const existingAppendSelect = document.getElementById(`${this.configuration.prefix}prompt_manager_footer_append_prompt`); | |
| if (existingAppendSelect instanceof HTMLSelectElement) { | |
| selectedPromptIndex = existingAppendSelect.selectedIndex; | |
| } | |
| const promptManagerDiv = this.containerElement; | |
| promptManagerDiv.innerHTML = ''; | |
| const errorDiv = this.error ? ` | |
| <div class="${this.configuration.prefix}prompt_manager_error"> | |
| <span class="fa-solid tooltip fa-triangle-exclamation text_danger"></span> ${DOMPurify.sanitize(this.error)} | |
| </div> | |
| ` : ''; | |
| const totalActiveTokens = this.tokenUsage; | |
| const headerHtml = await renderTemplateAsync('promptManagerHeader', { error: this.error, errorDiv, prefix: this.configuration.prefix, totalActiveTokens }); | |
| promptManagerDiv.insertAdjacentHTML('beforeend', headerHtml); | |
| this.listElement = promptManagerDiv.querySelector(`#${this.configuration.prefix}prompt_manager_list`); | |
| if (null !== this.activeCharacter) { | |
| const prompts = [...this.serviceSettings.prompts] | |
| .filter(prompt => prompt && !prompt?.system_prompt) | |
| .sort((promptA, promptB) => promptA.name.localeCompare(promptB.name)); | |
| const promptsHtml = prompts.reduce((acc, prompt) => acc + `<option value="${prompt.identifier}">${escapeHtml(prompt.name)}</option>`, ''); | |
| if (selectedPromptIndex > 0) { | |
| selectedPromptIndex = Math.min(selectedPromptIndex, prompts.length - 1); | |
| } | |
| if (selectedPromptIndex === -1 && prompts.length) { | |
| selectedPromptIndex = 0; | |
| } | |
| const rangeBlockDiv = promptManagerDiv.querySelector('.range-block'); | |
| const headerDiv = promptManagerDiv.querySelector('.completion_prompt_manager_header'); | |
| const footerHtml = await renderTemplateAsync('promptManagerFooter', { promptsHtml, prefix: this.configuration.prefix }); | |
| headerDiv.insertAdjacentHTML('afterend', footerHtml); | |
| rangeBlockDiv.querySelector('#prompt-manager-reset-character').addEventListener('click', this.handleCharacterReset); | |
| const footerDiv = rangeBlockDiv.querySelector(`.${this.configuration.prefix}prompt_manager_footer`); | |
| footerDiv.querySelector('.menu_button:nth-child(2)').addEventListener('click', this.handleAppendPrompt); | |
| footerDiv.querySelector('.caution').addEventListener('click', this.handleDeletePrompt); | |
| footerDiv.querySelector('.menu_button:last-child').addEventListener('click', this.handleNewPrompt); | |
| footerDiv.querySelector('select').selectedIndex = selectedPromptIndex; | |
| // Add prompt export dialogue and options | |
| footerDiv.querySelector('#prompt-manager-import').addEventListener('click', this.handleImport); | |
| footerDiv.querySelector('#prompt-manager-export').addEventListener('click', this.handleFullExport); | |
| } | |
| } | |
| /** | |
| * Empties, then re-assembles the prompt list | |
| */ | |
| async renderPromptManagerListItems() { | |
| if (!this.serviceSettings.prompts) return; | |
| const promptManagerList = this.listElement; | |
| promptManagerList.innerHTML = ''; | |
| const { prefix } = this.configuration; | |
| let listItemHtml = await renderTemplateAsync('promptManagerListHeader', { prefix }); | |
| this.getPromptsForCharacter(this.activeCharacter).forEach(prompt => { | |
| if (!prompt) return; | |
| const listEntry = this.getPromptOrderEntry(this.activeCharacter, prompt.identifier); | |
| const enabledClass = listEntry.enabled ? '' : `${prefix}prompt_manager_prompt_disabled`; | |
| const draggableClass = `${prefix}prompt_manager_prompt_draggable`; | |
| const markerClass = prompt.marker ? `${prefix}prompt_manager_marker` : ''; | |
| const tokens = this.tokenHandler?.getCounts()[prompt.identifier] ?? 0; | |
| // Warn the user if the chat history goes below certain token thresholds. | |
| let warningClass = ''; | |
| let warningTitle = ''; | |
| const tokenBudget = this.serviceSettings.openai_max_context - this.serviceSettings.openai_max_tokens; | |
| if (this.tokenUsage > tokenBudget * 0.8 && | |
| 'chatHistory' === prompt.identifier) { | |
| const warningThreshold = this.configuration.warningTokenThreshold; | |
| const dangerThreshold = this.configuration.dangerTokenThreshold; | |
| if (tokens <= dangerThreshold) { | |
| warningClass = 'fa-solid tooltip fa-triangle-exclamation text_danger'; | |
| warningTitle = 'Very little of your chat history is being sent, consider deactivating some other prompts.'; | |
| } else if (tokens <= warningThreshold) { | |
| warningClass = 'fa-solid tooltip fa-triangle-exclamation text_warning'; | |
| warningTitle = 'Only a few messages worth chat history are being sent.'; | |
| } | |
| } | |
| const calculatedTokens = tokens ? tokens : '-'; | |
| let detachSpanHtml = ''; | |
| if (this.isPromptDeletionAllowed(prompt)) { | |
| detachSpanHtml = ` | |
| <span title="Remove" class="prompt-manager-detach-action caution fa-solid fa-chain-broken fa-xs"></span> | |
| `; | |
| } else { | |
| detachSpanHtml = '<span class="fa-solid"></span>'; | |
| } | |
| let editSpanHtml = ''; | |
| if (this.isPromptEditAllowed(prompt)) { | |
| editSpanHtml = ` | |
| <span title="edit" class="prompt-manager-edit-action fa-solid fa-pencil fa-xs"></span> | |
| `; | |
| } else { | |
| editSpanHtml = '<span class="fa-solid"></span>'; | |
| } | |
| let toggleSpanHtml = ''; | |
| if (this.isPromptToggleAllowed(prompt)) { | |
| toggleSpanHtml = ` | |
| <span class="prompt-manager-toggle-action ${listEntry.enabled ? 'fa-solid fa-toggle-on' : 'fa-solid fa-toggle-off'}"></span> | |
| `; | |
| } else { | |
| toggleSpanHtml = '<span class="fa-solid"></span>'; | |
| } | |
| const encodedName = escapeHtml(prompt.name); | |
| const isMarkerPrompt = prompt.marker && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE; | |
| const isSystemPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && !prompt.forbid_overrides; | |
| const isImportantPrompt = !prompt.marker && prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE && prompt.forbid_overrides; | |
| const isUserPrompt = !prompt.marker && !prompt.system_prompt && prompt.injection_position !== INJECTION_POSITION.ABSOLUTE; | |
| const isInjectionPrompt = prompt.injection_position === INJECTION_POSITION.ABSOLUTE; | |
| const isOverriddenPrompt = Array.isArray(this.overriddenPrompts) && this.overriddenPrompts.includes(prompt.identifier); | |
| const importantClass = isImportantPrompt ? `${prefix}prompt_manager_important` : ''; | |
| const iconLookup = prompt.role === 'system' && (prompt.marker || prompt.system_prompt) ? '' : prompt.role; | |
| //add role icons to the right of prompt name | |
| const promptRoles = { | |
| assistant: { roleIcon: 'fa-robot', roleTitle: 'Prompt will be sent as Assistant' }, | |
| user: { roleIcon: 'fa-user', roleTitle: 'Prompt will be sent as User' }, | |
| }; | |
| const roleIcon = promptRoles[iconLookup]?.roleIcon || ''; | |
| const roleTitle = promptRoles[iconLookup]?.roleTitle || ''; | |
| listItemHtml += ` | |
| <li class="${prefix}prompt_manager_prompt ${draggableClass} ${enabledClass} ${markerClass} ${importantClass}" data-pm-identifier="${escapeHtml(prompt.identifier)}"> | |
| <span class="drag-handle">☰</span> | |
| <span class="${prefix}prompt_manager_prompt_name" data-pm-name="${encodedName}"> | |
| ${isMarkerPrompt ? '<span class="fa-fw fa-solid fa-thumb-tack" title="Marker"></span>' : ''} | |
| ${isSystemPrompt ? '<span class="fa-fw fa-solid fa-square-poll-horizontal" title="Global Prompt"></span>' : ''} | |
| ${isImportantPrompt ? '<span class="fa-fw fa-solid fa-star" title="Important Prompt"></span>' : ''} | |
| ${isUserPrompt ? '<span class="fa-fw fa-solid fa-asterisk" title="Preset Prompt"></span>' : ''} | |
| ${isInjectionPrompt ? '<span class="fa-fw fa-solid fa-syringe" title="In-Chat Injection"></span>' : ''} | |
| ${this.isPromptInspectionAllowed(prompt) ? `<a title="${encodedName}" class="prompt-manager-inspect-action">${encodedName}</a>` : `<span title="${encodedName}">${encodedName}</span>`} | |
| ${roleIcon ? `<span data-role="${escapeHtml(prompt.role)}" class="fa-xs fa-solid ${roleIcon}" title="${roleTitle}"></span>` : ''} | |
| ${isInjectionPrompt ? `<small class="prompt-manager-injection-depth">@ ${escapeHtml(prompt.injection_depth)}</small>` : ''} | |
| ${isOverriddenPrompt ? '<small class="fa-solid fa-address-card prompt-manager-overridden" title="Pulled from a character card"></small>' : ''} | |
| </span> | |
| <span> | |
| <span class="prompt_manager_prompt_controls"> | |
| ${detachSpanHtml} | |
| ${editSpanHtml} | |
| ${toggleSpanHtml} | |
| </span> | |
| </span> | |
| <span class="prompt_manager_prompt_tokens" data-pm-tokens="${calculatedTokens}"><span class="${warningClass}" title="${warningTitle}"> </span>${calculatedTokens}</span> | |
| </li> | |
| `; | |
| }); | |
| promptManagerList.insertAdjacentHTML('beforeend', listItemHtml); | |
| // Now that the new elements are in the DOM, you can add the event listeners. | |
| Array.from(promptManagerList.getElementsByClassName('prompt-manager-detach-action')).forEach(el => { | |
| el.addEventListener('click', this.handleDetach); | |
| }); | |
| Array.from(promptManagerList.getElementsByClassName('prompt-manager-inspect-action')).forEach(el => { | |
| el.addEventListener('click', this.handleInspect); | |
| }); | |
| Array.from(promptManagerList.getElementsByClassName('prompt-manager-edit-action')).forEach(el => { | |
| el.addEventListener('click', this.handleEdit); | |
| }); | |
| Array.from(promptManagerList.querySelectorAll('.prompt-manager-toggle-action')).forEach(el => { | |
| el.addEventListener('click', this.handleToggle); | |
| }); | |
| } | |
| /** | |
| * Writes the passed data to a json file | |
| * | |
| * @param data | |
| * @param type | |
| * @param name | |
| */ | |
| export(data, type, name = 'export') { | |
| const promptExport = { | |
| version: this.configuration.version, | |
| type: type, | |
| data: data, | |
| }; | |
| const serializedObject = JSON.stringify(promptExport, null, 4); | |
| const blob = new Blob([serializedObject], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const downloadLink = document.createElement('a'); | |
| downloadLink.href = url; | |
| const dateString = this.getFormattedDate(); | |
| downloadLink.download = `${name}-${dateString}.json`; | |
| downloadLink.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| /** | |
| * Imports a json file with prompts and an optional prompt list for the active character | |
| * | |
| * @param importData | |
| */ | |
| import(importData) { | |
| const mergeKeepNewer = (prompts, newPrompts) => { | |
| let merged = [...prompts, ...newPrompts]; | |
| let map = new Map(); | |
| for (let obj of merged) { | |
| map.set(obj.identifier, obj); | |
| } | |
| merged = Array.from(map.values()); | |
| return merged; | |
| }; | |
| const controlObj = { | |
| version: 1, | |
| type: '', | |
| data: { | |
| prompts: [], | |
| prompt_order: null, | |
| }, | |
| }; | |
| if (false === this.validateObject(controlObj, importData)) { | |
| toastr.warning(t`Could not import prompts. Export failed validation.`); | |
| return; | |
| } | |
| const prompts = mergeKeepNewer(this.serviceSettings.prompts, importData.data.prompts); | |
| this.setPrompts(prompts); | |
| this.log('Prompt import succeeded'); | |
| if ('global' === this.configuration.promptOrder.strategy) { | |
| const promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId }); | |
| Object.assign(promptOrder, importData.data.prompt_order); | |
| this.log('Prompt order import succeeded'); | |
| } else if ('character' === this.configuration.promptOrder.strategy) { | |
| if ('character' === importData.type) { | |
| const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); | |
| Object.assign(promptOrder, importData.data.prompt_order); | |
| this.log(`Prompt order import for character ${this.activeCharacter.name} succeeded`); | |
| } | |
| } else { | |
| throw new Error('Prompt order strategy not supported.'); | |
| } | |
| toastr.success(t`Prompt import complete.`); | |
| this.saveServiceSettings().then(() => this.render()); | |
| } | |
| /** | |
| * Helper function to check whether the structure of object matches controlObj | |
| * | |
| * @param controlObj | |
| * @param object | |
| * @returns {boolean} | |
| */ | |
| validateObject(controlObj, object) { | |
| for (let key in controlObj) { | |
| if (!Object.hasOwn(object, key)) { | |
| if (controlObj[key] === null) continue; | |
| else return false; | |
| } | |
| if (typeof controlObj[key] === 'object' && controlObj[key] !== null) { | |
| if (typeof object[key] !== 'object') return false; | |
| if (!this.validateObject(controlObj[key], object[key])) return false; | |
| } else { | |
| if (typeof object[key] !== typeof controlObj[key]) return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Get current date as mm/dd/YYYY | |
| * | |
| * @returns {`${string}_${string}_${string}`} | |
| */ | |
| getFormattedDate() { | |
| const date = new Date(); | |
| let month = String(date.getMonth() + 1); | |
| let day = String(date.getDate()); | |
| const year = String(date.getFullYear()); | |
| if (month.length < 2) month = '0' + month; | |
| if (day.length < 2) day = '0' + day; | |
| return `${month}_${day}_${year}`; | |
| } | |
| /** | |
| * Makes the prompt list draggable and handles swapping of two entries in the list. | |
| * @typedef {Object} Entry | |
| * @property {string} identifier | |
| * @returns {void} | |
| */ | |
| makeDraggable() { | |
| $(`#${this.configuration.prefix}prompt_manager_list`).sortable({ | |
| delay: this.configuration.sortableDelay, | |
| handle: isMobile() ? '.drag-handle' : null, | |
| items: `.${this.configuration.prefix}prompt_manager_prompt_draggable`, | |
| update: (event, ui) => { | |
| const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); | |
| const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', { attribute: 'data-pm-identifier' }); | |
| const idToObjectMap = new Map(promptOrder.map(prompt => [prompt.identifier, prompt])); | |
| const updatedPromptOrder = promptListElement.map(identifier => idToObjectMap.get(identifier)); | |
| this.removePromptOrderForCharacter(this.activeCharacter); | |
| this.addPromptOrderForCharacter(this.activeCharacter, updatedPromptOrder); | |
| this.log(`Prompt order updated for ${this.activeCharacter.name}.`); | |
| this.saveServiceSettings(); | |
| }, | |
| }); | |
| } | |
| /** | |
| * Slides down the edit form and adds the class 'openDrawer' to the first element of '#openai_prompt_manager_popup'. | |
| * @returns {void} | |
| */ | |
| showPopup(area = 'edit') { | |
| const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area); | |
| areaElement.style.display = 'flex'; | |
| $('#' + this.configuration.prefix + 'prompt_manager_popup').first() | |
| .slideDown(200, 'swing') | |
| .addClass('openDrawer'); | |
| } | |
| /** | |
| * Slides up the edit form and removes the class 'openDrawer' from the first element of '#openai_prompt_manager_popup'. | |
| * @returns {void} | |
| */ | |
| hidePopup() { | |
| $('#' + this.configuration.prefix + 'prompt_manager_popup').first() | |
| .slideUp(200, 'swing') | |
| .removeClass('openDrawer'); | |
| } | |
| /** | |
| * Quick uuid4 implementation | |
| * @returns {string} A string representation of an uuid4 | |
| */ | |
| getUuidv4() { | |
| return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
| let r = Math.random() * 16 | 0, | |
| v = c === 'x' ? r : (r & 0x3 | 0x8); | |
| return v.toString(16); | |
| }); | |
| } | |
| /** | |
| * Write to console with prefix | |
| * | |
| * @param output | |
| */ | |
| log(output) { | |
| if (power_user.console_log_prompts) console.log('[PromptManager] ' + output); | |
| } | |
| /** | |
| * Start a profiling task | |
| * | |
| * @param identifier | |
| */ | |
| profileStart(identifier) { | |
| if (power_user.console_log_prompts) console.time(identifier); | |
| } | |
| /** | |
| * End a profiling task | |
| * | |
| * @param identifier | |
| */ | |
| profileEnd(identifier) { | |
| if (power_user.console_log_prompts) { | |
| this.log('Profiling of "' + identifier + '" finished. Result below.'); | |
| console.timeEnd(identifier); | |
| } | |
| } | |
| } | |
| const chatCompletionDefaultPrompts = { | |
| 'prompts': [ | |
| { | |
| 'name': 'Main Prompt', | |
| 'system_prompt': true, | |
| 'role': 'system', | |
| 'content': 'Write {{char}}\'s next reply in a fictional chat between {{charIfNotGroup}} and {{user}}.', | |
| 'identifier': 'main', | |
| }, | |
| { | |
| 'name': 'Auxiliary Prompt', | |
| 'system_prompt': true, | |
| 'role': 'system', | |
| 'content': '', | |
| 'identifier': 'nsfw', | |
| }, | |
| { | |
| 'identifier': 'dialogueExamples', | |
| 'name': 'Chat Examples', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'name': 'Post-History Instructions', | |
| 'system_prompt': true, | |
| 'role': 'system', | |
| 'content': '', | |
| 'identifier': 'jailbreak', | |
| }, | |
| { | |
| 'identifier': 'chatHistory', | |
| 'name': 'Chat History', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'identifier': 'worldInfoAfter', | |
| 'name': 'World Info (after)', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'identifier': 'worldInfoBefore', | |
| 'name': 'World Info (before)', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'identifier': 'enhanceDefinitions', | |
| 'role': 'system', | |
| 'name': 'Enhance Definitions', | |
| 'content': 'If you have more knowledge of {{char}}, add to the character\'s lore and personality to enhance them but keep the Character Sheet\'s definitions absolute.', | |
| 'system_prompt': true, | |
| 'marker': false, | |
| }, | |
| { | |
| 'identifier': 'charDescription', | |
| 'name': 'Char Description', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'identifier': 'charPersonality', | |
| 'name': 'Char Personality', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'identifier': 'scenario', | |
| 'name': 'Scenario', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| { | |
| 'identifier': 'personaDescription', | |
| 'name': 'Persona Description', | |
| 'system_prompt': true, | |
| 'marker': true, | |
| }, | |
| ], | |
| }; | |
| const promptManagerDefaultPromptOrders = { | |
| 'prompt_order': [], | |
| }; | |
| const promptManagerDefaultPromptOrder = [ | |
| { | |
| 'identifier': 'main', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'worldInfoBefore', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'personaDescription', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'charDescription', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'charPersonality', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'scenario', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'enhanceDefinitions', | |
| 'enabled': false, | |
| }, | |
| { | |
| 'identifier': 'nsfw', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'worldInfoAfter', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'dialogueExamples', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'chatHistory', | |
| 'enabled': true, | |
| }, | |
| { | |
| 'identifier': 'jailbreak', | |
| 'enabled': true, | |
| }, | |
| ]; | |
| export { | |
| PromptManager, | |
| registerPromptManagerMigration, | |
| chatCompletionDefaultPrompts, | |
| promptManagerDefaultPromptOrders, | |
| Prompt, | |
| }; | |