mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-02-22 23:18:27 +01:00
Merge branch 'staging' into pm-i18n
This commit is contained in:
commit
8bcb1ef2db
4
.github/readme.md
vendored
4
.github/readme.md
vendored
@ -229,7 +229,9 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
|
||||
#### Install command
|
||||
|
||||
1. Open your Command Line
|
||||
2. Run the following command `docker create --name='sillytavern' --net='[DockerNet]' -e TZ="[TimeZone]" -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]' `
|
||||
2. Run the following command
|
||||
|
||||
`docker create --name='sillytavern' --net='[DockerNet]' -e TZ="[TimeZone]" -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`
|
||||
|
||||
> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
|
||||
|
||||
|
@ -1224,6 +1224,11 @@
|
||||
<input class="neo-range-slider" type="range" id="rep_pen_range_textgenerationwebui" name="volume" min="-1" max="8192" step="1">
|
||||
<input class="neo-range-input" type="number" min="-1" max="8192" step="1" data-for="rep_pen_range_textgenerationwebui" id="rep_pen_range_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div data-tg-type="tabby" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small data-i18n="rep.pen decay">Rep Pen Decay</small>
|
||||
<input class="neo-range-slider" type="range" id="rep_pen_decay_textgenerationwebui" name="volume" min="-1" max="8192" step="1">
|
||||
<input class="neo-range-input" type="number" min="-1" max="8192" step="1" data-for="rep_pen_decay_textgenerationwebui" id="rep_pen_decay_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div data-tg-type="ooba" data-newbie-hidden class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small data-i18n="Encoder Rep. Pen.">Encoder Penalty</small>
|
||||
<input class="neo-range-slider" type="range" id="encoder_rep_pen_textgenerationwebui" name="volume" min="0.8" max="1.5" step="0.01" />
|
||||
@ -1244,6 +1249,11 @@
|
||||
<input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">
|
||||
<input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="no_repeat_ngram_size_textgenerationwebui" id="no_repeat_ngram_size_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div data-newbie-hidden data-tg-type="tabby" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small data-i18n="Skew">Skew</small>
|
||||
<input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />
|
||||
<input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div data-newbie-hidden data-tg-type="mancer, ooba, tabby, dreamgen" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
|
||||
<small data-i18n="Min Length">Min Length</small>
|
||||
<input class="neo-range-slider" type="range" id="min_length_textgenerationwebui" name="volume" min="0" max="2000" step="1" />
|
||||
@ -1272,6 +1282,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Enable for llama.cpp when the PR is merged: https://github.com/ggerganov/llama.cpp/pull/6839 -->
|
||||
<div data-newbie-hidden data-tg-type="ooba" id="dryBlock" class="wide100p">
|
||||
<h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable.">
|
||||
<label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
|
||||
<a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
|
||||
<div class=" fa-solid fa-circle-info opacity50p"></div>
|
||||
</a>
|
||||
</h4>
|
||||
<div class="flex-container flexFlowRow gap10px flexShrink">
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" title="Set to value > 0 to enable DRY. Controls the magnitude of the penalty for the shortest penalized sequences.">
|
||||
<small data-i18n="Multiplier">Multiplier</small>
|
||||
<input class="neo-range-slider" type="range" id="dry_multiplier_textgenerationwebui" min="0" max="5" step="0.01" />
|
||||
<input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="dry_multiplier_textgenerationwebui" id="dry_multiplier_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" title="Controls how fast the penalty grows with increasing sequence length.">
|
||||
<small data-i18n="Base">Base</small>
|
||||
<input class="neo-range-slider" type="range" id="dry_base_textgenerationwebui" min="1" max="4" step="0.01" />
|
||||
<input class="neo-range-input" type="number" min="1" max="4" step="0.01" data-for="dry_base_textgenerationwebui" id="dry_base_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" title="Longest sequence that can be repeated without being penalized.">
|
||||
<small data-i18n="Allowed Length">Allowed Length</small>
|
||||
<input class="neo-range-slider" type="range" id="dry_allowed_length_textgenerationwebui" min="1" max="20" step="1" />
|
||||
<input class="neo-range-input" type="number" min="1" max="20" step="1" data-for="dry_allowed_length_textgenerationwebui" id="dry_allowed_length_counter_textgenerationwebui">
|
||||
</div>
|
||||
<div class="alignItemsCenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" data-tg-type="llamacpp">
|
||||
<small data-i18n="Penalty Range">Penalty Range</small>
|
||||
<input class="neo-range-slider" type="range" id="dry_penalty_last_n_textgenerationwebui" min="0" max="8192" step="1" />
|
||||
<input class="neo-range-input" type="number" min="0" max="8192" step="1" data-for="dry_penalty_last_n_textgenerationwebui" id="dry_penalty_last_n_counter_textgenerationwebui">
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block marginTop5" title="Tokens across which sequence matching is not continued. Specified as a comma-separated list of quoted strings.">
|
||||
<div class="range-block-title textAlignCenter">
|
||||
<small data-i18n="Sequence Breakers">Sequence Breakers</small>
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<textarea id="dry_sequence_breakers_textgenerationwebui" class="text_pole textarea_compact" name="sequence_breakers" rows="3" data-i18n="[placeholder]JSON-serialized array of strings." placeholder="JSON-serialized array of strings."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-newbie-hidden data-tg-type="ooba, mancer, koboldcpp, tabby, llamacpp, aphrodite" id="dynatemp_block_ooba" class="wide100p">
|
||||
<h4 class="wide100p textAlignCenter">
|
||||
<div class="flex-container alignitemscenter justifyCenter">
|
||||
@ -1405,6 +1454,13 @@
|
||||
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Use the temperature sampler last" title="Use the temperature sampler last. This is almost always the sensible thing to do. When enabled: sample the set of plausible tokens first, then apply temperature to adjust their relative probabilities (technically, logits). When disabled: apply temperature to adjust the relative probabilities of ALL tokens first, then sample plausible tokens from that. Disabling Temperature Last boosts the probabilities in the tail of the distribution, which tends to amplify the chances of getting an incoherent response."></div>
|
||||
</label>
|
||||
</label>
|
||||
<label data-tg-type="tabby" class="checkbox_label flexGrow flexShrink" for="speculative_ngram_textgenerationwebui">
|
||||
<input type="checkbox" id="speculative_ngram_textgenerationwebui" />
|
||||
<label>
|
||||
<small data-i18n="Speculative Ngram">Speculative Ngram</small>
|
||||
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Use a different speculative decoding method without a draft model" title="Use a different speculative decoding method without a draft model. Using a draft model is preferred. Speculative ngram is not as effective."></div>
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<label data-tg-type="vllm, aphrodite" class="checkbox_label" for="spaces_between_special_tokens_textgenerationwebui">
|
||||
<input type="checkbox" id="spaces_between_special_tokens_textgenerationwebui" />
|
||||
@ -2297,7 +2353,7 @@
|
||||
<option value="windowai">Window AI</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div data-newbie-hidden class="inline-drawer wide100p" data-source="openai,claude,mistralai">
|
||||
<div data-newbie-hidden class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b data-i18n="Reverse Proxy">Reverse Proxy</b>
|
||||
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
|
||||
@ -2765,6 +2821,7 @@
|
||||
<h4 data-i18n="Cohere Model">Cohere Model</h4>
|
||||
<select id="model_cohere_select">
|
||||
<optgroup label="Stable">
|
||||
<option value="c4ai-aya-23">c4ai-aya-23</option>
|
||||
<option value="command-light">command-light</option>
|
||||
<option value="command">command</option>
|
||||
<option value="command-r">command-r</option>
|
||||
|
101
public/script.js
101
public/script.js
@ -154,6 +154,7 @@ import {
|
||||
isValidUrl,
|
||||
ensureImageFormatSupported,
|
||||
flashHighlight,
|
||||
checkOverwriteExistingData,
|
||||
} from './scripts/utils.js';
|
||||
import { debounce_timeout } from './scripts/constants.js';
|
||||
|
||||
@ -684,6 +685,7 @@ export function reloadMarkdownProcessor(render_formulas = false) {
|
||||
parseImgDimensions: true,
|
||||
simpleLineBreaks: true,
|
||||
strikethrough: true,
|
||||
disableForced4SpacesIndentedSublists: true,
|
||||
extensions: [
|
||||
showdownKatex(
|
||||
{
|
||||
@ -704,6 +706,7 @@ export function reloadMarkdownProcessor(render_formulas = false) {
|
||||
underline: true,
|
||||
simpleLineBreaks: true,
|
||||
strikethrough: true,
|
||||
disableForced4SpacesIndentedSublists: true,
|
||||
extensions: [markdownUnderscoreExt()],
|
||||
});
|
||||
}
|
||||
@ -6463,7 +6466,8 @@ export async function getChatsFromFiles(data, isGroupChat) {
|
||||
* @param {null|number} [characterId=null] - When set, the function will use this character id instead of this_chid.
|
||||
*
|
||||
* @returns {Promise<Array>} - An array containing metadata of all past chats of the character, sorted
|
||||
* in descending order by file name. Returns `undefined` if the fetch request is unsuccessful.
|
||||
* in descending order by file name. Returns an empty array if the fetch request is unsuccessful or the
|
||||
* response is an object with an `error` property set to `true`.
|
||||
*/
|
||||
export async function getPastCharacterChats(characterId = null) {
|
||||
characterId = characterId ?? this_chid;
|
||||
@ -6479,10 +6483,13 @@ export async function getPastCharacterChats(characterId = null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let data = await response.json();
|
||||
data = Object.values(data);
|
||||
data = data.sort((a, b) => a['file_name'].localeCompare(b['file_name'])).reverse();
|
||||
return data;
|
||||
const data = await response.json();
|
||||
if (typeof data === 'object' && data.error === true) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const chats = Object.values(data);
|
||||
return chats.sort((a, b) => a['file_name'].localeCompare(b['file_name'])).reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -8013,12 +8020,14 @@ const swipe_right = () => {
|
||||
|
||||
const CONNECT_API_MAP = {
|
||||
'kobold': {
|
||||
selected: 'kobold',
|
||||
button: '#api_button',
|
||||
},
|
||||
'horde': {
|
||||
selected: 'koboldhorde',
|
||||
},
|
||||
'novel': {
|
||||
selected: 'novel',
|
||||
button: '#api_button_novel',
|
||||
},
|
||||
'ooba': {
|
||||
@ -8056,6 +8065,11 @@ const CONNECT_API_MAP = {
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.APHRODITE,
|
||||
},
|
||||
'koboldcpp': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.KOBOLDCPP,
|
||||
},
|
||||
'kcpp': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
@ -8066,6 +8080,11 @@ const CONNECT_API_MAP = {
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.TOGETHERAI,
|
||||
},
|
||||
'openai': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENAI,
|
||||
},
|
||||
'oai': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
@ -8193,7 +8212,29 @@ async function disableInstructCallback() {
|
||||
* @param {string} text API name
|
||||
*/
|
||||
async function connectAPISlash(_, text) {
|
||||
if (!text) return;
|
||||
if (!text.trim()) {
|
||||
for (const [key, config] of Object.entries(CONNECT_API_MAP)) {
|
||||
if (config.selected !== main_api) continue;
|
||||
|
||||
if (config.source) {
|
||||
if (oai_settings.chat_completion_source === config.source) {
|
||||
return key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.type) {
|
||||
if (textgen_settings.type === config.type) {
|
||||
return key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
const apiConfig = CONNECT_API_MAP[text.toLowerCase()];
|
||||
if (!apiConfig) {
|
||||
@ -8453,11 +8494,28 @@ export async function handleDeleteCharacter(popup_type, this_chid, delete_chats)
|
||||
return;
|
||||
}
|
||||
|
||||
const avatar = characters[this_chid].avatar;
|
||||
const name = characters[this_chid].name;
|
||||
const pastChats = await getPastCharacterChats();
|
||||
await deleteCharacter(characters[this_chid].avatar, { deleteChats: delete_chats });
|
||||
}
|
||||
|
||||
const msg = { avatar_url: avatar, delete_chats: delete_chats };
|
||||
/**
|
||||
* Deletes a character completely, including associated chats if specified
|
||||
*
|
||||
* @param {string} characterKey - The key (avatar) of the character to be deleted
|
||||
* @param {Object} [options] - Optional parameters for the deletion
|
||||
* @param {boolean} [options.deleteChats=true] - Whether to delete associated chats or not
|
||||
* @return {Promise<void>} - A promise that resolves when the character is successfully deleted
|
||||
*/
|
||||
export async function deleteCharacter(characterKey, { deleteChats = true } = {}) {
|
||||
const character = characters.find(x => x.avatar == characterKey);
|
||||
if (!character) {
|
||||
toastr.warning(`Character ${characterKey} not found. Cannot be deleted.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const chid = characters.indexOf(character);
|
||||
const pastChats = await getPastCharacterChats(chid);
|
||||
|
||||
const msg = { avatar_url: character.avatar, delete_chats: deleteChats };
|
||||
|
||||
const response = await fetch('/api/characters/delete', {
|
||||
method: 'POST',
|
||||
@ -8466,17 +8524,17 @@ export async function handleDeleteCharacter(popup_type, this_chid, delete_chats)
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await deleteCharacter(name, avatar);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete character: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
if (delete_chats) {
|
||||
for (const chat of pastChats) {
|
||||
const name = chat.file_name.replace('.jsonl', '');
|
||||
await eventSource.emit(event_types.CHAT_DELETED, name);
|
||||
}
|
||||
await removeCharacterFromUI(character.name, character.avatar);
|
||||
|
||||
if (deleteChats) {
|
||||
for (const chat of pastChats) {
|
||||
const name = chat.file_name.replace('.jsonl', '');
|
||||
await eventSource.emit(event_types.CHAT_DELETED, name);
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to delete character: ', response.status, response.statusText);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8493,7 +8551,7 @@ export async function handleDeleteCharacter(popup_type, this_chid, delete_chats)
|
||||
* @param {string} avatar - The avatar URL of the character to be deleted.
|
||||
* @param {boolean} reloadCharacters - Whether the character list should be refreshed after deletion.
|
||||
*/
|
||||
export async function deleteCharacter(name, avatar, reloadCharacters = true) {
|
||||
async function removeCharacterFromUI(name, avatar, reloadCharacters = true) {
|
||||
await clearChat();
|
||||
$('#character_cross').click();
|
||||
this_chid = undefined;
|
||||
@ -8622,7 +8680,7 @@ jQuery(async function () {
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Connect to an API.
|
||||
Connect to an API. If no argument is provided, it will return the currently connected API.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Available APIs:</strong>
|
||||
@ -9971,6 +10029,7 @@ jQuery(async function () {
|
||||
a.setAttribute('download', filename);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -519,6 +519,9 @@ app.use('/api/backends/scale-alt', require('./src/endpoints/backends/scale-alt')
|
||||
// Speech (text-to-speech and speech-to-text)
|
||||
app.use('/api/speech', require('./src/endpoints/speech').router);
|
||||
|
||||
// Azure TTS
|
||||
app.use('/api/azure', require('./src/endpoints/azure').router);
|
||||
|
||||
const tavernUrl = new URL(
|
||||
(cliArguments.ssl ? 'https://' : 'http://') +
|
||||
(listen ? '0.0.0.0' : '127.0.0.1') +
|
||||
|
92
src/endpoints/azure.js
Normal file
92
src/endpoints/azure.js
Normal file
@ -0,0 +1,92 @@
|
||||
const { readSecret, SECRET_KEYS } = require('./secrets');
|
||||
const fetch = require('node-fetch').default;
|
||||
const express = require('express');
|
||||
const { jsonParser } = require('../express-common');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/list', jsonParser, async (req, res) => {
|
||||
try {
|
||||
const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS);
|
||||
|
||||
if (!key) {
|
||||
console.error('Azure TTS API Key not set');
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
const region = req.body.region;
|
||||
|
||||
if (!region) {
|
||||
console.error('Azure TTS region not set');
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
const url = `https://${region}.tts.speech.microsoft.com/cognitiveservices/voices/list`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': key,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Azure Request failed', response.status, response.statusText);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
const voices = await response.json();
|
||||
return res.json(voices);
|
||||
} catch (error) {
|
||||
console.error('Azure Request failed', error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/generate', jsonParser, async (req, res) => {
|
||||
try {
|
||||
const key = readSecret(req.user.directories, SECRET_KEYS.AZURE_TTS);
|
||||
|
||||
if (!key) {
|
||||
console.error('Azure TTS API Key not set');
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
const { text, voice, region } = req.body;
|
||||
if (!text || !voice || !region) {
|
||||
console.error('Missing required parameters');
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
const url = `https://${region}.tts.speech.microsoft.com/cognitiveservices/v1`;
|
||||
const lang = String(voice).split('-').slice(0, 2).join('-');
|
||||
const escapedText = String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
const ssml = `<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='${lang}'><voice xml:lang='${lang}' name='${voice}'>${escapedText}</voice></speak>`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Ocp-Apim-Subscription-Key': key,
|
||||
'Content-Type': 'application/ssml+xml',
|
||||
'X-Microsoft-OutputFormat': 'ogg-48khz-16bit-mono-opus',
|
||||
},
|
||||
body: ssml,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Azure Request failed', response.status, response.statusText);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
|
||||
const audio = await response.buffer();
|
||||
res.set('Content-Type', 'audio/ogg');
|
||||
return res.send(audio);
|
||||
} catch (error) {
|
||||
console.error('Azure Request failed', error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
};
|
@ -16,6 +16,7 @@ const API_MISTRAL = 'https://api.mistral.ai/v1';
|
||||
const API_COHERE = 'https://api.cohere.ai/v1';
|
||||
const API_PERPLEXITY = 'https://api.perplexity.ai';
|
||||
const API_GROQ = 'https://api.groq.com/openai/v1';
|
||||
const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
|
||||
|
||||
/**
|
||||
* Applies a post-processing step to the generated messages.
|
||||
@ -232,9 +233,10 @@ async function sendScaleRequest(request, response) {
|
||||
* @param {express.Response} response Express response
|
||||
*/
|
||||
async function sendMakerSuiteRequest(request, response) {
|
||||
const apiKey = readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
|
||||
const apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE);
|
||||
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
|
||||
|
||||
if (!apiKey) {
|
||||
if (!request.body.reverse_proxy && !apiKey) {
|
||||
console.log('MakerSuite API key is missing.');
|
||||
return response.status(400).send({ error: true });
|
||||
}
|
||||
@ -316,7 +318,7 @@ async function sendMakerSuiteRequest(request, response) {
|
||||
? (stream ? 'streamGenerateContent' : 'generateContent')
|
||||
: (isText ? 'generateText' : 'generateMessage');
|
||||
|
||||
const generateResponse = await fetch(`https://generativelanguage.googleapis.com/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
|
||||
const generateResponse = await fetch(`${apiUrl.origin}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
|
||||
body: JSON.stringify(body),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -2,11 +2,27 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const writeFileSyncAtomic = require('write-file-atomic').sync;
|
||||
const express = require('express');
|
||||
const sanitize = require('sanitize-filename');
|
||||
const router = express.Router();
|
||||
const { validateAssetFileName } = require('./assets');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { clientRelativePath } = require('../util');
|
||||
|
||||
router.post('/sanitize-filename', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const fileName = String(request.body.fileName);
|
||||
if (!fileName) {
|
||||
return response.status(400).send('No fileName specified');
|
||||
}
|
||||
|
||||
const sanitizedFilename = sanitize(fileName);
|
||||
return response.send({ fileName: sanitizedFilename });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/upload', jsonParser, async (request, response) => {
|
||||
try {
|
||||
if (!request.body.name) {
|
||||
|
@ -4,14 +4,18 @@ const express = require('express');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { GEMINI_SAFETY } = require('../constants');
|
||||
|
||||
const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/caption-image', jsonParser, async (request, response) => {
|
||||
try {
|
||||
const mimeType = request.body.image.split(';')[0].split(':')[1];
|
||||
const base64Data = request.body.image.split(',')[1];
|
||||
const key = readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
|
||||
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${key}`;
|
||||
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
|
||||
const apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE);
|
||||
const model = request.body.model || 'gemini-pro-vision';
|
||||
const url = `${apiUrl.origin}/v1beta/models/${model}:generateContent?key=${apiKey}`;
|
||||
const body = {
|
||||
contents: [{
|
||||
parts: [
|
||||
@ -27,7 +31,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
|
||||
generationConfig: { maxOutputTokens: 1000 },
|
||||
};
|
||||
|
||||
console.log('Multimodal captioning request', body);
|
||||
console.log('Multimodal captioning request', model, body);
|
||||
|
||||
const result = await fetch(url, {
|
||||
body: JSON.stringify(body),
|
||||
|
@ -39,6 +39,7 @@ const SECRET_KEYS = {
|
||||
COHERE: 'api_key_cohere',
|
||||
PERPLEXITY: 'api_key_perplexity',
|
||||
GROQ: 'api_key_groq',
|
||||
AZURE_TTS: 'api_key_azure_tts',
|
||||
};
|
||||
|
||||
// These are the keys that are safe to expose, even if allowKeysExposure is false
|
||||
|
@ -10,6 +10,8 @@ const { TEXTGEN_TYPES } = require('../constants');
|
||||
const { jsonParser } = require('../express-common');
|
||||
const { setAdditionalHeaders } = require('../additional-headers');
|
||||
|
||||
const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
|
||||
|
||||
/**
|
||||
* @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler
|
||||
*/
|
||||
@ -555,8 +557,11 @@ router.post('/google/count', jsonParser, async function (req, res) {
|
||||
body: JSON.stringify({ contents: convertGooglePrompt(req.body, String(req.query.model)).contents }),
|
||||
};
|
||||
try {
|
||||
const key = readSecret(req.user.directories, SECRET_KEYS.MAKERSUITE);
|
||||
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${req.query.model}:countTokens?key=${key}`, options);
|
||||
const reverseProxy = req.query.reverse_proxy?.toString() || '';
|
||||
const proxyPassword = req.query.proxy_password?.toString() || '';
|
||||
const apiKey = reverseProxy ? proxyPassword : readSecret(req.user.directories, SECRET_KEYS.MAKERSUITE);
|
||||
const apiUrl = new URL(reverseProxy || API_MAKERSUITE);
|
||||
const response = await fetch(`${apiUrl.origin}/v1beta/models/${req.query.model}:countTokens?key=${apiKey}`, options);
|
||||
const data = await response.json();
|
||||
return res.send({ 'token_count': data?.totalTokens || 0 });
|
||||
} catch (err) {
|
||||
|
@ -168,14 +168,15 @@ async function deleteVectorItems(directories, collectionId, source, hashes) {
|
||||
* @param {Object} sourceSettings - Settings for the source, if it needs any
|
||||
* @param {string} searchText - The text to search for
|
||||
* @param {number} topK - The number of results to return
|
||||
* @param {number} threshold - The threshold for the search
|
||||
* @returns {Promise<{hashes: number[], metadata: object[]}>} - The metadata of the items that match the search text
|
||||
*/
|
||||
async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK) {
|
||||
async function queryCollection(directories, collectionId, source, sourceSettings, searchText, topK, threshold) {
|
||||
const store = await getIndex(directories, collectionId, source);
|
||||
const vector = await getVector(source, sourceSettings, searchText, true, directories);
|
||||
|
||||
const result = await store.queryItems(vector, topK);
|
||||
const metadata = result.map(x => x.item.metadata);
|
||||
const metadata = result.filter(x => x.score >= threshold).map(x => x.item.metadata);
|
||||
const hashes = result.map(x => Number(x.item.metadata.hash));
|
||||
return { metadata, hashes };
|
||||
}
|
||||
@ -188,9 +189,11 @@ async function queryCollection(directories, collectionId, source, sourceSettings
|
||||
* @param {Object} sourceSettings - Settings for the source, if it needs any
|
||||
* @param {string} searchText - The text to search for
|
||||
* @param {number} topK - The number of results to return
|
||||
* @param {number} threshold - The threshold for the search
|
||||
*
|
||||
* @returns {Promise<Record<string, { hashes: number[], metadata: object[] }>>} - The top K results from each collection
|
||||
*/
|
||||
async function multiQueryCollection(directories, collectionIds, source, sourceSettings, searchText, topK) {
|
||||
async function multiQueryCollection(directories, collectionIds, source, sourceSettings, searchText, topK, threshold) {
|
||||
const vector = await getVector(source, sourceSettings, searchText, true, directories);
|
||||
const results = [];
|
||||
|
||||
@ -200,9 +203,10 @@ async function multiQueryCollection(directories, collectionIds, source, sourceSe
|
||||
results.push(...result.map(result => ({ collectionId, result })));
|
||||
}
|
||||
|
||||
// Sort results by descending similarity
|
||||
// Sort results by descending similarity, apply threshold, and take top K
|
||||
const sortedResults = results
|
||||
.sort((a, b) => b.result.score - a.result.score)
|
||||
.filter(x => x.result.score >= threshold)
|
||||
.slice(0, topK);
|
||||
|
||||
/**
|
||||
@ -274,10 +278,11 @@ router.post('/query', jsonParser, async (req, res) => {
|
||||
const collectionId = String(req.body.collectionId);
|
||||
const searchText = String(req.body.searchText);
|
||||
const topK = Number(req.body.topK) || 10;
|
||||
const threshold = Number(req.body.threshold) || 0.0;
|
||||
const source = String(req.body.source) || 'transformers';
|
||||
const sourceSettings = getSourceSettings(source, req);
|
||||
|
||||
const results = await queryCollection(req.user.directories, collectionId, source, sourceSettings, searchText, topK);
|
||||
const results = await queryCollection(req.user.directories, collectionId, source, sourceSettings, searchText, topK, threshold);
|
||||
return res.json(results);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@ -294,10 +299,11 @@ router.post('/query-multi', jsonParser, async (req, res) => {
|
||||
const collectionIds = req.body.collectionIds.map(x => String(x));
|
||||
const searchText = String(req.body.searchText);
|
||||
const topK = Number(req.body.topK) || 10;
|
||||
const threshold = Number(req.body.threshold) || 0.0;
|
||||
const source = String(req.body.source) || 'transformers';
|
||||
const sourceSettings = getSourceSettings(source, req);
|
||||
|
||||
const results = await multiQueryCollection(req.user.directories, collectionIds, source, sourceSettings, searchText, topK);
|
||||
const results = await multiQueryCollection(req.user.directories, collectionIds, source, sourceSettings, searchText, topK, threshold);
|
||||
return res.json(results);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
Loading…
x
Reference in New Issue
Block a user