🎉 初始化项目
This commit is contained in:
136
web-app/public/scripts/macros/definitions/chat-macros.js
Normal file
136
web-app/public/scripts/macros/definitions/chat-macros.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import { MacroRegistry, MacroCategory, MacroValueType } from '../engine/MacroRegistry.js';
|
||||
import { chat, chat_metadata } from '../../../script.js';
|
||||
|
||||
/**
|
||||
* Registers macros that inspect the current chat log and swipe state
|
||||
* (message texts, indices, swipes, and context boundaries).
|
||||
*/
|
||||
export function registerChatMacros() {
|
||||
MacroRegistry.registerMacro('lastMessage', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: 'Last message in the chat.',
|
||||
returns: 'Last message in the chat.',
|
||||
handler: () => String(getLastMessage() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('lastMessageId', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: 'Index of the last message in the chat.',
|
||||
returns: 'Index of the last message in the chat.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
handler: () => String(getLastMessageId() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('lastUserMessage', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: 'Last user message in the chat.',
|
||||
returns: 'Last user message in the chat.',
|
||||
handler: () => String(getLastUserMessage() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('lastCharMessage', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: 'Last character/bot message in the chat.',
|
||||
returns: 'Last character/bot message in the chat.',
|
||||
handler: () => String(getLastCharMessage() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('firstIncludedMessageId', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: 'Index of the first message included in the current context.',
|
||||
returns: 'Index of the first message included in the context.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
handler: () => String(getFirstIncludedMessageId() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('firstDisplayedMessageId', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: 'Index of the first displayed message in the chat.',
|
||||
returns: 'Index of the first displayed message in the chat.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
handler: () => String(getFirstDisplayedMessageId() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('lastSwipeId', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: '1-based index of the last swipe for the last message.',
|
||||
returns: '1-based index of the last swipe.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
handler: () => String(getLastSwipeId() ?? ''),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('currentSwipeId', {
|
||||
category: MacroCategory.CHAT,
|
||||
description: '1-based index of the current swipe.',
|
||||
returns: '1-based index of the current swipe.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
handler: () => String(getCurrentSwipeId() ?? ''),
|
||||
});
|
||||
}
|
||||
|
||||
function getLastMessageId({ exclude_swipe_in_propress = true, filter = null } = {}) {
|
||||
if (!Array.isArray(chat) || chat.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = chat.length - 1; i >= 0; i--) {
|
||||
const message = chat[i];
|
||||
|
||||
if (exclude_swipe_in_propress && message.swipes && message.swipe_id >= message.swipes.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filter || filter(message)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getLastMessage() {
|
||||
const mid = getLastMessageId();
|
||||
return typeof mid === 'number' ? (chat[mid]?.mes ?? '') : '';
|
||||
}
|
||||
|
||||
function getLastUserMessage() {
|
||||
const mid = getLastMessageId({ filter: m => m.is_user && !m.is_system });
|
||||
return typeof mid === 'number' ? (chat[mid]?.mes ?? '') : '';
|
||||
}
|
||||
|
||||
function getLastCharMessage() {
|
||||
const mid = getLastMessageId({ filter: m => !m.is_user && !m.is_system });
|
||||
return typeof mid === 'number' ? (chat[mid]?.mes ?? '') : '';
|
||||
}
|
||||
|
||||
function getFirstIncludedMessageId() {
|
||||
const value = chat_metadata['lastInContextMessageId'];
|
||||
return typeof value === 'number' ? value : null;
|
||||
}
|
||||
|
||||
function getFirstDisplayedMessageId() {
|
||||
const mesElement = document.querySelector('#chat .mes');
|
||||
const mesId = Number(mesElement?.getAttribute('mesid'));
|
||||
if (!Number.isNaN(mesId) && mesId >= 0) {
|
||||
return mesId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getLastSwipeId() {
|
||||
const mid = getLastMessageId({ exclude_swipe_in_propress: false });
|
||||
if (typeof mid !== 'number') {
|
||||
return null;
|
||||
}
|
||||
const swipes = chat[mid]?.swipes;
|
||||
return Array.isArray(swipes) ? swipes.length : null;
|
||||
}
|
||||
|
||||
function getCurrentSwipeId() {
|
||||
const mid = getLastMessageId({ exclude_swipe_in_propress: false });
|
||||
if (typeof mid !== 'number') {
|
||||
return null;
|
||||
}
|
||||
const swipeId = chat[mid]?.swipe_id;
|
||||
return typeof swipeId === 'number' ? swipeId + 1 : null;
|
||||
}
|
||||
280
web-app/public/scripts/macros/definitions/core-macros.js
Normal file
280
web-app/public/scripts/macros/definitions/core-macros.js
Normal file
@@ -0,0 +1,280 @@
|
||||
import { seedrandom, droll } from '../../../lib.js';
|
||||
import { chat_metadata, main_api, getMaxContextSize, extension_prompts, getCurrentChatId } from '../../../script.js';
|
||||
import { getStringHash } from '../../utils.js';
|
||||
import { textgenerationwebui_banned_in_macros } from '../../textgen-settings.js';
|
||||
import { inject_ids } from '../../constants.js';
|
||||
import { MacroRegistry, MacroCategory, MacroValueType } from '../engine/MacroRegistry.js';
|
||||
|
||||
/**
|
||||
* Registers SillyTavern's core built-in macros in the MacroRegistry.
|
||||
*
|
||||
* These macros correspond to the main {{...}} macros that are available
|
||||
* in prompts (time/date/chat info, utility macros, etc.). They are
|
||||
* intended to preserve the behavior of the existing regex-based macros
|
||||
* in macros.js while using the new MacroRegistry/MacroEngine pipeline.
|
||||
*/
|
||||
export function registerCoreMacros() {
|
||||
// {{space}} -> ' '
|
||||
MacroRegistry.registerMacro('space', {
|
||||
category: MacroCategory.UTILITY,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'count',
|
||||
optional: true,
|
||||
defaultValue: '1',
|
||||
type: MacroValueType.INTEGER,
|
||||
description: 'Number of spaces to insert.',
|
||||
},
|
||||
],
|
||||
description: 'Returns one or more spaces. One space by default, more if the count argument is specified.',
|
||||
returns: 'One or more spaces.',
|
||||
exampleUsage: ['{{space}}', '{{space::4}}'],
|
||||
handler: ({ unnamedArgs: [count] }) => ' '.repeat(Number(count ?? 1)),
|
||||
});
|
||||
|
||||
// {{newline}} -> '\n'
|
||||
MacroRegistry.registerMacro('newline', {
|
||||
category: MacroCategory.UTILITY,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'count',
|
||||
optional: true,
|
||||
defaultValue: '1',
|
||||
type: MacroValueType.INTEGER,
|
||||
description: 'Number of newlines to insert.',
|
||||
},
|
||||
],
|
||||
description: 'Inserts one or more newlines. One newline by default, more if the count argument is specified.',
|
||||
returns: 'One or more \\n.',
|
||||
exampleUsage: ['{{newline}}', '{{newline::2}}'],
|
||||
handler: ({ unnamedArgs: [count] }) => '\n'.repeat(Number(count ?? 1)),
|
||||
});
|
||||
|
||||
// {{noop}} -> ''
|
||||
MacroRegistry.registerMacro('noop', {
|
||||
category: MacroCategory.UTILITY,
|
||||
description: 'Does nothing and produces an empty string.',
|
||||
returns: '',
|
||||
handler: () => '',
|
||||
});
|
||||
|
||||
// {{trim}} -> macro will currently replace itself with itself. Trimming is handled in post-processing.
|
||||
MacroRegistry.registerMacro('trim', {
|
||||
category: MacroCategory.UTILITY,
|
||||
description: 'Trims all whitespaces around the trim macro.',
|
||||
returns: '',
|
||||
handler: () => '{{trim}}',
|
||||
});
|
||||
|
||||
// {{input}} -> current textarea content
|
||||
MacroRegistry.registerMacro('input', {
|
||||
category: MacroCategory.UTILITY,
|
||||
description: 'Current text from the send textarea.',
|
||||
returns: 'Current text from the send textarea.',
|
||||
handler: () => (/** @type {HTMLTextAreaElement} */(document.querySelector('#send_textarea')))?.value ?? '',
|
||||
});
|
||||
|
||||
// {{maxPrompt}} -> max context size
|
||||
MacroRegistry.registerMacro('maxPrompt', {
|
||||
category: MacroCategory.STATE,
|
||||
description: 'Maximum prompt context size.',
|
||||
returns: 'Maximum prompt context size.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
handler: () => String(getMaxContextSize()),
|
||||
});
|
||||
|
||||
// String utilities
|
||||
MacroRegistry.registerMacro('reverse', {
|
||||
category: MacroCategory.UTILITY,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'value',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The string to reverse.',
|
||||
},
|
||||
],
|
||||
description: 'Reverses the characters of the argument provided.',
|
||||
returns: 'Reversed string.',
|
||||
exampleUsage: ['{{reverse::I am Lana}}'],
|
||||
handler: ({ unnamedArgs: [value] }) => Array.from(value).reverse().join(''),
|
||||
});
|
||||
|
||||
// Comment macro: {{// ...}} -> '' (consumes any arguments)
|
||||
MacroRegistry.registerMacro('//', {
|
||||
aliases: [{ alias: 'comment' }],
|
||||
category: MacroCategory.UTILITY,
|
||||
list: true, // We consume any arguments as if this is a list, but we'll ignore them in the handler anyway
|
||||
strictArgs: false, // and we also always remove it, even if the parsing might say it's invalid
|
||||
description: 'Comment macro that produces an empty string. Can be used for writing into prompt definitions, without being passed to the context.',
|
||||
returns: '',
|
||||
displayOverride: '{{// ...}}',
|
||||
exampleUsage: ['{{// This is a comment}}'],
|
||||
handler: () => '',
|
||||
});
|
||||
|
||||
// Time and date macros
|
||||
// Dice roll macro: {{roll 1d6}} or {{roll: 1d6}}
|
||||
MacroRegistry.registerMacro('roll', {
|
||||
category: MacroCategory.RANDOM,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'formula',
|
||||
sampleValue: '1d20',
|
||||
description: 'Dice roll formula using droll syntax (e.g. 1d20).',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
description: 'Rolls dice using droll syntax (e.g. {{roll 1d20}}).',
|
||||
returns: 'Dice roll result.',
|
||||
returnType: MacroValueType.INTEGER,
|
||||
exampleUsage: [
|
||||
'{{roll::1d20}}',
|
||||
'{{roll::6}}',
|
||||
'{{roll::3d6+4}}',
|
||||
],
|
||||
handler: ({ unnamedArgs: [formula] }) => {
|
||||
// If only digits were provided, treat it as `1dX`.
|
||||
if (/^\d+$/.test(formula)) {
|
||||
formula = `1d${formula}`;
|
||||
}
|
||||
|
||||
const isValid = droll.validate(formula);
|
||||
if (!isValid) {
|
||||
console.debug(`Invalid roll formula: ${formula}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const result = droll.roll(formula);
|
||||
if (result === false) return '';
|
||||
return String(result.total);
|
||||
},
|
||||
});
|
||||
|
||||
// Random choice macro: {{random::a::b}} or {{random a,b}}
|
||||
MacroRegistry.registerMacro('random', {
|
||||
category: MacroCategory.RANDOM,
|
||||
list: true,
|
||||
description: 'Picks a random item from a list. Will be re-rolled every time macros are resolved.',
|
||||
returns: 'Randomly selected item from the list.',
|
||||
exampleUsage: ['{{random::blonde::brown::red::black::blue}}'],
|
||||
handler: ({ list }) => {
|
||||
// Handle old legacy cases, where we have to split the list manually
|
||||
if (list.length === 1) {
|
||||
list = readSingleArgsRandomList(list[0]);
|
||||
}
|
||||
|
||||
if (list.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const rng = seedrandom('added entropy.', { entropy: true });
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
return list[randomIndex];
|
||||
},
|
||||
});
|
||||
|
||||
// Deterministic choice macro: {{pick::a::b}} or {{pick a,b}}
|
||||
MacroRegistry.registerMacro('pick', {
|
||||
category: MacroCategory.RANDOM,
|
||||
list: true,
|
||||
description: 'Picks a random item from a list, but keeps the choice stable for a given chat and macro position.',
|
||||
returns: 'Stable randomly selected item from the list.',
|
||||
exampleUsage: ['{{pick::blonde::brown::red::black::blue}}'],
|
||||
handler: ({ list, range, env }) => {
|
||||
// Handle old legacy cases, where we have to split the list manually
|
||||
if (list.length === 1) {
|
||||
list = readSingleArgsRandomList(list[0]);
|
||||
}
|
||||
|
||||
if (!list.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const chatIdHash = getChatIdHash();
|
||||
|
||||
// Use the full original input string for deterministic behavior
|
||||
const rawContentHash = env.contentHash;
|
||||
|
||||
const offset = typeof range?.startOffset === 'number' ? range.startOffset : 0;
|
||||
|
||||
const combinedSeedString = `${chatIdHash}-${rawContentHash}-${offset}`;
|
||||
const finalSeed = getStringHash(combinedSeedString);
|
||||
const rng = seedrandom(String(finalSeed));
|
||||
const randomIndex = Math.floor(rng() * list.length);
|
||||
return list[randomIndex];
|
||||
},
|
||||
});
|
||||
|
||||
/** @param {string} listString @return {string[]} */
|
||||
function readSingleArgsRandomList(listString) {
|
||||
// If it contains double colons, those will have precedence over comma-seperated lists.
|
||||
// This can only happen if the macro only had a single colon to introduce the list...
|
||||
// like, {{random:a::b::c}}
|
||||
if (listString.includes('::')) {
|
||||
return listString.split('::').map((/** @type {string} */ item) => item.trim());
|
||||
}
|
||||
// Otherwise, we fall back and split by commas that may be present
|
||||
return listString
|
||||
.replace(/\\,/g, '##<23>COMMA<4D>##')
|
||||
.split(',')
|
||||
.map((/** @type {string} */ item) => item.trim().replace(/##<23>COMMA<4D>##/g, ','));
|
||||
}
|
||||
|
||||
// Banned words macro: {{banned "word"}}
|
||||
MacroRegistry.registerMacro('banned', {
|
||||
category: MacroCategory.UTILITY,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'word',
|
||||
sampleValue: 'word',
|
||||
description: 'Word to ban for textgenerationwebui backend.',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
description: 'Bans a word for textgenerationwebui backend. (Strips quotes surrounding the banned word, if present)',
|
||||
returns: '',
|
||||
exampleUsage: ['{{banned::delve}}'],
|
||||
handler: ({ unnamedArgs: [bannedWord] }) => {
|
||||
// Strip quotes via regex, which were allowed in legacy syntax
|
||||
bannedWord = bannedWord.replace(/^"|"$/g, '');
|
||||
if (main_api === 'textgenerationwebui') {
|
||||
console.log('Found banned word in macros: ' + bannedWord);
|
||||
textgenerationwebui_banned_in_macros.push(bannedWord);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
// Outlet macro: {{outlet::key}}
|
||||
MacroRegistry.registerMacro('outlet', {
|
||||
category: MacroCategory.UTILITY,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'key',
|
||||
sampleValue: 'my-outlet-key',
|
||||
description: 'Outlet key.',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
description: 'Returns the world info outlet prompt for a given outlet key.',
|
||||
returns: 'World info outlet prompt.',
|
||||
exampleUsage: ['{{outlet::character-achievements}}'],
|
||||
handler: ({ unnamedArgs: [outlet] }) => {
|
||||
if (!outlet) return '';
|
||||
const value = extension_prompts[inject_ids.CUSTOM_WI_OUTLET(outlet)]?.value;
|
||||
return value || '';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getChatIdHash() {
|
||||
const cachedIdHash = chat_metadata['chat_id_hash'];
|
||||
if (typeof cachedIdHash === 'number') {
|
||||
return cachedIdHash;
|
||||
}
|
||||
|
||||
const chatId = chat_metadata['main_chat'] ?? getCurrentChatId();
|
||||
const chatIdHash = getStringHash(chatId);
|
||||
chat_metadata['chat_id_hash'] = chatIdHash;
|
||||
return chatIdHash;
|
||||
}
|
||||
181
web-app/public/scripts/macros/definitions/env-macros.js
Normal file
181
web-app/public/scripts/macros/definitions/env-macros.js
Normal file
@@ -0,0 +1,181 @@
|
||||
import { MacroRegistry, MacroCategory, MacroValueType } from '../engine/MacroRegistry.js';
|
||||
import { isMobile } from '../../RossAscends-mods.js';
|
||||
import { parseMesExamples, main_api } from '../../../script.js';
|
||||
import { power_user } from '../../power-user.js';
|
||||
import { formatInstructModeExamples } from '../../instruct-mode.js';
|
||||
|
||||
/** @typedef {import('../engine/MacroEnv.types.js').MacroEnv} MacroEnv */
|
||||
|
||||
/**
|
||||
* Registers macros that mostly act as simple accessors to MacroEnv fields
|
||||
* (names, character card fields, system metadata, extras) or basic
|
||||
* environment flags.
|
||||
*/
|
||||
export function registerEnvMacros() {
|
||||
// Names and participant macros (from MacroEnv.names)
|
||||
MacroRegistry.registerMacro('user', {
|
||||
category: MacroCategory.NAMES,
|
||||
description: 'Your current Persona username.',
|
||||
returns: 'Persona username.',
|
||||
handler: ({ env }) => env.names.user,
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('char', {
|
||||
category: MacroCategory.NAMES,
|
||||
description: 'The character\'s name.',
|
||||
returns: 'Character name.',
|
||||
handler: ({ env }) => env.names.char,
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('group', {
|
||||
aliases: [{ alias: 'charIfNotGroup', visible: false }],
|
||||
category: MacroCategory.NAMES,
|
||||
description: 'Comma-separated list of group member names (including muted) or the character name in solo chats.',
|
||||
returns: 'List of group member names.',
|
||||
handler: ({ env }) => env.names.group ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('groupNotMuted', {
|
||||
category: MacroCategory.NAMES,
|
||||
description: 'Comma-separated list of group member names excluding muted members.',
|
||||
returns: 'List of group member names excluding muted members.',
|
||||
handler: ({ env }) => env.names.groupNotMuted ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('notChar', {
|
||||
category: MacroCategory.NAMES,
|
||||
description: 'Comma-separated list of all participants except the current speaker.',
|
||||
returns: 'List of all participants except the current speaker.',
|
||||
handler: ({ env }) => env.names.notChar ?? '',
|
||||
});
|
||||
|
||||
// Character card field macros (from MacroEnv.character)
|
||||
MacroRegistry.registerMacro('charPrompt', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s Main Prompt override.',
|
||||
returns: 'Character Main Prompt override.',
|
||||
handler: ({ env }) => env.character.charPrompt ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('charInstruction', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s Post-History Instructions override.',
|
||||
returns: 'Character Post-History Instructions override.',
|
||||
handler: ({ env }) => env.character.charInstruction ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('charDescription', {
|
||||
aliases: [{ alias: 'description' }],
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s description.',
|
||||
returns: 'Character description.',
|
||||
handler: ({ env }) => env.character.description ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('charPersonality', {
|
||||
aliases: [{ alias: 'personality' }],
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s personality.',
|
||||
returns: 'Character personality.',
|
||||
handler: ({ env }) => env.character.personality ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('charScenario', {
|
||||
aliases: [{ alias: 'scenario' }],
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s scenario.',
|
||||
returns: 'Character scenario.',
|
||||
handler: ({ env }) => env.character.scenario ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('persona', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'Your current Persona description.',
|
||||
returns: 'Persona description.',
|
||||
handler: ({ env }) => env.character.persona ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('mesExamplesRaw', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'Unformatted dialogue examples from the character card.',
|
||||
returns: 'Unformatted dialogue examples.',
|
||||
handler: ({ env }) => env.character.mesExamplesRaw ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('mesExamples', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s dialogue examples, formatted for instruct mode when enabled.',
|
||||
returns: 'Formatted dialogue examples.',
|
||||
handler: ({ env }) => {
|
||||
const raw = env.character.mesExamplesRaw ?? '';
|
||||
if (!raw) return '';
|
||||
|
||||
const isInstruct = !!power_user?.instruct?.enabled && main_api !== 'openai';
|
||||
const parsed = parseMesExamples(raw, isInstruct);
|
||||
|
||||
if (!Array.isArray(parsed) || parsed.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (!isInstruct) {
|
||||
return parsed.join('');
|
||||
}
|
||||
|
||||
const formatted = formatInstructModeExamples(parsed, env.names.user, env.names.char);
|
||||
return Array.isArray(formatted) ? formatted.join('') : '';
|
||||
},
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('charDepthPrompt', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s @ Depth Note.',
|
||||
returns: 'Character @ Depth Note.',
|
||||
handler: ({ env }) => env.character.charDepthPrompt ?? '',
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('charCreatorNotes', {
|
||||
aliases: [{ alias: 'creatorNotes' }],
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'Creator notes from the character card.',
|
||||
returns: 'Creator notes.',
|
||||
handler: ({ env }) => env.character.creatorNotes ?? '',
|
||||
});
|
||||
|
||||
// Character version macros (legacy variants and documented {{charVersion}})
|
||||
MacroRegistry.registerMacro('charVersion', {
|
||||
aliases: [
|
||||
{ alias: 'version', visible: false }, // Legacy alias
|
||||
{ alias: 'char_version', visible: false }, // Legacy underscore variant
|
||||
],
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'The character\'s version number.',
|
||||
returns: 'Character version number.',
|
||||
handler: ({ env }) => env.character.version ?? '',
|
||||
});
|
||||
|
||||
// System / env extras macros (from MacroEnv.system / MacroEnv.extra)
|
||||
MacroRegistry.registerMacro('model', {
|
||||
category: MacroCategory.STATE,
|
||||
description: 'Model name for the currently selected API (Chat Completion or Chat Completion).',
|
||||
returns: 'Model name.',
|
||||
handler: ({ env }) => env.system.model,
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('original', {
|
||||
category: MacroCategory.CHARACTER,
|
||||
description: 'Original message content for {{original}} substitution in in character prompt overrides.',
|
||||
returns: 'Original message content.',
|
||||
handler: ({ env }) => {
|
||||
const value = env.functions.original();
|
||||
return value;
|
||||
},
|
||||
});
|
||||
|
||||
// Device / environment macros
|
||||
MacroRegistry.registerMacro('isMobile', {
|
||||
category: MacroCategory.STATE,
|
||||
description: '"true" if currently running in a mobile environment, "false" otherwise.',
|
||||
returns: 'Whether the environment is mobile.',
|
||||
returnType: MacroValueType.BOOLEAN,
|
||||
handler: () => String(isMobile()),
|
||||
});
|
||||
}
|
||||
76
web-app/public/scripts/macros/definitions/instruct-macros.js
Normal file
76
web-app/public/scripts/macros/definitions/instruct-macros.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { MacroRegistry, MacroCategory } from '../engine/MacroRegistry.js';
|
||||
import { power_user } from '../../power-user.js';
|
||||
|
||||
/**
|
||||
* Registers instruct-mode related {{...}} macros (instruct* and system
|
||||
* prompt/context macros) in the MacroRegistry.
|
||||
*/
|
||||
export function registerInstructMacros() {
|
||||
/**
|
||||
* Helper to register macros that just expose a value from power_user.instruct.
|
||||
* The first name is the primary, subsequent names become visible aliases.
|
||||
* @param {string[]} names - First is primary, rest are aliases.
|
||||
* @param {() => string} getValue
|
||||
* @param {() => boolean} isEnabled
|
||||
* @param {string} description
|
||||
* @param {string} [category=MacroCategory.PROMPTS]
|
||||
*/
|
||||
function registerSimple(names, getValue, isEnabled, description, category = MacroCategory.PROMPTS) {
|
||||
const [primary, ...aliasNames] = names;
|
||||
const aliases = aliasNames.map(alias => ({ alias }));
|
||||
|
||||
MacroRegistry.registerMacro(primary, {
|
||||
category,
|
||||
description,
|
||||
aliases: aliases.length > 0 ? aliases : undefined,
|
||||
handler: () => (isEnabled() ? (getValue() ?? '') : ''),
|
||||
});
|
||||
}
|
||||
|
||||
const instEnabled = () => !!power_user.instruct.enabled;
|
||||
const sysEnabled = () => !!power_user.sysprompt.enabled;
|
||||
|
||||
// Instruct template macros
|
||||
registerSimple(['instructStoryStringPrefix'], () => power_user.instruct.story_string_prefix, instEnabled, 'Instruct story string prefix.');
|
||||
registerSimple(['instructStoryStringSuffix'], () => power_user.instruct.story_string_suffix, instEnabled, 'Instruct story string suffix.');
|
||||
|
||||
registerSimple(['instructUserPrefix', 'instructInput'], () => power_user.instruct.input_sequence, instEnabled, 'Instruct input / user prefix sequence.');
|
||||
registerSimple(['instructUserSuffix'], () => power_user.instruct.input_suffix, instEnabled, 'Instruct input / user suffix sequence.');
|
||||
|
||||
registerSimple(['instructAssistantPrefix', 'instructOutput'], () => power_user.instruct.output_sequence, instEnabled, 'Instruct output / assistant prefix sequence.');
|
||||
registerSimple(['instructAssistantSuffix', 'instructSeparator'], () => power_user.instruct.output_suffix, instEnabled, 'Instruct output / assistant suffix sequence.');
|
||||
|
||||
registerSimple(['instructSystemPrefix'], () => power_user.instruct.system_sequence, instEnabled, 'Instruct system prefix sequence.');
|
||||
registerSimple(['instructSystemSuffix'], () => power_user.instruct.system_suffix, instEnabled, 'Instruct system suffix sequence.');
|
||||
|
||||
registerSimple(['instructFirstAssistantPrefix', 'instructFirstOutputPrefix'], () => power_user.instruct.first_output_sequence || power_user.instruct.output_sequence, instEnabled, 'Instruct first assistant / output prefix sequence');
|
||||
registerSimple(['instructLastAssistantPrefix', 'instructLastOutputPrefix'], () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence, instEnabled, 'Instruct last assistant / output prefix sequence.');
|
||||
|
||||
registerSimple(['instructStop'], () => power_user.instruct.stop_sequence, instEnabled, 'Instruct stop sequence.');
|
||||
registerSimple(['instructUserFiller'], () => power_user.instruct.user_alignment_message, instEnabled, 'Instruct user alignment filler.');
|
||||
registerSimple(['instructSystemInstructionPrefix'], () => power_user.instruct.last_system_sequence, instEnabled, 'Instruct system instruction prefix sequence.');
|
||||
|
||||
registerSimple(['instructFirstUserPrefix', 'instructFirstInput'], () => power_user.instruct.first_input_sequence || power_user.instruct.input_sequence, instEnabled, 'Instruct first user / input prefix sequence.');
|
||||
registerSimple(['instructLastUserPrefix', 'instructLastInput'], () => power_user.instruct.last_input_sequence || power_user.instruct.input_sequence, instEnabled, 'Instruct last user / input prefix sequence.');
|
||||
|
||||
// System prompt macros
|
||||
registerSimple(['defaultSystemPrompt', 'instructSystem', 'instructSystemPrompt'], () => power_user.sysprompt.content, sysEnabled, 'Default system prompt.');
|
||||
|
||||
MacroRegistry.registerMacro('systemPrompt', {
|
||||
category: MacroCategory.PROMPTS,
|
||||
description: 'Active system prompt text (optionally overridden by character prompt)',
|
||||
handler: ({ env }) => {
|
||||
const isEnabled = !!power_user.sysprompt.enabled;
|
||||
if (!isEnabled) return '';
|
||||
|
||||
if (power_user.prefer_character_prompt && env.character.charPrompt) {
|
||||
return env.character.charPrompt;
|
||||
}
|
||||
return power_user.sysprompt.content ?? '';
|
||||
},
|
||||
});
|
||||
|
||||
// Context template macros
|
||||
registerSimple(['exampleSeparator', 'chatSeparator'], () => power_user.context.example_separator, () => true, 'Separator used between example chat blocks in text completion prompts.');
|
||||
registerSimple(['chatStart'], () => power_user.context.chat_start, () => true, 'Chat start marker used in text completion prompts.');
|
||||
}
|
||||
40
web-app/public/scripts/macros/definitions/state-macros.js
Normal file
40
web-app/public/scripts/macros/definitions/state-macros.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { MacroRegistry, MacroCategory } from '../engine/MacroRegistry.js';
|
||||
import { eventSource, event_types } from '../../events.js';
|
||||
|
||||
let lastGenerationTypeValue = '';
|
||||
let lastGenerationTypeTrackingInitialized = false;
|
||||
|
||||
function ensureLastGenerationTypeTracking() {
|
||||
if (lastGenerationTypeTrackingInitialized) {
|
||||
return;
|
||||
}
|
||||
lastGenerationTypeTrackingInitialized = true;
|
||||
|
||||
try {
|
||||
eventSource?.on?.(event_types.GENERATION_STARTED, (type, _params, isDryRun) => {
|
||||
if (isDryRun) return;
|
||||
lastGenerationTypeValue = type || 'normal';
|
||||
});
|
||||
|
||||
eventSource?.on?.(event_types.CHAT_CHANGED, () => {
|
||||
lastGenerationTypeValue = '';
|
||||
});
|
||||
} catch {
|
||||
// In non-runtime environments (tests), eventSource may be undefined or not fully initialized.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers macros that depend on runtime application state or event tracking
|
||||
* rather than static environment fields.
|
||||
*/
|
||||
export function registerStateMacros() {
|
||||
ensureLastGenerationTypeTracking();
|
||||
|
||||
MacroRegistry.registerMacro('lastGenerationType', {
|
||||
category: MacroCategory.STATE,
|
||||
description: 'Type of the last queued generation request (e.g. "normal", "impersonate", "regenerate", "quiet", "swipe", "continue"). Empty if none yet or chat was switched.',
|
||||
returns: 'Type of the last queued generation request.',
|
||||
handler: () => lastGenerationTypeValue,
|
||||
});
|
||||
}
|
||||
151
web-app/public/scripts/macros/definitions/time-macros.js
Normal file
151
web-app/public/scripts/macros/definitions/time-macros.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import { moment } from '../../../lib.js';
|
||||
import { chat } from '../../../script.js';
|
||||
import { timestampToMoment } from '../../utils.js';
|
||||
import { MacroRegistry, MacroCategory, MacroValueType } from '../engine/MacroRegistry.js';
|
||||
|
||||
/**
|
||||
* Registers time/date related macros and utilities.
|
||||
*/
|
||||
export function registerTimeMacros() {
|
||||
// Time and date macros
|
||||
MacroRegistry.registerMacro('time', {
|
||||
category: MacroCategory.TIME,
|
||||
// Optional single list argument: UTC offset, e.g. {{time::UTC+2}}
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'offset',
|
||||
optional: true,
|
||||
defaultValue: 'null',
|
||||
type: MacroValueType.STRING,
|
||||
sampleValue: 'UTC+2',
|
||||
description: 'UTC offset in the format UTC±(offset).',
|
||||
},
|
||||
],
|
||||
description: 'Current local time, or UTC offset when called as {{time::UTC±(offset)}}',
|
||||
returns: 'A time string in the format HH:mm.',
|
||||
displayOverride: '{{time::[UTC±(offset)]}}',
|
||||
exampleUsage: ['{{time}}', '{{time::UTC+2}}', '{{time::UTC-7}}'],
|
||||
handler: ({ unnamedArgs: [offsetSpec] }) => {
|
||||
if (!offsetSpec) return moment().format('LT');
|
||||
|
||||
const match = /^UTC([+-]\d+)$/.exec(offsetSpec);
|
||||
if (!match) return moment().format('LT');
|
||||
|
||||
const offset = Number.parseInt(match[1], 10);
|
||||
if (Number.isNaN(offset)) return moment().format('LT');
|
||||
|
||||
return moment().utc().utcOffset(offset).format('LT');
|
||||
},
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('date', {
|
||||
category: MacroCategory.TIME,
|
||||
description: 'Current local date as a string in the local short format.',
|
||||
returns: 'Current local date in local short format.',
|
||||
handler: () => moment().format('LL'),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('weekday', {
|
||||
category: MacroCategory.TIME,
|
||||
description: 'Current weekday name.',
|
||||
returns: 'Current weekday name.',
|
||||
handler: () => moment().format('dddd'),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('isotime', {
|
||||
category: MacroCategory.TIME,
|
||||
description: 'Current time in HH:mm format.',
|
||||
returns: 'Current time in HH:mm format.',
|
||||
handler: () => moment().format('HH:mm'),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('isodate', {
|
||||
category: MacroCategory.TIME,
|
||||
description: 'Current date in YYYY-MM-DD format.',
|
||||
returns: 'Current date in YYYY-MM-DD format.',
|
||||
handler: () => moment().format('YYYY-MM-DD'),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('datetimeformat', {
|
||||
category: MacroCategory.TIME,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'format',
|
||||
sampleValue: 'YYYY-MM-DD HH:mm:ss',
|
||||
description: 'Moment.js format string.',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
description: 'Formats the current date/time using the given moment.js format string.',
|
||||
returns: 'Formatted date/time string.',
|
||||
exampleUsage: ['{{datetimeformat::YYYY-MM-DD HH:mm:ss}}', '{{datetimeformat::LLLL}}'],
|
||||
handler: ({ unnamedArgs: [format] }) => moment().format(format),
|
||||
});
|
||||
|
||||
MacroRegistry.registerMacro('idleDuration', {
|
||||
aliases: [{ alias: 'idle_duration', visible: false }],
|
||||
category: MacroCategory.TIME,
|
||||
description: 'Human-readable duration since the last user message.',
|
||||
returns: 'Human-readable duration since the last user message.',
|
||||
handler: () => getTimeSinceLastMessage(),
|
||||
});
|
||||
|
||||
// Time difference between two values
|
||||
MacroRegistry.registerMacro('timeDiff', {
|
||||
category: MacroCategory.TIME,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'left',
|
||||
sampleValue: '2023-01-01 12:00:00',
|
||||
description: 'Left time value.',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'right',
|
||||
sampleValue: '2023-01-01 15:00:00',
|
||||
description: 'Right time value.',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
description: 'Human-readable difference between two times. Order of times does not matter, it will return the absolute difference.',
|
||||
returns: 'Human-readable difference between two times.',
|
||||
displayOverride: '{{timeDiff::left::right}}', // Shorten this, otherwise it's too long. Full dates don't really help for understanding the macro.
|
||||
exampleUsage: ['{{ timeDiff :: 2023-01-01 12:00:00 :: 2023-01-01 15:00:00 }}'],
|
||||
handler: ({ unnamedArgs: [left, right] }) => {
|
||||
const diff = moment.duration(moment(left).diff(moment(right)));
|
||||
return diff.humanize(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getTimeSinceLastMessage() {
|
||||
const now = moment();
|
||||
|
||||
if (Array.isArray(chat) && chat.length > 0) {
|
||||
let lastMessage;
|
||||
let takeNext = false;
|
||||
|
||||
for (let i = chat.length - 1; i >= 0; i--) {
|
||||
const message = chat[i];
|
||||
|
||||
if (message.is_system) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (message.is_user && takeNext) {
|
||||
lastMessage = message;
|
||||
break;
|
||||
}
|
||||
|
||||
takeNext = true;
|
||||
}
|
||||
|
||||
if (lastMessage?.send_date) {
|
||||
const lastMessageDate = timestampToMoment(lastMessage.send_date);
|
||||
const duration = moment.duration(now.diff(lastMessageDate));
|
||||
return duration.humanize();
|
||||
}
|
||||
}
|
||||
|
||||
return 'just now';
|
||||
}
|
||||
224
web-app/public/scripts/macros/definitions/variable-macros.js
Normal file
224
web-app/public/scripts/macros/definitions/variable-macros.js
Normal file
@@ -0,0 +1,224 @@
|
||||
import { MacroRegistry, MacroCategory, MacroValueType } from '../engine/MacroRegistry.js';
|
||||
|
||||
/**
|
||||
* Registers variable-related {{...}} macros that operate on local and global
|
||||
* variables (e.g. {{setvar}}, {{getvar}}, {{incvar}}, etc.).
|
||||
*/
|
||||
export function registerVariableMacros() {
|
||||
const ctx = SillyTavern.getContext();
|
||||
|
||||
// {{setvar::name::value}} -> '' (side-effect on local variable)
|
||||
MacroRegistry.registerMacro('setvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the local variable to set.',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: [MacroValueType.STRING, MacroValueType.NUMBER],
|
||||
description: 'The value to set the local variable to.',
|
||||
},
|
||||
],
|
||||
description: 'Sets a local variable to the given value.',
|
||||
returns: '',
|
||||
exampleUsage: ['{{setvar::myvar::foo}}', '{{setvar::myintvar::3}}'],
|
||||
handler: ({ unnamedArgs: [name, value] }) => {
|
||||
ctx.variables.local.set(name, value);
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
// {{addvar::name::value}} -> '' (side-effect via addLocalVariable)
|
||||
MacroRegistry.registerMacro('addvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the local variable to add to.',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: [MacroValueType.STRING, MacroValueType.NUMBER],
|
||||
description: 'The value to add to the local variable.',
|
||||
},
|
||||
],
|
||||
description: 'Adds a value to an existing local variable (numeric or string append). If the variable does not exist, it will be created.',
|
||||
returns: '',
|
||||
exampleUsage: ['{{addvar::mystrvar::foo}}', '{{addvar::myintvar::3}}'],
|
||||
handler: ({ unnamedArgs: [name, value] }) => {
|
||||
ctx.variables.local.add(name, value);
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
// {{incvar::name}} -> returns new value
|
||||
MacroRegistry.registerMacro('incvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the local variable to increment.',
|
||||
},
|
||||
],
|
||||
description: 'Increments a local variable by 1 and returns the new value. If the variable does not exist, it will be created.',
|
||||
returns: 'The new value of the local variable.',
|
||||
returnType: MacroValueType.NUMBER,
|
||||
exampleUsage: ['{{incvar::myintvar}}'],
|
||||
handler: ({ unnamedArgs: [name], normalize }) => {
|
||||
const result = ctx.variables.local.inc(name);
|
||||
return normalize(result);
|
||||
},
|
||||
});
|
||||
|
||||
// {{decvar::name}} -> returns new value
|
||||
MacroRegistry.registerMacro('decvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the local variable to decrement.',
|
||||
},
|
||||
],
|
||||
description: 'Decrements a local variable by 1 and returns the new value. If the variable does not exist, it will be created.',
|
||||
returns: 'The new value of the local variable.',
|
||||
returnType: MacroValueType.NUMBER,
|
||||
exampleUsage: ['{{decvar::myintvar}}'],
|
||||
handler: ({ unnamedArgs: [name], normalize }) => {
|
||||
const result = ctx.variables.local.dec(name);
|
||||
return normalize(result);
|
||||
},
|
||||
});
|
||||
|
||||
// {{getvar::name}} -> returns current value
|
||||
MacroRegistry.registerMacro('getvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the local variable to get.',
|
||||
},
|
||||
],
|
||||
description: 'Gets the value of a local variable.',
|
||||
returns: 'The value of the local variable.',
|
||||
returnType: [MacroValueType.STRING, MacroValueType.NUMBER],
|
||||
exampleUsage: ['{{getvar::myvar}}', '{{getvar::myintvar}}'],
|
||||
handler: ({ unnamedArgs: [name], normalize }) => {
|
||||
const result = ctx.variables.local.get(name);
|
||||
return normalize(result);
|
||||
},
|
||||
});
|
||||
|
||||
// {{setglobalvar::name::value}} -> ''
|
||||
MacroRegistry.registerMacro('setglobalvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the global variable to set.',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: [MacroValueType.STRING, MacroValueType.NUMBER],
|
||||
description: 'The value to set the global variable to.',
|
||||
},
|
||||
],
|
||||
description: 'Sets a global variable to the given value.',
|
||||
returns: '',
|
||||
exampleUsage: ['{{setglobalvar::myvar::foo}}', '{{setglobalvar::myintvar::3}}'],
|
||||
handler: ({ unnamedArgs: [name, value] }) => {
|
||||
ctx.variables.global.set(name, value);
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
// {{addglobalvar::name::value}} -> ''
|
||||
MacroRegistry.registerMacro('addglobalvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the global variable to add to.',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: [MacroValueType.STRING, MacroValueType.NUMBER],
|
||||
description: 'The value to add to the global variable.',
|
||||
},
|
||||
],
|
||||
description: 'Adds a value to an existing global variable (numeric or string append). If the variable does not exist, it will be created.',
|
||||
returns: '',
|
||||
exampleUsage: ['{{addglobalvar::mystrvar::foo}}', '{{addglobalvar::myintvar::3}}'],
|
||||
handler: ({ unnamedArgs: [name, value] }) => {
|
||||
ctx.variables.global.add(name, value);
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
// {{incglobalvar::name}} -> returns new value
|
||||
MacroRegistry.registerMacro('incglobalvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the global variable to increment.',
|
||||
},
|
||||
],
|
||||
description: 'Increments a global variable by 1 and returns the new value. If the variable does not exist, it will be created.',
|
||||
returns: 'The new value of the global variable.',
|
||||
returnType: MacroValueType.NUMBER,
|
||||
handler: ({ unnamedArgs: [name], normalize }) => {
|
||||
const result = ctx.variables.global.inc(name);
|
||||
return normalize(result);
|
||||
},
|
||||
});
|
||||
|
||||
// {{decglobalvar::name}} -> returns new value
|
||||
MacroRegistry.registerMacro('decglobalvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the global variable to decrement.',
|
||||
},
|
||||
],
|
||||
description: 'Decrements a global variable by 1 and returns the new value. If the variable does not exist, it will be created.',
|
||||
returns: 'The new value of the global variable.',
|
||||
returnType: MacroValueType.NUMBER,
|
||||
exampleUsage: ['{{decglobalvar::myintvar}}'],
|
||||
handler: ({ unnamedArgs: [name], normalize }) => {
|
||||
const result = ctx.variables.global.dec(name);
|
||||
return normalize(result);
|
||||
},
|
||||
});
|
||||
|
||||
// {{getglobalvar::name}} -> returns current value
|
||||
MacroRegistry.registerMacro('getglobalvar', {
|
||||
category: MacroCategory.VARIABLE,
|
||||
unnamedArgs: [
|
||||
{
|
||||
name: 'name',
|
||||
type: MacroValueType.STRING,
|
||||
description: 'The name of the global variable to get.',
|
||||
},
|
||||
],
|
||||
description: 'Gets the value of a global variable.',
|
||||
returns: 'The value of the global variable.',
|
||||
returnType: [MacroValueType.STRING, MacroValueType.NUMBER],
|
||||
exampleUsage: ['{{getglobalvar::myvar}}', '{{getglobalvar::myintvar}}'],
|
||||
handler: ({ unnamedArgs: [name], normalize }) => {
|
||||
const result = ctx.variables.global.get(name);
|
||||
return normalize(result);
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user