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 ,
2024-03-23 17:44:40 +01:00
extension _prompt _roles ,
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-12-21 19:49:03 +01:00
removeMacros ,
2024-03-27 14:16:20 +01:00
retriggerFirstMessageOnEmptyChat ,
2023-07-20 19:32:15 +02:00
saveChatConditional ,
2023-12-07 02:26:29 +01:00
sendMessageAsUser ,
2023-07-20 19:32:15 +02:00
sendSystemMessage ,
2024-03-26 17:21:22 +01:00
setActiveCharacter ,
setActiveGroup ,
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' ;
2024-01-05 19:02:44 +01:00
import { findGroupMemberId , groups , is _group _generating , openGroupById , resetSelectedGroup , saveGroupChat , selected _group } from './group-chats.js' ;
2024-03-15 15:08:22 +01:00
import { chat _completion _sources , oai _settings } from './openai.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' ;
2024-03-15 15:08:22 +01:00
import { textgen _types , textgenerationwebui _settings } from './textgen-settings.js' ;
2023-12-07 02:26:29 +01:00
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 {
2024-03-25 13:22:39 +01:00
static COMMENT _KEYWORDS = [ '#' , '/' ] ;
static RESERVED _KEYWORDS = [
... this . COMMENT _KEYWORDS ,
] ;
2023-07-20 19:32:15 +02:00
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 } ;
2024-03-25 13:22:39 +01:00
if ( [ command , ... aliases ] . some ( x => SlashCommandParser . RESERVED _KEYWORDS . includes ( x ) ) ) {
console . error ( 'ERROR: Reserved slash command keyword used!' ) ;
return ;
}
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
}
2024-03-03 04:09:58 +01:00
/ * *
* Parses a slash command to extract the command name , the ( named ) arguments and the remaining text
* @ param { string } text - Slash command text
2024-03-06 22:10:52 +01:00
* @ returns { { command : string , args : object , value : string } } - The parsed command , its arguments and the remaining text
2024-03-03 04:09:58 +01:00
* /
2023-12-07 12:35:48 +01:00
parse ( text ) {
2024-03-03 04:09:58 +01:00
// Parses a command even when spaces are present in arguments
// /buttons labels=["OK","I do not accept"] some text
// /fuzzy list=[ "red pink" , "yellow" ] threshold=" 0.6 " he yelled when the color was reddish and not pink | /echo
2023-12-07 12:35:48 +01:00
const excludedFromRegex = [ 'sendas' ] ;
2024-03-03 04:09:58 +01:00
let command = '' ;
2023-07-20 19:32:15 +02:00
const argObj = { } ;
2024-03-03 04:09:58 +01:00
let unnamedArg = '' ;
2023-07-20 19:32:15 +02:00
2024-03-03 04:09:58 +01:00
// extract the command " /fuzzy " => "fuzzy"
text = text . trim ( ) ;
let remainingText = '' ;
const commandArgPattern = / ^ \ / ( [ ^ \ s ] + ) \ s * ( . * ) $ / s ;
let match = commandArgPattern . exec ( text ) ;
if ( match !== null && match [ 1 ] . length > 0 ) {
command = match [ 1 ] ;
remainingText = match [ 2 ] ;
2024-03-06 22:10:52 +01:00
console . debug ( 'command:' + command ) ;
2024-03-03 04:09:58 +01:00
}
2024-02-09 23:49:38 +01:00
2024-03-03 04:09:58 +01:00
// parse the rest of the string to extract named arguments, the remainder is the "unnamedArg" which is usually text, like the prompt to send
while ( remainingText . length > 0 ) {
// does the remaining text is like nameArg=[value] or nameArg=[value,value] or nameArg=[ value , value , value]
// where value can be a string like " this is some text " , note previously it was not possible to have have spaces
// where value can be a scalar like AScalar
// where value can be a number like +9 -1005.44
// where value can be a macro like {{getvar::name}}
const namedArrayArgPattern = / ^ ( \ w + ) = \ [ \ s * ( ( ( ? < q u o t e > [ " ' ] ) [ ^ " ] * ( \ k < q u o t e > ) | { { [ ^ } ] * } } | [ + - ] ? \ d * \ . ? \ d + | \ w * ) \ s * , ? \ s * ) + \ ] / s ;
match = namedArrayArgPattern . exec ( remainingText ) ;
if ( match !== null && match [ 0 ] . length > 0 ) {
//console.log(`matching: ${match[0]}`);
const posFirstEqual = match [ 0 ] . indexOf ( '=' ) ;
const key = match [ 0 ] . substring ( 0 , posFirstEqual ) . trim ( ) ;
const value = match [ 0 ] . substring ( posFirstEqual + 1 ) . trim ( ) ;
2024-02-09 23:49:38 +01:00
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 , '' ) ;
2024-03-03 04:09:58 +01:00
remainingText = remainingText . slice ( match [ 0 ] . length + 1 ) . trim ( ) ;
continue ;
2023-07-20 19:32:15 +02:00
}
2024-03-03 04:09:58 +01:00
// does the remaining text is like nameArg=value
// where value can be a string like " this is some text " , note previously it was not possible to have have spaces
// where value can be a scalar like AScalar
// where value can be a number like +9 -1005.44
// where value can be a macro like {{getvar::name}}
const namedScalarArgPattern = / ^ ( \ w + ) = ( ( ( ? < q u o t e > [ " ' ] ) [ ^ " ] * ( \ k < q u o t e > ) | { { [ ^ } ] * } } | [ + - ] ? \ d * \ . ? \ d + | \ w * ) ) / s ;
match = namedScalarArgPattern . exec ( remainingText ) ;
if ( match !== null && match [ 0 ] . length > 0 ) {
//console.log(`matching: ${match[0]}`);
const posFirstEqual = match [ 0 ] . indexOf ( '=' ) ;
const key = match [ 0 ] . substring ( 0 , posFirstEqual ) . trim ( ) ;
const value = match [ 0 ] . substring ( posFirstEqual + 1 ) . trim ( ) ;
// Remove the quotes around the value, if any
argObj [ key ] = value . replace ( /(^")|("$)/g , '' ) ;
remainingText = remainingText . slice ( match [ 0 ] . length + 1 ) . trim ( ) ;
continue ;
2023-07-20 19:32:15 +02:00
}
2024-03-03 04:09:58 +01:00
2024-03-06 22:10:52 +01:00
// the remainder that matches no named argument is the "unamedArg" previously mentioned
2024-03-03 04:09:58 +01:00
unnamedArg = remainingText . trim ( ) ;
remainingText = '' ;
2023-07-20 19:32:15 +02:00
}
2024-03-03 04:09:58 +01:00
// Excluded commands format in their own function
if ( ! excludedFromRegex . includes ( command ) ) {
2024-03-06 22:10:52 +01:00
console . debug ( ` parse: !excludedFromRegex.includes( ${ command } ` ) ;
console . debug ( ` parse: unnamedArg before: ${ unnamedArg } ` ) ;
2024-03-03 04:09:58 +01:00
unnamedArg = getRegexedString (
unnamedArg ,
regex _placement . SLASH _COMMAND ,
) ;
2024-03-06 22:10:52 +01:00
console . debug ( ` parse: unnamedArg after: ${ unnamedArg } ` ) ;
2024-03-03 04:09:58 +01:00
}
// your weird complex command is now transformed into a juicy tiny text or something useful :)
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
}
2024-03-06 22:10:52 +01:00
return null ;
2023-07-20 19:32:15 +02:00
}
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 ) ;
2024-02-20 15:57:00 +01:00
parser . addCommand ( 'sync' , syncCallback , [ ] , ' – syncs the user persona 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 ) ;
2024-03-27 18:40:34 +01:00
parser . addCommand ( 'sendas' , sendMessageAs , [ ] , '<span class="monospace">[name=CharName compact=true/false (text)] – 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>. If "compact" is set to true, the message is sent using a compact layout.' , true , true ) ;
parser . addCommand ( 'sys' , sendNarratorMessage , [ 'nar' ] , '<span class="monospace">[compact=true/false (text)]</span> – sends message as a system narrator. If "compact" is set to true, the message is sent using a compact layout.' , 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 ) ;
2024-03-27 18:40:34 +01:00
parser . addCommand ( 'comment' , sendCommentMessage , [ ] , '<span class="monospace">[compact=true/false (text)]</span> – adds a note/comment message not part of the chat. If "compact" is set to true, the message is sent using a compact layout.' , 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 ) ;
2024-02-20 01:29:14 +01:00
parser . addCommand ( 'continue' , continueChatCallback , [ 'cont' ] , '<span class="monospace">[prompt]</span> – continues the last message in the chat, with an optional additional prompt' , true , true ) ;
2024-01-05 19:02:44 +01:00
parser . addCommand ( 'go' , goToCharacterCallback , [ 'char' ] , '<span class="monospace">(name)</span> – opens up a chat with the character or group 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 ) ;
2024-03-27 18:40:34 +01:00
parser . addCommand ( 'send' , sendUserMessageCallback , [ ] , '<span class="monospace">[compact=true/false (text)]</span> – adds a user message to the chat log without triggering a generation. If "compact" is set to true, the message is sent using a compact layout.' , true , true ) ;
2024-02-07 23:20:36 +01:00
parser . addCommand ( 'trigger' , triggerGenerationCallback , [ ] , ' <span class="monospace">await=true/false</span> – triggers a message generation. If in group, can trigger a message for the specified group member index or name. If <code>await=true</code> named argument passed, the command will await for the triggered generation before continuing.' , 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);
2024-03-31 20:02:38 +02:00
parser . addCommand ( 'gen' , generateCallback , [ ] , '<span class="monospace">(lock=on/off name="System" length=123 [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System"). "as" argument controls the role of the output prompt: system (default) or char. If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.' , true , true ) ;
parser . addCommand ( 'genraw' , generateRawCallback , [ ] , '<span class="monospace">(lock=on/off instruct=on/off stop=[] as=system/char system="system prompt" length=123 [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>. "as" argument controls the role of the output prompt: system (default) or char. "system" argument adds an (optional) system prompt at the start. If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.' , 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 ) ;
2024-03-03 15:04:48 +01:00
parser . addCommand ( 'fuzzy' , fuzzyCallback , [ ] , 'list=["a","b","c"] threshold=0.4 (text to search) – performs a fuzzy match of each items of list within the text to search. If any item matches then its name is returned. If no item list matches the text to search then no value is returned. The optional threshold (default is 0.4) allows some control over the matching. A low value (min 0.0) means the match is very strict. At 1.0 (max) the match is very loose and probably matches anything. The returned value passes to the next command through the pipe.' , true , true ) ; parser . addCommand ( 'pass' , ( _ , arg ) => arg , [ 'return' ] , '<span class="monospace">(text)</span> – passes the text to the next command through the pipe.' , true , true ) ;
2023-12-07 12:35:48 +01:00
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-27 13:28:15 +01:00
parser . addCommand ( 'run' , runCallback , [ 'call' , 'exec' ] , '<span class="monospace">[key1=value key2=value ...] ([qrSet.]qrLabel)</span> – runs a Quick Reply with the specified name from a currently active preset or from another preset, named arguments can be referenced in a QR with {{arg::key}}.' , true , true ) ;
2023-12-07 12:35:48 +01:00
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 ) ;
2024-03-23 17:44:40 +01:00
parser . addCommand ( 'inject' , injectCallback , [ ] , '<span class="monospace">id=injectId (position=before/after/chat depth=number scan=true/false role=system/user/assistant [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). Role: role for in-chat injections (default: system). Scan: include injection content into World Info scans (default: false).' , true , true ) ;
2023-11-28 00:44:13 +01:00
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-12-11 23:51:07 +01:00
parser . addCommand ( 'tokens' , ( _ , text ) => getTokenCount ( text ) , [ ] , '<span class="monospace">(text)</span> – counts the number of tokens in the text.' , true , true ) ;
2024-04-02 23:52:30 +02:00
parser . addCommand ( 'model' , modelCallback , [ ] , '<span class="monospace">(model name)</span> – sets the model for the current API. Gets the current model name if no argument is provided.' , 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 ,
} ;
2024-03-23 17:44:40 +01:00
const roles = {
'system' : extension _prompt _roles . SYSTEM ,
'user' : extension _prompt _roles . USER ,
'assistant' : extension _prompt _roles . ASSISTANT ,
} ;
2023-11-28 00:44:13 +01:00
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 ;
2024-03-23 17:44:40 +01:00
const roleValue = typeof args ? . role === 'string' ? args . role . toLowerCase ( ) . trim ( ) : Number ( args ? . role ? ? extension _prompt _roles . SYSTEM ) ;
const role = roles [ roleValue ] ? ? roles [ extension _prompt _roles . SYSTEM ] ;
const scan = isTrueBoolean ( args ? . scan ) ;
2023-11-28 00:44:13 +01:00
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 ,
2024-03-23 17:44:40 +01:00
scan ,
role ,
2023-11-28 00:44:13 +01:00
} ;
2024-03-23 17:44:40 +01:00
setExtensionPrompt ( prefixedId , value , position , depth , scan , role ) ;
2023-11-28 00:44:13 +01:00
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' ;
2024-03-23 17:44:40 +01:00
return ` * ** ${ id } **: <code> ${ inject . value } </code> ( ${ positionName } , depth: ${ inject . depth } , scan: ${ inject . scan ? ? false } , role: ${ inject . role ? ? extension _prompt _roles . SYSTEM } ) ` ;
2023-11-28 00:44:13 +01:00
} )
. 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 } ` ;
2024-03-23 17:44:40 +01:00
setExtensionPrompt ( prefixedId , '' , inject . position , inject . depth , inject . scan , inject . role ) ;
2023-11-28 00:44:13 +01:00
}
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 ) ;
2024-03-23 17:44:40 +01:00
setExtensionPrompt ( prefixedId , inject . value , inject . position , inject . depth , inject . scan , inject . role ) ;
2023-11-28 00:44:13 +01:00
}
}
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 ) ;
2024-01-23 06:00:31 +01:00
const { text } = decodeTextTokens ( tokenizerId , sliceTokens ) ;
return text ;
2023-11-26 12:55:22 +01:00
} 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-27 13:28:15 +01:00
async function runCallback ( args , 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-27 13:28:15 +01:00
return await window [ 'executeQuickReplyByName' ] ( name , args ) ;
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 || '' ;
}
2024-03-03 03:43:44 +01:00
/ * *
* Each item in "args.list" is searched within "search_item" using fuzzy search . If any matches it returns the matched "item" .
2024-03-03 15:04:48 +01:00
* @ param { FuzzyCommandArgs } args - arguments containing "list" ( JSON array ) and optionaly "threshold" ( float between 0.0 and 1.0 )
* @ param { string } searchInValue - the string where items of list are searched
* @ returns { string } - the matched item from the list
* @ typedef { { list : string , threshold : string } } FuzzyCommandArgs - arguments for / f u z z y c o m m a n d
* @ example / fuzzy list = [ "down" , "left" , "up" , "right" ] "he looks up" | /echo / / should return "up"
* @ link https : //www.fusejs.io/
2024-03-03 03:43:44 +01:00
* /
2024-03-03 15:04:48 +01:00
function fuzzyCallback ( args , searchInValue ) {
if ( ! searchInValue ) {
2023-11-24 01:21:50 +01:00
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 '' ;
}
2024-03-03 03:43:44 +01:00
const params = {
2023-11-24 01:21:50 +01:00
includeScore : true ,
findAllMatches : true ,
ignoreLocation : true ,
2024-03-03 03:43:44 +01:00
threshold : 0.4 ,
} ;
// threshold determines how strict is the match, low threshold value is very strict, at 1 (nearly?) everything matches
2024-03-03 15:04:48 +01:00
if ( 'threshold' in args ) {
2024-03-03 03:43:44 +01:00
params . threshold = parseFloat ( resolveVariable ( args . threshold ) ) ;
2024-03-03 15:04:48 +01:00
if ( isNaN ( params . threshold ) ) {
2024-03-03 03:43:44 +01:00
console . warn ( 'WARN: \'threshold\' argument must be a float between 0.0 and 1.0 for /fuzzy command' ) ;
return '' ;
}
2024-03-03 15:04:48 +01:00
if ( params . threshold < 0 ) {
2024-03-03 03:43:44 +01:00
params . threshold = 0 ;
}
2024-03-03 15:04:48 +01:00
if ( params . threshold > 1 ) {
2024-03-03 03:43:44 +01:00
params . threshold = 1 ;
}
}
2024-03-03 15:04:48 +01:00
const fuse = new Fuse ( [ searchInValue ] , params ) ;
2024-03-03 03:43:44 +01:00
// each item in the "list" is searched within "search_item", if any matches it returns the matched "item"
2024-03-03 15:04:48 +01:00
for ( const searchItem of list ) {
const result = fuse . search ( searchItem ) ;
if ( result . length > 0 ) {
console . info ( 'fuzzyCallback Matched: ' + searchItem ) ;
return searchItem ;
2024-03-03 03:43:44 +01:00
}
}
return '' ;
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 ) ;
2024-03-28 20:51:02 +01:00
const as = args ? . as || 'system' ;
const quietToLoud = as === 'char' ;
2024-03-31 18:42:12 +02:00
const systemPrompt = resolveVariable ( args ? . system ) || '' ;
2024-03-31 20:02:38 +02:00
const length = Number ( resolveVariable ( args ? . length ) ? ? 0 ) || 0 ;
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 ) ) ;
2024-03-31 20:02:38 +02:00
const result = await generateRaw ( value , '' , isFalseBoolean ( args ? . instruct ) , quietToLoud , systemPrompt , length ) ;
2023-11-24 14:18:49 +01:00
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 ) ;
2024-03-30 17:13:55 +01:00
const as = args ? . as || 'system' ;
const quietToLoud = as === 'char' ;
2024-03-31 20:02:38 +02:00
const length = Number ( resolveVariable ( args ? . length ) ? ? 0 ) || 0 ;
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 ) ) ;
2024-01-12 18:16:42 +01:00
const name = args ? . name ;
2024-03-31 20:02:38 +02:00
const result = await generateQuietPrompt ( value , quietToLoud , false , '' , name , length ) ;
2023-11-24 14:18:49 +01:00
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-18 00:39:37 +01:00
/ * *
* Copium for running group actions when the member is offscreen .
* @ param { number } chid - character ID
2023-12-19 22:12:14 +01:00
* @ param { string } action - one of 'enable' , 'disable' , 'up' , 'down' , 'view' , 'remove'
2023-12-18 00:39:37 +01:00
* @ returns { void }
* /
function performGroupMemberAction ( chid , action ) {
const memberSelector = ` .group_member[chid=" ${ chid } "] ` ;
// Do not optimize. Paginator gets recreated on every action
const paginationSelector = '#rm_group_members_pagination' ;
const pageSizeSelector = '#rm_group_members_pagination select' ;
let wasOffscreen = false ;
let paginationValue = null ;
let pageValue = null ;
if ( $ ( memberSelector ) . length === 0 ) {
wasOffscreen = true ;
paginationValue = Number ( $ ( pageSizeSelector ) . val ( ) ) ;
pageValue = $ ( paginationSelector ) . pagination ( 'getCurrentPageNum' ) ;
$ ( pageSizeSelector ) . val ( $ ( pageSizeSelector ) . find ( 'option' ) . last ( ) . val ( ) ) . trigger ( 'change' ) ;
}
$ ( memberSelector ) . find ( ` [data-action=" ${ action } "] ` ) . trigger ( 'click' ) ;
if ( wasOffscreen ) {
$ ( pageSizeSelector ) . val ( paginationValue ) . trigger ( 'change' ) ;
2023-12-19 22:12:14 +01:00
if ( $ ( paginationSelector ) . length ) {
$ ( paginationSelector ) . pagination ( 'go' , pageValue ) ;
}
2023-12-18 00:39:37 +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-12-18 00:39:37 +01:00
performGroupMemberAction ( chid , 'disable' ) ;
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-12-18 00:39:37 +01:00
performGroupMemberAction ( chid , 'enable' ) ;
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
}
2023-12-18 00:39:37 +01:00
performGroupMemberAction ( chid , 'up' ) ;
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
}
2023-12-18 00:39:37 +01:00
performGroupMemberAction ( chid , 'down' ) ;
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
}
2023-12-19 22:12:14 +01:00
performGroupMemberAction ( chid , 'view' ) ;
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
}
2023-12-18 00:39:37 +01:00
performGroupMemberAction ( chid , 'remove' ) ;
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
}
2024-02-07 23:20:36 +01:00
async function triggerGenerationCallback ( args , value ) {
const shouldAwait = isTrueBoolean ( args ? . await ) ;
const outerPromise = new Promise ( ( outerResolve ) => setTimeout ( async ( ) => {
2023-12-02 21:34:46 +01:00
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
2024-02-07 23:20:36 +01:00
if ( selected _group && value ) {
chid = findGroupMemberId ( value ) ;
2023-11-27 10:16:40 +01:00
2023-12-02 21:34:46 +01:00
if ( chid === undefined ) {
2024-02-07 23:20:36 +01:00
console . warn ( ` WARN: No group member found for argument ${ value } ` ) ;
2023-12-02 21:34:46 +01:00
}
2023-11-27 10:16:40 +01:00
}
2023-11-20 22:49:04 +01:00
2024-02-07 23:20:36 +01:00
outerResolve ( new Promise ( innerResolve => setTimeout ( ( ) => innerResolve ( Generate ( 'normal' , { force _chid : chid } ) ) , 100 ) ) ) ;
} , 1 ) ) ;
if ( shouldAwait ) {
const innerPromise = await outerPromise ;
await innerPromise ;
}
2023-12-02 21:34:46 +01:00
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 ( ) ;
2024-03-27 18:40:34 +01:00
const compact = isTrueBoolean ( args ? . compact ) ;
2023-07-26 20:00:36 +02:00
const bias = extractMessageBias ( text ) ;
2023-11-27 18:48:49 +01:00
const insertAt = Number ( resolveVariable ( args ? . at ) ) ;
2024-03-27 18:40:34 +01:00
await sendMessageAsUser ( text , bias , insertAt , compact ) ;
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 ) ,
] ;
2024-02-16 19:24:47 +01:00
const exactAvatarMatch = characters . findIndex ( x => x . avatar === name ) ;
if ( exactAvatarMatch !== - 1 ) {
return exactAvatarMatch ;
}
2023-07-20 19:32:15 +02:00
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 ) ) ;
2024-03-26 17:21:22 +01:00
setActiveCharacter ( characters [ characterIndex ] ? . avatar ) ;
setActiveGroup ( null ) ;
2023-12-07 12:35:48 +01:00
return characters [ characterIndex ] ? . name ;
2023-07-20 19:32:15 +02:00
} else {
2024-01-05 19:12:59 +01:00
const group = groups . find ( it => it . name . toLowerCase ( ) == name . toLowerCase ( ) ) ;
2024-01-05 19:02:44 +01:00
if ( group ) {
await openGroupById ( group . id ) ;
2024-03-26 17:21:22 +01:00
setActiveCharacter ( null ) ;
setActiveGroup ( group . id ) ;
2024-01-05 19:02:44 +01:00
return group . name ;
} else {
console . warn ( ` No matches found for name " ${ name } " ` ) ;
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
}
2024-02-19 01:17:04 +01:00
function continueChatCallback ( _ , prompt ) {
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' ) ;
2024-02-19 01:17:04 +01:00
$ ( '#option_continue' ) . trigger ( 'click' , { fromSlashCommand : true , additionalPrompt : prompt } ) ;
2023-12-02 21:34:46 +01:00
} , 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 ) ;
2024-03-27 14:16:20 +01:00
retriggerFirstMessageOnEmptyChat ( ) ;
2023-07-20 19:32:15 +02:00
return ;
}
}
// Otherwise, set just the name
setUserName ( name ) ; //this prevented quickReply usage
2024-03-27 14:16:20 +01:00
retriggerFirstMessageOnEmptyChat ( ) ;
2023-07-20 19:32:15 +02:00
}
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 ) ;
2023-12-21 19:49:03 +01:00
const isSystem = bias && ! removeMacros ( mesText ) . length ;
2024-03-27 18:40:34 +01:00
const compact = isTrueBoolean ( args ? . compact ) ;
2023-07-20 19:32:15 +02:00
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 ( ) ,
2024-03-27 18:40:34 +01:00
isSmallSys : compact ,
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 ) ;
2023-12-21 19:49:03 +01:00
const isSystem = bias && ! removeMacros ( text ) . length ;
2024-03-27 18:40:34 +01:00
const compact = isTrueBoolean ( args ? . compact ) ;
2023-07-20 19:32:15 +02:00
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 ( ) ,
2024-03-27 18:40:34 +01:00
isSmallSys : compact ,
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 _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 ;
}
2024-03-27 18:40:34 +01:00
const compact = isTrueBoolean ( args ? . compact ) ;
2023-07-20 19:32:15 +02:00
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 ( ) ,
2024-03-27 18:40:34 +01:00
isSmallSys : compact ,
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 _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
}
}
2024-03-15 15:08:22 +01:00
/ * *
* Sets a model for the current API .
* @ param { object } _ Unused
2024-04-02 23:52:30 +02:00
* @ param { string } model New model name
* @ returns { string } New or existing model name
2024-03-15 15:08:22 +01:00
* /
function modelCallback ( _ , model ) {
const modelSelectMap = [
{ id : 'model_togetherai_select' , api : 'textgenerationwebui' , type : textgen _types . TOGETHERAI } ,
{ id : 'openrouter_model' , api : 'textgenerationwebui' , type : textgen _types . OPENROUTER } ,
{ id : 'model_infermaticai_select' , api : 'textgenerationwebui' , type : textgen _types . INFERMATICAI } ,
{ id : 'model_dreamgen_select' , api : 'textgenerationwebui' , type : textgen _types . DREAMGEN } ,
{ id : 'mancer_model' , api : 'textgenerationwebui' , type : textgen _types . MANCER } ,
{ id : 'aphrodite_model' , api : 'textgenerationwebui' , type : textgen _types . APHRODITE } ,
{ id : 'ollama_model' , api : 'textgenerationwebui' , type : textgen _types . OLLAMA } ,
{ id : 'model_openai_select' , api : 'openai' , type : chat _completion _sources . OPENAI } ,
{ id : 'model_claude_select' , api : 'openai' , type : chat _completion _sources . CLAUDE } ,
{ id : 'model_windowai_select' , api : 'openai' , type : chat _completion _sources . WINDOWAI } ,
{ id : 'model_openrouter_select' , api : 'openai' , type : chat _completion _sources . OPENROUTER } ,
{ id : 'model_ai21_select' , api : 'openai' , type : chat _completion _sources . AI21 } ,
{ id : 'model_google_select' , api : 'openai' , type : chat _completion _sources . MAKERSUITE } ,
{ id : 'model_mistralai_select' , api : 'openai' , type : chat _completion _sources . MISTRALAI } ,
{ id : 'model_custom_select' , api : 'openai' , type : chat _completion _sources . CUSTOM } ,
2024-04-01 23:20:17 +02:00
{ id : 'model_cohere_select' , api : 'openai' , type : chat _completion _sources . COHERE } ,
2024-03-15 15:08:22 +01:00
{ id : 'model_novel_select' , api : 'novel' , type : null } ,
{ id : 'horde_model' , api : 'koboldhorde' , type : null } ,
] ;
function getSubType ( ) {
switch ( main _api ) {
case 'textgenerationwebui' :
return textgenerationwebui _settings . type ;
case 'openai' :
return oai _settings . chat _completion _source ;
default :
return null ;
}
}
const apiSubType = getSubType ( ) ;
const modelSelectItem = modelSelectMap . find ( x => x . api == main _api && x . type == apiSubType ) ? . id ;
if ( ! modelSelectItem ) {
toastr . info ( 'Setting a model for your API is not supported or not implemented yet.' ) ;
2024-04-02 23:52:30 +02:00
return '' ;
2024-03-15 15:08:22 +01:00
}
const modelSelectControl = document . getElementById ( modelSelectItem ) ;
if ( ! ( modelSelectControl instanceof HTMLSelectElement ) ) {
toastr . error ( ` Model select control not found: ${ main _api } [ ${ apiSubType } ] ` ) ;
2024-04-02 23:52:30 +02:00
return '' ;
2024-03-15 15:08:22 +01:00
}
const options = Array . from ( modelSelectControl . options ) ;
if ( ! options . length ) {
toastr . warning ( 'No model options found. Check your API settings.' ) ;
2024-04-02 23:52:30 +02:00
return '' ;
}
model = String ( model || '' ) . trim ( ) ;
if ( ! model ) {
return modelSelectControl . value ;
2024-03-15 15:08:22 +01:00
}
2024-04-02 23:52:30 +02:00
console . log ( 'Set model to ' + model ) ;
2024-03-15 15:08:22 +01:00
let newSelectedOption = null ;
const fuse = new Fuse ( options , { keys : [ 'text' , 'value' ] } ) ;
const fuzzySearchResult = fuse . search ( model ) ;
const exactValueMatch = options . find ( x => x . value . trim ( ) . toLowerCase ( ) === model . trim ( ) . toLowerCase ( ) ) ;
const exactTextMatch = options . find ( x => x . text . trim ( ) . toLowerCase ( ) === model . trim ( ) . toLowerCase ( ) ) ;
if ( exactValueMatch ) {
newSelectedOption = exactValueMatch ;
} else if ( exactTextMatch ) {
newSelectedOption = exactTextMatch ;
} else if ( fuzzySearchResult . length ) {
newSelectedOption = fuzzySearchResult [ 0 ] . item ;
}
if ( newSelectedOption ) {
modelSelectControl . value = newSelectedOption . value ;
$ ( modelSelectControl ) . trigger ( 'change' ) ;
toastr . success ( ` Model set to " ${ newSelectedOption . text } " ` ) ;
2024-04-02 23:52:30 +02:00
return newSelectedOption . value ;
2024-03-15 15:08:22 +01:00
} else {
toastr . warning ( ` No model found with name " ${ model } " ` ) ;
2024-04-02 23:52:30 +02:00
return '' ;
2024-03-15 15:08:22 +01: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 ;
}
2024-03-25 13:22:39 +01:00
// Skip comment commands. They don't run macros or interrupt pipes.
if ( SlashCommandParser . COMMENT _KEYWORDS . includes ( result . command ) ) {
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 ) ) {
2024-01-24 20:19:29 +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
}
}
}
2024-02-03 01:06:49 +01:00
if ( typeof unnamedArg === 'string' ) {
if ( /{{pipe}}/i . test ( unnamedArg ) ) {
unnamedArg = unnamedArg . replace ( /{{pipe}}/i , pipeResult ? ? '' ) ;
}
unnamedArg = unnamedArg
? . replace ( /\\\|/g , '|' )
? . replace ( /\\\{/g , '{' )
2024-03-15 15:08:22 +01:00
? . replace ( /\\\}/g , '}' ) ;
2023-11-23 21:56:52 +01:00
}
2024-01-26 19:58:44 +01:00
for ( const [ key , value ] of Object . entries ( result . args ) ) {
2024-02-03 01:06:49 +01:00
if ( typeof value === 'string' ) {
result . args [ key ] = value
. replace ( /\\\|/g , '|' )
. replace ( /\\\{/g , '{' )
2024-03-15 15:08:22 +01:00
. replace ( /\\\}/g , '}' ) ;
2024-02-03 01:06:49 +01:00
}
2024-01-26 19:58:44 +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
} ) ;