diff --git a/public/context/Classic.json b/public/context/Classic.json index e3153831e..766e6a294 100644 --- a/public/context/Classic.json +++ b/public/context/Classic.json @@ -1,4 +1,5 @@ { + "name": "Classic", "storyString": "{{instructSystemPrompt}}\n{{wiBeforeCharacter}}\n{{description}}\n{{char}}'s personality: {{personality}}\nCircumstances and context of the dialogue: {{scenario}}\n{{wiAfterCharacter}}\nThis is how {{char}} should talk\n{{mesExamples}}\nThen the roleplay chat between {{user}} and {{char}} begins\n", "injections": [] } diff --git a/public/context/Pygmalion.json b/public/context/Pygmalion.json index ad1189fcc..c9762c090 100644 --- a/public/context/Pygmalion.json +++ b/public/context/Pygmalion.json @@ -1,4 +1,5 @@ { + "name": "Pygmalion", "storyString": "{{instructSystemPrompt}}\n{{wiBeforeCharacter}}\n{{char}}'s Persona: {{description}}\nPersonality: {{personality}}\nScenario: {{scenario}}\n{{wiAfterCharacter}}\n\n{{mesExamples}}\n\n", "injections": [] } diff --git a/public/index.html b/public/index.html index 37eb5e361..3833b8760 100644 --- a/public/index.html +++ b/public/index.html @@ -1365,13 +1365,11 @@ Context Templates
- - - + + +
@@ -2223,6 +2221,7 @@

Context Template Editor

+

