Merge pull request #2922 from SillyTavern/send-commands-return-value Update various sending commands to return a value

be103534e4122ada55d1416f8cc802232de59aac

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

Signed
6 files changed, +274 -90Ignore whitespace
public/script.js+3 -1
@@ -4785,7 +4785,7 @@ export function removeMacros(str) {
47854785 * @param {boolean} [compact] Send as a compact display message.
47864786 * @param {string} [name] Name of the user sending the message. Defaults to name1.
47874787 * @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar.
47884788 * @returns {Promise<voidany>} A promise that resolves whento the message when it is inserted.
47894789 */
47904790export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) {
47914791 messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
@@ -4832,6 +4832,8 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
48324832 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
48334833 await saveChatConditional();
48344834 }
4835+
4836+ return message;
48354837}
48364838
48374839/**
public/scripts/extensions/expressions/index.js+33 -7
@@ -12,6 +12,8 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
1212import { isFunctionCallingSupported } from '../../openai.js';
1313import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
1414import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
15+import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
16+import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
1517export { MODULE_NAME };
1618
1719const MODULE_NAME = 'expressions';
@@ -2134,18 +2136,42 @@ function migrateSettings() {
21342136 name: 'classify-expressions',
21352137 aliases: ['expressions'],
21362138 callback: async (args) => {
2137- const list = await getExpressionsList();
2139+ /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
2138- switch (String(args.format).toLowerCase()) {
2140+ // @ts-ignore
2139- case 'json':
2141+ let returnType = args.return;
2140- return JSON.stringify(list);
2142+
2141- default:
2143+ // Old legacy return type handling
2142- return list.join(', ');
2144+ if (args.format) {
2145+ toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
2146+ const type = String(args?.format).toLowerCase().trim();
2147+ switch (type) {
2148+ case 'json':
2149+ returnType = 'object';
2150+ break;
2151+ default:
2152+ returnType = 'pipe';
2153+ break;
2154+ }
21432155 }
2156+
2157+ // Now the actual new return type handling
2158+ const list = await getExpressionsList();
2159+
2160+ return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
21442161 },
21452162 namedArgumentList: [
21462163 SlashCommandNamedArgument.fromProps({
2164+ name: 'return',
2165+ description: 'The way how you want the return value to be provided',
2166+ typeList: [ARGUMENT_TYPE.STRING],
2167+ defaultValue: 'pipe',
2168+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
2169+ forceEnum: true,
2170+ }),
2171+ // TODO remove some day
2172+ SlashCommandNamedArgument.fromProps({
21472173 name: 'format',
21482174 description: '!!! DEPRECATED - use "return" instead !!! The format to return the list in: comma-separated plain text or JSON array. Default is plain text.',
21492175 typeList: [ARGUMENT_TYPE.STRING],
21502176 enumList: [
21512177 new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '),
public/scripts/slash-commands.js+110 -55
@@ -70,6 +70,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
7070import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
7171import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
7272import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
73+import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
7374export {
7475 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
7576};
@@ -242,6 +243,7 @@ export function initDefaultSlashCommands() {
242243 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
243244 name: 'sendas',
244245 callback: sendMessageAs,
246+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
245247 namedArgumentList: [
246248 SlashCommandNamedArgument.fromProps({
247249 name: 'name',
@@ -269,6 +271,14 @@ export function initDefaultSlashCommands() {
269271 typeList: [ARGUMENT_TYPE.NUMBER],
270272 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
271273 }),
274+ SlashCommandNamedArgument.fromProps({
275+ name: 'return',
276+ description: 'The way how you want the return value to be provided',
277+ typeList: [ARGUMENT_TYPE.STRING],
278+ defaultValue: 'none',
279+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
280+ forceEnum: true,
281+ }),
272282 ],
273283 unnamedArgumentList: [
274284 new SlashCommandArgument(
@@ -301,6 +311,7 @@ export function initDefaultSlashCommands() {
301311 name: 'sys',
302312 callback: sendNarratorMessage,
303313 aliases: ['nar'],
314+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
304315 namedArgumentList: [
305316 new SlashCommandNamedArgument(
306317 'compact',
@@ -316,6 +327,14 @@ export function initDefaultSlashCommands() {
316327 typeList: [ARGUMENT_TYPE.NUMBER],
317328 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
318329 }),
330+ SlashCommandNamedArgument.fromProps({
331+ name: 'return',
332+ description: 'The way how you want the return value to be provided',
333+ typeList: [ARGUMENT_TYPE.STRING],
334+ defaultValue: 'none',
335+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
336+ forceEnum: true,
337+ }),
319338 ],
320339 unnamedArgumentList: [
321340 new SlashCommandArgument(
@@ -355,6 +374,7 @@ export function initDefaultSlashCommands() {
355374 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
356375 name: 'comment',
357376 callback: sendCommentMessage,
377+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
358378 namedArgumentList: [
359379 new SlashCommandNamedArgument(
360380 'compact',
@@ -370,6 +390,14 @@ export function initDefaultSlashCommands() {
370390 typeList: [ARGUMENT_TYPE.NUMBER],
371391 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
372392 }),
393+ SlashCommandNamedArgument.fromProps({
394+ name: 'return',
395+ description: 'The way how you want the return value to be provided',
396+ typeList: [ARGUMENT_TYPE.STRING],
397+ defaultValue: 'none',
398+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
399+ forceEnum: true,
400+ }),
373401 ],
374402 unnamedArgumentList: [
375403 new SlashCommandArgument(
@@ -509,7 +537,7 @@ export function initDefaultSlashCommands() {
509537 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
510538 name: 'ask',
511539 callback: askCharacter,
512- returns: 'the generated text',
540+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
513541 namedArgumentList: [
514542 SlashCommandNamedArgument.fromProps({
515543 name: 'name',
@@ -518,6 +546,14 @@ export function initDefaultSlashCommands() {
518546 isRequired: true,
519547 enumProvider: commonEnumProviders.characters('character'),
520548 }),
549+ SlashCommandNamedArgument.fromProps({
550+ name: 'return',
551+ description: 'The way how you want the return value to be provided',
552+ typeList: [ARGUMENT_TYPE.STRING],
553+ defaultValue: 'pipe',
554+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
555+ forceEnum: true,
556+ }),
521557 ],
522558 unnamedArgumentList: [
523559 new SlashCommandArgument(
@@ -556,6 +592,7 @@ export function initDefaultSlashCommands() {
556592 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
557593 name: 'send',
558594 callback: sendUserMessageCallback,
595+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
559596 namedArgumentList: [
560597 new SlashCommandNamedArgument(
561598 'compact',
@@ -578,6 +615,14 @@ export function initDefaultSlashCommands() {
578615 defaultValue: '{{user}}',
579616 enumProvider: commonEnumProviders.personas,
580617 }),
618+ SlashCommandNamedArgument.fromProps({
619+ name: 'return',
620+ description: 'The way how you want the return value to be provided',
621+ typeList: [ARGUMENT_TYPE.STRING],
622+ defaultValue: 'none',
623+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
624+ forceEnum: true,
625+ }),
581626 ],
582627 unnamedArgumentList: [
583628 new SlashCommandArgument(
@@ -1568,12 +1613,21 @@ export function initDefaultSlashCommands() {
15681613 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
15691614 name: 'listinjects',
15701615 callback: listInjectsCallback,
15711616 helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>formatreturn</code> argument to change the outputreturn formattype.',
15721617 returns: 'Optionalls the JSON object of script injections',
15731618 namedArgumentList: [
15741619 SlashCommandNamedArgument.fromProps({
1620+ name: 'return',
1621+ description: 'The way how you want the return value to be provided',
1622+ typeList: [ARGUMENT_TYPE.STRING],
1623+ defaultValue: 'popup-html',
1624+ enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
1625+ forceEnum: true,
1626+ }),
1627+ // TODO remove some day
1628+ SlashCommandNamedArgument.fromProps({
15751629 name: 'format',
15761630 description: '!!! DEPRECATED - use "return" instead !!! output format',
15771631 typeList: [ARGUMENT_TYPE.STRING],
15781632 isRequired: true,
15791633 forceEnum: true,
@@ -1842,37 +1896,43 @@ function injectCallback(args, value) {
18421896}
18431897
18441898async function listInjectsCallback(args) {
1845- const type = String(args?.format).toLowerCase().trim();
1899+ /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
1846- if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
1900+ let returnType = args.return;
1847- type !== 'none' && toastr.info('No script injections for the current chat');
1901+
1848- return JSON.stringify({});
1902+ // Old legacy return type handling
1849- }
1903+ if (args.format) {
1850-
1904+ toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
1851- const injects = Object.entries(chat_metadata.script_injects)
1905+ const type = String(args?.format).toLowerCase().trim();
1852- .map(([id, inject]) => {
1906+ if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
1853- const position = Object.entries(extension_prompt_types);
1907+ type !== 'none' && toastr.info('No script injections for the current chat');
1854- const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
1908+ }
1855- return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
1909+ switch (type) {
1856- })
1910+ case 'none':
1857- .join('\n');
1911+ returnType = 'none';
1858-
1912+ break;
1859- const converter = new showdown.Converter();
1913+ case 'chat':
1860- const messageText = `### Script injections:\n${injects}`;
1914+ returnType = 'chat-html';
1861- const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText));
1915+ break;
1862-
1916+ case 'popup':
1863- switch (type) {
1917+ default:
1864- case 'none':
1918+ returnType = 'popup-html';
18651919 break;
1866- case 'chat':
1920+ }
1867- sendSystemMessage(system_message_types.GENERIC, htmlMessage);
1868- break;
1869- case 'popup':
1870- default:
1871- await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
1872- break;
18731921 }
18741922
1875- return JSON.stringify(chat_metadata.script_injects);
1923+ // Now the actual new return type handling
1924+ const buildTextValue = (injects) => {
1925+ const injectsStr = Object.entries(injects)
1926+ .map(([id, inject]) => {
1927+ const position = Object.entries(extension_prompt_types);
1928+ const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
1929+ return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
1930+ })
1931+ .join('\n');
1932+ return `### Script injections:\n${injectsStr || 'No script injections for the current chat'}`;
1933+ };
1934+
1935+ return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', chat_metadata.script_injects ?? {}, { objectToStringFunc: buildTextValue });
18761936}
18771937
18781938/**
@@ -2559,7 +2619,7 @@ async function askCharacter(args, text) {
25592619 // Not supported in group chats
25602620 // TODO: Maybe support group chats?
25612621 if (selected_group) {
25622622 toastr.errorwarning('Cannot run /ask command in a group chat!');
25632623 return '';
25642624 }
25652625
@@ -2633,7 +2693,9 @@ async function askCharacter(args, text) {
26332693 }
26342694 }
26352695
2636- return askResult;
2696+ const message = askResult ? chat[chat.length - 1] : null;
2697+
2698+ return await slashCommandReturnHelper.doReturn(args.return ?? 'pipe', message, { objectToStringFunc: x => x.mes });
26372699}
26382700
26392701async function hideMessageCallback(_, arg) {
@@ -2908,7 +2970,7 @@ function findPersonaByName(name) {
29082970
29092971async function sendUserMessageCallback(args, text) {
29102972 if (!text) {
29112973 consoletoastr.warnwarning('WARN:You Nomust textspecify providedtext forto /send command');
29122974 return;
29132975 }
29142976
@@ -2924,16 +2986,17 @@ async function sendUserMessageCallback(args, text) {
29242986 insertAt = chat.length + insertAt;
29252987 }
29262988
2989+ let message;
29272990 if ('name' in args) {
29282991 const name = args.name || '';
29292992 const avatar = findPersonaByName(name) || user_avatar;
29302993 message = await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);
29312994 }
29322995 else {
29332996 message = await sendMessageAsUser(text, bias, insertAt, compact);
29342997 }
29352998
2936- return '';
2999+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
29373000}
29383001
29393002async function deleteMessagesByNameCallback(_, name) {
@@ -3221,30 +3284,20 @@ export function getNameAndAvatarForMessage(character, name = null) {
32213284
32223285export async function sendMessageAs(args, text) {
32233286 if (!text) {
3287+ toastr.warning('You must specify text to send as');
32243288 return '';
32253289 }
32263290
3227- let name;
3291+ let name = args.name?.trim();
32283292 let mesText;
32293293
32303294 if (args.!name) {
3231- name = args.name.trim();
3232-
3233- if (!name && !text) {
3234- toastr.warning('You must specify a name and text to send as');
3235- return '';
3236- }
3237- } else {
32383295 const namelessWarningKey = 'sendAsNamelessWarningShown';
32393296 if (localStorage.getItem(namelessWarningKey) !== 'true') {
32403297 toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
32413298 localStorage.setItem(namelessWarningKey, 'true');
32423299 }
32433300 name = name2;
3244- if (!text) {
3245- toastr.warning('You must specify text to send as');
3246- return '';
3247- }
32483301 }
32493302
32503303 mesText = text.trim();
@@ -3321,11 +3374,12 @@ export async function sendMessageAs(args, text) {
33213374 await saveChatConditional();
33223375 }
33233376
3324- return '';
3377+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
33253378}
33263379
33273380export async function sendNarratorMessage(args, text) {
33283381 if (!text) {
3382+ toastr.warning('You must specify text to send');
33293383 return '';
33303384 }
33313385
@@ -3374,7 +3428,7 @@ export async function sendNarratorMessage(args, text) {
33743428 await saveChatConditional();
33753429 }
33763430
3377- return '';
3431+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
33783432}
33793433
33803434export async function promptQuietForLoudResponse(who, text) {
@@ -3420,6 +3474,7 @@ export async function promptQuietForLoudResponse(who, text) {
34203474
34213475async function sendCommentMessage(args, text) {
34223476 if (!text) {
3477+ toastr.warning('You must specify text to send');
34233478 return '';
34243479 }
34253480
@@ -3462,7 +3517,7 @@ async function sendCommentMessage(args, text) {
34623517 await saveChatConditional();
34633518 }
34643519
3465- return '';
3520+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
34663521}
34673522
34683523/**
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+1 -0
@@ -36,6 +36,7 @@ export const enumIcons = {
3636 message: '💬',
3737 voice: '🎤',
3838 server: '🖥️',
39+ popup: '🗔',
3940
4041 true: '✔️',
4142 false: '❌',
public/scripts/slash-commands/SlashCommandReturnHelper.js+80 -0
@@ -0,0 +1,80 @@
1+import { sendSystemMessage, system_message_types } from '../../script.js';
2+import { callGenericPopup, POPUP_TYPE } from '../popup.js';
3+import { escapeHtml } from '../utils.js';
4+import { enumIcons } from './SlashCommandCommonEnumsProvider.js';
5+import { enumTypes, SlashCommandEnumValue } from './SlashCommandEnumValue.js';
6+
7+/** @typedef {'pipe'|'object'|'chat-html'|'chat-text'|'popup-html'|'popup-text'|'toast-html'|'toast-text'|'console'|'none'} SlashCommandReturnType */
8+
9+export const slashCommandReturnHelper = {
10+ // Without this, VSCode formatter fucks up JS docs. Don't ask me why.
11+ _: false,
12+
13+ /**
14+ * Gets/creates the enum list of types of return relevant for a slash command
15+ *
16+ * @param {object} [options={}] Options
17+ * @param {boolean} [options.allowPipe=true] Allow option to pipe the return value
18+ * @param {boolean} [options.allowObject=false] Allow option to return the value as an object
19+ * @param {boolean} [options.allowChat=false] Allow option to return the value as a chat message
20+ * @param {boolean} [options.allowPopup=false] Allow option to return the value as a popup
21+ * @param {boolean}[options.allowTextVersion=true] Used in combination with chat/popup/toast, some of them do not make sense for text versions, e.g.if you are building a HTML string anyway
22+ * @returns {SlashCommandEnumValue[]} The enum list
23+ */
24+ enumList: ({ allowPipe = true, allowObject = false, allowChat = false, allowPopup = false, allowTextVersion = true } = {}) => [
25+ allowPipe && new SlashCommandEnumValue('pipe', 'Return to the pipe for the next command', enumTypes.name, '|'),
26+ allowObject && new SlashCommandEnumValue('object', 'Return as an object (or array) to the pipe for the next command', enumTypes.variable, enumIcons.dictionary),
27+ allowChat && new SlashCommandEnumValue('chat-html', 'Sending a chat message with the return value - Can display HTML', enumTypes.command, enumIcons.message),
28+ allowChat && allowTextVersion && new SlashCommandEnumValue('chat-text', 'Sending a chat message with the return value - Will only display as text', enumTypes.qr, enumIcons.message),
29+ allowPopup && new SlashCommandEnumValue('popup-html', 'Showing as a popup with the return value - Can display HTML', enumTypes.command, enumIcons.popup),
30+ allowPopup && allowTextVersion && new SlashCommandEnumValue('popup-text', 'Showing as a popup with the return value - Will only display as text', enumTypes.qr, enumIcons.popup),
31+ new SlashCommandEnumValue('toast-html', 'Show the return value as a toast notification - Can display HTML', enumTypes.command, 'ℹ️'),
32+ allowTextVersion && new SlashCommandEnumValue('toast-text', 'Show the return value as a toast notification - Will only display as text', enumTypes.qr, 'ℹ️'),
33+ new SlashCommandEnumValue('console', 'Log the return value (object, if it can be one) to the console', enumTypes.enum, '>'),
34+ new SlashCommandEnumValue('none', 'No return value'),
35+ ].filter(x => !!x),
36+
37+ /**
38+ * Handles the return value based on the specified type
39+ *
40+ * @param {SlashCommandReturnType} type The type of return
41+ * @param {object|number|string} value The value to return
42+ * @param {object} [options={}] Options
43+ * @param {(o: object) => string} [options.objectToStringFunc=null] Function to convert the object to a string, if object was provided and 'object' was not the chosen return type
44+ * @param {(o: object) => string} [options.objectToHtmlFunc=null] Analog to 'objectToStringFunc', which will be used here if not provided - but can do a different string layout if HTML is requested
45+ * @returns {Promise<*>} The processed return value
46+ */
47+ async doReturn(type, value, { objectToStringFunc = o => o?.toString(), objectToHtmlFunc = null } = {}) {
48+ const shouldHtml = type.endsWith('html');
49+ const actualConverterFunc = shouldHtml && objectToHtmlFunc ? objectToHtmlFunc : objectToStringFunc;
50+ const stringValue = typeof value !== 'string' ? actualConverterFunc(value) : value;
51+
52+ switch (type) {
53+ case 'popup-html':
54+ case 'popup-text':
55+ case 'chat-text':
56+ case 'chat-html':
57+ case 'toast-text':
58+ case 'toast-html': {
59+ const htmlOrNotHtml = shouldHtml ? DOMPurify.sanitize((new showdown.Converter()).makeHtml(stringValue)) : escapeHtml(stringValue);
60+
61+ if (type.startsWith('popup')) await callGenericPopup(htmlOrNotHtml, POPUP_TYPE.TEXT);
62+ if (type.startsWith('chat')) sendSystemMessage(system_message_types.GENERIC, htmlOrNotHtml);
63+ if (type.startsWith('toast')) toastr.info(htmlOrNotHtml, null, { escapeHtml: !shouldHtml });
64+
65+ return '';
66+ }
67+ case 'pipe':
68+ return stringValue ?? '';
69+ case 'object':
70+ return JSON.stringify(value);
71+ case 'console':
72+ console.info(value);
73+ return '';
74+ case 'none':
75+ return '';
76+ default:
77+ throw new Error(`Unknown return type: ${type}`);
78+ }
79+ },
80+};
public/scripts/variables.js+47 -27
@@ -11,6 +11,7 @@ import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureR
1111import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
1212import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
1313import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
14+import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
1415import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
1516import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
1617
@@ -305,7 +306,28 @@ export function replaceVariableMacros(input) {
305306}
306307
307308async function listVariablesCallback(args) {
308- const type = String(args?.format || '').toLowerCase().trim() || 'popup';
309+ /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
310+ let returnType = args.return;
311+
312+ // Old legacy return type handling
313+ if (args.format) {
314+ toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
315+ const type = String(args?.format).toLowerCase().trim();
316+ switch (type) {
317+ case 'none':
318+ returnType = 'none';
319+ break;
320+ case 'chat':
321+ returnType = 'chat-html';
322+ break;
323+ case 'popup':
324+ default:
325+ returnType = 'popup-html';
326+ break;
327+ }
328+ }
329+
330+ // Now the actual new return type handling
309331 const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
310332 if (!chat_metadata.variables) {
311333 chat_metadata.variables = {};
@@ -317,35 +339,24 @@ async function listVariablesCallback(args) {
317339 const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
318340 const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
319341
342+ const buildTextValue = (_) => {
343+ const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
344+ const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
345+ const chatName = getCurrentChatId();
346+
347+ const message = [
348+ includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
349+ includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
350+ ].filter(x => x).join('\n\n');
351+ return message;
352+ };
353+
320354 const jsonVariables = [
321355 ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
322356 ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
323357 ];
324358
325- const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
359+ return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', jsonVariables, { objectToStringFunc: buildTextValue });
326- const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
327- const chatName = getCurrentChatId();
328-
329- const converter = new showdown.Converter();
330- const message = [
331- includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
332- includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
333- ].filter(x => x).join('\n\n');
334- const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
335-
336- switch (type) {
337- case 'none':
338- break;
339- case 'chat':
340- sendSystemMessage(system_message_types.GENERIC, htmlMessage);
341- break;
342- case 'popup':
343- default:
344- await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
345- break;
346- }
347-
348- return JSON.stringify(jsonVariables);
349360}
350361
351362/**
@@ -916,7 +927,7 @@ export function registerVariableCommands() {
916927 name: 'listvar',
917928 callback: listVariablesCallback,
918929 aliases: ['listchatvar'],
919930 helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>formatreturn</code> argument to change the outputreturn formattype.',
920931 returns: 'JSON list of local variables',
921932 namedArgumentList: [
922933 SlashCommandNamedArgument.fromProps({
@@ -933,8 +944,17 @@ export function registerVariableCommands() {
933944 ],
934945 }),
935946 SlashCommandNamedArgument.fromProps({
947+ name: 'return',
948+ description: 'The way how you want the return value to be provided',
949+ typeList: [ARGUMENT_TYPE.STRING],
950+ defaultValue: 'popup-html',
951+ enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
952+ forceEnum: true,
953+ }),
954+ // TODO remove some day
955+ SlashCommandNamedArgument.fromProps({
936956 name: 'format',
937957 description: '!!! DEPRECATED - use "return" instead !!! output format',
938958 typeList: [ARGUMENT_TYPE.STRING],
939959 isRequired: true,
940960 forceEnum: true,