mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into pollinations
This commit is contained in:
@ -5,6 +5,7 @@ import {
|
||||
addCopyToCodeBlocks,
|
||||
appendMediaToMessage,
|
||||
callPopup,
|
||||
characters,
|
||||
chat,
|
||||
eventSource,
|
||||
event_types,
|
||||
@ -12,9 +13,14 @@ import {
|
||||
getRequestHeaders,
|
||||
hideSwipeButtons,
|
||||
name2,
|
||||
reloadCurrentChat,
|
||||
saveChatDebounced,
|
||||
saveSettingsDebounced,
|
||||
showSwipeButtons,
|
||||
this_chid,
|
||||
} from '../script.js';
|
||||
import { selected_group } from './group-chats.js';
|
||||
import { power_user } from './power-user.js';
|
||||
import {
|
||||
extractTextFromHTML,
|
||||
extractTextFromMarkdown,
|
||||
@ -416,6 +422,56 @@ export function decodeStyleTags(text) {
|
||||
});
|
||||
}
|
||||
|
||||
async function openExternalMediaOverridesDialog() {
|
||||
const entityId = getCurrentEntityId();
|
||||
|
||||
if (!entityId) {
|
||||
toastr.info('No character or group selected');
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $('#forbid_media_override_template > .forbid_media_override').clone();
|
||||
template.find('.forbid_media_global_state_forbidden').toggle(power_user.forbid_external_images);
|
||||
template.find('.forbid_media_global_state_allowed').toggle(!power_user.forbid_external_images);
|
||||
|
||||
if (power_user.external_media_allowed_overrides.includes(entityId)) {
|
||||
template.find('#forbid_media_override_allowed').prop('checked', true);
|
||||
}
|
||||
else if (power_user.external_media_forbidden_overrides.includes(entityId)) {
|
||||
template.find('#forbid_media_override_forbidden').prop('checked', true);
|
||||
}
|
||||
else {
|
||||
template.find('#forbid_media_override_global').prop('checked', true);
|
||||
}
|
||||
|
||||
callPopup(template, 'text', '', { wide: false, large: false });
|
||||
}
|
||||
|
||||
export function getCurrentEntityId() {
|
||||
if (selected_group) {
|
||||
return String(selected_group);
|
||||
}
|
||||
|
||||
return characters[this_chid]?.avatar ?? null;
|
||||
}
|
||||
|
||||
export function isExternalMediaAllowed() {
|
||||
const entityId = getCurrentEntityId();
|
||||
if (!entityId) {
|
||||
return !power_user.forbid_external_images;
|
||||
}
|
||||
|
||||
if (power_user.external_media_allowed_overrides.includes(entityId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (power_user.external_media_forbidden_overrides.includes(entityId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !power_user.forbid_external_images;
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
$(document).on('click', '.mes_hide', async function () {
|
||||
const messageBlock = $(this).closest('.mes');
|
||||
@ -511,6 +567,32 @@ jQuery(function () {
|
||||
$(this).closest('.mes').find('.mes_edit').trigger('click');
|
||||
});
|
||||
|
||||
$(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog);
|
||||
$(document).on('input', '#forbid_media_override_allowed', function () {
|
||||
const entityId = getCurrentEntityId();
|
||||
if (!entityId) return;
|
||||
power_user.external_media_allowed_overrides.push(entityId);
|
||||
power_user.external_media_forbidden_overrides = power_user.external_media_forbidden_overrides.filter((v) => v !== entityId);
|
||||
saveSettingsDebounced();
|
||||
reloadCurrentChat();
|
||||
});
|
||||
$(document).on('input', '#forbid_media_override_forbidden', function () {
|
||||
const entityId = getCurrentEntityId();
|
||||
if (!entityId) return;
|
||||
power_user.external_media_forbidden_overrides.push(entityId);
|
||||
power_user.external_media_allowed_overrides = power_user.external_media_allowed_overrides.filter((v) => v !== entityId);
|
||||
saveSettingsDebounced();
|
||||
reloadCurrentChat();
|
||||
});
|
||||
$(document).on('input', '#forbid_media_override_global', function () {
|
||||
const entityId = getCurrentEntityId();
|
||||
if (!entityId) return;
|
||||
power_user.external_media_allowed_overrides = power_user.external_media_allowed_overrides.filter((v) => v !== entityId);
|
||||
power_user.external_media_forbidden_overrides = power_user.external_media_forbidden_overrides.filter((v) => v !== entityId);
|
||||
saveSettingsDebounced();
|
||||
reloadCurrentChat();
|
||||
});
|
||||
|
||||
$('#file_form_input').on('change', onFileAttach);
|
||||
$('#file_form').on('reset', function () {
|
||||
$('#file_form').addClass('displayNone');
|
||||
|
@ -103,7 +103,8 @@ function downloadAssetsList(url) {
|
||||
if (assetType == 'extension') {
|
||||
assetTypeMenu.append(`
|
||||
<div class="assets-list-git">
|
||||
To download extensions from this page, you need to have <a href="https://git-scm.com/downloads" target="_blank">Git</a> installed.
|
||||
To download extensions from this page, you need to have <a href="https://git-scm.com/downloads" target="_blank">Git</a> installed.<br>
|
||||
Click the <i class="fa-solid fa-sm fa-arrow-up-right-from-square"></i> icon to visit the Extension's repo for tips on how to use it.
|
||||
</div>`);
|
||||
}
|
||||
|
||||
@ -180,6 +181,7 @@ function downloadAssetsList(url) {
|
||||
const displayName = DOMPurify.sanitize(asset['name'] || asset['id']);
|
||||
const description = DOMPurify.sanitize(asset['description'] || '');
|
||||
const url = isValidUrl(asset['url']) ? asset['url'] : '';
|
||||
const title = assetType === 'extension' ? `Extension repo/guide: ${url}` : 'Preview in browser';
|
||||
const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
|
||||
|
||||
const assetBlock = $('<i></i>')
|
||||
@ -187,7 +189,7 @@ function downloadAssetsList(url) {
|
||||
.append(`<div class="flex-container flexFlowColumn flexNoGap">
|
||||
<span class="asset-name flex-container alignitemscenter">
|
||||
<b>${displayName}</b>
|
||||
<a class="asset_preview" href="${url}" target="_blank" title="Preview in browser">
|
||||
<a class="asset_preview" href="${url}" target="_blank" title="${title}">
|
||||
<i class="fa-solid fa-sm ${previewIcon}"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
@ -30,7 +30,7 @@ function migrateSettings() {
|
||||
if (extension_settings.caption.source === 'openai') {
|
||||
extension_settings.caption.source = 'multimodal';
|
||||
extension_settings.caption.multimodal_api = 'openai';
|
||||
extension_settings.caption.multimodal_model = 'gpt-4-vision-preview';
|
||||
extension_settings.caption.multimodal_model = 'gpt-4-turbo';
|
||||
}
|
||||
|
||||
if (!extension_settings.caption.multimodal_api) {
|
||||
@ -38,7 +38,7 @@ function migrateSettings() {
|
||||
}
|
||||
|
||||
if (!extension_settings.caption.multimodal_model) {
|
||||
extension_settings.caption.multimodal_model = 'gpt-4-vision-preview';
|
||||
extension_settings.caption.multimodal_model = 'gpt-4-turbo';
|
||||
}
|
||||
|
||||
if (!extension_settings.caption.prompt) {
|
||||
@ -369,6 +369,7 @@ jQuery(function () {
|
||||
<label for="caption_multimodal_model">Model</label>
|
||||
<select id="caption_multimodal_model" class="flex1 text_pole">
|
||||
<option data-type="openai" value="gpt-4-vision-preview">gpt-4-vision-preview</option>
|
||||
<option data-type="openai" value="gpt-4-turbo">gpt-4-turbo</option>
|
||||
<option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option>
|
||||
<option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
|
||||
|
@ -11,7 +11,7 @@ const MODULE_NAME = 'expressions';
|
||||
const UPDATE_INTERVAL = 2000;
|
||||
const STREAMING_UPDATE_INTERVAL = 6000;
|
||||
const TALKINGCHECK_UPDATE_INTERVAL = 500;
|
||||
const FALLBACK_EXPRESSION = 'joy';
|
||||
const DEFAULT_FALLBACK_EXPRESSION = 'joy';
|
||||
const DEFAULT_EXPRESSIONS = [
|
||||
'talkinghead',
|
||||
'admiration',
|
||||
@ -58,6 +58,14 @@ function isTalkingHeadEnabled() {
|
||||
return extension_settings.expressions.talkinghead && !extension_settings.expressions.local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fallback expression if explicitly chosen, otherwise the default one
|
||||
* @returns {string} expression name
|
||||
*/
|
||||
function getFallbackExpression() {
|
||||
return extension_settings.expressions.fallback_expression ?? DEFAULT_FALLBACK_EXPRESSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles Talkinghead mode on/off.
|
||||
*
|
||||
@ -157,7 +165,8 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
|
||||
|
||||
const sprites = spriteCache[spriteFolderName];
|
||||
const expressionImage = container.find(`.expression-holder[data-avatar="${avatar}"]`);
|
||||
const defaultSpritePath = sprites.find(x => x.label === FALLBACK_EXPRESSION)?.path;
|
||||
const defaultExpression = getFallbackExpression();
|
||||
const defaultSpritePath = sprites.find(x => x.label === defaultExpression)?.path;
|
||||
const noSprites = sprites.length === 0;
|
||||
|
||||
if (expressionImage.length > 0) {
|
||||
@ -568,7 +577,7 @@ function handleImageChange() {
|
||||
// This preserves the same expression Talkinghead had at the moment it was switched off.
|
||||
const charName = getContext().name2;
|
||||
const last = lastExpression[charName];
|
||||
const targetExpression = last ? last : FALLBACK_EXPRESSION;
|
||||
const targetExpression = last ? last : getFallbackExpression();
|
||||
setExpression(charName, targetExpression, true);
|
||||
}
|
||||
}
|
||||
@ -691,8 +700,8 @@ async function moduleWorker() {
|
||||
const force = !!context.groupId;
|
||||
|
||||
// Character won't be angry on you for swiping
|
||||
if (currentLastMessage.mes == '...' && expressionsList.includes(FALLBACK_EXPRESSION)) {
|
||||
expression = FALLBACK_EXPRESSION;
|
||||
if (currentLastMessage.mes == '...' && expressionsList.includes(getFallbackExpression())) {
|
||||
expression = getFallbackExpression();
|
||||
}
|
||||
|
||||
await sendExpressionCall(spriteFolderName, expression, force, vnMode);
|
||||
@ -885,6 +894,22 @@ async function setSpriteSetCommand(_, folder) {
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function classifyCommand(_, text) {
|
||||
if (!text) {
|
||||
console.log('No text provided');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!modules.includes('classify') && !extension_settings.expressions.local) {
|
||||
toastr.warning('Text classification is disabled or not available');
|
||||
return '';
|
||||
}
|
||||
|
||||
const label = getExpressionLabel(text);
|
||||
console.debug(`Classification result for "${text}": ${label}`);
|
||||
return label;
|
||||
}
|
||||
|
||||
async function setSpriteSlashCommand(_, spriteId) {
|
||||
if (!spriteId) {
|
||||
console.log('No sprite id provided');
|
||||
@ -949,7 +974,7 @@ function sampleClassifyText(text) {
|
||||
async function getExpressionLabel(text) {
|
||||
// Return if text is undefined, saving a costly fetch request
|
||||
if ((!modules.includes('classify') && !extension_settings.expressions.local) || !text) {
|
||||
return FALLBACK_EXPRESSION;
|
||||
return getFallbackExpression();
|
||||
}
|
||||
|
||||
text = sampleClassifyText(text);
|
||||
@ -988,7 +1013,7 @@ async function getExpressionLabel(text) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return FALLBACK_EXPRESSION;
|
||||
return getFallbackExpression();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1092,6 +1117,11 @@ async function getSpritesList(name) {
|
||||
}
|
||||
}
|
||||
|
||||
async function renderAdditionalExpressionSettings() {
|
||||
renderCustomExpressions();
|
||||
await renderFallbackExpressionPicker();
|
||||
}
|
||||
|
||||
function renderCustomExpressions() {
|
||||
if (!Array.isArray(extension_settings.expressions.custom)) {
|
||||
extension_settings.expressions.custom = [];
|
||||
@ -1112,6 +1142,23 @@ function renderCustomExpressions() {
|
||||
}
|
||||
}
|
||||
|
||||
async function renderFallbackExpressionPicker() {
|
||||
const expressions = await getExpressionsList();
|
||||
|
||||
const defaultPicker = $('#expression_fallback');
|
||||
defaultPicker.empty();
|
||||
|
||||
const fallbackExpression = getFallbackExpression();
|
||||
|
||||
for (const expression of expressions) {
|
||||
const option = document.createElement('option');
|
||||
option.value = expression;
|
||||
option.text = expression;
|
||||
option.selected = expression == fallbackExpression;
|
||||
defaultPicker.append(option);
|
||||
}
|
||||
}
|
||||
|
||||
async function getExpressionsList() {
|
||||
// Return cached list if available
|
||||
if (Array.isArray(expressionsList)) {
|
||||
@ -1349,7 +1396,7 @@ async function onClickExpressionAddCustom() {
|
||||
|
||||
// Add custom expression into settings
|
||||
extension_settings.expressions.custom.push(expressionName);
|
||||
renderCustomExpressions();
|
||||
await renderAdditionalExpressionSettings();
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Force refresh sprites list
|
||||
@ -1376,7 +1423,11 @@ async function onClickExpressionRemoveCustom() {
|
||||
// Remove custom expression from settings
|
||||
const index = extension_settings.expressions.custom.indexOf(selectedExpression);
|
||||
extension_settings.expressions.custom.splice(index, 1);
|
||||
renderCustomExpressions();
|
||||
if (selectedExpression == getFallbackExpression()) {
|
||||
toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`);
|
||||
extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION;
|
||||
}
|
||||
await renderAdditionalExpressionSettings();
|
||||
saveSettingsDebounced();
|
||||
|
||||
// Force refresh sprites list
|
||||
@ -1385,6 +1436,14 @@ async function onClickExpressionRemoveCustom() {
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
function onExpressionFallbackChanged() {
|
||||
const expression = this.value;
|
||||
if (expression) {
|
||||
extension_settings.expressions.fallback_expression = expression;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFileUpload(url, formData) {
|
||||
try {
|
||||
const data = await jQuery.ajax({
|
||||
@ -1632,7 +1691,7 @@ async function fetchImagesNoCache() {
|
||||
return await Promise.allSettled(promises);
|
||||
}
|
||||
|
||||
(function () {
|
||||
(async function () {
|
||||
function addExpressionImage() {
|
||||
const html = `
|
||||
<div id="expression-wrapper">
|
||||
@ -1652,7 +1711,7 @@ async function fetchImagesNoCache() {
|
||||
element.hide();
|
||||
$('body').append(element);
|
||||
}
|
||||
function addSettings() {
|
||||
async function addSettings() {
|
||||
$('#extensions_settings').append(renderExtensionTemplate(MODULE_NAME, 'settings'));
|
||||
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
|
||||
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
|
||||
@ -1680,10 +1739,11 @@ async function fetchImagesNoCache() {
|
||||
}
|
||||
});
|
||||
|
||||
renderCustomExpressions();
|
||||
await renderAdditionalExpressionSettings();
|
||||
|
||||
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
|
||||
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
|
||||
$('#expression_fallback').on('change', onExpressionFallbackChanged)
|
||||
}
|
||||
|
||||
// Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized.
|
||||
@ -1716,7 +1776,7 @@ async function fetchImagesNoCache() {
|
||||
|
||||
addExpressionImage();
|
||||
addVisualNovelMode();
|
||||
addSettings();
|
||||
await addSettings();
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
const updateFunction = wrapper.update.bind(wrapper);
|
||||
setInterval(updateFunction, UPDATE_INTERVAL);
|
||||
@ -1758,5 +1818,6 @@ async function fetchImagesNoCache() {
|
||||
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">(spriteId)</span> – force sets the sprite for the current character', true, true);
|
||||
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">(optional folder)</span> – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
|
||||
registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '<span class="monospace">(charName)</span> – Returns the last set sprite / expression for the named character.', true, true);
|
||||
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.');
|
||||
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.', true, true);
|
||||
registerSlashCommand('classify', classifyCommand, [], '<span class="monospace">(text)</span> – performs an emotion classification of the given text and returns a label.', true, true);
|
||||
})();
|
||||
|
@ -18,6 +18,11 @@
|
||||
<input id="image_type_toggle" type="checkbox">
|
||||
<span>Image Type - talkinghead (extras)</span>
|
||||
</label>
|
||||
<div class="expression_fallback_block m-b-1 m-t-1">
|
||||
<label for="expression_fallback">Default / Fallback Expression</label>
|
||||
<small>Set the default and fallback expression being used when no matching expression is found.</small>
|
||||
<select id="expression_fallback" class="flex1 margin0" data-i18n="Fallback Expression" placeholder="Fallback Expression"></select>
|
||||
</div>
|
||||
<div class="expression_custom_block m-b-1 m-t-1">
|
||||
<label for="expression_custom">Custom Expressions</label>
|
||||
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
|
||||
|
@ -16,12 +16,20 @@
|
||||
<label for="qr--modal-message">
|
||||
Message / Command:
|
||||
</label>
|
||||
<small>
|
||||
<div class="qr--modal-editorSettings">
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-wrap">
|
||||
<span>Word wrap</span>
|
||||
</label>
|
||||
</small>
|
||||
<label class="checkbox_label">
|
||||
<span>Tab size:</span>
|
||||
<input type="number" min="1" max="9" id="qr--modal-tabSize" class="text_pole">
|
||||
</label>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="qr--modal-executeShortcut">
|
||||
<span>Ctrl+Enter to execute</span>
|
||||
</label>
|
||||
</div>
|
||||
<textarea class="monospace" id="qr--modal-message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@ -78,7 +86,7 @@
|
||||
<input type="checkbox" id="qr--executeOnGroupMemberDraft">
|
||||
<span><i class="fa-solid fa-fw fa-people-group"></i> Execute before group member message</span>
|
||||
</label>
|
||||
<div class="flex-container alignItemsBaseline" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
|
||||
<div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
|
||||
<small>Automation ID</small>
|
||||
<input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
|
||||
</div>
|
||||
|
@ -104,7 +104,7 @@ const loadSets = async () => {
|
||||
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
|
||||
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
|
||||
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
|
||||
qr.automationId = slot.automationId ?? false;
|
||||
qr.automationId = slot.automationId ?? '';
|
||||
qr.contextList = (slot.contextMenu ?? []).map(it=>({
|
||||
set: it.preset,
|
||||
isChained: it.chain,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { callPopup } from '../../../../script.js';
|
||||
import { POPUP_TYPE, Popup } from '../../../popup.js';
|
||||
import { getSortableDelay } from '../../../utils.js';
|
||||
import { log, warn } from '../index.js';
|
||||
import { QuickReplyContextLink } from './QuickReplyContextLink.js';
|
||||
@ -44,6 +44,13 @@ export class QuickReply {
|
||||
/**@type {HTMLInputElement}*/ settingsDomLabel;
|
||||
/**@type {HTMLTextAreaElement}*/ settingsDomMessage;
|
||||
|
||||
/**@type {Popup}*/ editorPopup;
|
||||
|
||||
/**@type {HTMLElement}*/ editorExecuteBtn;
|
||||
/**@type {HTMLElement}*/ editorExecuteErrors;
|
||||
/**@type {HTMLInputElement}*/ editorExecuteHide;
|
||||
/**@type {Promise}*/ editorExecutePromise;
|
||||
|
||||
|
||||
get hasContext() {
|
||||
return this.contextList && this.contextList.length > 0;
|
||||
@ -192,7 +199,8 @@ export class QuickReply {
|
||||
/**@type {HTMLElement} */
|
||||
// @ts-ignore
|
||||
const dom = this.template.cloneNode(true);
|
||||
const popupResult = callPopup(dom, 'text', undefined, { okButton: 'OK', wide: true, large: true, rows: 1 });
|
||||
this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 });
|
||||
const popupResult = this.editorPopup.show();
|
||||
|
||||
// basics
|
||||
/**@type {HTMLInputElement}*/
|
||||
@ -209,7 +217,7 @@ export class QuickReply {
|
||||
});
|
||||
/**@type {HTMLInputElement}*/
|
||||
const wrap = dom.querySelector('#qr--modal-wrap');
|
||||
wrap.checked = JSON.parse(localStorage.getItem('qr--wrap'));
|
||||
wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false');
|
||||
wrap.addEventListener('click', () => {
|
||||
localStorage.setItem('qr--wrap', JSON.stringify(wrap.checked));
|
||||
updateWrap();
|
||||
@ -221,9 +229,26 @@ export class QuickReply {
|
||||
message.style.whiteSpace = 'pre';
|
||||
}
|
||||
};
|
||||
/**@type {HTMLInputElement}*/
|
||||
const tabSize = dom.querySelector('#qr--modal-tabSize');
|
||||
tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4');
|
||||
const updateTabSize = () => {
|
||||
message.style.tabSize = tabSize.value;
|
||||
};
|
||||
tabSize.addEventListener('change', () => {
|
||||
localStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value)));
|
||||
updateTabSize();
|
||||
});
|
||||
/**@type {HTMLInputElement}*/
|
||||
const executeShortcut = dom.querySelector('#qr--modal-executeShortcut');
|
||||
executeShortcut.checked = JSON.parse(localStorage.getItem('qr--executeShortcut') ?? 'true');
|
||||
executeShortcut.addEventListener('click', () => {
|
||||
localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked));
|
||||
});
|
||||
/**@type {HTMLTextAreaElement}*/
|
||||
const message = dom.querySelector('#qr--modal-message');
|
||||
updateWrap();
|
||||
updateTabSize();
|
||||
message.value = this.message;
|
||||
message.addEventListener('input', () => {
|
||||
this.updateMessage(message.value);
|
||||
@ -257,6 +282,12 @@ export class QuickReply {
|
||||
message.selectionStart = start - 1;
|
||||
message.selectionEnd = end - count;
|
||||
this.updateMessage(message.value);
|
||||
} else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
if (executeShortcut.checked) {
|
||||
this.executeFromEditor();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -385,27 +416,15 @@ export class QuickReply {
|
||||
|
||||
/**@type {HTMLElement}*/
|
||||
const executeErrors = dom.querySelector('#qr--modal-executeErrors');
|
||||
this.editorExecuteErrors = executeErrors;
|
||||
/**@type {HTMLInputElement}*/
|
||||
const executeHide = dom.querySelector('#qr--modal-executeHide');
|
||||
let executePromise;
|
||||
this.editorExecuteHide = executeHide;
|
||||
/**@type {HTMLElement}*/
|
||||
const executeBtn = dom.querySelector('#qr--modal-execute');
|
||||
this.editorExecuteBtn = executeBtn;
|
||||
executeBtn.addEventListener('click', async()=>{
|
||||
if (executePromise) return;
|
||||
executeBtn.classList.add('qr--busy');
|
||||
executeErrors.innerHTML = '';
|
||||
if (executeHide.checked) {
|
||||
document.querySelector('#shadow_popup').classList.add('qr--hide');
|
||||
}
|
||||
try {
|
||||
executePromise = this.execute();
|
||||
await executePromise;
|
||||
} catch (ex) {
|
||||
executeErrors.textContent = ex.message;
|
||||
}
|
||||
executePromise = null;
|
||||
executeBtn.classList.remove('qr--busy');
|
||||
document.querySelector('#shadow_popup').classList.remove('qr--hide');
|
||||
await this.executeFromEditor();
|
||||
});
|
||||
|
||||
await popupResult;
|
||||
@ -414,6 +433,24 @@ export class QuickReply {
|
||||
}
|
||||
}
|
||||
|
||||
async executeFromEditor() {
|
||||
if (this.editorExecutePromise) return;
|
||||
this.editorExecuteBtn.classList.add('qr--busy');
|
||||
this.editorExecuteErrors.innerHTML = '';
|
||||
if (this.editorExecuteHide.checked) {
|
||||
this.editorPopup.dom.classList.add('qr--hide');
|
||||
}
|
||||
try {
|
||||
this.editorExecutePromise = this.execute();
|
||||
await this.editorExecutePromise;
|
||||
} catch (ex) {
|
||||
this.editorExecuteErrors.textContent = ex.message;
|
||||
}
|
||||
this.editorExecutePromise = null;
|
||||
this.editorExecuteBtn.classList.remove('qr--busy');
|
||||
this.editorPopup.dom.classList.remove('qr--hide');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -216,71 +216,85 @@
|
||||
align-items: baseline;
|
||||
}
|
||||
@media screen and (max-width: 750px) {
|
||||
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor {
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
|
||||
flex-direction: column;
|
||||
}
|
||||
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
flex-direction: column;
|
||||
}
|
||||
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
|
||||
min-height: 90svh;
|
||||
}
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) {
|
||||
.dialogue_popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label {
|
||||
flex: 1 1 1px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input {
|
||||
font-size: inherit;
|
||||
}
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor #qr--modal-execute {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy {
|
||||
.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy {
|
||||
opacity: 0.5;
|
||||
cursor: wait;
|
||||
}
|
||||
#shadow_popup.qr--hide {
|
||||
.shadow_popup.qr--hide {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@
|
||||
|
||||
|
||||
@media screen and (max-width: 750px) {
|
||||
body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor {
|
||||
body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor {
|
||||
flex-direction: column;
|
||||
> #qr--main > .qr--labels {
|
||||
flex-direction: column;
|
||||
@ -252,10 +252,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
#dialogue_popup:has(#qr--modalEditor) {
|
||||
.dialogue_popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
|
||||
#dialogue_popup_text {
|
||||
.dialogue_popup_text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -293,6 +293,20 @@
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .qr--modal-editorSettings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
color: var(--grey70);
|
||||
font-size: smaller;
|
||||
align-items: baseline;
|
||||
> .checkbox_label {
|
||||
white-space: nowrap;
|
||||
> input {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
> #qr--modal-message {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
@ -312,6 +326,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#shadow_popup.qr--hide {
|
||||
.shadow_popup.qr--hide {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
|
||||
|
||||
if (!isGoogle) {
|
||||
requestBody.api = extension_settings.caption.multimodal_api || 'openai';
|
||||
requestBody.model = extension_settings.caption.multimodal_model || 'gpt-4-vision-preview';
|
||||
requestBody.model = extension_settings.caption.multimodal_model || 'gpt-4-turbo';
|
||||
requestBody.reverse_proxy = proxyUrl;
|
||||
requestBody.proxy_password = proxyPassword;
|
||||
}
|
||||
@ -83,7 +83,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
|
||||
|
||||
if (isCustom) {
|
||||
requestBody.server_url = oai_settings.custom_url;
|
||||
requestBody.model = oai_settings.custom_model || 'gpt-4-vision-preview';
|
||||
requestBody.model = oai_settings.custom_model || 'gpt-4-turbo';
|
||||
requestBody.custom_include_headers = oai_settings.custom_include_headers;
|
||||
requestBody.custom_include_body = oai_settings.custom_include_body;
|
||||
requestBody.custom_exclude_body = oai_settings.custom_exclude_body;
|
||||
|
@ -2629,7 +2629,7 @@ async function generateComfyImage(prompt, negativePrompt) {
|
||||
}
|
||||
let workflow = (await workflowResponse.json()).replace('"%prompt%"', JSON.stringify(prompt));
|
||||
workflow = workflow.replace('"%negative_prompt%"', JSON.stringify(negativePrompt));
|
||||
workflow = workflow.replace('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
|
||||
workflow = workflow.replaceAll('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
|
||||
placeholders.forEach(ph => {
|
||||
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
||||
});
|
||||
|
@ -19,8 +19,9 @@ const UPDATE_INTERVAL = 1000;
|
||||
|
||||
let voiceMapEntries = [];
|
||||
let voiceMap = {}; // {charName:voiceid, charName2:voiceid2}
|
||||
let storedvalue = false;
|
||||
let talkingHeadState = false;
|
||||
let lastChatId = null;
|
||||
let lastMessage = null;
|
||||
let lastMessageHash = null;
|
||||
|
||||
const DEFAULT_VOICE_MARKER = '[Default Voice]';
|
||||
@ -67,7 +68,7 @@ export function getPreviewString(lang) {
|
||||
return previewStrings[lang] ?? fallbackPreview;
|
||||
}
|
||||
|
||||
let ttsProviders = {
|
||||
const ttsProviders = {
|
||||
ElevenLabs: ElevenLabsTtsProvider,
|
||||
Silero: SileroTtsProvider,
|
||||
XTTSv2: XTTSTtsProvider,
|
||||
@ -82,7 +83,6 @@ let ttsProviders = {
|
||||
let ttsProvider;
|
||||
let ttsProviderName;
|
||||
|
||||
let ttsLastMessage = null;
|
||||
|
||||
async function onNarrateOneMessage() {
|
||||
audioElement.src = '/sounds/silence.mp3';
|
||||
@ -130,103 +130,13 @@ async function onNarrateText(args, text) {
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
// Primarily determining when to add new chat to the TTS queue
|
||||
const enabled = $('#tts_enabled').is(':checked');
|
||||
$('body').toggleClass('tts', enabled);
|
||||
if (!enabled) {
|
||||
if (!extension_settings.tts.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
const chat = context.chat;
|
||||
|
||||
processTtsQueue();
|
||||
processAudioJobQueue();
|
||||
updateUiAudioPlayState();
|
||||
|
||||
// Auto generation is disabled
|
||||
if (extension_settings.tts.auto_generation == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// no characters or group selected
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat changed
|
||||
if (
|
||||
context.chatId !== lastChatId
|
||||
) {
|
||||
currentMessageNumber = context.chat.length ? context.chat.length : 0;
|
||||
saveLastValues();
|
||||
|
||||
// Force to speak on the first message in the new chat
|
||||
if (context.chat.length === 1) {
|
||||
lastMessageHash = -1;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// take the count of messages
|
||||
let lastMessageNumber = context.chat.length ? context.chat.length : 0;
|
||||
|
||||
// There's no new messages
|
||||
let diff = lastMessageNumber - currentMessageNumber;
|
||||
let hashNew = getStringHash((chat.length && chat[chat.length - 1].mes) ?? '');
|
||||
|
||||
// if messages got deleted, diff will be < 0
|
||||
if (diff < 0) {
|
||||
// necessary actions will be taken by the onChatDeleted() handler
|
||||
return;
|
||||
}
|
||||
|
||||
// if no new messages, or same message, or same message hash, do nothing
|
||||
if (diff == 0 && hashNew === lastMessageHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If streaming, wait for streaming to finish before processing new messages
|
||||
if (context.streamingProcessor && !context.streamingProcessor.isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clone message object, as things go haywire if message object is altered below (it's passed by reference)
|
||||
const message = structuredClone(chat[chat.length - 1]);
|
||||
|
||||
// if last message within current message, message got extended. only send diff to TTS.
|
||||
if (ttsLastMessage !== null && message.mes.indexOf(ttsLastMessage) !== -1) {
|
||||
let tmp = message.mes;
|
||||
message.mes = message.mes.replace(ttsLastMessage, '');
|
||||
ttsLastMessage = tmp;
|
||||
} else {
|
||||
ttsLastMessage = message.mes;
|
||||
}
|
||||
|
||||
// We're currently swiping. Don't generate voice
|
||||
if (!message || message.mes === '...' || message.mes === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate if message doesn't have a display text
|
||||
if (extension_settings.tts.narrate_translated_only && !(message?.extra?.display_text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate if message is a user message and user message narration is disabled
|
||||
if (message.is_user && !extension_settings.tts.narrate_user) {
|
||||
return;
|
||||
}
|
||||
|
||||
// New messages, add new chat to history
|
||||
lastMessageHash = hashNew;
|
||||
currentMessageNumber = lastMessageNumber;
|
||||
|
||||
console.debug(
|
||||
`Adding message from ${message.name} for TTS processing: "${message.mes}"`,
|
||||
);
|
||||
ttsJobQueue.push(message);
|
||||
}
|
||||
|
||||
function talkingAnimation(switchValue) {
|
||||
@ -238,11 +148,11 @@ function talkingAnimation(switchValue) {
|
||||
const apiUrl = getApiUrl();
|
||||
const animationType = switchValue ? 'start' : 'stop';
|
||||
|
||||
if (switchValue !== storedvalue) {
|
||||
if (switchValue !== talkingHeadState) {
|
||||
try {
|
||||
console.log(animationType + ' Talking Animation');
|
||||
doExtrasFetch(`${apiUrl}/api/talkinghead/${animationType}_talking`);
|
||||
storedvalue = switchValue; // Update the storedvalue to the current switchValue
|
||||
talkingHeadState = switchValue;
|
||||
} catch (error) {
|
||||
// Handle the error here or simply ignore it to prevent logging
|
||||
}
|
||||
@ -289,7 +199,6 @@ function debugTtsPlayback() {
|
||||
{
|
||||
'ttsProviderName': ttsProviderName,
|
||||
'voiceMap': voiceMap,
|
||||
'currentMessageNumber': currentMessageNumber,
|
||||
'audioPaused': audioPaused,
|
||||
'audioJobQueue': audioJobQueue,
|
||||
'currentAudioJob': currentAudioJob,
|
||||
@ -465,6 +374,7 @@ async function processAudioJobQueue() {
|
||||
playAudioData(currentAudioJob);
|
||||
talkingAnimation(true);
|
||||
} catch (error) {
|
||||
toastr.error(error.toString());
|
||||
console.error(error);
|
||||
audioQueueProcessorReady = true;
|
||||
}
|
||||
@ -476,21 +386,12 @@ async function processAudioJobQueue() {
|
||||
|
||||
let ttsJobQueue = [];
|
||||
let currentTtsJob; // Null if nothing is currently being processed
|
||||
let currentMessageNumber = 0;
|
||||
|
||||
function completeTtsJob() {
|
||||
console.info(`Current TTS job for ${currentTtsJob?.name} completed.`);
|
||||
currentTtsJob = null;
|
||||
}
|
||||
|
||||
function saveLastValues() {
|
||||
const context = getContext();
|
||||
lastChatId = context.chatId;
|
||||
lastMessageHash = getStringHash(
|
||||
(context.chat.length && context.chat[context.chat.length - 1].mes) ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
async function tts(text, voiceId, char) {
|
||||
async function processResponse(response) {
|
||||
// RVC injection
|
||||
@ -581,8 +482,9 @@ async function processTtsQueue() {
|
||||
toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`);
|
||||
throw `Unable to attain voiceId for ${char}`;
|
||||
}
|
||||
tts(text, voiceId, char);
|
||||
await tts(text, voiceId, char);
|
||||
} catch (error) {
|
||||
toastr.error(error.toString());
|
||||
console.error(error);
|
||||
currentTtsJob = null;
|
||||
}
|
||||
@ -654,6 +556,7 @@ function onRefreshClick() {
|
||||
initVoiceMap();
|
||||
updateVoiceMap();
|
||||
}).catch(error => {
|
||||
toastr.error(error.toString());
|
||||
console.error(error);
|
||||
setTtsStatus(error, false);
|
||||
});
|
||||
@ -761,26 +664,103 @@ async function onChatChanged() {
|
||||
await resetTtsPlayback();
|
||||
const voiceMapInit = initVoiceMap();
|
||||
await Promise.race([voiceMapInit, delay(1000)]);
|
||||
ttsLastMessage = null;
|
||||
lastMessage = null;
|
||||
}
|
||||
|
||||
async function onChatDeleted() {
|
||||
async function onMessageEvent(messageId) {
|
||||
// If TTS is disabled, do nothing
|
||||
if (!extension_settings.tts.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto generation is disabled
|
||||
if (!extension_settings.tts.auto_generation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
|
||||
// no characters or group selected
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Chat changed
|
||||
if (context.chatId !== lastChatId) {
|
||||
lastChatId = context.chatId;
|
||||
lastMessageHash = getStringHash(context.chat[messageId]?.mes ?? '');
|
||||
|
||||
// Force to speak on the first message in the new chat
|
||||
if (context.chat.length === 1) {
|
||||
lastMessageHash = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// clone message object, as things go haywire if message object is altered below (it's passed by reference)
|
||||
const message = structuredClone(context.chat[messageId]);
|
||||
const hashNew = getStringHash(message?.mes ?? '');
|
||||
|
||||
// if no new messages, or same message, or same message hash, do nothing
|
||||
if (hashNew === lastMessageHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isLastMessageInCurrent = () =>
|
||||
lastMessage &&
|
||||
typeof lastMessage === 'object' &&
|
||||
message.swipe_id === lastMessage.swipe_id &&
|
||||
message.name === lastMessage.name &&
|
||||
message.is_user === lastMessage.is_user &&
|
||||
message.mes.indexOf(lastMessage.mes) !== -1;
|
||||
|
||||
// if last message within current message, message got extended. only send diff to TTS.
|
||||
if (isLastMessageInCurrent()) {
|
||||
const tmp = structuredClone(message);
|
||||
message.mes = message.mes.replace(lastMessage.mes, '');
|
||||
lastMessage = tmp;
|
||||
} else {
|
||||
lastMessage = structuredClone(message);
|
||||
}
|
||||
|
||||
// We're currently swiping. Don't generate voice
|
||||
if (!message || message.mes === '...' || message.mes === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate if message doesn't have a display text
|
||||
if (extension_settings.tts.narrate_translated_only && !(message?.extra?.display_text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't generate if message is a user message and user message narration is disabled
|
||||
if (message.is_user && !extension_settings.tts.narrate_user) {
|
||||
return;
|
||||
}
|
||||
|
||||
// New messages, add new chat to history
|
||||
lastMessageHash = hashNew;
|
||||
lastChatId = context.chatId;
|
||||
|
||||
console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`);
|
||||
ttsJobQueue.push(message);
|
||||
}
|
||||
|
||||
async function onMessageDeleted() {
|
||||
const context = getContext();
|
||||
|
||||
// update internal references to new last message
|
||||
lastChatId = context.chatId;
|
||||
currentMessageNumber = context.chat.length ? context.chat.length : 0;
|
||||
|
||||
// compare against lastMessageHash. If it's the same, we did not delete the last chat item, so no need to reset tts queue
|
||||
let messageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1].mes) ?? '');
|
||||
const messageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1].mes) ?? '');
|
||||
if (messageHash === lastMessageHash) {
|
||||
return;
|
||||
}
|
||||
lastMessageHash = messageHash;
|
||||
ttsLastMessage = (context.chat.length && context.chat[context.chat.length - 1].mes) ?? '';
|
||||
lastMessage = context.chat.length ? structuredClone(context.chat[context.chat.length - 1]) : null;
|
||||
|
||||
// stop any tts playback since message might not exist anymore
|
||||
await resetTtsPlayback();
|
||||
resetTtsPlayback();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1076,8 +1056,10 @@ $(document).ready(function () {
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||
eventSource.on(event_types.MESSAGE_DELETED, onChatDeleted);
|
||||
eventSource.on(event_types.MESSAGE_DELETED, onMessageDeleted);
|
||||
eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
|
||||
eventSource.on(event_types.MESSAGE_SENT, onMessageEvent);
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, onMessageEvent);
|
||||
registerSlashCommand('speak', onNarrateText, ['narrate', 'tts'], '<span class="monospace">(text)</span> – narrate any text using currently selected character\'s voice. Use voice="Character Name" argument to set other voice from the voice map, example: <tt>/speak voice="Donald Duck" Quack!</tt>', true, true);
|
||||
document.body.appendChild(audioElement);
|
||||
});
|
||||
|
@ -9,9 +9,11 @@ import {
|
||||
saveBase64AsFile,
|
||||
PAGINATION_TEMPLATE,
|
||||
getBase64Async,
|
||||
resetScrollHeight,
|
||||
initScrollHeight,
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js';
|
||||
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js';
|
||||
|
||||
import {
|
||||
chat,
|
||||
@ -73,6 +75,7 @@ import {
|
||||
} from '../script.js';
|
||||
import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { isExternalMediaAllowed } from './chats.js';
|
||||
|
||||
export {
|
||||
selected_group,
|
||||
@ -176,7 +179,7 @@ async function loadGroupChat(chatId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getGroupChat(groupId) {
|
||||
export async function getGroupChat(groupId, reload = false) {
|
||||
const group = groups.find((x) => x.id === groupId);
|
||||
const chat_id = group.chat_id;
|
||||
const data = await loadGroupChat(chat_id);
|
||||
@ -216,6 +219,10 @@ export async function getGroupChat(groupId) {
|
||||
updateChatMetadata(metadata, true);
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
select_group_chats(groupId, true);
|
||||
}
|
||||
|
||||
await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId());
|
||||
}
|
||||
|
||||
@ -346,6 +353,46 @@ export function getGroupCharacterCards(groupId, characterId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the macro engine on a text, with custom <FIELDNAME> replace
|
||||
* @param {string} value Value to replace
|
||||
* @param {string} fieldName Name of the field
|
||||
* @param {string} characterName Name of the character
|
||||
* @returns {string} Replaced text
|
||||
* */
|
||||
function customBaseChatReplace(value, fieldName, characterName) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// We should do the custom field name replacement first, and then run it through the normal macro engine with provided names
|
||||
value = value.replace(/<FIELDNAME>/gi, fieldName);
|
||||
return baseChatReplace(value.trim(), name1, characterName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares text with prefix/suffix for a character field
|
||||
* @param {string} value Value to replace
|
||||
* @param {string} characterName Name of the character
|
||||
* @param {string} fieldName Name of the field
|
||||
* @returns {string} Prepared text
|
||||
* */
|
||||
function replaceAndPrepareForJoin(value, characterName, fieldName) {
|
||||
value = value.trim();
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Prepare and replace prefixes
|
||||
const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName);
|
||||
const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName);
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
// Also run the macro replacement on the actual content
|
||||
value = customBaseChatReplace(value, fieldName, characterName);
|
||||
|
||||
return `${prefix ? prefix + separator : ''}${value}${suffix ? separator + suffix : ''}`;
|
||||
}
|
||||
|
||||
const scenarioOverride = chat_metadata['scenario'];
|
||||
|
||||
let descriptions = [];
|
||||
@ -367,16 +414,16 @@ export function getGroupCharacterCards(groupId, characterId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptions.push(baseChatReplace(character.description.trim(), name1, character.name));
|
||||
personalities.push(baseChatReplace(character.personality.trim(), name1, character.name));
|
||||
scenarios.push(baseChatReplace(character.scenario.trim(), name1, character.name));
|
||||
mesExamplesArray.push(baseChatReplace(character.mes_example.trim(), name1, character.name));
|
||||
descriptions.push(replaceAndPrepareForJoin(character.description, character.name, 'Description'));
|
||||
personalities.push(replaceAndPrepareForJoin(character.personality, character.name, 'Personality'));
|
||||
scenarios.push(replaceAndPrepareForJoin(character.scenario, character.name, 'Scenario'));
|
||||
mesExamplesArray.push(replaceAndPrepareForJoin(character.mes_example, character.name, 'Example Messages'));
|
||||
}
|
||||
|
||||
const description = descriptions.join('\n');
|
||||
const personality = personalities.join('\n');
|
||||
const scenario = scenarioOverride?.trim() || scenarios.join('\n');
|
||||
const mesExamples = mesExamplesArray.join('\n');
|
||||
const description = descriptions.filter(x => x.length).join('\n');
|
||||
const personality = personalities.filter(x => x.length).join('\n');
|
||||
const scenario = scenarioOverride?.trim() || scenarios.filter(x => x.length).join('\n');
|
||||
const mesExamples = mesExamplesArray.filter(x => x.length).join('\n');
|
||||
|
||||
return { description, personality, scenario, mesExamples };
|
||||
}
|
||||
@ -1088,6 +1135,8 @@ async function onGroupGenerationModeInput(e) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
_thisGroup.generation_mode = Number(e.target.value);
|
||||
await editGroup(openGroupId, false, false);
|
||||
|
||||
toggleHiddenControls(_thisGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1100,6 +1149,15 @@ async function onGroupAutoModeDelayInput(e) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onGroupGenerationModeTemplateInput(e) {
|
||||
if (openGroupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
const prop = $(e.target).attr('setting');
|
||||
_thisGroup[prop] = String(e.target.value);
|
||||
await editGroup(openGroupId, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function onGroupNameInput() {
|
||||
if (openGroupId) {
|
||||
let _thisGroup = groups.find((x) => x.id == openGroupId);
|
||||
@ -1265,6 +1323,14 @@ async function onHideMutedSpritesClick(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleHiddenControls(group, generationMode = null) {
|
||||
const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(generationMode ?? group?.generation_mode);
|
||||
$('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin);
|
||||
$('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin);
|
||||
initScrollHeight($('#rm_group_generation_mode_join_prefix'));
|
||||
initScrollHeight($('#rm_group_generation_mode_join_suffix'));
|
||||
}
|
||||
|
||||
function select_group_chats(groupId, skipAnimation) {
|
||||
openGroupId = groupId;
|
||||
newGroupMembers = [];
|
||||
@ -1300,12 +1366,20 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$('#rm_group_hidemutedsprites').prop('checked', group && group.hideMutedSprites);
|
||||
$('#rm_group_automode_delay').val(group?.auto_mode_delay ?? DEFAULT_AUTO_MODE_DELAY);
|
||||
|
||||
$('#rm_group_generation_mode_join_prefix').val(group?.generation_mode_join_prefix ?? '').attr('setting', 'generation_mode_join_prefix');
|
||||
$('#rm_group_generation_mode_join_suffix').val(group?.generation_mode_join_suffix ?? '').attr('setting', 'generation_mode_join_suffix');
|
||||
toggleHiddenControls(group, generationMode);
|
||||
|
||||
// bottom buttons
|
||||
if (openGroupId) {
|
||||
$('#rm_group_submit').hide();
|
||||
$('#rm_group_delete').show();
|
||||
$('#rm_group_scenario').show();
|
||||
$('#group-metadata-controls .chat_lorebook_button').removeClass('disabled').prop('disabled', false);
|
||||
$('#group_open_media_overrides').show();
|
||||
const isMediaAllowed = isExternalMediaAllowed();
|
||||
$('#group_media_allowed_icon').toggle(isMediaAllowed);
|
||||
$('#group_media_forbidden_icon').toggle(!isMediaAllowed);
|
||||
} else {
|
||||
$('#rm_group_submit').show();
|
||||
if ($('#groupAddMemberListToggle .inline-drawer-content').css('display') !== 'block') {
|
||||
@ -1314,6 +1388,7 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$('#rm_group_delete').hide();
|
||||
$('#rm_group_scenario').hide();
|
||||
$('#group-metadata-controls .chat_lorebook_button').addClass('disabled').prop('disabled', true);
|
||||
$('#group_open_media_overrides').hide();
|
||||
}
|
||||
|
||||
updateFavButtonState(group?.fav ?? false);
|
||||
@ -1328,6 +1403,11 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$('#rm_group_automode_label').hide();
|
||||
}
|
||||
|
||||
// Toggle textbox sizes, as input events have not fired here
|
||||
$('#rm_group_chats_block .autoSetHeight').each(element => {
|
||||
resetScrollHeight(element);
|
||||
});
|
||||
|
||||
eventSource.emit('groupSelected', { detail: { id: openGroupId, group: group } });
|
||||
}
|
||||
|
||||
@ -1786,6 +1866,10 @@ function doCurMemberListPopout() {
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
$(document).on('input', '#rm_group_chats_block .autoSetHeight', function () {
|
||||
resetScrollHeight($(this));
|
||||
});
|
||||
|
||||
$(document).on('click', '.group_select', function () {
|
||||
const groupId = $(this).attr('chid') || $(this).attr('grid') || $(this).data('id');
|
||||
openGroupById(groupId);
|
||||
@ -1813,6 +1897,8 @@ jQuery(() => {
|
||||
$('#rm_group_activation_strategy').on('change', onGroupActivationStrategyInput);
|
||||
$('#rm_group_generation_mode').on('change', onGroupGenerationModeInput);
|
||||
$('#rm_group_automode_delay').on('input', onGroupAutoModeDelayInput);
|
||||
$('#rm_group_generation_mode_join_prefix').on('input', onGroupGenerationModeTemplateInput);
|
||||
$('#rm_group_generation_mode_join_suffix').on('input', onGroupGenerationModeTemplateInput);
|
||||
$('#group_avatar_button').on('input', uploadGroupAvatar);
|
||||
$('#rm_group_restore_avatar').on('click', restoreGroupAvatar);
|
||||
$(document).on('click', '.group_member .right_menu_button', onGroupActionClick);
|
||||
|
@ -26,6 +26,7 @@ const controls = [
|
||||
{ id: 'instruct_output_suffix', property: 'output_suffix', isCheckbox: false },
|
||||
{ id: 'instruct_system_sequence', property: 'system_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_system_suffix', property: 'system_suffix', isCheckbox: false },
|
||||
{ id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },
|
||||
{ id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_names', property: 'names', isCheckbox: true },
|
||||
@ -56,6 +57,7 @@ function migrateInstructModeSettings(settings) {
|
||||
system_sequence: '',
|
||||
system_suffix: '',
|
||||
user_alignment_message: '',
|
||||
last_system_sequence: '',
|
||||
names_force_groups: true,
|
||||
skip_examples: false,
|
||||
system_same_as_user: false,
|
||||
@ -249,8 +251,9 @@ export function getInstructStoppingSequences() {
|
||||
const first_output_sequence = power_user.instruct.first_output_sequence?.replace(/{{name}}/gi, name2) || '';
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence?.replace(/{{name}}/gi, name2) || '';
|
||||
const system_sequence = power_user.instruct.system_sequence?.replace(/{{name}}/gi, 'System') || '';
|
||||
const last_system_sequence = power_user.instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || '';
|
||||
|
||||
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}`;
|
||||
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}\n${last_system_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
@ -369,7 +372,7 @@ export function formatInstructModeSystemPrompt(systemPrompt) {
|
||||
* @returns {string[]} Formatted example messages string.
|
||||
*/
|
||||
export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
const blockHeading = power_user.context.example_separator ? power_user.context.example_separator + '\n' : '';
|
||||
const blockHeading = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : '';
|
||||
|
||||
if (power_user.instruct.skip_examples) {
|
||||
return mesExamplesArray.map(x => x.replace(/<START>\n/i, blockHeading));
|
||||
@ -416,10 +419,13 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
}
|
||||
|
||||
for (const example of blockExamples) {
|
||||
// If force group/persona names is set, we should override the include names for the user placeholder
|
||||
const includeThisName = includeNames || (power_user.instruct.names_force_groups && example.name == 'example_user');
|
||||
|
||||
const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
|
||||
const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
|
||||
const name = example.name == 'example_user' ? name1 : name2;
|
||||
const messageContent = includeNames ? `${name}: ${example.content}` : example.content;
|
||||
const messageContent = includeThisName ? `${name}: ${example.content}` : example.content;
|
||||
const formattedMessage = [prefix, messageContent + suffix].filter(x => x).join(separator);
|
||||
formattedExamples.push(formattedMessage);
|
||||
}
|
||||
@ -452,9 +458,10 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
|
||||
return power_user.instruct.input_sequence;
|
||||
}
|
||||
|
||||
// Neutral / system prompt
|
||||
// Neutral / system / quiet prompt
|
||||
// Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise
|
||||
if (isQuiet && !isQuietToLoud) {
|
||||
return power_user.instruct.output_sequence;
|
||||
return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
// Quiet in-character prompt
|
||||
@ -511,26 +518,37 @@ function selectMatchingContextTemplate(name) {
|
||||
/**
|
||||
* Replaces instruct mode macros in the given input string.
|
||||
* @param {string} input Input string.
|
||||
* @param {Object<string, *>} env - Map of macro names to the values they'll be substituted with. If the param
|
||||
* values are functions, those functions will be called and their return values are used.
|
||||
* @returns {string} String with macros replaced.
|
||||
*/
|
||||
export function replaceInstructMacros(input) {
|
||||
export function replaceInstructMacros(input, env) {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
const instructMacros = {
|
||||
'systemPrompt': (power_user.prefer_character_prompt && env.charPrompt ? env.charPrompt : power_user.instruct.system_prompt),
|
||||
'instructSystem|instructSystemPrompt': power_user.instruct.system_prompt,
|
||||
'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix,
|
||||
'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix,
|
||||
'instructInput|instructUserPrefix': power_user.instruct.input_sequence,
|
||||
'instructUserSuffix': power_user.instruct.input_suffix,
|
||||
'instructOutput|instructAssistantPrefix': power_user.instruct.output_sequence,
|
||||
'instructSeparator|instructAssistantSuffix': power_user.instruct.output_suffix,
|
||||
'instructSystemPrefix': power_user.instruct.system_sequence,
|
||||
'instructSystemSuffix': power_user.instruct.system_suffix,
|
||||
'instructFirstOutput|instructFirstAssistantPrefix': power_user.instruct.first_output_sequence || power_user.instruct.output_sequence,
|
||||
'instructLastOutput|instructLastAssistantPrefix': power_user.instruct.last_output_sequence || power_user.instruct.output_sequence,
|
||||
'instructStop': power_user.instruct.stop_sequence,
|
||||
'instructUserFiller': power_user.instruct.user_alignment_message,
|
||||
'instructSystemInstructionPrefix': power_user.instruct.last_system_sequence,
|
||||
};
|
||||
|
||||
for (const [placeholder, value] of Object.entries(instructMacros)) {
|
||||
const regex = new RegExp(`{{(${placeholder})}}`, 'gi');
|
||||
input = input.replace(regex, power_user.instruct.enabled ? value : '');
|
||||
}
|
||||
|
||||
input = input.replace(/{{(instructSystem|instructSystemPrompt)}}/gi, power_user.instruct.enabled ? power_user.instruct.system_prompt : '');
|
||||
input = input.replace(/{{instructSystemPromptPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_prefix : '');
|
||||
input = input.replace(/{{instructSystemPromptSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_suffix : '');
|
||||
input = input.replace(/{{(instructInput|instructUserPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.input_sequence : '');
|
||||
input = input.replace(/{{instructUserSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.input_suffix : '');
|
||||
input = input.replace(/{{(instructOutput|instructAssistantPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_sequence : '');
|
||||
input = input.replace(/{{(instructSeparator|instructAssistantSuffix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_suffix : '');
|
||||
input = input.replace(/{{instructSystemPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence : '');
|
||||
input = input.replace(/{{instructSystemSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_suffix : '');
|
||||
input = input.replace(/{{(instructFirstOutput|instructFirstAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.first_output_sequence || power_user.instruct.output_sequence) : '');
|
||||
input = input.replace(/{{(instructLastOutput|instructLastAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.last_output_sequence || power_user.instruct.output_sequence) : '');
|
||||
input = input.replace(/{{instructStop}}/gi, power_user.instruct.enabled ? power_user.instruct.stop_sequence : '');
|
||||
input = input.replace(/{{instructUserFiller}}/gi, power_user.instruct.enabled ? power_user.instruct.user_alignment_message : '');
|
||||
input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);
|
||||
input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { chat, main_api, getMaxContextSize, getCurrentChatId } from '../script.js';
|
||||
import { chat, chat_metadata, main_api, getMaxContextSize, getCurrentChatId } from '../script.js';
|
||||
import { timestampToMoment, isDigitsOnly, getStringHash } from './utils.js';
|
||||
import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
|
||||
import { replaceInstructMacros } from './instruct-mode.js';
|
||||
@ -8,121 +8,121 @@ import { replaceVariableMacros } from './variables.js';
|
||||
Handlebars.registerHelper('trim', () => '{{trim}}');
|
||||
|
||||
/**
|
||||
* Returns the ID of the last message in the chat.
|
||||
* @returns {string} The ID of the last message in the chat.
|
||||
* Gets a hashed id of the current chat from the metadata.
|
||||
* If no metadata exists, creates a new hash and saves it.
|
||||
* @returns {number} The hashed chat id
|
||||
*/
|
||||
function getLastMessageId() {
|
||||
const index = chat?.length - 1;
|
||||
function getChatIdHash() {
|
||||
const cachedIdHash = chat_metadata['chat_id_hash'];
|
||||
|
||||
if (!isNaN(index) && index >= 0) {
|
||||
return String(index);
|
||||
// If chat_id_hash is not already set, calculate it
|
||||
if (!cachedIdHash) {
|
||||
// Use the main_chat if it's available, otherwise get the current chat ID
|
||||
const chatId = chat_metadata['main_chat'] ?? getCurrentChatId();
|
||||
const chatIdHash = getStringHash(chatId);
|
||||
chat_metadata['chat_id_hash'] = chatIdHash;
|
||||
return chatIdHash;
|
||||
}
|
||||
|
||||
return '';
|
||||
return cachedIdHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the first message included in the context.
|
||||
* @returns {string} The ID of the first message in the context.
|
||||
* Returns the ID of the last message in the chat
|
||||
*
|
||||
* Optionally can only choose specific messages, if a filter is provided.
|
||||
*
|
||||
* @param {object} param0 - Optional arguments
|
||||
* @param {boolean} [param0.exclude_swipe_in_propress=true] - Whether a message that is currently being swiped should be ignored
|
||||
* @param {function(object):boolean} [param0.filter] - A filter applied to the search, ignoring all messages that don't match the criteria. For example to only find user messages, etc.
|
||||
* @returns {number|null} The message id, or null if none was found
|
||||
*/
|
||||
export function getLastMessageId({ exclude_swipe_in_propress = true, filter = null } = {}) {
|
||||
for (let i = chat?.length - 1; i >= 0; i--) {
|
||||
let message = chat[i];
|
||||
|
||||
// If ignoring swipes and the message is being swiped, continue
|
||||
// We can check if a message is being swiped by checking whether the current swipe id is not in the list of finished swipes yet
|
||||
if (exclude_swipe_in_propress && message.swipes && message.swipe_id >= message.swipes.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if no filter is provided, or if the message passes the filter
|
||||
if (!filter || filter(message)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the first message included in the context
|
||||
*
|
||||
* @returns {number|null} The ID of the first message in the context
|
||||
*/
|
||||
function getFirstIncludedMessageId() {
|
||||
const index = document.querySelector('.lastInContext')?.getAttribute('mesid');
|
||||
const index = Number(document.querySelector('.lastInContext')?.getAttribute('mesid'));
|
||||
|
||||
if (!isNaN(index) && index >= 0) {
|
||||
return String(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last message in the chat.
|
||||
* @returns {string} The last message in the chat.
|
||||
* Returns the last message in the chat
|
||||
*
|
||||
* @returns {string} The last message in the chat
|
||||
*/
|
||||
function getLastMessage() {
|
||||
const index = chat?.length - 1;
|
||||
|
||||
if (!isNaN(index) && index >= 0) {
|
||||
return chat[index].mes;
|
||||
}
|
||||
|
||||
return '';
|
||||
const mid = getLastMessageId();
|
||||
return chat[mid]?.mes ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last message from the user.
|
||||
* @returns {string} The last message from the user.
|
||||
* Returns the last message from the user
|
||||
*
|
||||
* @returns {string} The last message from the user
|
||||
*/
|
||||
function getLastUserMessage() {
|
||||
if (!Array.isArray(chat) || chat.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for (let i = chat.length - 1; i >= 0; i--) {
|
||||
if (chat[i].is_user && !chat[i].is_system) {
|
||||
return chat[i].mes;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
const mid = getLastMessageId({ filter: m => m.is_user && !m.is_system });
|
||||
return chat[mid]?.mes ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last message from the bot.
|
||||
* @returns {string} The last message from the bot.
|
||||
* Returns the last message from the bot
|
||||
*
|
||||
* @returns {string} The last message from the bot
|
||||
*/
|
||||
function getLastCharMessage() {
|
||||
if (!Array.isArray(chat) || chat.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for (let i = chat.length - 1; i >= 0; i--) {
|
||||
if (!chat[i].is_user && !chat[i].is_system) {
|
||||
return chat[i].mes;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
const mid = getLastMessageId({ filter: m => !m.is_user && !m.is_system });
|
||||
return chat[mid]?.mes ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the last swipe.
|
||||
* @returns {string} The 1-based ID of the last swipe
|
||||
* Returns the 1-based ID (number) of the last swipe
|
||||
*
|
||||
* @returns {number|null} The 1-based ID of the last swipe
|
||||
*/
|
||||
function getLastSwipeId() {
|
||||
const index = chat?.length - 1;
|
||||
|
||||
if (!isNaN(index) && index >= 0) {
|
||||
const swipes = chat[index].swipes;
|
||||
|
||||
if (!Array.isArray(swipes) || swipes.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(swipes.length);
|
||||
}
|
||||
|
||||
return '';
|
||||
// For swipe macro, we are accepting using the message that is currently being swiped
|
||||
const mid = getLastMessageId({ exclude_swipe_in_propress: false });
|
||||
const swipes = chat[mid]?.swipes;
|
||||
return swipes?.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the current swipe.
|
||||
* @returns {string} The 1-based ID of the current swipe.
|
||||
* Returns the 1-based ID (number) of the current swipe
|
||||
*
|
||||
* @returns {number|null} The 1-based ID of the current swipe
|
||||
*/
|
||||
function getCurrentSwipeId() {
|
||||
const index = chat?.length - 1;
|
||||
|
||||
if (!isNaN(index) && index >= 0) {
|
||||
const swipeId = chat[index].swipe_id;
|
||||
|
||||
if (swipeId === undefined || isNaN(swipeId)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return String(swipeId + 1);
|
||||
}
|
||||
|
||||
return '';
|
||||
// For swipe macro, we are accepting using the message that is currently being swiped
|
||||
const mid = getLastMessageId({ exclude_swipe_in_propress: false });
|
||||
const swipeId = chat[mid]?.swipe_id;
|
||||
return swipeId ? swipeId + 1 : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,7 +205,10 @@ function randomReplace(input, emptyListPlaceholder = '') {
|
||||
|
||||
function pickReplace(input, rawContent, emptyListPlaceholder = '') {
|
||||
const pickPattern = /{{pick\s?::?([^}]+)}}/gi;
|
||||
const chatIdHash = getStringHash(getCurrentChatId());
|
||||
|
||||
// We need to have a consistent chat hash, otherwise we'll lose rolls on chat file rename or branch switches
|
||||
// No need to save metadata here - branching and renaming will implicitly do the save for us, and until then loading it like this is consistent
|
||||
const chatIdHash = getChatIdHash();
|
||||
const rawContentHash = getStringHash(rawContent);
|
||||
|
||||
return input.replace(pickPattern, (match, listString, offset) => {
|
||||
@ -276,10 +279,11 @@ export function evaluateMacros(content, env) {
|
||||
}
|
||||
|
||||
content = diceRollReplace(content);
|
||||
content = replaceInstructMacros(content);
|
||||
content = replaceInstructMacros(content, env);
|
||||
content = replaceVariableMacros(content);
|
||||
content = content.replace(/{{newline}}/gi, '\n');
|
||||
content = content.replace(/\n*{{trim}}\n*/gi, '');
|
||||
content = content.replace(/{{noop}}/gi, '');
|
||||
content = content.replace(/{{input}}/gi, () => String($('#send_textarea').val()));
|
||||
|
||||
// Substitute passed-in variables
|
||||
@ -292,12 +296,12 @@ export function evaluateMacros(content, env) {
|
||||
|
||||
content = content.replace(/{{maxPrompt}}/gi, () => String(getMaxContextSize()));
|
||||
content = content.replace(/{{lastMessage}}/gi, () => getLastMessage());
|
||||
content = content.replace(/{{lastMessageId}}/gi, () => getLastMessageId());
|
||||
content = content.replace(/{{lastMessageId}}/gi, () => String(getLastMessageId() ?? ''));
|
||||
content = content.replace(/{{lastUserMessage}}/gi, () => getLastUserMessage());
|
||||
content = content.replace(/{{lastCharMessage}}/gi, () => getLastCharMessage());
|
||||
content = content.replace(/{{firstIncludedMessageId}}/gi, () => getFirstIncludedMessageId());
|
||||
content = content.replace(/{{lastSwipeId}}/gi, () => getLastSwipeId());
|
||||
content = content.replace(/{{currentSwipeId}}/gi, () => getCurrentSwipeId());
|
||||
content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? ''));
|
||||
content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? ''));
|
||||
content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? ''));
|
||||
|
||||
content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, '');
|
||||
|
||||
|
@ -186,6 +186,11 @@ const continue_postfix_types = {
|
||||
DOUBLE_NEWLINE: '\n\n',
|
||||
};
|
||||
|
||||
const custom_prompt_post_processing_types = {
|
||||
NONE: '',
|
||||
CLAUDE: 'claude',
|
||||
};
|
||||
|
||||
const prefixMap = selected_group ? {
|
||||
assistant: '',
|
||||
user: '',
|
||||
@ -262,6 +267,7 @@ const default_settings = {
|
||||
continue_prefill: false,
|
||||
names_behavior: character_names_behavior.NONE,
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
|
||||
seed: -1,
|
||||
n: 1,
|
||||
};
|
||||
@ -331,6 +337,7 @@ const oai_settings = {
|
||||
continue_prefill: false,
|
||||
names_behavior: character_names_behavior.NONE,
|
||||
continue_postfix: continue_postfix_types.SPACE,
|
||||
custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
|
||||
seed: -1,
|
||||
n: 1,
|
||||
};
|
||||
@ -431,15 +438,15 @@ function convertChatCompletionToInstruct(messages, type) {
|
||||
const exampleMessages = messages.filter(x => x.role === 'system' && (x.name === 'example_user' || x.name === 'example_assistant'));
|
||||
|
||||
if (exampleMessages.length) {
|
||||
examplesText = power_user.context.example_separator + '\n';
|
||||
examplesText += exampleMessages.map(toString).join('\n');
|
||||
examplesText = formatInstructModeExamples(examplesText, name1, name2);
|
||||
const blockHeading = power_user.context.example_separator ? (substituteParams(power_user.context.example_separator) + '\n') : '';
|
||||
const examplesArray = exampleMessages.map(m => '<START>\n' + toString(m));
|
||||
examplesText = blockHeading + formatInstructModeExamples(examplesArray, name1, name2).join('');
|
||||
}
|
||||
|
||||
const chatMessages = messages.slice(firstChatMessage);
|
||||
|
||||
if (chatMessages.length) {
|
||||
chatMessagesText = power_user.context.chat_start + '\n';
|
||||
chatMessagesText = substituteParams(power_user.context.chat_start) + '\n';
|
||||
|
||||
for (const message of chatMessages) {
|
||||
const name = getPrefix(message);
|
||||
@ -1743,6 +1750,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
generate_data['custom_include_body'] = oai_settings.custom_include_body;
|
||||
generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;
|
||||
generate_data['custom_include_headers'] = oai_settings.custom_include_headers;
|
||||
generate_data['custom_prompt_post_processing'] = oai_settings.custom_prompt_post_processing;
|
||||
}
|
||||
|
||||
if (isCohere) {
|
||||
@ -2625,6 +2633,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
|
||||
oai_settings.custom_exclude_body = settings.custom_exclude_body ?? default_settings.custom_exclude_body;
|
||||
oai_settings.custom_include_headers = settings.custom_include_headers ?? default_settings.custom_include_headers;
|
||||
oai_settings.custom_prompt_post_processing = settings.custom_prompt_post_processing ?? default_settings.custom_prompt_post_processing;
|
||||
oai_settings.google_model = settings.google_model ?? default_settings.google_model;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
|
||||
@ -2770,6 +2779,8 @@ function loadOpenAISettings(data, settings) {
|
||||
|
||||
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
|
||||
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
|
||||
$('#custom_prompt_post_processing').val(oai_settings.custom_prompt_post_processing);
|
||||
$(`#custom_prompt_post_processing option[value="${oai_settings.custom_prompt_post_processing}"]`).attr('selected', true);
|
||||
}
|
||||
|
||||
function setNamesBehaviorControls() {
|
||||
@ -2924,6 +2935,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
|
||||
custom_include_body: settings.custom_include_body,
|
||||
custom_exclude_body: settings.custom_exclude_body,
|
||||
custom_include_headers: settings.custom_include_headers,
|
||||
custom_prompt_post_processing: settings.custom_prompt_post_processing,
|
||||
google_model: settings.google_model,
|
||||
temperature: settings.temp_openai,
|
||||
frequency_penalty: settings.freq_pen_openai,
|
||||
@ -3313,6 +3325,7 @@ function onSettingsPresetChange() {
|
||||
custom_include_body: ['#custom_include_body', 'custom_include_body', false],
|
||||
custom_exclude_body: ['#custom_exclude_body', 'custom_exclude_body', false],
|
||||
custom_include_headers: ['#custom_include_headers', 'custom_include_headers', false],
|
||||
custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false],
|
||||
google_model: ['#model_google_select', 'google_model', false],
|
||||
openai_max_context: ['#openai_max_context', 'openai_max_context', false],
|
||||
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false],
|
||||
@ -4025,12 +4038,13 @@ export function isImageInliningSupported() {
|
||||
'gemini-1.0-pro-vision-latest',
|
||||
'gemini-1.5-pro-latest',
|
||||
'gemini-pro-vision',
|
||||
'claude-3'
|
||||
'claude-3',
|
||||
'gpt-4-turbo',
|
||||
];
|
||||
|
||||
switch (oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.OPENAI:
|
||||
return visionSupportedModels.some(model => oai_settings.openai_model.includes(model));
|
||||
return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));
|
||||
case chat_completion_sources.MAKERSUITE:
|
||||
return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
|
||||
case chat_completion_sources.CLAUDE:
|
||||
@ -4493,6 +4507,11 @@ $(document).ready(async function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#custom_prompt_post_processing').on('change', function () {
|
||||
oai_settings.custom_prompt_post_processing = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#names_behavior').on('input', function () {
|
||||
oai_settings.names_behavior = Number($(this).val());
|
||||
setNamesBehaviorControls();
|
||||
|
225
public/scripts/popup.js
Normal file
225
public/scripts/popup.js
Normal file
@ -0,0 +1,225 @@
|
||||
import { animation_duration, animation_easing } from '../script.js';
|
||||
import { delay } from './utils.js';
|
||||
|
||||
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {Number}*/
|
||||
export const POPUP_TYPE = {
|
||||
'TEXT': 1,
|
||||
'CONFIRM': 2,
|
||||
'INPUT': 3,
|
||||
};
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {Boolean}*/
|
||||
export const POPUP_RESULT = {
|
||||
'AFFIRMATIVE': true,
|
||||
'NEGATIVE': false,
|
||||
'CANCELLED': undefined,
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class Popup {
|
||||
/**@type {POPUP_TYPE}*/ type;
|
||||
|
||||
/**@type {HTMLElement}*/ dom;
|
||||
/**@type {HTMLElement}*/ dlg;
|
||||
/**@type {HTMLElement}*/ text;
|
||||
/**@type {HTMLTextAreaElement}*/ input;
|
||||
/**@type {HTMLElement}*/ ok;
|
||||
/**@type {HTMLElement}*/ cancel;
|
||||
|
||||
/**@type {POPUP_RESULT}*/ result;
|
||||
/**@type {any}*/ value;
|
||||
|
||||
/**@type {Promise}*/ promise;
|
||||
/**@type {Function}*/ resolver;
|
||||
|
||||
/**@type {Function}*/ keyListenerBound;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{okButton?: string, cancelButton?: string, rows?: number, wide?: boolean, large?: boolean, allowHorizontalScrolling?: boolean, allowVerticalScrolling?: boolean }} PopupOptions - Options for the popup.
|
||||
* @param {JQuery<HTMLElement>|string|Element} text - Text to display in the popup.
|
||||
* @param {POPUP_TYPE} type - One of Popup.TYPE
|
||||
* @param {string} inputValue - Value to set the input to.
|
||||
* @param {PopupOptions} options - Options for the popup.
|
||||
*/
|
||||
constructor(text, type, inputValue = '', { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) {
|
||||
this.type = type;
|
||||
|
||||
/**@type {HTMLTemplateElement}*/
|
||||
const template = document.querySelector('#shadow_popup_template');
|
||||
// @ts-ignore
|
||||
this.dom = template.content.cloneNode(true).querySelector('.shadow_popup');
|
||||
const dlg = this.dom.querySelector('.dialogue_popup');
|
||||
// @ts-ignore
|
||||
this.dlg = dlg;
|
||||
this.text = this.dom.querySelector('.dialogue_popup_text');
|
||||
this.input = this.dom.querySelector('.dialogue_popup_input');
|
||||
this.ok = this.dom.querySelector('.dialogue_popup_ok');
|
||||
this.cancel = this.dom.querySelector('.dialogue_popup_cancel');
|
||||
|
||||
if (wide) dlg.classList.add('wide_dialogue_popup');
|
||||
if (large) dlg.classList.add('large_dialogue_popup');
|
||||
if (allowHorizontalScrolling) dlg.classList.add('horizontal_scrolling_dialogue_popup');
|
||||
if (allowVerticalScrolling) dlg.classList.add('vertical_scrolling_dialogue_popup');
|
||||
|
||||
this.ok.textContent = okButton ?? 'OK';
|
||||
this.cancel.textContent = cancelButton ?? 'Cancel';
|
||||
|
||||
switch(type) {
|
||||
case POPUP_TYPE.TEXT: {
|
||||
this.input.style.display = 'none';
|
||||
this.cancel.style.display = 'none';
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.CONFIRM: {
|
||||
this.input.style.display = 'none';
|
||||
this.ok.textContent = okButton ?? 'Yes';
|
||||
this.cancel.textContent = cancelButton ?? 'No';
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.INPUT: {
|
||||
this.input.style.display = 'block';
|
||||
this.ok.textContent = okButton ?? 'Save';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// illegal argument
|
||||
}
|
||||
}
|
||||
|
||||
this.input.value = inputValue;
|
||||
this.input.rows = rows ?? 1;
|
||||
|
||||
this.text.innerHTML = '';
|
||||
if (text instanceof jQuery) {
|
||||
$(this.text).append(text);
|
||||
} else if (text instanceof HTMLElement) {
|
||||
this.text.append(text);
|
||||
} else if (typeof text == 'string') {
|
||||
this.text.innerHTML = text;
|
||||
} else {
|
||||
// illegal argument
|
||||
}
|
||||
|
||||
this.ok.addEventListener('click', ()=>this.completeAffirmative());
|
||||
this.cancel.addEventListener('click', ()=>this.completeNegative());
|
||||
const keyListener = (evt)=>{
|
||||
switch (evt.key) {
|
||||
case 'Escape': {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this.completeCancelled();
|
||||
window.removeEventListener('keydown', keyListenerBound);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
const keyListenerBound = keyListener.bind(this);
|
||||
window.addEventListener('keydown', keyListenerBound);
|
||||
}
|
||||
|
||||
async show() {
|
||||
document.body.append(this.dom);
|
||||
this.dom.style.display = 'block';
|
||||
switch(this.type) {
|
||||
case POPUP_TYPE.INPUT: {
|
||||
this.input.focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$(this.dom).transition({
|
||||
opacity: 1,
|
||||
duration: animation_duration,
|
||||
easing: animation_easing,
|
||||
});
|
||||
|
||||
this.promise = new Promise((resolve) => {
|
||||
this.resolver = resolve;
|
||||
});
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
completeAffirmative() {
|
||||
switch (this.type) {
|
||||
case POPUP_TYPE.TEXT:
|
||||
case POPUP_TYPE.CONFIRM: {
|
||||
this.value = true;
|
||||
break;
|
||||
}
|
||||
case POPUP_TYPE.INPUT: {
|
||||
this.value = this.input.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.result = POPUP_RESULT.AFFIRMATIVE;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
completeNegative() {
|
||||
switch (this.type) {
|
||||
case POPUP_TYPE.TEXT:
|
||||
case POPUP_TYPE.CONFIRM:
|
||||
case POPUP_TYPE.INPUT: {
|
||||
this.value = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.result = POPUP_RESULT.NEGATIVE;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
completeCancelled() {
|
||||
switch (this.type) {
|
||||
case POPUP_TYPE.TEXT:
|
||||
case POPUP_TYPE.CONFIRM:
|
||||
case POPUP_TYPE.INPUT: {
|
||||
this.value = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.result = POPUP_RESULT.CANCELLED;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
|
||||
|
||||
hide() {
|
||||
$(this.dom).transition({
|
||||
opacity: 0,
|
||||
duration: animation_duration,
|
||||
easing: animation_easing,
|
||||
});
|
||||
delay(animation_duration).then(()=>{
|
||||
this.dom.remove();
|
||||
});
|
||||
|
||||
this.resolver(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Displays a blocking popup with a given text and type.
|
||||
* @param {JQuery<HTMLElement>|string|Element} text - Text to display in the popup.
|
||||
* @param {POPUP_TYPE} type
|
||||
* @param {string} inputValue - Value to set the input to.
|
||||
* @param {PopupOptions} options - Options for the popup.
|
||||
* @returns
|
||||
*/
|
||||
export function callGenericPopup(text, type, inputValue = '', { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) {
|
||||
const popup = new Popup(
|
||||
text,
|
||||
type,
|
||||
inputValue,
|
||||
{ okButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling },
|
||||
);
|
||||
return popup.show();
|
||||
}
|
@ -204,6 +204,7 @@ let power_user = {
|
||||
output_suffix: '',
|
||||
system_sequence: '',
|
||||
system_suffix: '',
|
||||
last_system_sequence: '',
|
||||
first_output_sequence: '',
|
||||
last_output_sequence: '',
|
||||
system_sequence_prefix: '',
|
||||
@ -254,6 +255,8 @@ let power_user = {
|
||||
auto_connect: false,
|
||||
auto_load_chat: false,
|
||||
forbid_external_images: false,
|
||||
external_media_allowed_overrides: [],
|
||||
external_media_forbidden_overrides: [],
|
||||
};
|
||||
|
||||
let themes = [];
|
||||
@ -1939,7 +1942,9 @@ export function renderStoryString(params) {
|
||||
|
||||
// add a newline to the end of the story string if it doesn't have one
|
||||
if (output.length > 0 && !output.endsWith('\n')) {
|
||||
output += '\n';
|
||||
if (!power_user.instruct.enabled || power_user.instruct.wrap) {
|
||||
output += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
@ -2760,22 +2765,35 @@ export function getCustomStoppingStrings(limit = undefined) {
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
const adjustAutocompleteDebounced = debounce(() => {
|
||||
$('.ui-autocomplete-input').each(function () {
|
||||
const isOpen = $(this).autocomplete('widget')[0].style.display !== 'none';
|
||||
if (isOpen) {
|
||||
$(this).autocomplete('search');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(window).on('resize', async () => {
|
||||
if (isMobile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('Window resized!');
|
||||
const reportZoomLevelDebounced = debounce(() => {
|
||||
const zoomLevel = Number(window.devicePixelRatio).toFixed(2);
|
||||
const winWidth = window.innerWidth;
|
||||
const winHeight = window.innerHeight;
|
||||
console.debug(`Zoom: ${zoomLevel}, X:${winWidth}, Y:${winHeight}`);
|
||||
});
|
||||
|
||||
$(window).on('resize', async () => {
|
||||
adjustAutocompleteDebounced();
|
||||
setHotswapsDebounced();
|
||||
|
||||
if (isMobile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
reportZoomLevelDebounced();
|
||||
|
||||
if (Object.keys(power_user.movingUIState).length > 0) {
|
||||
resetMovablePanels('resize');
|
||||
}
|
||||
// Adjust layout and styling here
|
||||
setHotswapsDebounced();
|
||||
});
|
||||
|
||||
// Settings that go to settings.json
|
||||
|
@ -106,7 +106,7 @@ function getDelay(s) {
|
||||
/**
|
||||
* Parses the stream data and returns the parsed data and the chunk to be sent.
|
||||
* @param {object} json The JSON data.
|
||||
* @returns {AsyncGenerator<{data: object, chunk: string} | null>} The parsed data and the chunk to be sent.
|
||||
* @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent.
|
||||
*/
|
||||
async function* parseStreamData(json) {
|
||||
// Claude
|
||||
@ -120,6 +120,7 @@ async function* parseStreamData(json) {
|
||||
};
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// MakerSuite
|
||||
else if (Array.isArray(json.candidates)) {
|
||||
@ -145,6 +146,7 @@ async function* parseStreamData(json) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// NovelAI / KoboldCpp Classic
|
||||
else if (typeof json.token === 'string' && json.token.length > 0) {
|
||||
@ -155,6 +157,7 @@ async function* parseStreamData(json) {
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
// llama.cpp?
|
||||
else if (typeof json.content === 'string' && json.content.length > 0) {
|
||||
@ -165,6 +168,7 @@ async function* parseStreamData(json) {
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
// OpenAI-likes
|
||||
else if (Array.isArray(json.choices)) {
|
||||
@ -184,6 +188,7 @@ async function* parseStreamData(json) {
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (typeof json.choices[0].delta === 'object') {
|
||||
if (typeof json.choices[0].delta.text === 'string' && json.choices[0].delta.text.length > 0) {
|
||||
@ -197,6 +202,7 @@ async function* parseStreamData(json) {
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
|
||||
for (let j = 0; j < json.choices[0].delta.content.length; j++) {
|
||||
@ -209,6 +215,7 @@ async function* parseStreamData(json) {
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (typeof json.choices[0].message === 'object') {
|
||||
@ -223,11 +230,12 @@ async function* parseStreamData(json) {
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
throw new Error('Unknown event data format');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,6 +251,12 @@ export class SmoothEventSourceStream extends EventSourceStream {
|
||||
const data = event.data;
|
||||
try {
|
||||
const hasFocus = document.hasFocus();
|
||||
|
||||
if (data === '[DONE]') {
|
||||
lastStr = '';
|
||||
return controller.enqueue(event);
|
||||
}
|
||||
|
||||
const json = JSON.parse(data);
|
||||
|
||||
if (!json) {
|
||||
@ -251,17 +265,13 @@ export class SmoothEventSourceStream extends EventSourceStream {
|
||||
}
|
||||
|
||||
for await (const parsed of parseStreamData(json)) {
|
||||
if (!parsed) {
|
||||
lastStr = '';
|
||||
return controller.enqueue(event);
|
||||
}
|
||||
|
||||
hasFocus && await delay(getDelay(lastStr));
|
||||
controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) }));
|
||||
lastStr = parsed.chunk;
|
||||
hasFocus && await eventSource.emit(event_types.SMOOTH_STREAM_TOKEN_RECEIVED, parsed.chunk);
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error('Smooth Streaming parsing error', error);
|
||||
controller.enqueue(event);
|
||||
}
|
||||
},
|
||||
|
@ -43,6 +43,9 @@ export {
|
||||
|
||||
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
|
||||
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
|
||||
const TAG_TEMPLATE = $('#tag_template .tag');
|
||||
const FOLDER_TEMPLATE = $('#bogus_folder_template .bogus_folder_select');
|
||||
const VIEW_TAG_TEMPLATE = $('#tag_view_template .tag_view_item');
|
||||
|
||||
function getFilterHelper(listSelector) {
|
||||
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
|
||||
@ -115,6 +118,7 @@ const TAG_FOLDER_DEFAULT_TYPE = 'NONE';
|
||||
* @property {function} [action] - An optional function that gets executed when this tag is an actionable tag and is clicked on.
|
||||
* @property {string} [class] - An optional css class added to the control representing this tag when printed. Used for custom tags in the filters.
|
||||
* @property {string} [icon] - An optional css class of an icon representing this tag when printed. This will replace the tag name with the icon. Used for custom tags in the filters.
|
||||
* @property {string} [title] - An optional title for the tooltip of this tag. If there is no tooltip specified, and "icon" is chosen, the tooltip will be the "name" property.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -125,10 +129,17 @@ let tags = [];
|
||||
|
||||
/**
|
||||
* A map representing the key of an entity (character avatar, group id, etc) with a corresponding array of tags this entity has assigned. The array might not exist if no tags were assigned yet.
|
||||
* @type {Object.<string, string[]?>}
|
||||
* @type {{[identifier: string]: string[]?}}
|
||||
*/
|
||||
let tag_map = {};
|
||||
|
||||
/**
|
||||
* A cache of all cut-off tag lists that got expanded until the last reload. They will be printed expanded again.
|
||||
* It contains the key of the entity.
|
||||
* @type {string[]} ids
|
||||
*/
|
||||
let expanded_tags_cache = [];
|
||||
|
||||
/**
|
||||
* Applies the basic filter for the current state of the tags and their selection on an entity list.
|
||||
* @param {Array<Object>} entities List of entities for display, consisting of tags, characters and groups.
|
||||
@ -271,7 +282,7 @@ function getTagBlock(tag, entities, hidden = 0) {
|
||||
|
||||
const tagFolder = TAG_FOLDER_TYPES[tag.folder_type];
|
||||
|
||||
const template = $('#bogus_folder_template .bogus_folder_select').clone();
|
||||
const template = FOLDER_TEMPLATE.clone();
|
||||
template.addClass(tagFolder.class);
|
||||
template.attr({ 'tagid': tag.id, 'id': `BogusFolder${tag.id}` });
|
||||
template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`);
|
||||
@ -385,7 +396,7 @@ function getTagKey() {
|
||||
* Robust method to find a valid tag key for any entity.
|
||||
*
|
||||
* @param {object|number|string} entityOrKey An entity with id property (character, group, tag), or directly an id or tag key.
|
||||
* @returns {string} The tag key that can be found.
|
||||
* @returns {string|undefined} The tag key that can be found.
|
||||
*/
|
||||
export function getTagKeyForEntity(entityOrKey) {
|
||||
let x = entityOrKey;
|
||||
@ -416,6 +427,33 @@ export function getTagKeyForEntity(entityOrKey) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a tag key based on an entity for a given element.
|
||||
* It checks the given element and upwards parents for a set character id (chid) or group id (grid), and if there is any, returns its unique entity key.
|
||||
*
|
||||
* @param {JQuery<HTMLElement>|string} element - The element to search the entity id on
|
||||
* @returns {string|undefined} The tag key that can be found.
|
||||
*/
|
||||
export function getTagKeyForEntityElement(element) {
|
||||
if (typeof element === 'string') {
|
||||
element = $(element);
|
||||
}
|
||||
// Start with the given element and traverse up the DOM tree
|
||||
while (element.length && element.parent().length) {
|
||||
const grid = element.attr('grid');
|
||||
const chid = element.attr('chid');
|
||||
if (grid || chid) {
|
||||
const id = grid || chid;
|
||||
return getTagKeyForEntity(id);
|
||||
}
|
||||
|
||||
// Move up to the parent element
|
||||
element = element.parent();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function addTagToMap(tagId, characterId = null) {
|
||||
const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
|
||||
|
||||
@ -614,15 +652,16 @@ function createNewTag(tagName) {
|
||||
/**
|
||||
* Prints the list of tags
|
||||
*
|
||||
* @param {JQuery<HTMLElement>} element - The container element where the tags are to be printed.
|
||||
* @param {JQuery<HTMLElement>|string} element - The container element where the tags are to be printed. (Optionally can also be a string selector for the element, which will then be resolved)
|
||||
* @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list.
|
||||
*/
|
||||
function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) {
|
||||
const $element = (typeof element === 'string') ? $(element) : element;
|
||||
const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey();
|
||||
let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key);
|
||||
|
||||
if (empty === 'always' || (empty && (printableTags?.length > 0 || key))) {
|
||||
$(element).empty();
|
||||
$element.empty();
|
||||
}
|
||||
|
||||
if (addTag && (tagOptions.skipExistsCheck || !printableTags.some(x => x.id === addTag.id))) {
|
||||
@ -634,6 +673,16 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
|
||||
|
||||
const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null;
|
||||
|
||||
// Well, lets check if the tag list was expanded. Based on either a css class, or when any expand was clicked yet, then we search whether this element id matches
|
||||
const expanded = $element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0);
|
||||
|
||||
// We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display
|
||||
const TAGS_LIMIT = 50;
|
||||
const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_SAFE_INTEGER;
|
||||
let totalPrinted = 0;
|
||||
let hiddenTags = 0;
|
||||
const filterActive = (/** @type {Tag} */ tag) => tag.filter_state && !isFilterState(tag.filter_state, FILTER_STATES.UNDEFINED);
|
||||
|
||||
for (const tag of printableTags) {
|
||||
// If we have a custom action selector, we override that tag options for each tag
|
||||
if (customAction) {
|
||||
@ -645,7 +694,40 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
|
||||
}
|
||||
}
|
||||
|
||||
appendTagToList(element, tag, tagOptions);
|
||||
// Check if we should print this tag
|
||||
if (totalPrinted++ < MAX_TAGS || filterActive(tag)) {
|
||||
appendTagToList($element, tag, tagOptions);
|
||||
} else {
|
||||
hiddenTags++;
|
||||
}
|
||||
}
|
||||
|
||||
// After the loop, check if we need to add the placeholder.
|
||||
// The placehold if clicked expands the tags and remembers either via class or cache array which was expanded, so it'll stay expanded until the next reload.
|
||||
if (hiddenTags > 0) {
|
||||
const id = 'placeholder_' + uuidv4();
|
||||
|
||||
// Add click event
|
||||
const showHiddenTags = (_, event) => {
|
||||
const elementKey = key ?? getTagKeyForEntityElement($element);
|
||||
console.log(`Hidden tags shown for element ${elementKey}`);
|
||||
|
||||
// Mark the current char/group as expanded if we were in any. This will be kept in memory until reload
|
||||
$element.addClass('tags-expanded');
|
||||
expanded_tags_cache.push(elementKey);
|
||||
|
||||
// Do not bubble further, we are just expanding
|
||||
event.stopPropagation();
|
||||
printTagList($element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions });
|
||||
};
|
||||
|
||||
// Print the placeholder object with its styling and action to show the remaining tags
|
||||
/** @type {Tag} */
|
||||
const placeholderTag = { id: id, name: '...', title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' };
|
||||
// It should never be marked as a removable tag, because it's just an expander action
|
||||
/** @type {TagOptions} */
|
||||
const placeholderTagOptions = { ...tagOptions, removable: false };
|
||||
appendTagToList($element, placeholderTag, placeholderTagOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@ -665,7 +747,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
|
||||
return;
|
||||
}
|
||||
|
||||
let tagElement = $('#tag_template .tag').clone();
|
||||
let tagElement = TAG_TEMPLATE.clone();
|
||||
tagElement.attr('id', tag.id);
|
||||
|
||||
//tagElement.css('color', 'var(--SmartThemeBodyColor)');
|
||||
@ -679,13 +761,19 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
|
||||
if (tag.class) {
|
||||
tagElement.addClass(tag.class);
|
||||
}
|
||||
|
||||
if (tag.title) {
|
||||
tagElement.attr('title', tag.title);
|
||||
}
|
||||
if (tag.icon) {
|
||||
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
|
||||
tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title || ''}`.trim()).addClass(tag.icon);
|
||||
tagElement.addClass('actionable');
|
||||
}
|
||||
|
||||
// We could have multiple ways of actions passed in. The manual arguments have precendence in front of a specified tag action
|
||||
const clickableAction = action ?? tag.action;
|
||||
|
||||
// If this is a tag for a general list and its either selectable or actionable, lets mark its current state
|
||||
if ((selectable || action) && isGeneralList) {
|
||||
if ((selectable || clickableAction) && isGeneralList) {
|
||||
toggleTagThreeState(tagElement, { stateOverride: tag.filter_state ?? DEFAULT_FILTER_STATE });
|
||||
}
|
||||
|
||||
@ -693,14 +781,11 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
|
||||
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement));
|
||||
}
|
||||
|
||||
if (action) {
|
||||
if (clickableAction) {
|
||||
const filter = getFilterHelper($(listElement));
|
||||
tagElement.on('click', () => action.bind(tagElement)(filter));
|
||||
tagElement.addClass('actionable');
|
||||
tagElement.on('click', (e) => clickableAction.bind(tagElement)(filter, e));
|
||||
tagElement.addClass('clickable-action');
|
||||
}
|
||||
/*if (action && tag.id === 2) {
|
||||
tagElement.addClass('innerActionable hidden');
|
||||
}*/
|
||||
|
||||
$(listElement).append(tagElement);
|
||||
}
|
||||
@ -765,7 +850,9 @@ function toggleTagThreeState(element, { stateOverride = undefined, simulateClick
|
||||
element.toggleClass(FILTER_STATES[state].class, state === states[targetStateIndex]);
|
||||
});
|
||||
|
||||
console.debug('toggle three-way filter from', states[currentStateIndex], 'to', states[targetStateIndex], 'on', element);
|
||||
if (states[currentStateIndex] !== states[targetStateIndex]) {
|
||||
console.debug('toggle three-way filter from', states[currentStateIndex], 'to', states[targetStateIndex], 'on', element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1129,7 +1216,7 @@ function onTagCreateClick() {
|
||||
|
||||
function appendViewTagToList(list, tag, everything) {
|
||||
const count = everything.filter(x => x == tag.id).length;
|
||||
const template = $('#tag_view_template .tag_view_item').clone();
|
||||
const template = VIEW_TAG_TEMPLATE.clone();
|
||||
template.attr('id', tag.id);
|
||||
template.find('.tag_view_counter_value').text(count);
|
||||
template.find('.tag_view_name').text(tag.name);
|
||||
@ -1146,16 +1233,18 @@ function appendViewTagToList(list, tag, everything) {
|
||||
template.find('.tag_as_folder').hide();
|
||||
}
|
||||
|
||||
template.find('.tagColorPickerHolder').html(
|
||||
`<toolcool-color-picker id="${colorPickerId}" color="${tag.color}" class="tag-color"></toolcool-color-picker>`,
|
||||
);
|
||||
template.find('.tagColorPicker2Holder').html(
|
||||
`<toolcool-color-picker id="${colorPicker2Id}" color="${tag.color2}" class="tag-color2"></toolcool-color-picker>`,
|
||||
);
|
||||
const primaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
|
||||
.addClass('tag-color')
|
||||
.attr({ id: colorPickerId, color: tag.color });
|
||||
|
||||
const secondaryColorPicker = $('<toolcool-color-picker></toolcool-color-picker>')
|
||||
.addClass('tag-color2')
|
||||
.attr({ id: colorPicker2Id, color: tag.color2 });
|
||||
|
||||
template.find('.tagColorPickerHolder').append(primaryColorPicker);
|
||||
template.find('.tagColorPicker2Holder').append(secondaryColorPicker);
|
||||
|
||||
template.find('.tag_as_folder').attr('id', tagAsFolderId);
|
||||
template.find('.tag-color').attr('id', colorPickerId);
|
||||
template.find('.tag-color2').attr('id', colorPicker2Id);
|
||||
|
||||
list.append(template);
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
<ul>
|
||||
<li><tt>{{pipe}}</tt> – only for slash command batching. Replaced with the returned result of the previous command.</li>
|
||||
<li><tt>{{newline}}</tt> – just inserts a newline.</li>
|
||||
<li><tt>{{trim}}</tt> – trims newlines surrounding this macro.</li>
|
||||
<li><tt>{{noop}}</tt> – no operation, just an empty string.</li>
|
||||
<li><tt>{{original}}</tt> – global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.</li>
|
||||
<li><tt>{{input}}</tt> – the user input</li>
|
||||
<li><tt>{{charPrompt}}</tt> – the Character's Main Prompt override</li>
|
||||
@ -49,6 +51,7 @@
|
||||
<li><tt>{{maxPrompt}}</tt> – max allowed prompt length in tokens = (context size - response length)</li>
|
||||
<li><tt>{{exampleSeparator}}</tt> – context template example dialogues separator</li>
|
||||
<li><tt>{{chatStart}}</tt> – context template chat start line</li>
|
||||
<li><tt>{{systemPrompt}}</tt> – main system prompt (either character prompt override if chosen, or instructSystemPrompt)</li>
|
||||
<li><tt>{{instructSystemPrompt}}</tt> – instruct system prompt</li>
|
||||
<li><tt>{{instructSystemPromptPrefix}}</tt> – instruct system prompt prefix sequence</li>
|
||||
<li><tt>{{instructSystemPromptSuffix}}</tt> – instruct system prompt suffix sequence</li>
|
||||
@ -60,6 +63,7 @@
|
||||
<li><tt>{{instructLastAssistantPrefix}}</tt> – instruct assistant last output sequence</li>
|
||||
<li><tt>{{instructSystemPrefix}}</tt> – instruct system message prefix sequence</li>
|
||||
<li><tt>{{instructSystemSuffix}}</tt> – instruct system message suffix sequence</li>
|
||||
<li><tt>{{instructSystemInstructionPrefix}}</tt> – instruct system instruction prefix</li>
|
||||
<li><tt>{{instructUserFiller}}</tt> – instruct first user message filler</li>
|
||||
<li><tt>{{instructStop}}</tt> – instruct stop sequence</li>
|
||||
</ul>
|
||||
|
@ -568,7 +568,7 @@ jQuery(function () {
|
||||
const json_schema_string = String($(this).val());
|
||||
|
||||
try {
|
||||
settings.json_schema = JSON.parse(json_schema_string ?? '{}');
|
||||
settings.json_schema = JSON.parse(json_schema_string || '{}');
|
||||
} catch {
|
||||
// Ignore errors from here
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ const world_info_logic = {
|
||||
AND_ALL: 3,
|
||||
};
|
||||
|
||||
const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
|
||||
|
||||
let world_info = {};
|
||||
let selected_world_info = [];
|
||||
let world_names;
|
||||
@ -95,6 +97,11 @@ class WorldInfoBuffer {
|
||||
*/
|
||||
#skew = 0;
|
||||
|
||||
/**
|
||||
* @type {number} The starting depth of the global scan depth. Incremented by "min activations" feature to not repeat scans. When > 0 it means a complete scan was done up to #startDepth already, and `advanceScanPosition` was called.
|
||||
*/
|
||||
#startDepth = 0;
|
||||
|
||||
/**
|
||||
* Initialize the buffer with the given messages.
|
||||
* @param {string[]} messages Array of messages to add to the buffer
|
||||
@ -137,7 +144,10 @@ class WorldInfoBuffer {
|
||||
* @returns {string} A slice of buffer until the given depth (inclusive)
|
||||
*/
|
||||
get(entry) {
|
||||
let depth = entry.scanDepth ?? (world_info_depth + this.#skew);
|
||||
let depth = entry.scanDepth ?? this.getDepth();
|
||||
if (depth <= this.#startDepth) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (depth < 0) {
|
||||
console.error(`Invalid WI scan depth ${depth}. Must be >= 0`);
|
||||
@ -149,7 +159,7 @@ class WorldInfoBuffer {
|
||||
depth = MAX_SCAN_DEPTH;
|
||||
}
|
||||
|
||||
let result = this.#depthBuffer.slice(0, depth).join('\n');
|
||||
let result = this.#depthBuffer.slice(this.#startDepth, depth).join('\n');
|
||||
|
||||
if (this.#recurseBuffer.length > 0) {
|
||||
result += '\n' + this.#recurseBuffer.join('\n');
|
||||
@ -197,11 +207,19 @@ class WorldInfoBuffer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an increment to depth skew.
|
||||
* Increments skew and sets startDepth to previous depth.
|
||||
*/
|
||||
addSkew() {
|
||||
advanceScanPosition() {
|
||||
this.#startDepth = this.getDepth();
|
||||
this.#skew++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} Settings' depth + current skew.
|
||||
*/
|
||||
getDepth() {
|
||||
return world_info_depth + this.#skew;
|
||||
}
|
||||
}
|
||||
|
||||
export function getWorldInfoSettings() {
|
||||
@ -783,6 +801,11 @@ function displayWorldEntries(name, data, navigation = navigation_option.none) {
|
||||
afterSizeSelectorChange: function (e) {
|
||||
localStorage.setItem(storageKey, e.target.value);
|
||||
},
|
||||
afterPaging: function () {
|
||||
$('#world_popup_entries_list textarea[name="comment"]').each(function () {
|
||||
initScrollHeight($(this));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (typeof navigation === 'number' && Number(navigation) >= 0) {
|
||||
@ -970,7 +993,7 @@ function getWorldEntry(name, data, entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $('#entry_edit_template .world_entry').clone();
|
||||
const template = WI_ENTRY_EDIT_TEMPLATE.clone();
|
||||
template.data('uid', entry.uid);
|
||||
template.attr('uid', entry.uid);
|
||||
|
||||
@ -982,10 +1005,10 @@ function getWorldEntry(name, data, entry) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
keyInput.on('input', function () {
|
||||
keyInput.on('input', function (_, { skipReset } = {}) {
|
||||
const uid = $(this).data('uid');
|
||||
const value = String($(this).val());
|
||||
resetScrollHeight(this);
|
||||
!skipReset && resetScrollHeight(this);
|
||||
data.entries[uid].key = value
|
||||
.split(',')
|
||||
.map((x) => x.trim())
|
||||
@ -994,7 +1017,7 @@ function getWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, 'keys', data.entries[uid].key);
|
||||
saveWorldInfo(name, data);
|
||||
});
|
||||
keyInput.val(entry.key.join(', ')).trigger('input');
|
||||
keyInput.val(entry.key.join(', ')).trigger('input', { skipReset: true });
|
||||
//initScrollHeight(keyInput);
|
||||
|
||||
// logic AND/NOT
|
||||
@ -1008,7 +1031,6 @@ function getWorldEntry(name, data, entry) {
|
||||
selectiveLogicDropdown.on('input', function () {
|
||||
const uid = $(this).data('uid');
|
||||
const value = Number($(this).val());
|
||||
console.debug(`logic for ${entry.uid} set to ${value}`);
|
||||
data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
|
||||
setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
|
||||
saveWorldInfo(name, data);
|
||||
@ -1118,10 +1140,10 @@ function getWorldEntry(name, data, entry) {
|
||||
// keysecondary
|
||||
const keySecondaryInput = template.find('textarea[name="keysecondary"]');
|
||||
keySecondaryInput.data('uid', entry.uid);
|
||||
keySecondaryInput.on('input', function () {
|
||||
keySecondaryInput.on('input', function (_, { skipReset } = {}) {
|
||||
const uid = $(this).data('uid');
|
||||
const value = String($(this).val());
|
||||
resetScrollHeight(this);
|
||||
!skipReset && resetScrollHeight(this);
|
||||
data.entries[uid].keysecondary = value
|
||||
.split(',')
|
||||
.map((x) => x.trim())
|
||||
@ -1131,17 +1153,17 @@ function getWorldEntry(name, data, entry) {
|
||||
saveWorldInfo(name, data);
|
||||
});
|
||||
|
||||
keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input');
|
||||
initScrollHeight(keySecondaryInput);
|
||||
keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input', { skipReset: true });
|
||||
//initScrollHeight(keySecondaryInput);
|
||||
|
||||
// comment
|
||||
const commentInput = template.find('textarea[name="comment"]');
|
||||
const commentToggle = template.find('input[name="addMemo"]');
|
||||
commentInput.data('uid', entry.uid);
|
||||
commentInput.on('input', function () {
|
||||
commentInput.on('input', function (_, { skipReset } = {}) {
|
||||
const uid = $(this).data('uid');
|
||||
const value = $(this).val();
|
||||
resetScrollHeight(this);
|
||||
!skipReset && resetScrollHeight(this);
|
||||
data.entries[uid].comment = value;
|
||||
|
||||
setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
|
||||
@ -1160,8 +1182,8 @@ function getWorldEntry(name, data, entry) {
|
||||
value ? commentContainer.show() : commentContainer.hide();
|
||||
});
|
||||
|
||||
commentInput.val(entry.comment).trigger('input');
|
||||
initScrollHeight(commentInput);
|
||||
commentInput.val(entry.comment).trigger('input', { skipReset: true });
|
||||
//initScrollHeight(commentInput);
|
||||
commentToggle.prop('checked', true /* entry.addMemo */).trigger('input');
|
||||
commentToggle.parent().hide();
|
||||
|
||||
@ -1196,6 +1218,8 @@ function getWorldEntry(name, data, entry) {
|
||||
if (counter.data('first-run')) {
|
||||
counter.data('first-run', false);
|
||||
countTokensDebounced(counter, contentInput.val());
|
||||
initScrollHeight(keyInput);
|
||||
initScrollHeight(keySecondaryInput);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1362,7 +1386,7 @@ function getWorldEntry(name, data, entry) {
|
||||
}
|
||||
|
||||
const positionInput = template.find('select[name="position"]');
|
||||
initScrollHeight(positionInput);
|
||||
//initScrollHeight(positionInput);
|
||||
positionInput.data('uid', entry.uid);
|
||||
positionInput.on('click', function (event) {
|
||||
// Prevent closing the drawer on clicking the input
|
||||
@ -1419,7 +1443,6 @@ function getWorldEntry(name, data, entry) {
|
||||
//new tri-state selector for constant/normal/disabled
|
||||
const entryStateSelector = template.find('select[name="entryStateSelector"]');
|
||||
entryStateSelector.data('uid', entry.uid);
|
||||
console.log(entry.uid);
|
||||
entryStateSelector.on('click', function (event) {
|
||||
// Prevent closing the drawer on clicking the input
|
||||
event.stopPropagation();
|
||||
@ -1434,7 +1457,6 @@ function getWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, 'enabled', true);
|
||||
setOriginalDataValue(data, uid, 'constant', true);
|
||||
template.removeClass('disabledWIEntry');
|
||||
console.debug('set to constant');
|
||||
break;
|
||||
case 'normal':
|
||||
data.entries[uid].constant = false;
|
||||
@ -1442,7 +1464,6 @@ function getWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, 'enabled', true);
|
||||
setOriginalDataValue(data, uid, 'constant', false);
|
||||
template.removeClass('disabledWIEntry');
|
||||
console.debug('set to normal');
|
||||
break;
|
||||
case 'disabled':
|
||||
data.entries[uid].constant = false;
|
||||
@ -1450,7 +1471,6 @@ function getWorldEntry(name, data, entry) {
|
||||
setOriginalDataValue(data, uid, 'enabled', false);
|
||||
setOriginalDataValue(data, uid, 'constant', false);
|
||||
template.addClass('disabledWIEntry');
|
||||
console.debug('set to disabled');
|
||||
break;
|
||||
}
|
||||
saveWorldInfo(name, data);
|
||||
@ -1458,19 +1478,13 @@ function getWorldEntry(name, data, entry) {
|
||||
});
|
||||
|
||||
const entryState = function () {
|
||||
|
||||
console.log(`constant: ${entry.constant}, disabled: ${entry.disable}`);
|
||||
if (entry.constant === true) {
|
||||
console.debug('found constant');
|
||||
return 'constant';
|
||||
} else if (entry.disable === true) {
|
||||
console.debug('found disabled');
|
||||
return 'disabled';
|
||||
} else {
|
||||
console.debug('found normal');
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
};
|
||||
template
|
||||
.find(`select[name="entryStateSelector"] option[value=${entryState()}]`)
|
||||
@ -1966,15 +1980,12 @@ async function getSortedEntries() {
|
||||
|
||||
switch (Number(world_info_character_strategy)) {
|
||||
case world_info_insertion_strategy.evenly:
|
||||
console.debug('WI using evenly');
|
||||
entries = [...globalLore, ...characterLore].sort(sortFn);
|
||||
break;
|
||||
case world_info_insertion_strategy.character_first:
|
||||
console.debug('WI using char first');
|
||||
entries = [...characterLore.sort(sortFn), ...globalLore.sort(sortFn)];
|
||||
break;
|
||||
case world_info_insertion_strategy.global_first:
|
||||
console.debug('WI using global first');
|
||||
entries = [...globalLore.sort(sortFn), ...characterLore.sort(sortFn)];
|
||||
break;
|
||||
default:
|
||||
@ -2009,7 +2020,6 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
const buffer = new WorldInfoBuffer(chat);
|
||||
|
||||
// Combine the chat
|
||||
let minActivationMsgIndex = world_info_depth; // tracks chat index to satisfy `world_info_min_activations`
|
||||
|
||||
// Add the depth or AN if enabled
|
||||
// Put this code here since otherwise, the chat reference is modified
|
||||
@ -2102,8 +2112,6 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
const substituted = substituteParams(key);
|
||||
const textToScan = buffer.get(entry);
|
||||
|
||||
console.debug(`${entry.uid}: ${substituted}`);
|
||||
|
||||
if (substituted && buffer.matchKeys(textToScan, substituted.trim(), entry)) {
|
||||
console.debug(`WI UID ${entry.uid} found by primary match: ${substituted}.`);
|
||||
|
||||
@ -2160,7 +2168,7 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
activatedNow.add(entry);
|
||||
break primary;
|
||||
}
|
||||
} else { console.debug(`No active entries for logic checks for word: ${substituted}.`); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2225,15 +2233,14 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
// world_info_min_activations
|
||||
if (!needsToScan && !token_budget_overflowed) {
|
||||
if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) {
|
||||
let over_max = false;
|
||||
over_max = (
|
||||
let over_max = (
|
||||
world_info_min_activations_depth_max > 0 &&
|
||||
minActivationMsgIndex > world_info_min_activations_depth_max
|
||||
) || (minActivationMsgIndex >= chat.length);
|
||||
buffer.getDepth() > world_info_min_activations_depth_max
|
||||
) || (buffer.getDepth() > chat.length);
|
||||
|
||||
if (!over_max) {
|
||||
needsToScan = true;
|
||||
minActivationMsgIndex += 1;
|
||||
buffer.addSkew();
|
||||
needsToScan = true; // loop
|
||||
buffer.advanceScanPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user