Merge branch 'staging' into translation-improvements

fcc00e0b263bcdea477e45dfecce36d2df28d0fa

Cohee <18619528+Cohee1207@users.noreply.github.com>

10 files changed, +1509 -986Ignore whitespace
jsconfig.json+6 -1
@@ -15,6 +15,11 @@
15 "**/node_modules/*",15 "**/node_modules/*",
16 "public/lib",16 "public/lib",
17 "backups/*",17 "backups/*",
18 "data/*"18 "data/*",
19 "**/dist/*",
20 "dist/*",
21 "cache/*",
22 "src/tokenizers/*",
23 "docker/*",
19 ]24 ]
20}25}
public/script.js+2 -1
@@ -159,7 +159,7 @@ import {
159import { debounce_timeout } from './scripts/constants.js';159import { debounce_timeout } from './scripts/constants.js';
160160
161import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';161import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
162import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, getSlashCommandsHelp, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';162import { COMMENT_NAME_DEFAULT, executeSlashCommands, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, registerSlashCommand, stopScriptExecution } from './scripts/slash-commands.js';
163import {163import {
164 tag_map,164 tag_map,
165 tags,165 tags,
@@ -911,6 +911,7 @@ async function firstLoadInit() {
911 initKeyboard();911 initKeyboard();
912 initDynamicStyles();912 initDynamicStyles();
913 initTags();913 initTags();
914 initDefaultSlashCommands();
914 await getUserAvatars(true, user_avatar);915 await getUserAvatars(true, user_avatar);
915 await getCharacters();916 await getCharacters();
916 await getBackgrounds();917 await getBackgrounds();
public/scripts/extensions/caption/index.js+1 -0
@@ -356,6 +356,7 @@ jQuery(async function () {
356 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||356 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'llamacpp' && textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) ||
357 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||357 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'ooba' && textgenerationwebui_settings.server_urls[textgen_types.OOBA]) ||
358 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'koboldcpp' && textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]) ||358 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'koboldcpp' && textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP]) ||
359 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'vllm' && textgenerationwebui_settings.server_urls[textgen_types.VLLM]) ||
359 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'custom') ||360 (extension_settings.caption.source === 'multimodal' && extension_settings.caption.multimodal_api === 'custom') ||
360 extension_settings.caption.source === 'local' ||361 extension_settings.caption.source === 'local' ||
361 extension_settings.caption.source === 'horde';362 extension_settings.caption.source === 'horde';
public/scripts/extensions/caption/settings.html+2 -0
@@ -26,6 +26,7 @@
26 <option value="openai">OpenAI</option>26 <option value="openai">OpenAI</option>
27 <option value="openrouter">OpenRouter</option>27 <option value="openrouter">OpenRouter</option>
28 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>28 <option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
29 <option value="vllm">vLLM</option>
29 </select>30 </select>
30 </div>31 </div>
31 <div class="flex1 flex-container flexFlowColumn flexNoGap">32 <div class="flex1 flex-container flexFlowColumn flexNoGap">
@@ -66,6 +67,7 @@
66 <option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>67 <option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
67 <option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>68 <option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>
68 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>69 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
70 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
69 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>71 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
70 </select>72 </select>
71 </div>73 </div>
public/scripts/extensions/shared.js+17 -0
@@ -34,6 +34,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
34 const isCustom = extension_settings.caption.multimodal_api === 'custom';34 const isCustom = extension_settings.caption.multimodal_api === 'custom';
35 const isOoba = extension_settings.caption.multimodal_api === 'ooba';35 const isOoba = extension_settings.caption.multimodal_api === 'ooba';
36 const isKoboldCpp = extension_settings.caption.multimodal_api === 'koboldcpp';36 const isKoboldCpp = extension_settings.caption.multimodal_api === 'koboldcpp';
37 const isVllm = extension_settings.caption.multimodal_api === 'vllm';
37 const base64Bytes = base64Img.length * 0.75;38 const base64Bytes = base64Img.length * 0.75;
38 const compressionLimit = 2 * 1024 * 1024;39 const compressionLimit = 2 * 1024 * 1024;
39 if ((['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {40 if ((['google', 'openrouter'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
@@ -65,6 +66,14 @@ export async function getMultimodalCaption(base64Img, prompt) {
65 requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];66 requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
66 }67 }
6768
69 if (isVllm) {
70 if (extension_settings.caption.multimodal_model === 'vllm_current') {
71 requestBody.model = textgenerationwebui_settings.vllm_model;
72 }
73
74 requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.VLLM];
75 }
76
68 if (isLlamaCpp) {77 if (isLlamaCpp) {
69 requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];78 requestBody.server_url = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
70 }79 }
@@ -151,6 +160,14 @@ function throwIfInvalidModel(useReverseProxy) {
151 throw new Error('KoboldCpp server URL is not set.');160 throw new Error('KoboldCpp server URL is not set.');
152 }161 }
153162
163 if (extension_settings.caption.multimodal_api === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM]) {
164 throw new Error('vLLM server URL is not set.');
165 }
166
167 if (extension_settings.caption.multimodal_api === 'vllm' && extension_settings.caption.multimodal_model === 'vllm_current' && !textgenerationwebui_settings.vllm_model) {
168 throw new Error('vLLM model is not set.');
169 }
170
154 if (extension_settings.caption.multimodal_api === 'custom' && !oai_settings.custom_url) {171 if (extension_settings.caption.multimodal_api === 'custom' && !oai_settings.custom_url) {
155 throw new Error('Custom API URL is not set.');172 throw new Error('Custom API URL is not set.');
156 }173 }
public/scripts/extensions/tts/index.js+2 -0
@@ -10,6 +10,7 @@ import { NovelTtsProvider } from './novel.js';
10import { power_user } from '../../power-user.js';10import { power_user } from '../../power-user.js';
11import { OpenAITtsProvider } from './openai.js';11import { OpenAITtsProvider } from './openai.js';
12import { XTTSTtsProvider } from './xtts.js';12import { XTTSTtsProvider } from './xtts.js';
13import { VITSTtsProvider } from './vits.js';
13import { GSVITtsProvider } from './gsvi.js';14import { GSVITtsProvider } from './gsvi.js';
14import { SBVits2TtsProvider } from './sbvits2.js';15import { SBVits2TtsProvider } from './sbvits2.js';
15import { AllTalkTtsProvider } from './alltalk.js';16import { AllTalkTtsProvider } from './alltalk.js';
@@ -83,6 +84,7 @@ const ttsProviders = {
83 ElevenLabs: ElevenLabsTtsProvider,84 ElevenLabs: ElevenLabsTtsProvider,
84 Silero: SileroTtsProvider,85 Silero: SileroTtsProvider,
85 XTTSv2: XTTSTtsProvider,86 XTTSv2: XTTSTtsProvider,
87 VITS: VITSTtsProvider,
86 GSVI: GSVITtsProvider,88 GSVI: GSVITtsProvider,
87 SBVits2: SBVits2TtsProvider,89 SBVits2: SBVits2TtsProvider,
88 System: SystemTtsProvider,90 System: SystemTtsProvider,
public/scripts/extensions/tts/vits.js+404 -0
@@ -0,0 +1,404 @@
1import { getPreviewString, saveTtsProviderSettings } from './index.js';
2
3export { VITSTtsProvider };
4
5class VITSTtsProvider {
6 //########//
7 // Config //
8 //########//
9
10 settings;
11 ready = false;
12 voices = [];
13 separator = '. ';
14 audioElement = document.createElement('audio');
15
16 /**
17 * Perform any text processing before passing to TTS engine.
18 * @param {string} text Input text
19 * @returns {string} Processed text
20 */
21 processText(text) {
22 return text;
23 }
24
25 audioFormats = ['wav', 'ogg', 'silk', 'mp3', 'flac'];
26
27 languageLabels = {
28 'Auto': 'auto',
29 'Chinese': 'zh',
30 'English': 'en',
31 'Japanese': 'ja',
32 'Korean': 'ko',
33 };
34
35 langKey2LangCode = {
36 'zh': 'zh-CN',
37 'en': 'en-US',
38 'ja': 'ja-JP',
39 'ko': 'ko-KR',
40 };
41
42 modelTypes = {
43 VITS: 'VITS',
44 W2V2_VITS: 'W2V2-VITS',
45 BERT_VITS2: 'BERT-VITS2',
46 };
47
48 defaultSettings = {
49 provider_endpoint: 'http://localhost:23456',
50 format: 'wav',
51 lang: 'auto',
52 length: 1.0,
53 noise: 0.33,
54 noisew: 0.4,
55 segment_size: 50,
56 streaming: false,
57 dim_emotion: 0,
58 sdp_ratio: 0.2,
59 emotion: 0,
60 text_prompt: '',
61 style_text: '',
62 style_weight: 1,
63 };
64
65 get settingsHtml() {
66 let html = `
67 <label for="vits_lang">Text Language</label>
68 <select id="vits_lang">`;
69
70 for (let language in this.languageLabels) {
71 if (this.languageLabels[language] == this.settings?.lang) {
72 html += `<option value="${this.languageLabels[language]}" selected="selected">${language}</option>`;
73 continue;
74 }
75 html += `<option value="${this.languageLabels[language]}">${language}</option>`;
76 }
77
78 html += `
79 </select>
80 <label>VITS / W2V2-VITS / Bert-VITS2 Settings:</label><br/>
81 <label for="vits_endpoint">Provider Endpoint:</label>
82 <input id="vits_endpoint" type="text" class="text_pole" maxlength="250" value="${this.defaultSettings.provider_endpoint}"/>
83 <span>Use <a target="_blank" href="https://github.com/Artrajz/vits-simple-api">vits-simple-api</a>.</span><br/>
84
85 <label for="vits_format">Audio format:</label>
86 <select id="vits_format">`;
87
88 for (let format of this.audioFormats) {
89 if (format == this.settings?.format) {
90 html += `<option value="${format}" selected="selected">${format}</option>`;
91 continue;
92 }
93 html += `<option value="${format}">${format}</option>`;
94 }
95
96 html += `
97 </select>
98 <label for="vits_length">Audio length: <span id="vits_length_output">${this.defaultSettings.length}</span></label>
99 <input id="vits_length" type="range" value="${this.defaultSettings.length}" min="0.0" max="5" step="0.01" />
100
101 <label for="vits_noise">Noise: <span id="vits_noise_output">${this.defaultSettings.noise}</span></label>
102 <input id="vits_noise" type="range" value="${this.defaultSettings.noise}" min="0.1" max="2" step="0.01" />
103
104 <label for="vits_noisew">SDP noise: <span id="vits_noisew_output">${this.defaultSettings.noisew}</span></label>
105 <input id="vits_noisew" type="range" value="${this.defaultSettings.noisew}" min="0.1" max="2" step="0.01" />
106
107 <label for="vits_segment_size">Segment Size: <span id="vits_segment_size_output">${this.defaultSettings.segment_size}</span></label>
108 <input id="vits_segment_size" type="range" value="${this.defaultSettings.segment_size}" min="0" max="1000" step="1" />
109
110 <label for="vits_streaming" class="checkbox_label">
111 <input id="vits_streaming" type="checkbox" />
112 <span>Streaming</span>
113 </label>
114
115 <label>W2V2-VITS Settings:</label><br/>
116 <label for="vits_dim_emotion">Dimensional emotion:</label>
117 <input id="vits_dim_emotion" type="number" class="text_pole" min="0" max="5457" step="1" value="${this.defaultSettings.dim_emotion}"/>
118
119 <label>BERT-VITS2 Settings:</label><br/>
120 <label for="vits_sdp_ratio">sdp_ratio: <span id="vits_sdp_ratio_output">${this.defaultSettings.sdp_ratio}</span></label>
121 <input id="vits_sdp_ratio" type="range" value="${this.defaultSettings.sdp_ratio}" min="0.0" max="1" step="0.01" />
122
123 <label for="vits_emotion">emotion: <span id="vits_emotion_output">${this.defaultSettings.emotion}</span></label>
124 <input id="vits_emotion" type="range" value="${this.defaultSettings.emotion}" min="0" max="9" step="1" />
125
126 <label for="vits_text_prompt">Text Prompt:</label>
127 <input id="vits_text_prompt" type="text" class="text_pole" maxlength="512" value="${this.defaultSettings.text_prompt}"/>
128
129 <label for="vits_style_text">Style text:</label>
130 <input id="vits_style_text" type="text" class="text_pole" maxlength="512" value="${this.defaultSettings.style_text}"/>
131
132 <label for="vits_style_weight">Style weight <span id="vits_style_weight_output">${this.defaultSettings.style_weight}</span></label>
133 <input id="vits_style_weight" type="range" value="${this.defaultSettings.style_weight}" min="0" max="1" step="0.01" />
134 `;
135
136 return html;
137 }
138
139 onSettingsChange() {
140 // Used when provider settings are updated from UI
141 this.settings.provider_endpoint = $('#vits_endpoint').val();
142 this.settings.lang = $('#vits_lang').val();
143 this.settings.format = $('#vits_format').val();
144 this.settings.dim_emotion = $('#vits_dim_emotion').val();
145 this.settings.text_prompt = $('#vits_text_prompt').val();
146 this.settings.style_text = $('#vits_style_text').val();
147
148 // Update the default TTS settings based on input fields
149 this.settings.length = $('#vits_length').val();
150 this.settings.noise = $('#vits_noise').val();
151 this.settings.noisew = $('#vits_noisew').val();
152 this.settings.segment_size = $('#vits_segment_size').val();
153 this.settings.streaming = $('#vits_streaming').is(':checked');
154 this.settings.sdp_ratio = $('#vits_sdp_ratio').val();
155 this.settings.emotion = $('#vits_emotion').val();
156 this.settings.style_weight = $('#vits_style_weight').val();
157
158 // Update the UI to reflect changes
159 $('#vits_length_output').text(this.settings.length);
160 $('#vits_noise_output').text(this.settings.noise);
161 $('#vits_noisew_output').text(this.settings.noisew);
162 $('#vits_segment_size_output').text(this.settings.segment_size);
163 $('#vits_sdp_ratio_output').text(this.settings.sdp_ratio);
164 $('#vits_emotion_output').text(this.settings.emotion);
165 $('#vits_style_weight_output').text(this.settings.style_weight);
166
167 saveTtsProviderSettings();
168 this.changeTTSSettings();
169 }
170
171 async loadSettings(settings) {
172 // Pupulate Provider UI given input settings
173 if (Object.keys(settings).length == 0) {
174 console.info('Using default TTS Provider settings');
175 }
176
177 // Only accept keys defined in defaultSettings
178 this.settings = this.defaultSettings;
179
180 for (const key in settings) {
181 if (key in this.settings) {
182 this.settings[key] = settings[key];
183 } else {
184 console.debug(`Ignoring non-user-configurable setting: ${key}`);
185 }
186 }
187
188 // Set initial values from the settings
189 $('#vits_endpoint').val(this.settings.provider_endpoint);
190 $('#vits_lang').val(this.settings.lang);
191 $('#vits_format').val(this.settings.format);
192 $('#vits_length').val(this.settings.length);
193 $('#vits_noise').val(this.settings.noise);
194 $('#vits_noisew').val(this.settings.noisew);
195 $('#vits_segment_size').val(this.settings.segment_size);
196 $('#vits_streaming').prop('checked', this.settings.streaming);
197 $('#vits_dim_emotion').val(this.settings.dim_emotion);
198 $('#vits_sdp_ratio').val(this.settings.sdp_ratio);
199 $('#vits_emotion').val(this.settings.emotion);
200 $('#vits_text_prompt').val(this.settings.text_prompt);
201 $('#vits_style_text').val(this.settings.style_text);
202 $('#vits_style_weight').val(this.settings.style_weight);
203
204 // Update the UI to reflect changes
205 $('#vits_length_output').text(this.settings.length);
206 $('#vits_noise_output').text(this.settings.noise);
207 $('#vits_noisew_output').text(this.settings.noisew);
208 $('#vits_segment_size_output').text(this.settings.segment_size);
209 $('#vits_sdp_ratio_output').text(this.settings.sdp_ratio);
210 $('#vits_emotion_output').text(this.settings.emotion);
211 $('#vits_style_weight_output').text(this.settings.style_weight);
212
213 // Register input/change event listeners to update settings on user interaction
214 $('#vits_endpoint').on('input', () => { this.onSettingsChange(); });
215 $('#vits_lang').on('change', () => { this.onSettingsChange(); });
216 $('#vits_format').on('change', () => { this.onSettingsChange(); });
217 $('#vits_length').on('change', () => { this.onSettingsChange(); });
218 $('#vits_noise').on('change', () => { this.onSettingsChange(); });
219 $('#vits_noisew').on('change', () => { this.onSettingsChange(); });
220 $('#vits_segment_size').on('change', () => { this.onSettingsChange(); });
221 $('#vits_streaming').on('change', () => { this.onSettingsChange(); });
222 $('#vits_dim_emotion').on('change', () => { this.onSettingsChange(); });
223 $('#vits_sdp_ratio').on('change', () => { this.onSettingsChange(); });
224 $('#vits_emotion').on('change', () => { this.onSettingsChange(); });
225 $('#vits_text_prompt').on('change', () => { this.onSettingsChange(); });
226 $('#vits_style_text').on('change', () => { this.onSettingsChange(); });
227 $('#vits_style_weight').on('change', () => { this.onSettingsChange(); });
228
229 await this.checkReady();
230
231 console.info('VITS: Settings loaded');
232 }
233
234 // Perform a simple readiness check by trying to fetch voiceIds
235 async checkReady() {
236 await Promise.allSettled([this.fetchTtsVoiceObjects(), this.changeTTSSettings()]);
237 }
238
239 async onRefreshClick() {
240 return;
241 }
242
243 //#################//
244 // TTS Interfaces //
245 //#################//
246
247 async getVoice(voiceName) {
248 if (this.voices.length == 0) {
249 this.voices = await this.fetchTtsVoiceObjects();
250 }
251 const match = this.voices.filter(
252 v => v.name == voiceName,
253 )[0];
254 if (!match) {
255 throw `TTS Voice name ${voiceName} not found`;
256 }
257 return match;
258 }
259
260 async getVoiceById(voiceId) {
261 if (this.voices.length == 0) {
262 this.voices = await this.fetchTtsVoiceObjects();
263 }
264 const match = this.voices.filter(
265 v => v.voice_id == voiceId,
266 )[0];
267 if (!match) {
268 throw `TTS Voice id ${voiceId} not found`;
269 }
270 return match;
271 }
272
273 async generateTts(text, voiceId) {
274 const response = await this.fetchTtsGeneration(text, voiceId);
275 return response;
276 }
277
278 //###########//
279 // API CALLS //
280 //###########//
281 async fetchTtsVoiceObjects() {
282 const response = await fetch(`${this.settings.provider_endpoint}/voice/speakers`);
283 if (!response.ok) {
284 throw new Error(`HTTP ${response.status}: ${await response.json()}`);
285 }
286 const jsonData = await response.json();
287 const voices = [];
288
289 const addVoices = (modelType) => {
290 jsonData[modelType].forEach(voice => {
291 voices.push({
292 name: `[${modelType}] ${voice.name} (${voice.lang})`,
293 voice_id: `${modelType}&${voice.id}`,
294 preview_url: false,
295 lang: voice.lang,
296 });
297 });
298 };
299 for (const key in this.modelTypes) {
300 addVoices(this.modelTypes[key]);
301 }
302
303 this.voices = voices; // Assign to the class property
304 return voices; // Also return this list
305 }
306
307 // Each time a parameter is changed, we change the configuration
308 async changeTTSSettings() {
309 }
310
311 /**
312 * Fetch TTS generation from the API.
313 * @param {string} inputText Text to generate TTS for
314 * @param {string} voiceId Voice ID to use (model_type&speaker_id))
315 * @returns {Promise<Response|string>} Fetch response
316 */
317 async fetchTtsGeneration(inputText, voiceId, lang = null, forceNoStreaming = false) {
318 console.info(`Generating new TTS for voice_id ${voiceId}`);
319
320 const streaming = !forceNoStreaming && this.settings.streaming;
321 const [model_type, speaker_id] = voiceId.split('&');
322 const params = new URLSearchParams();
323 params.append('text', inputText);
324 params.append('id', speaker_id);
325 if (streaming) {
326 params.append('streaming', streaming);
327 // Streaming response only supports MP3
328 }
329 else {
330 params.append('format', this.settings.format);
331 }
332 params.append('lang', lang ?? this.settings.lang);
333 params.append('length', this.settings.length);
334 params.append('noise', this.settings.noise);
335 params.append('noisew', this.settings.noisew);
336 params.append('segment_size', this.settings.segment_size);
337
338 if (model_type == this.modelTypes.W2V2_VITS) {
339 params.append('emotion', this.settings.dim_emotion);
340 }
341 else if (model_type == this.modelTypes.BERT_VITS2) {
342 params.append('sdp_ratio', this.settings.sdp_ratio);
343 params.append('emotion', this.settings.emotion);
344 if (this.settings.text_prompt) {
345 params.append('text_prompt', this.settings.text_prompt);
346 }
347 if (this.settings.style_text) {
348 params.append('style_text', this.settings.style_text);
349 params.append('style_weight', this.settings.style_weight);
350 }
351 }
352
353 const url = `${this.settings.provider_endpoint}/voice/${model_type.toLowerCase()}`;
354
355 if (streaming) {
356 return url + `?${params.toString()}`;
357 }
358
359 const response = await fetch(
360 url,
361 {
362 method: 'POST',
363 headers: {
364 'Content-Type': 'application/x-www-form-urlencoded',
365 },
366 body: params,
367 },
368 );
369 if (!response.ok) {
370 toastr.error(response.statusText, 'TTS Generation Failed');
371 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
372 }
373 return response;
374 }
375
376 /**
377 * Preview TTS for a given voice ID.
378 * @param {string} id Voice ID
379 */
380 async previewTtsVoice(id) {
381 this.audioElement.pause();
382 this.audioElement.currentTime = 0;
383 const voice = await this.getVoiceById(id);
384 const lang = voice.lang.includes(this.settings.lang) ? this.settings.lang : voice.lang[0];
385
386 let lang_code = this.langKey2LangCode[lang];
387 const text = getPreviewString(lang_code);
388 const response = await this.fetchTtsGeneration(text, id, lang, true);
389 if (typeof response != 'string') {
390 if (!response.ok) {
391 throw new Error(`HTTP ${response.status}: ${await response.text()}`);
392 }
393 const audio = await response.blob();
394 const url = URL.createObjectURL(audio);
395 this.audioElement.src = url;
396 this.audioElement.play();
397 }
398 }
399
400 // Interface not used
401 async fetchTtsFromHistory(history_item_id) {
402 return Promise.resolve(history_item_id);
403 }
404}
public/scripts/slash-commands.js+1064 -977
@@ -38,13 +38,13 @@ import {
38 system_message_types,38 system_message_types,
39 this_chid,39 this_chid,
40} from '../script.js';40} from '../script.js';
41import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';41import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
42import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';42import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js';
43import { getMessageTimeStamp } from './RossAscends-mods.js';43import { getMessageTimeStamp } from './RossAscends-mods.js';
44import { hideChatMessageRange } from './chats.js';44import { hideChatMessageRange } from './chats.js';
45import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js';45import { getContext, saveMetadataDebounced } from './extensions.js';
46import { getRegexedString, regex_placement } from './extensions/regex/engine.js';46import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
47import { findGroupMemberId, getGroupMembers, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';47import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js';
48import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';48import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js';
49import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';49import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
50import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';50import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
@@ -53,7 +53,6 @@ import { decodeTextTokens, getFriendlyTokenizerName, getTextTokens, getTokenCoun
53import { debounce, delay, isFalseBoolean, isTrueBoolean, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';53import { debounce, delay, isFalseBoolean, isTrueBoolean, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
54import { registerVariableCommands, resolveVariable } from './variables.js';54import { registerVariableCommands, resolveVariable } from './variables.js';
55import { background_settings } from './backgrounds.js';55import { background_settings } from './backgrounds.js';
56import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
57import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';56import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
58import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';57import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
59import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';58import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
@@ -75,78 +74,79 @@ export const parser = new SlashCommandParser();
75const registerSlashCommand = SlashCommandParser.addCommand.bind(SlashCommandParser);74const registerSlashCommand = SlashCommandParser.addCommand.bind(SlashCommandParser);
76const getSlashCommandsHelp = parser.getHelpString.bind(parser);75const getSlashCommandsHelp = parser.getHelpString.bind(parser);
7776
78SlashCommandParser.addCommandObject(SlashCommand.fromProps({77export function initDefaultSlashCommands() {
79 name: '?',78 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
80 callback: helpCommandCallback,79 name: '?',
81 aliases: ['help'],80 callback: helpCommandCallback,
82 unnamedArgumentList: [SlashCommandArgument.fromProps({81 aliases: ['help'],
83 description: 'help topic',82 unnamedArgumentList: [SlashCommandArgument.fromProps({
84 typeList: [ARGUMENT_TYPE.STRING],83 description: 'help topic',
85 enumList: [
86 new SlashCommandEnumValue('slash', 'slash commands (STscript)', enumTypes.command, '/'),
87 new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, enumIcons.macro),
88 new SlashCommandEnumValue('format', 'chat/text formatting', enumTypes.name, '★'),
89 new SlashCommandEnumValue('hotkeys', 'keyboard shortcuts', enumTypes.enum, '⏎'),
90 ],
91 })],
92 helpString: 'Get help on macros, chat formatting and commands.',
93}));
94SlashCommandParser.addCommandObject(SlashCommand.fromProps({
95 name: 'persona',
96 callback: setNameCallback,
97 namedArgumentList: [
98 new SlashCommandNamedArgument(
99 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)',
100 [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'],
101 ),
102 ],
103 unnamedArgumentList: [
104 SlashCommandArgument.fromProps({
105 description: 'persona name',
106 typeList: [ARGUMENT_TYPE.STRING],84 typeList: [ARGUMENT_TYPE.STRING],
107 isRequired: true,85 enumList: [
108 enumProvider: commonEnumProviders.personas,86 new SlashCommandEnumValue('slash', 'slash commands (STscript)', enumTypes.command, '/'),
109 }),87 new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, enumIcons.macro),
110 ],88 new SlashCommandEnumValue('format', 'chat/text formatting', enumTypes.name, '★'),
111 helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',89 new SlashCommandEnumValue('hotkeys', 'keyboard shortcuts', enumTypes.enum, '⏎'),
112 aliases: ['name'],90 ],
113}));91 })],
114SlashCommandParser.addCommandObject(SlashCommand.fromProps({92 helpString: 'Get help on macros, chat formatting and commands.',
115 name: 'sync',93 }));
116 callback: syncCallback,94 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
117 helpString: 'Syncs the user persona in user-attributed messages in the current chat.',95 name: 'persona',
118}));96 callback: setNameCallback,
119SlashCommandParser.addCommandObject(SlashCommand.fromProps({97 namedArgumentList: [
120 name: 'lock',98 new SlashCommandNamedArgument(
121 callback: lockPersonaCallback,99 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)',
122 aliases: ['bind'],100 [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'],
123 helpString: 'Locks/unlocks a persona (name and avatar) to the current chat',101 ),
124 unnamedArgumentList: [102 ],
125 SlashCommandArgument.fromProps({103 unnamedArgumentList: [
126 description: 'state',104 SlashCommandArgument.fromProps({
127 typeList: [ARGUMENT_TYPE.STRING],105 description: 'persona name',
128 isRequired: true,106 typeList: [ARGUMENT_TYPE.STRING],
129 defaultValue: 'toggle',107 isRequired: true,
130 enumProvider: commonEnumProviders.boolean('onOffToggle'),108 enumProvider: commonEnumProviders.personas,
131 }),109 }),
132 ],110 ],
133}));111 helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.',
134SlashCommandParser.addCommandObject(SlashCommand.fromProps({112 aliases: ['name'],
135 name: 'bg',113 }));
136 callback: setBackgroundCallback,114 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
137 aliases: ['background'],115 name: 'sync',
138 returns: 'the current background',116 callback: syncCallback,
139 unnamedArgumentList: [117 helpString: 'Syncs the user persona in user-attributed messages in the current chat.',
140 SlashCommandArgument.fromProps({118 }));
141 description: 'filename',119 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
142 typeList: [ARGUMENT_TYPE.STRING],120 name: 'lock',
143 isRequired: true,121 callback: lockPersonaCallback,
144 enumProvider: () => [...document.querySelectorAll('.bg_example')]122 aliases: ['bind'],
145 .map(it => new SlashCommandEnumValue(it.getAttribute('bgfile')))123 helpString: 'Locks/unlocks a persona (name and avatar) to the current chat',
146 .filter(it => it.value?.length),124 unnamedArgumentList: [
147 }),125 SlashCommandArgument.fromProps({
148 ],126 description: 'state',
149 helpString: `127 typeList: [ARGUMENT_TYPE.STRING],
128 isRequired: true,
129 defaultValue: 'toggle',
130 enumProvider: commonEnumProviders.boolean('onOffToggle'),
131 }),
132 ],
133 }));
134 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
135 name: 'bg',
136 callback: setBackgroundCallback,
137 aliases: ['background'],
138 returns: 'the current background',
139 unnamedArgumentList: [
140 SlashCommandArgument.fromProps({
141 description: 'filename',
142 typeList: [ARGUMENT_TYPE.STRING],
143 isRequired: true,
144 enumProvider: () => [...document.querySelectorAll('.bg_example')]
145 .map(it => new SlashCommandEnumValue(it.getAttribute('bgfile')))
146 .filter(it => it.value?.length),
147 }),
148 ],
149 helpString: `
150 <div>150 <div>
151 Sets a background according to the provided filename. Partial names allowed.151 Sets a background according to the provided filename. Partial names allowed.
152 </div>152 </div>
@@ -159,35 +159,35 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
159 </ul>159 </ul>
160 </div>160 </div>
161 `,161 `,
162}));162 }));
163SlashCommandParser.addCommandObject(SlashCommand.fromProps({163 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
164 name: 'sendas',164 name: 'sendas',
165 callback: sendMessageAs,165 callback: sendMessageAs,
166 namedArgumentList: [166 namedArgumentList: [
167 SlashCommandNamedArgument.fromProps({167 SlashCommandNamedArgument.fromProps({
168 name: 'name',168 name: 'name',
169 description: 'Character name',169 description: 'Character name',
170 typeList: [ARGUMENT_TYPE.STRING],170 typeList: [ARGUMENT_TYPE.STRING],
171 isRequired: true,171 isRequired: true,
172 enumProvider: commonEnumProviders.characters('character'),172 enumProvider: commonEnumProviders.characters('character'),
173 forceEnum: false,173 forceEnum: false,
174 }),174 }),
175 new SlashCommandNamedArgument(175 new SlashCommandNamedArgument(
176 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',176 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
177 ),177 ),
178 SlashCommandNamedArgument.fromProps({178 SlashCommandNamedArgument.fromProps({
179 name: 'at',179 name: 'at',
180 description: 'position to insert the message',180 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
181 typeList: [ARGUMENT_TYPE.NUMBER],181 typeList: [ARGUMENT_TYPE.NUMBER],
182 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),182 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
183 }),183 }),
184 ],184 ],
185 unnamedArgumentList: [185 unnamedArgumentList: [
186 new SlashCommandArgument(186 new SlashCommandArgument(
187 'text', [ARGUMENT_TYPE.STRING], true,187 'text', [ARGUMENT_TYPE.STRING], true,
188 ),188 ),
189 ],189 ],
190 helpString: `190 helpString: `
191 <div>191 <div>
192 Sends a message as a specific character. Uses the character avatar if it exists in the characters list.192 Sends a message as a specific character. Uses the character avatar if it exists in the characters list.
193 </div>193 </div>
@@ -204,33 +204,33 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
204 If "compact" is set to true, the message is sent using a compact layout.204 If "compact" is set to true, the message is sent using a compact layout.
205 </div>205 </div>
206 `,206 `,
207}));207 }));
208SlashCommandParser.addCommandObject(SlashCommand.fromProps({208 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
209 name: 'sys',209 name: 'sys',
210 callback: sendNarratorMessage,210 callback: sendNarratorMessage,
211 aliases: ['nar'],211 aliases: ['nar'],
212 namedArgumentList: [212 namedArgumentList: [
213 new SlashCommandNamedArgument(213 new SlashCommandNamedArgument(
214 'compact',214 'compact',
215 'compact layout',215 'compact layout',
216 [ARGUMENT_TYPE.BOOLEAN],216 [ARGUMENT_TYPE.BOOLEAN],
217 false,217 false,
218 false,218 false,
219 'false',219 'false',
220 ),220 ),
221 SlashCommandNamedArgument.fromProps({221 SlashCommandNamedArgument.fromProps({
222 name: 'at',222 name: 'at',
223 description: 'position to insert the message',223 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
224 typeList: [ARGUMENT_TYPE.NUMBER],224 typeList: [ARGUMENT_TYPE.NUMBER],
225 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),225 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
226 }),226 }),
227 ],227 ],
228 unnamedArgumentList: [228 unnamedArgumentList: [
229 new SlashCommandArgument(229 new SlashCommandArgument(
230 'text', [ARGUMENT_TYPE.STRING], true,230 'text', [ARGUMENT_TYPE.STRING], true,
231 ),231 ),
232 ],232 ],
233 helpString: `233 helpString: `
234 <div>234 <div>
235 Sends a message as a system narrator.235 Sends a message as a system narrator.
236 </div>236 </div>
@@ -249,44 +249,44 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
249 </ul>249 </ul>
250 </div>250 </div>
251 `,251 `,
252}));252 }));
253SlashCommandParser.addCommandObject(SlashCommand.fromProps({253 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
254 name: 'sysname',254 name: 'sysname',
255 callback: setNarratorName,255 callback: setNarratorName,
256 unnamedArgumentList: [256 unnamedArgumentList: [
257 new SlashCommandArgument(257 new SlashCommandArgument(
258 'name', [ARGUMENT_TYPE.STRING], false,258 'name', [ARGUMENT_TYPE.STRING], false,
259 ),259 ),
260 ],260 ],
261 helpString: 'Sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.',261 helpString: 'Sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.',
262}));262 }));
263SlashCommandParser.addCommandObject(SlashCommand.fromProps({263 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
264 name: 'comment',264 name: 'comment',
265 callback: sendCommentMessage,265 callback: sendCommentMessage,
266 namedArgumentList: [266 namedArgumentList: [
267 new SlashCommandNamedArgument(267 new SlashCommandNamedArgument(
268 'compact',268 'compact',
269 'Whether to use a compact layout',269 'Whether to use a compact layout',
270 [ARGUMENT_TYPE.BOOLEAN],270 [ARGUMENT_TYPE.BOOLEAN],
271 false,271 false,
272 false,272 false,
273 'false',273 'false',
274 ),274 ),
275 SlashCommandNamedArgument.fromProps({275 SlashCommandNamedArgument.fromProps({
276 name: 'at',276 name: 'at',
277 description: 'position to insert the message',277 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
278 typeList: [ARGUMENT_TYPE.NUMBER],278 typeList: [ARGUMENT_TYPE.NUMBER],
279 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),279 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
280 }),280 }),
281 ],281 ],
282 unnamedArgumentList: [282 unnamedArgumentList: [
283 new SlashCommandArgument(283 new SlashCommandArgument(
284 'text',284 'text',
285 [ARGUMENT_TYPE.STRING],285 [ARGUMENT_TYPE.STRING],
286 true,286 true,
287 ),287 ),
288 ],288 ],
289 helpString: `289 helpString: `
290 <div>290 <div>
291 Adds a note/comment message not part of the chat.291 Adds a note/comment message not part of the chat.
292 </div>292 </div>
@@ -305,35 +305,35 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
305 </ul>305 </ul>
306 </div>306 </div>
307 `,307 `,
308}));308 }));
309SlashCommandParser.addCommandObject(SlashCommand.fromProps({309 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
310 name: 'single',310 name: 'single',
311 callback: setStoryModeCallback,311 callback: setStoryModeCallback,
312 aliases: ['story'],312 aliases: ['story'],
313 helpString: 'Sets the message style to single document mode without names or avatars visible.',313 helpString: 'Sets the message style to single document mode without names or avatars visible.',
314}));314 }));
315SlashCommandParser.addCommandObject(SlashCommand.fromProps({315 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
316 name: 'bubble',316 name: 'bubble',
317 callback: setBubbleModeCallback,317 callback: setBubbleModeCallback,
318 aliases: ['bubbles'],318 aliases: ['bubbles'],
319 helpString: 'Sets the message style to bubble chat mode.',319 helpString: 'Sets the message style to bubble chat mode.',
320}));320 }));
321SlashCommandParser.addCommandObject(SlashCommand.fromProps({321 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
322 name: 'flat',322 name: 'flat',
323 callback: setFlatModeCallback,323 callback: setFlatModeCallback,
324 aliases: ['default'],324 aliases: ['default'],
325 helpString: 'Sets the message style to flat chat mode.',325 helpString: 'Sets the message style to flat chat mode.',
326}));326 }));
327SlashCommandParser.addCommandObject(SlashCommand.fromProps({327 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
328 name: 'continue',328 name: 'continue',
329 callback: continueChatCallback,329 callback: continueChatCallback,
330 aliases: ['cont'],330 aliases: ['cont'],
331 unnamedArgumentList: [331 unnamedArgumentList: [
332 new SlashCommandArgument(332 new SlashCommandArgument(
333 'prompt', [ARGUMENT_TYPE.STRING], false,333 'prompt', [ARGUMENT_TYPE.STRING], false,
334 ),334 ),
335 ],335 ],
336 helpString: `336 helpString: `
337 <div>337 <div>
338 Continues the last message in the chat, with an optional additional prompt.338 Continues the last message in the chat, with an optional additional prompt.
339 </div>339 </div>
@@ -351,87 +351,87 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
351 </ul>351 </ul>
352 </div>352 </div>
353 `,353 `,
354}));354 }));
355SlashCommandParser.addCommandObject(SlashCommand.fromProps({355 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
356 name: 'go',356 name: 'go',
357 callback: goToCharacterCallback,357 callback: goToCharacterCallback,
358 unnamedArgumentList: [358 unnamedArgumentList: [
359 SlashCommandArgument.fromProps({359 SlashCommandArgument.fromProps({
360 description: 'name',360 description: 'name',
361 typeList: [ARGUMENT_TYPE.STRING],361 typeList: [ARGUMENT_TYPE.STRING],
362 isRequired: true,362 isRequired: true,
363 enumProvider: commonEnumProviders.characters('all'),363 enumProvider: commonEnumProviders.characters('all'),
364 }),364 }),
365 ],365 ],
366 helpString: 'Opens up a chat with the character or group by its name',366 helpString: 'Opens up a chat with the character or group by its name',
367 aliases: ['char'],367 aliases: ['char'],
368}));368 }));
369SlashCommandParser.addCommandObject(SlashCommand.fromProps({369 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
370 name: 'rename-char',370 name: 'rename-char',
371 /** @param {{silent: string, chats: string}} options @param {string} name */371 /** @param {{silent: string, chats: string}} options @param {string} name */
372 callback: async ({ silent = 'true', chats = null }, name) => {372 callback: async ({ silent = 'true', chats = null }, name) => {
373 const renamed = await renameCharacter(name, { silent: isTrueBoolean(silent), renameChats: chats !== null ? isTrueBoolean(chats) : null });373 const renamed = await renameCharacter(name, { silent: isTrueBoolean(silent), renameChats: chats !== null ? isTrueBoolean(chats) : null });
374 return String(renamed);374 return String(renamed);
375 },375 },
376 returns: 'true/false - Whether the rename was successful',376 returns: 'true/false - Whether the rename was successful',
377 namedArgumentList: [377 namedArgumentList: [
378 new SlashCommandNamedArgument(378 new SlashCommandNamedArgument(
379 'silent', 'Hide any blocking popups. (if false, the name is optional. If not supplied, a popup asking for it will appear)', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',379 'silent', 'Hide any blocking popups. (if false, the name is optional. If not supplied, a popup asking for it will appear)', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true',
380 ),380 ),
381 new SlashCommandNamedArgument(381 new SlashCommandNamedArgument(
382 'chats', 'Rename char in all previous chats', [ARGUMENT_TYPE.BOOLEAN], false, false, '<null>',382 'chats', 'Rename char in all previous chats', [ARGUMENT_TYPE.BOOLEAN], false, false, '<null>',
383 ),383 ),
384 ],384 ],
385 unnamedArgumentList: [385 unnamedArgumentList: [
386 new SlashCommandArgument(386 new SlashCommandArgument(
387 'new char name', [ARGUMENT_TYPE.STRING], true,387 'new char name', [ARGUMENT_TYPE.STRING], true,
388 ),388 ),
389 ],389 ],
390 helpString: 'Renames the current character.',390 helpString: 'Renames the current character.',
391}));391 }));
392SlashCommandParser.addCommandObject(SlashCommand.fromProps({392 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
393 name: 'sysgen',393 name: 'sysgen',
394 callback: generateSystemMessage,394 callback: generateSystemMessage,
395 unnamedArgumentList: [395 unnamedArgumentList: [
396 new SlashCommandArgument(396 new SlashCommandArgument(
397 'prompt', [ARGUMENT_TYPE.STRING], true,397 'prompt', [ARGUMENT_TYPE.STRING], true,
398 ),398 ),
399 ],399 ],
400 helpString: 'Generates a system message using a specified prompt.',400 helpString: 'Generates a system message using a specified prompt.',
401}));401 }));
402SlashCommandParser.addCommandObject(SlashCommand.fromProps({402 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
403 name: 'ask',403 name: 'ask',
404 callback: askCharacter,404 callback: askCharacter,
405 namedArgumentList: [405 namedArgumentList: [
406 SlashCommandNamedArgument.fromProps({406 SlashCommandNamedArgument.fromProps({
407 name: 'name',407 name: 'name',
408 description: 'character name',408 description: 'character name',
409 typeList: [ARGUMENT_TYPE.STRING],409 typeList: [ARGUMENT_TYPE.STRING],
410 isRequired: true,410 isRequired: true,
411 enumProvider: commonEnumProviders.characters('character'),411 enumProvider: commonEnumProviders.characters('character'),
412 }),412 }),
413 ],413 ],
414 unnamedArgumentList: [414 unnamedArgumentList: [
415 new SlashCommandArgument(415 new SlashCommandArgument(
416 'prompt', [ARGUMENT_TYPE.STRING], true, false,416 'prompt', [ARGUMENT_TYPE.STRING], true, false,
417 ),417 ),
418 ],418 ],
419 helpString: 'Asks a specified character card a prompt. Character name must be provided in a named argument.',419 helpString: 'Asks a specified character card a prompt. Character name must be provided in a named argument.',
420}));420 }));
421SlashCommandParser.addCommandObject(SlashCommand.fromProps({421 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
422 name: 'delname',422 name: 'delname',
423 callback: deleteMessagesByNameCallback,423 callback: deleteMessagesByNameCallback,
424 namedArgumentList: [],424 namedArgumentList: [],
425 unnamedArgumentList: [425 unnamedArgumentList: [
426 SlashCommandArgument.fromProps({426 SlashCommandArgument.fromProps({
427 description: 'name',427 description: 'name',
428 typeList: [ARGUMENT_TYPE.STRING],428 typeList: [ARGUMENT_TYPE.STRING],
429 isRequired: true,429 isRequired: true,
430 enumProvider: commonEnumProviders.characters('character'),430 enumProvider: commonEnumProviders.characters('character'),
431 }),431 }),
432 ],432 ],
433 aliases: ['cancel'],433 aliases: ['cancel'],
434 helpString: `434 helpString: `
435 <div>435 <div>
436 Deletes all messages attributed to a specified name.436 Deletes all messages attributed to a specified name.
437 </div>437 </div>
@@ -444,41 +444,41 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
444 </ul>444 </ul>
445 </div>445 </div>
446 `,446 `,
447}));447 }));
448SlashCommandParser.addCommandObject(SlashCommand.fromProps({448 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
449 name: 'send',449 name: 'send',
450 callback: sendUserMessageCallback,450 callback: sendUserMessageCallback,
451 namedArgumentList: [451 namedArgumentList: [
452 new SlashCommandNamedArgument(452 new SlashCommandNamedArgument(
453 'compact',453 'compact',
454 'whether to use a compact layout',454 'whether to use a compact layout',
455 [ARGUMENT_TYPE.BOOLEAN],455 [ARGUMENT_TYPE.BOOLEAN],
456 false,456 false,
457 false,457 false,
458 'false',458 'false',
459 ),459 ),
460 SlashCommandNamedArgument.fromProps({460 SlashCommandNamedArgument.fromProps({
461 name: 'at',461 name: 'at',
462 description: 'position to insert the message',462 description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.',
463 typeList: [ARGUMENT_TYPE.NUMBER],463 typeList: [ARGUMENT_TYPE.NUMBER],
464 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),464 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
465 }),465 }),
466 SlashCommandNamedArgument.fromProps({466 SlashCommandNamedArgument.fromProps({
467 name: 'name',467 name: 'name',
468 description: 'display name',468 description: 'display name',
469 typeList: [ARGUMENT_TYPE.STRING],469 typeList: [ARGUMENT_TYPE.STRING],
470 defaultValue: '{{user}}',470 defaultValue: '{{user}}',
471 enumProvider: commonEnumProviders.characters('character'),471 enumProvider: commonEnumProviders.characters('character'),
472 }),472 }),
473 ],473 ],
474 unnamedArgumentList: [474 unnamedArgumentList: [
475 new SlashCommandArgument(475 new SlashCommandArgument(
476 'text',476 'text',
477 [ARGUMENT_TYPE.STRING],477 [ARGUMENT_TYPE.STRING],
478 true,478 true,
479 ),479 ),
480 ],480 ],
481 helpString: `481 helpString: `
482 <div>482 <div>
483 Adds a user message to the chat log without triggering a generation.483 Adds a user message to the chat log without triggering a generation.
484 </div>484 </div>
@@ -500,29 +500,29 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
500 </ul>500 </ul>
501 </div>501 </div>
502 `,502 `,
503}));503 }));
504SlashCommandParser.addCommandObject(SlashCommand.fromProps({504 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
505 name: 'trigger',505 name: 'trigger',
506 callback: triggerGenerationCallback,506 callback: triggerGenerationCallback,
507 namedArgumentList: [507 namedArgumentList: [
508 new SlashCommandNamedArgument(508 new SlashCommandNamedArgument(
509 'await',509 'await',
510 'Whether to await for the triggered generation before continuing',510 'Whether to await for the triggered generation before continuing',
511 [ARGUMENT_TYPE.BOOLEAN],511 [ARGUMENT_TYPE.BOOLEAN],
512 false,512 false,
513 false,513 false,
514 'false',514 'false',
515 ),515 ),
516 ],516 ],
517 unnamedArgumentList: [517 unnamedArgumentList: [
518 SlashCommandArgument.fromProps({518 SlashCommandArgument.fromProps({
519 description: 'group member index (starts with 0) or name',519 description: 'group member index (starts with 0) or name',
520 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],520 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
521 isRequired: false,521 isRequired: false,
522 enumProvider: commonEnumProviders.groupMembers(),522 enumProvider: commonEnumProviders.groupMembers(),
523 }),523 }),
524 ],524 ],
525 helpString: `525 helpString: `
526 <div>526 <div>
527 Triggers a message generation. If in group, can trigger a message for the specified group member index or name.527 Triggers a message generation. If in group, can trigger a message for the specified group member index or name.
528 </div>528 </div>
@@ -530,74 +530,74 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
530 If <code>await=true</code> named argument is passed, the command will await for the triggered generation before continuing.530 If <code>await=true</code> named argument is passed, the command will await for the triggered generation before continuing.
531 </div>531 </div>
532 `,532 `,
533}));533 }));
534SlashCommandParser.addCommandObject(SlashCommand.fromProps({534 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
535 name: 'hide',535 name: 'hide',
536 callback: hideMessageCallback,536 callback: hideMessageCallback,
537 unnamedArgumentList: [537 unnamedArgumentList: [
538 SlashCommandArgument.fromProps({538 SlashCommandArgument.fromProps({
539 description: 'message index (starts with 0) or range',539 description: 'message index (starts with 0) or range',
540 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],540 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
541 isRequired: true,541 isRequired: true,
542 enumProvider: commonEnumProviders.messages(),542 enumProvider: commonEnumProviders.messages(),
543 }),543 }),
544 ],544 ],
545 helpString: 'Hides a chat message from the prompt.',545 helpString: 'Hides a chat message from the prompt.',
546}));546 }));
547SlashCommandParser.addCommandObject(SlashCommand.fromProps({547 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
548 name: 'unhide',548 name: 'unhide',
549 callback: unhideMessageCallback,549 callback: unhideMessageCallback,
550 unnamedArgumentList: [550 unnamedArgumentList: [
551 SlashCommandArgument.fromProps({551 SlashCommandArgument.fromProps({
552 description: 'message index (starts with 0) or range',552 description: 'message index (starts with 0) or range',
553 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],553 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
554 isRequired: true,554 isRequired: true,
555 enumProvider: commonEnumProviders.messages(),555 enumProvider: commonEnumProviders.messages(),
556 }),556 }),
557 ],557 ],
558 helpString: 'Unhides a message from the prompt.',558 helpString: 'Unhides a message from the prompt.',
559}));559 }));
560SlashCommandParser.addCommandObject(SlashCommand.fromProps({560 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
561 name: 'member-disable',561 name: 'member-disable',
562 callback: disableGroupMemberCallback,562 callback: disableGroupMemberCallback,
563 aliases: ['disable', 'disablemember', 'memberdisable'],563 aliases: ['disable', 'disablemember', 'memberdisable'],
564 unnamedArgumentList: [564 unnamedArgumentList: [
565 SlashCommandArgument.fromProps({565 SlashCommandArgument.fromProps({
566 description: 'member index (starts with 0) or name',566 description: 'member index (starts with 0) or name',
567 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],567 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
568 isRequired: true,568 isRequired: true,
569 enumProvider: commonEnumProviders.groupMembers(),569 enumProvider: commonEnumProviders.groupMembers(),
570 }),570 }),
571 ],571 ],
572 helpString: 'Disables a group member from being drafted for replies.',572 helpString: 'Disables a group member from being drafted for replies.',
573}));573 }));
574SlashCommandParser.addCommandObject(SlashCommand.fromProps({574 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
575 name: 'member-enable',575 name: 'member-enable',
576 aliases: ['enable', 'enablemember', 'memberenable'],576 aliases: ['enable', 'enablemember', 'memberenable'],
577 callback: enableGroupMemberCallback,577 callback: enableGroupMemberCallback,
578 unnamedArgumentList: [578 unnamedArgumentList: [
579 SlashCommandArgument.fromProps({579 SlashCommandArgument.fromProps({
580 description: 'member index (starts with 0) or name',580 description: 'member index (starts with 0) or name',
581 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],581 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
582 isRequired: true,582 isRequired: true,
583 enumProvider: commonEnumProviders.groupMembers(),583 enumProvider: commonEnumProviders.groupMembers(),
584 }),584 }),
585 ],585 ],
586 helpString: 'Enables a group member to be drafted for replies.',586 helpString: 'Enables a group member to be drafted for replies.',
587}));587 }));
588SlashCommandParser.addCommandObject(SlashCommand.fromProps({588 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
589 name: 'member-add',589 name: 'member-add',
590 callback: addGroupMemberCallback,590 callback: addGroupMemberCallback,
591 aliases: ['addmember', 'memberadd'],591 aliases: ['addmember', 'memberadd'],
592 unnamedArgumentList: [592 unnamedArgumentList: [
593 SlashCommandArgument.fromProps({593 SlashCommandArgument.fromProps({
594 description: 'character name',594 description: 'character name',
595 typeList: [ARGUMENT_TYPE.STRING],595 typeList: [ARGUMENT_TYPE.STRING],
596 isRequired: true,596 isRequired: true,
597 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],597 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],
598 }),598 }),
599 ],599 ],
600 helpString: `600 helpString: `
601 <div>601 <div>
602 Adds a new group member to the group chat.602 Adds a new group member to the group chat.
603 </div>603 </div>
@@ -610,20 +610,20 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
610 </ul>610 </ul>
611 </div>611 </div>
612 `,612 `,
613}));613 }));
614SlashCommandParser.addCommandObject(SlashCommand.fromProps({614 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
615 name: 'member-remove',615 name: 'member-remove',
616 callback: removeGroupMemberCallback,616 callback: removeGroupMemberCallback,
617 aliases: ['removemember', 'memberremove'],617 aliases: ['removemember', 'memberremove'],
618 unnamedArgumentList: [618 unnamedArgumentList: [
619 SlashCommandArgument.fromProps({619 SlashCommandArgument.fromProps({
620 description: 'member index (starts with 0) or name',620 description: 'member index (starts with 0) or name',
621 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],621 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
622 isRequired: true,622 isRequired: true,
623 enumProvider: commonEnumProviders.groupMembers(),623 enumProvider: commonEnumProviders.groupMembers(),
624 }),624 }),
625 ],625 ],
626 helpString: `626 helpString: `
627 <div>627 <div>
628 Removes a group member from the group chat.628 Removes a group member from the group chat.
629 </div>629 </div>
@@ -637,47 +637,47 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
637 </ul>637 </ul>
638 </div>638 </div>
639 `,639 `,
640}));640 }));
641SlashCommandParser.addCommandObject(SlashCommand.fromProps({641 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
642 name: 'member-up',642 name: 'member-up',
643 callback: moveGroupMemberUpCallback,643 callback: moveGroupMemberUpCallback,
644 aliases: ['upmember', 'memberup'],644 aliases: ['upmember', 'memberup'],
645 unnamedArgumentList: [645 unnamedArgumentList: [
646 SlashCommandArgument.fromProps({646 SlashCommandArgument.fromProps({
647 description: 'member index (starts with 0) or name',647 description: 'member index (starts with 0) or name',
648 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],648 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
649 isRequired: true,649 isRequired: true,
650 enumProvider: commonEnumProviders.groupMembers(),650 enumProvider: commonEnumProviders.groupMembers(),
651 }),651 }),
652 ],652 ],
653 helpString: 'Moves a group member up in the group chat list.',653 helpString: 'Moves a group member up in the group chat list.',
654}));654 }));
655SlashCommandParser.addCommandObject(SlashCommand.fromProps({655 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
656 name: 'member-down',656 name: 'member-down',
657 callback: moveGroupMemberDownCallback,657 callback: moveGroupMemberDownCallback,
658 aliases: ['downmember', 'memberdown'],658 aliases: ['downmember', 'memberdown'],
659 unnamedArgumentList: [659 unnamedArgumentList: [
660 SlashCommandArgument.fromProps({660 SlashCommandArgument.fromProps({
661 description: 'member index (starts with 0) or name',661 description: 'member index (starts with 0) or name',
662 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],662 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
663 isRequired: true,663 isRequired: true,
664 enumProvider: commonEnumProviders.groupMembers(),664 enumProvider: commonEnumProviders.groupMembers(),
665 }),665 }),
666 ],666 ],
667 helpString: 'Moves a group member down in the group chat list.',667 helpString: 'Moves a group member down in the group chat list.',
668}));668 }));
669SlashCommandParser.addCommandObject(SlashCommand.fromProps({669 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
670 name: 'peek',670 name: 'peek',
671 callback: peekCallback,671 callback: peekCallback,
672 unnamedArgumentList: [672 unnamedArgumentList: [
673 SlashCommandArgument.fromProps({673 SlashCommandArgument.fromProps({
674 description: 'member index (starts with 0) or name',674 description: 'member index (starts with 0) or name',
675 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],675 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
676 isRequired: true,676 isRequired: true,
677 enumProvider: commonEnumProviders.groupMembers(),677 enumProvider: commonEnumProviders.groupMembers(),
678 }),678 }),
679 ],679 ],
680 helpString: `680 helpString: `
681 <div>681 <div>
682 Shows a group member character card without switching chats.682 Shows a group member character card without switching chats.
683 </div>683 </div>
@@ -691,22 +691,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
691 </ul>691 </ul>
692 </div>692 </div>
693 `,693 `,
694}));694 }));
695SlashCommandParser.addCommandObject(SlashCommand.fromProps({695 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
696 name: 'delswipe',696 name: 'delswipe',
697 callback: deleteSwipeCallback,697 callback: deleteSwipeCallback,
698 aliases: ['swipedel'],698 aliases: ['swipedel'],
699 unnamedArgumentList: [699 unnamedArgumentList: [
700 SlashCommandArgument.fromProps({700 SlashCommandArgument.fromProps({
701 description: '1-based swipe id',701 description: '1-based swipe id',
702 typeList: [ARGUMENT_TYPE.NUMBER],702 typeList: [ARGUMENT_TYPE.NUMBER],
703 isRequired: true,703 isRequired: true,
704 enumProvider: () => Array.isArray(chat[chat.length - 1]?.swipes) ?704 enumProvider: () => Array.isArray(chat[chat.length - 1]?.swipes) ?
705 chat[chat.length - 1].swipes.map((/** @type {string} */ swipe, /** @type {number} */ i) => new SlashCommandEnumValue(String(i + 1), swipe, enumTypes.enum, enumIcons.message))705 chat[chat.length - 1].swipes.map((/** @type {string} */ swipe, /** @type {number} */ i) => new SlashCommandEnumValue(String(i + 1), swipe, enumTypes.enum, enumIcons.message))
706 : [],706 : [],
707 }),707 }),
708 ],708 ],
709 helpString: `709 helpString: `
710 <div>710 <div>
711 Deletes a swipe from the last chat message. If swipe id is not provided, it deletes the current swipe.711 Deletes a swipe from the last chat message. If swipe id is not provided, it deletes the current swipe.
712 </div>712 </div>
@@ -724,34 +724,60 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
724 </ul>724 </ul>
725 </div>725 </div>
726 `,726 `,
727}));727 }));
728SlashCommandParser.addCommandObject(SlashCommand.fromProps({728 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
729 name: 'echo',729 name: 'echo',
730 callback: echoCallback,730 callback: echoCallback,
731 returns: 'the text',731 returns: 'the text',
732 namedArgumentList: [732 namedArgumentList: [
733 new SlashCommandNamedArgument(733 new SlashCommandNamedArgument(
734 'title', 'title of the toast message', [ARGUMENT_TYPE.STRING], false,734 'title', 'title of the toast message', [ARGUMENT_TYPE.STRING], false,
735 ),735 ),
736 SlashCommandNamedArgument.fromProps({736 SlashCommandNamedArgument.fromProps({
737 name: 'severity',737 name: 'severity',
738 description: 'severity level of the toast message',738 description: 'severity level of the toast message',
739 typeList: [ARGUMENT_TYPE.STRING],739 typeList: [ARGUMENT_TYPE.STRING],
740 defaultValue: 'info',740 defaultValue: 'info',
741 enumProvider: () => [741 enumProvider: () => [
742 new SlashCommandEnumValue('info', 'info', enumTypes.macro, 'ℹ️'),742 new SlashCommandEnumValue('info', 'info', enumTypes.macro, 'ℹ️'),
743 new SlashCommandEnumValue('warning', 'warning', enumTypes.enum, '⚠️'),743 new SlashCommandEnumValue('warning', 'warning', enumTypes.enum, '⚠️'),
744 new SlashCommandEnumValue('error', 'error', enumTypes.enum, '❗'),744 new SlashCommandEnumValue('error', 'error', enumTypes.enum, '❗'),
745 new SlashCommandEnumValue('success', 'success', enumTypes.enum, '✅'),745 new SlashCommandEnumValue('success', 'success', enumTypes.enum, '✅'),
746 ],746 ],
747 }),747 }),
748 ],748 SlashCommandNamedArgument.fromProps({
749 unnamedArgumentList: [749 name: 'timeout',
750 new SlashCommandArgument(750 description: 'time in milliseconds to display the toast message. Set this and \'extendedTimeout\' to 0 to show indefinitely until dismissed.',
751 'text', [ARGUMENT_TYPE.STRING], true,751 typeList: [ARGUMENT_TYPE.NUMBER],
752 ),752 defaultValue: `${toastr.options.timeOut}`,
753 ],753 }),
754 helpString: `754 SlashCommandNamedArgument.fromProps({
755 name: 'extendedTimeout',
756 description: 'time in milliseconds to display the toast message. Set this and \'timeout\' to 0 to show indefinitely until dismissed.',
757 typeList: [ARGUMENT_TYPE.NUMBER],
758 defaultValue: `${toastr.options.extendedTimeOut}`,
759 }),
760 SlashCommandNamedArgument.fromProps({
761 name: 'preventDuplicates',
762 description: 'prevent duplicate toasts with the same message from being displayed.',
763 typeList: [ARGUMENT_TYPE.BOOLEAN],
764 defaultValue: 'false',
765 enumList: commonEnumProviders.boolean('trueFalse')(),
766 }),
767 SlashCommandNamedArgument.fromProps({
768 name: 'awaitDismissal',
769 description: 'wait for the toast to be dismissed before continuing.',
770 typeList: [ARGUMENT_TYPE.BOOLEAN],
771 defaultValue: 'false',
772 enumList: commonEnumProviders.boolean('trueFalse')(),
773 }),
774 ],
775 unnamedArgumentList: [
776 new SlashCommandArgument(
777 'text', [ARGUMENT_TYPE.STRING], true,
778 ),
779 ],
780 helpString: `
755 <div>781 <div>
756 Echoes the provided text to a toast message. Useful for pipes debugging.782 Echoes the provided text to a toast message. Useful for pipes debugging.
757 </div>783 </div>
@@ -764,42 +790,42 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
764 </ul>790 </ul>
765 </div>791 </div>
766 `,792 `,
767}));793 }));
768SlashCommandParser.addCommandObject(SlashCommand.fromProps({794 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
769 name: 'gen',795 name: 'gen',
770 callback: generateCallback,796 callback: generateCallback,
771 returns: 'generated text',797 returns: 'generated text',
772 namedArgumentList: [798 namedArgumentList: [
773 new SlashCommandNamedArgument(799 new SlashCommandNamedArgument(
774 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),800 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
775 ),801 ),
776 SlashCommandNamedArgument.fromProps({802 SlashCommandNamedArgument.fromProps({
777 name: 'name',803 name: 'name',
778 description: 'in-prompt name for instruct mode',804 description: 'in-prompt name for instruct mode',
779 typeList: [ARGUMENT_TYPE.STRING],805 typeList: [ARGUMENT_TYPE.STRING],
780 defaultValue: 'System',806 defaultValue: 'System',
781 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],807 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
782 forceEnum: false,808 forceEnum: false,
783 }),809 }),
784 new SlashCommandNamedArgument(810 new SlashCommandNamedArgument(
785 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,811 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,
786 ),812 ),
787 SlashCommandNamedArgument.fromProps({813 SlashCommandNamedArgument.fromProps({
788 name: 'as',814 name: 'as',
789 description: 'role of the output prompt',815 description: 'role of the output prompt',
790 typeList: [ARGUMENT_TYPE.STRING],816 typeList: [ARGUMENT_TYPE.STRING],
791 enumList: [817 enumList: [
792 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant),818 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant),
793 new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character),819 new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character),
794 ],820 ],
795 }),821 }),
796 ],822 ],
797 unnamedArgumentList: [823 unnamedArgumentList: [
798 new SlashCommandArgument(824 new SlashCommandArgument(
799 'prompt', [ARGUMENT_TYPE.STRING], true,825 'prompt', [ARGUMENT_TYPE.STRING], true,
800 ),826 ),
801 ],827 ],
802 helpString: `828 helpString: `
803 <div>829 <div>
804 Generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System").830 Generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System").
805 </div>831 </div>
@@ -807,43 +833,43 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
807 "as" argument controls the role of the output prompt: system (default) or char. If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.833 "as" argument controls the role of the output prompt: system (default) or char. If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.
808 </div>834 </div>
809 `,835 `,
810}));836 }));
811SlashCommandParser.addCommandObject(SlashCommand.fromProps({837 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
812 name: 'genraw',838 name: 'genraw',
813 callback: generateRawCallback,839 callback: generateRawCallback,
814 returns: 'generated text',840 returns: 'generated text',
815 namedArgumentList: [841 namedArgumentList: [
816 new SlashCommandNamedArgument(842 new SlashCommandNamedArgument(
817 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),843 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
818 ),844 ),
819 new SlashCommandNamedArgument(845 new SlashCommandNamedArgument(
820 'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(),846 'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(),
821 ),847 ),
822 new SlashCommandNamedArgument(848 new SlashCommandNamedArgument(
823 'stop', 'one-time custom stop strings', [ARGUMENT_TYPE.LIST], false,849 'stop', 'one-time custom stop strings', [ARGUMENT_TYPE.LIST], false,
824 ),850 ),
825 SlashCommandNamedArgument.fromProps({851 SlashCommandNamedArgument.fromProps({
826 name: 'as',852 name: 'as',
827 description: 'role of the output prompt',853 description: 'role of the output prompt',
828 typeList: [ARGUMENT_TYPE.STRING],854 typeList: [ARGUMENT_TYPE.STRING],
829 enumList: [855 enumList: [
830 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant),856 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant),
831 new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character),857 new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character),
832 ],858 ],
833 }),859 }),
834 new SlashCommandNamedArgument(860 new SlashCommandNamedArgument(
835 'system', 'system prompt at the start', [ARGUMENT_TYPE.STRING], false,861 'system', 'system prompt at the start', [ARGUMENT_TYPE.STRING], false,
836 ),862 ),
837 new SlashCommandNamedArgument(863 new SlashCommandNamedArgument(
838 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,864 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,
839 ),865 ),
840 ],866 ],
841 unnamedArgumentList: [867 unnamedArgumentList: [
842 new SlashCommandArgument(868 new SlashCommandArgument(
843 'prompt', [ARGUMENT_TYPE.STRING], true,869 'prompt', [ARGUMENT_TYPE.STRING], true,
844 ),870 ),
845 ],871 ],
846 helpString: `872 helpString: `
847 <div>873 <div>
848 Generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card.874 Generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card.
849 </div>875 </div>
@@ -860,55 +886,55 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
860 If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.886 If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.
861 </div>887 </div>
862 `,888 `,
863}));889 }));
864SlashCommandParser.addCommandObject(SlashCommand.fromProps({890 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
865 name: 'addswipe',891 name: 'addswipe',
866 callback: addSwipeCallback,892 callback: addSwipeCallback,
867 aliases: ['swipeadd'],893 aliases: ['swipeadd'],
868 unnamedArgumentList: [894 unnamedArgumentList: [
869 new SlashCommandArgument(895 new SlashCommandArgument(
870 'text', [ARGUMENT_TYPE.STRING], true,896 'text', [ARGUMENT_TYPE.STRING], true,
871 ),897 ),
872 ],898 ],
873 helpString: 'Adds a swipe to the last chat message.',899 helpString: 'Adds a swipe to the last chat message.',
874}));900 }));
875SlashCommandParser.addCommandObject(SlashCommand.fromProps({901 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
876 name: 'abort',902 name: 'abort',
877 callback: abortCallback,903 callback: abortCallback,
878 namedArgumentList: [904 namedArgumentList: [
879 SlashCommandNamedArgument.fromProps({905 SlashCommandNamedArgument.fromProps({
880 name: 'quiet',906 name: 'quiet',
881 description: 'Whether to suppress the toast message notifying about the /abort call.',907 description: 'Whether to suppress the toast message notifying about the /abort call.',
882 typeList: [ARGUMENT_TYPE.BOOLEAN],908 typeList: [ARGUMENT_TYPE.BOOLEAN],
883 defaultValue: 'true',909 defaultValue: 'true',
884 }),910 }),
885 ],911 ],
886 unnamedArgumentList: [912 unnamedArgumentList: [
887 SlashCommandArgument.fromProps({913 SlashCommandArgument.fromProps({
888 description: 'The reason for aborting command execution. Shown when quiet=false',914 description: 'The reason for aborting command execution. Shown when quiet=false',
889 typeList: [ARGUMENT_TYPE.STRING],915 typeList: [ARGUMENT_TYPE.STRING],
890 }),916 }),
891 ],917 ],
892 helpString: 'Aborts the slash command batch execution.',918 helpString: 'Aborts the slash command batch execution.',
893}));919 }));
894SlashCommandParser.addCommandObject(SlashCommand.fromProps({920 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
895 name: 'fuzzy',921 name: 'fuzzy',
896 callback: fuzzyCallback,922 callback: fuzzyCallback,
897 returns: 'first matching item',923 returns: 'first matching item',
898 namedArgumentList: [924 namedArgumentList: [
899 new SlashCommandNamedArgument(925 new SlashCommandNamedArgument(
900 'list', 'list of items to match against', [ARGUMENT_TYPE.LIST], true,926 'list', 'list of items to match against', [ARGUMENT_TYPE.LIST], true,
901 ),927 ),
902 new SlashCommandNamedArgument(928 new SlashCommandNamedArgument(
903 'threshold', 'fuzzy match threshold (0.0 to 1.0)', [ARGUMENT_TYPE.NUMBER], false, false, '0.4',929 'threshold', 'fuzzy match threshold (0.0 to 1.0)', [ARGUMENT_TYPE.NUMBER], false, false, '0.4',
904 ),930 ),
905 ],931 ],
906 unnamedArgumentList: [932 unnamedArgumentList: [
907 new SlashCommandArgument(933 new SlashCommandArgument(
908 'text to search', [ARGUMENT_TYPE.STRING], true,934 'text to search', [ARGUMENT_TYPE.STRING], true,
909 ),935 ),
910 ],936 ],
911 helpString: `937 helpString: `
912 <div>938 <div>
913 Performs a fuzzy match of each item in the <code>list</code> against the <code>text to search</code>.939 Performs a fuzzy match of each item in the <code>list</code> against the <code>text to search</code>.
914 If any item matches, then its name is returned. If no item matches the text, no value is returned.940 If any item matches, then its name is returned. If no item matches the text, no value is returned.
@@ -930,23 +956,23 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
930 </ul>956 </ul>
931 </div>957 </div>
932 `,958 `,
933}));959 }));
934SlashCommandParser.addCommandObject(SlashCommand.fromProps({960 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
935 name: 'pass',961 name: 'pass',
936 callback: (_, arg) => {962 callback: (_, arg) => {
937 // We do not support arrays of closures. Arrays of strings will be send as JSON963 // We do not support arrays of closures. Arrays of strings will be send as JSON
938 if (Array.isArray(arg) && arg.some(x => x instanceof SlashCommandClosure)) throw new Error('Command /pass does not support multiple closures');964 if (Array.isArray(arg) && arg.some(x => x instanceof SlashCommandClosure)) throw new Error('Command /pass does not support multiple closures');
939 if (Array.isArray(arg)) return JSON.stringify(arg);965 if (Array.isArray(arg)) return JSON.stringify(arg);
940 return arg;966 return arg;
941 },967 },
942 returns: 'the provided value',968 returns: 'the provided value',
943 unnamedArgumentList: [969 unnamedArgumentList: [
944 new SlashCommandArgument(970 new SlashCommandArgument(
945 'text', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY, ARGUMENT_TYPE.CLOSURE], true,971 'text', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY, ARGUMENT_TYPE.CLOSURE], true,
946 ),972 ),
947 ],973 ],
948 aliases: ['return'],974 aliases: ['return'],
949 helpString: `975 helpString: `
950 <div>976 <div>
951 <pre><span class="monospace">/pass (text)</span> – passes the text to the next command through the pipe.</pre>977 <pre><span class="monospace">/pass (text)</span> – passes the text to the next command through the pipe.</pre>
952 </div>978 </div>
@@ -957,17 +983,17 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
957 </ul>983 </ul>
958 </div>984 </div>
959 `,985 `,
960}));986 }));
961SlashCommandParser.addCommandObject(SlashCommand.fromProps({987 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
962 name: 'delay',988 name: 'delay',
963 callback: delayCallback,989 callback: delayCallback,
964 aliases: ['wait', 'sleep'],990 aliases: ['wait', 'sleep'],
965 unnamedArgumentList: [991 unnamedArgumentList: [
966 new SlashCommandArgument(992 new SlashCommandArgument(
967 'milliseconds', [ARGUMENT_TYPE.NUMBER], true,993 'milliseconds', [ARGUMENT_TYPE.NUMBER], true,
968 ),994 ),
969 ],995 ],
970 helpString: `996 helpString: `
971 <div>997 <div>
972 Delays the next command in the pipe by the specified number of milliseconds.998 Delays the next command in the pipe by the specified number of milliseconds.
973 </div>999 </div>
@@ -980,101 +1006,101 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
980 </ul>1006 </ul>
981 </div>1007 </div>
982 `,1008 `,
983}));1009 }));
984SlashCommandParser.addCommandObject(SlashCommand.fromProps({1010 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
985 name: 'input',1011 name: 'input',
986 aliases: ['prompt'],1012 aliases: ['prompt'],
987 callback: inputCallback,1013 callback: inputCallback,
988 returns: 'user input',1014 returns: 'user input',
989 namedArgumentList: [1015 namedArgumentList: [
990 new SlashCommandNamedArgument(1016 new SlashCommandNamedArgument(
991 'default', 'default value of the input field', [ARGUMENT_TYPE.STRING], false, false, '"string"',1017 'default', 'default value of the input field', [ARGUMENT_TYPE.STRING], false, false, '"string"',
992 ),1018 ),
993 new SlashCommandNamedArgument(1019 new SlashCommandNamedArgument(
994 'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),1020 'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
995 ),1021 ),
996 new SlashCommandNamedArgument(1022 new SlashCommandNamedArgument(
997 'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),1023 'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
998 ),1024 ),
999 new SlashCommandNamedArgument(1025 new SlashCommandNamedArgument(
1000 'okButton', 'text for the ok button', [ARGUMENT_TYPE.STRING], false,1026 'okButton', 'text for the ok button', [ARGUMENT_TYPE.STRING], false,
1001 ),1027 ),
1002 new SlashCommandNamedArgument(1028 new SlashCommandNamedArgument(
1003 'rows', 'number of rows for the input field', [ARGUMENT_TYPE.NUMBER], false,1029 'rows', 'number of rows for the input field', [ARGUMENT_TYPE.NUMBER], false,
1004 ),1030 ),
1005 ],1031 ],
1006 unnamedArgumentList: [1032 unnamedArgumentList: [
1007 new SlashCommandArgument(1033 new SlashCommandArgument(
1008 'text to display', [ARGUMENT_TYPE.STRING], false,1034 'text to display', [ARGUMENT_TYPE.STRING], false,
1009 ),1035 ),
1010 ],1036 ],
1011 helpString: `1037 helpString: `
1012 <div>1038 <div>
1013 Shows a popup with the provided text and an input field.1039 Shows a popup with the provided text and an input field.
1014 The <code>default</code> argument is the default value of the input field, and the text argument is the text to display.1040 The <code>default</code> argument is the default value of the input field, and the text argument is the text to display.
1015 </div>1041 </div>
1016 `,1042 `,
1017}));1043 }));
1018SlashCommandParser.addCommandObject(SlashCommand.fromProps({1044 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1019 name: 'run',1045 name: 'run',
1020 aliases: ['call', 'exec'],1046 aliases: ['call', 'exec'],
1021 callback: runCallback,1047 callback: runCallback,
1022 returns: 'result of the executed closure of QR',1048 returns: 'result of the executed closure of QR',
1023 namedArgumentList: [1049 namedArgumentList: [
1024 new SlashCommandNamedArgument(1050 new SlashCommandNamedArgument(
1025 'args', 'named arguments', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], false, true,1051 'args', 'named arguments', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], false, true,
1026 ),1052 ),
1027 ],1053 ],
1028 unnamedArgumentList: [1054 unnamedArgumentList: [
1029 SlashCommandArgument.fromProps({1055 SlashCommandArgument.fromProps({
1030 description: 'scoped variable or qr label',1056 description: 'scoped variable or qr label',
1031 typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING],1057 typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING],
1032 isRequired: true,1058 isRequired: true,
1033 enumProvider: () => [1059 enumProvider: () => [
1034 ...commonEnumProviders.variables('scope')(),1060 ...commonEnumProviders.variables('scope')(),
1035 ...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],1061 ...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [],
1036 ],1062 ],
1037 }),1063 }),
1038 ],1064 ],
1039 helpString: `1065 helpString: `
1040 <div>1066 <div>
1041 Runs a closure from a scoped variable, or a Quick Reply with the specified name from a currently active preset or from another preset.1067 Runs a closure from a scoped variable, or a Quick Reply with the specified name from a currently active preset or from another preset.
1042 Named arguments can be referenced in a QR with <code>{{arg::key}}</code>.1068 Named arguments can be referenced in a QR with <code>{{arg::key}}</code>.
1043 </div>1069 </div>
1044 `,1070 `,
1045}));1071 }));
1046SlashCommandParser.addCommandObject(SlashCommand.fromProps({1072 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1047 name: 'messages',1073 name: 'messages',
1048 callback: getMessagesCallback,1074 callback: getMessagesCallback,
1049 aliases: ['message'],1075 aliases: ['message'],
1050 namedArgumentList: [1076 namedArgumentList: [
1051 new SlashCommandNamedArgument(1077 new SlashCommandNamedArgument(
1052 'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),1078 'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(),
1053 ),1079 ),
1054 new SlashCommandNamedArgument(1080 new SlashCommandNamedArgument(
1055 'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(),1081 'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(),
1056 ),1082 ),
1057 SlashCommandNamedArgument.fromProps({1083 SlashCommandNamedArgument.fromProps({
1058 name: 'role',1084 name: 'role',
1059 description: 'filter messages by role',1085 description: 'filter messages by role',
1060 typeList: [ARGUMENT_TYPE.STRING],1086 typeList: [ARGUMENT_TYPE.STRING],
1061 enumList: [1087 enumList: [
1062 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system),1088 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system),
1063 new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant),1089 new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant),
1064 new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user),1090 new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user),
1065 ],1091 ],
1066 }),1092 }),
1067 ],1093 ],
1068 unnamedArgumentList: [1094 unnamedArgumentList: [
1069 SlashCommandArgument.fromProps({1095 SlashCommandArgument.fromProps({
1070 description: 'message index (starts with 0) or range',1096 description: 'message index (starts with 0) or range',
1071 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],1097 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE],
1072 isRequired: true,1098 isRequired: true,
1073 enumProvider: commonEnumProviders.messages(),1099 enumProvider: commonEnumProviders.messages(),
1074 }),1100 }),
1075 ],1101 ],
1076 returns: 'the specified message or range of messages as a string',1102 returns: 'the specified message or range of messages as a string',
1077 helpString: `1103 helpString: `
1078 <div>1104 <div>
1079 Returns the specified message or range of messages as a string.1105 Returns the specified message or range of messages as a string.
1080 </div>1106 </div>
@@ -1098,16 +1124,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1098 </ul>1124 </ul>
1099 </div>1125 </div>
1100 `,1126 `,
1101}));1127 }));
1102SlashCommandParser.addCommandObject(SlashCommand.fromProps({1128 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1103 name: 'setinput',1129 name: 'setinput',
1104 callback: setInputCallback,1130 callback: setInputCallback,
1105 unnamedArgumentList: [1131 unnamedArgumentList: [
1106 new SlashCommandArgument(1132 new SlashCommandArgument(
1107 'text', [ARGUMENT_TYPE.STRING], true,1133 'text', [ARGUMENT_TYPE.STRING], true,
1108 ),1134 ),
1109 ],1135 ],
1110 helpString: `1136 helpString: `
1111 <div>1137 <div>
1112 Sets the user input to the specified text and passes it to the next command through the pipe.1138 Sets the user input to the specified text and passes it to the next command through the pipe.
1113 </div>1139 </div>
@@ -1120,28 +1146,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1120 </ul>1146 </ul>
1121 </div>1147 </div>
1122 `,1148 `,
1123}));1149 }));
1124SlashCommandParser.addCommandObject(SlashCommand.fromProps({1150 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1125 name: 'popup',1151 name: 'popup',
1126 callback: popupCallback,1152 callback: popupCallback,
1127 returns: 'popup text',1153 returns: 'popup text',
1128 namedArgumentList: [1154 namedArgumentList: [
1129 new SlashCommandNamedArgument(1155 new SlashCommandNamedArgument(
1130 'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),1156 'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
1131 ),1157 ),
1132 new SlashCommandNamedArgument(1158 new SlashCommandNamedArgument(
1133 'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),1159 'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(),
1134 ),1160 ),
1135 new SlashCommandNamedArgument(1161 new SlashCommandNamedArgument(
1136 'okButton', 'text for the OK button', [ARGUMENT_TYPE.STRING], false,1162 'okButton', 'text for the OK button', [ARGUMENT_TYPE.STRING], false,
1137 ),1163 ),
1138 ],1164 ],
1139 unnamedArgumentList: [1165 unnamedArgumentList: [
1140 new SlashCommandArgument(1166 new SlashCommandArgument(
1141 'text', [ARGUMENT_TYPE.STRING], true,1167 'text', [ARGUMENT_TYPE.STRING], true,
1142 ),1168 ),
1143 ],1169 ],
1144 helpString: `1170 helpString: `
1145 <div>1171 <div>
1146 Shows a blocking popup with the specified text and buttons.1172 Shows a blocking popup with the specified text and buttons.
1147 Returns the popup text.1173 Returns the popup text.
@@ -1155,22 +1181,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1155 </ul>1181 </ul>
1156 </div>1182 </div>
1157 `,1183 `,
1158}));1184 }));
1159SlashCommandParser.addCommandObject(SlashCommand.fromProps({1185 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1160 name: 'buttons',1186 name: 'buttons',
1161 callback: buttonsCallback,1187 callback: buttonsCallback,
1162 returns: 'clicked button label',1188 returns: 'clicked button label',
1163 namedArgumentList: [1189 namedArgumentList: [
1164 new SlashCommandNamedArgument(1190 new SlashCommandNamedArgument(
1165 'labels', 'button labels', [ARGUMENT_TYPE.LIST], true,1191 'labels', 'button labels', [ARGUMENT_TYPE.LIST], true,
1166 ),1192 ),
1167 ],1193 ],
1168 unnamedArgumentList: [1194 unnamedArgumentList: [
1169 new SlashCommandArgument(1195 new SlashCommandArgument(
1170 'text', [ARGUMENT_TYPE.STRING], true,1196 'text', [ARGUMENT_TYPE.STRING], true,
1171 ),1197 ),
1172 ],1198 ],
1173 helpString: `1199 helpString: `
1174 <div>1200 <div>
1175 Shows a blocking popup with the specified text and buttons.1201 Shows a blocking popup with the specified text and buttons.
1176 Returns the clicked button label into the pipe or empty string if canceled.1202 Returns the clicked button label into the pipe or empty string if canceled.
@@ -1184,32 +1210,32 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1184 </ul>1210 </ul>
1185 </div>1211 </div>
1186 `,1212 `,
1187}));1213 }));
1188SlashCommandParser.addCommandObject(SlashCommand.fromProps({1214 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1189 name: 'trimtokens',1215 name: 'trimtokens',
1190 callback: trimTokensCallback,1216 callback: trimTokensCallback,
1191 returns: 'trimmed text',1217 returns: 'trimmed text',
1192 namedArgumentList: [1218 namedArgumentList: [
1193 new SlashCommandNamedArgument(1219 new SlashCommandNamedArgument(
1194 'limit', 'number of tokens to keep', [ARGUMENT_TYPE.NUMBER], true,1220 'limit', 'number of tokens to keep', [ARGUMENT_TYPE.NUMBER], true,
1195 ),1221 ),
1196 SlashCommandNamedArgument.fromProps({1222 SlashCommandNamedArgument.fromProps({
1197 name: 'direction',1223 name: 'direction',
1198 description: 'trim direction',1224 description: 'trim direction',
1199 typeList: [ARGUMENT_TYPE.STRING],1225 typeList: [ARGUMENT_TYPE.STRING],
1200 isRequired: true,1226 isRequired: true,
1201 enumList: [1227 enumList: [
1202 new SlashCommandEnumValue('start', null, enumTypes.enum, '⏪'),1228 new SlashCommandEnumValue('start', null, enumTypes.enum, '⏪'),
1203 new SlashCommandEnumValue('end', null, enumTypes.enum, '⏩'),1229 new SlashCommandEnumValue('end', null, enumTypes.enum, '⏩'),
1204 ],1230 ],
1205 }),1231 }),
1206 ],1232 ],
1207 unnamedArgumentList: [1233 unnamedArgumentList: [
1208 new SlashCommandArgument(1234 new SlashCommandArgument(
1209 'text', [ARGUMENT_TYPE.STRING], false,1235 'text', [ARGUMENT_TYPE.STRING], false,
1210 ),1236 ),
1211 ],1237 ],
1212 helpString: `1238 helpString: `
1213 <div>1239 <div>
1214 Trims the start or end of text to the specified number of tokens.1240 Trims the start or end of text to the specified number of tokens.
1215 </div>1241 </div>
@@ -1222,17 +1248,17 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1222 </ul>1248 </ul>
1223 </div>1249 </div>
1224 `,1250 `,
1225}));1251 }));
1226SlashCommandParser.addCommandObject(SlashCommand.fromProps({1252 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1227 name: 'trimstart',1253 name: 'trimstart',
1228 callback: trimStartCallback,1254 callback: trimStartCallback,
1229 returns: 'trimmed text',1255 returns: 'trimmed text',
1230 unnamedArgumentList: [1256 unnamedArgumentList: [
1231 new SlashCommandArgument(1257 new SlashCommandArgument(
1232 'text', [ARGUMENT_TYPE.STRING], true,1258 'text', [ARGUMENT_TYPE.STRING], true,
1233 ),1259 ),
1234 ],1260 ],
1235 helpString: `1261 helpString: `
1236 <div>1262 <div>
1237 Trims the text to the start of the first full sentence.1263 Trims the text to the start of the first full sentence.
1238 </div>1264 </div>
@@ -1245,148 +1271,149 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1245 </ul>1271 </ul>
1246 </div>1272 </div>
1247 `,1273 `,
1248}));1274 }));
1249SlashCommandParser.addCommandObject(SlashCommand.fromProps({1275 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1250 name: 'trimend',1276 name: 'trimend',
1251 callback: trimEndCallback,1277 callback: trimEndCallback,
1252 returns: 'trimmed text',1278 returns: 'trimmed text',
1253 unnamedArgumentList: [1279 unnamedArgumentList: [
1254 new SlashCommandArgument(1280 new SlashCommandArgument(
1255 'text', [ARGUMENT_TYPE.STRING], true,1281 'text', [ARGUMENT_TYPE.STRING], true,
1256 ),1282 ),
1257 ],1283 ],
1258 helpString: 'Trims the text to the end of the last full sentence.',1284 helpString: 'Trims the text to the end of the last full sentence.',
1259}));1285 }));
1260SlashCommandParser.addCommandObject(SlashCommand.fromProps({1286 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1261 name: 'inject',1287 name: 'inject',
1262 callback: injectCallback,1288 callback: injectCallback,
1263 namedArgumentList: [1289 namedArgumentList: [
1264 SlashCommandNamedArgument.fromProps({1290 SlashCommandNamedArgument.fromProps({
1265 name: 'id',1291 name: 'id',
1266 description: 'injection ID or variable name pointing to ID',1292 description: 'injection ID or variable name pointing to ID',
1267 typeList: [ARGUMENT_TYPE.STRING],1293 typeList: [ARGUMENT_TYPE.STRING],
1268 isRequired: true,1294 isRequired: true,
1269 enumProvider: commonEnumProviders.injects,1295 enumProvider: commonEnumProviders.injects,
1270 }),1296 }),
1271 new SlashCommandNamedArgument(1297 new SlashCommandNamedArgument(
1272 'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'],1298 'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'],
1273 ),1299 ),
1274 new SlashCommandNamedArgument(1300 new SlashCommandNamedArgument(
1275 'depth', 'injection depth', [ARGUMENT_TYPE.NUMBER], false, false, '4',1301 'depth', 'injection depth', [ARGUMENT_TYPE.NUMBER], false, false, '4',
1276 ),1302 ),
1277 new SlashCommandNamedArgument(1303 new SlashCommandNamedArgument(
1278 'scan', 'include injection content into World Info scans', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',1304 'scan', 'include injection content into World Info scans', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
1279 ),1305 ),
1280 SlashCommandNamedArgument.fromProps({1306 SlashCommandNamedArgument.fromProps({
1281 name: 'role',1307 name: 'role',
1282 description: 'role for in-chat injections',1308 description: 'role for in-chat injections',
1283 typeList: [ARGUMENT_TYPE.STRING],1309 typeList: [ARGUMENT_TYPE.STRING],
1284 isRequired: false,1310 isRequired: false,
1285 enumList: [1311 enumList: [
1286 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system),1312 new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system),
1287 new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant),1313 new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant),
1288 new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user),1314 new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user),
1289 ],1315 ],
1290 }),1316 }),
1291 new SlashCommandNamedArgument(1317 new SlashCommandNamedArgument(
1292 'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',1318 'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
1293 ),1319 ),
1294 ],1320 ],
1295 unnamedArgumentList: [1321 unnamedArgumentList: [
1296 new SlashCommandArgument(1322 new SlashCommandArgument(
1297 'text', [ARGUMENT_TYPE.STRING], false,1323 'text', [ARGUMENT_TYPE.STRING], false,
1298 ),1324 ),
1299 ],1325 ],
1300 helpString: 'Injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false).',1326 helpString: 'Injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false).',
1301}));1327 }));
1302SlashCommandParser.addCommandObject(SlashCommand.fromProps({1328 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1303 name: 'listinjects',1329 name: 'listinjects',
1304 callback: listInjectsCallback,1330 callback: listInjectsCallback,
1305 helpString: 'Lists all script injections for the current chat.',1331 helpString: 'Lists all script injections for the current chat.',
1306}));1332 }));
1307SlashCommandParser.addCommandObject(SlashCommand.fromProps({1333 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1308 name: 'flushinject',1334 name: 'flushinject',
1309 aliases: ['flushinjects'],1335 aliases: ['flushinjects'],
1310 unnamedArgumentList: [1336 unnamedArgumentList: [
1311 SlashCommandArgument.fromProps({1337 SlashCommandArgument.fromProps({
1312 description: 'injection ID or a variable name pointing to ID',1338 description: 'injection ID or a variable name pointing to ID',
1313 typeList: [ARGUMENT_TYPE.STRING],1339 typeList: [ARGUMENT_TYPE.STRING],
1314 defaultValue: '',1340 defaultValue: '',
1315 enumProvider: commonEnumProviders.injects,1341 enumProvider: commonEnumProviders.injects,
1316 }),1342 }),
1317 ],1343 ],
1318 callback: flushInjectsCallback,1344 callback: flushInjectsCallback,
1319 helpString: 'Removes a script injection for the current chat. If no ID is provided, removes all script injections.',1345 helpString: 'Removes a script injection for the current chat. If no ID is provided, removes all script injections.',
1320}));1346 }));
1321SlashCommandParser.addCommandObject(SlashCommand.fromProps({1347 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1322 name: 'tokens',1348 name: 'tokens',
1323 callback: (_, text) => {1349 callback: (_, text) => {
1324 if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens');1350 if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens');
1325 return getTokenCountAsync(text).then(count => String(count));1351 return getTokenCountAsync(text).then(count => String(count));
1326 },1352 },
1327 returns: 'number of tokens',1353 returns: 'number of tokens',
1328 unnamedArgumentList: [1354 unnamedArgumentList: [
1329 new SlashCommandArgument(1355 new SlashCommandArgument(
1330 'text', [ARGUMENT_TYPE.STRING], true,1356 'text', [ARGUMENT_TYPE.STRING], true,
1331 ),1357 ),
1332 ],1358 ],
1333 helpString: 'Counts the number of tokens in the provided text.',1359 helpString: 'Counts the number of tokens in the provided text.',
1334}));1360 }));
1335SlashCommandParser.addCommandObject(SlashCommand.fromProps({1361 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1336 name: 'model',1362 name: 'model',
1337 callback: modelCallback,1363 callback: modelCallback,
1338 returns: 'current model',1364 returns: 'current model',
1339 unnamedArgumentList: [1365 unnamedArgumentList: [
1340 SlashCommandArgument.fromProps({1366 SlashCommandArgument.fromProps({
1341 description: 'model name',1367 description: 'model name',
1342 typeList: [ARGUMENT_TYPE.STRING],1368 typeList: [ARGUMENT_TYPE.STRING],
1343 enumProvider: () => getModelOptions()?.options.map(option => new SlashCommandEnumValue(option.value, option.value !== option.text ? option.text : null)),1369 enumProvider: () => getModelOptions()?.options.map(option => new SlashCommandEnumValue(option.value, option.value !== option.text ? option.text : null)),
1344 }),1370 }),
1345 ],1371 ],
1346 helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',1372 helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.',
1347}));1373 }));
1348SlashCommandParser.addCommandObject(SlashCommand.fromProps({1374 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1349 name: 'setpromptentry',1375 name: 'setpromptentry',
1350 aliases: ['setpromptentries'],1376 aliases: ['setpromptentries'],
1351 callback: setPromptEntryCallback,1377 callback: setPromptEntryCallback,
1352 namedArgumentList: [1378 namedArgumentList: [
1353 SlashCommandNamedArgument.fromProps({1379 SlashCommandNamedArgument.fromProps({
1354 name: 'identifier',1380 name: 'identifier',
1355 description: 'Prompt entry identifier(s) to target',1381 description: 'Prompt entry identifier(s) to target',
1356 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],1382 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1357 acceptsMultiple: true,1383 acceptsMultiple: true,
1358 enumProvider: () => {1384 enumProvider: () => {
1359 const promptManager = setupChatCompletionPromptManager(oai_settings);1385 const promptManager = setupChatCompletionPromptManager(oai_settings);
1360 const prompts = promptManager.serviceSettings.prompts;1386 const prompts = promptManager.serviceSettings.prompts;
1361 return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));1387 return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum));
1362 },1388 },
1363 }),1389 }),
1364 SlashCommandNamedArgument.fromProps({1390 SlashCommandNamedArgument.fromProps({
1365 name: 'name',1391 name: 'name',
1366 description: 'Prompt entry name(s) to target',1392 description: 'Prompt entry name(s) to target',
1367 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],1393 typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST],
1368 acceptsMultiple: true,1394 acceptsMultiple: true,
1369 enumProvider: () => {1395 enumProvider: () => {
1370 const promptManager = setupChatCompletionPromptManager(oai_settings);1396 const promptManager = setupChatCompletionPromptManager(oai_settings);
1371 const prompts = promptManager.serviceSettings.prompts;1397 const prompts = promptManager.serviceSettings.prompts;
1372 return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));1398 return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum));
1373 },1399 },
1374 }),1400 }),
1375 ],1401 ],
1376 unnamedArgumentList: [1402 unnamedArgumentList: [
1377 SlashCommandArgument.fromProps({1403 SlashCommandArgument.fromProps({
1378 description: 'Set entry/entries on or off',1404 description: 'Set entry/entries on or off',
1379 typeList: [ARGUMENT_TYPE.STRING],1405 typeList: [ARGUMENT_TYPE.STRING],
1380 isRequired: true,1406 isRequired: true,
1381 acceptsMultiple: false,1407 acceptsMultiple: false,
1382 defaultValue: 'toggle', // unnamed arguments don't support default values yet1408 defaultValue: 'toggle', // unnamed arguments don't support default values yet
1383 enumList: commonEnumProviders.boolean('onOffToggle')(),1409 enumList: commonEnumProviders.boolean('onOffToggle')(),
1384 }),1410 }),
1385 ],1411 ],
1386 helpString: 'Sets the specified prompt manager entry/entries on or off.',1412 helpString: 'Sets the specified prompt manager entry/entries on or off.',
1387}));1413 }));
13881414
1389registerVariableCommands();1415 registerVariableCommands();
1416}
13901417
1391const NARRATOR_NAME_KEY = 'narrator_name';1418const NARRATOR_NAME_KEY = 'narrator_name';
1392const NARRATOR_NAME_DEFAULT = 'System';1419const NARRATOR_NAME_DEFAULT = 'System';
@@ -1938,32 +1965,67 @@ async function generateCallback(args, value) {
1938 }1965 }
1939}1966}
19401967
1968/**
1969 *
1970 * @param {{title?: string, severity?: string, timeout?: string, extendedTimeout?: string, preventDuplicates?: string, awaitDismissal?: string}} args - named arguments from the slash command
1971 * @param {string} value - The string to echo (unnamed argument from the slash command)
1972 * @returns {Promise<string>} The text that was echoed
1973 */
1941async function echoCallback(args, value) {1974async function echoCallback(args, value) {
1942 // Note: We don't need to sanitize input, as toastr is set up by default to escape HTML via toastr options1975 // Note: We don't need to sanitize input, as toastr is set up by default to escape HTML via toastr options
1943 if (value === '') {1976 if (value === '') {
1944 console.warn('WARN: No argument provided for /echo command');1977 console.warn('WARN: No argument provided for /echo command');
1945 return;1978 return '';
1946 }1979 }
1947 const title = args?.title !== undefined && typeof args?.title === 'string' ? args.title : undefined;1980
1948 const severity = args?.severity !== undefined && typeof args?.severity === 'string' ? args.severity : 'info';1981 if (args.severity && !['error', 'warning', 'success', 'info'].includes(args.severity)) {
1982 toastr.warning(`Invalid severity provided for /echo command: ${args.severity}`);
1983 args.severity = null;
1984 }
1985
1986 const title = args.title ? args.title : undefined;
1987 const severity = args.severity ? args.severity : 'info';
1988
1989 /** @type {ToastrOptions} */
1990 const options = {};
1991 if (args.timeout && !isNaN(parseInt(args.timeout))) options.timeOut = parseInt(args.timeout);
1992 if (args.extendedTimeout && !isNaN(parseInt(args.extendedTimeout))) options.extendedTimeOut = parseInt(args.extendedTimeout);
1993 if (isTrueBoolean(args.preventDuplicates)) options.preventDuplicates = true;
1994
1995 // Prepare possible await handling
1996 let awaitDismissal = isTrueBoolean(args.awaitDismissal);
1997 let resolveToastDismissal;
1998
1999 if (awaitDismissal) {
2000 options.onHidden = () => resolveToastDismissal(value);
2001 }
2002
1949 switch (severity) {2003 switch (severity) {
1950 case 'error':2004 case 'error':
1951 toastr.error(value, title);2005 toastr.error(value, title, options);
1952 break;2006 break;
1953 case 'warning':2007 case 'warning':
1954 toastr.warning(value, title);2008 toastr.warning(value, title, options);
1955 break;2009 break;
1956 case 'success':2010 case 'success':
1957 toastr.success(value, title);2011 toastr.success(value, title, options);
1958 break;2012 break;
1959 case 'info':2013 case 'info':
1960 default:2014 default:
1961 toastr.info(value, title);2015 toastr.info(value, title, options);
1962 break;2016 break;
1963 }2017 }
1964 return value;2018
2019 if (awaitDismissal) {
2020 return new Promise((resolve) => {
2021 resolveToastDismissal = resolve;
2022 });
2023 } else {
2024 return value;
2025 }
1965}2026}
19662027
2028
1967async function addSwipeCallback(_, arg) {2029async function addSwipeCallback(_, arg) {
1968 const lastMessage = chat[chat.length - 1];2030 const lastMessage = chat[chat.length - 1];
19692031
@@ -2428,7 +2490,14 @@ async function sendUserMessageCallback(args, text) {
2428 text = text.trim();2490 text = text.trim();
2429 const compact = isTrueBoolean(args?.compact);2491 const compact = isTrueBoolean(args?.compact);
2430 const bias = extractMessageBias(text);2492 const bias = extractMessageBias(text);
2431 const insertAt = Number(args?.at);2493
2494 let insertAt = Number(args?.at);
2495
2496 // Convert possible depth parameter to index
2497 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
2498 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
2499 insertAt = chat.length + insertAt;
2500 }
24322501
2433 if ('name' in args) {2502 if ('name' in args) {
2434 const name = args.name || '';2503 const name = args.name || '';
@@ -2737,7 +2806,13 @@ export async function sendMessageAs(args, text) {
2737 },2806 },
2738 }];2807 }];
27392808
2740 const insertAt = Number(args.at);2809 let insertAt = Number(args.at);
2810
2811 // Convert possible depth parameter to index
2812 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
2813 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
2814 insertAt = chat.length + insertAt;
2815 }
27412816
2742 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {2817 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
2743 chat.splice(insertAt, 0, message);2818 chat.splice(insertAt, 0, message);
@@ -2784,7 +2859,13 @@ export async function sendNarratorMessage(args, text) {
2784 },2859 },
2785 };2860 };
27862861
2787 const insertAt = Number(args.at);2862 let insertAt = Number(args.at);
2863
2864 // Convert possible depth parameter to index
2865 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
2866 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
2867 insertAt = chat.length + insertAt;
2868 }
27882869
2789 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {2870 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
2790 chat.splice(insertAt, 0, message);2871 chat.splice(insertAt, 0, message);
@@ -2866,7 +2947,13 @@ async function sendCommentMessage(args, text) {
2866 },2947 },
2867 };2948 };
28682949
2869 const insertAt = Number(args.at);2950 let insertAt = Number(args.at);
2951
2952 // Convert possible depth parameter to index
2953 if (!isNaN(insertAt) && (insertAt < 0 || insertAt === Number(-0))) {
2954 // Negative value means going back from current chat length. (E.g.: 8 messages, Depth 1 means insert at index 7)
2955 insertAt = chat.length + insertAt;
2956 }
28702957
2871 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {2958 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
2872 chat.splice(insertAt, 0, message);2959 chat.splice(insertAt, 0, message);
public/scripts/slash-commands/SlashCommandArgument.js+5 -5
@@ -59,7 +59,7 @@ export class SlashCommandArgument {
59 * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums59 * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums
60 * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options60 * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options
61 */61 */
62 constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = true) {62 constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], enumProvider = null, forceEnum = false) {
63 this.description = description;63 this.description = description;
64 this.typeList = types ? Array.isArray(types) ? types : [types] : [];64 this.typeList = types ? Array.isArray(types) ? types : [types] : [];
65 this.isRequired = isRequired ?? false;65 this.isRequired = isRequired ?? false;
@@ -90,7 +90,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
90 * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided90 * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided
91 * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values91 * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values
92 * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options92 * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options
93 * @param {boolean} [props.forceEnum=true] default: true - whether the input must match one of the enum values93 * @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values
94 */94 */
95 static fromProps(props) {95 static fromProps(props) {
96 return new SlashCommandNamedArgument(96 return new SlashCommandNamedArgument(
@@ -103,7 +103,7 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
103 props.enumList ?? [],103 props.enumList ?? [],
104 props.aliasList ?? [],104 props.aliasList ?? [],
105 props.enumProvider ?? null,105 props.enumProvider ?? null,
106 props.forceEnum ?? true,106 props.forceEnum ?? false,
107 );107 );
108 }108 }
109109
@@ -120,9 +120,9 @@ export class SlashCommandNamedArgument extends SlashCommandArgument {
120 * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]120 * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]]
121 * @param {string[]} [aliases=[]]121 * @param {string[]} [aliases=[]]
122 * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options122 * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options
123 * @param {boolean} [forceEnum=true]123 * @param {boolean} [forceEnum=false]
124 */124 */
125 constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = true) {125 constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = false) {
126 super(description, types, isRequired, acceptsMultiple, defaultValue, enums, enumProvider, forceEnum);126 super(description, types, isRequired, acceptsMultiple, defaultValue, enums, enumProvider, forceEnum);
127 this.name = name;127 this.name = name;
128 this.aliasList = aliases ? Array.isArray(aliases) ? aliases : [aliases] : [];128 this.aliasList = aliases ? Array.isArray(aliases) ? aliases : [aliases] : [];
src/endpoints/openai.js+6 -2
@@ -43,7 +43,11 @@ router.post('/caption-image', jsonParser, async (request, response) => {
43 key = readSecret(request.user.directories, SECRET_KEYS.KOBOLDCPP);43 key = readSecret(request.user.directories, SECRET_KEYS.KOBOLDCPP);
44 }44 }
4545
46 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp'].includes(request.body.api) === false) {46 if (request.body.api === 'vllm') {
47 key = readSecret(request.user.directories, SECRET_KEYS.VLLM);
48 }
49
50 if (!key && !request.body.reverse_proxy && ['custom', 'ooba', 'koboldcpp', 'vllm'].includes(request.body.api) === false) {
47 console.log('No key found for API', request.body.api);51 console.log('No key found for API', request.body.api);
48 return response.sendStatus(400);52 return response.sendStatus(400);
49 }53 }
@@ -110,7 +114,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
110 });114 });
111 }115 }
112116
113 if (request.body.api === 'koboldcpp') {117 if (request.body.api === 'koboldcpp' || request.body.api === 'vllm') {
114 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;118 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
115 }119 }
116120