Merge pull request #2922 from SillyTavern/send-commands-return-value

Update various sending commands to return a value
This commit is contained in:
Cohee 2024-10-01 19:25:19 +03:00 committed by GitHub
commit be103534e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 273 additions and 89 deletions

View File

@ -4785,7 +4785,7 @@ export function removeMacros(str) {
* @param {boolean} [compact] Send as a compact display message. * @param {boolean} [compact] Send as a compact display message.
* @param {string} [name] Name of the user sending the message. Defaults to name1. * @param {string} [name] Name of the user sending the message. Defaults to name1.
* @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar. * @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar.
* @returns {Promise<void>} A promise that resolves when the message is inserted. * @returns {Promise<any>} A promise that resolves to the message when it is inserted.
*/ */
export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) { export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) {
messageText = getRegexedString(messageText, regex_placement.USER_INPUT); messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
@ -4832,6 +4832,8 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
await saveChatConditional(); await saveChatConditional();
} }
return message;
} }
/** /**

View File

@ -12,6 +12,8 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
import { isFunctionCallingSupported } from '../../openai.js'; import { isFunctionCallingSupported } from '../../openai.js';
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
export { MODULE_NAME }; export { MODULE_NAME };
const MODULE_NAME = 'expressions'; const MODULE_NAME = 'expressions';
@ -2134,18 +2136,42 @@ function migrateSettings() {
name: 'classify-expressions', name: 'classify-expressions',
aliases: ['expressions'], aliases: ['expressions'],
callback: async (args) => { callback: async (args) => {
const list = await getExpressionsList(); /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
switch (String(args.format).toLowerCase()) { // @ts-ignore
case 'json': let returnType = args.return;
return JSON.stringify(list);
default: // Old legacy return type handling
return list.join(', '); if (args.format) {
toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
const type = String(args?.format).toLowerCase().trim();
switch (type) {
case 'json':
returnType = 'object';
break;
default:
returnType = 'pipe';
break;
}
} }
// Now the actual new return type handling
const list = await getExpressionsList();
return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
}, },
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'pipe',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
// TODO remove some day
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'format', name: 'format',
description: 'The format to return the list in: comma-separated plain text or JSON array. Default is plain text.', description: '!!! DEPRECATED - use "return" instead !!! The format to return the list in: comma-separated plain text or JSON array. Default is plain text.',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
enumList: [ enumList: [
new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '), new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '),

View File

@ -70,6 +70,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
export { export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
}; };
@ -242,6 +243,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'sendas', name: 'sendas',
callback: sendMessageAs, callback: sendMessageAs,
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'name', name: 'name',
@ -269,6 +271,14 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.NUMBER], typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -301,6 +311,7 @@ export function initDefaultSlashCommands() {
name: 'sys', name: 'sys',
callback: sendNarratorMessage, callback: sendNarratorMessage,
aliases: ['nar'], aliases: ['nar'],
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'compact', 'compact',
@ -316,6 +327,14 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.NUMBER], typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -355,6 +374,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'comment', name: 'comment',
callback: sendCommentMessage, callback: sendCommentMessage,
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'compact', 'compact',
@ -370,6 +390,14 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.NUMBER], typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -509,7 +537,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'ask', name: 'ask',
callback: askCharacter, callback: askCharacter,
returns: 'the generated text', returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'name', name: 'name',
@ -518,6 +546,14 @@ export function initDefaultSlashCommands() {
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.characters('character'), enumProvider: commonEnumProviders.characters('character'),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'pipe',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -556,6 +592,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'send', name: 'send',
callback: sendUserMessageCallback, callback: sendUserMessageCallback,
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'compact', 'compact',
@ -578,6 +615,14 @@ export function initDefaultSlashCommands() {
defaultValue: '{{user}}', defaultValue: '{{user}}',
enumProvider: commonEnumProviders.personas, enumProvider: commonEnumProviders.personas,
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@ -1568,12 +1613,21 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'listinjects', name: 'listinjects',
callback: listInjectsCallback, callback: listInjectsCallback,
helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>format</code> argument to change the output format.', helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>return</code> argument to change the return type.',
returns: 'JSON object of script injections', returns: 'Optionalls the JSON object of script injections',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'popup-html',
enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
forceEnum: true,
}),
// TODO remove some day
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'format', name: 'format',
description: 'output format', description: '!!! DEPRECATED - use "return" instead !!! output format',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true, isRequired: true,
forceEnum: true, forceEnum: true,
@ -1842,37 +1896,43 @@ function injectCallback(args, value) {
} }
async function listInjectsCallback(args) { async function listInjectsCallback(args) {
const type = String(args?.format).toLowerCase().trim(); /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) { let returnType = args.return;
type !== 'none' && toastr.info('No script injections for the current chat');
return JSON.stringify({}); // Old legacy return type handling
if (args.format) {
toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
const type = String(args?.format).toLowerCase().trim();
if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
type !== 'none' && toastr.info('No script injections for the current chat');
}
switch (type) {
case 'none':
returnType = 'none';
break;
case 'chat':
returnType = 'chat-html';
break;
case 'popup':
default:
returnType = 'popup-html';
break;
}
} }
const injects = Object.entries(chat_metadata.script_injects) // Now the actual new return type handling
.map(([id, inject]) => { const buildTextValue = (injects) => {
const position = Object.entries(extension_prompt_types); const injectsStr = Object.entries(injects)
const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown'; .map(([id, inject]) => {
return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`; const position = Object.entries(extension_prompt_types);
}) const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
.join('\n'); return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
})
.join('\n');
return `### Script injections:\n${injectsStr || 'No script injections for the current chat'}`;
};
const converter = new showdown.Converter(); return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', chat_metadata.script_injects ?? {}, { objectToStringFunc: buildTextValue });
const messageText = `### Script injections:\n${injects}`;
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText));
switch (type) {
case 'none':
break;
case 'chat':
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
break;
case 'popup':
default:
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
break;
}
return JSON.stringify(chat_metadata.script_injects);
} }
/** /**
@ -2559,7 +2619,7 @@ async function askCharacter(args, text) {
// Not supported in group chats // Not supported in group chats
// TODO: Maybe support group chats? // TODO: Maybe support group chats?
if (selected_group) { if (selected_group) {
toastr.error('Cannot run /ask command in a group chat!'); toastr.warning('Cannot run /ask command in a group chat!');
return ''; return '';
} }
@ -2633,7 +2693,9 @@ async function askCharacter(args, text) {
} }
} }
return askResult; const message = askResult ? chat[chat.length - 1] : null;
return await slashCommandReturnHelper.doReturn(args.return ?? 'pipe', message, { objectToStringFunc: x => x.mes });
} }
async function hideMessageCallback(_, arg) { async function hideMessageCallback(_, arg) {
@ -2908,7 +2970,7 @@ function findPersonaByName(name) {
async function sendUserMessageCallback(args, text) { async function sendUserMessageCallback(args, text) {
if (!text) { if (!text) {
console.warn('WARN: No text provided for /send command'); toastr.warning('You must specify text to send');
return; return;
} }
@ -2924,16 +2986,17 @@ async function sendUserMessageCallback(args, text) {
insertAt = chat.length + insertAt; insertAt = chat.length + insertAt;
} }
let message;
if ('name' in args) { if ('name' in args) {
const name = args.name || ''; const name = args.name || '';
const avatar = findPersonaByName(name) || user_avatar; const avatar = findPersonaByName(name) || user_avatar;
await sendMessageAsUser(text, bias, insertAt, compact, name, avatar); message = await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);
} }
else { else {
await sendMessageAsUser(text, bias, insertAt, compact); message = await sendMessageAsUser(text, bias, insertAt, compact);
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
async function deleteMessagesByNameCallback(_, name) { async function deleteMessagesByNameCallback(_, name) {
@ -3221,30 +3284,20 @@ export function getNameAndAvatarForMessage(character, name = null) {
export async function sendMessageAs(args, text) { export async function sendMessageAs(args, text) {
if (!text) { if (!text) {
toastr.warning('You must specify text to send as');
return ''; return '';
} }
let name; let name = args.name?.trim();
let mesText; let mesText;
if (args.name) { if (!name) {
name = args.name.trim();
if (!name && !text) {
toastr.warning('You must specify a name and text to send as');
return '';
}
} else {
const namelessWarningKey = 'sendAsNamelessWarningShown'; const namelessWarningKey = 'sendAsNamelessWarningShown';
if (localStorage.getItem(namelessWarningKey) !== 'true') { if (localStorage.getItem(namelessWarningKey) !== 'true') {
toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
localStorage.setItem(namelessWarningKey, 'true'); localStorage.setItem(namelessWarningKey, 'true');
} }
name = name2; name = name2;
if (!text) {
toastr.warning('You must specify text to send as');
return '';
}
} }
mesText = text.trim(); mesText = text.trim();
@ -3321,11 +3374,12 @@ export async function sendMessageAs(args, text) {
await saveChatConditional(); await saveChatConditional();
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
export async function sendNarratorMessage(args, text) { export async function sendNarratorMessage(args, text) {
if (!text) { if (!text) {
toastr.warning('You must specify text to send');
return ''; return '';
} }
@ -3374,7 +3428,7 @@ export async function sendNarratorMessage(args, text) {
await saveChatConditional(); await saveChatConditional();
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
export async function promptQuietForLoudResponse(who, text) { export async function promptQuietForLoudResponse(who, text) {
@ -3420,6 +3474,7 @@ export async function promptQuietForLoudResponse(who, text) {
async function sendCommentMessage(args, text) { async function sendCommentMessage(args, text) {
if (!text) { if (!text) {
toastr.warning('You must specify text to send');
return ''; return '';
} }
@ -3462,7 +3517,7 @@ async function sendCommentMessage(args, text) {
await saveChatConditional(); await saveChatConditional();
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
/** /**

View File

@ -36,6 +36,7 @@ export const enumIcons = {
message: '💬', message: '💬',
voice: '🎤', voice: '🎤',
server: '🖥️', server: '🖥️',
popup: '🗔',
true: '✔️', true: '✔️',
false: '❌', false: '❌',

View File

@ -0,0 +1,80 @@
import { sendSystemMessage, system_message_types } from '../../script.js';
import { callGenericPopup, POPUP_TYPE } from '../popup.js';
import { escapeHtml } from '../utils.js';
import { enumIcons } from './SlashCommandCommonEnumsProvider.js';
import { enumTypes, SlashCommandEnumValue } from './SlashCommandEnumValue.js';
/** @typedef {'pipe'|'object'|'chat-html'|'chat-text'|'popup-html'|'popup-text'|'toast-html'|'toast-text'|'console'|'none'} SlashCommandReturnType */
export const slashCommandReturnHelper = {
// Without this, VSCode formatter fucks up JS docs. Don't ask me why.
_: false,
/**
* Gets/creates the enum list of types of return relevant for a slash command
*
* @param {object} [options={}] Options
* @param {boolean} [options.allowPipe=true] Allow option to pipe the return value
* @param {boolean} [options.allowObject=false] Allow option to return the value as an object
* @param {boolean} [options.allowChat=false] Allow option to return the value as a chat message
* @param {boolean} [options.allowPopup=false] Allow option to return the value as a popup
* @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
* @returns {SlashCommandEnumValue[]} The enum list
*/
enumList: ({ allowPipe = true, allowObject = false, allowChat = false, allowPopup = false, allowTextVersion = true } = {}) => [
allowPipe && new SlashCommandEnumValue('pipe', 'Return to the pipe for the next command', enumTypes.name, '|'),
allowObject && new SlashCommandEnumValue('object', 'Return as an object (or array) to the pipe for the next command', enumTypes.variable, enumIcons.dictionary),
allowChat && new SlashCommandEnumValue('chat-html', 'Sending a chat message with the return value - Can display HTML', enumTypes.command, enumIcons.message),
allowChat && allowTextVersion && new SlashCommandEnumValue('chat-text', 'Sending a chat message with the return value - Will only display as text', enumTypes.qr, enumIcons.message),
allowPopup && new SlashCommandEnumValue('popup-html', 'Showing as a popup with the return value - Can display HTML', enumTypes.command, enumIcons.popup),
allowPopup && allowTextVersion && new SlashCommandEnumValue('popup-text', 'Showing as a popup with the return value - Will only display as text', enumTypes.qr, enumIcons.popup),
new SlashCommandEnumValue('toast-html', 'Show the return value as a toast notification - Can display HTML', enumTypes.command, ''),
allowTextVersion && new SlashCommandEnumValue('toast-text', 'Show the return value as a toast notification - Will only display as text', enumTypes.qr, ''),
new SlashCommandEnumValue('console', 'Log the return value (object, if it can be one) to the console', enumTypes.enum, '>'),
new SlashCommandEnumValue('none', 'No return value'),
].filter(x => !!x),
/**
* Handles the return value based on the specified type
*
* @param {SlashCommandReturnType} type The type of return
* @param {object|number|string} value The value to return
* @param {object} [options={}] Options
* @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
* @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
* @returns {Promise<*>} The processed return value
*/
async doReturn(type, value, { objectToStringFunc = o => o?.toString(), objectToHtmlFunc = null } = {}) {
const shouldHtml = type.endsWith('html');
const actualConverterFunc = shouldHtml && objectToHtmlFunc ? objectToHtmlFunc : objectToStringFunc;
const stringValue = typeof value !== 'string' ? actualConverterFunc(value) : value;
switch (type) {
case 'popup-html':
case 'popup-text':
case 'chat-text':
case 'chat-html':
case 'toast-text':
case 'toast-html': {
const htmlOrNotHtml = shouldHtml ? DOMPurify.sanitize((new showdown.Converter()).makeHtml(stringValue)) : escapeHtml(stringValue);
if (type.startsWith('popup')) await callGenericPopup(htmlOrNotHtml, POPUP_TYPE.TEXT);
if (type.startsWith('chat')) sendSystemMessage(system_message_types.GENERIC, htmlOrNotHtml);
if (type.startsWith('toast')) toastr.info(htmlOrNotHtml, null, { escapeHtml: !shouldHtml });
return '';
}
case 'pipe':
return stringValue ?? '';
case 'object':
return JSON.stringify(value);
case 'console':
console.info(value);
return '';
case 'none':
return '';
default:
throw new Error(`Unknown return type: ${type}`);
}
},
};

