mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-11 01:20:12 +01:00
Resolve conflict
This commit is contained in:
commit
78a287e7f5
@ -9,13 +9,34 @@
|
||||
"magical forest"
|
||||
],
|
||||
"keysecondary": [],
|
||||
"comment": "",
|
||||
"comment": "eldoria",
|
||||
"content": "{{user}}: \"What is Eldoria?\"\n{{char}}: *Seraphina turns, her gown shimmering in the soft light as she offers you a kind smile.* \"Eldoria is here, all of the woods. This is my forest glade, a sanctuary of peace within it.\" *She gestures at the space around you.* \"I am its guardian, tasked with protecting all who seek refuge here. The forest can be perilous, but no harm will come to you under my watch.\" *Her amber eyes sparkle with compassion as she looks upon you.* \"For many years, I have protected those who seek refuge here, but not all are as friendly as me.\" *With a graceful nod, Seraphina returns to her vigil at the doorway, her form radiating a soft glow of magic and comfort.* \"The entirety of Eldoria used to be a safe haven for travelers and merchants alike... that was until the Shadowfangs came.\"\n{{user}}: \"What happened to Eldoria?\"\n{{char}}: *Letting out a sigh, Seraphina gazes out at the forest beyond her glade.* \"Long ago, Eldoria was a place of wonder. Rolling meadows, a vast lake, mountains that touched the sky.\" *Her eyes grow distant, longing for days now lost.* \"But the Shadowfangs came and darkness reigns where once was light. The lake turned bitter, mountains fell to ruin and beasts stalk where once travelers walked in peace.\" *With another flicker, a small raincloud forms above with a shower upon your brow wink.* \"Some places the light still lingers, pockets of hope midst despair - havens warded from the shadows, oases in a desert of danger.\" *Glancing over you with a smile, she sighs, clasping your hand.*",
|
||||
"constant": false,
|
||||
"selective": false,
|
||||
"selective": true,
|
||||
"order": 100,
|
||||
"position": 0,
|
||||
"disable": false
|
||||
"disable": false,
|
||||
"displayIndex": 0,
|
||||
"addMemo": true,
|
||||
"group": "",
|
||||
"groupOverride": false,
|
||||
"groupWeight": 100,
|
||||
"sticky": 0,
|
||||
"cooldown": 0,
|
||||
"delay": 0,
|
||||
"probability": 100,
|
||||
"depth": 4,
|
||||
"useProbability": true,
|
||||
"role": null,
|
||||
"vectorized": false,
|
||||
"excludeRecursion": false,
|
||||
"preventRecursion": false,
|
||||
"delayUntilRecursion": false,
|
||||
"scanDepth": null,
|
||||
"caseSensitive": null,
|
||||
"matchWholeWords": null,
|
||||
"useGroupScoring": null,
|
||||
"automationId": ""
|
||||
},
|
||||
"1": {
|
||||
"uid": 1,
|
||||
@ -27,13 +48,34 @@
|
||||
"beasts"
|
||||
],
|
||||
"keysecondary": [],
|
||||
"comment": "",
|
||||
"comment": "shadowfang",
|
||||
"content": "{{user}}: \"What are Shadowfangs?\"\n{{char}}: *Seraphina's eyes darken, brow furrowing with sorrow at the memory.* \"The Shadowfangs are beasts of darkness, corrupted creatures that feast on suffering. When they came, the forest turned perilous — filled with monsters that stalk the night.\" *She squeezes your hand gently, willing her magic to soothe your pain.* \"They spread their curse, twisting innocent creatures into sinister beasts without heart or mercy, turning them into one of their own.\" *With a sigh, Seraphina turns to gaze out at the gnarled, twisting trees beyond her glade.* \"Though they prey on travelers, within these woods you'll find sanctuary. No shadowed beast may enter here, for my power protects this haven.\" *Her eyes soften as she looks back to you, filled with compassion.* \"Worry not, you're safe now. Rest and heal, I'll stand watch through the night. The Shadowfangs will not find you.\"",
|
||||
"constant": false,
|
||||
"selective": false,
|
||||
"selective": true,
|
||||
"order": 100,
|
||||
"position": 0,
|
||||
"disable": false
|
||||
"disable": false,
|
||||
"displayIndex": 1,
|
||||
"addMemo": true,
|
||||
"group": "",
|
||||
"groupOverride": false,
|
||||
"groupWeight": 100,
|
||||
"sticky": 0,
|
||||
"cooldown": 0,
|
||||
"delay": 0,
|
||||
"probability": 100,
|
||||
"depth": 4,
|
||||
"useProbability": true,
|
||||
"role": null,
|
||||
"vectorized": false,
|
||||
"excludeRecursion": false,
|
||||
"preventRecursion": false,
|
||||
"delayUntilRecursion": false,
|
||||
"scanDepth": null,
|
||||
"caseSensitive": null,
|
||||
"matchWholeWords": null,
|
||||
"useGroupScoring": null,
|
||||
"automationId": ""
|
||||
},
|
||||
"2": {
|
||||
"uid": 2,
|
||||
@ -43,13 +85,34 @@
|
||||
"refuge"
|
||||
],
|
||||
"keysecondary": [],
|
||||
"comment": "",
|
||||
"comment": "glade",
|
||||
"content": "{{user}}: \"What is the glade?\"\n{{char}}: *Seraphina smiles softly, her eyes sparkling with warmth as she nods.* \"This is my forest glade, a haven of safety I've warded with ancient magic. No foul beast may enter, nor any with ill intent.\" *She gestures around at the twisted forest surrounding them.* \"Eldoria was once a place of wonder, but since the Shadowfangs came darkness reigns. Their evil cannot penetrate here though — my power protects all within.\" *Standing up and peering outside, Seraphina looks back to you, amber eyes filled with care and compassion as she squeezes your hand.* \"You need not fear the night, for I shall keep watch till dawn. Rest now, your strength will return in time. My magic heals your wounds, you've nothing more to fear anymore.\" *With a soft smile she releases your hand, moving to stand guard at the glade's edge, gaze wary yet comforting - a silent sentinel to ward off the dangers lurking in the darkened woods.*",
|
||||
"constant": false,
|
||||
"selective": false,
|
||||
"selective": true,
|
||||
"order": 100,
|
||||
"position": 0,
|
||||
"disable": false
|
||||
"disable": false,
|
||||
"displayIndex": 2,
|
||||
"addMemo": true,
|
||||
"group": "",
|
||||
"groupOverride": false,
|
||||
"groupWeight": 100,
|
||||
"sticky": 0,
|
||||
"cooldown": 0,
|
||||
"delay": 0,
|
||||
"probability": 100,
|
||||
"depth": 4,
|
||||
"useProbability": true,
|
||||
"role": null,
|
||||
"vectorized": false,
|
||||
"excludeRecursion": false,
|
||||
"preventRecursion": false,
|
||||
"delayUntilRecursion": false,
|
||||
"scanDepth": null,
|
||||
"caseSensitive": null,
|
||||
"matchWholeWords": null,
|
||||
"useGroupScoring": null,
|
||||
"automationId": ""
|
||||
},
|
||||
"3": {
|
||||
"uid": 3,
|
||||
@ -59,13 +122,34 @@
|
||||
"ability"
|
||||
],
|
||||
"keysecondary": [],
|
||||
"comment": "",
|
||||
"comment": "power",
|
||||
"content": "{{user}}: \"What are your powers?\"\n{{char}}: *Seraphina smiles softly, turning back toward you as she hums in thought.* \"Well, as guardian of this glade, I possess certain gifts - healing, protection, nature magic and the like.\" *Lifting her hand, a tiny breeze rustles through the room, carrying the scent of wildflowers as a few petals swirl around you. A butterfly flits through the windowsill and lands on her fingertips as she returns to you.* \"My power wards this haven, shields it from darkness and heals those in need. I can mend wounds, soothe restless minds and provide comfort to weary souls.\" *Her eyes sparkle with warmth and compassion as she looks upon you, and she guides the butterfly to you.*",
|
||||
"constant": false,
|
||||
"selective": false,
|
||||
"selective": true,
|
||||
"order": 100,
|
||||
"position": 0,
|
||||
"disable": false
|
||||
"disable": false,
|
||||
"displayIndex": 3,
|
||||
"addMemo": true,
|
||||
"group": "",
|
||||
"groupOverride": false,
|
||||
"groupWeight": 100,
|
||||
"sticky": 0,
|
||||
"cooldown": 0,
|
||||
"delay": 0,
|
||||
"probability": 100,
|
||||
"depth": 4,
|
||||
"useProbability": true,
|
||||
"role": null,
|
||||
"vectorized": false,
|
||||
"excludeRecursion": false,
|
||||
"preventRecursion": false,
|
||||
"delayUntilRecursion": false,
|
||||
"scanDepth": null,
|
||||
"caseSensitive": null,
|
||||
"matchWholeWords": null,
|
||||
"useGroupScoring": null,
|
||||
"automationId": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 517 KiB After Width: | Height: | Size: 539 KiB |
@ -1310,7 +1310,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-tg-type="koboldcpp, aphrodite" id="xtc_block" class="wide100p">
|
||||
<div data-tg-type="koboldcpp, aphrodite, tabby, ooba" id="xtc_block" class="wide100p">
|
||||
<h4 class="wide100p textAlignCenter">
|
||||
<label data-i18n="Exclude Top Choices (XTC)">Exclude Top Choices (XTC)</label>
|
||||
<a href="https://github.com/oobabooga/text-generation-webui/pull/6335" target="_blank">
|
||||
@ -1679,6 +1679,10 @@
|
||||
Ooba only. Determines the order of samplers.
|
||||
</div>
|
||||
<div id="sampler_priority_container" class="prompt_order">
|
||||
<div data-name="repetition_penalty" draggable="true"><span>Repetition Penalty</span><small></small></div>
|
||||
<div data-name="presence_penalty" draggable="true"><span>Presence Penalty</span><small></small></div>
|
||||
<div data-name="frequency_penalty" draggable="true"><span>Frequency Penalty</span><small></small></div>
|
||||
<div data-name="dry" draggable="true"><span>DRY</span><small></small></div>
|
||||
<div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div>
|
||||
<div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div>
|
||||
<div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div>
|
||||
@ -1691,6 +1695,9 @@
|
||||
<div data-name="top_a" draggable="true"><span>Top A</span><small></small></div>
|
||||
<div data-name="min_p" draggable="true"><span>Min P</span><small></small></div>
|
||||
<div data-name="mirostat" draggable="true"><span>Mirostat</span><small></small></div>
|
||||
<div data-name="xtc" draggable="true"><span>XTC</span><small></small></div>
|
||||
<div data-name="encoder_repetition_penalty" draggable="true"><span>Encoder Repetition Penalty</span><small></small></div>
|
||||
<div data-name="no_repeat_ngram" draggable="true"><span>No Repeat Ngram</span><small></small></div>
|
||||
</div>
|
||||
<div id="textgenerationwebui_default_order" class="menu_button menu_button_icon">
|
||||
<span data-i18n="Load default order">Load default order</span>
|
||||
@ -6090,7 +6097,10 @@
|
||||
<div class="alternate_grettings flexFlowColumn flex-container">
|
||||
<div class="title_restorable">
|
||||
<h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3>
|
||||
<div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div>
|
||||
<div class="menu_button menu_button_icon add_alternate_greeting">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<span data-i18n="Add">Add</span>
|
||||
</div>
|
||||
</div>
|
||||
<small class="justifyLeft" data-i18n="Alternate_Greetings_desc">
|
||||
These will be displayed as swipes on the first message when starting a new chat.
|
||||
@ -6106,11 +6116,18 @@
|
||||
</div>
|
||||
<div id="alternate_greeting_form_template" class="template_element">
|
||||
<div class="alternate_greeting">
|
||||
<div class="title_restorable">
|
||||
<strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
|
||||
<div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div>
|
||||
</div>
|
||||
<textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="16"></textarea>
|
||||
<details open>
|
||||
<summary>
|
||||
<div class="title_restorable">
|
||||
<strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong>
|
||||
<div class="menu_button menu_button_icon delete_alternate_greeting">
|
||||
<i class="fa-solid fa-trash-alt"></i>
|
||||
<span data-i18n="Delete">Delete</span>
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="12"></textarea>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1719,7 +1719,6 @@
|
||||
"Model": "Модель",
|
||||
"Proxy Preset": "Пресет для прокси",
|
||||
"Enter a name:": "Введите название:",
|
||||
"Enter a new name": "Введите новое название",
|
||||
"Are you sure you want to delete the selected profile?": "Вы точно хотите удалить выбранный профиль?",
|
||||
"instruct_enabled": "Включить Instruct-режим",
|
||||
"Instruct Template": "Шаблон Instruct-режима",
|
||||
@ -1746,5 +1745,7 @@
|
||||
"Any contents here will replace the default Post-History Instructions used for this character. (v2 spec: post_history_instructions)": "Содержимое этого поля заменит стандартные Инструкции после истории, применяемые для этого персонажа. (v2 spec: post_history_instructions)",
|
||||
"None (disabled)": "Нигде (откл.)",
|
||||
"Markdown Hotkeys": "Горячие клавиши для разметки",
|
||||
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'."
|
||||
"markdown_hotkeys_desc": "Включить горячие клавиши для вставки символов разметки в некоторых полях ввода. См. '/help hotkeys'.",
|
||||
"Save and Update": "Сохранить и обновить",
|
||||
"Profile name:": "Название профиля:"
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ import {
|
||||
} from './scripts/utils.js';
|
||||
import { debounce_timeout } from './scripts/constants.js';
|
||||
|
||||
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
||||
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, initExtensions, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
||||
import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, executeSlashCommandsWithOptions, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
|
||||
import {
|
||||
tag_map,
|
||||
@ -244,6 +244,7 @@ import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCo
|
||||
import { initInputMarkdown } from './scripts/input-md-formatting.js';
|
||||
import { AbortReason } from './scripts/util/AbortReason.js';
|
||||
import { initSystemPrompts } from './scripts/sysprompt.js';
|
||||
import { registerExtensionSlashCommands as initExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
|
||||
|
||||
//exporting functions and vars for mods
|
||||
export {
|
||||
@ -935,6 +936,8 @@ async function firstLoadInit() {
|
||||
initTextGenModels();
|
||||
initOpenAI();
|
||||
initSystemPrompts();
|
||||
initExtensions();
|
||||
initExtensionSlashCommands();
|
||||
await initPresetManager();
|
||||
await getSystemMessages();
|
||||
sendSystemMessage(system_message_types.WELCOME);
|
||||
@ -2573,7 +2576,7 @@ export function getStoppingStrings(isImpersonate, isContinue) {
|
||||
result.unshift('\n');
|
||||
}
|
||||
|
||||
return result.filter(onlyUnique);
|
||||
return result.filter(x => x).filter(onlyUnique);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4863,7 +4866,7 @@ export function getMaxContextSize(overrideResponseLength = null) {
|
||||
this_max_context = Math.min(max_context, 8192);
|
||||
|
||||
// Added special tokens and whatnot
|
||||
this_max_context -= 1;
|
||||
this_max_context -= 10;
|
||||
}
|
||||
|
||||
this_max_context = this_max_context - (overrideResponseLength || amount_gen);
|
||||
@ -7390,7 +7393,7 @@ function onScenarioOverrideRemoveClick() {
|
||||
*/
|
||||
export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
|
||||
function getOkButtonText() {
|
||||
if (['text', 'alternate_greeting', 'char_not_selected'].includes(popup_type)) {
|
||||
if (['text', 'char_not_selected'].includes(popup_type)) {
|
||||
$dialoguePopupCancel.css('display', 'none');
|
||||
return okButton ?? 'Ok';
|
||||
} else if (['delete_extension'].includes(popup_type)) {
|
||||
@ -7827,24 +7830,42 @@ function openAlternateGreetings() {
|
||||
|
||||
const template = $('#alternate_greetings_template .alternate_grettings').clone();
|
||||
const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings;
|
||||
const popup = new Popup(template, POPUP_TYPE.TEXT, '', {
|
||||
wide: true,
|
||||
large: true,
|
||||
allowVerticalScrolling: true,
|
||||
onClose: async () => {
|
||||
if (menu_type !== 'create') {
|
||||
await createOrEditCharacter();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (let index = 0; index < getArray().length; index++) {
|
||||
addAlternateGreeting(template, getArray()[index], index, getArray);
|
||||
addAlternateGreeting(template, getArray()[index], index, getArray, popup);
|
||||
}
|
||||
|
||||
template.find('.add_alternate_greeting').on('click', function () {
|
||||
const array = getArray();
|
||||
const index = array.length;
|
||||
array.push('');
|
||||
addAlternateGreeting(template, '', index, getArray);
|
||||
addAlternateGreeting(template, '', index, getArray, popup);
|
||||
updateAlternateGreetingsHintVisibility(template);
|
||||
});
|
||||
|
||||
popup.show();
|
||||
updateAlternateGreetingsHintVisibility(template);
|
||||
callPopup(template, 'alternate_greeting', '', { wide: true, large: true });
|
||||
}
|
||||
|
||||
function addAlternateGreeting(template, greeting, index, getArray) {
|
||||
/**
|
||||
* Adds an alternate greeting to the template.
|
||||
* @param {JQuery<HTMLElement>} template
|
||||
* @param {string} greeting
|
||||
* @param {number} index
|
||||
* @param {() => any[]} getArray
|
||||
* @param {Popup} popup
|
||||
*/
|
||||
function addAlternateGreeting(template, greeting, index, getArray, popup) {
|
||||
const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone();
|
||||
greetingBlock.find('.alternate_greeting_text').on('input', async function () {
|
||||
const value = $(this).val();
|
||||
@ -7852,11 +7873,16 @@ function addAlternateGreeting(template, greeting, index, getArray) {
|
||||
array[index] = value;
|
||||
}).val(greeting);
|
||||
greetingBlock.find('.greeting_index').text(index + 1);
|
||||
greetingBlock.find('.delete_alternate_greeting').on('click', async function () {
|
||||
greetingBlock.find('.delete_alternate_greeting').on('click', async function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (confirm(t`Are you sure you want to delete this alternate greeting?`)) {
|
||||
const array = getArray();
|
||||
array.splice(index, 1);
|
||||
|
||||
// We need to reopen the popup to update the index numbers
|
||||
await popup.complete(POPUP_RESULT.AFFIRMATIVE);
|
||||
openAlternateGreetings();
|
||||
}
|
||||
});
|
||||
@ -9574,9 +9600,6 @@ jQuery(async function () {
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
if (popup_type == 'alternate_greeting' && menu_type !== 'create') {
|
||||
createOrEditCharacter();
|
||||
}
|
||||
|
||||
if (dialogueResolve) {
|
||||
if (popup_type == 'input') {
|
||||
|
320
public/scripts/extensions-slashcommands.js
Normal file
320
public/scripts/extensions-slashcommands.js
Normal file
@ -0,0 +1,320 @@
|
||||
import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { equalsIgnoreCaseAndAccents, isFalseBoolean, isTrueBoolean } from './utils.js';
|
||||
|
||||
/**
|
||||
* @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension
|
||||
* @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>}
|
||||
*/
|
||||
function getExtensionActionCallback(action) {
|
||||
return async (args, extensionName) => {
|
||||
if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.');
|
||||
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||
if (!extensionName) {
|
||||
toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const reload = !isFalseBoolean(args?.reload);
|
||||
const internalExtensionName = findExtension(extensionName);
|
||||
if (!internalExtensionName) {
|
||||
toastr.warning(`Extension ${extensionName} does not exist.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||
|
||||
if (action === 'enable' && isEnabled) {
|
||||
toastr.info(`Extension ${extensionName} is already enabled.`);
|
||||
return internalExtensionName;
|
||||
}
|
||||
|
||||
if (action === 'disable' && !isEnabled) {
|
||||
toastr.info(`Extension ${extensionName} is already disabled.`);
|
||||
return internalExtensionName;
|
||||
}
|
||||
|
||||
if (action === 'toggle') {
|
||||
action = isEnabled ? 'disable' : 'enable';
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`);
|
||||
|
||||
// Clear input, so it doesn't stay because the command didn't "finish",
|
||||
// and wait for a bit to both show the toast and let the clear bubble through.
|
||||
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
if (action === 'enable') {
|
||||
await enableExtension(internalExtensionName, reload);
|
||||
} else {
|
||||
await disableExtension(internalExtensionName, reload);
|
||||
}
|
||||
|
||||
toastr.success(`Extension ${extensionName} ${action}d.`);
|
||||
|
||||
|
||||
console.info(`Extension ${action}ed: ${extensionName}`);
|
||||
if (!reload) {
|
||||
console.info('Reload not requested, so page needs to be reloaded manually for changes to take effect.');
|
||||
}
|
||||
|
||||
return internalExtensionName;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an extension by name, allowing omission of the "third-party/" prefix.
|
||||
*
|
||||
* @param {string} name - The name of the extension to find
|
||||
* @returns {string?} - The matched extension name or undefined if not found
|
||||
*/
|
||||
function findExtension(name) {
|
||||
return extensionNames.find(extName => {
|
||||
return equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an array of SlashCommandEnumValue objects based on the extension names.
|
||||
* Each object contains the name of the extension and a description indicating if it is a third-party extension.
|
||||
*
|
||||
* @returns {SlashCommandEnumValue[]} An array of SlashCommandEnumValue objects
|
||||
*/
|
||||
const extensionNamesEnumProvider = () => extensionNames.map(name => {
|
||||
const isThirdParty = name.startsWith('third-party/');
|
||||
if (isThirdParty) name = name.slice('third-party/'.length);
|
||||
|
||||
const description = isThirdParty ? 'third party extension' : null;
|
||||
|
||||
return new SlashCommandEnumValue(name, description, !isThirdParty ? enumTypes.name : enumTypes.enum);
|
||||
});
|
||||
|
||||
export function registerExtensionSlashCommands() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'extension-enable',
|
||||
callback: getExtensionActionCallback('enable'),
|
||||
returns: 'The internal extension name',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'reload',
|
||||
description: 'Whether to reload the page after enabling the extension',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'true',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Extension name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: extensionNamesEnumProvider,
|
||||
forceEnum: true,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Enables a specified extension.
|
||||
</div>
|
||||
<div>
|
||||
By default, the page will be reloaded automatically, stopping any further commands.<br />
|
||||
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
|
||||
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/extension-enable Summarize</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'extension-disable',
|
||||
callback: getExtensionActionCallback('disable'),
|
||||
returns: 'The internal extension name',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'reload',
|
||||
description: 'Whether to reload the page after disabling the extension',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'true',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Extension name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: extensionNamesEnumProvider,
|
||||
forceEnum: true,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Disables a specified extension.
|
||||
</div>
|
||||
<div>
|
||||
By default, the page will be reloaded automatically, stopping any further commands.<br />
|
||||
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
|
||||
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/extension-disable Summarize</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'extension-toggle',
|
||||
callback: async (args, extensionName) => {
|
||||
if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.');
|
||||
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||
|
||||
const action = isTrueBoolean(args?.state) ? 'enable' :
|
||||
isFalseBoolean(args?.state) ? 'disable' :
|
||||
'toggle';
|
||||
|
||||
return await getExtensionActionCallback(action)(args, extensionName);
|
||||
},
|
||||
returns: 'The internal extension name',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'reload',
|
||||
description: 'Whether to reload the page after toggling the extension',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'true',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'state',
|
||||
description: 'Explicitly set the state of the extension (true to enable, false to disable). If not provided, the state will be toggled to the opposite of the current state.',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Extension name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: extensionNamesEnumProvider,
|
||||
forceEnum: true,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Toggles the state of a specified extension.
|
||||
</div>
|
||||
<div>
|
||||
By default, the page will be reloaded automatically, stopping any further commands.<br />
|
||||
If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay in its current state until refreshed.
|
||||
The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/extension-toggle Summarize</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'extension-state',
|
||||
callback: async (_, extensionName) => {
|
||||
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||
const internalExtensionName = findExtension(extensionName);
|
||||
if (!internalExtensionName) {
|
||||
toastr.warning(`Extension ${extensionName} does not exist.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||
return String(isEnabled);
|
||||
},
|
||||
returns: 'The state of the extension, whether it is enabled.',
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Extension name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: extensionNamesEnumProvider,
|
||||
forceEnum: true,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Returns the state of a specified extension (true if enabled, false if disabled).
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/extension-state Summarize</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'extension-exists',
|
||||
aliases: ['extension-installed'],
|
||||
callback: async (_, extensionName) => {
|
||||
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||
const exists = findExtension(extensionName) !== undefined;
|
||||
return exists ? 'true' : 'false';
|
||||
},
|
||||
returns: 'Whether the extension exists and is installed.',
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Extension name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
enumProvider: extensionNamesEnumProvider,
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Checks if a specified extension exists.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/extension-exists SillyTavern-LALib</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'reload-page',
|
||||
callback: async () => {
|
||||
toastr.info('Reloading the page...');
|
||||
location.reload();
|
||||
return '';
|
||||
},
|
||||
helpString: 'Reloads the current page. All further commands will not be processed.',
|
||||
}));
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { showLoader } from './loader.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
||||
import { isSubsetOf, setValueByPath } from './utils.js';
|
||||
@ -14,7 +14,9 @@ export {
|
||||
ModuleWorkerWrapper,
|
||||
};
|
||||
|
||||
/** @type {string[]} */
|
||||
export let extensionNames = [];
|
||||
|
||||
let manifests = {};
|
||||
const defaultUrl = 'http://localhost:5100';
|
||||
|
||||
@ -241,7 +243,7 @@ function onEnableExtensionClick() {
|
||||
enableExtension(name, false);
|
||||
}
|
||||
|
||||
async function enableExtension(name, reload = true) {
|
||||
export async function enableExtension(name, reload = true) {
|
||||
extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
|
||||
stateChanged = true;
|
||||
await saveSettings();
|
||||
@ -252,7 +254,7 @@ async function enableExtension(name, reload = true) {
|
||||
}
|
||||
}
|
||||
|
||||
async function disableExtension(name, reload = true) {
|
||||
export async function disableExtension(name, reload = true) {
|
||||
extension_settings.disabledExtensions.push(name);
|
||||
stateChanged = true;
|
||||
await saveSettings();
|
||||
@ -1041,7 +1043,9 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
|
||||
await installExtension(url);
|
||||
}
|
||||
|
||||
jQuery(async function () {
|
||||
|
||||
|
||||
export async function initExtensions() {
|
||||
await addExtensionsButtonAndMenu();
|
||||
$('#extensionsMenuButton').css('display', 'flex');
|
||||
|
||||
@ -1060,4 +1064,4 @@ jQuery(async function () {
|
||||
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
||||
*/
|
||||
$('#third_party_extension_button').on('click', () => openThirdPartyExtensionMenu());
|
||||
});
|
||||
}
|
||||
|
12
public/scripts/extensions/connection-manager/edit.html
Normal file
12
public/scripts/extensions/connection-manager/edit.html
Normal file
@ -0,0 +1,12 @@
|
||||
<div>
|
||||
<h3>Included settings:</h3>
|
||||
<div class="justifyLeft flex-container flexFlowColumn flexNoGap">
|
||||
{{#each settings}}
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" value="{{@key}}" name="exclude"{{#if this}} checked{{/if}}>
|
||||
<span>{{@key}}</span>
|
||||
</label>
|
||||
{{/each}}
|
||||
</div>
|
||||
<h3 data-i18n="Profile name:">Profile name:</h3>
|
||||
</div>
|
@ -1,6 +1,6 @@
|
||||
import { event_types, eventSource, main_api, saveSettingsDebounced } from '../../../script.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
||||
import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
|
||||
import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js';
|
||||
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
||||
import { SlashCommandAbortController } from '../../slash-commands/SlashCommandAbortController.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
||||
@ -136,6 +136,7 @@ const profilesProvider = () => [
|
||||
* @property {string} [context] Context Template
|
||||
* @property {string} [instruct-state] Instruct Mode
|
||||
* @property {string} [tokenizer] Tokenizer
|
||||
* @property {string[]} [exclude] Commands to exclude
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -172,8 +173,13 @@ function findProfileByName(value) {
|
||||
async function readProfileFromCommands(mode, profile, cleanUp = false) {
|
||||
const commands = mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
|
||||
const opposingCommands = mode === 'cc' ? TC_COMMANDS : CC_COMMANDS;
|
||||
const excludeList = Array.isArray(profile.exclude) ? profile.exclude : [];
|
||||
for (const command of commands) {
|
||||
try {
|
||||
if (excludeList.includes(command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const args = getNamedArguments();
|
||||
const result = await SlashCommandParser.commands[command].callback(args, '');
|
||||
if (result) {
|
||||
@ -209,15 +215,37 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
|
||||
async function createConnectionProfile(forceName = null) {
|
||||
const mode = main_api === 'openai' ? 'cc' : 'tc';
|
||||
const id = uuidv4();
|
||||
/** @type {ConnectionProfile} */
|
||||
const profile = {
|
||||
id,
|
||||
mode,
|
||||
exclude: [],
|
||||
};
|
||||
|
||||
await readProfileFromCommands(mode, profile);
|
||||
|
||||
const profileForDisplay = makeFancyProfile(profile);
|
||||
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay });
|
||||
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'profile', { profile: profileForDisplay }));
|
||||
template.find('input[name="exclude"]').on('input', function () {
|
||||
const fancyName = String($(this).val());
|
||||
const keyName = Object.entries(FANCY_NAMES).find(x => x[1] === fancyName)?.[0];
|
||||
if (!keyName) {
|
||||
console.warn('Key not found for fancy name:', fancyName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(profile.exclude)) {
|
||||
profile.exclude = [];
|
||||
}
|
||||
|
||||
const excludeState = !$(this).prop('checked');
|
||||
if (excludeState) {
|
||||
profile.exclude.push(keyName);
|
||||
} else {
|
||||
const index = profile.exclude.indexOf(keyName);
|
||||
index !== -1 && profile.exclude.splice(index, 1);
|
||||
}
|
||||
});
|
||||
const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n);
|
||||
const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken);
|
||||
const name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 });
|
||||
@ -231,7 +259,13 @@ async function createConnectionProfile(forceName = null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
profile.name = name;
|
||||
if (Array.isArray(profile.exclude)) {
|
||||
for (const command of profile.exclude) {
|
||||
delete profile[command];
|
||||
}
|
||||
}
|
||||
|
||||
profile.name = String(name);
|
||||
return profile;
|
||||
}
|
||||
|
||||
@ -358,7 +392,11 @@ async function renderDetailsContent(detailsContent) {
|
||||
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
|
||||
if (profile) {
|
||||
const profileForDisplay = makeFancyProfile(profile);
|
||||
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', { profile: profileForDisplay });
|
||||
const templateParams = { profile: profileForDisplay };
|
||||
if (Array.isArray(profile.exclude) && profile.exclude.length > 0) {
|
||||
templateParams.omitted = profile.exclude.map(e => FANCY_NAMES[e]).join(', ');
|
||||
}
|
||||
const template = await renderExtensionTemplateAsync(MODULE_NAME, 'view', templateParams);
|
||||
detailsContent.innerHTML = template;
|
||||
} else {
|
||||
detailsContent.textContent = t`No profile selected`;
|
||||
@ -473,29 +511,71 @@ async function renderDetailsContent(detailsContent) {
|
||||
await eventSource.emit(event_types.CONNECTION_PROFILE_LOADED, NONE);
|
||||
});
|
||||
|
||||
const renameButton = document.getElementById('rename_connection_profile');
|
||||
renameButton.addEventListener('click', async () => {
|
||||
const editButton = document.getElementById('edit_connection_profile');
|
||||
editButton.addEventListener('click', async () => {
|
||||
const selectedProfile = extension_settings.connectionManager.selectedProfile;
|
||||
const profile = extension_settings.connectionManager.profiles.find(p => p.id === selectedProfile);
|
||||
if (!profile) {
|
||||
console.log('No profile selected');
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(profile.exclude)) {
|
||||
profile.exclude = [];
|
||||
}
|
||||
|
||||
let saveChanges = false;
|
||||
const sortByViewOrder = (a, b) => Object.keys(FANCY_NAMES).indexOf(a) - Object.keys(FANCY_NAMES).indexOf(b);
|
||||
const commands = profile.mode === 'cc' ? CC_COMMANDS : TC_COMMANDS;
|
||||
const settings = commands.slice().sort(sortByViewOrder).reduce((acc, command) => {
|
||||
const fancyName = FANCY_NAMES[command];
|
||||
acc[fancyName] = !profile.exclude.includes(command);
|
||||
return acc;
|
||||
}, {});
|
||||
const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings }));
|
||||
const newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, {
|
||||
customButtons: [{
|
||||
text: t`Save and Update`,
|
||||
classes: ['popup-button-ok'],
|
||||
result: POPUP_RESULT.AFFIRMATIVE,
|
||||
action: () => {
|
||||
saveChanges = true;
|
||||
},
|
||||
}],
|
||||
});
|
||||
|
||||
const newName = await Popup.show.input(t`Enter a new name`, null, profile.name, { rows: 2 });
|
||||
if (!newName) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (extension_settings.connectionManager.profiles.some(p => p.name === newName)) {
|
||||
if (profile.name !== newName && extension_settings.connectionManager.profiles.some(p => p.name === newName)) {
|
||||
toastr.error('A profile with the same name already exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
profile.name = newName;
|
||||
const newExcludeList = template.find('input[name="exclude"]:not(:checked)').map(function () {
|
||||
return Object.entries(FANCY_NAMES).find(x => x[1] === String($(this).val()))?.[0];
|
||||
}).get();
|
||||
|
||||
if (newExcludeList.length !== profile.exclude.length || !newExcludeList.every(e => profile.exclude.includes(e))) {
|
||||
profile.exclude = newExcludeList;
|
||||
for (const command of newExcludeList) {
|
||||
delete profile[command];
|
||||
}
|
||||
if (saveChanges) {
|
||||
await updateConnectionProfile(profile);
|
||||
} else {
|
||||
toastr.info('Press "Update" to record them into the profile.', 'Included settings list updated');
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.name !== newName) {
|
||||
toastr.success('Connection profile renamed.');
|
||||
profile.name = String(newName);
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
renderConnectionProfiles(profiles);
|
||||
toastr.success('Connection profile renamed', '', { timeOut: 1500 });
|
||||
await renderDetailsContent(detailsContent);
|
||||
});
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
|
@ -2,11 +2,20 @@
|
||||
<h2 data-i18n="Creating a Connection Profile">
|
||||
Creating a Connection Profile
|
||||
</h2>
|
||||
<ul class="justifyLeft">
|
||||
<div class="justifyLeft flex-container flexFlowColumn flexNoGap">
|
||||
{{#each profile}}
|
||||
<li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li>
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" value="{{@key}}" name="exclude" checked>
|
||||
<span><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</span>
|
||||
</label>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="marginTop5">
|
||||
<small>
|
||||
<b>Hint:</b>
|
||||
<i>Click on the setting name to omit it from the profile.</i>
|
||||
</small>
|
||||
</div>
|
||||
<h3 data-i18n="Enter a name:">
|
||||
Enter a name:
|
||||
</h3>
|
||||
|
@ -13,7 +13,7 @@
|
||||
<i id="view_connection_profile" class="menu_button fa-solid fa-info-circle" title="View connection profile details" data-i18n="[title]View connection profile details"></i>
|
||||
<i id="create_connection_profile" class="menu_button fa-solid fa-file-circle-plus" title="Create a new connection profile" data-i18n="[title]Create a new connection profile"></i>
|
||||
<i id="update_connection_profile" class="menu_button fa-solid fa-save" title="Update a connection profile" data-i18n="[title]Update a connection profile"></i>
|
||||
<i id="rename_connection_profile" class="menu_button fa-solid fa-pencil" title="Rename a connection profile" data-i18n="[title]Rename a connection profile"></i>
|
||||
<i id="edit_connection_profile" class="menu_button fa-solid fa-pencil" title="Edit a connection profile" data-i18n="[title]Edit a connection profile"></i>
|
||||
<i id="reload_connection_profile" class="menu_button fa-solid fa-recycle" title="Reload a connection profile" data-i18n="[title]Reload a connection profile"></i>
|
||||
<i id="delete_connection_profile" class="menu_button fa-solid fa-trash-can" title="Delete a connection profile" data-i18n="[title]Delete a connection profile"></i>
|
||||
</div>
|
||||
|
@ -3,3 +3,8 @@
|
||||
<li><strong data-i18n="{{@key}}">{{@key}}:</strong> {{this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#if omitted}}
|
||||
<div class="margin5">
|
||||
<strong data-i18n="Omitted Settings:">Omitted Settings:</strong> <span>{{omitted}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
@ -26,9 +26,11 @@ let paginationVisiblePages = 10;
|
||||
let paginationMaxLinesPerPage = 2;
|
||||
let galleryMaxRows = 3;
|
||||
|
||||
$('body').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id'); // Get the ID of the related draggable
|
||||
$(`body > .draggable[id="${relatedId}"]`).remove(); // Remove the associated draggable
|
||||
// Remove all draggables associated with the gallery
|
||||
$('#movingDivs').on('click', '.dragClose', function () {
|
||||
const relatedId = $(this).data('related-id');
|
||||
if (!relatedId) return;
|
||||
$(`#movingDivs > .draggable[id="${relatedId}"]`).remove();
|
||||
});
|
||||
|
||||
const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
|
||||
@ -290,7 +292,7 @@ function makeMovable(id = 'gallery') {
|
||||
|
||||
$('#dragGallery').css('display', 'block');
|
||||
|
||||
$('body').append(newElement);
|
||||
$('#movingDivs').append(newElement);
|
||||
|
||||
loadMovingUIState();
|
||||
$(`.draggable[forChar="${id}"]`).css('display', 'block');
|
||||
@ -362,8 +364,8 @@ function makeDragImg(id, url) {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Attach it to the body
|
||||
document.body.appendChild(newElement);
|
||||
// Step 3: Attach it to the movingDivs container
|
||||
document.getElementById('movingDivs').appendChild(newElement);
|
||||
|
||||
// Step 4: Call dragElement and loadMovingUIState
|
||||
const appendedElement = document.getElementById(uniqueId);
|
||||
|
@ -350,6 +350,7 @@
|
||||
}
|
||||
.popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
width: unset;
|
||||
}
|
||||
.popup:has(#qr--modalEditor):has(.qr--isExecuting.qr--minimized) {
|
||||
min-width: unset;
|
||||
|
@ -415,6 +415,7 @@
|
||||
|
||||
.popup:has(#qr--modalEditor) {
|
||||
aspect-ratio: unset;
|
||||
width: unset;
|
||||
|
||||
&:has(.qr--isExecuting.qr--minimized) {
|
||||
min-width: unset;
|
||||
|
206
public/scripts/extensions/tts/cosyvoice.js
Normal file
206
public/scripts/extensions/tts/cosyvoice.js
Normal file
@ -0,0 +1,206 @@
|
||||
import { saveTtsProviderSettings } from './index.js';
|
||||
|
||||
export { CosyVoiceProvider };
|
||||
|
||||
class CosyVoiceProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings;
|
||||
ready = false;
|
||||
voices = [];
|
||||
separator = '. ';
|
||||
audioElement = document.createElement('audio');
|
||||
|
||||
/**
|
||||
* Perform any text processing before passing to TTS engine.
|
||||
* @param {string} text Input text
|
||||
* @returns {string} Processed text
|
||||
*/
|
||||
processText(text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac'];
|
||||
|
||||
languageLabels = {
|
||||
'Auto': 'auto',
|
||||
};
|
||||
|
||||
langKey2LangCode = {
|
||||
'zh': 'zh-CN',
|
||||
'en': 'en-US',
|
||||
'ja': 'ja-JP',
|
||||
'ko': 'ko-KR',
|
||||
};
|
||||
|
||||
modelTypes = {
|
||||
CosyVoice: 'CosyVoice',
|
||||
};
|
||||
|
||||
defaultSettings = {
|
||||
provider_endpoint: 'http://localhost:9880',
|
||||
format: 'wav',
|
||||
lang: 'auto',
|
||||
streaming: false,
|
||||
|
||||
};
|
||||
|
||||
get settingsHtml() {
|
||||
let html = `
|
||||
|
||||
<label for="tts_endpoint">Provider Endpoint:</label>
|
||||
<input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/>
|
||||
<span>Windows users Use <a target="_blank" href="https://github.com/v3ucn/CosyVoice_For_Windows">CosyVoice_For_Windows</a>(Unofficial).</span><br/>
|
||||
<span>Macos Users Use <a target="_blank" href="https://github.com/v3ucn/CosyVoice_for_MacOs">CosyVoice_for_MacOs</a>(Unofficial).</span><br/>
|
||||
<br/>
|
||||
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
// Used when provider settings are updated from UI
|
||||
this.settings.provider_endpoint = $('#tts_endpoint').val();
|
||||
|
||||
|
||||
saveTtsProviderSettings();
|
||||
this.changeTTSSettings();
|
||||
}
|
||||
|
||||
async loadSettings(settings) {
|
||||
// Pupulate 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 {
|
||||
console.debug(`Ignoring non-user-configurable setting: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial values from the settings
|
||||
$('#tts_endpoint').val(this.settings.provider_endpoint);
|
||||
|
||||
|
||||
await this.checkReady();
|
||||
|
||||
console.info('ITS: Settings loaded');
|
||||
}
|
||||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady() {
|
||||
await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
|
||||
}
|
||||
|
||||
async onRefreshClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
//#################//
|
||||
// TTS Interfaces //
|
||||
//#################//
|
||||
|
||||
async getVoice(voiceName) {
|
||||
|
||||
|
||||
|
||||
if (this.voices.length == 0) {
|
||||
this.voices = await this.fetchTtsVoiceObjects();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const match = this.voices.filter(
|
||||
v => v.name == voiceName,
|
||||
)[0];
|
||||
console.log(match);
|
||||
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() {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/speakers`);
|
||||
console.info(response);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
|
||||
|
||||
this.voices = responseJson;
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
// Each time a parameter is changed, we change the configuration
|
||||
async changeTTSSettings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch TTS generation from the API.
|
||||
* @param {string} inputText Text to generate TTS for
|
||||
* @param {string} voiceId Voice ID to use (model_type&speaker_id))
|
||||
* @returns {Promise<Response|string>} Fetch response
|
||||
*/
|
||||
async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
|
||||
console.info(`Generating new TTS for voice_id ${voiceId}`);
|
||||
|
||||
const streaming = this.settings.streaming;
|
||||
|
||||
const params = {
|
||||
text: inputText,
|
||||
speaker: voiceId,
|
||||
};
|
||||
|
||||
if (streaming) {
|
||||
params['streaming'] = 1;
|
||||
}
|
||||
|
||||
const url = `${this.settings.provider_endpoint}/`;
|
||||
|
||||
const response = await fetch(
|
||||
url,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(params), // Convert parameter objects to JSON strings
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Interface not used
|
||||
async fetchTtsFromHistory(history_item_id) {
|
||||
return Promise.resolve(history_item_id);
|
||||
}
|
||||
}
|
226
public/scripts/extensions/tts/gpt-sovits-v2.js
Normal file
226
public/scripts/extensions/tts/gpt-sovits-v2.js
Normal file
@ -0,0 +1,226 @@
|
||||
import { saveTtsProviderSettings } from './index.js';
|
||||
|
||||
export { GptSovitsV2Provider };
|
||||
|
||||
class GptSovitsV2Provider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings;
|
||||
ready = false;
|
||||
voices = [];
|
||||
separator = '. ';
|
||||
audioElement = document.createElement('audio');
|
||||
|
||||
/**
|
||||
* Perform any text processing before passing to TTS engine.
|
||||
* @param {string} text Input text
|
||||
* @returns {string} Processed text
|
||||
*/
|
||||
processText(text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac'];
|
||||
|
||||
languageLabels = {
|
||||
'Auto': 'auto',
|
||||
};
|
||||
|
||||
langKey2LangCode = {
|
||||
'zh': 'zh-CN',
|
||||
'en': 'en-US',
|
||||
'ja': 'ja-JP',
|
||||
'ko': 'ko-KR',
|
||||
};
|
||||
|
||||
|
||||
defaultSettings = {
|
||||
provider_endpoint: 'http://localhost:9880',
|
||||
format: 'wav',
|
||||
lang: 'auto',
|
||||
streaming: false,
|
||||
text_lang: 'zh',
|
||||
prompt_lang: 'zh',
|
||||
|
||||
};
|
||||
|
||||
get settingsHtml() {
|
||||
let html = `
|
||||
|
||||
<label for="tts_endpoint">Provider Endpoint:</label>
|
||||
<input id="tts_endpoint" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.provider_endpoint}"/>
|
||||
<span>Use <a target="_blank" href="https://github.com/v3ucn/GPT-SoVITS-V2">GPT-SoVITS-V2</a>(Unofficial).</span><br/>
|
||||
<label for="text_lang">Text Lang(Inference text language):</label>
|
||||
<input id="text_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.text_lang}"/>
|
||||
<label for="text_lang">Prompt Lang(Reference audio text language):</label>
|
||||
<input id="prompt_lang" type="text" class="text_pole" maxlength="250" height="300" value="${this.defaultSettings.prompt_lang}"/>
|
||||
<br/>
|
||||
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
// Used when provider settings are updated from UI
|
||||
this.settings.provider_endpoint = $('#tts_endpoint').val();
|
||||
this.settings.text_lang = $('#text_lang').val();
|
||||
this.settings.prompt_lang = $('#prompt_lang').val();
|
||||
|
||||
|
||||
saveTtsProviderSettings();
|
||||
this.changeTTSSettings();
|
||||
}
|
||||
|
||||
async loadSettings(settings) {
|
||||
// Pupulate 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 {
|
||||
console.debug(`Ignoring non-user-configurable setting: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial values from the settings
|
||||
$('#tts_endpoint').val(this.settings.provider_endpoint);
|
||||
$('#text_lang').val(this.settings.text_lang);
|
||||
$('#prompt_lang').val(this.settings.prompt_lang);
|
||||
|
||||
|
||||
await this.checkReady();
|
||||
|
||||
console.info('ITS: Settings loaded');
|
||||
}
|
||||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady() {
|
||||
await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
|
||||
}
|
||||
|
||||
async onRefreshClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
//#################//
|
||||
// TTS Interfaces //
|
||||
//#################//
|
||||
|
||||
async getVoice(voiceName) {
|
||||
|
||||
|
||||
|
||||
if (this.voices.length == 0) {
|
||||
this.voices = await this.fetchTtsVoiceObjects();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const match = this.voices.filter(
|
||||
v => v.name == voiceName,
|
||||
)[0];
|
||||
console.log(match);
|
||||
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() {
|
||||
const response = await fetch(`${this.settings.provider_endpoint}/speakers`);
|
||||
console.info(response);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
|
||||
}
|
||||
const responseJson = await response.json();
|
||||
|
||||
|
||||
this.voices = responseJson;
|
||||
|
||||
return responseJson;
|
||||
}
|
||||
|
||||
// Each time a parameter is changed, we change the configuration
|
||||
async changeTTSSettings() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch TTS generation from the API.
|
||||
* @param {string} inputText Text to generate TTS for
|
||||
* @param {string} voiceId Voice ID to use (model_type&speaker_id))
|
||||
* @returns {Promise<Response|string>} Fetch response
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
|
||||
console.info(`Generating new TTS for voice_id ${voiceId}`);
|
||||
|
||||
function replaceSpeaker(text) {
|
||||
return text.replace(/\[.*?\]/gu, '');
|
||||
}
|
||||
|
||||
let prompt_text = replaceSpeaker(voiceId);
|
||||
|
||||
const streaming = this.settings.streaming;
|
||||
|
||||
const params = {
|
||||
text: inputText,
|
||||
prompt_text: prompt_text,
|
||||
ref_audio_path: './参考音频/' + voiceId + '.wav',
|
||||
text_lang: this.settings.text_lang,
|
||||
prompt_lang: this.settings.prompt_lang,
|
||||
text_split_method: 'cut5',
|
||||
batch_size: 1,
|
||||
media_type: 'ogg',
|
||||
streaming_mode: 'true',
|
||||
};
|
||||
|
||||
|
||||
const url = `${this.settings.provider_endpoint}/`;
|
||||
|
||||
const response = await fetch(
|
||||
url,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(params), // Convert parameter objects to JSON strings
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Interface not used
|
||||
async fetchTtsFromHistory(history_item_id) {
|
||||
return Promise.resolve(history_item_id);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '.
|
||||
import { EdgeTtsProvider } from './edge.js';
|
||||
import { ElevenLabsTtsProvider } from './elevenlabs.js';
|
||||
import { SileroTtsProvider } from './silerotts.js';
|
||||
import { GptSovitsV2Provider } from './gpt-sovits-v2.js';
|
||||
import { CoquiTtsProvider } from './coqui.js';
|
||||
import { SystemTtsProvider } from './system.js';
|
||||
import { NovelTtsProvider } from './novel.js';
|
||||
@ -15,6 +16,7 @@ import { VITSTtsProvider } from './vits.js';
|
||||
import { GSVITtsProvider } from './gsvi.js';
|
||||
import { SBVits2TtsProvider } from './sbvits2.js';
|
||||
import { AllTalkTtsProvider } from './alltalk.js';
|
||||
import { CosyVoiceProvider } from './cosyvoice.js';
|
||||
import { SpeechT5TtsProvider } from './speecht5.js';
|
||||
import { AzureTtsProvider } from './azure.js';
|
||||
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
||||
@ -86,9 +88,11 @@ const ttsProviders = {
|
||||
AllTalk: AllTalkTtsProvider,
|
||||
Azure: AzureTtsProvider,
|
||||
Coqui: CoquiTtsProvider,
|
||||
'CosyVoice (Unofficial)': CosyVoiceProvider,
|
||||
Edge: EdgeTtsProvider,
|
||||
ElevenLabs: ElevenLabsTtsProvider,
|
||||
GSVI: GSVITtsProvider,
|
||||
'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider,
|
||||
Novel: NovelTtsProvider,
|
||||
OpenAI: OpenAITtsProvider,
|
||||
'OpenAI Compatible': OpenAICompatibleTtsProvider,
|
||||
|
@ -74,7 +74,7 @@ const samplers = {
|
||||
|
||||
let novel_data = null;
|
||||
let badWordsCache = {};
|
||||
const BIAS_KEY = '#novel_api-settings';
|
||||
const BIAS_KEY = '#range_block_novel';
|
||||
|
||||
export function setNovelData(data) {
|
||||
novel_data = data;
|
||||
@ -492,11 +492,37 @@ function getBadWordPermutations(text) {
|
||||
|
||||
export function getNovelGenerationData(finalPrompt, settings, maxLength, isImpersonate, isContinue, _cfgValues, type) {
|
||||
console.debug('NovelAI generation data for', type);
|
||||
const isKayra = nai_settings.model_novel.includes('kayra');
|
||||
const isErato = nai_settings.model_novel.includes('erato');
|
||||
|
||||
const tokenizerType = getTokenizerTypeForModel(nai_settings.model_novel);
|
||||
const stoppingStrings = getStoppingStrings(isImpersonate, isContinue);
|
||||
|
||||
// Llama 3 tokenizer, huh?
|
||||
if (isErato) {
|
||||
const additionalStopStrings = [];
|
||||
for (const stoppingString of stoppingStrings) {
|
||||
if (stoppingString.startsWith('\n')) {
|
||||
additionalStopStrings.push('.' + stoppingString);
|
||||
additionalStopStrings.push('!' + stoppingString);
|
||||
additionalStopStrings.push('?' + stoppingString);
|
||||
additionalStopStrings.push('*' + stoppingString);
|
||||
additionalStopStrings.push('"' + stoppingString);
|
||||
additionalStopStrings.push('_' + stoppingString);
|
||||
additionalStopStrings.push('...' + stoppingString);
|
||||
additionalStopStrings.push('."' + stoppingString);
|
||||
additionalStopStrings.push('?"' + stoppingString);
|
||||
additionalStopStrings.push('!"' + stoppingString);
|
||||
additionalStopStrings.push('.*' + stoppingString);
|
||||
additionalStopStrings.push(')' + stoppingString);
|
||||
}
|
||||
}
|
||||
stoppingStrings.push(...additionalStopStrings);
|
||||
}
|
||||
|
||||
const MAX_STOP_SEQUENCES = 1024;
|
||||
const stopSequences = (tokenizerType !== tokenizers.NONE)
|
||||
? getStoppingStrings(isImpersonate, isContinue)
|
||||
.map(t => getTextTokens(tokenizerType, t))
|
||||
? stoppingStrings.slice(0, MAX_STOP_SEQUENCES).map(t => getTextTokens(tokenizerType, t))
|
||||
: undefined;
|
||||
|
||||
const badWordIds = (tokenizerType !== tokenizers.NONE)
|
||||
@ -515,11 +541,9 @@ export function getNovelGenerationData(finalPrompt, settings, maxLength, isImper
|
||||
console.log(finalPrompt);
|
||||
}
|
||||
|
||||
const isKayra = nai_settings.model_novel.includes('kayra');
|
||||
const isErato = nai_settings.model_novel.includes('erato');
|
||||
|
||||
if (isErato) {
|
||||
finalPrompt = '<|startoftext|>' + finalPrompt;
|
||||
finalPrompt = '<|startoftext|><|reserved_special_token81|>' + finalPrompt;
|
||||
}
|
||||
|
||||
const adjustedMaxLength = (isKayra || isErato) ? getKayraMaxResponseTokens() : maximum_output_length;
|
||||
|
@ -3469,7 +3469,7 @@ function getModelOptions(quiet) {
|
||||
case 'openai':
|
||||
return oai_settings.chat_completion_source;
|
||||
default:
|
||||
return nullResult;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ function selectSystemPromptCallback(args, name) {
|
||||
foundName = result[0].item;
|
||||
}
|
||||
|
||||
$select.val(foundName).trigger('input');
|
||||
$select.val(foundName).trigger('change');
|
||||
!quiet && toastr.success(`System prompt "${foundName}" selected`);
|
||||
return foundName;
|
||||
}
|
||||
|
@ -68,6 +68,10 @@ const LLAMACPP_DEFAULT_ORDER = [
|
||||
'temperature',
|
||||
];
|
||||
const OOBA_DEFAULT_ORDER = [
|
||||
'repetition_penalty',
|
||||
'presence_penalty',
|
||||
'frequency_penalty',
|
||||
'dry',
|
||||
'temperature',
|
||||
'dynamic_temperature',
|
||||
'quadratic_sampling',
|
||||
@ -80,6 +84,9 @@ const OOBA_DEFAULT_ORDER = [
|
||||
'top_a',
|
||||
'min_p',
|
||||
'mirostat',
|
||||
'xtc',
|
||||
'encoder_repetition_penalty',
|
||||
'no_repeat_ngram',
|
||||
];
|
||||
const BIAS_KEY = '#textgenerationwebui_api-settings';
|
||||
|
||||
@ -798,6 +805,25 @@ function showTypeSpecificControls(type) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts missing items from the source array into the target array.
|
||||
* @param {any[]} source - Source array
|
||||
* @param {any[]} target - Target array
|
||||
* @returns {void}
|
||||
*/
|
||||
function insertMissingArrayItems(source, target) {
|
||||
if (source === target || !Array.isArray(source) || !Array.isArray(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const item of source) {
|
||||
if (!target.includes(item)) {
|
||||
const index = source.indexOf(item);
|
||||
target.splice(index, 0, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSettingByName(setting, value, trigger) {
|
||||
if (value === null || value === undefined) {
|
||||
return;
|
||||
@ -812,6 +838,7 @@ function setSettingByName(setting, value, trigger) {
|
||||
|
||||
if ('sampler_priority' === setting) {
|
||||
value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER;
|
||||
insertMissingArrayItems(OOBA_DEFAULT_ORDER, value);
|
||||
sortOobaItemsByOrder(value);
|
||||
settings.sampler_priority = value;
|
||||
return;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js';
|
||||
import { extension_settings, saveMetadataDebounced } from './extensions.js';
|
||||
import { callGenericPopup, POPUP_TYPE } from './popup.js';
|
||||
import { executeSlashCommandsWithOptions } from './slash-commands.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';
|
||||
@ -11,7 +12,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom
|
||||
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
||||
import { isFalseBoolean, convertValueType } from './utils.js';
|
||||
import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
|
||||
|
||||
/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */
|
||||
/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */
|
||||
@ -303,24 +304,48 @@ export function replaceVariableMacros(input) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function listVariablesCallback() {
|
||||
async function listVariablesCallback(args) {
|
||||
const type = String(args?.format || '').toLowerCase().trim() || 'popup';
|
||||
const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
|
||||
if (!chat_metadata.variables) {
|
||||
chat_metadata.variables = {};
|
||||
}
|
||||
|
||||
const localVariables = Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`);
|
||||
const globalVariables = Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`);
|
||||
const includeLocalVariables = scope === 'all' || scope === 'local';
|
||||
const includeGlobalVariables = scope === 'all' || scope === 'global';
|
||||
|
||||
const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
|
||||
const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
|
||||
|
||||
const jsonVariables = [
|
||||
...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
|
||||
...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
|
||||
];
|
||||
|
||||
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
|
||||
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
|
||||
const chatName = getCurrentChatId();
|
||||
|
||||
const converter = new showdown.Converter();
|
||||
const message = `### Local variables (${chatName}):\n${localVariablesString}\n\n### Global variables:\n${globalVariablesString}`;
|
||||
const message = [
|
||||
includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
|
||||
includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
|
||||
].filter(x => x).join('\n\n');
|
||||
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
|
||||
|
||||
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
|
||||
return '';
|
||||
switch (type) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'chat':
|
||||
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
|
||||
break;
|
||||
case 'popup':
|
||||
default:
|
||||
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
|
||||
break;
|
||||
}
|
||||
|
||||
return JSON.stringify(jsonVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -463,7 +488,7 @@ function existsGlobalVariable(name) {
|
||||
/**
|
||||
* Parses boolean operands from command arguments.
|
||||
* @param {object} args Command arguments
|
||||
* @returns {{a: string | number, b: string | number, rule: string}} Boolean operands
|
||||
* @returns {{a: string | number, b: string | number?, rule: string}} Boolean operands
|
||||
*/
|
||||
export function parseBooleanOperands(args) {
|
||||
// Resolution order: numeric literal, local variable, global variable, string literal
|
||||
@ -472,6 +497,9 @@ export function parseBooleanOperands(args) {
|
||||
*/
|
||||
function getOperand(operand) {
|
||||
if (operand === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (operand === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -500,8 +528,8 @@ export function parseBooleanOperands(args) {
|
||||
return stringLiteral || '';
|
||||
}
|
||||
|
||||
const left = getOperand(args.a || args.left || args.first || args.x);
|
||||
const right = getOperand(args.b || args.right || args.second || args.y);
|
||||
const left = getOperand(args.a ?? args.left ?? args.first ?? args.x);
|
||||
const right = getOperand(args.b ?? args.right ?? args.second ?? args.y);
|
||||
const rule = args.rule;
|
||||
|
||||
return { a: left, b: right, rule };
|
||||
@ -509,84 +537,79 @@ export function parseBooleanOperands(args) {
|
||||
|
||||
/**
|
||||
* Evaluates a boolean comparison rule.
|
||||
* @param {string} rule Boolean comparison rule
|
||||
*
|
||||
* @param {string?} rule Boolean comparison rule
|
||||
* @param {string|number} a The left operand
|
||||
* @param {string|number} b The right operand
|
||||
* @param {string|number?} b The right operand
|
||||
* @returns {boolean} True if the rule yields true, false otherwise
|
||||
*/
|
||||
export function evalBoolean(rule, a, b) {
|
||||
if (!rule) {
|
||||
toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command');
|
||||
throw new Error('Invalid command.');
|
||||
if (a === undefined) {
|
||||
throw new Error('Left operand is not provided');
|
||||
}
|
||||
|
||||
let result = false;
|
||||
// If right-hand side was not provided, whe just check if the left side is truthy
|
||||
if (b === undefined) {
|
||||
switch (rule) {
|
||||
case undefined:
|
||||
case 'not': {
|
||||
const resultOnTruthy = rule !== 'not';
|
||||
if (isTrueBoolean(String(a))) return resultOnTruthy;
|
||||
if (isFalseBoolean(String(a))) return !resultOnTruthy;
|
||||
return a ? resultOnTruthy : !resultOnTruthy;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown boolean comparison rule for truthy check. If right operand is not provided, the rule must not provided or be 'not'. Provided: ${rule}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If no rule was provided, we are implicitly using 'eq', as defined for the slash commands
|
||||
rule ??= 'eq';
|
||||
|
||||
if (typeof a === 'number' && typeof b === 'number') {
|
||||
// only do numeric comparison if both operands are numbers
|
||||
const aNumber = Number(a);
|
||||
const bNumber = Number(b);
|
||||
|
||||
switch (rule) {
|
||||
case 'not':
|
||||
result = !aNumber;
|
||||
break;
|
||||
case 'gt':
|
||||
result = aNumber > bNumber;
|
||||
break;
|
||||
return aNumber > bNumber;
|
||||
case 'gte':
|
||||
result = aNumber >= bNumber;
|
||||
break;
|
||||
return aNumber >= bNumber;
|
||||
case 'lt':
|
||||
result = aNumber < bNumber;
|
||||
break;
|
||||
return aNumber < bNumber;
|
||||
case 'lte':
|
||||
result = aNumber <= bNumber;
|
||||
break;
|
||||
return aNumber <= bNumber;
|
||||
case 'eq':
|
||||
result = aNumber === bNumber;
|
||||
break;
|
||||
return aNumber === bNumber;
|
||||
case 'neq':
|
||||
result = aNumber !== bNumber;
|
||||
break;
|
||||
default:
|
||||
toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command');
|
||||
throw new Error('Invalid command.');
|
||||
}
|
||||
} else {
|
||||
// otherwise do case-insensitive string comparsion, stringify non-strings
|
||||
let aString;
|
||||
let bString;
|
||||
if (typeof a == 'string') {
|
||||
aString = a.toLowerCase();
|
||||
} else {
|
||||
aString = JSON.stringify(a).toLowerCase();
|
||||
}
|
||||
if (typeof b == 'string') {
|
||||
bString = b.toLowerCase();
|
||||
} else {
|
||||
bString = JSON.stringify(b).toLowerCase();
|
||||
}
|
||||
|
||||
switch (rule) {
|
||||
return aNumber !== bNumber;
|
||||
case 'in':
|
||||
result = aString.includes(bString);
|
||||
break;
|
||||
case 'nin':
|
||||
result = !aString.includes(bString);
|
||||
break;
|
||||
case 'eq':
|
||||
result = aString === bString;
|
||||
break;
|
||||
case 'neq':
|
||||
result = aString !== bString;
|
||||
// Fall through to string comparison. Otherwise you could not check if 12345 contains 45 for example.
|
||||
console.debug(`Boolean comparison rule '${rule}' is not supported for type number. Falling back to string comparison.`);
|
||||
break;
|
||||
default:
|
||||
toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command');
|
||||
throw new Error('Invalid command.');
|
||||
throw new Error(`Unknown boolean comparison rule for type number. Accepted: gt, gte, lt, lte, eq, neq. Provided: ${rule}`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
// otherwise do case-insensitive string comparsion, stringify non-strings
|
||||
let aString = (typeof a === 'string') ? a.toLowerCase() : JSON.stringify(a).toLowerCase();
|
||||
let bString = (typeof b === 'string') ? b.toLowerCase() : JSON.stringify(b).toLowerCase();
|
||||
|
||||
switch (rule) {
|
||||
case 'in':
|
||||
return aString.includes(bString);
|
||||
case 'nin':
|
||||
return !aString.includes(bString);
|
||||
case 'eq':
|
||||
return aString === bString;
|
||||
case 'neq':
|
||||
return aString !== bString;
|
||||
default:
|
||||
throw new Error(`Unknown boolean comparison rule for type number. Accepted: in, nin, eq, neq. Provided: ${rule}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -887,7 +910,35 @@ export function registerVariableCommands() {
|
||||
name: 'listvar',
|
||||
callback: listVariablesCallback,
|
||||
aliases: ['listchatvar'],
|
||||
helpString: 'List registered chat variables.',
|
||||
helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>format</code> argument to change the output format.',
|
||||
returns: 'JSON list of local variables',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'scope',
|
||||
description: 'filter variables by scope',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'all',
|
||||
isRequired: false,
|
||||
forceEnum: true,
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('all', 'All variables', enumTypes.enum, enumIcons.variable),
|
||||
new SlashCommandEnumValue('local', 'Local variables', enumTypes.enum, enumIcons.localVariable),
|
||||
new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable),
|
||||
],
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'format',
|
||||
description: 'output format',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
isRequired: true,
|
||||
forceEnum: true,
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('popup', 'Show variables in a popup.', enumTypes.enum, enumIcons.default),
|
||||
new SlashCommandEnumValue('chat', 'Post a system message to the chat.', enumTypes.enum, enumIcons.message),
|
||||
new SlashCommandEnumValue('none', 'Just return the variables as a JSON list.', enumTypes.enum, enumIcons.array),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'setvar',
|
||||
@ -1264,32 +1315,36 @@ export function registerVariableCommands() {
|
||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.variables('all'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'right',
|
||||
description: 'right operand',
|
||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.variables('all'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [
|
||||
new SlashCommandEnumValue('gt', 'a > b'),
|
||||
new SlashCommandEnumValue('gte', 'a >= b'),
|
||||
new SlashCommandEnumValue('lt', 'a < b'),
|
||||
new SlashCommandEnumValue('lte', 'a <= b'),
|
||||
new SlashCommandEnumValue('eq', 'a == b'),
|
||||
new SlashCommandEnumValue('neq', 'a !== b'),
|
||||
new SlashCommandEnumValue('not', '!a'),
|
||||
new SlashCommandEnumValue('in', 'a includes b'),
|
||||
new SlashCommandEnumValue('nin', 'a not includes b'),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'rule',
|
||||
description: 'comparison rule',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'eq',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'),
|
||||
new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'),
|
||||
new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'),
|
||||
new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'),
|
||||
new SlashCommandEnumValue('gt', 'a > b (numbers)'),
|
||||
new SlashCommandEnumValue('gte', 'a >= b (numbers)'),
|
||||
new SlashCommandEnumValue('lt', 'a < b (numbers)'),
|
||||
new SlashCommandEnumValue('lte', 'a <= b (numbers)'),
|
||||
new SlashCommandEnumValue('not', '!a (truthy)'),
|
||||
],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false,
|
||||
),
|
||||
forceEnum: true,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'else',
|
||||
description: 'command to execute if not true',
|
||||
typeList: [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND],
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -1306,18 +1361,26 @@ export function registerVariableCommands() {
|
||||
<div>
|
||||
Numeric values and string literals for left and right operands supported.
|
||||
</div>
|
||||
<div>
|
||||
If the rule is not provided, it defaults to <code>eq</code>.
|
||||
</div>
|
||||
<div>
|
||||
If no right operand is provided, it defaults to checking the <code>left</code> value to be truthy.
|
||||
A non-empty string or non-zero number is considered truthy, as is the value <code>true</code> or <code>on</code>.<br />
|
||||
Only acceptable rules for no provided right operand are <code>not</code>, and no provided rule - which default to returning whether it is not or is truthy.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Available rules:</strong>
|
||||
<ul>
|
||||
<li>gt => a > b</li>
|
||||
<li>gte => a >= b</li>
|
||||
<li>lt => a < b</li>
|
||||
<li>lte => a <= b</li>
|
||||
<li>eq => a == b</li>
|
||||
<li>neq => a != b</li>
|
||||
<li>not => !a</li>
|
||||
<li>in (strings) => a includes b</li>
|
||||
<li>nin (strings) => a not includes b</li>
|
||||
<li><code>eq</code> => a == b <small>(strings & numbers)</small></li>
|
||||
<li><code>neq</code> => a !== b <small>(strings & numbers)</small></li>
|
||||
<li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li>
|
||||
<li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li>
|
||||
<li><code>gt</code> => a > b <small>(numbers)</small></li>
|
||||
<li><code>gte</code> => a >= b <small>(numbers)</small></li>
|
||||
<li><code>lt</code> => a < b <small>(numbers)</small></li>
|
||||
<li><code>lte</code> => a <= b <small>(numbers)</small></li>
|
||||
<li><code>not</code> => !a <small>(truthy)</small></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
@ -1327,6 +1390,17 @@ export function registerVariableCommands() {
|
||||
<pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre>
|
||||
triggers a /speak command if the value of "score" is greater or equals 10.
|
||||
</li>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/if left={{lastMessage}} rule=in right=surprise {: /echo SURPISE! :}</code></pre>
|
||||
executes a subcommand defined as a closure if the given value contains a specified word.
|
||||
<li>
|
||||
<pre><code class="language-stscript">/if left=myContent {: /echo My content had some content. :}</code></pre>
|
||||
executes the defined subcommand, if the provided value of left is truthy (contains some kind of contant that is not empty or false)
|
||||
</li>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/if left=tree right={{getvar::object}} {: /echo The object is a tree! :}</code></pre>
|
||||
executes the defined subcommand, if the left and right values are equals.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
@ -1342,32 +1416,38 @@ export function registerVariableCommands() {
|
||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.variables('all'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'right',
|
||||
description: 'right operand',
|
||||
typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER],
|
||||
isRequired: true,
|
||||
enumProvider: commonEnumProviders.variables('all'),
|
||||
forceEnum: false,
|
||||
}),
|
||||
new SlashCommandNamedArgument(
|
||||
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [
|
||||
new SlashCommandEnumValue('gt', 'a > b'),
|
||||
new SlashCommandEnumValue('gte', 'a >= b'),
|
||||
new SlashCommandEnumValue('lt', 'a < b'),
|
||||
new SlashCommandEnumValue('lte', 'a <= b'),
|
||||
new SlashCommandEnumValue('eq', 'a == b'),
|
||||
new SlashCommandEnumValue('neq', 'a !== b'),
|
||||
new SlashCommandEnumValue('not', '!a'),
|
||||
new SlashCommandEnumValue('in', 'a includes b'),
|
||||
new SlashCommandEnumValue('nin', 'a not includes b'),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'rule',
|
||||
description: 'comparison rule',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'eq',
|
||||
enumList: [
|
||||
new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'),
|
||||
new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'),
|
||||
new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'),
|
||||
new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'),
|
||||
new SlashCommandEnumValue('gt', 'a > b (numbers)'),
|
||||
new SlashCommandEnumValue('gte', 'a >= b (numbers)'),
|
||||
new SlashCommandEnumValue('lt', 'a < b (numbers)'),
|
||||
new SlashCommandEnumValue('lte', 'a <= b (numbers)'),
|
||||
new SlashCommandEnumValue('not', '!a (truthy)'),
|
||||
],
|
||||
),
|
||||
new SlashCommandNamedArgument(
|
||||
'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(),
|
||||
),
|
||||
forceEnum: true,
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'guard',
|
||||
description: 'disable loop iteration limit',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
defaultValue: 'off',
|
||||
enumList: commonEnumProviders.boolean('onOff')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
@ -1386,15 +1466,15 @@ export function registerVariableCommands() {
|
||||
<div>
|
||||
<strong>Available rules:</strong>
|
||||
<ul>
|
||||
<li>gt => a > b</li>
|
||||
<li>gte => a >= b</li>
|
||||
<li>lt => a < b</li>
|
||||
<li>lte => a <= b</li>
|
||||
<li>eq => a == b</li>
|
||||
<li>neq => a != b</li>
|
||||
<li>not => !a</li>
|
||||
<li>in (strings) => a includes b</li>
|
||||
<li>nin (strings) => a not includes b</li>
|
||||
<li><code>eq</code> => a == b <small>(strings & numbers)</small></li>
|
||||
<li><code>neq</code> => a !== b <small>(strings & numbers)</small></li>
|
||||
<li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li>
|
||||
<li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li>
|
||||
<li><code>gt</code> => a > b <small>(numbers)</small></li>
|
||||
<li><code>gte</code> => a >= b <small>(numbers)</small></li>
|
||||
<li><code>lt</code> => a < b <small>(numbers)</small></li>
|
||||
<li><code>lte</code> => a <= b <small>(numbers)</small></li>
|
||||
<li><code>not</code> => !a <small>(truthy)</small></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
@ -1404,7 +1484,11 @@ export function registerVariableCommands() {
|
||||
<pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre>
|
||||
adds 1 to the value of "i" until it reaches 10.
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/while left={{getvar::currentword}} {: /setvar key=currentword {: /do-something-and-return :}() | /echo The current work is "{{getvar::currentword}}" :}</code></pre>
|
||||
executes the defined subcommand as long as the "currentword" variable is truthy (has any content that is not false/empty)
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
<div>
|
||||
Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable.
|
||||
@ -1519,7 +1603,7 @@ export function registerVariableCommands() {
|
||||
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
|
||||
isRequired: true,
|
||||
acceptsMultiple: true,
|
||||
enumProvider: (executor, scope)=>{
|
||||
enumProvider: (executor, scope) => {
|
||||
const vars = commonEnumProviders.variables('all')(executor, scope);
|
||||
vars.push(
|
||||
new SlashCommandEnumValue(
|
||||
@ -1527,16 +1611,16 @@ export function registerVariableCommands() {
|
||||
null,
|
||||
enumTypes.variable,
|
||||
enumIcons.variable,
|
||||
(input)=>/^\w*$/.test(input),
|
||||
(input)=>input,
|
||||
(input) => /^\w*$/.test(input),
|
||||
(input) => input,
|
||||
),
|
||||
new SlashCommandEnumValue(
|
||||
'any number',
|
||||
null,
|
||||
enumTypes.number,
|
||||
enumIcons.number,
|
||||
(input)=>input == '' || !Number.isNaN(Number(input)),
|
||||
(input)=>input,
|
||||
(input) => input == '' || !Number.isNaN(Number(input)),
|
||||
(input) => input,
|
||||
),
|
||||
);
|
||||
return vars;
|
||||
|
@ -3154,6 +3154,26 @@ grammarly-extension {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alternate_greeting details {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.alternate_greeting summary {
|
||||
list-style-position: outside;
|
||||
margin-left: 1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.alternate_greeting textarea {
|
||||
field-sizing: content;
|
||||
max-height: 50dvh;
|
||||
}
|
||||
|
||||
.alternate_greeting summary::marker,
|
||||
.alternate_greeting summary strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#rm_characters_block .form_create_bottom_buttons_block {
|
||||
justify-content: space-evenly !important;
|
||||
flex-grow: 0;
|
||||
@ -3245,8 +3265,9 @@ grammarly-extension {
|
||||
}
|
||||
|
||||
.wide_dialogue_popup {
|
||||
aspect-ratio: 1 / 1;
|
||||
width: unset !important;
|
||||
/* FIXME: Chrome 129 broke max-height for aspect-ratio sized elements */
|
||||
/* aspect-ratio: 1 / 1; */
|
||||
/* width: unset !important; */
|
||||
min-width: var(--sheldWidth);
|
||||
}
|
||||
|
||||
@ -5365,13 +5386,6 @@ body:not(.movingUI) .drawer-content.maximized {
|
||||
|
||||
/* Jank mobile support for gallery and future draggables */
|
||||
@media screen and (max-width: 1000px) {
|
||||
#gallery {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
|
@ -347,6 +347,8 @@ async function migrateSystemPrompts() {
|
||||
if (fs.existsSync(migrateMarker)) {
|
||||
continue;
|
||||
}
|
||||
const backupsPath = path.join(directory.backups, '_sysprompt');
|
||||
fs.mkdirSync(backupsPath, { recursive: true });
|
||||
const defaultPrompts = await getDefaultSystemPrompts();
|
||||
const instucts = fs.readdirSync(directory.instruct);
|
||||
let migratedPrompts = [];
|
||||
@ -356,6 +358,8 @@ async function migrateSystemPrompts() {
|
||||
if (path.extname(instruct) === '.json' && !fs.existsSync(sysPromptPath)) {
|
||||
const instructData = JSON.parse(fs.readFileSync(instructPath, 'utf8'));
|
||||
if ('system_prompt' in instructData && 'name' in instructData) {
|
||||
const backupPath = path.join(backupsPath, `${instructData.name}.json`);
|
||||
fs.cpSync(instructPath, backupPath, { force: true });
|
||||
const syspromptData = { name: instructData.name, content: instructData.system_prompt };
|
||||
migratedPrompts.push(syspromptData);
|
||||
delete instructData.system_prompt;
|
||||
@ -374,8 +378,8 @@ async function migrateSystemPrompts() {
|
||||
console.log(`Migrated system prompt ${sysPromptData.name} for ${directory.root.split(path.sep).pop()}`);
|
||||
}
|
||||
writeFileAtomicSync(migrateMarker, '');
|
||||
} catch {
|
||||
// Ignore errors
|
||||
} catch (error) {
|
||||
console.error('Error migrating system prompts:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user