@@ -2256,7 +2255,7 @@
Chat Injections - diff --git a/public/script.js b/public/script.js index 5f656da84..b46a09a31 100644 --- a/public/script.js +++ b/public/script.js @@ -141,6 +141,7 @@ import { } from "./scripts/secrets.js"; import uniqolor from "./scripts/uniqolor.js"; import { EventEmitter } from './scripts/eventemitter.js'; +import { context_settings, loadContextTemplatesFromSettings } from "./scripts/context-template.js"; //exporting functions and vars for mods export { @@ -3881,6 +3882,9 @@ async function getSettings(type) { // Load- character tags loadTagsSettings(settings); + // Load context templates + loadContextTemplatesFromSettings(data, settings); + // Set context size after loading power user (may override the max value) $("#max_context").val(max_context); $("#max_context_counter").text(`${max_context}`); @@ -3964,6 +3968,7 @@ async function saveSettings(type) { power_user: power_user, poe_settings: poe_settings, extension_settings: extension_settings, + context_settings: context_settings, tags: tags, tag_map: tag_map, ...nai_settings, @@ -4432,7 +4437,7 @@ function callPopup(text, type, inputValue = '') { $("#dialogue_popup_input").css("display", "none"); } - $("#dialogue_popup_text").html(text); + $("#dialogue_popup_text").empty().append(text); $("#shadow_popup").css("display", "block"); if (popup_type == 'input') { $("#dialogue_popup_input").focus(); diff --git a/public/scripts/context-template.js b/public/scripts/context-template.js index a59c908ab..2452adbf4 100644 --- a/public/scripts/context-template.js +++ b/public/scripts/context-template.js @@ -1,11 +1,165 @@ import { callPopup, + getRequestHeaders, + saveSettingsDebounced, } from '../script.js'; +import { debounce } from './utils.js'; + +export let context_templates = []; +export let context_settings = { + selected_template: '', +}; + +const saveTemplateDebounced = debounce((name) => alert('implement me', name), 2000); + +export function loadContextTemplatesFromSettings(data, settings) { + context_templates = data.context || []; + context_settings = Object.assign(context_settings, (settings.context_settings || {})); + + const dropdown = $('#context_template'); + dropdown.empty(); + dropdown.append('') + + for (const template of context_templates) { + const name = template.name; + const option = document.createElement('option'); + option.innerText = name; + option.value = name; + option.selected = context_settings.selected_template == name; + dropdown.append(option); + } +} + +function onContextTemplateChange() { + const value = $(this).find(':selected').val(); + context_settings.selected_template = value; + saveSettingsDebounced(); +} function openContextTemplateEditor() { + const template = context_templates.find(x => x.name == context_settings.selected_template); + + if (!template || !context_settings.selected_template) { + toastr.info('No context template selected'); + return; + } + const editor = $('#context_editor_template .context_editor').clone(); + const injectionsContainer = editor.find('.chat_injections_list'); + editor.find('.template_name').text(template.name); + editor.find('.story_string_template').text(template.storyString).on('input', function () { + const value = $(this).val(); + template.storyString = value; + saveTemplateDebounced(template.name); + }); + editor.find('.chat_injection_add').on('click', function () { + const injection = { id: Date.now(), text: '', depth: 0 }; + template.injections.push(injection); + addChatInjection(injectionsContainer, injection, template); + saveTemplateDebounced(template.name); + }); + + for (const injection of template.injections) { + addChatInjection(injectionsContainer, injection, template); + } + $('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup'); - callPopup(editor.html(), 'text'); + callPopup(editor, 'text'); +} + +async function onRenameContextTemplateClick() { + const oldName = context_settings.selected_template; + const newName = await inputTemplateName(); + const template = context_templates.find(x => x.name === oldName); + + if (!template || !newName || oldName === newName) { + return; + } + + await saveContextTemplate(newName); + context_settings.selected_template = newName; + saveSettingsDebounced(); + await deleteContextTemplate(oldName); + toastr.success('Context template renamed', newName); +} + +async function deleteContextTemplate(name) { + const response = await fetch('/delete_context_template', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ name }), + }); + + if (!response.ok) { + throw new Error('Context template not deleted'); + } +} + +async function saveContextTemplate(name) { + const template = context_templates.find(x => x.name === name); + + if (!template) { + throw new Error(`Context template not found: ${name}`); + } + + const response = await fetch('/save_context_template', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ name, template }), + }); + + if (!response.ok) { + throw new Error('Context template not saved'); + } +} + +async function inputTemplateName() { + let name = await callPopup('Enter a template name:', 'input'); + + if (!name) { + return false; + } + + name = DOMPurify.sanitize(name.trim()); + + if (context_templates.findIndex(x => x.name == name) > -1) { + toastr.warning('Template with that name already exists', 'Pick a unique name'); + return false; + } + + return name; +} + +function addChatInjection(container, model, parent) { + const template = $('#chat_injection_template .chat_injection').clone(); + template.attr('id', model.id); + template.find('.chat_injection_text').val(model.text).on('input', function () { + const value = $(this).val(); + model.text = value; + saveTemplateDebounced(parent.name); + }); + template.find('.chat_injection_depth').val(model.depth).on('input', function () { + const value = Math.abs(Number($(this).val())); + model.depth = value; + saveTemplateDebounced(parent.name); + }); + template.find('.chat_injection_remove').on('click', function () { + if (!confirm('Are you sure?')) { + return; + } + + const index = parent.injections.findIndex(x => x == model); + + if (index === -1) { + console.error('Does not compute, injection index was lost'); + return; + } + + parent.injections.splice(index, 1); + template.remove(); + saveTemplateDebounced(parent.name); + }); + container.append(template); } function copyTemplateParameter(event) { @@ -14,7 +168,47 @@ function copyTemplateParameter(event) { toastr.info('Copied!', '', { timeOut: 2000 }); } +async function onNewContextTemplateClick() { + const name = await inputTemplateName(); + + if (!name) { + return; + } + + const template = { name: name, injections: [], storyString: '' }; + context_templates.push(template); + const option = document.createElement('option'); + option.innerText = name; + option.value = name; + option.selected = true; + $('#context_template').append(option).val(name).trigger('change'); + saveTemplateDebounced(name); +} + +async function onDeleteContextTemplateClick() { + const template = context_templates.find(x => x.name == context_settings.selected_template); + + if (!template || !context_settings.selected_template) { + toastr.info('No context template selected'); + return; + } + + const confirm = await callPopup('Are you sure?', 'confirm'); + + if (!confirm) { + return; + } + + await deleteContextTemplate(context_settings.selected_template); + $(`#context_template option[value="${context_settings.selected_template}"]`).remove(); + $('#context_template').trigger('change'); +} + jQuery(() => { $('#context_template_edit').on('click', openContextTemplateEditor); + $('#context_template').on('change', onContextTemplateChange); + $('#context_template_new').on('click', onNewContextTemplateClick); + $('#context_template_rename').on('click', onRenameContextTemplateClick); + $('#context_template_delete').on('click', onDeleteContextTemplateClick); $(document).on('pointerup', '.template_parameters_list code', copyTemplateParameter); -}) \ No newline at end of file +})