View File

@ -11,6 +11,7 @@ import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureR
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js'; import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
@ -305,7 +306,28 @@ export function replaceVariableMacros(input) {
} }
async function listVariablesCallback(args) { async function listVariablesCallback(args) {
const type = String(args?.format || '').toLowerCase().trim() || 'popup'; /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
let returnType = args.return;
// Old legacy return type handling
if (args.format) {
toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
const type = String(args?.format).toLowerCase().trim();
switch (type) {
case 'none':
returnType = 'none';
break;
case 'chat':
returnType = 'chat-html';
break;
case 'popup':
default:
returnType = 'popup-html';
break;
}
}
// Now the actual new return type handling
const scope = String(args?.scope || '').toLowerCase().trim() || 'all'; const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
if (!chat_metadata.variables) { if (!chat_metadata.variables) {
chat_metadata.variables = {}; chat_metadata.variables = {};
@ -317,35 +339,24 @@ async function listVariablesCallback(args) {
const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : []; const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : []; const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
const buildTextValue = (_) => {
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
const chatName = getCurrentChatId();
const message = [
includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
].filter(x => x).join('\n\n');
return message;
};
const jsonVariables = [ const jsonVariables = [
...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })), ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })), ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
]; ];
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables'; return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', jsonVariables, { objectToStringFunc: buildTextValue });
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
const chatName = getCurrentChatId();
const converter = new showdown.Converter();
const message = [
includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
].filter(x => x).join('\n\n');
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
switch (type) {
case 'none':
break;
case 'chat':
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
break;
case 'popup':
default:
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
break;
}
return JSON.stringify(jsonVariables);
} }
/** /**
@ -916,7 +927,7 @@ export function registerVariableCommands() {
name: 'listvar', name: 'listvar',
callback: listVariablesCallback, callback: listVariablesCallback,
aliases: ['listchatvar'], aliases: ['listchatvar'],
helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>format</code> argument to change the output format.', helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>return</code> argument to change the return type.',
returns: 'JSON list of local variables', returns: 'JSON list of local variables',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
@ -932,9 +943,18 @@ export function registerVariableCommands() {
new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable), new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable),
], ],
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'popup-html',
enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
forceEnum: true,
}),
// TODO remove some day
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'format', name: 'format',
description: 'output format', description: '!!! DEPRECATED - use "return" instead !!! output format',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true, isRequired: true,
forceEnum: true, forceEnum: true,