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>
Signed| @@ -1,7 +1,6 @@ | ||
| 1 | 1 | import { |
| 2 | 2 | showdown, |
| 3 | 3 | moment, |
| 4 | - Fuse, | |
| 5 | 4 | DOMPurify, |
| 6 | 5 | hljs, |
| 7 | 6 | Handlebars, |
| @@ -89,7 +88,6 @@ import { | ||
| 89 | 88 | sortEntitiesList, |
| 90 | 89 | registerDebugFunction, |
| 91 | 90 | flushEphemeralStoppingStrings, |
| 92 | - context_presets, | |
| 93 | 91 | resetMovableStyles, |
| 94 | 92 | forceCharacterEditorTokenize, |
| 95 | 93 | applyPowerUserSettings, |
| @@ -167,7 +165,6 @@ import { | ||
| 167 | 165 | isValidUrl, |
| 168 | 166 | ensureImageFormatSupported, |
| 169 | 167 | flashHighlight, |
| 170 | - isTrueBoolean, | |
| 171 | 168 | toggleDrawer, |
| 172 | 169 | isElementInViewport, |
| 173 | 170 | copyText, |
| @@ -182,7 +179,7 @@ import { | ||
| 182 | 179 | import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; |
| 183 | 180 | |
| 184 | 181 | import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; |
| 185 | 182 | import { COMMENT_NAME_DEFAULT, CONNECT_API_MAP, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution, UNIQUE_APIS } from './scripts/slash-commands.js'; |
| 186 | 183 | import { |
| 187 | 184 | tag_map, |
| 188 | 185 | tags, |
| @@ -223,9 +220,6 @@ import { | ||
| 223 | 220 | formatInstructModeExamples, |
| 224 | 221 | getInstructStoppingSequences, |
| 225 | 222 | formatInstructModeSystemPrompt, |
| 226 | - selectInstructPreset, | |
| 227 | - instruct_presets, | |
| 228 | - selectContextPreset, | |
| 229 | 223 | } from './scripts/instruct-mode.js'; |
| 230 | 224 | import { initLocales, t } from './scripts/i18n.js'; |
| 231 | 225 | import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache } from './scripts/tokenizers.js'; |
| @@ -251,16 +245,11 @@ import { currentUser, setUserControls } from './scripts/user.js'; | ||
| 251 | 245 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; |
| 252 | 246 | import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; |
| 253 | 247 | import { initScrapers } from './scripts/scrapers.js'; |
| 254 | -import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js'; | |
| 255 | -import { SlashCommand } from './scripts/slash-commands/SlashCommand.js'; | |
| 256 | -import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js'; | |
| 257 | 248 | import { SlashCommandBrowser } from './scripts/slash-commands/SlashCommandBrowser.js'; |
| 258 | 249 | import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/samplerSelect.js'; |
| 259 | 250 | import { DragAndDropHandler } from './scripts/dragdrop.js'; |
| 260 | 251 | import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js'; |
| 261 | 252 | import { initDynamicStyles } from './scripts/dynamic-styles.js'; |
| 262 | -import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; | |
| 263 | -import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; | |
| 264 | 253 | import { initInputMarkdown } from './scripts/input-md-formatting.js'; |
| 265 | 254 | import { AbortReason } from './scripts/util/AbortReason.js'; |
| 266 | 255 | import { initSystemPrompts } from './scripts/sysprompt.js'; |
| @@ -306,6 +295,8 @@ export { | ||
| 306 | 295 | koboldai_setting_names, |
| 307 | 296 | novelai_settings, |
| 308 | 297 | novelai_setting_names, |
| 298 | + UNIQUE_APIS, | |
| 299 | + CONNECT_API_MAP, | |
| 309 | 300 | }; |
| 310 | 301 | |
| 311 | 302 | /** |
| @@ -5257,7 +5248,7 @@ function addChatsSeparator(mesSendString) { | ||
| 5257 | 5248 | } |
| 5258 | 5249 | } |
| 5259 | 5250 | |
| 5260 | 5251 | export async function duplicateCharacter() { |
| 5261 | 5252 | if (this_chid === undefined || !characters[this_chid]) { |
| 5262 | 5253 | toastr.warning(t`You must first select a character to duplicate!`); |
| 5263 | 5254 | return ''; |
| @@ -7385,7 +7376,7 @@ export async function getPastCharacterChats(characterId = null) { | ||
| 7385 | 7376 | /** |
| 7386 | 7377 | * Helper for `displayPastChats`, to make the same info consistently available for other functions |
| 7387 | 7378 | */ |
| 7388 | 7379 | export function getCurrentChatDetails() { |
| 7389 | 7380 | if (!characters[this_chid] && !selected_group) { |
| 7390 | 7381 | return { sessionName: '', group: null, characterName: '', avatarImgURL: '' }; |
| 7391 | 7382 | } |
| @@ -8931,219 +8922,6 @@ export function swipe_right(_event = null, { source, repeated } = {}) { | ||
| 8931 | 8922 | } |
| 8932 | 8923 | |
| 8933 | 8924 | /** |
| 8934 | - * @typedef {object} ConnectAPIMap | |
| 8935 | - * @property {string} selected - API name (e.g. "textgenerationwebui", "openai") | |
| 8936 | - * @property {string?} [button] - CSS selector for the API button | |
| 8937 | - * @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter") | |
| 8938 | - * @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai") | |
| 8939 | - */ | |
| 8940 | - | |
| 8941 | -/** | |
| 8942 | - * @type {Record<string, ConnectAPIMap>} | |
| 8943 | - */ | |
| 8944 | -export const CONNECT_API_MAP = { | |
| 8945 | - // Default APIs not contined inside text gen / chat gen | |
| 8946 | - 'kobold': { | |
| 8947 | - selected: 'kobold', | |
| 8948 | - button: '#api_button', | |
| 8949 | - }, | |
| 8950 | - 'horde': { | |
| 8951 | - selected: 'koboldhorde', | |
| 8952 | - }, | |
| 8953 | - 'novel': { | |
| 8954 | - selected: 'novel', | |
| 8955 | - button: '#api_button_novel', | |
| 8956 | - }, | |
| 8957 | - 'koboldcpp': { | |
| 8958 | - selected: 'textgenerationwebui', | |
| 8959 | - button: '#api_button_textgenerationwebui', | |
| 8960 | - type: textgen_types.KOBOLDCPP, | |
| 8961 | - }, | |
| 8962 | - // KoboldCpp alias | |
| 8963 | - 'kcpp': { | |
| 8964 | - selected: 'textgenerationwebui', | |
| 8965 | - button: '#api_button_textgenerationwebui', | |
| 8966 | - type: textgen_types.KOBOLDCPP, | |
| 8967 | - }, | |
| 8968 | - 'openai': { | |
| 8969 | - selected: 'openai', | |
| 8970 | - button: '#api_button_openai', | |
| 8971 | - source: chat_completion_sources.OPENAI, | |
| 8972 | - }, | |
| 8973 | - // OpenAI alias | |
| 8974 | - 'oai': { | |
| 8975 | - selected: 'openai', | |
| 8976 | - button: '#api_button_openai', | |
| 8977 | - source: chat_completion_sources.OPENAI, | |
| 8978 | - }, | |
| 8979 | - // Google alias | |
| 8980 | - 'google': { | |
| 8981 | - selected: 'openai', | |
| 8982 | - button: '#api_button_openai', | |
| 8983 | - source: chat_completion_sources.MAKERSUITE, | |
| 8984 | - }, | |
| 8985 | - // OpenRouter special naming, to differentiate between chat comp and text comp | |
| 8986 | - 'openrouter': { | |
| 8987 | - selected: 'openai', | |
| 8988 | - button: '#api_button_openai', | |
| 8989 | - source: chat_completion_sources.OPENROUTER, | |
| 8990 | - }, | |
| 8991 | - 'openrouter-text': { | |
| 8992 | - selected: 'textgenerationwebui', | |
| 8993 | - button: '#api_button_textgenerationwebui', | |
| 8994 | - type: textgen_types.OPENROUTER, | |
| 8995 | - }, | |
| 8996 | -}; | |
| 8997 | - | |
| 8998 | -// Collect all unique API names in an array | |
| 8999 | -export const UNIQUE_APIS = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))]; | |
| 9000 | - | |
| 9001 | -// Fill connections map from textgen_types and chat_completion_sources | |
| 9002 | -for (const textGenType of Object.values(textgen_types)) { | |
| 9003 | - if (CONNECT_API_MAP[textGenType]) continue; | |
| 9004 | - CONNECT_API_MAP[textGenType] = { | |
| 9005 | - selected: 'textgenerationwebui', | |
| 9006 | - button: '#api_button_textgenerationwebui', | |
| 9007 | - type: textGenType, | |
| 9008 | - }; | |
| 9009 | -} | |
| 9010 | -for (const chatCompletionSource of Object.values(chat_completion_sources)) { | |
| 9011 | - if (CONNECT_API_MAP[chatCompletionSource]) continue; | |
| 9012 | - CONNECT_API_MAP[chatCompletionSource] = { | |
| 9013 | - selected: 'openai', | |
| 9014 | - button: '#api_button_openai', | |
| 9015 | - source: chatCompletionSource, | |
| 9016 | - }; | |
| 9017 | -} | |
| 9018 | - | |
| 9019 | -async function selectContextCallback(args, name) { | |
| 9020 | - if (!name) { | |
| 9021 | - return power_user.context.preset; | |
| 9022 | - } | |
| 9023 | - | |
| 9024 | - const quiet = isTrueBoolean(args?.quiet); | |
| 9025 | - const contextNames = context_presets.map(preset => preset.name); | |
| 9026 | - const fuse = new Fuse(contextNames); | |
| 9027 | - const result = fuse.search(name); | |
| 9028 | - | |
| 9029 | - if (result.length === 0) { | |
| 9030 | - !quiet && toastr.warning(t`Context template '${name}' not found`); | |
| 9031 | - return ''; | |
| 9032 | - } | |
| 9033 | - | |
| 9034 | - const foundName = result[0].item; | |
| 9035 | - selectContextPreset(foundName, { quiet: quiet }); | |
| 9036 | - return foundName; | |
| 9037 | -} | |
| 9038 | - | |
| 9039 | -async function selectInstructCallback(args, name) { | |
| 9040 | - if (!name) { | |
| 9041 | - return power_user.instruct.enabled || isTrueBoolean(args?.forceGet) ? power_user.instruct.preset : ''; | |
| 9042 | - } | |
| 9043 | - | |
| 9044 | - const quiet = isTrueBoolean(args?.quiet); | |
| 9045 | - const instructNames = instruct_presets.map(preset => preset.name); | |
| 9046 | - const fuse = new Fuse(instructNames); | |
| 9047 | - const result = fuse.search(name); | |
| 9048 | - | |
| 9049 | - if (result.length === 0) { | |
| 9050 | - !quiet && toastr.warning(t`Instruct template '${name}' not found`); | |
| 9051 | - return ''; | |
| 9052 | - } | |
| 9053 | - | |
| 9054 | - const foundName = result[0].item; | |
| 9055 | - selectInstructPreset(foundName, { quiet: quiet }); | |
| 9056 | - return foundName; | |
| 9057 | -} | |
| 9058 | - | |
| 9059 | -async function enableInstructCallback() { | |
| 9060 | - $('#instruct_enabled').prop('checked', true).trigger('input').trigger('change'); | |
| 9061 | - return ''; | |
| 9062 | -} | |
| 9063 | - | |
| 9064 | -async function disableInstructCallback() { | |
| 9065 | - $('#instruct_enabled').prop('checked', false).trigger('input').trigger('change'); | |
| 9066 | - return ''; | |
| 9067 | -} | |
| 9068 | - | |
| 9069 | -/** | |
| 9070 | - * @param {string} text API name | |
| 9071 | - */ | |
| 9072 | -async function connectAPISlash(args, text) { | |
| 9073 | - if (!text.trim()) { | |
| 9074 | - for (const [key, config] of Object.entries(CONNECT_API_MAP)) { | |
| 9075 | - if (config.selected !== main_api) continue; | |
| 9076 | - | |
| 9077 | - if (config.source) { | |
| 9078 | - if (oai_settings.chat_completion_source === config.source) { | |
| 9079 | - return key; | |
| 9080 | - } else { | |
| 9081 | - continue; | |
| 9082 | - } | |
| 9083 | - } | |
| 9084 | - | |
| 9085 | - if (config.type) { | |
| 9086 | - if (textgen_settings.type === config.type) { | |
| 9087 | - return key; | |
| 9088 | - } else { | |
| 9089 | - continue; | |
| 9090 | - } | |
| 9091 | - } | |
| 9092 | - | |
| 9093 | - return key; | |
| 9094 | - } | |
| 9095 | - | |
| 9096 | - console.error('FIXME: The current API is not in the API map'); | |
| 9097 | - return ''; | |
| 9098 | - } | |
| 9099 | - | |
| 9100 | - const apiConfig = CONNECT_API_MAP[text.toLowerCase()]; | |
| 9101 | - if (!apiConfig) { | |
| 9102 | - toastr.error(t`Error: ${text} is not a valid API`); | |
| 9103 | - return ''; | |
| 9104 | - } | |
| 9105 | - | |
| 9106 | - let connectionRequired = false; | |
| 9107 | - | |
| 9108 | - if (main_api !== apiConfig.selected) { | |
| 9109 | - $(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true); | |
| 9110 | - $('#main_api').trigger('change'); | |
| 9111 | - connectionRequired = true; | |
| 9112 | - } | |
| 9113 | - | |
| 9114 | - if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) { | |
| 9115 | - $(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true); | |
| 9116 | - $('#chat_completion_source').trigger('change'); | |
| 9117 | - connectionRequired = true; | |
| 9118 | - } | |
| 9119 | - | |
| 9120 | - if (apiConfig.type && textgen_settings.type !== apiConfig.type) { | |
| 9121 | - $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true); | |
| 9122 | - $('#textgen_type').trigger('change'); | |
| 9123 | - connectionRequired = true; | |
| 9124 | - } | |
| 9125 | - | |
| 9126 | - if (connectionRequired && apiConfig.button) { | |
| 9127 | - $(apiConfig.button).trigger('click'); | |
| 9128 | - } | |
| 9129 | - | |
| 9130 | - const quiet = isTrueBoolean(args?.quiet); | |
| 9131 | - const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`); | |
| 9132 | - | |
| 9133 | - try { | |
| 9134 | - if (connectionRequired) { | |
| 9135 | - await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); | |
| 9136 | - } | |
| 9137 | - console.log('Connection successful'); | |
| 9138 | - } catch { | |
| 9139 | - console.log('Could not connect after 5 seconds, skipping.'); | |
| 9140 | - } | |
| 9141 | - | |
| 9142 | - toastr.clear(toast); | |
| 9143 | - return text; | |
| 9144 | -} | |
| 9145 | - | |
| 9146 | -/** | |
| 9147 | 8925 | * Imports supported files dropped into the app window. |
| 9148 | 8926 | * @param {File[]} files Array of files to process |
| 9149 | 8927 | * @param {Map<File, string>} [data] Extra data to pass to the import function |
| @@ -9286,32 +9064,6 @@ async function importFromURL(items, files) { | ||
| 9286 | 9064 | } |
| 9287 | 9065 | } |
| 9288 | 9066 | |
| 9289 | -async function doImpersonate(args, prompt) { | |
| 9290 | - const options = prompt?.trim() ? { quiet_prompt: prompt.trim(), quietToLoud: true } : {}; | |
| 9291 | - const shouldAwait = isTrueBoolean(args?.await); | |
| 9292 | - const outerPromise = new Promise((outerResolve) => setTimeout(async () => { | |
| 9293 | - try { | |
| 9294 | - await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100); | |
| 9295 | - } catch { | |
| 9296 | - console.warn('Timeout waiting for generation unlock'); | |
| 9297 | - toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`); | |
| 9298 | - return ''; | |
| 9299 | - } | |
| 9300 | - | |
| 9301 | - // Prevent generate recursion | |
| 9302 | - $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true })); | |
| 9303 | - | |
| 9304 | - outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('impersonate', options)), 1))); | |
| 9305 | - }, 1)); | |
| 9306 | - | |
| 9307 | - if (shouldAwait) { | |
| 9308 | - const innerPromise = await outerPromise; | |
| 9309 | - await innerPromise; | |
| 9310 | - } | |
| 9311 | - | |
| 9312 | - return ''; | |
| 9313 | -} | |
| 9314 | - | |
| 9315 | 9067 | export async function doNewChat({ deleteCurrentChat = false } = {}) { |
| 9316 | 9068 | //Make a new chat for selected character |
| 9317 | 9069 | if ((!selected_group && this_chid == undefined) || menu_type == 'create') { |
| @@ -9346,53 +9098,6 @@ export async function doNewChat({ deleteCurrentChat = false } = {}) { | ||
| 9346 | 9098 | |
| 9347 | 9099 | } |
| 9348 | 9100 | |
| 9349 | -async function doDeleteChat() { | |
| 9350 | - return displayPastChats().then(() => new Promise((resolve) => { | |
| 9351 | - let resolved = false; | |
| 9352 | - const timeOutId = setTimeout(() => { | |
| 9353 | - toastr.error(t`Chat deletion timed out. Please try again.`); | |
| 9354 | - setResolved(); | |
| 9355 | - }, 5000); | |
| 9356 | - | |
| 9357 | - const setResolved = () => { | |
| 9358 | - if (resolved) { | |
| 9359 | - return; | |
| 9360 | - } | |
| 9361 | - resolved = true; | |
| 9362 | - [event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => { | |
| 9363 | - eventSource.removeListener(eventType, setResolved); | |
| 9364 | - }); | |
| 9365 | - clearTimeout(timeOutId); | |
| 9366 | - resolve(''); | |
| 9367 | - }; | |
| 9368 | - | |
| 9369 | - [event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => { | |
| 9370 | - eventSource.on(eventType, setResolved); | |
| 9371 | - }); | |
| 9372 | - | |
| 9373 | - const currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross'); | |
| 9374 | - $(currentChatDeleteButton).trigger('click', { fromSlashCommand: true }); | |
| 9375 | - })); | |
| 9376 | -} | |
| 9377 | - | |
| 9378 | -async function doRenameChat(_, chatName) { | |
| 9379 | - if (!chatName) { | |
| 9380 | - toastr.warning(t`Name must be provided as an argument to rename this chat.`); | |
| 9381 | - return ''; | |
| 9382 | - } | |
| 9383 | - | |
| 9384 | - const currentChatName = getCurrentChatId(); | |
| 9385 | - if (!currentChatName) { | |
| 9386 | - toastr.warning(t`No chat selected that can be renamed.`); | |
| 9387 | - return ''; | |
| 9388 | - } | |
| 9389 | - | |
| 9390 | - await renameChat(currentChatName, chatName); | |
| 9391 | - | |
| 9392 | - toastr.success(t`Successfully renamed chat to: ${chatName}`); | |
| 9393 | - return ''; | |
| 9394 | -} | |
| 9395 | - | |
| 9396 | 9101 | /** |
| 9397 | 9102 | * Renames a group or character chat. |
| 9398 | 9103 | * @param {object} param Parameters for renaming chat |
| @@ -9506,12 +9211,6 @@ export async function updateRemoteChatName(characterId, newName) { | ||
| 9506 | 9211 | } |
| 9507 | 9212 | } |
| 9508 | 9213 | |
| 9509 | -/** | |
| 9510 | - * /getchatname` slash command | |
| 9511 | - */ | |
| 9512 | -async function doGetChatName() { | |
| 9513 | - return getCurrentChatDetails().sessionName; | |
| 9514 | -} | |
| 9515 | 9214 | |
| 9516 | 9215 | function doCharListDisplaySwitch() { |
| 9517 | 9216 | power_user.charListGrid = !power_user.charListGrid; |
| @@ -9519,11 +9218,6 @@ function doCharListDisplaySwitch() { | ||
| 9519 | 9218 | saveSettingsDebounced(); |
| 9520 | 9219 | } |
| 9521 | 9220 | |
| 9522 | -function doCloseChat() { | |
| 9523 | - $('#option_close_chat').trigger('click'); | |
| 9524 | - return ''; | |
| 9525 | -} | |
| 9526 | - | |
| 9527 | 9221 | /** |
| 9528 | 9222 | * Function to handle the deletion of a character, given a specific popup type and character ID. |
| 9529 | 9223 | * 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 } = {}) { | ||
| 9634 | 9328 | sendSystemMessage(system_message_types.ASSISTANT_NOTE); |
| 9635 | 9329 | } |
| 9636 | 9330 | |
| 9637 | -function doTogglePanels() { | |
| 9638 | - $('#option_settings').trigger('click'); | |
| 9639 | - return ''; | |
| 9640 | -} | |
| 9641 | - | |
| 9642 | 9331 | /** |
| 9643 | 9332 | * Event handler to open a navbar drawer when a drawer open button is clicked. |
| 9644 | 9333 | * Handles click events on .drawer-opener elements. |
| @@ -9776,249 +9465,6 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api | ||
| 9776 | 9465 | |
| 9777 | 9466 | // MARK: DOM Handlers Start |
| 9778 | 9467 | jQuery(async function () { |
| 9779 | - async function doForceSave() { | |
| 9780 | - await saveSettings(); | |
| 9781 | - await saveChatConditional(); | |
| 9782 | - toastr.success('Chat and settings saved.'); | |
| 9783 | - return ''; | |
| 9784 | - } | |
| 9785 | - | |
| 9786 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9787 | - name: 'dupe', | |
| 9788 | - callback: duplicateCharacter, | |
| 9789 | - helpString: 'Duplicates the currently selected character.', | |
| 9790 | - })); | |
| 9791 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9792 | - name: 'api', | |
| 9793 | - callback: connectAPISlash, | |
| 9794 | - returns: 'the current API', | |
| 9795 | - namedArgumentList: [ | |
| 9796 | - SlashCommandNamedArgument.fromProps({ | |
| 9797 | - name: 'quiet', | |
| 9798 | - description: 'Suppress the toast message on connection', | |
| 9799 | - typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 9800 | - defaultValue: 'false', | |
| 9801 | - enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 9802 | - }), | |
| 9803 | - ], | |
| 9804 | - unnamedArgumentList: [ | |
| 9805 | - SlashCommandArgument.fromProps({ | |
| 9806 | - description: 'API to connect to', | |
| 9807 | - typeList: [ARGUMENT_TYPE.STRING], | |
| 9808 | - enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => | |
| 9809 | - new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)), | |
| 9810 | - selected[0].toUpperCase() ?? enumIcons.default)), | |
| 9811 | - }), | |
| 9812 | - ], | |
| 9813 | - helpString: ` | |
| 9814 | - <div> | |
| 9815 | - Connect to an API. If no argument is provided, it will return the currently connected API. | |
| 9816 | - </div> | |
| 9817 | - <div> | |
| 9818 | - <strong>Available APIs:</strong> | |
| 9819 | - <pre><code>${Object.keys(CONNECT_API_MAP).join(', ')}</code></pre> | |
| 9820 | - </div> | |
| 9821 | - `, | |
| 9822 | - })); | |
| 9823 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9824 | - name: 'impersonate', | |
| 9825 | - callback: doImpersonate, | |
| 9826 | - aliases: ['imp'], | |
| 9827 | - namedArgumentList: [ | |
| 9828 | - new SlashCommandNamedArgument( | |
| 9829 | - 'await', | |
| 9830 | - 'Whether to await for the triggered generation before continuing', | |
| 9831 | - [ARGUMENT_TYPE.BOOLEAN], | |
| 9832 | - false, | |
| 9833 | - false, | |
| 9834 | - 'false', | |
| 9835 | - ), | |
| 9836 | - ], | |
| 9837 | - unnamedArgumentList: [ | |
| 9838 | - new SlashCommandArgument( | |
| 9839 | - 'prompt', [ARGUMENT_TYPE.STRING], false, | |
| 9840 | - ), | |
| 9841 | - ], | |
| 9842 | - helpString: ` | |
| 9843 | - <div> | |
| 9844 | - Calls an impersonation response, with an optional additional prompt. | |
| 9845 | - </div> | |
| 9846 | - <div> | |
| 9847 | - If <code>await=true</code> named argument is passed, the command will wait for the impersonation to end before continuing. | |
| 9848 | - </div> | |
| 9849 | - <div> | |
| 9850 | - <strong>Example:</strong> | |
| 9851 | - <ul> | |
| 9852 | - <li> | |
| 9853 | - <pre><code class="language-stscript">/impersonate What is the meaning of life?</code></pre> | |
| 9854 | - </li> | |
| 9855 | - </ul> | |
| 9856 | - </div> | |
| 9857 | - `, | |
| 9858 | - })); | |
| 9859 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9860 | - name: 'delchat', | |
| 9861 | - callback: doDeleteChat, | |
| 9862 | - helpString: 'Deletes the current chat.', | |
| 9863 | - })); | |
| 9864 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9865 | - name: 'renamechat', | |
| 9866 | - callback: doRenameChat, | |
| 9867 | - unnamedArgumentList: [ | |
| 9868 | - new SlashCommandArgument( | |
| 9869 | - 'new chat name', [ARGUMENT_TYPE.STRING], true, | |
| 9870 | - ), | |
| 9871 | - ], | |
| 9872 | - helpString: 'Renames the current chat.', | |
| 9873 | - })); | |
| 9874 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9875 | - name: 'getchatname', | |
| 9876 | - callback: doGetChatName, | |
| 9877 | - returns: 'chat file name', | |
| 9878 | - helpString: 'Returns the name of the current chat file into the pipe.', | |
| 9879 | - })); | |
| 9880 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9881 | - name: 'closechat', | |
| 9882 | - callback: doCloseChat, | |
| 9883 | - helpString: 'Closes the current chat.', | |
| 9884 | - })); | |
| 9885 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9886 | - name: 'tempchat', | |
| 9887 | - callback: () => { | |
| 9888 | - return new Promise((resolve, reject) => { | |
| 9889 | - const eventCallback = async (chatId) => { | |
| 9890 | - if (chatId) { | |
| 9891 | - return reject('Not in a temporary chat'); | |
| 9892 | - } | |
| 9893 | - await newAssistantChat({ temporary: true }); | |
| 9894 | - return resolve(''); | |
| 9895 | - }; | |
| 9896 | - eventSource.once(event_types.CHAT_CHANGED, eventCallback); | |
| 9897 | - doCloseChat(); | |
| 9898 | - setTimeout(() => { | |
| 9899 | - reject('Failed to open temporary chat'); | |
| 9900 | - eventSource.removeListener(event_types.CHAT_CHANGED, eventCallback); | |
| 9901 | - }, debounce_timeout.relaxed); | |
| 9902 | - }); | |
| 9903 | - }, | |
| 9904 | - helpString: 'Opens a temporary chat with Assistant.', | |
| 9905 | - })); | |
| 9906 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9907 | - name: 'panels', | |
| 9908 | - callback: doTogglePanels, | |
| 9909 | - aliases: ['togglepanels'], | |
| 9910 | - helpString: 'Toggle UI panels on/off', | |
| 9911 | - })); | |
| 9912 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9913 | - name: 'forcesave', | |
| 9914 | - callback: doForceSave, | |
| 9915 | - helpString: 'Forces a save of the current chat and settings', | |
| 9916 | - })); | |
| 9917 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9918 | - name: 'instruct', | |
| 9919 | - callback: selectInstructCallback, | |
| 9920 | - returns: 'current template', | |
| 9921 | - namedArgumentList: [ | |
| 9922 | - SlashCommandNamedArgument.fromProps({ | |
| 9923 | - name: 'quiet', | |
| 9924 | - description: 'Suppress the toast message on template change', | |
| 9925 | - typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 9926 | - defaultValue: 'false', | |
| 9927 | - enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 9928 | - }), | |
| 9929 | - SlashCommandNamedArgument.fromProps({ | |
| 9930 | - name: 'forceGet', | |
| 9931 | - description: 'Force getting a name even if instruct mode is disabled', | |
| 9932 | - typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 9933 | - defaultValue: 'false', | |
| 9934 | - enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 9935 | - }), | |
| 9936 | - ], | |
| 9937 | - unnamedArgumentList: [ | |
| 9938 | - SlashCommandArgument.fromProps({ | |
| 9939 | - description: 'instruct template name', | |
| 9940 | - typeList: [ARGUMENT_TYPE.STRING], | |
| 9941 | - enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), | |
| 9942 | - }), | |
| 9943 | - ], | |
| 9944 | - helpString: ` | |
| 9945 | - <div> | |
| 9946 | - Selects instruct mode template by name. Enables instruct mode if not already enabled. | |
| 9947 | - Gets the current instruct template if no name is provided and instruct mode is enabled or <code>forceGet=true</code> is passed. | |
| 9948 | - </div> | |
| 9949 | - <div> | |
| 9950 | - <strong>Example:</strong> | |
| 9951 | - <ul> | |
| 9952 | - <li> | |
| 9953 | - <pre><code class="language-stscript">/instruct creative</code></pre> | |
| 9954 | - </li> | |
| 9955 | - </ul> | |
| 9956 | - </div> | |
| 9957 | - `, | |
| 9958 | - })); | |
| 9959 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9960 | - name: 'instruct-on', | |
| 9961 | - callback: enableInstructCallback, | |
| 9962 | - helpString: 'Enables instruct mode.', | |
| 9963 | - })); | |
| 9964 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9965 | - name: 'instruct-off', | |
| 9966 | - callback: disableInstructCallback, | |
| 9967 | - helpString: 'Disables instruct mode', | |
| 9968 | - })); | |
| 9969 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9970 | - name: 'instruct-state', | |
| 9971 | - aliases: ['instruct-toggle'], | |
| 9972 | - helpString: 'Gets the current instruct mode state. If an argument is provided, it will set the instruct mode state.', | |
| 9973 | - unnamedArgumentList: [ | |
| 9974 | - SlashCommandArgument.fromProps({ | |
| 9975 | - description: 'instruct mode state', | |
| 9976 | - typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 9977 | - enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 9978 | - }), | |
| 9979 | - ], | |
| 9980 | - callback: async (_args, state) => { | |
| 9981 | - if (!state || typeof state !== 'string') { | |
| 9982 | - return String(power_user.instruct.enabled); | |
| 9983 | - } | |
| 9984 | - | |
| 9985 | - const newState = isTrueBoolean(state); | |
| 9986 | - newState ? enableInstructCallback() : disableInstructCallback(); | |
| 9987 | - return String(power_user.instruct.enabled); | |
| 9988 | - }, | |
| 9989 | - })); | |
| 9990 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 9991 | - name: 'context', | |
| 9992 | - callback: selectContextCallback, | |
| 9993 | - returns: 'template name', | |
| 9994 | - namedArgumentList: [ | |
| 9995 | - SlashCommandNamedArgument.fromProps({ | |
| 9996 | - name: 'quiet', | |
| 9997 | - description: 'Suppress the toast message on template change', | |
| 9998 | - typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 9999 | - defaultValue: 'false', | |
| 10000 | - enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 10001 | - }), | |
| 10002 | - ], | |
| 10003 | - unnamedArgumentList: [ | |
| 10004 | - SlashCommandArgument.fromProps({ | |
| 10005 | - description: 'context template name', | |
| 10006 | - typeList: [ARGUMENT_TYPE.STRING], | |
| 10007 | - enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), | |
| 10008 | - }), | |
| 10009 | - ], | |
| 10010 | - helpString: 'Selects context template by name. Gets the current template if no name is provided', | |
| 10011 | - })); | |
| 10012 | - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 10013 | - name: 'chat-manager', | |
| 10014 | - callback: () => { | |
| 10015 | - $('#option_select_chat').trigger('click'); | |
| 10016 | - return ''; | |
| 10017 | - }, | |
| 10018 | - aliases: ['chat-history', 'manage-chats'], | |
| 10019 | - helpString: 'Opens the chat manager for the current character/group.', | |
| 10020 | - })); | |
| 10021 | - | |
| 10022 | 9468 | setTimeout(function () { |
| 10023 | 9469 | $('#groupControlsToggle').trigger('click'); |
| 10024 | 9470 | $('#groupCurrentMemberListToggle .inline-drawer-icon').trigger('click'); |
| @@ -471,7 +471,7 @@ export class ConnectionManagerRequestService { | ||
| 471 | 471 | |
| 472 | 472 | /** |
| 473 | 473 | * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile] |
| 474 | 474 | * @return {import('../../scriptslash-commands.js').ConnectAPIMap} |
| 475 | 475 | * @throws {Error} |
| 476 | 476 | */ |
| 477 | 477 | static validateProfile(profile) { |
| @@ -3,7 +3,6 @@ import { copyText, flashHighlight } from './utils.js'; | ||
| 3 | 3 | |
| 4 | 4 | import { |
| 5 | 5 | Generate, |
| 6 | - UNIQUE_APIS, | |
| 7 | 6 | activateSendButtons, |
| 8 | 7 | addOneMessage, |
| 9 | 8 | characters, |
| @@ -13,6 +12,8 @@ import { | ||
| 13 | 12 | deactivateSendButtons, |
| 14 | 13 | default_avatar, |
| 15 | 14 | deleteSwipe, |
| 15 | + displayPastChats, | |
| 16 | + duplicateCharacter, | |
| 16 | 17 | eventSource, |
| 17 | 18 | event_types, |
| 18 | 19 | extension_prompt_roles, |
| @@ -20,6 +21,8 @@ import { | ||
| 20 | 21 | extractMessageBias, |
| 21 | 22 | generateQuietPrompt, |
| 22 | 23 | generateRaw, |
| 24 | + getCurrentChatDetails, | |
| 25 | + getCurrentChatId, | |
| 23 | 26 | getFirstDisplayedMessageId, |
| 24 | 27 | getThumbnailUrl, |
| 25 | 28 | is_send_press, |
| @@ -27,10 +30,14 @@ import { | ||
| 27 | 30 | name1, |
| 28 | 31 | name2, |
| 29 | 32 | neutralCharacterName, |
| 33 | + newAssistantChat, | |
| 34 | + online_status, | |
| 30 | 35 | reloadCurrentChat, |
| 31 | 36 | removeMacros, |
| 32 | 37 | renameCharacter, |
| 38 | + renameChat, | |
| 33 | 39 | saveChatConditional, |
| 40 | + saveSettings, | |
| 34 | 41 | saveSettingsDebounced, |
| 35 | 42 | sendMessageAsUser, |
| 36 | 43 | sendSystemMessage, |
| @@ -56,7 +63,7 @@ import { getRegexedString, regex_placement } from './extensions/regex/engine.js' | ||
| 56 | 63 | import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js'; |
| 57 | 64 | import { chat_completion_sources, oai_settings, promptManager } from './openai.js'; |
| 58 | 65 | import { user_avatar } from './personas.js'; |
| 59 | 66 | import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, power_user } from './power-user.js'; |
| 60 | 67 | import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 61 | 68 | import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js'; |
| 62 | 69 | 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 | ||
| 80 | 87 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 81 | 88 | import { t } from './i18n.js'; |
| 82 | 89 | import { kai_settings } from './kai-settings.js'; |
| 90 | +import { instruct_presets, selectContextPreset, selectInstructPreset } from './instruct-mode.js'; | |
| 91 | +import { debounce_timeout } from './constants.js'; | |
| 83 | 92 | export { |
| 84 | 93 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, |
| 85 | 94 | }; |
| @@ -110,7 +119,536 @@ function closureToFilter(closure) { | ||
| 110 | 119 | }; |
| 111 | 120 | } |
| 112 | 121 | |
| 122 | +/** | |
| 123 | + * @typedef {object} ConnectAPIMap | |
| 124 | + * @property {string} selected - API name (e.g. "textgenerationwebui", "openai") | |
| 125 | + * @property {string?} [button] - CSS selector for the API button | |
| 126 | + * @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter") | |
| 127 | + * @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai") | |
| 128 | + */ | |
| 129 | + | |
| 130 | +/** @type {Record<string, ConnectAPIMap>} */ | |
| 131 | +export const CONNECT_API_MAP = {}; | |
| 132 | + | |
| 133 | +/** @type {string[]} */ | |
| 134 | +export const UNIQUE_APIS = []; | |
| 135 | + | |
| 136 | +function setupConnectAPIMap() { | |
| 137 | + /** @type {Record<string, ConnectAPIMap>} */ | |
| 138 | + const result = { | |
| 139 | + // Default APIs not contained inside text gen / chat gen | |
| 140 | + 'kobold': { | |
| 141 | + selected: 'kobold', | |
| 142 | + button: '#api_button', | |
| 143 | + }, | |
| 144 | + 'horde': { | |
| 145 | + selected: 'koboldhorde', | |
| 146 | + }, | |
| 147 | + 'novel': { | |
| 148 | + selected: 'novel', | |
| 149 | + button: '#api_button_novel', | |
| 150 | + }, | |
| 151 | + 'koboldcpp': { | |
| 152 | + selected: 'textgenerationwebui', | |
| 153 | + button: '#api_button_textgenerationwebui', | |
| 154 | + type: textgen_types.KOBOLDCPP, | |
| 155 | + }, | |
| 156 | + // KoboldCpp alias | |
| 157 | + 'kcpp': { | |
| 158 | + selected: 'textgenerationwebui', | |
| 159 | + button: '#api_button_textgenerationwebui', | |
| 160 | + type: textgen_types.KOBOLDCPP, | |
| 161 | + }, | |
| 162 | + 'openai': { | |
| 163 | + selected: 'openai', | |
| 164 | + button: '#api_button_openai', | |
| 165 | + source: chat_completion_sources.OPENAI, | |
| 166 | + }, | |
| 167 | + // OpenAI alias | |
| 168 | + 'oai': { | |
| 169 | + selected: 'openai', | |
| 170 | + button: '#api_button_openai', | |
| 171 | + source: chat_completion_sources.OPENAI, | |
| 172 | + }, | |
| 173 | + // Google alias | |
| 174 | + 'google': { | |
| 175 | + selected: 'openai', | |
| 176 | + button: '#api_button_openai', | |
| 177 | + source: chat_completion_sources.MAKERSUITE, | |
| 178 | + }, | |
| 179 | + // OpenRouter special naming, to differentiate between chat comp and text comp | |
| 180 | + 'openrouter': { | |
| 181 | + selected: 'openai', | |
| 182 | + button: '#api_button_openai', | |
| 183 | + source: chat_completion_sources.OPENROUTER, | |
| 184 | + }, | |
| 185 | + 'openrouter-text': { | |
| 186 | + selected: 'textgenerationwebui', | |
| 187 | + button: '#api_button_textgenerationwebui', | |
| 188 | + type: textgen_types.OPENROUTER, | |
| 189 | + }, | |
| 190 | + }; | |
| 191 | + | |
| 192 | + // Fill connections map from textgen_types and chat_completion_sources | |
| 193 | + for (const textGenType of Object.values(textgen_types)) { | |
| 194 | + if (result[textGenType]) continue; | |
| 195 | + result[textGenType] = { | |
| 196 | + selected: 'textgenerationwebui', | |
| 197 | + button: '#api_button_textgenerationwebui', | |
| 198 | + type: textGenType, | |
| 199 | + }; | |
| 200 | + } | |
| 201 | + | |
| 202 | + for (const chatCompletionSource of Object.values(chat_completion_sources)) { | |
| 203 | + if (result[chatCompletionSource]) continue; | |
| 204 | + result[chatCompletionSource] = { | |
| 205 | + selected: 'openai', | |
| 206 | + button: '#api_button_openai', | |
| 207 | + source: chatCompletionSource, | |
| 208 | + }; | |
| 209 | + } | |
| 210 | + | |
| 211 | + Object.assign(CONNECT_API_MAP, result); | |
| 212 | + UNIQUE_APIS.push(...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))); | |
| 213 | +} | |
| 214 | + | |
| 113 | 215 | export function initDefaultSlashCommands() { |
| 216 | + setupConnectAPIMap(); | |
| 217 | + | |
| 218 | + async function enableInstructCallback() { | |
| 219 | + $('#instruct_enabled').prop('checked', true).trigger('input').trigger('change'); | |
| 220 | + return ''; | |
| 221 | + } | |
| 222 | + | |
| 223 | + async function disableInstructCallback() { | |
| 224 | + $('#instruct_enabled').prop('checked', false).trigger('input').trigger('change'); | |
| 225 | + return ''; | |
| 226 | + } | |
| 227 | + | |
| 228 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 229 | + name: 'dupe', | |
| 230 | + callback: duplicateCharacter, | |
| 231 | + helpString: 'Duplicates the currently selected character.', | |
| 232 | + })); | |
| 233 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 234 | + name: 'api', | |
| 235 | + callback: async function (args, text) { | |
| 236 | + if (!text?.toString()?.trim()) { | |
| 237 | + for (const [key, config] of Object.entries(CONNECT_API_MAP)) { | |
| 238 | + if (config.selected !== main_api) continue; | |
| 239 | + | |
| 240 | + if (config.source) { | |
| 241 | + if (oai_settings.chat_completion_source === config.source) { | |
| 242 | + return key; | |
| 243 | + } else { | |
| 244 | + continue; | |
| 245 | + } | |
| 246 | + } | |
| 247 | + | |
| 248 | + if (config.type) { | |
| 249 | + if (textgenerationwebui_settings.type === config.type) { | |
| 250 | + return key; | |
| 251 | + } else { | |
| 252 | + continue; | |
| 253 | + } | |
| 254 | + } | |
| 255 | + | |
| 256 | + return key; | |
| 257 | + } | |
| 258 | + | |
| 259 | + console.error('FIXME: The current API is not in the API map'); | |
| 260 | + return ''; | |
| 261 | + } | |
| 262 | + | |
| 263 | + const apiConfig = CONNECT_API_MAP[text?.toString()?.toLowerCase() ?? '']; | |
| 264 | + if (!apiConfig) { | |
| 265 | + toastr.error(t`Error: ${text} is not a valid API`); | |
| 266 | + return ''; | |
| 267 | + } | |
| 268 | + | |
| 269 | + let connectionRequired = false; | |
| 270 | + | |
| 271 | + if (main_api !== apiConfig.selected) { | |
| 272 | + $(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true); | |
| 273 | + $('#main_api').trigger('change'); | |
| 274 | + connectionRequired = true; | |
| 275 | + } | |
| 276 | + | |
| 277 | + if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) { | |
| 278 | + $(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true); | |
| 279 | + $('#chat_completion_source').trigger('change'); | |
| 280 | + connectionRequired = true; | |
| 281 | + } | |
| 282 | + | |
| 283 | + if (apiConfig.type && textgenerationwebui_settings.type !== apiConfig.type) { | |
| 284 | + $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true); | |
| 285 | + $('#textgen_type').trigger('change'); | |
| 286 | + connectionRequired = true; | |
| 287 | + } | |
| 288 | + | |
| 289 | + if (connectionRequired && apiConfig.button) { | |
| 290 | + $(apiConfig.button).trigger('click'); | |
| 291 | + } | |
| 292 | + | |
| 293 | + const quiet = isTrueBoolean(args?.quiet?.toString()); | |
| 294 | + const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`); | |
| 295 | + | |
| 296 | + try { | |
| 297 | + if (connectionRequired) { | |
| 298 | + await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); | |
| 299 | + } | |
| 300 | + console.log('Connection successful'); | |
| 301 | + } catch { | |
| 302 | + console.log('Could not connect after 5 seconds, skipping.'); | |
| 303 | + } | |
| 304 | + | |
| 305 | + toastr.clear(toast); | |
| 306 | + return text?.toString()?.trim() ?? ''; | |
| 307 | + }, | |
| 308 | + returns: 'the current API', | |
| 309 | + namedArgumentList: [ | |
| 310 | + SlashCommandNamedArgument.fromProps({ | |
| 311 | + name: 'quiet', | |
| 312 | + description: 'Suppress the toast message on connection', | |
| 313 | + typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 314 | + defaultValue: 'false', | |
| 315 | + enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 316 | + }), | |
| 317 | + ], | |
| 318 | + unnamedArgumentList: [ | |
| 319 | + SlashCommandArgument.fromProps({ | |
| 320 | + description: 'API to connect to', | |
| 321 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 322 | + enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => | |
| 323 | + new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)), | |
| 324 | + selected[0].toUpperCase() ?? enumIcons.default)), | |
| 325 | + }), | |
| 326 | + ], | |
| 327 | + helpString: ` | |
| 328 | + <div> | |
| 329 | + Connect to an API. If no argument is provided, it will return the currently connected API. | |
| 330 | + </div> | |
| 331 | + <div> | |
| 332 | + <strong>Available APIs:</strong> | |
| 333 | + <pre><code>${Object.keys(CONNECT_API_MAP).sort((a, b) => a.localeCompare(b)).join(', ')}</code></pre> | |
| 334 | + </div> | |
| 335 | + `, | |
| 336 | + })); | |
| 337 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 338 | + name: 'impersonate', | |
| 339 | + callback: async function (args, prompt) { | |
| 340 | + const options = prompt?.toString()?.trim() ? { quiet_prompt: prompt.toString().trim(), quietToLoud: true } : {}; | |
| 341 | + const shouldAwait = isTrueBoolean(args?.await?.toString()); | |
| 342 | + const outerPromise = new Promise((outerResolve) => setTimeout(async () => { | |
| 343 | + try { | |
| 344 | + await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100); | |
| 345 | + } catch { | |
| 346 | + console.warn('Timeout waiting for generation unlock'); | |
| 347 | + toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`); | |
| 348 | + return ''; | |
| 349 | + } | |
| 350 | + | |
| 351 | + // Prevent generate recursion | |
| 352 | + $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true })); | |
| 353 | + | |
| 354 | + outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('impersonate', options)), 1))); | |
| 355 | + }, 1)); | |
| 356 | + | |
| 357 | + if (shouldAwait) { | |
| 358 | + const innerPromise = await outerPromise; | |
| 359 | + await innerPromise; | |
| 360 | + } | |
| 361 | + | |
| 362 | + return ''; | |
| 363 | + } | |
| 364 | + , | |
| 365 | + aliases: ['imp'], | |
| 366 | + namedArgumentList: [ | |
| 367 | + new SlashCommandNamedArgument( | |
| 368 | + 'await', | |
| 369 | + 'Whether to await for the triggered generation before continuing', | |
| 370 | + [ARGUMENT_TYPE.BOOLEAN], | |
| 371 | + false, | |
| 372 | + false, | |
| 373 | + 'false', | |
| 374 | + ), | |
| 375 | + ], | |
| 376 | + unnamedArgumentList: [ | |
| 377 | + new SlashCommandArgument( | |
| 378 | + 'prompt', [ARGUMENT_TYPE.STRING], false, | |
| 379 | + ), | |
| 380 | + ], | |
| 381 | + helpString: ` | |
| 382 | + <div> | |
| 383 | + Calls an impersonation response, with an optional additional prompt. | |
| 384 | + </div> | |
| 385 | + <div> | |
| 386 | + If <code>await=true</code> named argument is passed, the command will wait for the impersonation to end before continuing. | |
| 387 | + </div> | |
| 388 | + <div> | |
| 389 | + <strong>Example:</strong> | |
| 390 | + <ul> | |
| 391 | + <li> | |
| 392 | + <pre><code class="language-stscript">/impersonate What is the meaning of life?</code></pre> | |
| 393 | + </li> | |
| 394 | + </ul> | |
| 395 | + </div> | |
| 396 | + `, | |
| 397 | + })); | |
| 398 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 399 | + name: 'delchat', | |
| 400 | + callback: async function () { | |
| 401 | + return displayPastChats().then(() => new Promise((resolve) => { | |
| 402 | + let resolved = false; | |
| 403 | + const timeOutId = setTimeout(() => { | |
| 404 | + toastr.error(t`Chat deletion timed out. Please try again.`); | |
| 405 | + setResolved(); | |
| 406 | + }, 5000); | |
| 407 | + | |
| 408 | + const setResolved = () => { | |
| 409 | + if (resolved) { | |
| 410 | + return; | |
| 411 | + } | |
| 412 | + resolved = true; | |
| 413 | + [event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => { | |
| 414 | + eventSource.removeListener(eventType, setResolved); | |
| 415 | + }); | |
| 416 | + clearTimeout(timeOutId); | |
| 417 | + resolve(''); | |
| 418 | + }; | |
| 419 | + | |
| 420 | + [event_types.CHAT_DELETED, event_types.GROUP_CHAT_DELETED].forEach((eventType) => { | |
| 421 | + eventSource.on(eventType, setResolved); | |
| 422 | + }); | |
| 423 | + | |
| 424 | + const currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross'); | |
| 425 | + $(currentChatDeleteButton).trigger('click', { fromSlashCommand: true }); | |
| 426 | + })); | |
| 427 | + }, | |
| 428 | + helpString: 'Deletes the current chat.', | |
| 429 | + })); | |
| 430 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 431 | + name: 'renamechat', | |
| 432 | + callback: async function doRenameChat(_, chatName) { | |
| 433 | + if (!chatName) { | |
| 434 | + toastr.warning(t`Name must be provided as an argument to rename this chat.`); | |
| 435 | + return ''; | |
| 436 | + } | |
| 437 | + | |
| 438 | + const currentChatName = getCurrentChatId(); | |
| 439 | + if (!currentChatName) { | |
| 440 | + toastr.warning(t`No chat selected that can be renamed.`); | |
| 441 | + return ''; | |
| 442 | + } | |
| 443 | + | |
| 444 | + await renameChat(currentChatName, chatName.toString()); | |
| 445 | + | |
| 446 | + toastr.success(t`Successfully renamed chat to: ${chatName}`); | |
| 447 | + return ''; | |
| 448 | + }, | |
| 449 | + unnamedArgumentList: [ | |
| 450 | + new SlashCommandArgument( | |
| 451 | + 'new chat name', [ARGUMENT_TYPE.STRING], true, | |
| 452 | + ), | |
| 453 | + ], | |
| 454 | + helpString: 'Renames the current chat.', | |
| 455 | + })); | |
| 456 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 457 | + name: 'getchatname', | |
| 458 | + callback: async function doGetChatName() { | |
| 459 | + return getCurrentChatDetails().sessionName; | |
| 460 | + }, | |
| 461 | + returns: 'chat file name', | |
| 462 | + helpString: 'Returns the name of the current chat file into the pipe.', | |
| 463 | + })); | |
| 464 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 465 | + name: 'closechat', | |
| 466 | + callback: function () { | |
| 467 | + $('#option_close_chat').trigger('click'); | |
| 468 | + return ''; | |
| 469 | + }, | |
| 470 | + helpString: 'Closes the current chat.', | |
| 471 | + })); | |
| 472 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 473 | + name: 'tempchat', | |
| 474 | + callback: () => { | |
| 475 | + return new Promise((resolve, reject) => { | |
| 476 | + const eventCallback = async (chatId) => { | |
| 477 | + if (chatId) { | |
| 478 | + return reject('Not in a temporary chat'); | |
| 479 | + } | |
| 480 | + await newAssistantChat({ temporary: true }); | |
| 481 | + return resolve(''); | |
| 482 | + }; | |
| 483 | + eventSource.once(event_types.CHAT_CHANGED, eventCallback); | |
| 484 | + $('#option_close_chat').trigger('click'); | |
| 485 | + setTimeout(() => { | |
| 486 | + reject('Failed to open temporary chat'); | |
| 487 | + eventSource.removeListener(event_types.CHAT_CHANGED, eventCallback); | |
| 488 | + }, debounce_timeout.relaxed); | |
| 489 | + }); | |
| 490 | + }, | |
| 491 | + helpString: 'Opens a temporary chat with Assistant.', | |
| 492 | + })); | |
| 493 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 494 | + name: 'panels', | |
| 495 | + callback: function () { | |
| 496 | + $('#option_settings').trigger('click'); | |
| 497 | + return ''; | |
| 498 | + }, | |
| 499 | + aliases: ['togglepanels'], | |
| 500 | + helpString: 'Toggle UI panels on/off', | |
| 501 | + })); | |
| 502 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 503 | + name: 'forcesave', | |
| 504 | + callback: async function () { | |
| 505 | + await saveSettings(); | |
| 506 | + await saveChatConditional(); | |
| 507 | + toastr.success('Chat and settings saved.'); | |
| 508 | + return ''; | |
| 509 | + }, | |
| 510 | + helpString: 'Forces a save of the current chat and settings', | |
| 511 | + })); | |
| 512 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 513 | + name: 'instruct', | |
| 514 | + callback: async function (args, name) { | |
| 515 | + if (!name) { | |
| 516 | + return power_user.instruct.enabled || isTrueBoolean(args?.forceGet?.toString()) ? power_user.instruct.preset : ''; | |
| 517 | + } | |
| 518 | + | |
| 519 | + const quiet = isTrueBoolean(args?.quiet?.toString()); | |
| 520 | + const instructNames = instruct_presets.map(preset => preset.name); | |
| 521 | + const fuse = new Fuse(instructNames); | |
| 522 | + const result = fuse.search(name?.toString() ?? ''); | |
| 523 | + | |
| 524 | + if (result.length === 0) { | |
| 525 | + !quiet && toastr.warning(t`Instruct template '${name}' not found`); | |
| 526 | + return ''; | |
| 527 | + } | |
| 528 | + | |
| 529 | + const foundName = result[0].item; | |
| 530 | + selectInstructPreset(foundName, { quiet: quiet }); | |
| 531 | + return foundName; | |
| 532 | + }, | |
| 533 | + returns: 'current template', | |
| 534 | + namedArgumentList: [ | |
| 535 | + SlashCommandNamedArgument.fromProps({ | |
| 536 | + name: 'quiet', | |
| 537 | + description: 'Suppress the toast message on template change', | |
| 538 | + typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 539 | + defaultValue: 'false', | |
| 540 | + enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 541 | + }), | |
| 542 | + SlashCommandNamedArgument.fromProps({ | |
| 543 | + name: 'forceGet', | |
| 544 | + description: 'Force getting a name even if instruct mode is disabled', | |
| 545 | + typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 546 | + defaultValue: 'false', | |
| 547 | + enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 548 | + }), | |
| 549 | + ], | |
| 550 | + unnamedArgumentList: [ | |
| 551 | + SlashCommandArgument.fromProps({ | |
| 552 | + description: 'instruct template name', | |
| 553 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 554 | + enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), | |
| 555 | + }), | |
| 556 | + ], | |
| 557 | + helpString: ` | |
| 558 | + <div> | |
| 559 | + Selects instruct mode template by name. Enables instruct mode if not already enabled. | |
| 560 | + Gets the current instruct template if no name is provided and instruct mode is enabled or <code>forceGet=true</code> is passed. | |
| 561 | + </div> | |
| 562 | + <div> | |
| 563 | + <strong>Example:</strong> | |
| 564 | + <ul> | |
| 565 | + <li> | |
| 566 | + <pre><code class="language-stscript">/instruct creative</code></pre> | |
| 567 | + </li> | |
| 568 | + </ul> | |
| 569 | + </div> | |
| 570 | + `, | |
| 571 | + })); | |
| 572 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 573 | + name: 'instruct-on', | |
| 574 | + callback: enableInstructCallback, | |
| 575 | + helpString: 'Enables instruct mode.', | |
| 576 | + })); | |
| 577 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 578 | + name: 'instruct-off', | |
| 579 | + callback: disableInstructCallback, | |
| 580 | + helpString: 'Disables instruct mode', | |
| 581 | + })); | |
| 582 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 583 | + name: 'instruct-state', | |
| 584 | + aliases: ['instruct-toggle'], | |
| 585 | + helpString: 'Gets the current instruct mode state. If an argument is provided, it will set the instruct mode state.', | |
| 586 | + unnamedArgumentList: [ | |
| 587 | + SlashCommandArgument.fromProps({ | |
| 588 | + description: 'instruct mode state', | |
| 589 | + typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 590 | + enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 591 | + }), | |
| 592 | + ], | |
| 593 | + callback: async (_args, state) => { | |
| 594 | + if (!state || typeof state !== 'string') { | |
| 595 | + return String(power_user.instruct.enabled); | |
| 596 | + } | |
| 597 | + | |
| 598 | + const newState = isTrueBoolean(state); | |
| 599 | + newState ? enableInstructCallback() : disableInstructCallback(); | |
| 600 | + return String(power_user.instruct.enabled); | |
| 601 | + }, | |
| 602 | + })); | |
| 603 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 604 | + name: 'context', | |
| 605 | + callback: async function (args, name) { | |
| 606 | + if (!name) { | |
| 607 | + return power_user.context.preset; | |
| 608 | + } | |
| 609 | + | |
| 610 | + const quiet = isTrueBoolean(args?.quiet?.toString()); | |
| 611 | + const contextNames = context_presets.map(preset => preset.name); | |
| 612 | + const fuse = new Fuse(contextNames); | |
| 613 | + const result = fuse.search(name?.toString() ?? ''); | |
| 614 | + | |
| 615 | + if (result.length === 0) { | |
| 616 | + !quiet && toastr.warning(t`Context template '${name}' not found`); | |
| 617 | + return ''; | |
| 618 | + } | |
| 619 | + | |
| 620 | + const foundName = result[0].item; | |
| 621 | + selectContextPreset(foundName, { quiet: quiet }); | |
| 622 | + return foundName; | |
| 623 | + }, | |
| 624 | + returns: 'template name', | |
| 625 | + namedArgumentList: [ | |
| 626 | + SlashCommandNamedArgument.fromProps({ | |
| 627 | + name: 'quiet', | |
| 628 | + description: 'Suppress the toast message on template change', | |
| 629 | + typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| 630 | + defaultValue: 'false', | |
| 631 | + enumList: commonEnumProviders.boolean('trueFalse')(), | |
| 632 | + }), | |
| 633 | + ], | |
| 634 | + unnamedArgumentList: [ | |
| 635 | + SlashCommandArgument.fromProps({ | |
| 636 | + description: 'context template name', | |
| 637 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 638 | + enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), | |
| 639 | + }), | |
| 640 | + ], | |
| 641 | + helpString: 'Selects context template by name. Gets the current template if no name is provided', | |
| 642 | + })); | |
| 643 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 644 | + name: 'chat-manager', | |
| 645 | + callback: () => { | |
| 646 | + $('#option_select_chat').trigger('click'); | |
| 647 | + return ''; | |
| 648 | + }, | |
| 649 | + aliases: ['chat-history', 'manage-chats'], | |
| 650 | + helpString: 'Opens the chat manager for the current character/group.', | |
| 651 | + })); | |
| 114 | 652 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 115 | 653 | name: '?', |
| 116 | 654 | callback: helpCommandCallback, |
| @@ -3558,7 +4096,7 @@ async function countGroupMemberCallback() { | ||
| 3558 | 4096 | return ''; |
| 3559 | 4097 | } |
| 3560 | 4098 | |
| 3561 | 4099 | return String(getGroupMembers(selected_group).length); |
| 3562 | 4100 | } |
| 3563 | 4101 | |
| 3564 | 4102 | async function removeGroupMemberCallback(_, arg) { |
| @@ -4684,18 +5222,21 @@ export function stopScriptExecution() { | ||
| 4684 | 5222 | */ |
| 4685 | 5223 | async function clearCommandProgress() { |
| 4686 | 5224 | if (isExecutingCommandsFromChatInput) return; |
| 4687 | - document.querySelector('#send_textarea').style.setProperty('--progDone', '1'); | |
| 5225 | + const ta = document.getElementById('send_textarea'); | |
| 5226 | + const fs = document.getElementById('form_sheld'); | |
| 5227 | + if (!ta || !fs) return; | |
| 5228 | + ta.style.setProperty('--progDone', '1'); | |
| 4688 | 5229 | await delay(250); |
| 4689 | 5230 | if (isExecutingCommandsFromChatInput) return; |
| 4690 | 5231 | document.querySelector('#send_textarea')ta.style.transition = 'none'; |
| 4691 | 5232 | await delay(1); |
| 4692 | 5233 | document.querySelector('#send_textarea')ta.style.setProperty('--prog', '0%'); |
| 4693 | 5234 | document.querySelector('#send_textarea')ta.style.setProperty('--progDone', '0'); |
| 4694 | 5235 | document.querySelector('#form_sheld')fs.classList.remove('script_success'); |
| 4695 | 5236 | document.querySelector('#form_sheld')fs.classList.remove('script_error'); |
| 4696 | 5237 | document.querySelector('#form_sheld')fs.classList.remove('script_aborted'); |
| 4697 | 5238 | await delay(1); |
| 4698 | 5239 | document.querySelector('#send_textarea')ta.style.transition = null; |
| 4699 | 5240 | } |
| 4700 | 5241 | /** |
| 4701 | 5242 | * Debounced version of clearCommandProgress. |
| @@ -4742,19 +5283,20 @@ export async function executeSlashCommandsOnChatInput(text, options = {}) { | ||
| 4742 | 5283 | commandsFromChatInputAbortController?.abort('processCommands was called'); |
| 4743 | 5284 | activateScriptButtons(); |
| 4744 | 5285 | |
| 4745 | 5286 | /** @type {HTMLTextAreaElement} */ |
| 4746 | 5287 | const ta = document.querySelector('#send_textarea'); |
| 5288 | + const fs = document.querySelector('#form_sheld'); | |
| 4747 | 5289 | |
| 4748 | 5290 | if (options.clearChatInput) { |
| 4749 | 5291 | ta.value = ''; |
| 4750 | 5292 | ta.dispatchEvent(new Event('input', { bubbles: true })); |
| 4751 | 5293 | } |
| 4752 | 5294 | |
| 4753 | 5295 | document.querySelector('#send_textarea')ta.style.setProperty('--prog', '0%'); |
| 4754 | 5296 | document.querySelector('#send_textarea')ta.style.setProperty('--progDone', '0'); |
| 4755 | 5297 | document.querySelector('#form_sheld')fs.classList.remove('script_success'); |
| 4756 | 5298 | document.querySelector('#form_sheld')fs.classList.remove('script_error'); |
| 4757 | 5299 | document.querySelector('#form_sheld')fs.classList.remove('script_aborted'); |
| 4758 | 5300 | |
| 4759 | 5301 | /**@type {SlashCommandClosureResult} */ |
| 4760 | 5302 | let result = null; |
| @@ -4905,7 +5447,7 @@ async function executeSlashCommandsWithOptions(text, options = {}) { | ||
| 4905 | 5447 | * @param {boolean} handleParserErrors Whether to handle parser errors (show toast on error) or throw |
| 4906 | 5448 | * @param {SlashCommandScope} scope The scope to be used when executing the commands. |
| 4907 | 5449 | * @param {boolean} handleExecutionErrors Whether to handle execution errors (show toast on error) or throw |
| 4908 | 5450 | * @param {{[id:import('./slash-commands/SlashCommandParser.js').PARSER_FLAG]:boolean}} parserFlags Parser flags to apply |
| 4909 | 5451 | * @param {SlashCommandAbortController} abortController Controller used to abort or pause command execution |
| 4910 | 5452 | * @param {(done:number, total:number)=>void} onProgress Callback to handle progress events |
| 4911 | 5453 | * @returns {Promise<SlashCommandClosureResult>} |