mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into pm-i18n
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
||||
characterGroupOverlay,
|
||||
callPopup,
|
||||
characters,
|
||||
deleteCharacter,
|
||||
event_types,
|
||||
eventSource,
|
||||
getCharacters,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
buildAvatarList,
|
||||
characterToEntity,
|
||||
printCharactersDebounced,
|
||||
deleteCharacter,
|
||||
} from '../script.js';
|
||||
|
||||
import { favsToHotswap } from './RossAscends-mods.js';
|
||||
@@ -115,24 +115,7 @@ class CharacterContextMenu {
|
||||
static delete = async (characterId, deleteChats = false) => {
|
||||
const character = CharacterContextMenu.#getCharacter(characterId);
|
||||
|
||||
return fetch('/api/characters/delete', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ avatar_url: character.avatar, delete_chats: deleteChats }),
|
||||
cache: 'no-cache',
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
eventSource.emit(event_types.CHARACTER_DELETED, { id: characterId, character: character });
|
||||
return deleteCharacter(character.name, character.avatar, false).then(() => {
|
||||
if (deleteChats) getPastCharacterChats(characterId).then(pastChats => {
|
||||
for (const chat of pastChats) {
|
||||
const name = chat.file_name.replace('.jsonl', '');
|
||||
eventSource.emit(event_types.CHAT_DELETED, name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
await deleteCharacter(character.avatar, { deleteChats: deleteChats });
|
||||
};
|
||||
|
||||
static #getCharacter = (characterId) => characters[characterId] ?? null;
|
||||
|
@@ -348,8 +348,8 @@ jQuery(function () {
|
||||
(modules.includes('caption') && extension_settings.caption.source === 'extras') ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openai' && (secret_state[SECRET_KEYS.OPENAI] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'openrouter' && secret_state[SECRET_KEYS.OPENROUTER]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && secret_state[SECRET_KEYS.MAKERSUITE]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && secret_state[SECRET_KEYS.CLAUDE]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'google' && (secret_state[SECRET_KEYS.MAKERSUITE] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'anthropic' && (secret_state[SECRET_KEYS.CLAUDE] || extension_settings.caption.allow_reverse_proxy)) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ollama' && textgenerationwebui_settings.server_urls[textgen_types.OLLAMA]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||
|
||||
(extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||
|
||||
@@ -465,7 +465,7 @@ jQuery(function () {
|
||||
<option data-type="custom" value="custom_current">[Currently selected]</option>
|
||||
</select>
|
||||
</div>
|
||||
<label data-type="openai,anthropic" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
|
||||
<label data-type="openai,anthropic,google" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
|
||||
<input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
|
||||
Allow reverse proxy
|
||||
</label>
|
||||
|
@@ -357,6 +357,7 @@ export class SettingsUi {
|
||||
a.download = `${this.currentQrSet.name}.json`;
|
||||
a.click();
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
selectQrSet(qrs) {
|
||||
|
@@ -12,7 +12,13 @@ import { createThumbnail, isValidUrl } from '../utils.js';
|
||||
* @returns {Promise<string>} Generated caption
|
||||
*/
|
||||
export async function getMultimodalCaption(base64Img, prompt) {
|
||||
throwIfInvalidModel();
|
||||
const useReverseProxy =
|
||||
(['openai', 'anthropic', 'google'].includes(extension_settings.caption.multimodal_api))
|
||||
&& extension_settings.caption.allow_reverse_proxy
|
||||
&& oai_settings.reverse_proxy
|
||||
&& isValidUrl(oai_settings.reverse_proxy);
|
||||
|
||||
throwIfInvalidModel(useReverseProxy);
|
||||
|
||||
const noPrefix = ['google', 'ollama', 'llamacpp'].includes(extension_settings.caption.multimodal_api);
|
||||
|
||||
@@ -39,27 +45,18 @@ export async function getMultimodalCaption(base64Img, prompt) {
|
||||
}
|
||||
}
|
||||
|
||||
const useReverseProxy =
|
||||
(extension_settings.caption.multimodal_api === 'openai' || extension_settings.caption.multimodal_api === 'anthropic')
|
||||
&& extension_settings.caption.allow_reverse_proxy
|
||||
&& oai_settings.reverse_proxy
|
||||
&& isValidUrl(oai_settings.reverse_proxy);
|
||||
|
||||
const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : '';
|
||||
const proxyPassword = useReverseProxy ? oai_settings.proxy_password : '';
|
||||
|
||||
const requestBody = {
|
||||
image: base64Img,
|
||||
prompt: prompt,
|
||||
reverse_proxy: proxyUrl,
|
||||
proxy_password: proxyPassword,
|
||||
api: extension_settings.caption.multimodal_api || 'openai',
|
||||
model: extension_settings.caption.multimodal_model || 'gpt-4-turbo',
|
||||
};
|
||||
|
||||
if (!isGoogle) {
|
||||
requestBody.api = extension_settings.caption.multimodal_api || 'openai';
|
||||
requestBody.model = extension_settings.caption.multimodal_model || 'gpt-4-turbo';
|
||||
requestBody.reverse_proxy = proxyUrl;
|
||||
requestBody.proxy_password = proxyPassword;
|
||||
}
|
||||
|
||||
if (isOllama) {
|
||||
if (extension_settings.caption.multimodal_model === 'ollama_current') {
|
||||
requestBody.model = textgenerationwebui_settings.ollama_model;
|
||||
@@ -117,8 +114,8 @@ export async function getMultimodalCaption(base64Img, prompt) {
|
||||
return String(caption).trim();
|
||||
}
|
||||
|
||||
function throwIfInvalidModel() {
|
||||
if (extension_settings.caption.multimodal_api === 'openai' && !secret_state[SECRET_KEYS.OPENAI]) {
|
||||
function throwIfInvalidModel(useReverseProxy) {
|
||||
if (extension_settings.caption.multimodal_api === 'openai' && !secret_state[SECRET_KEYS.OPENAI] && !useReverseProxy) {
|
||||
throw new Error('OpenAI API key is not set.');
|
||||
}
|
||||
|
||||
@@ -126,7 +123,11 @@ function throwIfInvalidModel() {
|
||||
throw new Error('OpenRouter API key is not set.');
|
||||
}
|
||||
|
||||
if (extension_settings.caption.multimodal_api === 'google' && !secret_state[SECRET_KEYS.MAKERSUITE]) {
|
||||
if (extension_settings.caption.multimodal_api === 'anthropic' && !secret_state[SECRET_KEYS.CLAUDE] && !useReverseProxy) {
|
||||
throw new Error('Anthropic (Claude) API key is not set.');
|
||||
}
|
||||
|
||||
if (extension_settings.caption.multimodal_api === 'google' && !secret_state[SECRET_KEYS.MAKERSUITE] && !useReverseProxy) {
|
||||
throw new Error('MakerSuite API key is not set.');
|
||||
}
|
||||
|
||||
|
@@ -3160,7 +3160,9 @@ jQuery(async () => {
|
||||
}
|
||||
|
||||
eventSource.on(event_types.EXTRAS_CONNECTED, async () => {
|
||||
await loadSettingOptions();
|
||||
if (extension_settings.sd.source === sources.extras) {
|
||||
await loadSettingOptions();
|
||||
}
|
||||
});
|
||||
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||
|
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
<label for="sd_drawthings_auth" data-i18n="Authentication (optional)">Authentication (optional)</label>
|
||||
<input id="sd_drawthings_auth" type="text" class="text_pole" data-i18n="[placeholder]Example: username:password" placeholder="Example: username:password" value="" />
|
||||
<!-- (Original Text)<b>Important:</b> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine. -->
|
||||
<!-- (Original Text)<b>Important:</b> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine. -->
|
||||
<i><b data-i18n="Important:">Important:</b></i><i data-i18n="sd_drawthings_auth_txt"> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine.</i>
|
||||
</div>
|
||||
<div data-sd-source="vlad">
|
||||
|
207
public/scripts/extensions/tts/azure.js
Normal file
207
public/scripts/extensions/tts/azure.js
Normal file
@@ -0,0 +1,207 @@
|
||||
import { callPopup, getRequestHeaders } from '../../../script.js';
|
||||
import { SECRET_KEYS, findSecret, secret_state, writeSecret } from '../../secrets.js';
|
||||
import { getPreviewString, saveTtsProviderSettings } from './index.js';
|
||||
export { AzureTtsProvider };
|
||||
|
||||
class AzureTtsProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings;
|
||||
voices = [];
|
||||
separator = ' . ';
|
||||
audioElement = document.createElement('audio');
|
||||
|
||||
defaultSettings = {
|
||||
region: '',
|
||||
voiceMap: {},
|
||||
};
|
||||
|
||||
get settingsHtml() {
|
||||
let html = `
|
||||
<div class="azure_tts_settings">
|
||||
<div class="flex-container alignItemsBaseline">
|
||||
<h4 for="azure_tts_key" class="flex1 margin0">
|
||||
<a href="https://portal.azure.com/" target="_blank">Azure TTS Key</a>
|
||||
</h4>
|
||||
<div id="azure_tts_key" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-key"></i>
|
||||
<span>Click to set</span>
|
||||
</div>
|
||||
</div>
|
||||
<label for="azure_tts_region">Region:</label>
|
||||
<input id="azure_tts_region" type="text" class="text_pole" placeholder="e.g. westus" />
|
||||
<hr>
|
||||
</div>
|
||||
`;
|
||||
return html;
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
// Update dynamically
|
||||
this.settings.region = String($('#azure_tts_region').val());
|
||||
// Reset voices
|
||||
this.voices = [];
|
||||
saveTtsProviderSettings();
|
||||
}
|
||||
|
||||
async loadSettings(settings) {
|
||||
// Populate Provider UI given input settings
|
||||
if (Object.keys(settings).length == 0) {
|
||||
console.info('Using default TTS Provider settings');
|
||||
}
|
||||
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings;
|
||||
|
||||
for (const key in settings) {
|
||||
if (key in this.settings) {
|
||||
this.settings[key] = settings[key];
|
||||
} else {
|
||||
throw `Invalid setting passed to TTS Provider: ${key}`;
|
||||
}
|
||||
}
|
||||
|
||||
$('#azure_tts_region').val(this.settings.region).on('input', () => this.onSettingsChange());
|
||||
$('#azure_tts_key').toggleClass('success', secret_state[SECRET_KEYS.AZURE_TTS]);
|
||||
$('#azure_tts_key').on('click', async () => {
|
||||
const popupText = 'Azure TTS API Key';
|
||||
const savedKey = secret_state[SECRET_KEYS.AZURE_TTS] ? await findSecret(SECRET_KEYS.AZURE_TTS) : '';
|
||||
|
||||
const key = await callPopup(popupText, 'input', savedKey);
|
||||
|
||||
if (key == false || key == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeSecret(SECRET_KEYS.AZURE_TTS, key);
|
||||
|
||||
toastr.success('API Key saved');
|
||||
$('#azure_tts_key').addClass('success');
|
||||
await this.onRefreshClick();
|
||||
});
|
||||
|
||||
try {
|
||||
await this.checkReady();
|
||||
console.debug('Azure: Settings loaded');
|
||||
} catch {
|
||||
console.debug('Azure: Settings loaded, but not ready');
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady() {
|
||||
if (secret_state[SECRET_KEYS.AZURE_TTS]) {
|
||||
await this.fetchTtsVoiceObjects();
|
||||
} else {
|
||||
this.voices = [];
|
||||
}
|
||||
}
|
||||
|
||||
async onRefreshClick() {
|
||||
await this.checkReady();
|
||||
}
|
||||
|
||||
//#################//
|
||||
// TTS Interfaces //
|
||||
//#################//
|
||||
|
||||
async getVoice(voiceName) {
|
||||
if (this.voices.length == 0) {
|
||||
this.voices = await this.fetchTtsVoiceObjects();
|
||||
}
|
||||
const match = this.voices.filter(
|
||||
voice => voice.name == voiceName,
|
||||
)[0];
|
||||
if (!match) {
|
||||
throw `TTS Voice name ${voiceName} not found`;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
async generateTts(text, voiceId) {
|
||||
const response = await this.fetchTtsGeneration(text, voiceId);
|
||||
return response;
|
||||
}
|
||||
|
||||
//###########//
|
||||
// API CALLS //
|
||||
//###########//
|
||||
async fetchTtsVoiceObjects() {
|
||||
if (!secret_state[SECRET_KEYS.AZURE_TTS]) {
|
||||
console.warn('Azure TTS API Key not set');
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!this.settings.region) {
|
||||
console.warn('Azure TTS region not set');
|
||||
return [];
|
||||
}
|
||||
|
||||
const response = await fetch('/api/azure/list', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
region: this.settings.region,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
let responseJson = await response.json();
|
||||
responseJson = responseJson
|
||||
.sort((a, b) => a.Locale.localeCompare(b.Locale) || a.ShortName.localeCompare(b.ShortName))
|
||||
.map(x => ({ name: x.ShortName, voice_id: x.ShortName, preview_url: false, lang: x.Locale }));
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview TTS for a given voice ID.
|
||||
* @param {string} id Voice ID
|
||||
*/
|
||||
async previewTtsVoice(id) {
|
||||
this.audioElement.pause();
|
||||
this.audioElement.currentTime = 0;
|
||||
const voice = await this.getVoice(id);
|
||||
const text = getPreviewString(voice.lang);
|
||||
const response = await this.fetchTtsGeneration(text, id);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
|
||||
const audio = await response.blob();
|
||||
const url = URL.createObjectURL(audio);
|
||||
this.audioElement.src = url;
|
||||
this.audioElement.play();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async fetchTtsGeneration(text, voiceId) {
|
||||
if (!secret_state[SECRET_KEYS.AZURE_TTS]) {
|
||||
throw new Error('Azure TTS API Key not set');
|
||||
}
|
||||
|
||||
if (!this.settings.region) {
|
||||
throw new Error('Azure TTS region not set');
|
||||
}
|
||||
|
||||
const response = await fetch('/api/azure/generate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
text: text,
|
||||
voice: voiceId,
|
||||
region: this.settings.region,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@@ -155,6 +155,7 @@ class EdgeTtsProvider {
|
||||
const url = URL.createObjectURL(audio);
|
||||
this.audioElement.src = url;
|
||||
this.audioElement.play();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -13,6 +13,7 @@ import { XTTSTtsProvider } from './xtts.js';
|
||||
import { GSVITtsProvider } from './gsvi.js';
|
||||
import { AllTalkTtsProvider } from './alltalk.js';
|
||||
import { SpeechT5TtsProvider } from './speecht5.js';
|
||||
import { AzureTtsProvider } from './azure.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
@@ -83,6 +84,7 @@ const ttsProviders = {
|
||||
OpenAI: OpenAITtsProvider,
|
||||
AllTalk: AllTalkTtsProvider,
|
||||
SpeechT5: SpeechT5TtsProvider,
|
||||
Azure: AzureTtsProvider,
|
||||
};
|
||||
let ttsProvider;
|
||||
let ttsProviderName;
|
||||
|
@@ -180,6 +180,7 @@ class NovelTtsProvider {
|
||||
const url = URL.createObjectURL(audio);
|
||||
this.audioElement.src = url;
|
||||
this.audioElement.play();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async* fetchTtsGeneration(inputText, voiceId) {
|
||||
|
@@ -60,6 +60,7 @@ class SpeechT5TtsProvider {
|
||||
const url = URL.createObjectURL(audio);
|
||||
this.audioElement.src = url;
|
||||
this.audioElement.play();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async loadSettings(settings) {
|
||||
|
@@ -52,6 +52,7 @@ const settings = {
|
||||
insert: 3,
|
||||
query: 2,
|
||||
message_chunk_size: 400,
|
||||
score_threshold: 0.25,
|
||||
|
||||
// For files
|
||||
enabled_files: false,
|
||||
@@ -760,6 +761,7 @@ async function queryCollection(collectionId, searchText, topK) {
|
||||
searchText: searchText,
|
||||
topK: topK,
|
||||
source: settings.source,
|
||||
threshold: settings.score_threshold,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -788,6 +790,7 @@ async function queryMultipleCollections(collectionIds, searchText, topK) {
|
||||
searchText: searchText,
|
||||
topK: topK,
|
||||
source: settings.source,
|
||||
threshold: settings.score_threshold,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1310,6 +1313,12 @@ jQuery(async () => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#vectors_score_threshold').val(settings.score_threshold).on('input', () => {
|
||||
settings.score_threshold = Number($('#vectors_score_threshold').val());
|
||||
Object.assign(extension_settings.vectors, settings);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
const validSecret = !!secret_state[SECRET_KEYS.NOMICAI];
|
||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
||||
$('#api_key_nomicai').attr('placeholder', placeholder);
|
||||
|
@@ -81,11 +81,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container flexFlowColumn" title="How many last messages will be matched for relevance.">
|
||||
<label for="vectors_query">
|
||||
<span>Query messages</span>
|
||||
</label>
|
||||
<input type="number" id="vectors_query" class="text_pole widthUnset" min="1" max="99" />
|
||||
<div class="flex-container marginTopBot5">
|
||||
<div class="flex-container flex1 flexFlowColumn" title="How many last messages will be matched for relevance.">
|
||||
<label for="vectors_query">
|
||||
<span>Query messages</span>
|
||||
</label>
|
||||
<input type="number" id="vectors_query" class="text_pole widthUnset" min="1" max="99" />
|
||||
</div>
|
||||
<div class="flex-container flex1 flexFlowColumn" title="Cut-off score for relevance. Helps to filter out irrelevant data.">
|
||||
<label for="vectors_query">
|
||||
<span>Score threshold</span>
|
||||
</label>
|
||||
<input type="number" id="vectors_score_threshold" class="text_pole widthUnset" min="0" max="1" step="0.05" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
|
@@ -1743,8 +1743,8 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
delete generate_data.stop;
|
||||
}
|
||||
|
||||
// Proxy is only supported for Claude, OpenAI and Mistral
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) {
|
||||
// Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE].includes(oai_settings.chat_completion_source)) {
|
||||
validateReverseProxy();
|
||||
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
|
||||
generate_data['proxy_password'] = oai_settings.proxy_password;
|
||||
@@ -3857,6 +3857,9 @@ async function onModelChange() {
|
||||
else if (['command-r', 'command-r-plus'].includes(oai_settings.cohere_model)) {
|
||||
$('#openai_max_context').attr('max', max_128k);
|
||||
}
|
||||
else if(['c4ai-aya-23'].includes(oai_settings.cohere_model)) {
|
||||
$('#openai_max_context').attr('max', max_8k);
|
||||
}
|
||||
else {
|
||||
$('#openai_max_context').attr('max', max_4k);
|
||||
}
|
||||
@@ -4035,7 +4038,7 @@ async function onConnectButtonClick(e) {
|
||||
await writeSecret(SECRET_KEYS.MAKERSUITE, api_key_makersuite);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.MAKERSUITE]) {
|
||||
if (!secret_state[SECRET_KEYS.MAKERSUITE] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for MakerSuite');
|
||||
return;
|
||||
}
|
||||
@@ -4087,7 +4090,7 @@ async function onConnectButtonClick(e) {
|
||||
await writeSecret(SECRET_KEYS.MISTRALAI, api_key_mistralai);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.MISTRALAI]) {
|
||||
if (!secret_state[SECRET_KEYS.MISTRALAI] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for MistralAI');
|
||||
return;
|
||||
}
|
||||
|
@@ -678,6 +678,9 @@ async function CreateZenSliders(elmnt) {
|
||||
sliderID == 'top_k' ||
|
||||
sliderID == 'mirostat_mode_kobold' ||
|
||||
sliderID == 'rep_pen_range' ||
|
||||
sliderID == 'dry_allowed_length_textgenerationwebui' ||
|
||||
sliderID == 'rep_pen_decay_textgenerationwebui' ||
|
||||
sliderID == 'dry_penalty_last_n_textgenerationwebui' ||
|
||||
sliderID == 'max_tokens_second_textgenerationwebui') {
|
||||
decimals = 0;
|
||||
}
|
||||
@@ -685,7 +688,9 @@ async function CreateZenSliders(elmnt) {
|
||||
sliderID == 'max_temp_textgenerationwebui' ||
|
||||
sliderID == 'dynatemp_exponent_textgenerationwebui' ||
|
||||
sliderID == 'smoothing_curve_textgenerationwebui' ||
|
||||
sliderID == 'smoothing_factor_textgenerationwebui') {
|
||||
sliderID == 'smoothing_factor_textgenerationwebui' ||
|
||||
sliderID == 'dry_multiplier_textgenerationwebui' ||
|
||||
sliderID == 'dry_base_textgenerationwebui') {
|
||||
decimals = 2;
|
||||
}
|
||||
if (sliderID == 'eta_cutoff_textgenerationwebui' ||
|
||||
@@ -746,6 +751,8 @@ async function CreateZenSliders(elmnt) {
|
||||
sliderID == 'rep_pen_slope' ||
|
||||
sliderID == 'smoothing_factor_textgenerationwebui' ||
|
||||
sliderID == 'smoothing_curve_textgenerationwebui' ||
|
||||
sliderID == 'skew_textgenerationwebui' ||
|
||||
sliderID == 'dry_multiplier_textgenerationwebui' ||
|
||||
sliderID == 'min_length_textgenerationwebui') {
|
||||
offVal = 0;
|
||||
}
|
||||
@@ -1754,11 +1761,24 @@ function loadMaxContextUnlocked() {
|
||||
}
|
||||
|
||||
function switchMaxContextSize() {
|
||||
const elements = [$('#max_context'), $('#max_context_counter'), $('#rep_pen_range'), $('#rep_pen_range_counter'), $('#rep_pen_range_textgenerationwebui'), $('#rep_pen_range_counter_textgenerationwebui')];
|
||||
const elements = [
|
||||
$('#max_context'),
|
||||
$('#max_context_counter'),
|
||||
$('#rep_pen_range'),
|
||||
$('#rep_pen_range_counter'),
|
||||
$('#rep_pen_range_textgenerationwebui'),
|
||||
$('#rep_pen_range_counter_textgenerationwebui'),
|
||||
$('#dry_penalty_last_n_textgenerationwebui'),
|
||||
$('#dry_penalty_last_n_counter_textgenerationwebui'),
|
||||
$('#rep_pen_decay_textgenerationwebui'),
|
||||
$('#rep_pen_decay_counter_textgenerationwebui'),
|
||||
];
|
||||
const maxValue = power_user.max_context_unlocked ? MAX_CONTEXT_UNLOCKED : MAX_CONTEXT_DEFAULT;
|
||||
const minValue = power_user.max_context_unlocked ? maxContextMin : maxContextMin;
|
||||
const steps = power_user.max_context_unlocked ? unlockedMaxContextStep : maxContextStep;
|
||||
$('#rep_pen_range_textgenerationwebui_zenslider').remove(); //unsure why, but this is necessary.
|
||||
$('#dry_penalty_last_n_textgenerationwebui_zenslider').remove();
|
||||
$('#rep_pen_decay_textgenerationwebui_zenslider').remove();
|
||||
for (const element of elements) {
|
||||
const id = element.attr('id');
|
||||
element.attr('max', maxValue);
|
||||
@@ -1787,6 +1807,10 @@ function switchMaxContextSize() {
|
||||
CreateZenSliders($('#max_context'));
|
||||
$('#rep_pen_range_textgenerationwebui_zenslider').remove();
|
||||
CreateZenSliders($('#rep_pen_range_textgenerationwebui'));
|
||||
$('#dry_penalty_last_n_textgenerationwebui_zenslider').remove();
|
||||
CreateZenSliders($('#dry_penalty_last_n_textgenerationwebui'));
|
||||
$('#rep_pen_decay_textgenerationwebui_zenslider').remove();
|
||||
CreateZenSliders($('#rep_pen_decay_textgenerationwebui'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3009,7 +3033,7 @@ $(document).ready(() => {
|
||||
var coreTruthWinHeight = window.innerHeight;
|
||||
|
||||
$(window).on('resize', async () => {
|
||||
console.log(`Window resize: ${coreTruthWinWidth}x${coreTruthWinHeight} -> ${window.innerWidth}x${window.innerHeight}`)
|
||||
console.log(`Window resize: ${coreTruthWinWidth}x${coreTruthWinHeight} -> ${window.innerWidth}x${window.innerHeight}`);
|
||||
adjustAutocompleteDebounced();
|
||||
setHotswapsDebounced();
|
||||
|
||||
@@ -3062,7 +3086,7 @@ $(document).ready(() => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('aborting MUI reset', Object.keys(power_user.movingUIState).length)
|
||||
console.log('aborting MUI reset', Object.keys(power_user.movingUIState).length);
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
coreTruthWinWidth = window.innerWidth;
|
||||
|
@@ -27,6 +27,7 @@ export const SECRET_KEYS = {
|
||||
COHERE: 'api_key_cohere',
|
||||
PERPLEXITY: 'api_key_perplexity',
|
||||
GROQ: 'api_key_groq',
|
||||
AZURE_TTS: 'api_key_azure_tts',
|
||||
};
|
||||
|
||||
const INPUT_MAP = {
|
||||
|
@@ -1112,6 +1112,9 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
new SlashCommandNamedArgument(
|
||||
'role', 'role for in-chat injections', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'user', 'assistant'],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'],
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@@ -1173,6 +1176,7 @@ function injectCallback(args, value) {
|
||||
};
|
||||
|
||||
const id = resolveVariable(args?.id);
|
||||
const ephemeral = isTrueBoolean(args?.ephemeral);
|
||||
|
||||
if (!id) {
|
||||
console.warn('WARN: No ID provided for /inject command');
|
||||
@@ -1206,6 +1210,23 @@ function injectCallback(args, value) {
|
||||
|
||||
setExtensionPrompt(prefixedId, value, position, depth, scan, role);
|
||||
saveMetadataDebounced();
|
||||
|
||||
if (ephemeral) {
|
||||
let deleted = false;
|
||||
const unsetInject = () => {
|
||||
if (deleted) {
|
||||
return;
|
||||
}
|
||||
console.log('Removing ephemeral script injection', id);
|
||||
delete chat_metadata.script_injects[id];
|
||||
setExtensionPrompt(prefixedId, '', position, depth, scan, role);
|
||||
saveMetadataDebounced();
|
||||
deleted = true;
|
||||
};
|
||||
eventSource.once(event_types.GENERATION_ENDED, unsetInject);
|
||||
eventSource.once(event_types.GENERATION_STOPPED, unsetInject);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@@ -100,6 +100,7 @@ const settings = {
|
||||
min_p: 0,
|
||||
rep_pen: 1.2,
|
||||
rep_pen_range: 0,
|
||||
rep_pen_decay: 0,
|
||||
no_repeat_ngram_size: 0,
|
||||
penalty_alpha: 0,
|
||||
num_beams: 1,
|
||||
@@ -108,6 +109,7 @@ const settings = {
|
||||
encoder_rep_pen: 1,
|
||||
freq_pen: 0,
|
||||
presence_pen: 0,
|
||||
skew: 0,
|
||||
do_sample: true,
|
||||
early_stopping: false,
|
||||
dynatemp: false,
|
||||
@@ -116,6 +118,11 @@ const settings = {
|
||||
dynatemp_exponent: 1.0,
|
||||
smoothing_factor: 0.0,
|
||||
smoothing_curve: 1.0,
|
||||
dry_allowed_length: 2,
|
||||
dry_multiplier: 0.0,
|
||||
dry_base: 1.75,
|
||||
dry_sequence_breakers: '["\\n", ":", "\\"", "*"]',
|
||||
dry_penalty_last_n: 0,
|
||||
max_tokens_second: 0,
|
||||
seed: -1,
|
||||
preset: 'Default',
|
||||
@@ -139,6 +146,7 @@ const settings = {
|
||||
//best_of_aphrodite: 1,
|
||||
ignore_eos_token: false,
|
||||
spaces_between_special_tokens: true,
|
||||
speculative_ngram: false,
|
||||
//logits_processors_aphrodite: [],
|
||||
//log_probs_aphrodite: 0,
|
||||
//prompt_log_probs_aphrodite: 0,
|
||||
@@ -171,6 +179,7 @@ export const setting_names = [
|
||||
'temperature_last',
|
||||
'rep_pen',
|
||||
'rep_pen_range',
|
||||
'rep_pen_decay',
|
||||
'no_repeat_ngram_size',
|
||||
'top_k',
|
||||
'top_p',
|
||||
@@ -190,10 +199,16 @@ export const setting_names = [
|
||||
'dynatemp_exponent',
|
||||
'smoothing_factor',
|
||||
'smoothing_curve',
|
||||
'dry_allowed_length',
|
||||
'dry_multiplier',
|
||||
'dry_base',
|
||||
'dry_sequence_breakers',
|
||||
'dry_penalty_last_n',
|
||||
'max_tokens_second',
|
||||
'encoder_rep_pen',
|
||||
'freq_pen',
|
||||
'presence_pen',
|
||||
'skew',
|
||||
'do_sample',
|
||||
'early_stopping',
|
||||
'seed',
|
||||
@@ -214,6 +229,7 @@ export const setting_names = [
|
||||
//'best_of_aphrodite',
|
||||
'ignore_eos_token',
|
||||
'spaces_between_special_tokens',
|
||||
'speculative_ngram',
|
||||
//'logits_processors_aphrodite',
|
||||
//'log_probs_aphrodite',
|
||||
//'prompt_log_probs_aphrodite'
|
||||
@@ -638,6 +654,7 @@ jQuery(function () {
|
||||
'min_p_textgenerationwebui': 0,
|
||||
'rep_pen_textgenerationwebui': 1,
|
||||
'rep_pen_range_textgenerationwebui': 0,
|
||||
'rep_pen_decay_textgenerationwebui': 0,
|
||||
'dynatemp_textgenerationwebui': false,
|
||||
'seed_textgenerationwebui': -1,
|
||||
'ban_eos_token_textgenerationwebui': false,
|
||||
@@ -656,7 +673,9 @@ jQuery(function () {
|
||||
'encoder_rep_pen_textgenerationwebui': 1,
|
||||
'freq_pen_textgenerationwebui': 0,
|
||||
'presence_pen_textgenerationwebui': 0,
|
||||
'skew_textgenerationwebui': 0,
|
||||
'no_repeat_ngram_size_textgenerationwebui': 0,
|
||||
'speculative_ngram_textgenerationwebui': false,
|
||||
'min_length_textgenerationwebui': 0,
|
||||
'num_beams_textgenerationwebui': 1,
|
||||
'length_penalty_textgenerationwebui': 1,
|
||||
@@ -665,6 +684,10 @@ jQuery(function () {
|
||||
'guidance_scale_textgenerationwebui': 1,
|
||||
'smoothing_factor_textgenerationwebui': 0,
|
||||
'smoothing_curve_textgenerationwebui': 1,
|
||||
'dry_allowed_length_textgenerationwebui': 2,
|
||||
'dry_multiplier_textgenerationwebui': 0,
|
||||
'dry_base_textgenerationwebui': 1.75,
|
||||
'dry_penalty_last_n_textgenerationwebui': 0,
|
||||
};
|
||||
|
||||
for (const [id, value] of Object.entries(inputs)) {
|
||||
@@ -1014,6 +1037,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'frequency_penalty': settings.freq_pen,
|
||||
'presence_penalty': settings.presence_pen,
|
||||
'top_k': settings.top_k,
|
||||
'skew': settings.skew,
|
||||
'min_length': settings.type === OOBA ? settings.min_length : undefined,
|
||||
'minimum_message_content_tokens': settings.type === DREAMGEN ? settings.min_length : undefined,
|
||||
'min_tokens': settings.min_length,
|
||||
@@ -1028,6 +1052,11 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
'dynatemp_exponent': settings.dynatemp ? settings.dynatemp_exponent : undefined,
|
||||
'smoothing_factor': settings.smoothing_factor,
|
||||
'smoothing_curve': settings.smoothing_curve,
|
||||
'dry_allowed_length': settings.dry_allowed_length,
|
||||
'dry_multiplier': settings.dry_multiplier,
|
||||
'dry_base': settings.dry_base,
|
||||
'dry_sequence_breakers': settings.dry_sequence_breakers,
|
||||
'dry_penalty_last_n': settings.dry_penalty_last_n,
|
||||
'max_tokens_second': settings.max_tokens_second,
|
||||
'sampler_priority': settings.type === OOBA ? settings.sampler_priority : undefined,
|
||||
'samplers': settings.type === LLAMACPP ? settings.samplers : undefined,
|
||||
@@ -1055,11 +1084,13 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
const nonAphroditeParams = {
|
||||
'rep_pen': settings.rep_pen,
|
||||
'rep_pen_range': settings.rep_pen_range,
|
||||
'repetition_decay': settings.type === TABBY ? settings.rep_pen_decay : undefined,
|
||||
'repetition_penalty_range': settings.rep_pen_range,
|
||||
'encoder_repetition_penalty': settings.type === OOBA ? settings.encoder_rep_pen : undefined,
|
||||
'no_repeat_ngram_size': settings.type === OOBA ? settings.no_repeat_ngram_size : undefined,
|
||||
'penalty_alpha': settings.type === OOBA ? settings.penalty_alpha : undefined,
|
||||
'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined,
|
||||
'speculative_ngram': settings.type === TABBY ? settings.speculative_ngram : undefined,
|
||||
'do_sample': settings.type === OOBA ? settings.do_sample : undefined,
|
||||
'seed': settings.seed,
|
||||
'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1,
|
||||
|
@@ -560,7 +560,7 @@ export function countTokensOpenAI(messages, full = false) {
|
||||
if (shouldTokenizeAI21) {
|
||||
tokenizerEndpoint = '/api/tokenizers/ai21/count';
|
||||
} else if (shouldTokenizeGoogle) {
|
||||
tokenizerEndpoint = `/api/tokenizers/google/count?model=${getTokenizerModel()}`;
|
||||
tokenizerEndpoint = `/api/tokenizers/google/count?model=${getTokenizerModel()}&reverse_proxy=${oai_settings.reverse_proxy}&proxy_password=${oai_settings.proxy_password}`;
|
||||
} else {
|
||||
tokenizerEndpoint = `/api/tokenizers/openai/count?model=${getTokenizerModel()}`;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { getContext } from './extensions.js';
|
||||
import { getRequestHeaders } from '../script.js';
|
||||
import { callPopup, getRequestHeaders } from '../script.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
import { collapseNewlines } from './power-user.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
@@ -139,6 +139,7 @@ export function download(content, fileName, contentType) {
|
||||
a.href = URL.createObjectURL(file);
|
||||
a.download = fileName;
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1020,6 +1021,36 @@ export function extractDataFromPng(data, identifier = 'chara') {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the server to sanitize a given filename
|
||||
*
|
||||
* @param {string} fileName - The name of the file to sanitize
|
||||
* @returns {Promise<string>} A Promise that resolves to the sanitized filename if successful, or rejects with an error message if unsuccessful
|
||||
*/
|
||||
export async function getSanitizedFilename(fileName) {
|
||||
try {
|
||||
const result = await fetch('/api/files/sanitize-filename', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
fileName: fileName,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
const error = await result.text();
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const responseData = await result.json();
|
||||
return responseData.fileName;
|
||||
} catch (error) {
|
||||
toastr.error(String(error), 'Could not sanitize fileName');
|
||||
console.error('Could not sanitize fileName', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a base64 encoded image to the backend to be saved as a file.
|
||||
*
|
||||
@@ -1468,23 +1499,47 @@ export function flashHighlight(element, timespan = 2000) {
|
||||
setTimeout(() => element.removeClass('flash animated'), timespan);
|
||||
}
|
||||
|
||||
/**
|
||||
* A common base function for case-insensitive and accent-insensitive string comparisons.
|
||||
*
|
||||
* @param {string} a - The first string to compare.
|
||||
* @param {string} b - The second string to compare.
|
||||
* @param {(a:string,b:string)=>boolean} comparisonFunction - The function to use for the comparison.
|
||||
* @returns {*} - The result of the comparison.
|
||||
*/
|
||||
export function compareIgnoreCaseAndAccents(a, b, comparisonFunction) {
|
||||
if (!a || !b) return comparisonFunction(a, b); // Return the comparison result if either string is empty
|
||||
|
||||
// Normalize and remove diacritics, then convert to lower case
|
||||
const normalizedA = a.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
const normalizedB = b.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
|
||||
// Check if the normalized strings are equal
|
||||
return comparisonFunction(normalizedA, normalizedB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a case-insensitive and accent-insensitive substring search.
|
||||
* This function normalizes the strings to remove diacritical marks and converts them to lowercase to ensure the search is insensitive to case and accents.
|
||||
*
|
||||
* @param {string} text - The text in which to search for the substring.
|
||||
* @param {string} searchTerm - The substring to search for in the text.
|
||||
* @returns {boolean} - Returns true if the searchTerm is found within the text, otherwise returns false.
|
||||
* @param {string} text - The text in which to search for the substring
|
||||
* @param {string} searchTerm - The substring to search for in the text
|
||||
* @returns {boolean} true if the searchTerm is found within the text, otherwise returns false
|
||||
*/
|
||||
export function includesIgnoreCaseAndAccents(text, searchTerm) {
|
||||
if (!text || !searchTerm) return false; // Return false if either string is empty
|
||||
return compareIgnoreCaseAndAccents(text, searchTerm, (a, b) => a?.includes(b) === true);
|
||||
}
|
||||
|
||||
// Normalize and remove diacritics, then convert to lower case
|
||||
const normalizedText = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
const normalizedSearchTerm = searchTerm.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
|
||||
// Check if the normalized text includes the normalized search term
|
||||
return normalizedText.includes(normalizedSearchTerm);
|
||||
/**
|
||||
* Performs a case-insensitive and accent-insensitive equality check.
|
||||
* This function normalizes the strings to remove diacritical marks and converts them to lowercase to ensure the search is insensitive to case and accents.
|
||||
*
|
||||
* @param {string} a - The first string to compare
|
||||
* @param {string} b - The second string to compare
|
||||
* @returns {boolean} true if the strings are equal, otherwise returns false
|
||||
*/
|
||||
export function equalsIgnoreCaseAndAccents(a, b) {
|
||||
return compareIgnoreCaseAndAccents(a, b, (a, b) => a === b);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1665,3 +1720,38 @@ export function highlightRegex(regexStr) {
|
||||
|
||||
return `<span class="regex-highlight">${regexStr}</span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms if the user wants to overwrite an existing data object (like character, world info, etc) if one exists.
|
||||
* If no data with the name exists, this simply returns true.
|
||||
*
|
||||
* @param {string} type - The type of the check ("World Info", "Character", etc)
|
||||
* @param {string[]} existingNames - The list of existing names to check against
|
||||
* @param {string} name - The new name
|
||||
* @param {object} options - Optional parameters
|
||||
* @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when needing to overwrite an existing data object
|
||||
* @param {string} [options.actionName='overwrite'] - The action name to display in the confirmation dialog
|
||||
* @param {(existingName:string)=>void} [options.deleteAction=null] - Optional action to execute wen deleting an existing data object on overwrite
|
||||
* @returns {Promise<boolean>} True if the user confirmed the overwrite or there is no overwrite needed, false otherwise
|
||||
*/
|
||||
export async function checkOverwriteExistingData(type, existingNames, name, { interactive = false, actionName = 'Overwrite', deleteAction = null } = {}) {
|
||||
const existing = existingNames.find(x => equalsIgnoreCaseAndAccents(x, name));
|
||||
if (!existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const overwrite = interactive ? await callPopup(`<h3>${type} ${actionName}</h3><p>A ${type.toLowerCase()} with the same name already exists:<br />${existing}</p>Do you want to overwrite it?`, 'confirm') : false;
|
||||
if (!overwrite) {
|
||||
toastr.warning(`${type} ${actionName.toLowerCase()} cancelled. A ${type.toLowerCase()} with the same name already exists:<br />${existing}`, `${type} ${actionName}`, { escapeHtml: false });
|
||||
return false;
|
||||
}
|
||||
|
||||
toastr.info(`Overwriting Existing ${type}:<br />${existing}`, `${type} ${actionName}`, { escapeHtml: false });
|
||||
|
||||
// If there is an action to delete the existing data, do it, as the name might be slightly different so file name would not be the same
|
||||
if (deleteAction) {
|
||||
deleteAction(existing);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -795,27 +795,26 @@ function letCallback(args, value) {
|
||||
|
||||
/**
|
||||
* Set or retrieve a variable in the current scope or nearest ancestor scope.
|
||||
* @param {{_scope:SlashCommandScope, key?:string, index?:String|Number}} args Named arguments.
|
||||
* @param {String|[String, SlashCommandClosure]} value Name and optional value for the variable.
|
||||
* @param {{_scope:SlashCommandScope, key?:string, index?:string|number}} args Named arguments.
|
||||
* @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]} value Name and optional value for the variable.
|
||||
* @returns The variable's value
|
||||
*/
|
||||
function varCallback(args, value) {
|
||||
if (Array.isArray(value)) {
|
||||
args._scope.setVariable(value[0], typeof value[1] == 'string' ? value.slice(1).join(' ') : value[1], args.index);
|
||||
return value[1];
|
||||
}
|
||||
if (!Array.isArray(value)) value = [value];
|
||||
if (args.key !== undefined) {
|
||||
const key = args.key;
|
||||
const val = value;
|
||||
args._scope.setVariable(key, val, args.index);
|
||||
return val;
|
||||
} else if (value.includes(' ')) {
|
||||
const key = value.split(' ')[0];
|
||||
const val = value.split(' ').slice(1).join(' ');
|
||||
const val = value.join(' ');
|
||||
args._scope.setVariable(key, val, args.index);
|
||||
return val;
|
||||
}
|
||||
return args._scope.getVariable(args.key ?? value, args.index);
|
||||
const key = value.shift();
|
||||
if (value.length > 0) {
|
||||
const val = value.join(' ');
|
||||
args._scope.setVariable(key, val, args.index);
|
||||
return val;
|
||||
} else {
|
||||
return args._scope.getVariable(key, args.index);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerVariableCommands() {
|
||||
@@ -1733,7 +1732,7 @@ export function registerVariableCommands() {
|
||||
returns: 'the variable value',
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], false,
|
||||
'key', 'variable name; forces setting the variable, even if no value is provided', [ARGUMENT_TYPE.VARIABLE_NAME], false,
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'index',
|
||||
@@ -1769,7 +1768,7 @@ export function registerVariableCommands() {
|
||||
<pre><code class="language-stscript">/let x foo | /var x foo bar | /var x | /echo</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var key=x | /echo</code></pre>
|
||||
<pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean } from './utils.js';
|
||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, equalsIgnoreCaseAndAccents, getSanitizedFilename, checkOverwriteExistingData } from './utils.js';
|
||||
import { extension_settings, getContext } from './extensions.js';
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
@@ -50,6 +50,7 @@ const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
|
||||
|
||||
let world_info = {};
|
||||
let selected_world_info = [];
|
||||
/** @type {string[]} */
|
||||
let world_names;
|
||||
let world_info_depth = 2;
|
||||
let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated
|
||||
@@ -1057,6 +1058,34 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
|
||||
return;
|
||||
}
|
||||
|
||||
// Regardless of whether success is displayed or not. Make sure the delete button is available.
|
||||
// Do not put this code behind.
|
||||
$('#world_popup_delete').off('click').on('click', async () => {
|
||||
const confirmation = await callPopup(`<h3>Delete the World/Lorebook: "${name}"?</h3>This action is irreversible!`, 'confirm');
|
||||
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (world_info.charLore) {
|
||||
world_info.charLore.forEach((charLore, index) => {
|
||||
if (charLore.extraBooks?.includes(name)) {
|
||||
const tempCharLore = charLore.extraBooks.filter((e) => e !== name);
|
||||
if (tempCharLore.length === 0) {
|
||||
world_info.charLore.splice(index, 1);
|
||||
} else {
|
||||
charLore.extraBooks = tempCharLore;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// Selected world_info automatically refreshes
|
||||
await deleteWorldInfo(name);
|
||||
});
|
||||
|
||||
// Before printing the WI, we check if we should enable/disable search sorting
|
||||
verifyWorldInfoSearchSortRule();
|
||||
|
||||
@@ -1225,32 +1254,6 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
|
||||
}
|
||||
});
|
||||
|
||||
$('#world_popup_delete').off('click').on('click', async () => {
|
||||
const confirmation = await callPopup(`<h3>Delete the World/Lorebook: "${name}"?</h3>This action is irreversible!`, 'confirm');
|
||||
|
||||
if (!confirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (world_info.charLore) {
|
||||
world_info.charLore.forEach((charLore, index) => {
|
||||
if (charLore.extraBooks?.includes(name)) {
|
||||
const tempCharLore = charLore.extraBooks.filter((e) => e !== name);
|
||||
if (tempCharLore.length === 0) {
|
||||
world_info.charLore.splice(index, 1);
|
||||
} else {
|
||||
charLore.extraBooks = tempCharLore;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
// Selected world_info automatically refreshes
|
||||
await deleteWorldInfo(name);
|
||||
});
|
||||
|
||||
// Check if a sortable instance exists
|
||||
if (worldEntriesList.sortable('instance') !== undefined) {
|
||||
// Destroy the instance
|
||||
@@ -2542,9 +2545,15 @@ async function renameWorldInfo(name, data) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a world info with the given name
|
||||
*
|
||||
* @param {string} worldInfoName - The name of the world info to delete
|
||||
* @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise
|
||||
*/
|
||||
async function deleteWorldInfo(worldInfoName) {
|
||||
if (!world_names.includes(worldInfoName)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/worldinfo/delete', {
|
||||
@@ -2553,24 +2562,28 @@ async function deleteWorldInfo(worldInfoName) {
|
||||
body: JSON.stringify({ name: worldInfoName }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const existingWorldIndex = selected_world_info.findIndex((e) => e === worldInfoName);
|
||||
if (existingWorldIndex !== -1) {
|
||||
selected_world_info.splice(existingWorldIndex, 1);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await updateWorldInfoList();
|
||||
$('#world_editor_select').trigger('change');
|
||||
const existingWorldIndex = selected_world_info.findIndex((e) => e === worldInfoName);
|
||||
if (existingWorldIndex !== -1) {
|
||||
selected_world_info.splice(existingWorldIndex, 1);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
if ($('#character_world').val() === worldInfoName) {
|
||||
$('#character_world').val('').trigger('change');
|
||||
setWorldInfoButtonClass(undefined, false);
|
||||
if (menu_type != 'create') {
|
||||
saveCharacterDebounced();
|
||||
}
|
||||
await updateWorldInfoList();
|
||||
$('#world_editor_select').trigger('change');
|
||||
|
||||
if ($('#character_world').val() === worldInfoName) {
|
||||
$('#character_world').val('').trigger('change');
|
||||
setWorldInfoButtonClass(undefined, false);
|
||||
if (menu_type != 'create') {
|
||||
saveCharacterDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getFreeWorldEntryUid(data) {
|
||||
@@ -2602,22 +2615,40 @@ function getFreeWorldName() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function createNewWorldInfo(worldInfoName) {
|
||||
/**
|
||||
* Creates a new world info/lorebook with the given name.
|
||||
* Checks if a world with the same name already exists, providing a warning or optionally a user confirmation dialog.
|
||||
*
|
||||
* @param {string} worldName - The name of the new world info
|
||||
* @param {Object} options - Optional parameters
|
||||
* @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world
|
||||
* @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise
|
||||
*/
|
||||
async function createNewWorldInfo(worldName, { interactive = false } = {}) {
|
||||
const worldInfoTemplate = { entries: {} };
|
||||
|
||||
if (!worldInfoName) {
|
||||
return;
|
||||
if (!worldName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await saveWorldInfo(worldInfoName, worldInfoTemplate, true);
|
||||
const sanitizedWorldName = await getSanitizedFilename(worldName);
|
||||
|
||||
const allowed = await checkOverwriteExistingData('World Info', world_names, sanitizedWorldName, { interactive: interactive, actionName: 'Create', deleteAction: (existingName) => deleteWorldInfo(existingName) });
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await saveWorldInfo(worldName, worldInfoTemplate, true);
|
||||
await updateWorldInfoList();
|
||||
|
||||
const selectedIndex = world_names.indexOf(worldInfoName);
|
||||
const selectedIndex = world_names.indexOf(worldName);
|
||||
if (selectedIndex !== -1) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
hideWorldEditor();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getCharacterLore() {
|
||||
@@ -3550,6 +3581,13 @@ export async function importWorldInfo(file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const worldName = file.name.substr(0, file.name.lastIndexOf("."));
|
||||
const sanitizedWorldName = await getSanitizedFilename(worldName);
|
||||
const allowed = await checkOverwriteExistingData('World Info', world_names, sanitizedWorldName, { interactive: true, actionName: 'Import', deleteAction: (existingName) => deleteWorldInfo(existingName) });
|
||||
if (!allowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/worldinfo/import',
|
||||
@@ -3567,7 +3605,7 @@ export async function importWorldInfo(file) {
|
||||
$('#world_editor_select').val(newIndex).trigger('change');
|
||||
}
|
||||
|
||||
toastr.info(`World Info "${data.name}" imported successfully!`);
|
||||
toastr.success(`World Info "${data.name}" imported successfully!`);
|
||||
}
|
||||
},
|
||||
error: (_jqXHR, _exception) => { },
|
||||
@@ -3642,7 +3680,7 @@ jQuery(() => {
|
||||
const finalName = await callPopup('<h3>Create a new World Info?</h3>Enter a name for the new file:', 'input', tempName);
|
||||
|
||||
if (finalName) {
|
||||
await createNewWorldInfo(finalName);
|
||||
await createNewWorldInfo(finalName, { interactive: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user