mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into qolfeatures
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import {callPopup, event_types, eventSource, is_send_press, main_api, substituteParams} from "../script.js";
|
||||
"use strict";
|
||||
|
||||
import { callPopup, event_types, eventSource, is_send_press, main_api, substituteParams } from "../script.js";
|
||||
import { is_group_generating } from "./group-chats.js";
|
||||
import {TokenHandler} from "./openai.js";
|
||||
import {power_user} from "./power-user.js";
|
||||
import { TokenHandler } from "./openai.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { debounce, waitUntilCondition } from "./utils.js";
|
||||
|
||||
function debouncePromise(func, delay) {
|
||||
@ -70,7 +72,7 @@ class Prompt {
|
||||
* @param {string} param0.name - The name of the prompt.
|
||||
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
|
||||
*/
|
||||
constructor({identifier, role, content, name, system_prompt} = {}) {
|
||||
constructor({ identifier, role, content, name, system_prompt } = {}) {
|
||||
this.identifier = identifier;
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
@ -101,8 +103,8 @@ class PromptCollection {
|
||||
* @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)) {
|
||||
for (let prompt of prompts) {
|
||||
if (!(prompt instanceof Prompt)) {
|
||||
throw new Error('Only Prompt instances can be added to PromptCollection');
|
||||
}
|
||||
}
|
||||
@ -250,7 +252,7 @@ function PromptManagerModule() {
|
||||
this.handleCharacterExport = () => { };
|
||||
|
||||
/** Character reset button click*/
|
||||
this.handleCharacterReset = () => {};
|
||||
this.handleCharacterReset = () => { };
|
||||
|
||||
/** Debounced version of render */
|
||||
this.renderDebounced = debounce(this.render.bind(this), 1000);
|
||||
@ -271,6 +273,8 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
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
|
||||
@ -382,7 +386,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
|
||||
if (prompt){
|
||||
if (prompt) {
|
||||
this.appendPrompt(prompt, this.activeCharacter);
|
||||
this.saveServiceSettings().then(() => this.render());
|
||||
}
|
||||
@ -390,7 +394,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
// Delete selected prompt from list form and close edit form
|
||||
this.handleDeletePrompt = (event) => {
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value;
|
||||
const prompt = this.getPromptById(promptID);
|
||||
|
||||
if (prompt && true === this.isPromptDeletionAllowed(prompt)) {
|
||||
@ -427,7 +431,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
|
||||
let promptOrder = [];
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
promptOrder = this.getPromptOrderForCharacter({id: this.configuration.promptOrder.dummyId});
|
||||
promptOrder = this.getPromptOrderForCharacter({ id: this.configuration.promptOrder.dummyId });
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
promptOrder = [];
|
||||
} else {
|
||||
@ -490,10 +494,10 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
});
|
||||
|
||||
fileOpener.click();
|
||||
});
|
||||
fileOpener.click();
|
||||
});
|
||||
}
|
||||
|
||||
// Restore default state of a characters prompt order
|
||||
@ -556,7 +560,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
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 = () => {
|
||||
const closeAndClearPopup = () => {
|
||||
this.hidePopup();
|
||||
this.clearEditForm();
|
||||
this.clearInspectForm();
|
||||
@ -590,7 +594,7 @@ PromptManagerModule.prototype.init = function (moduleConfiguration, serviceSetti
|
||||
PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
if (main_api !== 'openai') return;
|
||||
|
||||
if (null === this.activeCharacter) 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(() => {
|
||||
@ -604,6 +608,11 @@ PromptManagerModule.prototype.render = function (afterTryGenerate = true) {
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
this.profileEnd('render');
|
||||
}).catch(error => {
|
||||
this.log('Error caught during render: ' + error);
|
||||
this.renderPromptManager();
|
||||
this.renderPromptManagerListItems()
|
||||
this.makeDraggable();
|
||||
});
|
||||
} else {
|
||||
// Executed during live communication
|
||||
@ -652,7 +661,7 @@ PromptManagerModule.prototype.updatePrompts = function (prompts) {
|
||||
})
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.getTokenHandler = function() {
|
||||
PromptManagerModule.prototype.getTokenHandler = function () {
|
||||
return this.tokenHandler;
|
||||
}
|
||||
|
||||
@ -666,7 +675,7 @@ PromptManagerModule.prototype.appendPrompt = function (prompt, character) {
|
||||
const promptOrder = this.getPromptOrderForCharacter(character);
|
||||
const index = promptOrder.findIndex(entry => entry.identifier === prompt.identifier);
|
||||
|
||||
if (-1 === index) promptOrder.push({identifier: prompt.identifier, enabled: false});
|
||||
if (-1 === index) promptOrder.push({ identifier: prompt.identifier, enabled: false });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -713,7 +722,7 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
this.serviceSettings.prompt_order = this.serviceSettings.prompt_order ?? [];
|
||||
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const dummyCharacter = {id: this.configuration.promptOrder.dummyId};
|
||||
const dummyCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
const promptOrder = this.getPromptOrderForCharacter(dummyCharacter);
|
||||
|
||||
if (0 === promptOrder.length) this.addPromptOrderForCharacter(dummyCharacter, promptManagerDefaultPromptOrder);
|
||||
@ -729,11 +738,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
|
||||
if (this.activeCharacter) {
|
||||
const promptReferences = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
for(let i = promptReferences.length - 1; i >= 0; i--) {
|
||||
const reference = promptReferences[i];
|
||||
if(-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) {
|
||||
for (let i = promptReferences.length - 1; i >= 0; i--) {
|
||||
const reference = promptReferences[i];
|
||||
if (-1 === this.serviceSettings.prompts.findIndex(prompt => prompt.identifier === reference.identifier)) {
|
||||
promptReferences.splice(i, 1);
|
||||
this.log('Removed unused reference: ' + reference.identifier);
|
||||
this.log('Removed unused reference: ' + reference.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -745,11 +754,11 @@ PromptManagerModule.prototype.sanitizeServiceSettings = function () {
|
||||
*
|
||||
* @param prompts
|
||||
*/
|
||||
PromptManagerModule.prototype.checkForMissingPrompts = function(prompts) {
|
||||
const defaultPromptIdentifiers = chatCompletionDefaultPrompts.prompts.reduce((list, prompt) => { list.push(prompt.identifier); return list;}, []);
|
||||
PromptManagerModule.prototype.checkForMissingPrompts = function (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)
|
||||
!prompts.some(prompt => prompt.identifier === identifier)
|
||||
);
|
||||
|
||||
missingIdentifiers.forEach(identifier => {
|
||||
@ -815,10 +824,10 @@ PromptManagerModule.prototype.handleCharacterDeleted = function (event) {
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterSelected = function (event) {
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
|
||||
} else if ('character' === 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};
|
||||
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.
|
||||
@ -836,11 +845,11 @@ PromptManagerModule.prototype.handleCharacterSelected = function (event) {
|
||||
*/
|
||||
PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = {id: event.detail.id, ...event.detail.character};
|
||||
this.activeCharacter = { id: event.detail.id, ...event.detail.character };
|
||||
} else {
|
||||
throw new Error ('Prompt order strategy not supported.')
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
@ -851,15 +860,15 @@ PromptManagerModule.prototype.handleCharacterUpdated = function (event) {
|
||||
*/
|
||||
PromptManagerModule.prototype.handleGroupSelected = function (event) {
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
this.activeCharacter = {id: this.configuration.promptOrder.dummyId};
|
||||
this.activeCharacter = { id: this.configuration.promptOrder.dummyId };
|
||||
} else if ('character' === this.configuration.promptOrder.strategy) {
|
||||
const characterDummy = {id: event.detail.id, group: event.detail.group};
|
||||
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.')
|
||||
throw new Error('Prompt order strategy not supported.')
|
||||
}
|
||||
}
|
||||
|
||||
@ -868,7 +877,7 @@ PromptManagerModule.prototype.handleGroupSelected = function (event) {
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
PromptManagerModule.prototype.getActiveGroupCharacters = function() {
|
||||
PromptManagerModule.prototype.getActiveGroupCharacters = function () {
|
||||
// ToDo: Ideally, this should return the actual characters.
|
||||
return (this.activeCharacter?.group?.members || []).map(member => member && member.substring(0, member.lastIndexOf('.')));
|
||||
}
|
||||
@ -982,7 +991,7 @@ PromptManagerModule.prototype.preparePrompt = function (prompt, original = null)
|
||||
* and handle input events to update the prompt content.
|
||||
*
|
||||
*/
|
||||
PromptManagerModule.prototype.createQuickEdit = function(identifier, title) {
|
||||
PromptManagerModule.prototype.createQuickEdit = function (identifier, title) {
|
||||
const prompt = this.getPromptById(identifier);
|
||||
const textareaIdentifier = `${identifier}_prompt_quick_edit_textarea`;
|
||||
const html = `<div class="range-block m-t-1">
|
||||
@ -1006,7 +1015,7 @@ PromptManagerModule.prototype.createQuickEdit = function(identifier, title) {
|
||||
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) {
|
||||
PromptManagerModule.prototype.updateQuickEdit = function (identifier, prompt) {
|
||||
const textarea = document.getElementById(`${identifier}_prompt_quick_edit_textarea`);
|
||||
textarea.value = prompt.content;
|
||||
}
|
||||
@ -1018,13 +1027,13 @@ PromptManagerModule.prototype.updateQuickEdit = function(identifier, prompt) {
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PromptManagerModule.prototype.isValidName = function(name) {
|
||||
PromptManagerModule.prototype.isValidName = function (name) {
|
||||
const regex = /^[a-zA-Z0-9_]{1,64}$/;
|
||||
|
||||
return regex.test(name);
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.sanitizeName = function(name) {
|
||||
PromptManagerModule.prototype.sanitizeName = function (name) {
|
||||
return name.replace(/[^a-zA-Z0-9_]/g, '_').substring(0, 64);
|
||||
}
|
||||
|
||||
@ -1111,7 +1120,7 @@ PromptManagerModule.prototype.clearEditForm = function () {
|
||||
roleField.disabled = false;
|
||||
}
|
||||
|
||||
PromptManagerModule.prototype.clearInspectForm = function() {
|
||||
PromptManagerModule.prototype.clearInspectForm = function () {
|
||||
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');
|
||||
@ -1150,7 +1159,7 @@ PromptManagerModule.prototype.setMessages = function (messages) {
|
||||
*
|
||||
* @param {ChatCompletion} chatCompletion
|
||||
*/
|
||||
PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) {
|
||||
PromptManagerModule.prototype.setChatCompletion = function (chatCompletion) {
|
||||
const messages = chatCompletion.getMessages();
|
||||
|
||||
this.setMessages(messages);
|
||||
@ -1163,7 +1172,7 @@ PromptManagerModule.prototype.setChatCompletion = function(chatCompletion) {
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
*/
|
||||
PromptManagerModule.prototype.populateTokenCounts = function(messages) {
|
||||
PromptManagerModule.prototype.populateTokenCounts = function (messages) {
|
||||
this.tokenHandler.resetCounts();
|
||||
const counts = this.tokenHandler.getCounts();
|
||||
messages.getCollection().forEach(message => {
|
||||
@ -1182,7 +1191,7 @@ PromptManagerModule.prototype.populateTokenCounts = function(messages) {
|
||||
*
|
||||
* @param {MessageCollection} messages
|
||||
*/
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function(messages) {
|
||||
PromptManagerModule.prototype.populateLegacyTokenCounts = function (messages) {
|
||||
// Update general token counts
|
||||
const chatHistory = messages.getItemByIdentifier('chatHistory');
|
||||
const startChat = chatHistory?.getCollection()[0].getTokens() || 0;
|
||||
@ -1246,8 +1255,8 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
</select>
|
||||
<a class="menu_button fa-chain fa-solid" title="Insert prompt" data-i18n="Insert"></a>
|
||||
<a class="caution menu_button fa-x fa-solid" title="Delete prompt" data-i18n="Delete"></a>
|
||||
<a class="menu_button fa-file-arrow-down fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="Export"></a>
|
||||
<a class="menu_button fa-file-arrow-up fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="Import"></a>
|
||||
<a class="menu_button fa-file-import fa-solid" id="prompt-manager-import" title="Import a prompt list" data-i18n="Import"></a>
|
||||
<a class="menu_button fa-file-export fa-solid" id="prompt-manager-export" title="Export this prompt list" data-i18n="Export"></a>
|
||||
<a class="menu_button fa-undo fa-solid" id="prompt-manager-reset-character" title="Reset current character" data-i18n="Reset current character"></a>
|
||||
<a class="menu_button fa-plus-square fa-solid" title="New prompt" data-i18n="New"></a>
|
||||
</div>
|
||||
@ -1270,9 +1279,9 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
<a class="export-promptmanager-prompts-full list-group-item" data-i18n="Export all">Export all</a>
|
||||
<span class="tooltip fa-solid fa-info-circle" title="Export all your prompts to a file"></span>
|
||||
</div>
|
||||
${ 'global' === this.configuration.promptOrder.strategy
|
||||
? ''
|
||||
: `<div class="row">
|
||||
${'global' === this.configuration.promptOrder.strategy
|
||||
? ''
|
||||
: `<div class="row">
|
||||
<a class="export-promptmanager-prompts-character list-group-item" data-i18n="Export for character">Export
|
||||
for character</a>
|
||||
<span class="tooltip fa-solid fa-info-circle"
|
||||
@ -1287,7 +1296,7 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
let exportPopper = Popper.createPopper(
|
||||
document.getElementById('prompt-manager-export'),
|
||||
document.getElementById('prompt-manager-export-format-popup'),
|
||||
{placement: 'bottom'}
|
||||
{ placement: 'bottom' }
|
||||
);
|
||||
|
||||
const showExportSelection = () => {
|
||||
@ -1306,14 +1315,46 @@ PromptManagerModule.prototype.renderPromptManager = function () {
|
||||
rangeBlockDiv.querySelector('.export-promptmanager-prompts-character')?.addEventListener('click', this.handleCharacterExport);
|
||||
|
||||
const quickEditContainer = document.getElementById('quick-edit-container');
|
||||
const heights = this.saveTextAreaHeights(quickEditContainer);
|
||||
quickEditContainer.innerHTML = '';
|
||||
|
||||
this.createQuickEdit('jailbreak', 'Jailbreak');
|
||||
this.createQuickEdit('nsfw', 'NSFW');
|
||||
this.createQuickEdit('main', 'Main');
|
||||
|
||||
this.restoreTextAreaHeights(quickEditContainer, heights);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores the height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @param heights An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.restoreTextAreaHeights = function(container, heights) {
|
||||
if (Object.keys(heights).length === 0) return;
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
const height = heights[this.id];
|
||||
if (height > 0) $(this).height(height);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current height of each textarea in the container
|
||||
* @param container The container to search for textareas
|
||||
* @returns {{}} An object with textarea ids as keys and heights as values
|
||||
*/
|
||||
PromptManagerModule.prototype.saveTextAreaHeights = function(container) {
|
||||
const heights = {};
|
||||
|
||||
$(container).find('textarea').each(function () {
|
||||
heights[this.id] = $(this).height();
|
||||
});
|
||||
|
||||
return heights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties, then re-assembles the prompt list
|
||||
*/
|
||||
@ -1323,7 +1364,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
const promptManagerList = this.listElement;
|
||||
promptManagerList.innerHTML = '';
|
||||
|
||||
const {prefix} = this.configuration;
|
||||
const { prefix } = this.configuration;
|
||||
|
||||
let listItemHtml = `
|
||||
<li class="${prefix}prompt_manager_list_head">
|
||||
@ -1350,7 +1391,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
let warningTitle = '';
|
||||
|
||||
const tokenBudget = this.serviceSettings.openai_max_context - this.serviceSettings.openai_max_tokens;
|
||||
if ( this.tokenUsage > tokenBudget * 0.8 &&
|
||||
if (this.tokenUsage > tokenBudget * 0.8 &&
|
||||
'chatHistory' === prompt.identifier) {
|
||||
const warningThreshold = this.configuration.warningTokenThreshold;
|
||||
const dangerThreshold = this.configuration.dangerTokenThreshold;
|
||||
@ -1399,7 +1440,7 @@ PromptManagerModule.prototype.renderPromptManagerListItems = function () {
|
||||
${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>' : ''}
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name }
|
||||
${this.isPromptInspectionAllowed(prompt) ? `<a class="prompt-manager-inspect-action">${prompt.name}</a>` : prompt.name}
|
||||
</span>
|
||||
<span>
|
||||
<span class="prompt_manager_prompt_controls">
|
||||
@ -1449,7 +1490,7 @@ PromptManagerModule.prototype.export = function (data, type, name = 'export') {
|
||||
};
|
||||
|
||||
const serializedObject = JSON.stringify(promptExport);
|
||||
const blob = new Blob([serializedObject], {type: "application/json"});
|
||||
const blob = new Blob([serializedObject], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.href = url;
|
||||
@ -1502,7 +1543,7 @@ PromptManagerModule.prototype.import = function (importData) {
|
||||
|
||||
let promptOrder = [];
|
||||
if ('global' === this.configuration.promptOrder.strategy) {
|
||||
const promptOrder = this.getPromptOrderForCharacter({id: this.configuration.promptOrder.dummyId});
|
||||
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) {
|
||||
@ -1526,7 +1567,7 @@ PromptManagerModule.prototype.import = function (importData) {
|
||||
* @param object
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PromptManagerModule.prototype.validateObject = function(controlObj, object) {
|
||||
PromptManagerModule.prototype.validateObject = function (controlObj, object) {
|
||||
for (let key in controlObj) {
|
||||
if (!object.hasOwnProperty(key)) {
|
||||
if (controlObj[key] === null) continue;
|
||||
@ -1549,7 +1590,7 @@ PromptManagerModule.prototype.validateObject = function(controlObj, object) {
|
||||
*
|
||||
* @returns {`${string}_${string}_${string}`}
|
||||
*/
|
||||
PromptManagerModule.prototype.getFormattedDate = function() {
|
||||
PromptManagerModule.prototype.getFormattedDate = function () {
|
||||
const date = new Date();
|
||||
let month = String(date.getMonth() + 1);
|
||||
let day = String(date.getDate());
|
||||
@ -1571,9 +1612,9 @@ PromptManagerModule.prototype.makeDraggable = function () {
|
||||
$(`#${this.configuration.prefix}prompt_manager_list`).sortable({
|
||||
delay: this.configuration.sortableDelay,
|
||||
items: `.${this.configuration.prefix}prompt_manager_prompt_draggable`,
|
||||
update: ( event, ui ) => {
|
||||
update: (event, ui) => {
|
||||
const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter);
|
||||
const promptListElement = $(`#${this.configuration.prefix}prompt_manager_list`).sortable('toArray', {attribute: 'data-pm-identifier'});
|
||||
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));
|
||||
|
||||
@ -1583,7 +1624,8 @@ PromptManagerModule.prototype.makeDraggable = function () {
|
||||
this.log(`Prompt order updated for ${this.activeCharacter.name}.`);
|
||||
|
||||
this.saveServiceSettings();
|
||||
}});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1594,7 +1636,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') {
|
||||
const areaElement = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_' + area);
|
||||
areaElement.style.display = 'block';
|
||||
|
||||
$('#'+this.configuration.prefix +'prompt_manager_popup').first()
|
||||
$('#' + this.configuration.prefix + 'prompt_manager_popup').first()
|
||||
.slideDown(200, "swing")
|
||||
.addClass('openDrawer');
|
||||
}
|
||||
@ -1604,7 +1646,7 @@ PromptManagerModule.prototype.showPopup = function (area = 'edit') {
|
||||
* @returns {void}
|
||||
*/
|
||||
PromptManagerModule.prototype.hidePopup = function () {
|
||||
$('#'+this.configuration.prefix +'prompt_manager_popup').first()
|
||||
$('#' + this.configuration.prefix + 'prompt_manager_popup').first()
|
||||
.slideUp(200, "swing")
|
||||
.removeClass('openDrawer');
|
||||
}
|
||||
|
@ -33,12 +33,12 @@ import {
|
||||
} from "./power-user.js";
|
||||
|
||||
import { LoadLocal, SaveLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups } from "./group-chats.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups, openGroupById } from "./group-chats.js";
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder, debounce, delay } from "./utils.js";
|
||||
import { debounce, delay } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
@ -354,10 +354,8 @@ async function RA_autoloadchat() {
|
||||
selectCharacterById(String(active_character_id));
|
||||
}
|
||||
|
||||
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
|
||||
|
||||
if (groupToAutoLoad != null) {
|
||||
$(groupToAutoLoad).click();
|
||||
if (active_group != null) {
|
||||
openGroupById(String(active_group));
|
||||
}
|
||||
|
||||
// if the character list hadn't been loaded yet, try again.
|
||||
@ -395,17 +393,18 @@ export async function favsToHotswap() {
|
||||
slot.attr('grid', isGroup ? grid : '');
|
||||
slot.attr('chid', isCharacter ? chid : '');
|
||||
slot.data('id', isGroup ? grid : chid);
|
||||
slot.attr('title', '');
|
||||
|
||||
if (isGroup) {
|
||||
const group = groups.find(x => x.id === grid);
|
||||
const avatar = getGroupAvatar(group);
|
||||
$(slot).find('img').replaceWith(avatar);
|
||||
$(slot).attr('title', group.name);
|
||||
}
|
||||
|
||||
if (isCharacter) {
|
||||
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
|
||||
$(slot).find('img').attr('src', avatarUrl);
|
||||
$(slot).attr('title', entity.item.avatar);
|
||||
}
|
||||
|
||||
$(slot).css('cursor', 'pointer');
|
||||
@ -933,14 +932,16 @@ $("document").ready(function () {
|
||||
|
||||
// when a char is selected from the list, save their name as the auto-load character for next page load
|
||||
$(document).on("click", ".character_select", function () {
|
||||
setActiveCharacter($(this).find('.avatar').attr('title'));
|
||||
const characterId = $(this).find('.avatar').attr('title') || $(this).attr('title');
|
||||
setActiveCharacter(characterId);
|
||||
setActiveGroup(null);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on("click", ".group_select", function () {
|
||||
const groupId = $(this).data('id') || $(this).attr('grid');
|
||||
setActiveCharacter(null);
|
||||
setActiveGroup($(this).data('id'));
|
||||
setActiveGroup(groupId);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {
|
||||
characters,
|
||||
saveChat,
|
||||
sendSystemMessage,
|
||||
system_messages,
|
||||
system_message_types,
|
||||
this_chid,
|
||||
@ -19,6 +18,7 @@ import {
|
||||
getGroupPastChats,
|
||||
group_activation_strategy,
|
||||
groups,
|
||||
openGroupById,
|
||||
openGroupChat,
|
||||
saveGroupBookmarkChat,
|
||||
selected_group,
|
||||
@ -301,13 +301,12 @@ async function convertSoloToGroupChat() {
|
||||
}
|
||||
|
||||
// Click on the freshly selected group to open it
|
||||
$(`.group_select[grid="${group.id}"]`).click();
|
||||
await openGroupById(group.id);
|
||||
|
||||
await delay(1);
|
||||
toastr.success('The chat has been successfully converted!');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
jQuery(function () {
|
||||
$('#option_new_bookmark').on('click', saveBookmarkMenu);
|
||||
$('#option_back_to_main').on('click', backToMainChat);
|
||||
$('#option_convert_to_group').on('click', convertSoloToGroupChat);
|
||||
|
@ -499,7 +499,7 @@ async function moduleWorker() {
|
||||
const context = getContext();
|
||||
|
||||
// non-characters not supported
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
if (!context.groupId && (context.characterId === undefined || context.characterId === 'invalid-safety-id')) {
|
||||
removeExpression();
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
substituteParams,
|
||||
saveSettingsDebounced,
|
||||
systemUserName,
|
||||
hideSwipeButtons,
|
||||
@ -14,7 +13,8 @@ import {
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename } from "../../utils.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename, saveBase64AsFile } from "../../utils.js";
|
||||
import { humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
@ -512,7 +512,7 @@ function getQuietPrompt(mode, trigger) {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
return substituteParams(stringFormat(extension_settings.sd.prompts[mode], trigger));
|
||||
return stringFormat(extension_settings.sd.prompts[mode], trigger);
|
||||
}
|
||||
|
||||
function processReply(str) {
|
||||
@ -537,6 +537,7 @@ function processReply(str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
function getRawLastMessage() {
|
||||
const context = getContext();
|
||||
const lastMessage = context.chat.slice(-1)[0].mes,
|
||||
@ -565,6 +566,10 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
const quiet_prompt = getQuietPrompt(generationType, trigger);
|
||||
const context = getContext();
|
||||
|
||||
// if context.characterId is not null, then we get context.characters[context.characterId].avatar, else we get groupId and context.groups[groupId].id
|
||||
// sadly, groups is not an array, but is a dict with keys being index numbers, so we have to filter it
|
||||
const characterName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]].id.toString();
|
||||
|
||||
const prevSDHeight = extension_settings.sd.height;
|
||||
const prevSDWidth = extension_settings.sd.width;
|
||||
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
|
||||
@ -580,8 +585,10 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64;
|
||||
const callbackOriginal = callback;
|
||||
callback = function (prompt, base64Image) {
|
||||
const imgUrl = `url(${base64Image})`;
|
||||
callback = async function (prompt, base64Image) {
|
||||
const imagePath = base64Image;
|
||||
const imgUrl = `url('${encodeURIComponent(base64Image)}')`;
|
||||
|
||||
if ('forceSetBackground' in window) {
|
||||
forceSetBackground(imgUrl);
|
||||
} else {
|
||||
@ -590,9 +597,9 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
}
|
||||
|
||||
if (typeof callbackOriginal === 'function') {
|
||||
callbackOriginal(prompt, base64Image);
|
||||
callbackOriginal(prompt, imagePath);
|
||||
} else {
|
||||
sendMessage(prompt, base64Image);
|
||||
sendMessage(prompt, imagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -604,7 +611,7 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
context.deactivateSendButtons();
|
||||
hideSwipeButtons();
|
||||
|
||||
await sendGenerationRequest(generationType, prompt, callback);
|
||||
await sendGenerationRequest(generationType, prompt, characterName, callback);
|
||||
} catch (err) {
|
||||
console.trace(err);
|
||||
throw new Error('SD prompt text generation failed.')
|
||||
@ -644,19 +651,31 @@ async function generatePrompt(quiet_prompt) {
|
||||
return processReply(reply);
|
||||
}
|
||||
|
||||
async function sendGenerationRequest(generationType, prompt, callback) {
|
||||
async function sendGenerationRequest(generationType, prompt, characterName = null, callback) {
|
||||
const prefix = generationType !== generationMode.BACKGROUND
|
||||
? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix())
|
||||
: extension_settings.sd.prompt_prefix;
|
||||
|
||||
if (extension_settings.sd.horde) {
|
||||
await generateHordeImage(prompt, prefix, callback);
|
||||
await generateHordeImage(prompt, prefix, characterName, callback);
|
||||
} else {
|
||||
await generateExtrasImage(prompt, prefix, callback);
|
||||
await generateExtrasImage(prompt, prefix, characterName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateExtrasImage(prompt, prefix, callback) {
|
||||
/**
|
||||
* Generates an "extras" image using a provided prompt and other settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
||||
console.debug(extension_settings.sd);
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/image';
|
||||
@ -680,14 +699,28 @@ async function generateExtrasImage(prompt, prefix, callback) {
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
const base64Image = `data:image/jpeg;base64,${data.image}`;
|
||||
//filename should be character name + human readable timestamp + generation mode
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data.image, characterName, filename, "jpg");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
} else {
|
||||
callPopup('Image generation has failed. Please try again.', 'text');
|
||||
}
|
||||
}
|
||||
|
||||
async function generateHordeImage(prompt, prefix, callback) {
|
||||
/**
|
||||
* Generates a "horde" image using the provided prompt and configuration settings,
|
||||
* then saves the generated image and either invokes a callback or sends a message with the image.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} prefix - Additional context or prefix to guide the image generation.
|
||||
* @param {string} characterName - The name used to determine the sub-directory for saving.
|
||||
* @param {function} [callback] - Optional callback function invoked with the prompt and saved image.
|
||||
* If not provided, `sendMessage` is called instead.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||
const result = await fetch('/horde_generateimage', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
@ -709,7 +742,8 @@ async function generateHordeImage(prompt, prefix, callback) {
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.text();
|
||||
const base64Image = `data:image/webp;base64,${data}`;
|
||||
const filename = `${characterName}_${humanizedDateTime()}`;
|
||||
const base64Image = await saveBase64AsFile(data, characterName, filename, "webp");
|
||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||
} else {
|
||||
toastr.error('Image generation has failed. Please try again.');
|
||||
@ -827,7 +861,7 @@ async function sdMessageButton(e) {
|
||||
const message_id = $mes.attr('mesid');
|
||||
const message = context.chat[message_id];
|
||||
const characterName = message?.name || context.name2;
|
||||
const messageText = substituteParams(message?.mes);
|
||||
const messageText = message?.mes;
|
||||
const hasSavedImage = message?.extra?.image && message?.extra?.title;
|
||||
|
||||
if ($icon.hasClass(busyClass)) {
|
||||
@ -842,7 +876,7 @@ async function sdMessageButton(e) {
|
||||
message.extra.title = prompt;
|
||||
|
||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, saveGeneratedImage);
|
||||
await sendGenerationRequest(generationMode.FREE, prompt, characterName, saveGeneratedImage);
|
||||
}
|
||||
else {
|
||||
console.log("doing /sd raw last");
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
isDataURL,
|
||||
createThumbnail,
|
||||
extractAllWords,
|
||||
saveBase64AsFile
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
@ -63,8 +64,8 @@ import {
|
||||
getCropPopup,
|
||||
system_avatar,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
import { FilterHelper } from './filters.js';
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map, printTagFilters } from './tags.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
|
||||
export {
|
||||
selected_group,
|
||||
@ -309,6 +310,9 @@ async function getGroups() {
|
||||
|
||||
// Convert groups to new format
|
||||
for (const group of groups) {
|
||||
if (typeof group.id === 'number') {
|
||||
group.id = String(group.id);
|
||||
}
|
||||
if (group.disabled_members == undefined) {
|
||||
group.disabled_members = [];
|
||||
}
|
||||
@ -334,25 +338,25 @@ async function getGroups() {
|
||||
}
|
||||
|
||||
export function getGroupBlock(group) {
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
template.find(".ch_name").html(group.name).css('color', group.fav ? 'gold' : 'white');
|
||||
template.find('.group_fav_icon').css("display", 'none');
|
||||
template.addClass(group.fav ? 'is_fav' : '');
|
||||
template.find(".ch_fav").val(group.fav);
|
||||
const template = $("#group_list_template .group_select").clone();
|
||||
template.data("id", group.id);
|
||||
template.attr("grid", group.id);
|
||||
template.find(".ch_name").text(group.name);
|
||||
template.find('.group_fav_icon').css("display", 'none');
|
||||
template.addClass(group.fav ? 'is_fav' : '');
|
||||
template.find(".ch_fav").val(group.fav);
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
$(template).find(".avatar").replaceWith(avatar);
|
||||
}
|
||||
|
||||
return template;
|
||||
return template;
|
||||
}
|
||||
|
||||
function updateGroupAvatar(group) {
|
||||
@ -360,17 +364,27 @@ function updateGroupAvatar(group) {
|
||||
|
||||
$(".group_select").each(function () {
|
||||
if ($(this).data("id") == group.id) {
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
$(this).find(".avatar").replaceWith(getGroupAvatar(group));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// check if isDataURLor if it's a valid local file url
|
||||
function isValidImageUrl(url) {
|
||||
console.trace(url);
|
||||
// check if empty dict
|
||||
if (Object.keys(url).length === 0) {
|
||||
return false;
|
||||
}
|
||||
return isDataURL(url) || (url && url.startsWith("user"));
|
||||
}
|
||||
|
||||
function getGroupAvatar(group) {
|
||||
if (!group) {
|
||||
return $(`<div class="avatar"><img src="${default_avatar}"></div>`);
|
||||
}
|
||||
|
||||
if (isDataURL(group.avatar_url)) {
|
||||
// if isDataURL or if it's a valid local file url
|
||||
if (isValidImageUrl(group.avatar_url)) {
|
||||
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
|
||||
}
|
||||
|
||||
@ -1090,8 +1104,7 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
|
||||
setMenuType(!!group ? 'group_edit' : 'group_create');
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
|
||||
$("#rm_group_chat_name").val(groupName);
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isValidImageUrl(group.avatar_url));
|
||||
$("#rm_group_filter").val("").trigger("input");
|
||||
$(`input[name="rm_group_activation_strategy"][value="${replyStrategy}"]`).prop('checked', true);
|
||||
|
||||
@ -1133,9 +1146,18 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_automode_label").hide();
|
||||
}
|
||||
|
||||
eventSource.emit('groupSelected', {detail: {id: openGroupId, group: group}});
|
||||
eventSource.emit('groupSelected', { detail: { id: openGroupId, group: group } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the upload and processing of a group avatar.
|
||||
* The selected image is read, cropped using a popup, processed into a thumbnail,
|
||||
* and then uploaded to the server.
|
||||
*
|
||||
* @param {Event} event - The event triggered by selecting a file input, containing the image file to upload.
|
||||
*
|
||||
* @returns {Promise<void>} - A promise that resolves when the processing and upload is complete.
|
||||
*/
|
||||
async function uploadGroupAvatar(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
@ -1158,16 +1180,22 @@ async function uploadGroupAvatar(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
|
||||
let thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
//remove data:image/whatever;base64
|
||||
thumbnail = thumbnail.replace(/^data:image\/[a-z]+;base64,/, "");
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
// filename should be group id + human readable timestamp
|
||||
const filename = `${_thisGroup.id}_${humanizedDateTime()}`;
|
||||
let thumbnailUrl = await saveBase64AsFile(thumbnail, openGroupId.toString(), filename, 'jpg');
|
||||
if (!openGroupId) {
|
||||
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||
$('#group_avatar_preview img').attr('src', thumbnailUrl);
|
||||
$('#rm_group_restore_avatar').show();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.avatar_url = thumbnail;
|
||||
|
||||
|
||||
_thisGroup.avatar_url = thumbnailUrl;
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").show();
|
||||
await editGroup(openGroupId, true, true);
|
||||
@ -1248,8 +1276,11 @@ function updateFavButtonState(state) {
|
||||
$("#group_favorite_button").toggleClass('fav_off', !fav_grp_checked);
|
||||
}
|
||||
|
||||
async function selectGroup() {
|
||||
const groupId = $(this).data("id");
|
||||
export async function openGroupById(groupId) {
|
||||
if (!groups.find(x => x.id === groupId)) {
|
||||
console.log('Group not found', groupId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_send_press && !is_group_generating) {
|
||||
if (selected_group !== groupId) {
|
||||
@ -1290,16 +1321,8 @@ function openCharacterDefinition(characterSelect) {
|
||||
}
|
||||
|
||||
function filterGroupMembers() {
|
||||
const searchValue = $(this).val().trim().toLowerCase();
|
||||
|
||||
if (!searchValue) {
|
||||
$("#rm_group_add_members .group_member").removeClass('hiddenBySearch');
|
||||
} else {
|
||||
$("#rm_group_add_members .group_member").each(function () {
|
||||
const isValidSearch = $(this).find(".ch_name").text().toLowerCase().includes(searchValue);
|
||||
$(this).toggleClass('hiddenBySearch', !isValidSearch);
|
||||
});
|
||||
}
|
||||
const searchValue = $(this).val().toLowerCase();
|
||||
groupCandidatesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
|
||||
}
|
||||
|
||||
async function createGroup() {
|
||||
@ -1324,7 +1347,7 @@ async function createGroup() {
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
members: members,
|
||||
avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
|
||||
avatar_url: isValidImageUrl(avatar_url) ? avatar_url : default_avatar,
|
||||
allow_self_responses: allow_self_responses,
|
||||
activation_strategy: activation_strategy,
|
||||
disabled_members: [],
|
||||
@ -1570,7 +1593,10 @@ function doCurMemberListPopout() {
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$(document).on("click", ".group_select", selectGroup);
|
||||
$(document).on("click", ".group_select", function () {
|
||||
const groupId = $(this).data("id");
|
||||
openGroupById(groupId);
|
||||
});
|
||||
$("#rm_group_filter").on("input", filterGroupMembers);
|
||||
$("#rm_group_submit").on("click", createGroup);
|
||||
$("#rm_group_scenario").on("click", setScenarioOverride);
|
||||
|
@ -578,7 +578,7 @@ function calculateLogitBias() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms instruction into compatible format for Novel AI.
|
||||
* Transforms instruction into compatible format for Novel AI if Novel AI instruct format not already detected.
|
||||
* 1. Instruction must begin and end with curly braces followed and preceded by a space.
|
||||
* 2. Instruction must not contain square brackets as it serves different purpose in NAI.
|
||||
* @param {string} prompt Original instruction prompt
|
||||
@ -586,7 +586,10 @@ function calculateLogitBias() {
|
||||
*/
|
||||
export function adjustNovelInstructionPrompt(prompt) {
|
||||
const stripedPrompt = prompt.replace(/[\[\]]/g, '').trim();
|
||||
return `{ ${stripedPrompt} }`;
|
||||
if (!stripedPrompt.includes('{ ')) {
|
||||
return `{ ${stripedPrompt} }`;
|
||||
}
|
||||
return stripedPrompt;
|
||||
}
|
||||
|
||||
export async function generateNovelWithStreaming(generate_data, signal) {
|
||||
|
@ -19,7 +19,6 @@ import {
|
||||
system_message_types,
|
||||
replaceBiasMarkup,
|
||||
is_send_press,
|
||||
saveSettings,
|
||||
Generate,
|
||||
main_api,
|
||||
eventSource,
|
||||
@ -45,6 +44,7 @@ import {
|
||||
} from "./secrets.js";
|
||||
|
||||
import {
|
||||
deepClone,
|
||||
delay,
|
||||
download,
|
||||
getFileText, getSortableDelay,
|
||||
@ -390,7 +390,7 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
promptManager.tokenHandler = tokenHandler;
|
||||
|
||||
promptManager.init(configuration, openAiSettings);
|
||||
promptManager.render();
|
||||
promptManager.render(false);
|
||||
|
||||
return promptManager;
|
||||
}
|
||||
@ -480,11 +480,19 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
// 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(', ')) || '';
|
||||
// Reserve budget for new chat message
|
||||
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
|
||||
const newChatMessage = new Message('system', newChat, 'newMainChat');
|
||||
const newChatMessage = new Message('system', substituteParams(newChat, null, null, null, names), 'newMainChat');
|
||||
chatCompletion.reserveBudget(newChatMessage);
|
||||
|
||||
// Reserve budget for group nudge
|
||||
let groupNudgeMessage = null;
|
||||
if(selected_group) {
|
||||
const groupNudgeMessage = new Message.fromPrompt(prompts.get('groupNudge'));
|
||||
chatCompletion.reserveBudget(groupNudgeMessage);
|
||||
}
|
||||
|
||||
// Reserve budget for continue nudge
|
||||
let continueMessage = null;
|
||||
if (type === 'continue' && cyclePrompt) {
|
||||
@ -513,7 +521,8 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt));
|
||||
|
||||
if (true === promptManager.serviceSettings.names_in_completion && prompt.name) {
|
||||
chatMessage.name = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
|
||||
chatMessage.setName(messageName);
|
||||
}
|
||||
|
||||
if (chatCompletion.canAfford(chatMessage)) chatCompletion.insertAtStart(chatMessage, 'chatHistory');
|
||||
@ -525,6 +534,12 @@ function populateChatHistory(prompts, chatCompletion, type = null, cyclePrompt =
|
||||
chatCompletion.freeBudget(newChatMessage);
|
||||
chatCompletion.insertAtStart(newChatMessage, 'chatHistory');
|
||||
|
||||
// Reserve budget for group nudge
|
||||
if(selected_group && groupNudgeMessage) {
|
||||
chatCompletion.freeBudget(groupNudgeMessage);
|
||||
chatCompletion.insertAtEnd(groupNudgeMessage, 'chatHistory');
|
||||
}
|
||||
|
||||
// Insert and free continue nudge
|
||||
if (type === 'continue' && continueMessage) {
|
||||
chatCompletion.freeBudget(continueMessage);
|
||||
@ -542,9 +557,11 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
||||
chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));
|
||||
if (openai_msgs_example.length) {
|
||||
const newExampleChat = new Message('system', oai_settings.new_example_chat_prompt, 'newChat');
|
||||
chatCompletion.reserveBudget(newExampleChat);
|
||||
|
||||
[...openai_msgs_example].forEach((dialogue, dialogueIndex) => {
|
||||
let examplesAdded = 0;
|
||||
|
||||
if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples');
|
||||
|
||||
dialogue.forEach((prompt, promptIndex) => {
|
||||
const role = 'system';
|
||||
const content = prompt.content || '';
|
||||
@ -554,14 +571,14 @@ function populateDialogueExamples(prompts, chatCompletion) {
|
||||
chatMessage.setName(prompt.name);
|
||||
if (chatCompletion.canAfford(chatMessage)) {
|
||||
chatCompletion.insert(chatMessage, 'dialogueExamples');
|
||||
examplesAdded++;
|
||||
}
|
||||
});
|
||||
|
||||
if (0 === examplesAdded) {
|
||||
chatCompletion.removeLastFrom('dialogueExamples');
|
||||
}
|
||||
});
|
||||
|
||||
chatCompletion.freeBudget(newExampleChat);
|
||||
|
||||
const chatExamples = chatCompletion.getMessages().getItemByIdentifier('dialogueExamples').getCollection();
|
||||
if (chatExamples.length) chatCompletion.insertAtStart(newExampleChat, 'dialogueExamples');
|
||||
}
|
||||
}
|
||||
|
||||
@ -700,9 +717,10 @@ function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, ty
|
||||
*
|
||||
* @returns {Object} prompts - The prepared and merged system and user-defined prompts.
|
||||
*/
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts) {
|
||||
function preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride) {
|
||||
const scenarioText = Scenario ? `[Circumstances and context of the dialogue: ${Scenario}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : '';
|
||||
const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : ''
|
||||
const groupNudge = `[Write the next reply only as ${name2}]`;
|
||||
|
||||
// Create entries for system prompts
|
||||
const systemPrompts = [
|
||||
@ -716,7 +734,8 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
{ role: 'system', content: oai_settings.nsfw_avoidance_prompt, identifier: 'nsfwAvoidance' },
|
||||
{ role: 'system', content: oai_settings.impersonation_prompt, identifier: 'impersonate' },
|
||||
{ role: 'system', content: quietPrompt, identifier: 'quietPrompt' },
|
||||
{ role: 'system', content: bias, identifier: 'bias' }
|
||||
{ role: 'system', content: bias, identifier: 'bias' },
|
||||
{ role: 'system', content: groupNudge, identifier: 'groupNudge' }
|
||||
];
|
||||
|
||||
// Tavern Extras - Summary
|
||||
@ -753,7 +772,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
});
|
||||
|
||||
// Apply character-specific main prompt
|
||||
const systemPromptOverride = promptManager.activeCharacter.data?.system_prompt ?? null;
|
||||
const systemPrompt = prompts.get('main') ?? null;
|
||||
if (systemPromptOverride && systemPrompt) {
|
||||
const mainOriginalContent = systemPrompt.content;
|
||||
@ -763,7 +781,6 @@ function preparePromptsForChatCompletion(Scenario, charPersonality, name2, world
|
||||
}
|
||||
|
||||
// Apply character-specific jailbreak
|
||||
const jailbreakPromptOverride = promptManager.activeCharacter.data?.post_history_instructions ?? null;
|
||||
const jailbreakPrompt = prompts.get('jailbreak') ?? null;
|
||||
if (jailbreakPromptOverride && jailbreakPrompt) {
|
||||
const jbOriginalContent = jailbreakPrompt.content;
|
||||
@ -807,7 +824,9 @@ function prepareOpenAIMessages({
|
||||
type,
|
||||
quietPrompt,
|
||||
extensionPrompts,
|
||||
cyclePrompt
|
||||
cyclePrompt,
|
||||
systemPromptOverride,
|
||||
jailbreakPromptOverride,
|
||||
} = {}, dryRun) {
|
||||
// Without a character selected, there is no way to accurately calculate tokens
|
||||
if (!promptManager.activeCharacter && dryRun) return [null, false];
|
||||
@ -820,7 +839,7 @@ function prepareOpenAIMessages({
|
||||
|
||||
try {
|
||||
// Merge markers and ordered user prompts with system prompts
|
||||
const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts);
|
||||
const prompts = preparePromptsForChatCompletion(Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride);
|
||||
|
||||
// Fill the chat completion with as much context as the budget allows
|
||||
populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, type, cyclePrompt });
|
||||
@ -1371,7 +1390,7 @@ function countTokens(messages, full = false) {
|
||||
|
||||
for (const message of messages) {
|
||||
const model = getTokenizerModel();
|
||||
const hash = getStringHash(message.content);
|
||||
const hash = getStringHash(JSON.stringify(message));
|
||||
const cacheKey = `${model}-${hash}`;
|
||||
const cachedCount = tokenCache[chatId][cacheKey];
|
||||
|
||||
@ -1443,8 +1462,8 @@ class Message {
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
|
||||
if (this.content) {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content })
|
||||
if (typeof this.content === 'string') {
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content });
|
||||
} else {
|
||||
this.tokens = 0;
|
||||
}
|
||||
@ -1452,6 +1471,7 @@ class Message {
|
||||
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1663,6 +1683,21 @@ class ChatCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the last item of the collection
|
||||
*
|
||||
* @param identifier
|
||||
*/
|
||||
removeLastFrom(identifier) {
|
||||
const index = this.findMessageIndex(identifier);
|
||||
const message = this.messages.collection[index].collection.pop();
|
||||
|
||||
this.increaseTokenBudgetBy(message.getTokens());
|
||||
|
||||
this.log(`Removed ${message.identifier} from ${identifier}. Remaining tokens: ${this.tokenBudget}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the token budget can afford the tokens of the specified message.
|
||||
*
|
||||
@ -2349,7 +2384,11 @@ async function onExportPresetClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]];
|
||||
const preset = deepClone(openai_settings[openai_setting_names[oai_settings.preset_settings_openai]]);
|
||||
|
||||
delete preset.reverse_proxy;
|
||||
delete preset.proxy_password;
|
||||
|
||||
const presetJsonString = JSON.stringify(preset, null, 4);
|
||||
download(presetJsonString, oai_settings.preset_settings_openai, 'application/json');
|
||||
}
|
||||
|
@ -285,8 +285,8 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
|
||||
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
|
||||
}
|
||||
|
||||
if (tag.excluded) {
|
||||
isGeneralList ? $(tagElement).addClass('excluded') : $(listElement).closest('.character_select, .group_select').addClass('hiddenByTag');
|
||||
if (tag.excluded && isGeneralList) {
|
||||
$(tagElement).addClass('excluded');
|
||||
}
|
||||
|
||||
if (selectable) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { getContext } from "./extensions.js";
|
||||
import { getRequestHeaders } from "../script.js";
|
||||
|
||||
export function onlyUnique(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
@ -554,6 +555,48 @@ export function extractDataFromPng(data, identifier = 'chara') {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a base64 encoded image to the backend to be saved as a file.
|
||||
*
|
||||
* @param {string} base64Data - The base64 encoded image data.
|
||||
* @param {string} characterName - The character name to determine the sub-directory for saving.
|
||||
* @param {string} ext - The file extension for the image (e.g., 'jpg', 'png', 'webp').
|
||||
*
|
||||
* @returns {Promise<string>} - Resolves to the saved image's path on the server.
|
||||
* Rejects with an error if the upload fails.
|
||||
*/
|
||||
export async function saveBase64AsFile(base64Data, characterName, filename = "", ext) {
|
||||
// Construct the full data URL
|
||||
const format = ext; // Extract the file extension (jpg, png, webp)
|
||||
const dataURL = `data:image/${format};base64,${base64Data}`;
|
||||
|
||||
// Prepare the request body
|
||||
const requestBody = {
|
||||
image: dataURL,
|
||||
ch_name: characterName,
|
||||
filename: filename
|
||||
};
|
||||
|
||||
// Send the data URL to your backend using fetch
|
||||
const response = await fetch('/uploadimage', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(requestBody),
|
||||
headers: {
|
||||
...getRequestHeaders(),
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
|
||||
// If the response is successful, get the saved image path from the server's response
|
||||
if (response.ok) {
|
||||
const responseData = await response.json();
|
||||
return responseData.path;
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Failed to upload the image to the server');
|
||||
}
|
||||
}
|
||||
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
|
@ -464,7 +464,7 @@ function appendWorldEntry(name, data, entry) {
|
||||
|
||||
const contentInput = template.find('textarea[name="content"]');
|
||||
contentInput.data("uid", entry.uid);
|
||||
contentInput.on("input", function () {
|
||||
contentInput.on("input", function (_, { skipCount } = {}) {
|
||||
const uid = $(this).data("uid");
|
||||
const value = $(this).val();
|
||||
data.entries[uid].content = value;
|
||||
@ -472,12 +472,25 @@ function appendWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, "content", data.entries[uid].content);
|
||||
saveWorldInfo(name, data);
|
||||
|
||||
if (skipCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// count tokens
|
||||
countTokensDebounced(this, value);
|
||||
});
|
||||
contentInput.val(entry.content).trigger("input");
|
||||
contentInput.val(entry.content).trigger("input", { skipCount: true });
|
||||
//initScrollHeight(contentInput);
|
||||
|
||||
template.find('.inline-drawer-toggle').on('click', function () {
|
||||
const counter = template.find(".world_entry_form_token_counter");
|
||||
|
||||
if (counter.data('first-run')) {
|
||||
counter.data('first-run', false);
|
||||
countTokensDebounced(contentInput, contentInput.val());
|
||||
}
|
||||
});
|
||||
|
||||
// selective
|
||||
const selectiveInput = template.find('input[name="selective"]');
|
||||
selectiveInput.data("uid", entry.uid);
|
||||
|
Reference in New Issue
Block a user