mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into slash-fix-bleed
This commit is contained in:
@@ -3,8 +3,9 @@ TODO:
|
||||
*/
|
||||
//const DEBUG_TONY_SAMA_FORK_MODE = true
|
||||
|
||||
import { getRequestHeaders, callPopup } from '../../../script.js';
|
||||
import { deleteExtension, extensionNames, installExtension, renderExtensionTemplate } from '../../extensions.js';
|
||||
import { getRequestHeaders, callPopup, processDroppedFiles } from '../../../script.js';
|
||||
import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplate } from '../../extensions.js';
|
||||
import { executeSlashCommands } from '../../slash-commands.js';
|
||||
import { getStringHash, isValidUrl } from '../../utils.js';
|
||||
export { MODULE_NAME };
|
||||
|
||||
@@ -61,8 +62,8 @@ function downloadAssetsList(url) {
|
||||
for (const i in availableAssets[assetType]) {
|
||||
const asset = availableAssets[assetType][i];
|
||||
const elemId = `assets_install_${assetType}_${i}`;
|
||||
let element = $('<button />', { id: elemId, type: 'button', class: 'asset-download-button menu_button' });
|
||||
const label = $('<i class="fa-fw fa-solid fa-download fa-xl"></i>');
|
||||
let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
|
||||
const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
|
||||
element.append(label);
|
||||
|
||||
//if (DEBUG_TONY_SAMA_FORK_MODE)
|
||||
@@ -90,6 +91,11 @@ function downloadAssetsList(url) {
|
||||
};
|
||||
|
||||
const assetDelete = async function () {
|
||||
if (assetType === 'character') {
|
||||
toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
|
||||
await executeSlashCommands(`/go ${asset['id']}`);
|
||||
return;
|
||||
}
|
||||
element.off('click');
|
||||
await deleteAsset(assetType, asset['id']);
|
||||
label.removeClass('fa-check');
|
||||
@@ -126,20 +132,27 @@ 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 previewIcon = assetType == 'extension' ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
|
||||
const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
|
||||
|
||||
$('<i></i>')
|
||||
const assetBlock = $('<i></i>')
|
||||
.append(element)
|
||||
.append(`<div class="flex-container flexFlowColumn">
|
||||
<span class="flex-container alignitemscenter">
|
||||
.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">
|
||||
<i class="fa-solid fa-sm ${previewIcon}"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span>${description}</span>
|
||||
</div>`)
|
||||
.appendTo(assetTypeMenu);
|
||||
<small class="asset-description">
|
||||
${description}
|
||||
</small>
|
||||
</div>`);
|
||||
|
||||
if (assetType === 'character') {
|
||||
assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['url']}" alt="${displayName}"></div>`);
|
||||
}
|
||||
|
||||
assetTypeMenu.append(assetBlock);
|
||||
}
|
||||
assetTypeMenu.appendTo('#assets_menu');
|
||||
assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
|
||||
@@ -186,6 +199,10 @@ function isAssetInstalled(assetType, filename) {
|
||||
assetList = extensionNames.filter(x => x.startsWith(thirdPartyMarker)).map(x => x.replace(thirdPartyMarker, ''));
|
||||
}
|
||||
|
||||
if (assetType == 'character') {
|
||||
assetList = getContext().characters.map(x => x.avatar);
|
||||
}
|
||||
|
||||
for (const i of assetList) {
|
||||
//console.debug(DEBUG_PREFIX,i,filename)
|
||||
if (i.includes(filename))
|
||||
@@ -215,6 +232,13 @@ async function installAsset(url, assetType, filename) {
|
||||
});
|
||||
if (result.ok) {
|
||||
console.debug(DEBUG_PREFIX, 'Download success.');
|
||||
if (category === 'character') {
|
||||
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
|
||||
const blob = await result.blob();
|
||||
const file = new File([blob], filename, { type: blob.type });
|
||||
await processDroppedFiles([file]);
|
||||
console.debug(DEBUG_PREFIX, 'Character downloaded.');
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
|
@@ -27,17 +27,14 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.assets-list-div i {
|
||||
.assets-list-div > i {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
padding: 5px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.assets-list-div i span {
|
||||
margin-left: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.assets-list-div i span:first-of-type {
|
||||
@@ -46,12 +43,11 @@
|
||||
|
||||
.asset-download-button {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.asset-download-button:active {
|
||||
@@ -85,6 +81,21 @@
|
||||
animation: asset-download-button-loading-spinner 1s ease infinite;
|
||||
}
|
||||
|
||||
.asset-name .avatar {
|
||||
--imgSize: 30px !important;
|
||||
flex: unset;
|
||||
width: var(--imgSize);
|
||||
height: var(--imgSize);
|
||||
}
|
||||
|
||||
.asset-name .avatar img {
|
||||
width: var(--imgSize);
|
||||
height: var(--imgSize);
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
object-position: center center;
|
||||
}
|
||||
|
||||
@keyframes asset-download-button-loading-spinner {
|
||||
from {
|
||||
transform: rotate(0turn);
|
||||
|
@@ -5,9 +5,16 @@
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div id="open_regex_editor" class="menu_button">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<span>Open Editor</span>
|
||||
<div class="flex-container">
|
||||
<div id="open_regex_editor" class="menu_button">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
<span>Open Editor</span>
|
||||
</div>
|
||||
<div id="import_regex" class="menu_button">
|
||||
<i class="fa-solid fa-file-import"></i>
|
||||
<span>Import Script</span>
|
||||
</div>
|
||||
<input type="file" id="import_regex_file" hidden accept="*.json" />
|
||||
</div>
|
||||
<hr />
|
||||
<label>Saved Scripts</label>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { callPopup, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced } from '../../../script.js';
|
||||
import { extension_settings } from '../../extensions.js';
|
||||
import { registerSlashCommand } from '../../slash-commands.js';
|
||||
import { getSortableDelay, uuidv4 } from '../../utils.js';
|
||||
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
|
||||
import { resolveVariable } from '../../variables.js';
|
||||
import { regex_placement, runRegexScript } from './engine.js';
|
||||
|
||||
@@ -93,6 +93,11 @@ async function loadRegexScripts() {
|
||||
scriptHtml.find('.edit_existing_regex').on('click', async function () {
|
||||
await onRegexEditorOpenClick(scriptHtml.attr('id'));
|
||||
});
|
||||
scriptHtml.find('.export_regex').on('click', async function () {
|
||||
const fileName = `${script.scriptName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.json`;
|
||||
const fileData = JSON.stringify(script, null, 4);
|
||||
download(fileData, fileName, 'application/json');
|
||||
});
|
||||
scriptHtml.find('.delete_regex').on('click', async function () {
|
||||
const confirm = await callPopup('Are you sure you want to delete this regex script?', 'confirm');
|
||||
|
||||
@@ -270,6 +275,35 @@ function runRegexCallback(args, value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the import of the regex file.
|
||||
* @param {File} file Input file
|
||||
*/
|
||||
async function onRegexImportFileChange(file) {
|
||||
if (!file) {
|
||||
toastr.error('No file provided.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileText = await getFileText(file);
|
||||
const regexScript = JSON.parse(fileText);
|
||||
if (!regexScript.scriptName) {
|
||||
throw new Error('No script name provided.');
|
||||
}
|
||||
|
||||
extension_settings.regex.push(regexScript);
|
||||
|
||||
saveSettingsDebounced();
|
||||
await loadRegexScripts();
|
||||
toastr.success(`Regex script "${regexScript.scriptName}" imported.`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error('Invalid JSON file.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for loading in sequence with other extensions
|
||||
// NOTE: Always puts extension at the top of the list, but this is fine since it's static
|
||||
jQuery(async () => {
|
||||
@@ -287,6 +321,14 @@ jQuery(async () => {
|
||||
$('#open_regex_editor').on('click', function () {
|
||||
onRegexEditorOpenClick(false);
|
||||
});
|
||||
$('#import_regex_file').on('change', async function () {
|
||||
const inputElement = this instanceof HTMLInputElement && this;
|
||||
await onRegexImportFileChange(inputElement.files[0]);
|
||||
inputElement.value = '';
|
||||
});
|
||||
$('#import_regex').on('click', function () {
|
||||
$('#import_regex_file').trigger('click');
|
||||
});
|
||||
|
||||
$('#saved_regex_scripts').sortable({
|
||||
delay: getSortableDelay(),
|
||||
|
@@ -7,10 +7,13 @@
|
||||
<span class="regex-toggle-on fa-solid fa-toggle-on" title="Disable script"></span>
|
||||
<span class="regex-toggle-off fa-solid fa-toggle-off" title="Enable script"></span>
|
||||
</label>
|
||||
<div class="edit_existing_regex menu_button">
|
||||
<div class="edit_existing_regex menu_button" title="Edit script">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</div>
|
||||
<div class="delete_regex menu_button">
|
||||
<div class="export_regex menu_button" title="Export script">
|
||||
<i class="fa-solid fa-file-export"></i>
|
||||
</div>
|
||||
<div class="delete_regex menu_button" title="Delete script">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -206,6 +206,7 @@ const defaultSettings = {
|
||||
expand: false,
|
||||
interactive_mode: false,
|
||||
multimodal_captioning: false,
|
||||
snap: false,
|
||||
|
||||
prompts: promptTemplates,
|
||||
|
||||
@@ -389,6 +390,7 @@ async function loadSettings() {
|
||||
$('#sd_openai_quality').val(extension_settings.sd.openai_quality);
|
||||
$('#sd_comfy_url').val(extension_settings.sd.comfy_url);
|
||||
$('#sd_comfy_prompt').val(extension_settings.sd.comfy_prompt);
|
||||
$('#sd_snap').prop('checked', extension_settings.sd.snap);
|
||||
|
||||
for (const style of extension_settings.sd.styles) {
|
||||
const option = document.createElement('option');
|
||||
@@ -398,23 +400,7 @@ async function loadSettings() {
|
||||
$('#sd_style').append(option);
|
||||
}
|
||||
|
||||
// Find a closest resolution option match for the current width and height
|
||||
let resolutionId = null, minAspectDiff = Infinity, minResolutionDiff = Infinity;
|
||||
for (const [id, resolution] of Object.entries(resolutionOptions)) {
|
||||
const aspectDiff = Math.abs((resolution.width / resolution.height) - (extension_settings.sd.width / extension_settings.sd.height));
|
||||
const resolutionDiff = Math.abs(resolution.width * resolution.height - extension_settings.sd.width * extension_settings.sd.height);
|
||||
|
||||
if (resolutionDiff < minResolutionDiff || (resolutionDiff === minResolutionDiff && aspectDiff < minAspectDiff)) {
|
||||
resolutionId = id;
|
||||
minAspectDiff = aspectDiff;
|
||||
minResolutionDiff = resolutionDiff;
|
||||
}
|
||||
|
||||
if (resolutionDiff === 0 && aspectDiff === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const resolutionId = getClosestKnownResolution();
|
||||
$('#sd_resolution').val(resolutionId);
|
||||
|
||||
toggleSourceControls();
|
||||
@@ -423,6 +409,32 @@ async function loadSettings() {
|
||||
await loadSettingOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a closest resolution option match for the current width and height.
|
||||
*/
|
||||
function getClosestKnownResolution() {
|
||||
let resolutionId = null;
|
||||
let minTotalDiff = Infinity;
|
||||
|
||||
const targetAspect = extension_settings.sd.width / extension_settings.sd.height;
|
||||
const targetResolution = extension_settings.sd.width * extension_settings.sd.height;
|
||||
|
||||
const diffs = Object.entries(resolutionOptions).map(([id, resolution]) => {
|
||||
const aspectDiff = Math.abs((resolution.width / resolution.height) - targetAspect) / targetAspect;
|
||||
const resolutionDiff = Math.abs(resolution.width * resolution.height - targetResolution) / targetResolution;
|
||||
return { id, totalDiff: aspectDiff + resolutionDiff };
|
||||
});
|
||||
|
||||
for (const { id, totalDiff } of diffs) {
|
||||
if (totalDiff < minTotalDiff) {
|
||||
minTotalDiff = totalDiff;
|
||||
resolutionId = id;
|
||||
}
|
||||
}
|
||||
|
||||
return resolutionId;
|
||||
}
|
||||
|
||||
async function loadSettingOptions() {
|
||||
return Promise.all([
|
||||
loadSamplers(),
|
||||
@@ -475,6 +487,11 @@ function onMultimodalCaptioningInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onSnapInput() {
|
||||
extension_settings.sd.snap = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onStyleSelect() {
|
||||
const selectedStyle = String($('#sd_style').find(':selected').val());
|
||||
const styleObject = extension_settings.sd.styles.find(x => x.name === selectedStyle);
|
||||
@@ -1659,7 +1676,7 @@ function processReply(str) {
|
||||
str = str.replaceAll('“', '');
|
||||
str = str.replaceAll('.', ',');
|
||||
str = str.replaceAll('\n', ', ');
|
||||
str = str.replace(/[^a-zA-Z0-9,:()]+/g, ' '); // Replace everything except alphanumeric characters and commas with spaces
|
||||
str = str.replace(/[^a-zA-Z0-9,:()']+/g, ' '); // Replace everything except alphanumeric characters and commas with spaces
|
||||
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
|
||||
str = str.trim();
|
||||
|
||||
@@ -1765,7 +1782,7 @@ function setTypeSpecificDimensions(generationType) {
|
||||
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
|
||||
|
||||
// Face images are always portrait (pun intended)
|
||||
if (generationType == generationMode.FACE && aspectRatio >= 1) {
|
||||
if ((generationType == generationMode.FACE || generationType == generationMode.FACE_MULTIMODAL) && aspectRatio >= 1) {
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.height = Math.round(extension_settings.sd.width * 1.5 / 64) * 64;
|
||||
}
|
||||
@@ -1778,6 +1795,28 @@ function setTypeSpecificDimensions(generationType) {
|
||||
}
|
||||
}
|
||||
|
||||
if (extension_settings.sd.snap) {
|
||||
// Force to use roughly the same pixel count as before rescaling
|
||||
const prevPixelCount = prevSDHeight * prevSDWidth;
|
||||
const newPixelCount = extension_settings.sd.height * extension_settings.sd.width;
|
||||
|
||||
if (prevPixelCount !== newPixelCount) {
|
||||
const ratio = Math.sqrt(prevPixelCount / newPixelCount);
|
||||
extension_settings.sd.height = Math.round(extension_settings.sd.height * ratio / 64) * 64;
|
||||
extension_settings.sd.width = Math.round(extension_settings.sd.width * ratio / 64) * 64;
|
||||
console.log(`Pixel counts after rescaling: ${prevPixelCount} -> ${newPixelCount} (ratio: ${ratio})`);
|
||||
|
||||
const resolution = resolutionOptions[getClosestKnownResolution()];
|
||||
if (resolution) {
|
||||
extension_settings.sd.height = resolution.height;
|
||||
extension_settings.sd.width = resolution.width;
|
||||
console.log('Snap to resolution', JSON.stringify(resolution));
|
||||
} else {
|
||||
console.warn('Snap to resolution failed, using custom dimensions');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { height: prevSDHeight, width: prevSDWidth };
|
||||
}
|
||||
|
||||
@@ -2349,7 +2388,7 @@ async function onComfyOpenWorkflowEditorClick() {
|
||||
`);
|
||||
$('#sd_comfy_workflow_editor_placeholder_list_custom').append(el);
|
||||
el.find('.sd_comfy_workflow_editor_custom_find').val(placeholder.find);
|
||||
el.find('.sd_comfy_workflow_editor_custom_find').on('input', function() {
|
||||
el.find('.sd_comfy_workflow_editor_custom_find').on('input', function () {
|
||||
placeholder.find = this.value;
|
||||
el.find('.sd_comfy_workflow_editor_custom_final').text(`"%${this.value}%"`);
|
||||
el.attr('data-placeholder', `${this.value}`);
|
||||
@@ -2357,7 +2396,7 @@ async function onComfyOpenWorkflowEditorClick() {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
el.find('.sd_comfy_workflow_editor_custom_replace').val(placeholder.replace);
|
||||
el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function() {
|
||||
el.find('.sd_comfy_workflow_editor_custom_replace').on('input', function () {
|
||||
placeholder.replace = this.value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
@@ -2379,7 +2418,7 @@ async function onComfyOpenWorkflowEditorClick() {
|
||||
addPlaceholderDom(placeholder);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
(extension_settings.sd.comfy_placeholders ?? []).forEach(placeholder=>{
|
||||
(extension_settings.sd.comfy_placeholders ?? []).forEach(placeholder => {
|
||||
addPlaceholderDom(placeholder);
|
||||
});
|
||||
checkPlaceholders();
|
||||
@@ -2700,6 +2739,7 @@ jQuery(async () => {
|
||||
$('#sd_openai_style').on('change', onOpenAiStyleSelect);
|
||||
$('#sd_openai_quality').on('change', onOpenAiQualitySelect);
|
||||
$('#sd_multimodal_captioning').on('input', onMultimodalCaptioningInput);
|
||||
$('#sd_snap').on('input', onSnapInput);
|
||||
|
||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||
initScrollHeight($('#sd_prompt_prefix'));
|
||||
|
@@ -26,6 +26,10 @@
|
||||
<input id="sd_expand" type="checkbox" />
|
||||
Auto-enhance prompts
|
||||
</label>
|
||||
<label for="sd_snap" class="checkbox_label" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">
|
||||
<input id="sd_snap" type="checkbox" />
|
||||
Snap auto-adjusted resolutions
|
||||
</label>
|
||||
<label for="sd_source">Source</label>
|
||||
<select id="sd_source">
|
||||
<option value="extras">Extras API (local / remote)</option>
|
||||
|
@@ -196,14 +196,18 @@ class AllTalkTtsProvider {
|
||||
$('#narrator_voice').val(this.settings.narrator_voice_gen);
|
||||
|
||||
console.debug('AllTalkTTS: Settings loaded');
|
||||
await this.initEndpoint();
|
||||
}
|
||||
|
||||
async initEndpoint() {
|
||||
try {
|
||||
// Check if TTS provider is ready
|
||||
this.setupEventListeners();
|
||||
this.updateLanguageDropdown();
|
||||
await this.checkReady();
|
||||
await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server
|
||||
await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready
|
||||
this.updateNarratorVoicesDropdown();
|
||||
this.updateLanguageDropdown();
|
||||
this.setupEventListeners();
|
||||
this.applySettingsToHTML();
|
||||
updateStatus('Ready');
|
||||
} catch (error) {
|
||||
@@ -488,15 +492,14 @@ class AllTalkTtsProvider {
|
||||
const modelSelect = document.getElementById('switch_model');
|
||||
if (modelSelect) {
|
||||
// Remove the event listener if it was previously added
|
||||
modelSelect.removeEventListener('change', debouncedModelSelectChange);
|
||||
// Add the debounced event listener
|
||||
modelSelect.addEventListener('change', debouncedModelSelectChange);
|
||||
$(modelSelect).off('change').on('change', debouncedModelSelectChange);
|
||||
}
|
||||
|
||||
// DeepSpeed Listener
|
||||
const deepspeedCheckbox = document.getElementById('deepspeed');
|
||||
if (deepspeedCheckbox) {
|
||||
deepspeedCheckbox.addEventListener('change', async (event) => {
|
||||
$(deepspeedCheckbox).off('change').on('change', async (event) => {
|
||||
const deepSpeedValue = event.target.checked ? 'True' : 'False';
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
@@ -522,7 +525,7 @@ class AllTalkTtsProvider {
|
||||
// Low VRAM Listener
|
||||
const lowVramCheckbox = document.getElementById('low_vram');
|
||||
if (lowVramCheckbox) {
|
||||
lowVramCheckbox.addEventListener('change', async (event) => {
|
||||
$(lowVramCheckbox).off('change').on('change', async (event) => {
|
||||
const lowVramValue = event.target.checked ? 'True' : 'False';
|
||||
// Set status to Processing
|
||||
updateStatus('Processing');
|
||||
@@ -548,7 +551,7 @@ class AllTalkTtsProvider {
|
||||
// Narrator Voice Dropdown Listener
|
||||
const narratorVoiceSelect = document.getElementById('narrator_voice');
|
||||
if (narratorVoiceSelect) {
|
||||
narratorVoiceSelect.addEventListener('change', (event) => {
|
||||
$(narratorVoiceSelect).off('change').on('change', (event) => {
|
||||
this.settings.narrator_voice_gen = `${event.target.value}.wav`;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
@@ -556,7 +559,7 @@ class AllTalkTtsProvider {
|
||||
|
||||
const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside');
|
||||
if (textNotInsideSelect) {
|
||||
textNotInsideSelect.addEventListener('change', (event) => {
|
||||
$(textNotInsideSelect).off('change').on('change', (event) => {
|
||||
this.settings.text_not_inside = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
@@ -569,7 +572,7 @@ class AllTalkTtsProvider {
|
||||
const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js
|
||||
|
||||
if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) {
|
||||
atNarratorSelect.addEventListener('change', (event) => {
|
||||
$(atNarratorSelect).off('change').on('change', (event) => {
|
||||
const isNarratorEnabled = event.target.value === 'true';
|
||||
this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here
|
||||
textNotInsideSelect.disabled = !isNarratorEnabled;
|
||||
@@ -605,7 +608,7 @@ class AllTalkTtsProvider {
|
||||
const atGenerationMethodSelect = document.getElementById('at_generation_method');
|
||||
const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled');
|
||||
if (atGenerationMethodSelect) {
|
||||
atGenerationMethodSelect.addEventListener('change', (event) => {
|
||||
$(atGenerationMethodSelect).off('change').on('change', (event) => {
|
||||
const selectedMethod = event.target.value;
|
||||
|
||||
if (selectedMethod === 'streaming_enabled') {
|
||||
@@ -626,7 +629,7 @@ class AllTalkTtsProvider {
|
||||
// Listener for Language Dropdown
|
||||
const languageSelect = document.getElementById('language_options');
|
||||
if (languageSelect) {
|
||||
languageSelect.addEventListener('change', (event) => {
|
||||
$(languageSelect).off('change').on('change', (event) => {
|
||||
this.settings.language = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
@@ -635,7 +638,7 @@ class AllTalkTtsProvider {
|
||||
// Listener for AllTalk Endpoint Input
|
||||
const atServerInput = document.getElementById('at_server');
|
||||
if (atServerInput) {
|
||||
atServerInput.addEventListener('input', (event) => {
|
||||
$(atServerInput).off('input').on('input', (event) => {
|
||||
this.settings.provider_endpoint = event.target.value;
|
||||
this.onSettingsChange(); // Save the settings after change
|
||||
});
|
||||
@@ -665,8 +668,7 @@ class AllTalkTtsProvider {
|
||||
//#########################//
|
||||
|
||||
async onRefreshClick() {
|
||||
await this.checkReady(); // Check if the TTS provider is ready
|
||||
await this.loadSettings(this.settings); // Reload the settings
|
||||
await this.initEndpoint();
|
||||
// Additional actions as needed
|
||||
}
|
||||
|
||||
|
@@ -670,10 +670,9 @@ export function isOpenRouterWithInstruct() {
|
||||
async function populateChatHistory(messages, prompts, chatCompletion, type = null, cyclePrompt = null) {
|
||||
chatCompletion.add(new MessageCollection('chatHistory'), prompts.index('chatHistory'));
|
||||
|
||||
let names = (selected_group && groups.find(x => x.id === selected_group)?.members.map(member => characters.find(c => c.avatar === member)?.name).filter(Boolean).join(', ')) || '';
|
||||
// Reserve budget for new chat message
|
||||
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
|
||||
const newChatMessage = new Message('system', substituteParams(newChat, null, null, null, names), 'newMainChat');
|
||||
const newChatMessage = new Message('system', substituteParams(newChat), 'newMainChat');
|
||||
chatCompletion.reserveBudget(newChatMessage);
|
||||
|
||||
// Reserve budget for group nudge
|
||||
@@ -1692,24 +1691,17 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
throw new Error(`Got response status ${response.status}`);
|
||||
}
|
||||
if (stream) {
|
||||
let reader;
|
||||
let isSSEStream = oai_settings.chat_completion_source !== chat_completion_sources.MAKERSUITE;
|
||||
if (isSSEStream) {
|
||||
const eventStream = new EventSourceStream();
|
||||
response.body.pipeThrough(eventStream);
|
||||
reader = eventStream.readable.getReader();
|
||||
} else {
|
||||
reader = response.body.getReader();
|
||||
}
|
||||
const eventStream = new EventSourceStream();
|
||||
response.body.pipeThrough(eventStream);
|
||||
const reader = eventStream.readable.getReader();
|
||||
return async function* streamData() {
|
||||
let text = '';
|
||||
let utf8Decoder = new TextDecoder();
|
||||
const swipes = [];
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) return;
|
||||
const rawData = isSSEStream ? value.data : utf8Decoder.decode(value, { stream: true });
|
||||
if (isSSEStream && rawData === '[DONE]') return;
|
||||
const rawData = value.data;
|
||||
if (rawData === '[DONE]') return;
|
||||
tryParseStreamingError(response, rawData);
|
||||
const parsed = JSON.parse(rawData);
|
||||
|
||||
@@ -1750,7 +1742,7 @@ function getStreamingReply(data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return data?.completion || '';
|
||||
} else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
|
||||
return data?.candidates[0].content.parts[0].text || '';
|
||||
return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
} else {
|
||||
return data.choices[0]?.delta?.content || data.choices[0]?.message?.content || data.choices[0]?.text || '';
|
||||
}
|
||||
@@ -2173,8 +2165,13 @@ class ChatCompletion {
|
||||
let squashedMessages = [];
|
||||
|
||||
for (let message of this.messages.collection) {
|
||||
// Force exclude empty messages
|
||||
if (message.role === 'system' && !message.content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!excludeList.includes(message.identifier) && message.role === 'system' && !message.name) {
|
||||
if (lastMessage && message.content && lastMessage.role === 'system') {
|
||||
if (lastMessage && lastMessage.role === 'system') {
|
||||
lastMessage.content += '\n' + message.content;
|
||||
lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content });
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ import { tags } from './tags.js';
|
||||
import { tokenizers } from './tokenizers.js';
|
||||
import { BIAS_CACHE } from './logit-bias.js';
|
||||
|
||||
import { countOccurrences, debounce, delay, isOdd, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
|
||||
import { countOccurrences, debounce, delay, download, getFileText, isOdd, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
@@ -682,6 +682,7 @@ async function CreateZenSliders(elmnt) {
|
||||
sliderID == 'top_k_textgenerationwebui' ||
|
||||
sliderID == 'top_k' ||
|
||||
sliderID == 'rep_pen_slope' ||
|
||||
sliderID == 'smoothing_factor_textgenerationwebui' ||
|
||||
sliderID == 'min_length_textgenerationwebui') {
|
||||
offVal = 0;
|
||||
}
|
||||
@@ -696,6 +697,9 @@ async function CreateZenSliders(elmnt) {
|
||||
sliderID == 'encoder_rep_pen_textgenerationwebui' ||
|
||||
sliderID == 'temp_textgenerationwebui' ||
|
||||
sliderID == 'temp' ||
|
||||
sliderID == 'min_temp_textgenerationwebui' ||
|
||||
sliderID == 'max_temp_textgenerationwebui' ||
|
||||
sliderID == 'dynatemp_exponent_textgenerationwebui' ||
|
||||
sliderID == 'guidance_scale_textgenerationwebui' ||
|
||||
sliderID == 'guidance_scale') {
|
||||
offVal = 1;
|
||||
@@ -703,6 +707,9 @@ async function CreateZenSliders(elmnt) {
|
||||
if (sliderID == 'guidance_scale_textgenerationwebui') {
|
||||
numSteps = 78;
|
||||
}
|
||||
if (sliderID == 'top_k_textgenerationwebui') {
|
||||
sliderMin = 0;
|
||||
}
|
||||
//customize amt gen steps
|
||||
if (sliderID !== 'amount_gen' && sliderID !== 'rep_pen_range_textgenerationwebui') {
|
||||
stepScale = sliderRange / numSteps;
|
||||
@@ -1981,10 +1988,51 @@ async function updateTheme() {
|
||||
toastr.success('Theme saved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the current theme to a file.
|
||||
*/
|
||||
async function exportTheme() {
|
||||
const themeFile = await saveTheme(power_user.theme);
|
||||
const fileName = `${themeFile.name}.json`;
|
||||
download(JSON.stringify(themeFile, null, 4), fileName, 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports a theme from a file.
|
||||
* @param {File} file File to import.
|
||||
* @returns {Promise<void>} A promise that resolves when the theme is imported.
|
||||
*/
|
||||
async function importTheme(file) {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileText = await getFileText(file);
|
||||
const parsed = JSON.parse(fileText);
|
||||
|
||||
if (!parsed.name) {
|
||||
throw new Error('Missing name');
|
||||
}
|
||||
|
||||
if (themes.some(t => t.name === parsed.name)) {
|
||||
throw new Error('Theme with that name already exists');
|
||||
}
|
||||
|
||||
themes.push(parsed);
|
||||
await applyTheme(parsed.name);
|
||||
await saveTheme(parsed.name);
|
||||
const option = document.createElement('option');
|
||||
option.selected = true;
|
||||
option.value = parsed.name;
|
||||
option.innerText = parsed.name;
|
||||
$('#themes').append(option);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current theme to the server.
|
||||
* @param {string|undefined} name Theme name. If undefined, a popup will be shown to enter a name.
|
||||
* @returns {Promise<void>} A promise that resolves when the theme is saved.
|
||||
* @returns {Promise<object>} A promise that resolves when the theme is saved.
|
||||
*/
|
||||
async function saveTheme(name = undefined) {
|
||||
if (typeof name !== 'string') {
|
||||
@@ -2056,6 +2104,8 @@ async function saveTheme(name = undefined) {
|
||||
power_user.theme = name;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
async function saveMovingUI() {
|
||||
@@ -3278,6 +3328,30 @@ $(document).ready(() => {
|
||||
reloadCurrentChat();
|
||||
});
|
||||
|
||||
$('#ui_preset_import_button').on('click', function () {
|
||||
$('#ui_preset_import_file').trigger('click');
|
||||
});
|
||||
|
||||
$('#ui_preset_import_file').on('change', async function() {
|
||||
const inputElement = this instanceof HTMLInputElement && this;
|
||||
|
||||
try {
|
||||
const file = inputElement?.files?.[0];
|
||||
await importTheme(file);
|
||||
} catch (error) {
|
||||
console.error('Error importing UI theme', error);
|
||||
toastr.error(String(error), 'Failed to import UI theme');
|
||||
} finally {
|
||||
if (inputElement) {
|
||||
inputElement.value = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#ui_preset_export_button').on('click', async function () {
|
||||
await exportTheme();
|
||||
});
|
||||
|
||||
$(document).on('click', '#debug_table [data-debug-function]', function () {
|
||||
const functionId = $(this).data('debug-function');
|
||||
const functionRecord = debug_functions.find(f => f.functionId === functionId);
|
||||
|
@@ -1121,6 +1121,12 @@ function findCharacterIndex(name) {
|
||||
(a, b) => a.includes(b),
|
||||
];
|
||||
|
||||
const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
|
||||
|
||||
if (exactAvatarMatch !== -1) {
|
||||
return exactAvatarMatch;
|
||||
}
|
||||
|
||||
for (const matchType of matchTypes) {
|
||||
const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
|
||||
if (index !== -1) {
|
||||
|
@@ -16,6 +16,8 @@
|
||||
<li><tt>{{mesExamplesRaw}}</tt> – unformatted Dialogue Examples <b>(only for Story String)</b></li>
|
||||
<li><tt>{{user}}</tt> – your current Persona username</li>
|
||||
<li><tt>{{char}}</tt> – the Character's name</li>
|
||||
<li><tt>{{group}}</tt> – a comma-separated list of group member names or the character name in solo chats. Alias: {{charIfNotGroup}}</li>
|
||||
<li><tt>{{model}}</tt> – a text generation model name for the currently selected API. <b>Can be inaccurate!</b></li>
|
||||
<li><tt>{{lastMessage}}</tt> - the text of the latest chat message.</li>
|
||||
<li><tt>{{lastMessageId}}</tt> – index # of the latest chat message. Useful for slash command batching.</li>
|
||||
<li><tt>{{firstIncludedMessageId}}</tt> - the ID of the first message included in the context. Requires generation to be ran at least once in the current session.</li>
|
||||
|
Reference in New Issue
Block a user