2023-07-20 19:32:15 +02:00
import {
2023-12-07 02:26:29 +01:00
Generate ,
activateSendButtons ,
2023-07-20 19:32:15 +02:00
addOneMessage ,
2023-12-07 02:26:29 +01:00
callPopup ,
2023-07-20 19:32:15 +02:00
characters ,
chat ,
chat _metadata ,
2023-12-07 02:26:29 +01:00
comment _avatar ,
deactivateSendButtons ,
2023-07-20 19:32:15 +02:00
default _avatar ,
eventSource ,
event _types ,
2023-12-07 02:26:29 +01:00
extension _prompt _types ,
2023-07-20 19:32:15 +02:00
extractMessageBias ,
2023-12-07 02:26:29 +01:00
generateQuietPrompt ,
generateRaw ,
2023-07-20 19:32:15 +02:00
getThumbnailUrl ,
2023-12-07 02:26:29 +01:00
is _send _press ,
main _api ,
name1 ,
reloadCurrentChat ,
2023-07-20 19:32:15 +02:00
replaceBiasMarkup ,
saveChatConditional ,
2023-12-07 02:26:29 +01:00
sendMessageAsUser ,
2023-07-20 19:32:15 +02:00
sendSystemMessage ,
2023-12-07 02:26:29 +01:00
setCharacterId ,
setCharacterName ,
setExtensionPrompt ,
2023-07-20 19:32:15 +02:00
setUserName ,
substituteParams ,
system _avatar ,
system _message _types ,
2023-10-16 08:12:12 +02:00
this _chid ,
2023-12-02 19:04:51 +01:00
} from '../script.js' ;
import { getMessageTimeStamp } from './RossAscends-mods.js' ;
2023-12-07 02:26:29 +01:00
import { hideChatMessage , unhideChatMessage } from './chats.js' ;
import { getContext , saveMetadataDebounced } from './extensions.js' ;
2023-12-02 19:04:51 +01:00
import { getRegexedString , regex _placement } from './extensions/regex/engine.js' ;
2023-12-07 02:26:29 +01:00
import { findGroupMemberId , groups , is _group _generating , resetSelectedGroup , saveGroupChat , selected _group } from './group-chats.js' ;
2023-12-02 19:04:51 +01:00
import { autoSelectPersona } from './personas.js' ;
2023-12-07 02:26:29 +01:00
import { addEphemeralStoppingString , chat _styles , flushEphemeralStoppingStrings , power _user } from './power-user.js' ;
import { decodeTextTokens , getFriendlyTokenizerName , getTextTokens , getTokenCount } from './tokenizers.js' ;
2023-12-02 19:43:41 +01:00
import { delay , isFalseBoolean , isTrueBoolean , stringToRange , trimToEndSentence , trimToStartSentence , waitUntilCondition } from './utils.js' ;
2023-12-02 19:04:51 +01:00
import { registerVariableCommands , resolveVariable } from './variables.js' ;
2023-07-20 19:32:15 +02:00
export {
2023-12-07 02:26:29 +01:00
executeSlashCommands , getSlashCommandsHelp , registerSlashCommand ,
2023-12-02 20:11:06 +01:00
} ;
2023-07-20 19:32:15 +02:00
class SlashCommandParser {
constructor ( ) {
this . commands = { } ;
2023-11-04 00:21:20 +01:00
this . helpStrings = { } ;
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
addCommand ( command , callback , aliases , helpString = '' , interruptsGeneration = false , purgeFromMessage = true ) {
2023-07-20 19:32:15 +02:00
const fnObj = { callback , helpString , interruptsGeneration , purgeFromMessage } ;
2023-12-07 12:35:48 +01:00
if ( [ command , ... aliases ] . some ( x => Object . hasOwn ( this . commands , x ) ) ) {
2023-07-20 19:32:15 +02:00
console . trace ( 'WARN: Duplicate slash command registered!' ) ;
}
2023-12-07 12:35:48 +01:00
this . commands [ command ] = fnObj ;
2023-07-20 19:32:15 +02:00
if ( Array . isArray ( aliases ) ) {
aliases . forEach ( ( alias ) => {
2023-12-07 12:35:48 +01:00
this . commands [ alias ] = fnObj ;
2023-07-20 19:32:15 +02:00
} ) ;
}
let stringBuilder = ` <span class="monospace">/ ${ command } </span> ${ helpString } ` ;
if ( Array . isArray ( aliases ) && aliases . length ) {
2023-10-07 18:25:36 +02:00
let aliasesString = ` (alias: ${ aliases . map ( x => ` <span class="monospace">/ ${ x } </span> ` ) . join ( ', ' ) } ) ` ;
2023-07-20 19:32:15 +02:00
stringBuilder += aliasesString ;
}
2023-12-07 12:35:48 +01:00
this . helpStrings [ command ] = stringBuilder ;
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
parse ( text ) {
const excludedFromRegex = [ 'sendas' ] ;
2023-07-20 19:32:15 +02:00
const firstSpace = text . indexOf ( ' ' ) ;
const command = firstSpace !== - 1 ? text . substring ( 1 , firstSpace ) : text . substring ( 1 ) ;
const args = firstSpace !== - 1 ? text . substring ( firstSpace + 1 ) : '' ;
const argObj = { } ;
let unnamedArg ;
if ( args . length > 0 ) {
2023-11-21 01:54:04 +01:00
// Match named arguments
const namedArgPattern = /(\w+)=("(?:\\.|[^"\\])*"|\S+)/g ;
let match ;
while ( ( match = namedArgPattern . exec ( args ) ) !== null ) {
2023-12-07 12:35:48 +01:00
const key = match [ 1 ] ;
const value = match [ 2 ] ;
2023-11-21 01:54:04 +01:00
// Remove the quotes around the value, if any
2023-12-07 12:35:48 +01:00
argObj [ key ] = value . replace ( /(^")|("$)/g , '' ) ;
2023-07-20 19:32:15 +02:00
}
2023-11-21 01:54:04 +01:00
// Match unnamed argument
const unnamedArgPattern = / ( ? : \ w + = ( ? : " ( ? : \ \ . | [ ^ " \ \ ] ) * " | \ S + ) \ s * ) * ( . * ) / s ;
match = unnamedArgPattern . exec ( args ) ;
if ( match !== null ) {
2023-12-07 12:35:48 +01:00
unnamedArg = match [ 1 ] . trim ( ) ;
2023-11-21 01:54:04 +01:00
}
2023-07-20 19:32:15 +02:00
// Excluded commands format in their own function
if ( ! excludedFromRegex . includes ( command ) ) {
unnamedArg = getRegexedString (
unnamedArg ,
2023-12-02 21:06:57 +01:00
regex _placement . SLASH _COMMAND ,
2023-07-20 19:32:15 +02:00
) ;
}
}
2023-12-07 12:35:48 +01:00
if ( this . commands [ command ] ) {
return { command : this . commands [ command ] , args : argObj , value : unnamedArg } ;
2023-07-20 19:32:15 +02:00
}
return false ;
}
2023-12-07 12:35:48 +01:00
getHelpString ( ) {
2023-11-04 00:21:20 +01:00
const listItems = Object
. entries ( this . helpStrings )
2023-12-07 12:35:48 +01:00
. sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) )
. map ( x => x [ 1 ] )
2023-11-04 00:21:20 +01:00
. map ( x => ` <li> ${ x } </li> ` )
. join ( '\n' ) ;
2023-10-09 01:22:00 +02:00
return ` <p>Slash commands:</p><ol> ${ listItems } </ol>
< small > Slash commands can be batched into a single input by adding a pipe character | at the end , and then writing a new slash command . < / s m a l l >
< ul > < li > < small > Example : < / s m a l l > < c o d e > / c u t 1 | / s y s H e l l o , | / c o n t i n u e < / c o d e > < / l i >
< li > This will remove the first message in chat , send a system message that starts with 'Hello,' , and then ask the AI to continue the message . < / l i > < / u l > ` ;
2023-07-20 19:32:15 +02:00
}
}
const parser = new SlashCommandParser ( ) ;
const registerSlashCommand = parser . addCommand . bind ( parser ) ;
const getSlashCommandsHelp = parser . getHelpString . bind ( parser ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( '?' , helpCommandCallback , [ 'help' ] , ' – get help on macros, chat formatting and commands' , true , true ) ;
parser . addCommand ( 'name' , setNameCallback , [ 'persona' ] , '<span class="monospace">(name)</span> – sets user name and persona avatar (if set)' , true , true ) ;
2023-07-20 19:32:15 +02:00
parser . addCommand ( 'sync' , syncCallback , [ ] , ' – syncs user name in user-attributed messages in the current chat' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'lock' , bindCallback , [ 'bind' ] , ' – locks/unlocks a persona (name and avatar) to the current chat' , true , true ) ;
parser . addCommand ( 'bg' , setBackgroundCallback , [ 'background' ] , '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed' , false , true ) ;
2023-12-02 19:04:51 +01:00
parser . addCommand ( 'sendas' , sendMessageAs , [ ] , ' – sends message as a specific character. Uses character avatar if it exists in the characters list. Example that will send "Hello, guys!" from "Chloe": <tt>/sendas name="Chloe" Hello, guys!</tt>' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'sys' , sendNarratorMessage , [ 'nar' ] , '<span class="monospace">(text)</span> – sends message as a system narrator' , false , true ) ;
2023-07-20 19:32:15 +02:00
parser . addCommand ( 'sysname' , setNarratorName , [ ] , '<span class="monospace">(name)</span> – sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.' , true , true ) ;
parser . addCommand ( 'comment' , sendCommentMessage , [ ] , '<span class="monospace">(text)</span> – adds a note/comment message not part of the chat' , false , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'single' , setStoryModeCallback , [ 'story' ] , ' – sets the message style to single document mode without names or avatars visible' , true , true ) ;
parser . addCommand ( 'bubble' , setBubbleModeCallback , [ 'bubbles' ] , ' – sets the message style to bubble chat mode' , true , true ) ;
parser . addCommand ( 'flat' , setFlatModeCallback , [ 'default' ] , ' – sets the message style to flat chat mode' , true , true ) ;
parser . addCommand ( 'continue' , continueChatCallback , [ 'cont' ] , ' – continues the last message in the chat' , true , true ) ;
parser . addCommand ( 'go' , goToCharacterCallback , [ 'char' ] , '<span class="monospace">(name)</span> – opens up a chat with the character by its name' , true , true ) ;
2023-07-20 19:32:15 +02:00
parser . addCommand ( 'sysgen' , generateSystemMessage , [ ] , '<span class="monospace">(prompt)</span> – generates a system message using a specified prompt' , true , true ) ;
2023-10-16 08:12:12 +02:00
parser . addCommand ( 'ask' , askCharacter , [ ] , '<span class="monospace">(prompt)</span> – asks a specified character card a prompt' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'delname' , deleteMessagesByNameCallback , [ 'cancel' ] , '<span class="monospace">(name)</span> – deletes all messages attributed to a specified name' , true , true ) ;
2023-11-28 00:44:13 +01:00
parser . addCommand ( 'send' , sendUserMessageCallback , [ ] , '<span class="monospace">(text)</span> – adds a user message to the chat log without triggering a generation' , true , true ) ;
2023-11-27 10:16:40 +01:00
parser . addCommand ( 'trigger' , triggerGenerationCallback , [ ] , ' – triggers a message generation. If in group, can trigger a message for the specified group member index or name.' , true , true ) ;
2023-11-14 20:37:37 +01:00
parser . addCommand ( 'hide' , hideMessageCallback , [ ] , '<span class="monospace">(message index or range)</span> – hides a chat message from the prompt' , true , true ) ;
parser . addCommand ( 'unhide' , unhideMessageCallback , [ ] , '<span class="monospace">(message index or range)</span> – unhides a message from the prompt' , true , true ) ;
2023-11-20 22:49:04 +01:00
parser . addCommand ( 'disable' , disableGroupMemberCallback , [ ] , '<span class="monospace">(member index or name)</span> – disables a group member from being drafted for replies' , true , true ) ;
parser . addCommand ( 'enable' , enableGroupMemberCallback , [ ] , '<span class="monospace">(member index or name)</span> – enables a group member to be drafted for replies' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'memberadd' , addGroupMemberCallback , [ 'addmember' ] , '<span class="monospace">(character name)</span> – adds a new group member to the group chat' , true , true ) ;
parser . addCommand ( 'memberremove' , removeGroupMemberCallback , [ 'removemember' ] , '<span class="monospace">(member index or name)</span> – removes a group member from the group chat' , true , true ) ;
parser . addCommand ( 'memberup' , moveGroupMemberUpCallback , [ 'upmember' ] , '<span class="monospace">(member index or name)</span> – moves a group member up in the group chat list' , true , true ) ;
parser . addCommand ( 'memberdown' , moveGroupMemberDownCallback , [ 'downmember' ] , '<span class="monospace">(member index or name)</span> – moves a group member down in the group chat list' , true , true ) ;
2023-11-21 21:35:59 +01:00
parser . addCommand ( 'peek' , peekCallback , [ ] , '<span class="monospace">(message index or range)</span> – shows a group member character card without switching chats' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'delswipe' , deleteSwipeCallback , [ 'swipedel' ] , '<span class="monospace">(optional 1-based id)</span> – deletes a swipe from the last chat message. If swipe id not provided - deletes the current swipe.' , true , true ) ;
2023-12-07 02:26:29 +01:00
parser . addCommand ( 'echo' , echoCallback , [ ] , '<span class="monospace">(title=string severity=info/warning/error/success [text])</span> – echoes the text to toast message. Useful for pipes debugging.' , true , true ) ;
2023-12-07 23:38:13 +01:00
//parser.addCommand('#', (_, value) => '', [], ' – a comment, does nothing, e.g. <tt>/# the next three commands switch variables a and b</tt>', true, true);
2023-11-24 14:18:49 +01:00
parser . addCommand ( 'gen' , generateCallback , [ ] , '<span class="monospace">(lock=on/off [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating.' , true , true ) ;
parser . addCommand ( 'genraw' , generateRawCallback , [ ] , '<span class="monospace">(lock=on/off [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. <tt>/genraw instruct=off Why is the sky blue?</tt>. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. <tt>/genraw stop=["\\n"] Say hi</tt>' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'addswipe' , addSwipeCallback , [ 'swipeadd' ] , '<span class="monospace">(text)</span> – adds a swipe to the last chat message.' , true , true ) ;
2023-11-23 23:18:07 +01:00
parser . addCommand ( 'abort' , abortCallback , [ ] , ' – aborts the slash command batch execution' , true , true ) ;
2023-11-24 01:21:50 +01:00
parser . addCommand ( 'fuzzy' , fuzzyCallback , [ ] , 'list=["a","b","c"] (search value) – performs a fuzzy match of the provided search using the provided list of value and passes the closest match to the next command through the pipe.' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'pass' , ( _ , arg ) => arg , [ 'return' ] , '<span class="monospace">(text)</span> – passes the text to the next command through the pipe.' , true , true ) ;
parser . addCommand ( 'delay' , delayCallback , [ 'wait' , 'sleep' ] , '<span class="monospace">(milliseconds)</span> – delays the next command in the pipe by the specified number of milliseconds.' , true , true ) ;
2023-12-07 12:44:49 +01:00
parser . addCommand ( 'input' , inputCallback , [ 'prompt' ] , '<span class="monospace">(default="string" large=on/off wide=on/off okButton="string" rows=number [text])</span> – Shows a popup with the provided text and an input field. The default argument is the default value of the input field, and the text argument is the text to display.' , true , true ) ;
2023-12-07 12:35:48 +01:00
parser . addCommand ( 'run' , runCallback , [ 'call' , 'exec' ] , '<span class="monospace">(QR label)</span> – runs a Quick Reply with the specified name from the current preset.' , true , true ) ;
parser . addCommand ( 'messages' , getMessagesCallback , [ 'message' ] , '<span class="monospace">(names=off/on [message index or range])</span> – returns the specified message or range of messages as a string.' , true , true ) ;
2023-11-24 16:12:59 +01:00
parser . addCommand ( 'setinput' , setInputCallback , [ ] , '<span class="monospace">(text)</span> – sets the user input to the specified text and passes it to the next command through the pipe.' , true , true ) ;
2023-12-07 02:26:29 +01:00
parser . addCommand ( 'popup' , popupCallback , [ ] , '<span class="monospace">(large=on/off wide=on/off okButton="string" text)</span> – shows a blocking popup with the specified text and buttons. Returns the input value into the pipe or empty string if canceled.' , true , true ) ;
2023-11-26 16:05:55 +01:00
parser . addCommand ( 'buttons' , buttonsCallback , [ ] , '<span class="monospace">labels=["a","b"] (text)</span> – shows a blocking popup with the specified text and buttons. Returns the clicked button label into the pipe or empty string if canceled.' , true , true ) ;
2023-11-26 14:47:11 +01:00
parser . addCommand ( 'trimtokens' , trimTokensCallback , [ ] , '<span class="monospace">limit=number (direction=start/end [text])</span> – trims the start or end of text to the specified number of tokens.' , true , true ) ;
2023-11-26 12:55:22 +01:00
parser . addCommand ( 'trimstart' , trimStartCallback , [ ] , '<span class="monospace">(text)</span> – trims the text to the start of the first full sentence.' , true , true ) ;
parser . addCommand ( 'trimend' , trimEndCallback , [ ] , '<span class="monospace">(text)</span> – trims the text to the end of the last full sentence.' , true , true ) ;
2023-11-28 00:44:13 +01:00
parser . addCommand ( 'inject' , injectCallback , [ ] , '<span class="monospace">id=injectId (position=before/after/chat depth=number [text])</span> – injects a text into the LLM prompt for the current chat. Requires a unique injection ID. Positions: "before" main prompt, "after" main prompt, in-"chat" (default: after). Depth: injection depth for the prompt (default: 4).' , true , true ) ;
parser . addCommand ( 'listinjects' , listInjectsCallback , [ ] , ' – lists all script injections for the current chat.' , true , true ) ;
parser . addCommand ( 'flushinjects' , flushInjectsCallback , [ ] , ' – removes all script injections for the current chat.' , true , true ) ;
2023-11-23 21:36:48 +01:00
registerVariableCommands ( ) ;
2023-07-20 19:32:15 +02:00
const NARRATOR _NAME _KEY = 'narrator_name' ;
const NARRATOR _NAME _DEFAULT = 'System' ;
2023-08-26 19:26:23 +02:00
export const COMMENT _NAME _DEFAULT = 'Note' ;
2023-11-28 00:44:13 +01:00
const SCRIPT _PROMPT _KEY = 'script_inject_' ;
2023-12-07 12:35:48 +01:00
function injectCallback ( args , value ) {
2023-11-28 00:44:13 +01:00
const positions = {
'before' : extension _prompt _types . BEFORE _PROMPT ,
'after' : extension _prompt _types . IN _PROMPT ,
'chat' : extension _prompt _types . IN _CHAT ,
} ;
const id = resolveVariable ( args ? . id ) ;
if ( ! id ) {
console . warn ( 'WARN: No ID provided for /inject command' ) ;
toastr . warning ( 'No ID provided for /inject command' ) ;
return '' ;
}
const defaultPosition = 'after' ;
const defaultDepth = 4 ;
const positionValue = args ? . position ? ? defaultPosition ;
2023-12-07 12:35:48 +01:00
const position = positions [ positionValue ] ? ? positions [ defaultPosition ] ;
2023-11-28 00:44:13 +01:00
const depthValue = Number ( args ? . depth ) ? ? defaultDepth ;
const depth = isNaN ( depthValue ) ? defaultDepth : depthValue ;
value = value || '' ;
const prefixedId = ` ${ SCRIPT _PROMPT _KEY } ${ id } ` ;
if ( ! chat _metadata . script _injects ) {
chat _metadata . script _injects = { } ;
}
2023-12-07 12:35:48 +01:00
chat _metadata . script _injects [ id ] = {
2023-11-28 00:44:13 +01:00
value ,
position ,
depth ,
} ;
setExtensionPrompt ( prefixedId , value , position , depth ) ;
saveMetadataDebounced ( ) ;
return '' ;
}
2023-12-07 12:35:48 +01:00
function listInjectsCallback ( ) {
2023-11-28 00:44:13 +01:00
if ( ! chat _metadata . script _injects ) {
toastr . info ( 'No script injections for the current chat' ) ;
return '' ;
}
const injects = Object . entries ( chat _metadata . script _injects )
2023-12-07 12:35:48 +01:00
. map ( ( [ id , inject ] ) => {
2023-11-28 00:44:13 +01:00
const position = Object . entries ( extension _prompt _types ) ;
2023-12-07 12:35:48 +01:00
const positionName = position . find ( ( [ _ , value ] ) => value === inject . position ) ? . [ 0 ] ? ? 'unknown' ;
2023-11-28 00:44:13 +01:00
return ` * ** ${ id } **: <code> ${ inject . value } </code> ( ${ positionName } , depth: ${ inject . depth } ) ` ;
} )
. join ( '\n' ) ;
const converter = new showdown . Converter ( ) ;
const messageText = ` ### Script injections: \n ${ injects } ` ;
const htmlMessage = DOMPurify . sanitize ( converter . makeHtml ( messageText ) ) ;
sendSystemMessage ( system _message _types . GENERIC , htmlMessage ) ;
}
2023-12-07 12:35:48 +01:00
function flushInjectsCallback ( ) {
2023-11-28 00:44:13 +01:00
if ( ! chat _metadata . script _injects ) {
return '' ;
}
2023-12-07 12:35:48 +01:00
for ( const [ id , inject ] of Object . entries ( chat _metadata . script _injects ) ) {
2023-11-28 00:44:13 +01:00
const prefixedId = ` ${ SCRIPT _PROMPT _KEY } ${ id } ` ;
setExtensionPrompt ( prefixedId , '' , inject . position , inject . depth ) ;
}
chat _metadata . script _injects = { } ;
saveMetadataDebounced ( ) ;
return '' ;
}
2023-12-07 12:35:48 +01:00
export function processChatSlashCommands ( ) {
2023-11-28 00:44:13 +01:00
const context = getContext ( ) ;
if ( ! ( context . chatMetadata . script _injects ) ) {
return ;
}
for ( const id of Object . keys ( context . extensionPrompts ) ) {
if ( ! id . startsWith ( SCRIPT _PROMPT _KEY ) ) {
continue ;
}
console . log ( 'Removing script injection' , id ) ;
2023-12-07 12:35:48 +01:00
delete context . extensionPrompts [ id ] ;
2023-11-28 00:44:13 +01:00
}
2023-12-07 12:35:48 +01:00
for ( const [ id , inject ] of Object . entries ( context . chatMetadata . script _injects ) ) {
2023-11-28 00:44:13 +01:00
const prefixedId = ` ${ SCRIPT _PROMPT _KEY } ${ id } ` ;
console . log ( 'Adding script injection' , id ) ;
setExtensionPrompt ( prefixedId , inject . value , inject . position , inject . depth ) ;
}
}
2023-07-20 19:32:15 +02:00
2023-12-07 12:35:48 +01:00
function setInputCallback ( _ , value ) {
2023-11-24 16:12:59 +01:00
$ ( '#send_textarea' ) . val ( value || '' ) . trigger ( 'input' ) ;
return value ;
}
2023-12-07 12:35:48 +01:00
function trimStartCallback ( _ , value ) {
2023-11-26 12:55:22 +01:00
if ( ! value ) {
return '' ;
}
return trimToStartSentence ( value ) ;
}
2023-12-07 12:35:48 +01:00
function trimEndCallback ( _ , value ) {
2023-11-26 12:55:22 +01:00
if ( ! value ) {
return '' ;
}
return trimToEndSentence ( value ) ;
}
2023-12-07 12:35:48 +01:00
function trimTokensCallback ( arg , value ) {
2023-11-26 12:55:22 +01:00
if ( ! value ) {
console . warn ( 'WARN: No argument provided for /trimtokens command' ) ;
return '' ;
}
const limit = Number ( resolveVariable ( arg . limit ) ) ;
if ( isNaN ( limit ) ) {
console . warn ( ` WARN: Invalid limit provided for /trimtokens command: ${ limit } ` ) ;
return value ;
}
if ( limit <= 0 ) {
return '' ;
}
const direction = arg . direction || 'end' ;
2023-12-02 20:11:06 +01:00
const tokenCount = getTokenCount ( value ) ;
2023-11-26 12:55:22 +01:00
// Token count is less than the limit, do nothing
if ( tokenCount <= limit ) {
return value ;
}
const { tokenizerName , tokenizerId } = getFriendlyTokenizerName ( main _api ) ;
console . debug ( 'Requesting tokenization for /trimtokens command' , tokenizerName ) ;
try {
const textTokens = getTextTokens ( tokenizerId , value ) ;
if ( ! Array . isArray ( textTokens ) || ! textTokens . length ) {
console . warn ( 'WARN: No tokens returned for /trimtokens command, falling back to estimation' ) ;
const percentage = limit / tokenCount ;
const trimIndex = Math . floor ( value . length * percentage ) ;
const trimmedText = direction === 'start' ? value . substring ( trimIndex ) : value . substring ( 0 , value . length - trimIndex ) ;
return trimmedText ;
}
const sliceTokens = direction === 'start' ? textTokens . slice ( 0 , limit ) : textTokens . slice ( - limit ) ;
const decodedText = decodeTextTokens ( tokenizerId , sliceTokens ) ;
return decodedText ;
} catch ( error ) {
console . warn ( 'WARN: Tokenization failed for /trimtokens command, returning original' , error ) ;
return value ;
}
}
2023-12-07 12:35:48 +01:00
async function buttonsCallback ( args , text ) {
2023-11-26 16:05:55 +01:00
try {
const buttons = JSON . parse ( resolveVariable ( args ? . labels ) ) ;
if ( ! Array . isArray ( buttons ) || ! buttons . length ) {
console . warn ( 'WARN: Invalid labels provided for /buttons command' ) ;
return '' ;
}
return new Promise ( async ( resolve ) => {
const safeValue = DOMPurify . sanitize ( text || '' ) ;
const buttonContainer = document . createElement ( 'div' ) ;
buttonContainer . classList . add ( 'flex-container' , 'flexFlowColumn' , 'wide100p' , 'm-t-1' ) ;
for ( const button of buttons ) {
const buttonElement = document . createElement ( 'div' ) ;
buttonElement . classList . add ( 'menu_button' , 'wide100p' ) ;
buttonElement . addEventListener ( 'click' , ( ) => {
resolve ( button ) ;
$ ( '#dialogue_popup_ok' ) . trigger ( 'click' ) ;
} ) ;
buttonElement . innerText = button ;
buttonContainer . appendChild ( buttonElement ) ;
}
const popupContainer = document . createElement ( 'div' ) ;
popupContainer . innerHTML = safeValue ;
popupContainer . appendChild ( buttonContainer ) ;
callPopup ( popupContainer , 'text' , '' , { okButton : 'Cancel' } )
. then ( ( ) => resolve ( '' ) )
. catch ( ( ) => resolve ( '' ) ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-11-26 16:05:55 +01:00
} catch {
return '' ;
}
}
2023-12-07 12:35:48 +01:00
async function popupCallback ( args , value ) {
2023-11-24 18:50:49 +01:00
const safeValue = DOMPurify . sanitize ( value || '' ) ;
2023-12-07 12:35:48 +01:00
const popupOptions = {
2023-12-07 02:26:29 +01:00
large : isTrueBoolean ( args ? . large ) ,
wide : isTrueBoolean ( args ? . wide ) ,
2023-12-07 12:35:48 +01:00
okButton : args ? . okButton !== undefined && typeof args ? . okButton === 'string' ? args . okButton : 'Ok' ,
2023-12-07 02:26:29 +01:00
} ;
2023-11-24 18:50:49 +01:00
await delay ( 1 ) ;
2023-12-07 02:26:29 +01:00
await callPopup ( safeValue , 'text' , '' , popupOptions ) ;
2023-11-24 18:50:49 +01:00
await delay ( 1 ) ;
return value ;
}
2023-12-07 12:35:48 +01:00
function getMessagesCallback ( args , value ) {
2023-11-24 16:12:59 +01:00
const includeNames = ! isFalseBoolean ( args ? . names ) ;
const range = stringToRange ( value , 0 , chat . length - 1 ) ;
if ( ! range ) {
console . warn ( ` WARN: Invalid range provided for /getmessages command: ${ value } ` ) ;
return '' ;
}
const messages = [ ] ;
for ( let messageId = range . start ; messageId <= range . end ; messageId ++ ) {
2023-12-07 12:35:48 +01:00
const message = chat [ messageId ] ;
2023-11-24 16:12:59 +01:00
if ( ! message ) {
console . warn ( ` WARN: No message found with ID ${ messageId } ` ) ;
continue ;
}
2023-11-26 00:15:19 +01:00
if ( message . is _system ) {
continue ;
}
2023-11-24 16:12:59 +01:00
if ( includeNames ) {
messages . push ( ` ${ message . name } : ${ message . mes } ` ) ;
} else {
messages . push ( message . mes ) ;
}
}
return messages . join ( '\n\n' ) ;
}
2023-12-07 12:35:48 +01:00
async function runCallback ( _ , name ) {
2023-11-24 14:58:00 +01:00
if ( ! name ) {
toastr . warning ( 'No name provided for /run command' ) ;
return '' ;
}
2023-12-07 12:35:48 +01:00
if ( typeof window [ 'executeQuickReplyByName' ] !== 'function' ) {
2023-11-24 14:58:00 +01:00
toastr . warning ( 'Quick Reply extension is not loaded' ) ;
return '' ;
}
try {
name = name . trim ( ) ;
2023-12-07 12:35:48 +01:00
return await window [ 'executeQuickReplyByName' ] ( name ) ;
2023-11-24 14:58:00 +01:00
} catch ( error ) {
toastr . error ( ` Error running Quick Reply " ${ name } ": ${ error . message } ` , 'Error' ) ;
return '' ;
}
}
2023-12-07 12:35:48 +01:00
function abortCallback ( ) {
2023-11-24 16:12:59 +01:00
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
2023-11-23 23:18:07 +01:00
throw new Error ( '/abort command executed' ) ;
}
2023-12-07 12:35:48 +01:00
async function delayCallback ( _ , amount ) {
2023-11-24 11:49:14 +01:00
if ( ! amount ) {
console . warn ( 'WARN: No amount provided for /delay command' ) ;
return ;
}
amount = Number ( amount ) ;
if ( isNaN ( amount ) ) {
amount = 0 ;
}
await delay ( amount ) ;
}
2023-12-07 12:35:48 +01:00
async function inputCallback ( args , prompt ) {
2023-12-07 02:26:29 +01:00
const safeValue = DOMPurify . sanitize ( prompt || '' ) ;
2023-12-07 12:44:49 +01:00
const defaultInput = args ? . default !== undefined && typeof args ? . default === 'string' ? args . default : '' ;
2023-12-07 02:26:29 +01:00
const popupOptions = {
large : isTrueBoolean ( args ? . large ) ,
wide : isTrueBoolean ( args ? . wide ) ,
2023-12-07 12:35:48 +01:00
okButton : args ? . okButton !== undefined && typeof args ? . okButton === 'string' ? args . okButton : 'Ok' ,
2023-12-07 02:26:29 +01:00
rows : args ? . rows !== undefined && typeof args ? . rows === 'string' ? isNaN ( Number ( args . rows ) ) ? 4 : Number ( args . rows ) : 4 ,
} ;
2023-11-24 11:49:14 +01:00
// Do not remove this delay, otherwise the prompt will not show up
await delay ( 1 ) ;
2023-12-07 12:44:49 +01:00
const result = await callPopup ( safeValue , 'input' , defaultInput , popupOptions ) ;
2023-11-24 11:49:14 +01:00
await delay ( 1 ) ;
return result || '' ;
}
2023-12-07 12:35:48 +01:00
function fuzzyCallback ( args , value ) {
2023-11-24 01:21:50 +01:00
if ( ! value ) {
console . warn ( 'WARN: No argument provided for /fuzzy command' ) ;
return '' ;
}
if ( ! args . list ) {
console . warn ( 'WARN: No list argument provided for /fuzzy command' ) ;
return '' ;
}
try {
2023-11-24 16:41:49 +01:00
const list = JSON . parse ( resolveVariable ( args . list ) ) ;
2023-11-24 01:21:50 +01:00
if ( ! Array . isArray ( list ) ) {
console . warn ( 'WARN: Invalid list argument provided for /fuzzy command' ) ;
return '' ;
}
const fuse = new Fuse ( list , {
includeScore : true ,
findAllMatches : true ,
ignoreLocation : true ,
threshold : 0.7 ,
} ) ;
const result = fuse . search ( value ) ;
2023-12-07 12:35:48 +01:00
return result [ 0 ] ? . item ;
2023-11-24 01:21:50 +01:00
} catch {
console . warn ( 'WARN: Invalid list argument provided for /fuzzy command' ) ;
return '' ;
}
}
2023-11-24 18:06:31 +01:00
function setEphemeralStopStrings ( value ) {
if ( typeof value === 'string' && value . length ) {
2023-11-23 23:51:27 +01:00
try {
2023-11-24 18:06:31 +01:00
const stopStrings = JSON . parse ( value ) ;
2023-11-23 23:51:27 +01:00
if ( Array . isArray ( stopStrings ) ) {
2023-12-07 02:26:29 +01:00
stopStrings . forEach ( stopString => addEphemeralStoppingString ( stopString ) ) ;
2023-11-23 23:51:27 +01:00
}
} catch {
// Do nothing
}
}
2023-11-24 18:06:31 +01:00
}
2023-12-07 12:35:48 +01:00
async function generateRawCallback ( args , value ) {
2023-11-24 18:06:31 +01:00
if ( ! value ) {
console . warn ( 'WARN: No argument provided for /genraw command' ) ;
return ;
}
// Prevent generate recursion
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
const lock = isTrueBoolean ( args ? . lock ) ;
2023-11-23 23:51:27 +01:00
2023-11-24 14:18:49 +01:00
try {
if ( lock ) {
deactivateSendButtons ( ) ;
}
2023-11-25 23:56:55 +01:00
setEphemeralStopStrings ( resolveVariable ( args ? . stop ) ) ;
2023-11-24 14:18:49 +01:00
const result = await generateRaw ( value , '' , isFalseBoolean ( args ? . instruct ) ) ;
return result ;
} finally {
if ( lock ) {
activateSendButtons ( ) ;
}
2023-11-24 18:06:31 +01:00
flushEphemeralStoppingStrings ( ) ;
2023-11-24 14:18:49 +01:00
}
2023-11-23 21:50:13 +01:00
}
2023-12-07 12:35:48 +01:00
async function generateCallback ( args , value ) {
2023-11-24 14:18:49 +01:00
if ( ! value ) {
2023-11-21 23:39:17 +01:00
console . warn ( 'WARN: No argument provided for /gen command' ) ;
return ;
}
// Prevent generate recursion
2023-11-24 16:12:59 +01:00
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
2023-11-24 14:18:49 +01:00
const lock = isTrueBoolean ( args ? . lock ) ;
2023-11-21 23:39:17 +01:00
2023-11-24 14:18:49 +01:00
try {
if ( lock ) {
deactivateSendButtons ( ) ;
}
2023-11-25 23:56:55 +01:00
setEphemeralStopStrings ( resolveVariable ( args ? . stop ) ) ;
2023-11-24 14:18:49 +01:00
const result = await generateQuietPrompt ( value , false , false , '' ) ;
return result ;
} finally {
if ( lock ) {
activateSendButtons ( ) ;
}
2023-11-24 18:06:31 +01:00
flushEphemeralStoppingStrings ( ) ;
2023-11-24 14:18:49 +01:00
}
2023-11-21 23:39:17 +01:00
}
2023-12-07 12:35:48 +01:00
async function echoCallback ( args , value ) {
2023-12-07 18:37:55 +01:00
const safeValue = DOMPurify . sanitize ( String ( value ) || '' ) ;
2023-12-07 12:35:48 +01:00
if ( safeValue === '' ) {
2023-11-21 23:39:17 +01:00
console . warn ( 'WARN: No argument provided for /echo command' ) ;
return ;
}
2023-12-07 02:26:29 +01:00
const title = args ? . title !== undefined && typeof args ? . title === 'string' ? args . title : undefined ;
2023-12-07 12:35:48 +01:00
const severity = args ? . severity !== undefined && typeof args ? . severity === 'string' ? args . severity : 'info' ;
2023-12-07 02:26:29 +01:00
switch ( severity ) {
case 'error' :
toastr . error ( safeValue , title ) ;
break ;
case 'warning' :
toastr . warning ( safeValue , title ) ;
break ;
case 'success' :
toastr . success ( safeValue , title ) ;
break ;
case 'info' :
default :
toastr . info ( safeValue , title ) ;
break ;
}
2023-12-07 18:37:55 +01:00
return value ;
2023-11-21 23:39:17 +01:00
}
2023-12-07 12:35:48 +01:00
async function addSwipeCallback ( _ , arg ) {
const lastMessage = chat [ chat . length - 1 ] ;
2023-11-22 00:26:17 +01:00
if ( ! lastMessage ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'No messages to add swipes to.' ) ;
2023-11-22 00:26:17 +01:00
return ;
}
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for /addswipe command' ) ;
return ;
}
if ( lastMessage . is _user ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Can\'t add swipes to user messages.' ) ;
2023-11-22 00:26:17 +01:00
return ;
}
if ( lastMessage . is _system ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Can\'t add swipes to system messages.' ) ;
2023-11-22 00:26:17 +01:00
return ;
}
if ( lastMessage . extra ? . image ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Can\'t add swipes to message containing an image.' ) ;
2023-11-22 00:26:17 +01:00
return ;
}
if ( ! Array . isArray ( lastMessage . swipes ) ) {
2023-12-07 12:35:48 +01:00
lastMessage . swipes = [ lastMessage . mes ] ;
lastMessage . swipe _info = [ { } ] ;
2023-11-22 00:26:17 +01:00
lastMessage . swipe _id = 0 ;
}
lastMessage . swipes . push ( arg ) ;
lastMessage . swipe _info . push ( {
send _date : getMessageTimeStamp ( ) ,
gen _started : null ,
gen _finished : null ,
extra : {
bias : extractMessageBias ( arg ) ,
gen _id : Date . now ( ) ,
api : 'manual' ,
model : 'slash command' ,
2023-12-02 21:06:57 +01:00
} ,
2023-11-23 23:18:07 +01:00
} ) ;
2023-11-22 00:26:17 +01:00
await saveChatConditional ( ) ;
await reloadCurrentChat ( ) ;
}
2023-12-07 12:35:48 +01:00
async function deleteSwipeCallback ( _ , arg ) {
const lastMessage = chat [ chat . length - 1 ] ;
2023-11-21 22:28:11 +01:00
if ( ! lastMessage || ! Array . isArray ( lastMessage . swipes ) || ! lastMessage . swipes . length ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'No messages to delete swipes from.' ) ;
2023-11-21 22:28:11 +01:00
return ;
}
if ( lastMessage . swipes . length <= 1 ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Can\'t delete the last swipe.' ) ;
2023-11-21 22:28:11 +01:00
return ;
}
const swipeId = arg && ! isNaN ( Number ( arg ) ) ? ( Number ( arg ) - 1 ) : lastMessage . swipe _id ;
2023-11-21 22:33:20 +01:00
if ( swipeId < 0 || swipeId >= lastMessage . swipes . length ) {
toastr . warning ( ` Invalid swipe ID: ${ swipeId + 1 } ` ) ;
return ;
}
2023-11-21 22:28:11 +01:00
lastMessage . swipes . splice ( swipeId , 1 ) ;
if ( Array . isArray ( lastMessage . swipe _info ) && lastMessage . swipe _info . length ) {
lastMessage . swipe _info . splice ( swipeId , 1 ) ;
}
const newSwipeId = Math . min ( swipeId , lastMessage . swipes . length - 1 ) ;
lastMessage . swipe _id = newSwipeId ;
2023-12-07 12:35:48 +01:00
lastMessage . mes = lastMessage . swipes [ newSwipeId ] ;
2023-11-21 22:28:11 +01:00
await saveChatConditional ( ) ;
await reloadCurrentChat ( ) ;
}
2023-12-07 12:35:48 +01:00
async function askCharacter ( _ , text ) {
2023-10-16 08:12:12 +02:00
// Prevent generate recursion
2023-11-24 16:12:59 +01:00
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
2023-10-16 08:12:12 +02:00
// Not supported in group chats
// TODO: Maybe support group chats?
if ( selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . error ( 'Cannot run this command in a group chat!' ) ;
2023-10-16 08:12:12 +02:00
return ;
}
if ( ! text ) {
2023-12-02 20:11:06 +01:00
console . warn ( 'WARN: No text provided for /ask command' ) ;
2023-10-16 08:12:12 +02:00
}
const parts = text . split ( '\n' ) ;
if ( parts . length <= 1 ) {
toastr . warning ( 'Both character name and message are required. Separate them with a new line.' ) ;
return ;
}
// Grabbing the message
const name = parts . shift ( ) . trim ( ) ;
let mesText = parts . join ( '\n' ) . trim ( ) ;
const prevChId = this _chid ;
// Find the character
const chId = characters . findIndex ( ( e ) => e . name === name ) ;
2023-12-07 12:35:48 +01:00
if ( ! characters [ chId ] || chId === - 1 ) {
2023-12-02 19:04:51 +01:00
toastr . error ( 'Character not found.' ) ;
2023-10-16 08:12:12 +02:00
return ;
}
// Override character and send a user message
setCharacterId ( chId ) ;
// TODO: Maybe look up by filename instead of name
2023-12-07 12:35:48 +01:00
const character = characters [ chId ] ;
2023-10-16 08:12:12 +02:00
let force _avatar , original _avatar ;
if ( character && character . avatar !== 'none' ) {
force _avatar = getThumbnailUrl ( 'avatar' , character . avatar ) ;
original _avatar = character . avatar ;
}
else {
force _avatar = default _avatar ;
original _avatar = default _avatar ;
}
setCharacterName ( character . name ) ;
2023-11-27 18:48:49 +01:00
await sendMessageAsUser ( mesText , '' ) ;
2023-10-16 08:12:12 +02:00
const restoreCharacter = ( ) => {
setCharacterId ( prevChId ) ;
2023-12-07 12:35:48 +01:00
setCharacterName ( characters [ prevChId ] . name ) ;
2023-10-16 08:12:12 +02:00
// Only force the new avatar if the character name is the same
// This skips if an error was fired
2023-12-07 12:35:48 +01:00
const lastMessage = chat [ chat . length - 1 ] ;
2023-10-16 08:12:12 +02:00
if ( lastMessage && lastMessage ? . name === character . name ) {
lastMessage . force _avatar = force _avatar ;
lastMessage . original _avatar = original _avatar ;
}
// Kill this callback once the event fires
2023-12-02 20:11:06 +01:00
eventSource . removeListener ( event _types . CHARACTER _MESSAGE _RENDERED , restoreCharacter ) ;
} ;
2023-10-16 08:12:12 +02:00
// Run generate and restore previous character on error
try {
toastr . info ( ` Asking ${ character . name } something... ` ) ;
2023-12-02 20:11:06 +01:00
await Generate ( 'ask_command' ) ;
2023-10-16 08:12:12 +02:00
} catch {
2023-12-02 20:11:06 +01:00
restoreCharacter ( ) ;
2023-10-16 08:12:12 +02:00
}
// Restore previous character once message renders
// Hack for generate
eventSource . on ( event _types . CHARACTER _MESSAGE _RENDERED , restoreCharacter ) ;
}
2023-12-07 12:35:48 +01:00
async function hideMessageCallback ( _ , arg ) {
2023-11-03 22:45:56 +01:00
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for /hide command' ) ;
return ;
}
2023-11-14 20:37:37 +01:00
const range = stringToRange ( arg , 0 , chat . length - 1 ) ;
2023-11-03 22:45:56 +01:00
2023-11-14 20:37:37 +01:00
if ( ! range ) {
console . warn ( ` WARN: Invalid range provided for /hide command: ${ arg } ` ) ;
2023-11-03 22:45:56 +01:00
return ;
}
2023-11-14 20:37:37 +01:00
for ( let messageId = range . start ; messageId <= range . end ; messageId ++ ) {
const messageBlock = $ ( ` .mes[mesid=" ${ messageId } "] ` ) ;
if ( ! messageBlock . length ) {
console . warn ( ` WARN: No message found with ID ${ messageId } ` ) ;
return ;
}
await hideChatMessage ( messageId , messageBlock ) ;
}
2023-11-03 22:45:56 +01:00
}
2023-12-07 12:35:48 +01:00
async function unhideMessageCallback ( _ , arg ) {
2023-11-03 22:45:56 +01:00
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for /unhide command' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-03 22:45:56 +01:00
}
2023-11-14 20:37:37 +01:00
const range = stringToRange ( arg , 0 , chat . length - 1 ) ;
2023-11-03 22:45:56 +01:00
2023-11-14 20:37:37 +01:00
if ( ! range ) {
console . warn ( ` WARN: Invalid range provided for /unhide command: ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-03 22:45:56 +01:00
}
2023-11-14 20:37:37 +01:00
for ( let messageId = range . start ; messageId <= range . end ; messageId ++ ) {
const messageBlock = $ ( ` .mes[mesid=" ${ messageId } "] ` ) ;
if ( ! messageBlock . length ) {
console . warn ( ` WARN: No message found with ID ${ messageId } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-14 20:37:37 +01:00
}
await unhideChatMessage ( messageId , messageBlock ) ;
}
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-03 22:45:56 +01:00
}
2023-12-07 12:35:48 +01:00
async function disableGroupMemberCallback ( _ , arg ) {
2023-10-29 18:35:26 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /disable command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-11 15:12:02 +01:00
}
2023-11-20 22:49:04 +01:00
const chid = findGroupMemberId ( arg ) ;
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-10-29 18:35:26 +01:00
}
2023-11-20 22:49:04 +01:00
$ ( ` .group_member[chid=" ${ chid } "] [data-action="disable"] ` ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-20 22:49:04 +01:00
}
2023-10-29 18:35:26 +01:00
2023-12-07 12:35:48 +01:00
async function enableGroupMemberCallback ( _ , arg ) {
2023-11-20 22:49:04 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /enable command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-10-29 18:35:26 +01:00
}
2023-11-20 22:49:04 +01:00
const chid = findGroupMemberId ( arg ) ;
2023-10-29 18:35:26 +01:00
2023-11-20 22:49:04 +01:00
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-10-29 18:35:26 +01:00
}
2023-11-20 22:49:04 +01:00
$ ( ` .group_member[chid=" ${ chid } "] [data-action="enable"] ` ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-20 22:49:04 +01:00
}
2023-10-29 18:35:26 +01:00
2023-12-07 12:35:48 +01:00
async function moveGroupMemberUpCallback ( _ , arg ) {
2023-11-21 21:35:59 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /memberup command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
const chid = findGroupMemberId ( arg ) ;
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
$ ( ` .group_member[chid=" ${ chid } "] [data-action="up"] ` ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
2023-12-07 12:35:48 +01:00
async function moveGroupMemberDownCallback ( _ , arg ) {
2023-11-21 21:35:59 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /memberdown command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
const chid = findGroupMemberId ( arg ) ;
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
$ ( ` .group_member[chid=" ${ chid } "] [data-action="down"] ` ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
2023-12-07 12:35:48 +01:00
async function peekCallback ( _ , arg ) {
2023-11-21 21:35:59 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /peek command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
if ( is _group _generating ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /peek command while the group reply is generating.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
const chid = findGroupMemberId ( arg ) ;
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
$ ( ` .group_member[chid=" ${ chid } "] [data-action="view"] ` ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
2023-12-07 12:35:48 +01:00
async function removeGroupMemberCallback ( _ , arg ) {
2023-11-21 21:35:59 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /memberremove command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
if ( is _group _generating ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /memberremove command while the group reply is generating.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
const chid = findGroupMemberId ( arg ) ;
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
$ ( ` .group_member[chid=" ${ chid } "] [data-action="remove"] ` ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
2023-12-07 12:35:48 +01:00
async function addGroupMemberCallback ( _ , arg ) {
2023-11-21 21:35:59 +01:00
if ( ! selected _group ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'Cannot run /memberadd command outside of a group chat.' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for /memberadd command' ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
arg = arg . trim ( ) ;
const chid = findCharacterIndex ( arg ) ;
if ( chid === - 1 ) {
console . warn ( ` WARN: No character found for argument ${ arg } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
2023-12-07 12:35:48 +01:00
const character = characters [ chid ] ;
2023-11-21 21:35:59 +01:00
const group = groups . find ( x => x . id === selected _group ) ;
if ( ! group || ! Array . isArray ( group . members ) ) {
console . warn ( ` WARN: No group found for ID ${ selected _group } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
const avatar = character . avatar ;
if ( group . members . includes ( avatar ) ) {
toastr . warning ( ` ${ character . name } is already a member of this group. ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-11-21 21:35:59 +01:00
}
group . members . push ( avatar ) ;
await saveGroupChat ( selected _group , true ) ;
// Trigger to reload group UI
$ ( '#rm_button_selected_ch' ) . trigger ( 'click' ) ;
2023-11-27 01:18:36 +01:00
return character . name ;
2023-11-21 21:35:59 +01:00
}
2023-12-07 12:35:48 +01:00
async function triggerGenerationCallback ( _ , arg ) {
2023-12-02 21:34:46 +01:00
setTimeout ( async ( ) => {
try {
await waitUntilCondition ( ( ) => ! is _send _press && ! is _group _generating , 10000 , 100 ) ;
} catch {
console . warn ( 'Timeout waiting for generation unlock' ) ;
toastr . warning ( 'Cannot run /trigger command while the reply is being generated.' ) ;
return '' ;
}
2023-10-29 18:35:26 +01:00
2023-12-02 21:34:46 +01:00
// Prevent generate recursion
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
2023-10-29 18:35:26 +01:00
2023-12-02 21:34:46 +01:00
let chid = undefined ;
2023-10-29 18:35:26 +01:00
2023-12-02 21:34:46 +01:00
if ( selected _group && arg ) {
chid = findGroupMemberId ( arg ) ;
2023-11-27 10:16:40 +01:00
2023-12-02 21:34:46 +01:00
if ( chid === undefined ) {
console . warn ( ` WARN: No group member found for argument ${ arg } ` ) ;
}
2023-11-27 10:16:40 +01:00
}
2023-11-20 22:49:04 +01:00
2023-12-02 21:34:46 +01:00
setTimeout ( ( ) => Generate ( 'normal' , { force _chid : chid } ) , 100 ) ;
} , 1 ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-10-29 18:35:26 +01:00
}
2023-12-07 12:35:48 +01:00
async function sendUserMessageCallback ( args , text ) {
2023-07-26 20:00:36 +02:00
if ( ! text ) {
console . warn ( 'WARN: No text provided for /send command' ) ;
return ;
}
text = text . trim ( ) ;
const bias = extractMessageBias ( text ) ;
2023-11-27 18:48:49 +01:00
const insertAt = Number ( resolveVariable ( args ? . at ) ) ;
await sendMessageAsUser ( text , bias , insertAt ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-07-26 20:00:36 +02:00
}
2023-12-07 12:35:48 +01:00
async function deleteMessagesByNameCallback ( _ , name ) {
2023-07-20 19:32:15 +02:00
if ( ! name ) {
console . warn ( 'WARN: No name provided for /delname command' ) ;
return ;
}
name = name . trim ( ) ;
const messagesToDelete = [ ] ;
chat . forEach ( ( value ) => {
if ( value . name === name ) {
messagesToDelete . push ( value ) ;
}
} ) ;
if ( ! messagesToDelete . length ) {
console . debug ( '/delname: Nothing to delete' ) ;
return ;
}
for ( const message of messagesToDelete ) {
const index = chat . indexOf ( message ) ;
if ( index !== - 1 ) {
console . debug ( ` /delname: Deleting message # ${ index } ` , message ) ;
chat . splice ( index , 1 ) ;
}
}
await saveChatConditional ( ) ;
await reloadCurrentChat ( ) ;
toastr . info ( ` Deleted ${ messagesToDelete . length } messages from ${ name } ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
function findCharacterIndex ( name ) {
2023-07-20 19:32:15 +02:00
const matchTypes = [
( a , b ) => a === b ,
( a , b ) => a . startsWith ( b ) ,
( a , b ) => a . includes ( b ) ,
] ;
for ( const matchType of matchTypes ) {
const index = characters . findIndex ( x => matchType ( x . name . toLowerCase ( ) , name . toLowerCase ( ) ) ) ;
if ( index !== - 1 ) {
return index ;
}
}
return - 1 ;
}
2023-12-07 12:35:48 +01:00
async function goToCharacterCallback ( _ , name ) {
2023-07-20 19:32:15 +02:00
if ( ! name ) {
console . warn ( 'WARN: No character name provided for /go command' ) ;
return ;
}
name = name . trim ( ) ;
const characterIndex = findCharacterIndex ( name ) ;
if ( characterIndex !== - 1 ) {
2023-11-27 01:18:36 +01:00
await openChat ( new String ( characterIndex ) ) ;
2023-12-07 12:35:48 +01:00
return characters [ characterIndex ] ? . name ;
2023-07-20 19:32:15 +02:00
} else {
console . warn ( ` No matches found for name " ${ name } " ` ) ;
2023-11-27 01:18:36 +01:00
return '' ;
2023-07-20 19:32:15 +02:00
}
}
2023-12-07 12:35:48 +01:00
async function openChat ( id ) {
2023-07-20 19:32:15 +02:00
resetSelectedGroup ( ) ;
setCharacterId ( id ) ;
2023-11-27 01:18:36 +01:00
await delay ( 1 ) ;
await reloadCurrentChat ( ) ;
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
function continueChatCallback ( ) {
2023-12-02 21:34:46 +01:00
setTimeout ( async ( ) => {
try {
await waitUntilCondition ( ( ) => ! is _send _press && ! is _group _generating , 10000 , 100 ) ;
} catch {
console . warn ( 'Timeout waiting for generation unlock' ) ;
toastr . warning ( 'Cannot run /continue command while the reply is being generated.' ) ;
}
// Prevent infinite recursion
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
$ ( '#option_continue' ) . trigger ( 'click' , { fromSlashCommand : true } ) ;
} , 1 ) ;
2023-12-02 19:12:36 +01:00
return '' ;
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
export async function generateSystemMessage ( _ , prompt ) {
2023-11-24 16:12:59 +01:00
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
2023-07-20 19:32:15 +02:00
if ( ! prompt ) {
console . warn ( 'WARN: No prompt provided for /sysgen command' ) ;
toastr . warning ( 'You must provide a prompt for the system message' ) ;
return ;
}
// Generate and regex the output if applicable
toastr . info ( 'Please wait' , 'Generating...' ) ;
let message = await generateQuietPrompt ( prompt ) ;
message = getRegexedString ( message , regex _placement . SLASH _COMMAND ) ;
sendNarratorMessage ( _ , message ) ;
}
2023-12-07 12:35:48 +01:00
function syncCallback ( ) {
2023-07-20 19:32:15 +02:00
$ ( '#sync_name_button' ) . trigger ( 'click' ) ;
}
2023-12-07 12:35:48 +01:00
function bindCallback ( ) {
2023-07-20 19:32:15 +02:00
$ ( '#lock_user_name' ) . trigger ( 'click' ) ;
}
2023-12-07 12:35:48 +01:00
function setStoryModeCallback ( ) {
2023-07-20 19:32:15 +02:00
$ ( '#chat_display' ) . val ( chat _styles . DOCUMENT ) . trigger ( 'change' ) ;
}
2023-12-07 12:35:48 +01:00
function setBubbleModeCallback ( ) {
2023-07-20 19:32:15 +02:00
$ ( '#chat_display' ) . val ( chat _styles . BUBBLES ) . trigger ( 'change' ) ;
}
2023-12-07 12:35:48 +01:00
function setFlatModeCallback ( ) {
2023-07-20 19:32:15 +02:00
$ ( '#chat_display' ) . val ( chat _styles . DEFAULT ) . trigger ( 'change' ) ;
}
2023-12-07 12:35:48 +01:00
function setNameCallback ( _ , name ) {
2023-07-20 19:32:15 +02:00
if ( ! name ) {
2023-12-02 20:11:06 +01:00
toastr . warning ( 'you must specify a name to change to' ) ;
2023-07-20 19:32:15 +02:00
return ;
}
name = name . trim ( ) ;
// If the name is a persona, auto-select it
for ( let persona of Object . values ( power _user . personas ) ) {
if ( persona . toLowerCase ( ) === name . toLowerCase ( ) ) {
autoSelectPersona ( name ) ;
return ;
}
}
// Otherwise, set just the name
setUserName ( name ) ; //this prevented quickReply usage
}
2023-12-07 12:35:48 +01:00
async function setNarratorName ( _ , text ) {
2023-07-20 19:32:15 +02:00
const name = text || NARRATOR _NAME _DEFAULT ;
2023-12-07 12:35:48 +01:00
chat _metadata [ NARRATOR _NAME _KEY ] = name ;
2023-07-20 19:32:15 +02:00
toastr . info ( ` System narrator name set to ${ name } ` ) ;
2023-08-23 09:32:48 +02:00
await saveChatConditional ( ) ;
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
export async function sendMessageAs ( args , text ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return ;
}
2023-11-21 01:54:04 +01:00
let name ;
let mesText ;
2023-07-20 19:32:15 +02:00
2023-11-27 18:48:49 +01:00
if ( args . name ) {
name = args . name . trim ( ) ;
2023-11-21 01:54:04 +01:00
mesText = text . trim ( ) ;
if ( ! name && ! text ) {
toastr . warning ( 'You must specify a name and text to send as' ) ;
return ;
}
} else {
const parts = text . split ( '\n' ) ;
if ( parts . length <= 1 ) {
toastr . warning ( 'Both character name and message are required. Separate them with a new line.' ) ;
return ;
}
name = parts . shift ( ) . trim ( ) ;
mesText = parts . join ( '\n' ) . trim ( ) ;
}
2023-07-20 19:32:15 +02:00
// Requires a regex check after the slash command is pushed to output
mesText = getRegexedString ( mesText , regex _placement . SLASH _COMMAND , { characterOverride : name } ) ;
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias ( mesText ) ;
const isSystem = replaceBiasMarkup ( mesText ) . trim ( ) . length === 0 ;
const character = characters . find ( x => x . name === name ) ;
let force _avatar , original _avatar ;
if ( character && character . avatar !== 'none' ) {
force _avatar = getThumbnailUrl ( 'avatar' , character . avatar ) ;
original _avatar = character . avatar ;
}
else {
force _avatar = default _avatar ;
original _avatar = default _avatar ;
}
const message = {
name : name ,
is _user : false ,
is _system : isSystem ,
2023-08-22 17:13:03 +02:00
send _date : getMessageTimeStamp ( ) ,
2023-07-20 19:32:15 +02:00
mes : substituteParams ( mesText ) ,
force _avatar : force _avatar ,
original _avatar : original _avatar ,
extra : {
bias : bias . trim ( ) . length ? bias : null ,
gen _id : Date . now ( ) ,
2023-12-02 21:06:57 +01:00
} ,
2023-07-20 19:32:15 +02:00
} ;
2023-11-27 18:48:49 +01:00
const insertAt = Number ( resolveVariable ( args . at ) ) ;
if ( ! isNaN ( insertAt ) && insertAt >= 0 && insertAt <= chat . length ) {
chat . splice ( insertAt , 0 , message ) ;
await saveChatConditional ( ) ;
await eventSource . emit ( event _types . MESSAGE _RECEIVED , insertAt ) ;
await reloadCurrentChat ( ) ;
await eventSource . emit ( event _types . CHARACTER _MESSAGE _RENDERED , insertAt ) ;
} else {
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _RECEIVED , ( chat . length - 1 ) ) ;
addOneMessage ( message ) ;
await eventSource . emit ( event _types . CHARACTER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
await saveChatConditional ( ) ;
}
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
export async function sendNarratorMessage ( args , text ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return ;
}
2023-12-07 12:35:48 +01:00
const name = chat _metadata [ NARRATOR _NAME _KEY ] || NARRATOR _NAME _DEFAULT ;
2023-07-20 19:32:15 +02:00
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias ( text ) ;
const isSystem = replaceBiasMarkup ( text ) . trim ( ) . length === 0 ;
const message = {
name : name ,
is _user : false ,
is _system : isSystem ,
2023-08-22 17:13:03 +02:00
send _date : getMessageTimeStamp ( ) ,
2023-07-20 19:32:15 +02:00
mes : substituteParams ( text . trim ( ) ) ,
force _avatar : system _avatar ,
extra : {
type : system _message _types . NARRATOR ,
bias : bias . trim ( ) . length ? bias : null ,
gen _id : Date . now ( ) ,
} ,
} ;
2023-11-27 18:48:49 +01:00
const insertAt = Number ( resolveVariable ( args . at ) ) ;
if ( ! isNaN ( insertAt ) && insertAt >= 0 && insertAt <= chat . length ) {
chat . splice ( insertAt , 0 , message ) ;
await saveChatConditional ( ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , insertAt ) ;
await reloadCurrentChat ( ) ;
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , insertAt ) ;
} else {
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , ( chat . length - 1 ) ) ;
addOneMessage ( message ) ;
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
await saveChatConditional ( ) ;
}
2023-07-20 19:32:15 +02:00
}
2023-12-07 12:35:48 +01:00
export async function promptQuietForLoudResponse ( who , text ) {
2023-09-17 21:00:10 +02:00
let character _id = getContext ( ) . characterId ;
if ( who === 'sys' ) {
2023-12-02 19:04:51 +01:00
text = 'System: ' + text ;
2023-09-17 21:00:10 +02:00
} else if ( who === 'user' ) {
2023-12-02 19:04:51 +01:00
text = name1 + ': ' + text ;
2023-09-17 21:00:10 +02:00
} else if ( who === 'char' ) {
2023-12-07 12:35:48 +01:00
text = characters [ character _id ] . name + ': ' + text ;
2023-09-17 21:00:10 +02:00
} else if ( who === 'raw' ) {
2023-12-02 15:18:49 +01:00
// We don't need to modify the text
2023-09-17 21:00:10 +02:00
}
//text = `${text}${power_user.instruct.enabled ? '' : '\n'}${(power_user.always_force_name2 && who != 'raw') ? characters[character_id].name + ":" : ""}`
let reply = await generateQuietPrompt ( text , true ) ;
text = await getRegexedString ( reply , regex _placement . SLASH _COMMAND ) ;
const message = {
2023-12-07 12:35:48 +01:00
name : characters [ character _id ] . name ,
2023-09-17 21:00:10 +02:00
is _user : false ,
is _name : true ,
is _system : false ,
send _date : getMessageTimeStamp ( ) ,
mes : substituteParams ( text . trim ( ) ) ,
extra : {
type : system _message _types . COMMENT ,
gen _id : Date . now ( ) ,
} ,
} ;
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , ( chat . length - 1 ) ) ;
addOneMessage ( message ) ;
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
await saveChatConditional ( ) ;
}
2023-12-07 12:35:48 +01:00
async function sendCommentMessage ( args , text ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return ;
}
const message = {
name : COMMENT _NAME _DEFAULT ,
is _user : false ,
2023-08-26 19:26:23 +02:00
is _system : true ,
2023-08-22 17:13:03 +02:00
send _date : getMessageTimeStamp ( ) ,
2023-07-20 19:32:15 +02:00
mes : substituteParams ( text . trim ( ) ) ,
force _avatar : comment _avatar ,
extra : {
type : system _message _types . COMMENT ,
gen _id : Date . now ( ) ,
} ,
} ;
2023-11-27 18:48:49 +01:00
const insertAt = Number ( resolveVariable ( args . at ) ) ;
if ( ! isNaN ( insertAt ) && insertAt >= 0 && insertAt <= chat . length ) {
chat . splice ( insertAt , 0 , message ) ;
await saveChatConditional ( ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , insertAt ) ;
await reloadCurrentChat ( ) ;
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , insertAt ) ;
} else {
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , ( chat . length - 1 ) ) ;
addOneMessage ( message ) ;
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
await saveChatConditional ( ) ;
}
2023-07-20 19:32:15 +02:00
}
2023-11-04 20:35:50 +01:00
/ * *
* Displays a help message from the slash command
* @ param { any } _ Unused
* @ param { string } type Type of help to display
* /
2023-12-07 12:35:48 +01:00
function helpCommandCallback ( _ , type ) {
2023-11-04 20:35:50 +01:00
switch ( type ? . trim ( ) ? . toLowerCase ( ) ) {
2023-07-20 19:32:15 +02:00
case 'slash' :
2023-11-04 20:35:50 +01:00
case 'commands' :
case 'slashes' :
case 'slash commands' :
2023-07-20 19:32:15 +02:00
case '1' :
sendSystemMessage ( system _message _types . SLASH _COMMANDS ) ;
break ;
case 'format' :
2023-11-04 20:35:50 +01:00
case 'formatting' :
case 'formats' :
case 'chat formatting' :
2023-07-20 19:32:15 +02:00
case '2' :
sendSystemMessage ( system _message _types . FORMATTING ) ;
break ;
case 'hotkeys' :
2023-11-04 20:35:50 +01:00
case 'hotkey' :
2023-07-20 19:32:15 +02:00
case '3' :
sendSystemMessage ( system _message _types . HOTKEYS ) ;
break ;
case 'macros' :
2023-11-04 20:35:50 +01:00
case 'macro' :
2023-07-20 19:32:15 +02:00
case '4' :
sendSystemMessage ( system _message _types . MACROS ) ;
break ;
default :
sendSystemMessage ( system _message _types . HELP ) ;
break ;
}
}
2023-08-07 21:21:10 +02:00
$ ( document ) . on ( 'click' , '[data-displayHelp]' , function ( e ) {
e . preventDefault ( ) ;
const page = String ( $ ( this ) . data ( 'displayhelp' ) ) ;
helpCommandCallback ( null , page ) ;
} ) ;
2023-07-20 19:32:15 +02:00
2023-12-07 12:35:48 +01:00
function setBackgroundCallback ( _ , bg ) {
2023-07-20 19:32:15 +02:00
if ( ! bg ) {
return ;
}
2023-11-14 20:43:08 +01:00
2023-07-20 19:32:15 +02:00
console . log ( 'Set background to ' + bg ) ;
2023-12-02 19:04:51 +01:00
const bgElements = Array . from ( document . querySelectorAll ( '.bg_example' ) ) . map ( ( x ) => ( { element : x , bgfile : x . getAttribute ( 'bgfile' ) } ) ) ;
2023-11-14 20:43:08 +01:00
2023-12-07 12:35:48 +01:00
const fuse = new Fuse ( bgElements , { keys : [ 'bgfile' ] } ) ;
2023-11-14 20:43:08 +01:00
const result = fuse . search ( bg ) ;
if ( ! result . length ) {
toastr . error ( ` No background found with name " ${ bg } " ` ) ;
return ;
}
2023-12-07 12:35:48 +01:00
const bgElement = result [ 0 ] . item . element ;
2023-11-14 20:43:08 +01:00
if ( bgElement instanceof HTMLElement ) {
bgElement . click ( ) ;
2023-07-20 19:32:15 +02:00
}
}
2023-11-23 21:36:48 +01:00
/ * *
* Executes slash commands in the provided text
* @ param { string } text Slash command text
2023-11-23 23:18:07 +01:00
* @ param { boolean } unescape Whether to unescape the batch separator
2023-11-23 21:36:48 +01:00
* @ returns { Promise < { interrupt : boolean , newText : string , pipe : string } | boolean > }
* /
2023-12-07 12:35:48 +01:00
async function executeSlashCommands ( text , unescape = false ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return false ;
}
2023-11-24 11:49:14 +01:00
// Unescape the pipe character and macro braces
2023-11-23 23:18:07 +01:00
if ( unescape ) {
text = text . replace ( /\\\|/g , '|' ) ;
2023-11-24 11:49:14 +01:00
text = text . replace ( /\\\{/g , '{' ) ;
text = text . replace ( /\\\}/g , '}' ) ;
2023-11-23 23:18:07 +01:00
}
2023-07-20 19:32:15 +02:00
// Hack to allow multi-line slash commands
// All slash command messages should begin with a slash
2023-11-23 23:18:07 +01:00
const placeholder = '\u200B' ; // Use a zero-width space as a placeholder
const chars = text . split ( '' ) ;
for ( let i = 1 ; i < chars . length ; i ++ ) {
2023-12-07 12:35:48 +01:00
if ( chars [ i ] === '|' && chars [ i - 1 ] !== '\\' ) {
chars [ i ] = placeholder ;
2023-11-23 23:18:07 +01:00
}
}
const lines = chars . join ( '' ) . split ( placeholder ) . map ( line => line . trim ( ) ) ;
2023-07-20 19:32:15 +02:00
const linesToRemove = [ ] ;
let interrupt = false ;
2023-11-21 23:39:17 +01:00
let pipeResult = '' ;
2023-07-20 19:32:15 +02:00
for ( let index = 0 ; index < lines . length ; index ++ ) {
2023-12-07 12:35:48 +01:00
const trimmedLine = lines [ index ] . trim ( ) ;
2023-07-20 19:32:15 +02:00
if ( ! trimmedLine . startsWith ( '/' ) ) {
continue ;
}
const result = parser . parse ( trimmedLine ) ;
if ( ! result ) {
continue ;
}
2023-11-04 12:33:09 +01:00
if ( result . value && typeof result . value === 'string' ) {
result . value = substituteParams ( result . value . trim ( ) ) ;
}
2023-07-20 19:32:15 +02:00
console . debug ( 'Slash command executing:' , result ) ;
2023-11-23 21:56:52 +01:00
let unnamedArg = result . value || pipeResult ;
2023-11-24 00:56:43 +01:00
if ( typeof result . args === 'object' ) {
2023-12-07 12:35:48 +01:00
for ( let [ key , value ] of Object . entries ( result . args ) ) {
2023-11-24 00:56:43 +01:00
if ( typeof value === 'string' ) {
2023-11-25 17:18:57 +01:00
value = substituteParams ( value . trim ( ) ) ;
2023-11-24 11:49:14 +01:00
if ( /{{pipe}}/i . test ( value ) ) {
2023-11-25 17:18:57 +01:00
value = value . replace ( /{{pipe}}/i , pipeResult || '' ) ;
2023-11-24 00:56:43 +01:00
}
2023-12-07 12:35:48 +01:00
result . args [ key ] = value ;
2023-11-23 23:18:07 +01:00
}
}
}
2023-11-24 11:49:14 +01:00
if ( typeof unnamedArg === 'string' && /{{pipe}}/i . test ( unnamedArg ) ) {
unnamedArg = unnamedArg . replace ( /{{pipe}}/i , pipeResult || '' ) ;
2023-11-23 21:56:52 +01:00
}
2023-11-21 23:39:17 +01:00
pipeResult = await result . command . callback ( result . args , unnamedArg ) ;
2023-07-20 19:32:15 +02:00
if ( result . command . interruptsGeneration ) {
interrupt = true ;
}
if ( result . command . purgeFromMessage ) {
2023-12-07 12:35:48 +01:00
linesToRemove . push ( lines [ index ] ) ;
2023-07-20 19:32:15 +02:00
}
}
const newText = lines . filter ( x => linesToRemove . indexOf ( x ) === - 1 ) . join ( '\n' ) ;
2023-11-23 21:36:48 +01:00
return { interrupt , newText , pipe : pipeResult } ;
2023-07-20 19:32:15 +02:00
}
2023-11-04 00:21:20 +01:00
2023-12-07 12:35:48 +01:00
function setSlashCommandAutocomplete ( textarea ) {
2023-11-04 00:21:20 +01:00
textarea . autocomplete ( {
source : ( input , output ) => {
// Only show for slash commands and if there's no space
if ( ! input . term . startsWith ( '/' ) || input . term . includes ( ' ' ) ) {
output ( [ ] ) ;
return ;
}
const slashCommand = input . term . toLowerCase ( ) . substring ( 1 ) ; // Remove the slash
const result = Object
. keys ( parser . helpStrings ) // Get all slash commands
. filter ( x => x . startsWith ( slashCommand ) ) // Filter by the input
. sort ( ( a , b ) => a . localeCompare ( b ) ) // Sort alphabetically
// .slice(0, 20) // Limit to 20 results
2023-12-07 12:35:48 +01:00
. map ( x => ( { label : parser . helpStrings [ x ] , value : ` / ${ x } ` } ) ) ; // Map to the help string
2023-11-04 00:21:20 +01:00
output ( result ) ; // Return the results
} ,
select : ( e , u ) => {
// unfocus the input
$ ( e . target ) . val ( u . item . value ) ;
} ,
minLength : 1 ,
2023-12-02 19:04:51 +01:00
position : { my : 'left bottom' , at : 'left top' , collision : 'none' } ,
2023-11-04 00:21:20 +01:00
} ) ;
2023-12-02 19:04:51 +01:00
textarea . autocomplete ( 'instance' ) . _renderItem = function ( ul , item ) {
2023-11-04 00:21:20 +01:00
const width = $ ( textarea ) . innerWidth ( ) ;
const content = $ ( '<div></div>' ) . html ( item . label ) ;
2023-12-02 19:04:51 +01:00
return $ ( '<li>' ) . width ( width ) . append ( content ) . appendTo ( ul ) ;
2023-11-04 00:21:20 +01:00
} ;
}
jQuery ( function ( ) {
const textarea = $ ( '#send_textarea' ) ;
setSlashCommandAutocomplete ( textarea ) ;
2023-12-02 20:11:06 +01:00
} ) ;