2023-07-20 19:32:15 +02:00
import {
addOneMessage ,
characters ,
chat ,
chat _metadata ,
default _avatar ,
eventSource ,
event _types ,
extractMessageBias ,
getThumbnailUrl ,
replaceBiasMarkup ,
saveChatConditional ,
sendSystemMessage ,
setUserName ,
substituteParams ,
comment _avatar ,
system _avatar ,
system _message _types ,
setCharacterId ,
generateQuietPrompt ,
reloadCurrentChat ,
2023-07-26 20:00:36 +02:00
sendMessageAsUser ,
2023-09-17 21:00:10 +02:00
name1 ,
2023-10-16 08:12:12 +02:00
Generate ,
this _chid ,
setCharacterName ,
2023-07-20 19:32:15 +02:00
} from "../script.js" ;
2023-08-22 17:13:03 +02:00
import { getMessageTimeStamp } from "./RossAscends-mods.js" ;
2023-11-11 15:12:02 +01:00
import { groups , is _group _generating , resetSelectedGroup , selected _group } from "./group-chats.js" ;
2023-07-20 19:32:15 +02:00
import { getRegexedString , regex _placement } from "./extensions/regex/engine.js" ;
import { chat _styles , power _user } from "./power-user.js" ;
2023-08-29 00:54:11 +02:00
import { autoSelectPersona } from "./personas.js" ;
2023-09-17 21:00:10 +02:00
import { getContext } from "./extensions.js" ;
2023-11-03 22:45:56 +01:00
import { hideChatMessage , unhideChatMessage } from "./chats.js" ;
2023-11-14 20:37:37 +01:00
import { stringToRange } from "./utils.js" ;
2023-07-20 19:32:15 +02:00
export {
executeSlashCommands ,
registerSlashCommand ,
getSlashCommandsHelp ,
}
class SlashCommandParser {
constructor ( ) {
this . commands = { } ;
2023-11-04 00:21:20 +01:00
this . helpStrings = { } ;
2023-07-20 19:32:15 +02:00
}
addCommand ( command , callback , aliases , helpString = '' , interruptsGeneration = false , purgeFromMessage = true ) {
const fnObj = { callback , helpString , interruptsGeneration , purgeFromMessage } ;
if ( [ command , ... aliases ] . some ( x => this . commands . hasOwnProperty ( x ) ) ) {
console . trace ( 'WARN: Duplicate slash command registered!' ) ;
}
this . commands [ command ] = fnObj ;
if ( Array . isArray ( aliases ) ) {
aliases . forEach ( ( alias ) => {
this . commands [ alias ] = fnObj ;
} ) ;
}
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-11-04 00:21:20 +01:00
this . helpStrings [ command ] = stringBuilder ;
2023-07-20 19:32:15 +02:00
}
parse ( text ) {
const excludedFromRegex = [ "sendas" ]
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 ) {
const argsArray = args . split ( ' ' ) ;
for ( let arg of argsArray ) {
const equalsIndex = arg . indexOf ( '=' ) ;
if ( equalsIndex !== - 1 ) {
const key = arg . substring ( 0 , equalsIndex ) ;
const value = arg . substring ( equalsIndex + 1 ) ;
2023-11-09 01:57:40 +01:00
// Replace "wrapping quotes" used for escaping spaces
argObj [ key ] = value . replace ( /(^")|("$)/g , '' ) ;
2023-07-20 19:32:15 +02:00
}
else {
break ;
}
}
unnamedArg = argsArray . slice ( Object . keys ( argObj ) . length ) . join ( ' ' ) ;
// Excluded commands format in their own function
if ( ! excludedFromRegex . includes ( command ) ) {
unnamedArg = getRegexedString (
unnamedArg ,
regex _placement . SLASH _COMMAND
) ;
}
}
if ( this . commands [ command ] ) {
return { command : this . commands [ command ] , args : argObj , value : unnamedArg } ;
}
return false ;
}
getHelpString ( ) {
2023-11-04 00:21:20 +01:00
const listItems = Object
. entries ( this . helpStrings )
. sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) )
. map ( x => x [ 1 ] )
. 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-11-04 11:37:13 +01:00
parser . addCommand ( '?' , helpCommandCallback , [ 'help' ] , ' – get help on macros, chat formatting and commands' , true , true ) ;
2023-07-20 19:32:15 +02:00
parser . addCommand ( 'name' , setNameCallback , [ 'persona' ] , '<span class="monospace">(name)</span> – sets user name and persona avatar (if set)' , true , true ) ;
parser . addCommand ( 'sync' , syncCallback , [ ] , ' – syncs user name in user-attributed messages in the current chat' , true , true ) ;
parser . addCommand ( 'lock' , bindCallback , [ 'bind' ] , ' – locks/unlocks a persona (name and avatar) to the current chat' , true , true ) ;
2023-11-14 20:43:08 +01:00
parser . addCommand ( 'bg' , setBackgroundCallback , [ 'background' ] , '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed' , false , true ) ;
2023-10-07 18:25:36 +02: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": <pre><code>/sendas Chloe Hello, guys!</code></pre> ` , true , true ) ;
2023-10-02 02:28:25 +02: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 ) ;
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 ) ;
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-07-20 19:32:15 +02:00
parser . addCommand ( 'delname' , deleteMessagesByNameCallback , [ 'cancel' ] , '<span class="monospace">(name)</span> – deletes all messages attributed to a specified name' , true , true ) ;
2023-07-26 20:00:36 +02:00
parser . addCommand ( 'send' , sendUserMessageCallback , [ 'add' ] , '<span class="monospace">(text)</span> – adds a user message to the chat log without triggering a generation' , true , true ) ;
2023-10-29 18:35:26 +01:00
parser . addCommand ( 'trigger' , triggerGroupMessageCallback , [ ] , '<span class="monospace">(member index or name)</span> – triggers a message generation for the specified group member' , 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-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-07-20 19:32:15 +02:00
2023-10-16 08:12:12 +02:00
async function askCharacter ( _ , text ) {
// Prevent generate recursion
$ ( '#send_textarea' ) . val ( '' ) ;
// Not supported in group chats
// TODO: Maybe support group chats?
if ( selected _group ) {
toastr . error ( "Cannot run this command in a group chat!" ) ;
return ;
}
if ( ! text ) {
console . warn ( 'WARN: No text provided for /ask command' )
}
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 ) ;
if ( ! characters [ chId ] || chId === - 1 ) {
toastr . error ( "Character not found." ) ;
return ;
}
// Override character and send a user message
setCharacterId ( chId ) ;
// TODO: Maybe look up by filename instead of name
const character = characters [ chId ] ;
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 ) ;
sendMessageAsUser ( mesText )
const restoreCharacter = ( ) => {
setCharacterId ( prevChId ) ;
setCharacterName ( characters [ prevChId ] . name ) ;
// Only force the new avatar if the character name is the same
// This skips if an error was fired
const lastMessage = chat [ chat . length - 1 ] ;
if ( lastMessage && lastMessage ? . name === character . name ) {
lastMessage . force _avatar = force _avatar ;
lastMessage . original _avatar = original _avatar ;
}
// Kill this callback once the event fires
eventSource . removeListener ( event _types . CHARACTER _MESSAGE _RENDERED , restoreCharacter )
}
// Run generate and restore previous character on error
try {
toastr . info ( ` Asking ${ character . name } something... ` ) ;
await Generate ( 'ask_command' )
} catch {
restoreCharacter ( )
}
// Restore previous character once message renders
// Hack for generate
eventSource . on ( event _types . CHARACTER _MESSAGE _RENDERED , restoreCharacter ) ;
}
2023-11-03 22:45:56 +01:00
async function hideMessageCallback ( _ , arg ) {
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
}
async function unhideMessageCallback ( _ , arg ) {
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for /unhide 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 /unhide 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 unhideChatMessage ( messageId , messageBlock ) ;
}
2023-11-03 22:45:56 +01:00
}
2023-10-29 18:35:26 +01:00
async function triggerGroupMessageCallback ( _ , arg ) {
if ( ! selected _group ) {
2023-11-11 15:12:02 +01:00
toastr . warning ( "Cannot run trigger command outside of a group chat." ) ;
return ;
}
if ( is _group _generating ) {
toastr . warning ( "Cannot run trigger command while the group reply is generating." ) ;
2023-10-29 18:35:26 +01:00
return ;
}
arg = arg ? . trim ( ) ;
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for /trigger command' ) ;
return ;
}
const group = groups . find ( x => x . id == selected _group ) ;
if ( ! group || ! Array . isArray ( group . members ) ) {
console . warn ( 'WARN: No group found for selected group ID' ) ;
return ;
}
// Prevent generate recursion
$ ( '#send_textarea' ) . val ( '' ) ;
// Index is 1-based
const index = parseInt ( arg ) - 1 ;
const searchByName = isNaN ( index ) ;
if ( searchByName ) {
const memberNames = group . members . map ( x => ( { name : characters . find ( y => y . avatar === x ) ? . name , index : characters . findIndex ( y => y . avatar === x ) } ) ) ;
const fuse = new Fuse ( memberNames , { keys : [ 'name' ] } ) ;
const result = fuse . search ( arg ) ;
if ( ! result . length ) {
console . warn ( ` WARN: No group member found with name ${ arg } ` ) ;
return ;
}
const chid = result [ 0 ] . item . index ;
if ( chid === - 1 ) {
console . warn ( ` WARN: No character found for group member ${ arg } ` ) ;
return ;
}
console . log ( ` Triggering group member ${ chid } ( ${ arg } ) from search result ` , result [ 0 ] ) ;
Generate ( 'normal' , { force _chid : chid } ) ;
} else {
const memberAvatar = group . members [ index ] ;
if ( memberAvatar === undefined ) {
console . warn ( ` WARN: No group member found at index ${ index } ` ) ;
return ;
}
const chid = characters . findIndex ( x => x . avatar === memberAvatar ) ;
if ( chid === - 1 ) {
console . warn ( ` WARN: No character found for group member ${ memberAvatar } at index ${ index } ` ) ;
return ;
}
console . log ( ` Triggering group member ${ memberAvatar } at index ${ index } ` ) ;
Generate ( 'normal' , { force _chid : chid } ) ;
}
}
2023-07-26 20:00:36 +02:00
async function sendUserMessageCallback ( _ , text ) {
if ( ! text ) {
console . warn ( 'WARN: No text provided for /send command' ) ;
return ;
}
text = text . trim ( ) ;
const bias = extractMessageBias ( text ) ;
2023-11-18 18:23:58 +01:00
await sendMessageAsUser ( text , bias ) ;
2023-07-26 20:00:36 +02:00
}
2023-07-20 19:32:15 +02:00
async function deleteMessagesByNameCallback ( _ , name ) {
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 } ` ) ;
}
function findCharacterIndex ( name ) {
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 ;
}
function goToCharacterCallback ( _ , name ) {
if ( ! name ) {
console . warn ( 'WARN: No character name provided for /go command' ) ;
return ;
}
name = name . trim ( ) ;
const characterIndex = findCharacterIndex ( name ) ;
if ( characterIndex !== - 1 ) {
2023-07-24 16:22:51 +02:00
openChat ( new String ( characterIndex ) ) ;
2023-07-20 19:32:15 +02:00
} else {
console . warn ( ` No matches found for name " ${ name } " ` ) ;
}
}
function openChat ( id ) {
resetSelectedGroup ( ) ;
setCharacterId ( id ) ;
setTimeout ( ( ) => {
reloadCurrentChat ( ) ;
} , 1 ) ;
}
function continueChatCallback ( ) {
// Prevent infinite recursion
$ ( '#send_textarea' ) . val ( '' ) ;
$ ( '#option_continue' ) . trigger ( 'click' , { fromSlashCommand : true } ) ;
}
2023-09-17 21:00:10 +02:00
export async function generateSystemMessage ( _ , prompt ) {
2023-07-20 19:32:15 +02:00
$ ( '#send_textarea' ) . val ( '' ) ;
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 ) ;
}
function syncCallback ( ) {
$ ( '#sync_name_button' ) . trigger ( 'click' ) ;
}
function bindCallback ( ) {
$ ( '#lock_user_name' ) . trigger ( 'click' ) ;
}
function setStoryModeCallback ( ) {
$ ( '#chat_display' ) . val ( chat _styles . DOCUMENT ) . trigger ( 'change' ) ;
}
function setBubbleModeCallback ( ) {
$ ( '#chat_display' ) . val ( chat _styles . BUBBLES ) . trigger ( 'change' ) ;
}
function setFlatModeCallback ( ) {
$ ( '#chat_display' ) . val ( chat _styles . DEFAULT ) . trigger ( 'change' ) ;
}
function setNameCallback ( _ , name ) {
if ( ! name ) {
toastr . warning ( 'you must specify a name to change to' )
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-08-23 09:32:48 +02:00
async function setNarratorName ( _ , text ) {
2023-07-20 19:32:15 +02:00
const name = text || NARRATOR _NAME _DEFAULT ;
chat _metadata [ NARRATOR _NAME _KEY ] = name ;
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-09-17 21:00:10 +02:00
export async function sendMessageAs ( _ , text ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return ;
}
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 ;
}
const name = parts . shift ( ) . trim ( ) ;
let mesText = parts . join ( '\n' ) . trim ( ) ;
// 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 ( ) ,
}
} ;
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , ( chat . length - 1 ) ) ;
2023-08-22 16:46:37 +02:00
addOneMessage ( message ) ;
2023-08-22 21:45:12 +02:00
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
2023-08-23 09:32:48 +02:00
await saveChatConditional ( ) ;
2023-07-20 19:32:15 +02:00
}
2023-09-17 21:00:10 +02:00
export async function sendNarratorMessage ( _ , text ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return ;
}
const name = chat _metadata [ NARRATOR _NAME _KEY ] || NARRATOR _NAME _DEFAULT ;
// 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 ( ) ,
} ,
} ;
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , ( chat . length - 1 ) ) ;
2023-08-22 16:46:37 +02:00
addOneMessage ( message ) ;
2023-08-22 21:45:12 +02:00
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
2023-08-23 09:32:48 +02:00
await saveChatConditional ( ) ;
2023-07-20 19:32:15 +02:00
}
2023-09-17 21:00:10 +02:00
export async function promptQuietForLoudResponse ( who , text ) {
let character _id = getContext ( ) . characterId ;
if ( who === 'sys' ) {
text = "System: " + text ;
} else if ( who === 'user' ) {
text = name1 + ": " + text ;
} else if ( who === 'char' ) {
text = characters [ character _id ] . name + ": " + text ;
} else if ( who === 'raw' ) {
text = text ;
}
//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 = {
name : characters [ character _id ] . name ,
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-07-20 19:32:15 +02:00
async function sendCommentMessage ( _ , text ) {
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 ( ) ,
} ,
} ;
chat . push ( message ) ;
await eventSource . emit ( event _types . MESSAGE _SENT , ( chat . length - 1 ) ) ;
2023-08-22 16:46:37 +02:00
addOneMessage ( message ) ;
2023-08-22 21:45:12 +02:00
await eventSource . emit ( event _types . USER _MESSAGE _RENDERED , ( chat . length - 1 ) ) ;
2023-08-23 09:32:48 +02:00
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-07-20 19:32:15 +02: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
function setBackgroundCallback ( _ , bg ) {
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-11-14 20:43:08 +01:00
const bgElements = Array . from ( document . querySelectorAll ( ` .bg_example ` ) ) . map ( ( x ) => ( { element : x , bgfile : x . getAttribute ( 'bgfile' ) } ) ) ;
const fuse = new Fuse ( bgElements , { keys : [ 'bgfile' ] } ) ;
const result = fuse . search ( bg ) ;
if ( ! result . length ) {
toastr . error ( ` No background found with name " ${ bg } " ` ) ;
return ;
}
const bgElement = result [ 0 ] . item . element ;
if ( bgElement instanceof HTMLElement ) {
bgElement . click ( ) ;
2023-07-20 19:32:15 +02:00
}
}
2023-11-04 12:33:09 +01:00
async function executeSlashCommands ( text ) {
2023-07-20 19:32:15 +02:00
if ( ! text ) {
return false ;
}
// Hack to allow multi-line slash commands
// All slash command messages should begin with a slash
const lines = text . split ( '|' ) . map ( line => line . trim ( ) ) ;
const linesToRemove = [ ] ;
let interrupt = false ;
for ( let index = 0 ; index < lines . length ; index ++ ) {
const trimmedLine = lines [ index ] . trim ( ) ;
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-04 12:33:09 +01:00
await result . command . callback ( result . args , result . value ) ;
2023-07-20 19:32:15 +02:00
if ( result . command . interruptsGeneration ) {
interrupt = true ;
}
if ( result . command . purgeFromMessage ) {
linesToRemove . push ( lines [ index ] ) ;
}
}
const newText = lines . filter ( x => linesToRemove . indexOf ( x ) === - 1 ) . join ( '\n' ) ;
return { interrupt , newText } ;
}
2023-11-04 00:21:20 +01:00
function setSlashCommandAutocomplete ( textarea ) {
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
. map ( x => ( { label : parser . helpStrings [ x ] , value : ` / ${ x } ` } ) ) ; // Map to the help string
output ( result ) ; // Return the results
} ,
select : ( e , u ) => {
// unfocus the input
$ ( e . target ) . val ( u . item . value ) ;
} ,
minLength : 1 ,
position : { my : "left bottom" , at : "left top" , collision : "none" } ,
} ) ;
textarea . autocomplete ( "instance" ) . _renderItem = function ( ul , item ) {
const width = $ ( textarea ) . innerWidth ( ) ;
const content = $ ( '<div></div>' ) . html ( item . label ) ;
return $ ( "<li>" ) . width ( width ) . append ( content ) . appendTo ( ul ) ;
} ;
}
jQuery ( function ( ) {
const textarea = $ ( '#send_textarea' ) ;
setSlashCommandAutocomplete ( textarea ) ;
} )