Move default slash commands to respective module (#4240)
* Move default slash commands to respective module * Update public/scripts/slash-commands.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
+5
-559
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
showdown,
|
||||
moment,
|
||||
Fuse,
|
||||
DOMPurify,
|
||||
hljs,
|
||||
Handlebars,
|
||||
@@ -89,7 +88,6 @@ import {
|
||||
sortEntitiesList,
|
||||
registerDebugFunction,
|
||||
flushEphemeralStoppingStrings,
|
||||
context_presets,
|
||||
resetMovableStyles,
|
||||
forceCharacterEditorTokenize,
|
||||
applyPowerUserSettings,
|
||||
@@ -167,7 +165,6 @@ import {
|
||||
isValidUrl,
|
||||
ensureImageFormatSupported,
|
||||
flashHighlight,
|
||||
isTrueBoolean,
|
||||
toggleDrawer,
|
||||
isElementInViewport,
|
||||
copyText,
|
||||
@@ -182,7 +179,7 @@ import {
|
||||
import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js';
|
||||
|
||||
import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
|
||||
import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';
|
||||
import { COMMENT_NAME_DEFAULT, CONNECT_API_MAP, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution, UNIQUE_APIS } from './scripts/slash-commands.js';
|
||||
import {
|
||||
tag_map,
|
||||
tags,
|
||||
@@ -223,9 +220,6 @@ import {
|
||||
formatInstructModeExamples,
|
||||
getInstructStoppingSequences,
|
||||
formatInstructModeSystemPrompt,
|
||||
selectInstructPreset,
|
||||
instruct_presets,
|
||||
selectContextPreset,
|
||||
} from './scripts/instruct-mode.js';
|
||||
import { initLocales, t } from './scripts/i18n.js';
|
||||
import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache } from './scripts/tokenizers.js';
|
||||
@@ -251,16 +245,11 @@ import { currentUser, setUserControls } from './scripts/user.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
|
||||
import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
|
||||
import { initScrapers } from './scripts/scrapers.js';
|
||||
import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandBrowser } from './scripts/slash-commands/SlashCommandBrowser.js';
|
||||
import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/samplerSelect.js';
|
||||
import { DragAndDropHandler } from './scripts/dragdrop.js';
|
||||
import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js';
|
||||
import { initDynamicStyles } from './scripts/dynamic-styles.js';
|
||||
import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js';
|
||||
import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { initInputMarkdown } from './scripts/input-md-formatting.js';
|
||||
import { AbortReason } from './scripts/util/AbortReason.js';
|
||||
import { initSystemPrompts } from './scripts/sysprompt.js';
|
||||
@@ -306,6 +295,8 @@ export {
|
||||
koboldai_setting_names,
|
||||
novelai_settings,
|
||||
novelai_setting_names,
|
||||
UNIQUE_APIS,
|
||||
CONNECT_API_MAP,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -5257,7 +5248,7 @@ function addChatsSeparator(mesSendString) {
|
||||
}
|
||||
}
|
||||
|
||||
async function duplicateCharacter() {
|
||||
export async function duplicateCharacter() {
|
||||
if (this_chid === undefined || !characters[this_chid]) {
|
||||
toastr.warning(t`You must first select a character to duplicate!`);
|
||||
return '';
|
||||
@@ -7385,7 +7376,7 @@ export async function getPastCharacterChats(characterId = null) {
|
||||
/**
|
||||
* Helper for `displayPastChats`, to make the same info consistently available for other functions
|
||||
*/
|
||||
function getCurrentChatDetails() {
|
||||
export function getCurrentChatDetails() {
|
||||
if (!characters[this_chid] && !selected_group) {
|
||||
return { sessionName: '', group: null, characterName: '', avatarImgURL: '' };
|
||||
}
|
||||
@@ -8930,219 +8921,6 @@ export function swipe_right(_event = null, { source, repeated } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} ConnectAPIMap
|
||||
* @property {string} selected - API name (e.g. "textgenerationwebui", "openai")
|
||||
* @property {string?} [button] - CSS selector for the API button
|
||||
* @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter")
|
||||
* @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai")
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Record<string, ConnectAPIMap>}
|
||||
*/
|
||||
export const CONNECT_API_MAP = {
|
||||
// Default APIs not contined inside text gen / chat gen
|
||||
'kobold': {
|
||||
selected: 'kobold',
|
||||
button: '#api_button',
|
||||
},
|
||||
'horde': {
|
||||
selected: 'koboldhorde',
|
||||
},
|
||||
'novel': {
|
||||
selected: 'novel',
|
||||
button: '#api_button_novel',
|
||||
},
|
||||
'koboldcpp': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.KOBOLDCPP,
|
||||
},
|
||||
// KoboldCpp alias
|
||||
'kcpp': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.KOBOLDCPP,
|
||||
},
|
||||
'openai': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENAI,
|
||||
},
|
||||
// OpenAI alias
|
||||
'oai': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENAI,
|
||||
},
|
||||
// Google alias
|
||||
'google': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.MAKERSUITE,
|
||||
},
|
||||
// OpenRouter special naming, to differentiate between chat comp and text comp
|
||||
'openrouter': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENROUTER,
|
||||
},
|
||||
'openrouter-text': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.OPENROUTER,
|
||||
},
|
||||
};
|
||||
|
||||
// Collect all unique API names in an array
|
||||
export const UNIQUE_APIS = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))];
|
||||
|
||||
// Fill connections map from textgen_types and chat_completion_sources
|
||||
for (const textGenType of Object.values(textgen_types)) {
|
||||
if (CONNECT_API_MAP[textGenType]) continue;
|
||||
CONNECT_API_MAP[textGenType] = {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textGenType,
|
||||
};
|
||||
}
|
||||
for (const chatCompletionSource of Object.values(chat_completion_sources)) {
|
||||
if (CONNECT_API_MAP[chatCompletionSource]) continue;
|
||||
CONNECT_API_MAP[chatCompletionSource] = {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chatCompletionSource,
|
||||
};
|
||||
}
|
||||
|
||||
async function selectContextCallback(args, name) {
|
||||
if (!name) {
|
||||
return power_user.context.preset;
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet);
|
||||
const contextNames = context_presets.map(preset => preset.name);
|
||||
const fuse = new Fuse(contextNames);
|
||||
const result = fuse.search(name);
|
||||
|
||||
if (result.length === 0) {
|
||||
!quiet && toastr.warning(t`Context template '${name}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const foundName = result[0].item;
|
||||
selectContextPreset(foundName, { quiet: quiet });
|
||||
return foundName;
|
||||
}
|
||||
|
||||
async function selectInstructCallback(args, name) {
|
||||
if (!name) {
|
||||
return power_user.instruct.enabled || isTrueBoolean(args?.forceGet) ? power_user.instruct.preset : '';
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet);
|
||||
const instructNames = instruct_presets.map(preset => preset.name);
|
||||
const fuse = new Fuse(instructNames);
|
||||
const result = fuse.search(name);
|
||||
|
||||
if (result.length === 0) {
|
||||
!quiet && toastr.warning(t`Instruct template '${name}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const foundName = result[0].item;
|
||||
selectInstructPreset(foundName, { quiet: quiet });
|
||||
return foundName;
|
||||
}
|
||||
|
||||
async function enableInstructCallback() {
|
||||
$('#instruct_enabled').prop('checked', true).trigger('input').trigger('change');
|
||||
return '';
|
||||
}
|
||||
|
||||
async function disableInstructCallback() {
|
||||
$('#instruct_enabled').prop('checked', false).trigger('input').trigger('change');
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text API name
|
||||
*/
|
||||
async function connectAPISlash(args, text) {
|
||||
if (!text.trim()) {
|
||||
for (const [key, config] of Object.entries(CONNECT_API_MAP)) {
|
||||
if (config.selected !== main_api) continue;
|
||||
|
||||
if (config.source) {
|
||||
if (oai_settings.chat_completion_source === config.source) {
|
||||
return key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.type) {
|
||||
if (textgen_settings.type === config.type) {
|
||||
return key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
console.error('FIXME: The current API is not in the API map');
|
||||
return '';
|
||||
}
|
||||
|
||||
const apiConfig = CONNECT_API_MAP[text.toLowerCase()];
|
||||
if (!apiConfig) {
|
||||
toastr.error(t`Error: ${text} is not a valid API`);
|
||||
return '';
|
||||
}
|
||||
|
||||
let connectionRequired = false;
|
||||
|
||||
if (main_api !== apiConfig.selected) {
|
||||
$(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);
|
||||
$('#main_api').trigger('change');
|
||||
connectionRequired = true;
|
||||
}
|
||||
|
||||
if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) {
|
||||
$(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true);
|
||||
$('#chat_completion_source').trigger('change');
|
||||
connectionRequired = true;
|
||||
}
|
||||
|
||||
if (apiConfig.type && textgen_settings.type !== apiConfig.type) {
|
||||
$(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true);
|
||||
$('#textgen_type').trigger('change');
|
||||
connectionRequired = true;
|
||||
}
|
||||
|
||||
if (connectionRequired && apiConfig.button) {
|
||||
$(apiConfig.button).trigger('click');
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet);
|
||||
const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`);
|
||||
|
||||
try {
|
||||
if (connectionRequired) {
|
||||
await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100);
|
||||
}
|
||||
console.log('Connection successful');
|
||||
} catch {
|
||||
console.log('Could not connect after 5 seconds, skipping.');
|
||||
}
|
||||
|
||||
toastr.clear(toast);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports supported files dropped into the app window.
|
||||
* @param {File[]} files Array of files to process
|
||||
@@ -9286,32 +9064,6 @@ async function importFromURL(items, files) {
|
||||
}
|
||||
}
|
||||
|
||||
async function doImpersonate(args, prompt) {
|
||||
const options = prompt?.trim() ? { quiet_prompt: prompt.trim(), quietToLoud: true } : {};
|
||||
const shouldAwait = isTrueBoolean(args?.await);
|
||||
const outerPromise = new Promise((outerResolve) => setTimeout(async () => {
|
||||
try {
|
||||
await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
|
||||
} catch {
|
||||
console.warn('Timeout waiting for generation unlock');
|
||||
toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Prevent generate recursion
|
||||
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
|
||||
|
||||
outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('impersonate', options)), 1)));
|
||||
}, 1));
|
||||
|
||||
if (shouldAwait) {
|
||||
const innerPromise = await outerPromise;
|
||||
await innerPromise;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export async function doNewChat({ deleteCurrentChat = false } = {}) {
|
||||
//Make a new chat for selected character
|
||||
if ((!selected_group && this_chid == undefined) || menu_type == 'create') {
|
||||
@@ -9346,53 +9098,6 @@ export async function doNewChat({ deleteCurrentChat = false } = {}) {
|
||||
|
||||
}
|
||||
|
||||
async function doDeleteChat() {
|
||||
return displayPastChats().then(() => new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
const timeOutId = setTimeout(() => {
|
||||
toastr.error(t`Chat deletion timed out. Please try again.`);
|
||||
setResolved();
|
||||
}, 5000);
|
||||
|
||||
const setResolved = () => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
[event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => {
|
||||
eventSource.removeListener(eventType, setResolved);
|
||||
});
|
||||
clearTimeout(timeOutId);
|
||||
resolve('');
|
||||
};
|
||||
|
||||
[event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => {
|
||||
eventSource.on(eventType, setResolved);
|
||||
});
|
||||
|
||||
const currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross');
|
||||
$(currentChatDeleteButton).trigger('click', { fromSlashCommand: true });
|
||||
}));
|
||||
}
|
||||
|
||||
async function doRenameChat(_, chatName) {
|
||||
if (!chatName) {
|
||||
toastr.warning(t`Name must be provided as an argument to rename this chat.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const currentChatName = getCurrentChatId();
|
||||
if (!currentChatName) {
|
||||
toastr.warning(t`No chat selected that can be renamed.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
await renameChat(currentChatName, chatName);
|
||||
|
||||
toastr.success(t`Successfully renamed chat to: ${chatName}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a group or character chat.
|
||||
* @param {object} param Parameters for renaming chat
|
||||
@@ -9506,12 +9211,6 @@ export async function updateRemoteChatName(characterId, newName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* /getchatname` slash command
|
||||
*/
|
||||
async function doGetChatName() {
|
||||
return getCurrentChatDetails().sessionName;
|
||||
}
|
||||
|
||||
function doCharListDisplaySwitch() {
|
||||
power_user.charListGrid = !power_user.charListGrid;
|
||||
@@ -9519,11 +9218,6 @@ function doCharListDisplaySwitch() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function doCloseChat() {
|
||||
$('#option_close_chat').trigger('click');
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to handle the deletion of a character, given a specific popup type and character ID.
|
||||
* If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function.
|
||||
@@ -9634,11 +9328,6 @@ export async function newAssistantChat({ temporary = false } = {}) {
|
||||
sendSystemMessage(system_message_types.ASSISTANT_NOTE);
|
||||
}
|
||||
|
||||
function doTogglePanels() {
|
||||
$('#option_settings').trigger('click');
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to open a navbar drawer when a drawer open button is clicked.
|
||||
* Handles click events on .drawer-opener elements.
|
||||
@@ -9776,249 +9465,6 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api
|
||||
|
||||
// MARK: DOM Handlers Start
|
||||
jQuery(async function () {
|
||||
async function doForceSave() {
|
||||
await saveSettings();
|
||||
await saveChatConditional();
|
||||
toastr.success('Chat and settings saved.');
|
||||
return '';
|
||||
}
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'dupe',
|
||||
callback: duplicateCharacter,
|
||||
helpString: 'Duplicates the currently selected character.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'api',
|
||||
callback: connectAPISlash,
|
||||
returns: 'the current API',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: 'Suppress the toast message on connection',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'API to connect to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
||||
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)),
|
||||
selected[0].toUpperCase() ?? enumIcons.default)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Connect to an API. If no argument is provided, it will return the currently connected API.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Available APIs:</strong>
|
||||
<pre><code>${Object.keys(CONNECT_API_MAP).join(', ')}</code></pre>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'impersonate',
|
||||
callback: doImpersonate,
|
||||
aliases: ['imp'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'await',
|
||||
'Whether to await for the triggered generation before continuing',
|
||||
[ARGUMENT_TYPE.BOOLEAN],
|
||||
false,
|
||||
false,
|
||||
'false',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'prompt', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Calls an impersonation response, with an optional additional prompt.
|
||||
</div>
|
||||
<div>
|
||||
If <code>await=true</code> named argument is passed, the command will wait for the impersonation to end before continuing.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/impersonate What is the meaning of life?</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'delchat',
|
||||
callback: doDeleteChat,
|
||||
helpString: 'Deletes the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'renamechat',
|
||||
callback: doRenameChat,
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'new chat name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
],
|
||||
helpString: 'Renames the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'getchatname',
|
||||
callback: doGetChatName,
|
||||
returns: 'chat file name',
|
||||
helpString: 'Returns the name of the current chat file into the pipe.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'closechat',
|
||||
callback: doCloseChat,
|
||||
helpString: 'Closes the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'tempchat',
|
||||
callback: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventCallback = async (chatId) => {
|
||||
if (chatId) {
|
||||
return reject('Not in a temporary chat');
|
||||
}
|
||||
await newAssistantChat({ temporary: true });
|
||||
return resolve('');
|
||||
};
|
||||
eventSource.once(event_types.CHAT_CHANGED, eventCallback);
|
||||
doCloseChat();
|
||||
setTimeout(() => {
|
||||
reject('Failed to open temporary chat');
|
||||
eventSource.removeListener(event_types.CHAT_CHANGED, eventCallback);
|
||||
}, debounce_timeout.relaxed);
|
||||
});
|
||||
},
|
||||
helpString: 'Opens a temporary chat with Assistant.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'panels',
|
||||
callback: doTogglePanels,
|
||||
aliases: ['togglepanels'],
|
||||
helpString: 'Toggle UI panels on/off',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'forcesave',
|
||||
callback: doForceSave,
|
||||
helpString: 'Forces a save of the current chat and settings',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct',
|
||||
callback: selectInstructCallback,
|
||||
returns: 'current template',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: 'Suppress the toast message on template change',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'forceGet',
|
||||
description: 'Force getting a name even if instruct mode is disabled',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'instruct template name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Selects instruct mode template by name. Enables instruct mode if not already enabled.
|
||||
Gets the current instruct template if no name is provided and instruct mode is enabled or <code>forceGet=true</code> is passed.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/instruct creative</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct-on',
|
||||
callback: enableInstructCallback,
|
||||
helpString: 'Enables instruct mode.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct-off',
|
||||
callback: disableInstructCallback,
|
||||
helpString: 'Disables instruct mode',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct-state',
|
||||
aliases: ['instruct-toggle'],
|
||||
helpString: 'Gets the current instruct mode state. If an argument is provided, it will set the instruct mode state.',
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'instruct mode state',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
callback: async (_args, state) => {
|
||||
if (!state || typeof state !== 'string') {
|
||||
return String(power_user.instruct.enabled);
|
||||
}
|
||||
|
||||
const newState = isTrueBoolean(state);
|
||||
newState ? enableInstructCallback() : disableInstructCallback();
|
||||
return String(power_user.instruct.enabled);
|
||||
},
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'context',
|
||||
callback: selectContextCallback,
|
||||
returns: 'template name',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: 'Suppress the toast message on template change',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'context template name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: 'Selects context template by name. Gets the current template if no name is provided',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'chat-manager',
|
||||
callback: () => {
|
||||
$('#option_select_chat').trigger('click');
|
||||
return '';
|
||||
},
|
||||
aliases: ['chat-history', 'manage-chats'],
|
||||
helpString: 'Opens the chat manager for the current character/group.',
|
||||
}));
|
||||
|
||||
setTimeout(function () {
|
||||
$('#groupControlsToggle').trigger('click');
|
||||
$('#groupCurrentMemberListToggle .inline-drawer-icon').trigger('click');
|
||||
|
||||
@@ -471,7 +471,7 @@ export class ConnectionManagerRequestService {
|
||||
|
||||
/**
|
||||
* @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
|
||||
* @return {import('../../script.js').ConnectAPIMap}
|
||||
* @return {import('../slash-commands.js').ConnectAPIMap}
|
||||
* @throws {Error}
|
||||
*/
|
||||
static validateProfile(profile) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { copyText, flashHighlight } from './utils.js';
|
||||
|
||||
import {
|
||||
Generate,
|
||||
UNIQUE_APIS,
|
||||
activateSendButtons,
|
||||
addOneMessage,
|
||||
characters,
|
||||
@@ -13,6 +12,8 @@ import {
|
||||
deactivateSendButtons,
|
||||
default_avatar,
|
||||
deleteSwipe,
|
||||
displayPastChats,
|
||||
duplicateCharacter,
|
||||
eventSource,
|
||||
event_types,
|
||||
extension_prompt_roles,
|
||||
@@ -20,6 +21,8 @@ import {
|
||||
extractMessageBias,
|
||||
generateQuietPrompt,
|
||||
generateRaw,
|
||||
getCurrentChatDetails,
|
||||
getCurrentChatId,
|
||||
getFirstDisplayedMessageId,
|
||||
getThumbnailUrl,
|
||||
is_send_press,
|
||||
@@ -27,10 +30,14 @@ import {
|
||||
name1,
|
||||
name2,
|
||||
neutralCharacterName,
|
||||
newAssistantChat,
|
||||
online_status,
|
||||
reloadCurrentChat,
|
||||
removeMacros,
|
||||
renameCharacter,
|
||||
renameChat,
|
||||
saveChatConditional,
|
||||
saveSettings,
|
||||
saveSettingsDebounced,
|
||||
sendMessageAsUser,
|
||||
sendSystemMessage,
|
||||
@@ -56,7 +63,7 @@ import { getRegexedString, regex_placement } from './extensions/regex/engine.js'
|
||||
import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';
|
||||
import { chat_completion_sources, oai_settings, promptManager } from './openai.js';
|
||||
import { user_avatar } from './personas.js';
|
||||
import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||
import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, power_user } from './power-user.js';
|
||||
import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
|
||||
import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
|
||||
import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, regexFromString, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
|
||||
@@ -80,6 +87,8 @@ import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugC
|
||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
||||
import { t } from './i18n.js';
|
||||
import { kai_settings } from './kai-settings.js';
|
||||
import { instruct_presets, selectContextPreset, selectInstructPreset } from './instruct-mode.js';
|
||||
import { debounce_timeout } from './constants.js';
|
||||
export {
|
||||
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
|
||||
};
|
||||
@@ -110,7 +119,536 @@ function closureToFilter(closure) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} ConnectAPIMap
|
||||
* @property {string} selected - API name (e.g. "textgenerationwebui", "openai")
|
||||
* @property {string?} [button] - CSS selector for the API button
|
||||
* @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter")
|
||||
* @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai")
|
||||
*/
|
||||
|
||||
/** @type {Record<string, ConnectAPIMap>} */
|
||||
export const CONNECT_API_MAP = {};
|
||||
|
||||
/** @type {string[]} */
|
||||
export const UNIQUE_APIS = [];
|
||||
|
||||
function setupConnectAPIMap() {
|
||||
/** @type {Record<string, ConnectAPIMap>} */
|
||||
const result = {
|
||||
// Default APIs not contained inside text gen / chat gen
|
||||
'kobold': {
|
||||
selected: 'kobold',
|
||||
button: '#api_button',
|
||||
},
|
||||
'horde': {
|
||||
selected: 'koboldhorde',
|
||||
},
|
||||
'novel': {
|
||||
selected: 'novel',
|
||||
button: '#api_button_novel',
|
||||
},
|
||||
'koboldcpp': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.KOBOLDCPP,
|
||||
},
|
||||
// KoboldCpp alias
|
||||
'kcpp': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.KOBOLDCPP,
|
||||
},
|
||||
'openai': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENAI,
|
||||
},
|
||||
// OpenAI alias
|
||||
'oai': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENAI,
|
||||
},
|
||||
// Google alias
|
||||
'google': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.MAKERSUITE,
|
||||
},
|
||||
// OpenRouter special naming, to differentiate between chat comp and text comp
|
||||
'openrouter': {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chat_completion_sources.OPENROUTER,
|
||||
},
|
||||
'openrouter-text': {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textgen_types.OPENROUTER,
|
||||
},
|
||||
};
|
||||
|
||||
// Fill connections map from textgen_types and chat_completion_sources
|
||||
for (const textGenType of Object.values(textgen_types)) {
|
||||
if (result[textGenType]) continue;
|
||||
result[textGenType] = {
|
||||
selected: 'textgenerationwebui',
|
||||
button: '#api_button_textgenerationwebui',
|
||||
type: textGenType,
|
||||
};
|
||||
}
|
||||
|
||||
for (const chatCompletionSource of Object.values(chat_completion_sources)) {
|
||||
if (result[chatCompletionSource]) continue;
|
||||
result[chatCompletionSource] = {
|
||||
selected: 'openai',
|
||||
button: '#api_button_openai',
|
||||
source: chatCompletionSource,
|
||||
};
|
||||
}
|
||||
|
||||
Object.assign(CONNECT_API_MAP, result);
|
||||
UNIQUE_APIS.push(...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected)));
|
||||
}
|
||||
|
||||
export function initDefaultSlashCommands() {
|
||||
setupConnectAPIMap();
|
||||
|
||||
async function enableInstructCallback() {
|
||||
$('#instruct_enabled').prop('checked', true).trigger('input').trigger('change');
|
||||
return '';
|
||||
}
|
||||
|
||||
async function disableInstructCallback() {
|
||||
$('#instruct_enabled').prop('checked', false).trigger('input').trigger('change');
|
||||
return '';
|
||||
}
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'dupe',
|
||||
callback: duplicateCharacter,
|
||||
helpString: 'Duplicates the currently selected character.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'api',
|
||||
callback: async function (args, text) {
|
||||
if (!text?.toString()?.trim()) {
|
||||
for (const [key, config] of Object.entries(CONNECT_API_MAP)) {
|
||||
if (config.selected !== main_api) continue;
|
||||
|
||||
if (config.source) {
|
||||
if (oai_settings.chat_completion_source === config.source) {
|
||||
return key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.type) {
|
||||
if (textgenerationwebui_settings.type === config.type) {
|
||||
return key;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
console.error('FIXME: The current API is not in the API map');
|
||||
return '';
|
||||
}
|
||||
|
||||
const apiConfig = CONNECT_API_MAP[text?.toString()?.toLowerCase() ?? ''];
|
||||
if (!apiConfig) {
|
||||
toastr.error(t`Error: ${text} is not a valid API`);
|
||||
return '';
|
||||
}
|
||||
|
||||
let connectionRequired = false;
|
||||
|
||||
if (main_api !== apiConfig.selected) {
|
||||
$(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true);
|
||||
$('#main_api').trigger('change');
|
||||
connectionRequired = true;
|
||||
}
|
||||
|
||||
if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) {
|
||||
$(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true);
|
||||
$('#chat_completion_source').trigger('change');
|
||||
connectionRequired = true;
|
||||
}
|
||||
|
||||
if (apiConfig.type && textgenerationwebui_settings.type !== apiConfig.type) {
|
||||
$(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true);
|
||||
$('#textgen_type').trigger('change');
|
||||
connectionRequired = true;
|
||||
}
|
||||
|
||||
if (connectionRequired && apiConfig.button) {
|
||||
$(apiConfig.button).trigger('click');
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet?.toString());
|
||||
const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`);
|
||||
|
||||
try {
|
||||
if (connectionRequired) {
|
||||
await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100);
|
||||
}
|
||||
console.log('Connection successful');
|
||||
} catch {
|
||||
console.log('Could not connect after 5 seconds, skipping.');
|
||||
}
|
||||
|
||||
toastr.clear(toast);
|
||||
return text?.toString()?.trim() ?? '';
|
||||
},
|
||||
returns: 'the current API',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: 'Suppress the toast message on connection',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'API to connect to',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) =>
|
||||
new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)),
|
||||
selected[0].toUpperCase() ?? enumIcons.default)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Connect to an API. If no argument is provided, it will return the currently connected API.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Available APIs:</strong>
|
||||
<pre><code>${Object.keys(CONNECT_API_MAP).sort((a, b) => a.localeCompare(b)).join(', ')}</code></pre>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'impersonate',
|
||||
callback: async function (args, prompt) {
|
||||
const options = prompt?.toString()?.trim() ? { quiet_prompt: prompt.toString().trim(), quietToLoud: true } : {};
|
||||
const shouldAwait = isTrueBoolean(args?.await?.toString());
|
||||
const outerPromise = new Promise((outerResolve) => setTimeout(async () => {
|
||||
try {
|
||||
await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
|
||||
} catch {
|
||||
console.warn('Timeout waiting for generation unlock');
|
||||
toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Prevent generate recursion
|
||||
$('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
|
||||
|
||||
outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('impersonate', options)), 1)));
|
||||
}, 1));
|
||||
|
||||
if (shouldAwait) {
|
||||
const innerPromise = await outerPromise;
|
||||
await innerPromise;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
,
|
||||
aliases: ['imp'],
|
||||
namedArgumentList: [
|
||||
new SlashCommandNamedArgument(
|
||||
'await',
|
||||
'Whether to await for the triggered generation before continuing',
|
||||
[ARGUMENT_TYPE.BOOLEAN],
|
||||
false,
|
||||
false,
|
||||
'false',
|
||||
),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'prompt', [ARGUMENT_TYPE.STRING], false,
|
||||
),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Calls an impersonation response, with an optional additional prompt.
|
||||
</div>
|
||||
<div>
|
||||
If <code>await=true</code> named argument is passed, the command will wait for the impersonation to end before continuing.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/impersonate What is the meaning of life?</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'delchat',
|
||||
callback: async function () {
|
||||
return displayPastChats().then(() => new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
const timeOutId = setTimeout(() => {
|
||||
toastr.error(t`Chat deletion timed out. Please try again.`);
|
||||
setResolved();
|
||||
}, 5000);
|
||||
|
||||
const setResolved = () => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
resolved = true;
|
||||
[event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => {
|
||||
eventSource.removeListener(eventType, setResolved);
|
||||
});
|
||||
clearTimeout(timeOutId);
|
||||
resolve('');
|
||||
};
|
||||
|
||||
[event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => {
|
||||
eventSource.on(eventType, setResolved);
|
||||
});
|
||||
|
||||
const currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross');
|
||||
$(currentChatDeleteButton).trigger('click', { fromSlashCommand: true });
|
||||
}));
|
||||
},
|
||||
helpString: 'Deletes the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'renamechat',
|
||||
callback: async function doRenameChat(_, chatName) {
|
||||
if (!chatName) {
|
||||
toastr.warning(t`Name must be provided as an argument to rename this chat.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const currentChatName = getCurrentChatId();
|
||||
if (!currentChatName) {
|
||||
toastr.warning(t`No chat selected that can be renamed.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
await renameChat(currentChatName, chatName.toString());
|
||||
|
||||
toastr.success(t`Successfully renamed chat to: ${chatName}`);
|
||||
return '';
|
||||
},
|
||||
unnamedArgumentList: [
|
||||
new SlashCommandArgument(
|
||||
'new chat name', [ARGUMENT_TYPE.STRING], true,
|
||||
),
|
||||
],
|
||||
helpString: 'Renames the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'getchatname',
|
||||
callback: async function doGetChatName() {
|
||||
return getCurrentChatDetails().sessionName;
|
||||
},
|
||||
returns: 'chat file name',
|
||||
helpString: 'Returns the name of the current chat file into the pipe.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'closechat',
|
||||
callback: function () {
|
||||
$('#option_close_chat').trigger('click');
|
||||
return '';
|
||||
},
|
||||
helpString: 'Closes the current chat.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'tempchat',
|
||||
callback: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventCallback = async (chatId) => {
|
||||
if (chatId) {
|
||||
return reject('Not in a temporary chat');
|
||||
}
|
||||
await newAssistantChat({ temporary: true });
|
||||
return resolve('');
|
||||
};
|
||||
eventSource.once(event_types.CHAT_CHANGED, eventCallback);
|
||||
$('#option_close_chat').trigger('click');
|
||||
setTimeout(() => {
|
||||
reject('Failed to open temporary chat');
|
||||
eventSource.removeListener(event_types.CHAT_CHANGED, eventCallback);
|
||||
}, debounce_timeout.relaxed);
|
||||
});
|
||||
},
|
||||
helpString: 'Opens a temporary chat with Assistant.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'panels',
|
||||
callback: function () {
|
||||
$('#option_settings').trigger('click');
|
||||
return '';
|
||||
},
|
||||
aliases: ['togglepanels'],
|
||||
helpString: 'Toggle UI panels on/off',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'forcesave',
|
||||
callback: async function () {
|
||||
await saveSettings();
|
||||
await saveChatConditional();
|
||||
toastr.success('Chat and settings saved.');
|
||||
return '';
|
||||
},
|
||||
helpString: 'Forces a save of the current chat and settings',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct',
|
||||
callback: async function (args, name) {
|
||||
if (!name) {
|
||||
return power_user.instruct.enabled || isTrueBoolean(args?.forceGet?.toString()) ? power_user.instruct.preset : '';
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet?.toString());
|
||||
const instructNames = instruct_presets.map(preset => preset.name);
|
||||
const fuse = new Fuse(instructNames);
|
||||
const result = fuse.search(name?.toString() ?? '');
|
||||
|
||||
if (result.length === 0) {
|
||||
!quiet && toastr.warning(t`Instruct template '${name}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const foundName = result[0].item;
|
||||
selectInstructPreset(foundName, { quiet: quiet });
|
||||
return foundName;
|
||||
},
|
||||
returns: 'current template',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: 'Suppress the toast message on template change',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'forceGet',
|
||||
description: 'Force getting a name even if instruct mode is disabled',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'instruct template name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: `
|
||||
<div>
|
||||
Selects instruct mode template by name. Enables instruct mode if not already enabled.
|
||||
Gets the current instruct template if no name is provided and instruct mode is enabled or <code>forceGet=true</code> is passed.
|
||||
</div>
|
||||
<div>
|
||||
<strong>Example:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code class="language-stscript">/instruct creative</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct-on',
|
||||
callback: enableInstructCallback,
|
||||
helpString: 'Enables instruct mode.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct-off',
|
||||
callback: disableInstructCallback,
|
||||
helpString: 'Disables instruct mode',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'instruct-state',
|
||||
aliases: ['instruct-toggle'],
|
||||
helpString: 'Gets the current instruct mode state. If an argument is provided, it will set the instruct mode state.',
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'instruct mode state',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
callback: async (_args, state) => {
|
||||
if (!state || typeof state !== 'string') {
|
||||
return String(power_user.instruct.enabled);
|
||||
}
|
||||
|
||||
const newState = isTrueBoolean(state);
|
||||
newState ? enableInstructCallback() : disableInstructCallback();
|
||||
return String(power_user.instruct.enabled);
|
||||
},
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'context',
|
||||
callback: async function (args, name) {
|
||||
if (!name) {
|
||||
return power_user.context.preset;
|
||||
}
|
||||
|
||||
const quiet = isTrueBoolean(args?.quiet?.toString());
|
||||
const contextNames = context_presets.map(preset => preset.name);
|
||||
const fuse = new Fuse(contextNames);
|
||||
const result = fuse.search(name?.toString() ?? '');
|
||||
|
||||
if (result.length === 0) {
|
||||
!quiet && toastr.warning(t`Context template '${name}' not found`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const foundName = result[0].item;
|
||||
selectContextPreset(foundName, { quiet: quiet });
|
||||
return foundName;
|
||||
},
|
||||
returns: 'template name',
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'quiet',
|
||||
description: 'Suppress the toast message on template change',
|
||||
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||
defaultValue: 'false',
|
||||
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'context template name',
|
||||
typeList: [ARGUMENT_TYPE.STRING],
|
||||
enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)),
|
||||
}),
|
||||
],
|
||||
helpString: 'Selects context template by name. Gets the current template if no name is provided',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'chat-manager',
|
||||
callback: () => {
|
||||
$('#option_select_chat').trigger('click');
|
||||
return '';
|
||||
},
|
||||
aliases: ['chat-history', 'manage-chats'],
|
||||
helpString: 'Opens the chat manager for the current character/group.',
|
||||
}));
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: '?',
|
||||
callback: helpCommandCallback,
|
||||
@@ -3558,7 +4096,7 @@ async function countGroupMemberCallback() {
|
||||
return '';
|
||||
}
|
||||
|
||||
return getGroupMembers(selected_group).length;
|
||||
return String(getGroupMembers(selected_group).length);
|
||||
}
|
||||
|
||||
async function removeGroupMemberCallback(_, arg) {
|
||||
@@ -4684,18 +5222,21 @@ export function stopScriptExecution() {
|
||||
*/
|
||||
async function clearCommandProgress() {
|
||||
if (isExecutingCommandsFromChatInput) return;
|
||||
document.querySelector('#send_textarea').style.setProperty('--progDone', '1');
|
||||
const ta = document.getElementById('send_textarea');
|
||||
const fs = document.getElementById('form_sheld');
|
||||
if (!ta || !fs) return;
|
||||
ta.style.setProperty('--progDone', '1');
|
||||
await delay(250);
|
||||
if (isExecutingCommandsFromChatInput) return;
|
||||
document.querySelector('#send_textarea').style.transition = 'none';
|
||||
ta.style.transition = 'none';
|
||||
await delay(1);
|
||||
document.querySelector('#send_textarea').style.setProperty('--prog', '0%');
|
||||
document.querySelector('#send_textarea').style.setProperty('--progDone', '0');
|
||||
document.querySelector('#form_sheld').classList.remove('script_success');
|
||||
document.querySelector('#form_sheld').classList.remove('script_error');
|
||||
document.querySelector('#form_sheld').classList.remove('script_aborted');
|
||||
ta.style.setProperty('--prog', '0%');
|
||||
ta.style.setProperty('--progDone', '0');
|
||||
fs.classList.remove('script_success');
|
||||
fs.classList.remove('script_error');
|
||||
fs.classList.remove('script_aborted');
|
||||
await delay(1);
|
||||
document.querySelector('#send_textarea').style.transition = null;
|
||||
ta.style.transition = null;
|
||||
}
|
||||
/**
|
||||
* Debounced version of clearCommandProgress.
|
||||
@@ -4742,19 +5283,20 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) {
|
||||
commandsFromChatInputAbortController?.abort('processCommands was called');
|
||||
activateScriptButtons();
|
||||
|
||||
/**@type {HTMLTextAreaElement}*/
|
||||
/** @type {HTMLTextAreaElement} */
|
||||
const ta = document.querySelector('#send_textarea');
|
||||
const fs = document.querySelector('#form_sheld');
|
||||
|
||||
if (options.clearChatInput) {
|
||||
ta.value = '';
|
||||
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
document.querySelector('#send_textarea').style.setProperty('--prog', '0%');
|
||||
document.querySelector('#send_textarea').style.setProperty('--progDone', '0');
|
||||
document.querySelector('#form_sheld').classList.remove('script_success');
|
||||
document.querySelector('#form_sheld').classList.remove('script_error');
|
||||
document.querySelector('#form_sheld').classList.remove('script_aborted');
|
||||
ta.style.setProperty('--prog', '0%');
|
||||
ta.style.setProperty('--progDone', '0');
|
||||
fs.classList.remove('script_success');
|
||||
fs.classList.remove('script_error');
|
||||
fs.classList.remove('script_aborted');
|
||||
|
||||
/**@type {SlashCommandClosureResult} */
|
||||
let result = null;
|
||||
@@ -4905,7 +5447,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) {
|
||||
* @param {boolean} handleParserErrors Whether to handle parser errors (show toast on error) or throw
|
||||
* @param {SlashCommandScope} scope The scope to be used when executing the commands.
|
||||
* @param {boolean} handleExecutionErrors Whether to handle execution errors (show toast on error) or throw
|
||||
* @param {{[id:PARSER_FLAG]:boolean}} parserFlags Parser flags to apply
|
||||
* @param {{[id:import('./slash-commands/SlashCommandParser.js').PARSER_FLAG]:boolean}} parserFlags Parser flags to apply
|
||||
* @param {SlashCommandAbortController} abortController Controller used to abort or pause command execution
|
||||
* @param {(done:number, total:number)=>void} onProgress Callback to handle progress events
|
||||
* @returns {Promise<SlashCommandClosureResult>}
|
||||
|
||||
Reference in New Issue
Block a user