2023-07-20 19:32:15 +02:00
import {
shuffle ,
onlyUnique ,
debounce ,
delay ,
isDataURL ,
createThumbnail ,
2023-07-30 22:10:37 +02:00
extractAllWords ,
2023-08-21 20:10:11 +02:00
saveBase64AsFile ,
PAGINATION _TEMPLATE ,
2023-08-24 16:46:44 +02:00
waitUntilCondition ,
2023-10-08 10:34:39 +02:00
getBase64Async ,
2023-07-20 19:32:15 +02:00
} from './utils.js' ;
2023-08-22 17:13:03 +02:00
import { RA _CountCharTokens , humanizedDateTime , dragElement , favsToHotswap , getMessageTimeStamp } from "./RossAscends-mods.js" ;
2023-08-19 01:53:05 +02:00
import { loadMovingUIState , sortEntitiesList } from './power-user.js' ;
2023-07-20 19:32:15 +02:00
import {
chat ,
sendSystemMessage ,
printMessages ,
substituteParams ,
characters ,
default _avatar ,
addOneMessage ,
callPopup ,
clearChat ,
Generate ,
select _rm _info ,
setCharacterId ,
setCharacterName ,
setEditedMessageId ,
is _send _press ,
name1 ,
resetChatState ,
setSendButtonState ,
getCharacters ,
system _message _types ,
online _status ,
talkativeness _default ,
selectRightMenuWithAnimation ,
default _ch _mes ,
deleteLastMessage ,
showSwipeButtons ,
hideSwipeButtons ,
chat _metadata ,
updateChatMetadata ,
isStreamingEnabled ,
getThumbnailUrl ,
streamingProcessor ,
getRequestHeaders ,
setMenuType ,
menu _type ,
select _selected _character ,
cancelTtsPlay ,
displayPastChats ,
sendMessageAsUser ,
getBiasStrings ,
saveChatConditional ,
deactivateSendButtons ,
activateSendButtons ,
eventSource ,
event _types ,
getCurrentChatId ,
setScenarioOverride ,
getCropPopup ,
2023-08-19 02:13:40 +02:00
system _avatar ,
2023-08-23 20:32:38 +02:00
isChatSaving ,
2023-08-24 20:23:35 +02:00
setExternalAbortController ,
2023-10-25 23:09:22 +02:00
baseChatReplace ,
2023-10-25 23:39:11 +02:00
depth _prompt _depth _default ,
2023-11-04 22:25:22 +01:00
loadItemizedPrompts ,
2023-07-20 19:32:15 +02:00
} from "../script.js" ;
2023-08-19 20:08:35 +02:00
import { appendTagToList , createTagMapFromList , getTagsList , applyTagsOnCharacterSelect , tag _map , printTagFilters } from './tags.js' ;
import { FILTER _TYPES , FilterHelper } from './filters.js' ;
2023-07-20 19:32:15 +02:00
export {
selected _group ,
is _group _automode _enabled ,
2023-11-21 23:48:35 +01:00
hideMutedSprites ,
2023-07-20 19:32:15 +02:00
is _group _generating ,
group _generation _id ,
groups ,
saveGroupChat ,
generateGroupWrapper ,
deleteGroup ,
getGroupAvatar ,
getGroups ,
regenerateGroup ,
resetSelectedGroup ,
select _group _chats ,
2023-08-19 18:45:20 +02:00
getGroupChatNames ,
2023-07-20 19:32:15 +02:00
}
let is _group _generating = false ; // Group generation flag
let is _group _automode _enabled = false ;
2023-11-21 23:48:35 +01:00
let hideMutedSprites = true ;
2023-07-20 19:32:15 +02:00
let groups = [ ] ;
let selected _group = null ;
let group _generation _id = null ;
let fav _grp _checked = false ;
2023-08-19 01:53:05 +02:00
let openGroupId = null ;
let newGroupMembers = [ ] ;
2023-07-20 19:32:15 +02:00
export const group _activation _strategy = {
NATURAL : 0 ,
LIST : 1 ,
} ;
2023-10-25 21:39:31 +02:00
export const group _generation _mode = {
SWAP : 0 ,
APPEND : 1 ,
}
2023-08-18 22:13:15 +02:00
export const groupCandidatesFilter = new FilterHelper ( debounce ( printGroupCandidates , 100 ) ) ;
2023-07-20 19:32:15 +02:00
const groupAutoModeInterval = setInterval ( groupChatAutoModeWorker , 5000 ) ;
2023-08-19 01:53:05 +02:00
const saveGroupDebounced = debounce ( async ( group , reload ) => await _save ( group , reload ) , 500 ) ;
2023-08-18 22:13:15 +02:00
2023-07-20 19:32:15 +02:00
async function _save ( group , reload = true ) {
await fetch ( "/editgroup" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( group ) ,
} ) ;
if ( reload ) {
await getCharacters ( ) ;
}
}
// Group chats
async function regenerateGroup ( ) {
let generationId = getLastMessageGenerationId ( ) ;
while ( chat . length > 0 ) {
const lastMes = chat [ chat . length - 1 ] ;
const this _generationId = lastMes . extra ? . gen _id ;
// for new generations after the update
if ( ( generationId && this _generationId ) && generationId !== this _generationId ) {
break ;
}
// legacy for generations before the update
else if ( lastMes . is _user || lastMes . is _system ) {
break ;
}
await deleteLastMessage ( ) ;
}
2023-08-24 20:23:35 +02:00
const abortController = new AbortController ( ) ;
setExternalAbortController ( abortController ) ;
generateGroupWrapper ( false , 'normal' , { signal : abortController . signal } ) ;
2023-07-20 19:32:15 +02:00
}
async function loadGroupChat ( chatId ) {
const response = await fetch ( "/getgroupchat" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { id : chatId } ) ,
} ) ;
if ( response . ok ) {
const data = await response . json ( ) ;
return data ;
}
return [ ] ;
}
export async function getGroupChat ( groupId ) {
const group = groups . find ( ( x ) => x . id === groupId ) ;
const chat _id = group . chat _id ;
const data = await loadGroupChat ( chat _id ) ;
2023-11-04 22:25:22 +01:00
await loadItemizedPrompts ( getCurrentChatId ( ) ) ;
2023-07-20 19:32:15 +02:00
if ( Array . isArray ( data ) && data . length ) {
data [ 0 ] . is _group = true ;
for ( let key of data ) {
chat . push ( key ) ;
}
2023-09-08 11:10:41 +02:00
await printMessages ( ) ;
2023-07-20 19:32:15 +02:00
} else {
sendSystemMessage ( system _message _types . GROUP , '' , { isSmallSys : true } ) ;
if ( group && Array . isArray ( group . members ) ) {
for ( let member of group . members ) {
const character = characters . find ( x => x . avatar === member || x . name === member ) ;
if ( ! character ) {
continue ;
}
const mes = getFirstCharacterMessage ( character ) ;
chat . push ( mes ) ;
addOneMessage ( mes ) ;
}
}
2023-08-19 14:58:17 +02:00
await saveGroupChat ( groupId , false ) ;
2023-07-20 19:32:15 +02:00
}
if ( group ) {
let metadata = group . chat _metadata ? ? { } ;
updateChatMetadata ( metadata , true ) ;
}
2023-10-29 22:23:42 +01:00
await eventSource . emit ( event _types . CHAT _CHANGED , getCurrentChatId ( ) ) ;
2023-07-20 19:32:15 +02:00
}
2023-11-20 22:49:04 +01:00
/ * *
* Finds the character ID for a group member .
* @ param { string } arg 1 - based member index or character name
* @ returns { number } 0 - based character ID
* /
export function findGroupMemberId ( arg ) {
arg = arg ? . trim ( ) ;
if ( ! arg ) {
console . warn ( 'WARN: No argument provided for findGroupMemberId' ) ;
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 ;
}
// 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 ] ) ;
return 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 } ` ) ;
return chid ;
}
}
2023-10-25 23:39:11 +02:00
/ * *
* Gets depth prompts for group members .
* @ param { string } groupId Group ID
2023-10-26 01:10:14 +02:00
* @ param { number } characterId Current Character ID
2023-10-25 23:39:11 +02:00
* @ returns { { depth : number , text : string } [ ] } Array of depth prompts
* /
2023-10-26 01:10:14 +02:00
export function getGroupDepthPrompts ( groupId , characterId ) {
2023-10-25 23:39:11 +02:00
if ( ! groupId ) {
return [ ] ;
}
console . debug ( 'getGroupDepthPrompts entered for group: ' , groupId ) ;
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group || ! Array . isArray ( group . members ) || ! group . members . length ) {
return [ ] ;
}
if ( group . generation _mode === group _generation _mode . SWAP ) {
return [ ] ;
}
const depthPrompts = [ ] ;
for ( const member of group . members ) {
2023-10-26 01:10:14 +02:00
const index = characters . findIndex ( x => x . avatar === member ) ;
const character = characters [ index ] ;
if ( index === - 1 || ! character ) {
console . debug ( ` Skipping missing member: ${ member } ` ) ;
2023-10-25 23:39:11 +02:00
continue ;
}
2023-10-26 01:10:14 +02:00
if ( group . disabled _members . includes ( member ) && characterId !== index ) {
console . debug ( ` Skipping disabled group member: ${ member } ` ) ;
2023-10-25 23:39:11 +02:00
continue ;
}
const depthPromptText = baseChatReplace ( character . data ? . extensions ? . depth _prompt ? . prompt ? . trim ( ) , name1 , character . name ) || '' ;
const depthPromptDepth = character . data ? . extensions ? . depth _prompt ? . depth ? ? depth _prompt _depth _default ;
if ( depthPromptText ) {
depthPrompts . push ( { text : depthPromptText , depth : depthPromptDepth } ) ;
}
}
return depthPrompts ;
}
2023-10-25 23:09:22 +02:00
/ * *
* Combines group members info a single string . Only for groups with generation mode set to APPEND .
* @ param { string } groupId Group ID
2023-10-26 01:10:14 +02:00
* @ param { number } characterId Current Character ID
2023-11-08 15:28:55 +01:00
* @ returns { { description : string , personality : string , scenario : string , mesExamples : string } } Group character cards combined
2023-10-25 23:09:22 +02:00
* /
2023-10-26 01:10:14 +02:00
export function getGroupCharacterCards ( groupId , characterId ) {
2023-10-25 23:09:22 +02:00
console . debug ( 'getGroupCharacterCards entered for group: ' , groupId ) ;
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group || group ? . generation _mode !== group _generation _mode . APPEND || ! Array . isArray ( group . members ) || ! group . members . length ) {
return null ;
}
const scenarioOverride = chat _metadata [ 'scenario' ] ;
let descriptions = [ ] ;
let personalities = [ ] ;
let scenarios = [ ] ;
2023-11-08 15:28:55 +01:00
let mesExamplesArray = [ ] ;
2023-10-25 23:09:22 +02:00
for ( const member of group . members ) {
2023-10-26 01:10:14 +02:00
const index = characters . findIndex ( x => x . avatar === member ) ;
const character = characters [ index ] ;
if ( index === - 1 || ! character ) {
console . debug ( ` Skipping missing member: ${ member } ` ) ;
2023-10-25 23:09:22 +02:00
continue ;
}
2023-10-26 01:10:14 +02:00
if ( group . disabled _members . includes ( member ) && characterId !== index ) {
console . debug ( ` Skipping disabled group member: ${ member } ` ) ;
2023-10-25 23:09:22 +02:00
continue ;
}
descriptions . push ( baseChatReplace ( character . description . trim ( ) , name1 , character . name ) ) ;
personalities . push ( baseChatReplace ( character . personality . trim ( ) , name1 , character . name ) ) ;
scenarios . push ( baseChatReplace ( character . scenario . trim ( ) , name1 , character . name ) ) ;
2023-11-08 15:28:55 +01:00
mesExamplesArray . push ( baseChatReplace ( character . mes _example . trim ( ) , name1 , character . name ) ) ;
2023-10-25 23:09:22 +02:00
}
const description = descriptions . join ( '\n' ) ;
const personality = personalities . join ( '\n' ) ;
const scenario = scenarioOverride ? . trim ( ) || scenarios . join ( '\n' ) ;
2023-11-08 15:28:55 +01:00
const mesExamples = mesExamplesArray . join ( '\n' ) ;
2023-10-25 23:09:22 +02:00
2023-11-08 15:28:55 +01:00
return { description , personality , scenario , mesExamples } ;
2023-10-25 23:09:22 +02:00
}
2023-07-20 19:32:15 +02:00
function getFirstCharacterMessage ( character ) {
let messageText = character . first _mes ;
// if there are alternate greetings, pick one at random
if ( Array . isArray ( character . data ? . alternate _greetings ) ) {
const messageTexts = [ character . first _mes , ... character . data . alternate _greetings ] . filter ( x => x ) ;
messageText = messageTexts [ Math . floor ( Math . random ( ) * messageTexts . length ) ] ;
}
const mes = { } ;
mes [ "is_user" ] = false ;
mes [ "is_system" ] = false ;
mes [ "name" ] = character . name ;
2023-08-22 17:13:03 +02:00
mes [ "send_date" ] = getMessageTimeStamp ( ) ;
2023-07-20 19:32:15 +02:00
mes [ "original_avatar" ] = character . avatar ;
mes [ "extra" ] = { "gen_id" : Date . now ( ) * Math . random ( ) * 1000000 } ;
mes [ "mes" ] = messageText
? substituteParams ( messageText . trim ( ) , name1 , character . name )
: default _ch _mes ;
mes [ "force_avatar" ] =
character . avatar != "none"
? getThumbnailUrl ( 'avatar' , character . avatar )
: default _avatar ;
return mes ;
}
function resetSelectedGroup ( ) {
selected _group = null ;
is _group _generating = false ;
}
async function saveGroupChat ( groupId , shouldSaveGroup ) {
const group = groups . find ( x => x . id == groupId ) ;
const chat _id = group . chat _id ;
group [ 'date_last_chat' ] = Date . now ( ) ;
const response = await fetch ( "/savegroupchat" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { id : chat _id , chat : [ ... chat ] } ) ,
} ) ;
if ( shouldSaveGroup && response . ok ) {
2023-08-19 01:53:05 +02:00
await editGroup ( groupId , false , false ) ;
2023-07-20 19:32:15 +02:00
}
}
export async function renameGroupMember ( oldAvatar , newAvatar , newName ) {
// Scan every group for our renamed character
for ( const group of groups ) {
try {
// Try finding the member by old avatar link
const memberIndex = group . members . findIndex ( x => x == oldAvatar ) ;
// Character was not present in the group...
if ( memberIndex == - 1 ) {
continue ;
}
// Replace group member avatar id and save the changes
group . members [ memberIndex ] = newAvatar ;
2023-08-19 01:53:05 +02:00
await editGroup ( group . id , true , false ) ;
2023-07-20 19:32:15 +02:00
console . log ( ` Renamed character ${ newName } in group: ${ group . name } ` )
// Load all chats from this group
for ( const chatId of group . chats ) {
const messages = await loadGroupChat ( chatId ) ;
// Only save the chat if there were any changes to the chat content
let hadChanges = false ;
// Chat shouldn't be empty
if ( Array . isArray ( messages ) && messages . length ) {
// Iterate over every chat message
for ( const message of messages ) {
// Only look at character messages
if ( message . is _user || message . is _system ) {
continue ;
}
// Message belonged to the old-named character:
// Update name, avatar thumbnail URL and original avatar link
if ( message . force _avatar && message . force _avatar . indexOf ( encodeURIComponent ( oldAvatar ) ) !== - 1 ) {
message . name = newName ;
message . force _avatar = message . force _avatar . replace ( encodeURIComponent ( oldAvatar ) , encodeURIComponent ( newAvatar ) ) ;
message . original _avatar = newAvatar ;
hadChanges = true ;
}
}
if ( hadChanges ) {
const saveChatResponse = await fetch ( "/savegroupchat" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { id : chatId , chat : [ ... messages ] } ) ,
} ) ;
if ( saveChatResponse . ok ) {
console . log ( ` Renamed character ${ newName } in group chat: ${ chatId } ` ) ;
}
}
}
}
}
catch ( error ) {
console . log ( ` An error during renaming the character ${ newName } in group: ${ group . name } ` ) ;
console . error ( error ) ;
}
}
}
async function getGroups ( ) {
const response = await fetch ( "/getgroups" , {
method : "POST" ,
headers : getRequestHeaders ( )
} ) ;
if ( response . ok ) {
const data = await response . json ( ) ;
groups = data . sort ( ( a , b ) => a . id - b . id ) ;
// Convert groups to new format
for ( const group of groups ) {
2023-08-19 21:22:24 +02:00
if ( typeof group . id === 'number' ) {
group . id = String ( group . id ) ;
}
2023-07-20 19:32:15 +02:00
if ( group . disabled _members == undefined ) {
group . disabled _members = [ ] ;
}
if ( group . chat _id == undefined ) {
group . chat _id = group . id ;
group . chats = [ group . id ] ;
group . members = group . members
. map ( x => characters . find ( y => y . name == x ) ? . avatar )
. filter ( x => x )
. filter ( onlyUnique )
}
if ( group . past _metadata == undefined ) {
group . past _metadata = { } ;
}
if ( typeof group . chat _id === 'number' ) {
group . chat _id = String ( group . chat _id ) ;
}
if ( Array . isArray ( group . chats ) && group . chats . some ( x => typeof x === 'number' ) ) {
group . chats = group . chats . map ( x => String ( x ) ) ;
}
}
}
}
2023-08-18 22:13:15 +02:00
export function getGroupBlock ( group ) {
2023-08-20 11:37:38 +02:00
const template = $ ( "#group_list_template .group_select" ) . clone ( ) ;
template . data ( "id" , group . id ) ;
template . attr ( "grid" , group . id ) ;
2023-08-20 17:47:43 +02:00
template . find ( ".ch_name" ) . text ( group . name ) ;
2023-08-20 11:37:38 +02:00
template . find ( '.group_fav_icon' ) . css ( "display" , 'none' ) ;
template . addClass ( group . fav ? 'is_fav' : '' ) ;
template . find ( ".ch_fav" ) . val ( group . fav ) ;
2023-08-18 22:13:15 +02:00
2023-08-20 11:37:38 +02:00
// Display inline tags
const tags = getTagsList ( group . id ) ;
const tagsElement = template . find ( '.tags' ) ;
tags . forEach ( tag => appendTagToList ( tagsElement , tag , { } ) ) ;
const avatar = getGroupAvatar ( group ) ;
if ( avatar ) {
$ ( template ) . find ( ".avatar" ) . replaceWith ( avatar ) ;
}
2023-08-18 22:13:15 +02:00
2023-08-20 11:37:38 +02:00
return template ;
2023-07-20 19:32:15 +02:00
}
2023-08-18 22:13:15 +02:00
2023-07-20 19:32:15 +02:00
function updateGroupAvatar ( group ) {
2023-08-19 02:13:40 +02:00
$ ( "#group_avatar_preview" ) . empty ( ) . append ( getGroupAvatar ( group ) ) ;
$ ( ".group_select" ) . each ( function ( ) {
2023-07-20 19:32:15 +02:00
if ( $ ( this ) . data ( "id" ) == group . id ) {
2023-08-20 11:37:38 +02:00
$ ( this ) . find ( ".avatar" ) . replaceWith ( getGroupAvatar ( group ) ) ;
2023-07-20 19:32:15 +02:00
}
} ) ;
}
2023-08-20 05:53:34 +02:00
// check if isDataURLor if it's a valid local file url
function isValidImageUrl ( url ) {
// check if empty dict
if ( Object . keys ( url ) . length === 0 ) {
return false ;
}
return isDataURL ( url ) || ( url && url . startsWith ( "user" ) ) ;
}
2023-07-20 19:32:15 +02:00
function getGroupAvatar ( group ) {
if ( ! group ) {
return $ ( ` <div class="avatar"><img src=" ${ default _avatar } "></div> ` ) ;
}
2023-08-20 11:37:38 +02:00
// if isDataURL or if it's a valid local file url
2023-08-20 05:53:34 +02:00
if ( isValidImageUrl ( group . avatar _url ) ) {
2023-07-20 19:32:15 +02:00
return $ ( ` <div class="avatar"><img src=" ${ group . avatar _url } "></div> ` ) ;
}
const memberAvatars = [ ] ;
if ( group && Array . isArray ( group . members ) && group . members . length ) {
for ( const member of group . members ) {
const charIndex = characters . findIndex ( x => x . avatar === member ) ;
if ( charIndex !== - 1 && characters [ charIndex ] . avatar !== "none" ) {
const avatar = getThumbnailUrl ( 'avatar' , characters [ charIndex ] . avatar ) ;
memberAvatars . push ( avatar ) ;
}
if ( memberAvatars . length === 4 ) {
break ;
}
}
}
const avatarCount = memberAvatars . length ;
if ( avatarCount >= 1 && avatarCount <= 4 ) {
const groupAvatar = $ ( ` #group_avatars_template .collage_ ${ avatarCount } ` ) . clone ( ) ;
for ( let i = 0 ; i < avatarCount ; i ++ ) {
groupAvatar . find ( ` .img_ ${ i + 1 } ` ) . attr ( "src" , memberAvatars [ i ] ) ;
}
return groupAvatar ;
}
// default avatar
const groupAvatar = $ ( "#group_avatars_template .collage_1" ) . clone ( ) ;
2023-08-19 02:13:40 +02:00
groupAvatar . find ( ".img_1" ) . attr ( "src" , group . avatar _url || system _avatar ) ;
2023-07-20 19:32:15 +02:00
return groupAvatar ;
}
2023-08-19 18:45:20 +02:00
function getGroupChatNames ( groupId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group ) {
return [ ] ;
}
const names = [ ] ;
for ( const chatId of group . chats ) {
names . push ( chatId ) ;
}
return names ;
}
2023-07-20 19:32:15 +02:00
async function generateGroupWrapper ( by _auto _mode , type = null , params = { } ) {
if ( online _status === "no_connection" ) {
is _group _generating = false ;
setSendButtonState ( false ) ;
return ;
}
if ( is _group _generating ) {
return false ;
}
// Auto-navigate back to group menu
if ( menu _type !== "group_edit" ) {
select _group _chats ( selected _group ) ;
await delay ( 1 ) ;
}
const group = groups . find ( ( x ) => x . id === selected _group ) ;
let typingIndicator = $ ( "#chat .typing_indicator" ) ;
if ( ! group || ! Array . isArray ( group . members ) || ! group . members . length ) {
sendSystemMessage ( system _message _types . EMPTY , '' , { isSmallSys : true } ) ;
return ;
}
try {
hideSwipeButtons ( ) ;
is _group _generating = true ;
setCharacterName ( '' ) ;
setCharacterId ( undefined ) ;
2023-08-22 13:30:49 +02:00
const userInput = String ( $ ( "#send_textarea" ) . val ( ) ) ;
2023-07-20 19:32:15 +02:00
if ( typingIndicator . length === 0 && ! isStreamingEnabled ( ) ) {
typingIndicator = $ (
"#typing_indicator_template .typing_indicator"
) . clone ( ) ;
typingIndicator . hide ( ) ;
$ ( "#chat" ) . append ( typingIndicator ) ;
}
// id of this specific batch for regeneration purposes
group _generation _id = Date . now ( ) ;
const lastMessage = chat [ chat . length - 1 ] ;
let messagesBefore = chat . length ;
let lastMessageText = lastMessage ? . mes || '' ;
let activationText = "" ;
let isUserInput = false ;
let isGenerationDone = false ;
let isGenerationAborted = false ;
if ( userInput ? . length && ! by _auto _mode ) {
isUserInput = true ;
activationText = userInput ;
messagesBefore ++ ;
} else {
if ( lastMessage && ! lastMessage . is _system ) {
activationText = lastMessage . mes ;
}
}
const resolveOriginal = params . resolve ;
const rejectOriginal = params . reject ;
if ( params . signal instanceof AbortSignal ) {
if ( params . signal . aborted ) {
throw new Error ( 'Already aborted signal passed. Group generation stopped' ) ;
}
params . signal . onabort = ( ) => {
isGenerationAborted = true ;
} ;
}
if ( typeof params . resolve === 'function' ) {
params . resolve = function ( ) {
isGenerationDone = true ;
resolveOriginal . apply ( this , arguments ) ;
} ;
}
if ( typeof params . reject === 'function' ) {
params . reject = function ( ) {
isGenerationDone = true ;
rejectOriginal . apply ( this , arguments ) ;
}
}
const activationStrategy = Number ( group . activation _strategy ? ? group _activation _strategy . NATURAL ) ;
const enabledMembers = group . members . filter ( x => ! group . disabled _members . includes ( x ) ) ;
let activatedMembers = [ ] ;
if ( params && typeof params . force _chid == 'number' ) {
activatedMembers = [ params . force _chid ] ;
} else if ( type === "quiet" ) {
activatedMembers = activateSwipe ( group . members ) ;
if ( activatedMembers . length === 0 ) {
activatedMembers = activateListOrder ( group . members . slice ( 0 , 1 ) ) ;
}
}
else if ( type === "swipe" || type === 'continue' ) {
activatedMembers = activateSwipe ( group . members ) ;
if ( activatedMembers . length === 0 ) {
toastr . warning ( 'Deleted group member swiped. To get a reply, add them back to the group.' ) ;
throw new Error ( 'Deleted group member swiped' ) ;
}
}
else if ( type === "impersonate" ) {
$ ( "#send_textarea" ) . attr ( "disabled" , true ) ;
activatedMembers = activateImpersonate ( group . members ) ;
}
else if ( activationStrategy === group _activation _strategy . NATURAL ) {
activatedMembers = activateNaturalOrder ( enabledMembers , activationText , lastMessage , group . allow _self _responses , isUserInput ) ;
}
else if ( activationStrategy === group _activation _strategy . LIST ) {
activatedMembers = activateListOrder ( enabledMembers ) ;
}
if ( activatedMembers . length === 0 ) {
2023-09-02 16:28:03 +02:00
//toastr.warning('All group members are disabled. Enable at least one to get a reply.');
2023-07-20 19:32:15 +02:00
// Send user message as is
const bias = getBiasStrings ( userInput , type ) ;
await sendMessageAsUser ( userInput , bias . messageBias ) ;
await saveChatConditional ( ) ;
$ ( '#send_textarea' ) . val ( '' ) . trigger ( 'input' ) ;
}
// now the real generation begins: cycle through every activated character
for ( const chId of activatedMembers ) {
deactivateSendButtons ( ) ;
isGenerationDone = false ;
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" || type == 'continue' ? type : "group_chat" ;
setCharacterId ( chId ) ;
setCharacterName ( characters [ chId ] . name )
await Generate ( generateType , { automatic _trigger : by _auto _mode , ... ( params || { } ) } ) ;
2023-09-15 20:34:41 +02:00
if ( type !== "swipe" && type !== "impersonate" && ! isStreamingEnabled ( ) ) {
2023-07-20 19:32:15 +02:00
// update indicator and scroll down
typingIndicator
. find ( ".typing_indicator_name" )
. text ( characters [ chId ] . name ) ;
2023-09-08 15:36:00 +02:00
typingIndicator . show ( ) ;
2023-07-20 19:32:15 +02:00
}
// TODO: This is awful. Refactor this
while ( true ) {
deactivateSendButtons ( ) ;
if ( isGenerationAborted ) {
throw new Error ( 'Group generation aborted' ) ;
}
// if not swipe - check if message generated already
2023-09-15 20:34:41 +02:00
if ( generateType === "group_chat" && chat . length == messagesBefore ) {
2023-07-20 19:32:15 +02:00
await delay ( 100 ) ;
}
// if swipe - see if message changed
else if ( type === "swipe" ) {
if ( isStreamingEnabled ( ) ) {
if ( streamingProcessor && ! streamingProcessor . isFinished ) {
await delay ( 100 ) ;
}
else {
break ;
}
}
else {
if ( lastMessageText === chat [ chat . length - 1 ] . mes ) {
await delay ( 100 ) ;
}
else {
break ;
}
}
}
else if ( type === "impersonate" ) {
if ( isStreamingEnabled ( ) ) {
if ( streamingProcessor && ! streamingProcessor . isFinished ) {
await delay ( 100 ) ;
}
else {
break ;
}
}
else {
if ( ! $ ( "#send_textarea" ) . val ( ) || $ ( "#send_textarea" ) . val ( ) == userInput ) {
await delay ( 100 ) ;
}
else {
break ;
}
}
}
else if ( type === 'quiet' ) {
if ( isGenerationDone ) {
break ;
} else {
await delay ( 100 ) ;
}
}
else if ( isStreamingEnabled ( ) ) {
if ( streamingProcessor && ! streamingProcessor . isFinished ) {
await delay ( 100 ) ;
} else {
2023-08-24 16:46:44 +02:00
await waitUntilCondition ( ( ) => streamingProcessor == null , 1000 , 10 ) ;
2023-07-20 19:32:15 +02:00
messagesBefore ++ ;
break ;
}
}
else {
messagesBefore ++ ;
break ;
}
}
}
} finally {
2023-09-08 15:36:00 +02:00
typingIndicator . hide ( ) ;
2023-07-20 19:32:15 +02:00
is _group _generating = false ;
$ ( "#send_textarea" ) . attr ( "disabled" , false ) ;
setSendButtonState ( false ) ;
setCharacterId ( undefined ) ;
setCharacterName ( '' ) ;
activateSendButtons ( ) ;
showSwipeButtons ( ) ;
}
}
function getLastMessageGenerationId ( ) {
let generationId = null ;
if ( chat . length > 0 ) {
const lastMes = chat [ chat . length - 1 ] ;
if ( ! lastMes . is _user && ! lastMes . is _system && lastMes . extra ) {
generationId = lastMes . extra . gen _id ;
}
}
return generationId ;
}
function activateImpersonate ( members ) {
const randomIndex = Math . floor ( Math . random ( ) * members . length ) ;
const activatedMembers = [ members [ randomIndex ] ] ;
const memberIds = activatedMembers
. map ( ( x ) => characters . findIndex ( ( y ) => y . avatar === x ) )
. filter ( ( x ) => x !== - 1 ) ;
return memberIds ;
}
function activateSwipe ( members ) {
let activatedNames = [ ] ;
// pre-update group chat swipe
if ( ! chat [ chat . length - 1 ] . original _avatar ) {
const matches = characters . filter ( x => x . name == chat [ chat . length - 1 ] . name ) ;
for ( const match of matches ) {
if ( members . includes ( match . avatar ) ) {
activatedNames . push ( match . avatar ) ;
break ;
}
}
}
else {
activatedNames . push ( chat [ chat . length - 1 ] . original _avatar ) ;
}
const memberIds = activatedNames
. map ( ( x ) => characters . findIndex ( ( y ) => y . avatar === x ) )
. filter ( ( x ) => x !== - 1 ) ;
return memberIds ;
}
function activateListOrder ( members ) {
let activatedMembers = members . filter ( onlyUnique ) ;
// map to character ids
const memberIds = activatedMembers
. map ( ( x ) => characters . findIndex ( ( y ) => y . avatar === x ) )
. filter ( ( x ) => x !== - 1 ) ;
return memberIds ;
}
function activateNaturalOrder ( members , input , lastMessage , allowSelfResponses , isUserInput ) {
let activatedMembers = [ ] ;
// prevents the same character from speaking twice
let bannedUser = ! isUserInput && lastMessage && ! lastMessage . is _user && lastMessage . name ;
// ...unless allowed to do so
if ( allowSelfResponses ) {
bannedUser = undefined ;
}
// find mentions (excluding self)
if ( input && input . length ) {
for ( let inputWord of extractAllWords ( input ) ) {
for ( let member of members ) {
const character = characters . find ( x => x . avatar === member )
if ( ! character || character . name === bannedUser ) {
continue ;
}
if ( extractAllWords ( character . name ) . includes ( inputWord ) ) {
activatedMembers . push ( member ) ;
break ;
}
}
}
}
// activation by talkativeness (in shuffled order, except banned)
const shuffledMembers = shuffle ( [ ... members ] ) ;
for ( let member of shuffledMembers ) {
const character = characters . find ( ( x ) => x . avatar === member ) ;
if ( ! character || character . name === bannedUser ) {
continue ;
}
const rollValue = Math . random ( ) ;
let talkativeness = Number ( character . talkativeness ) ;
talkativeness = Number . isNaN ( talkativeness )
? talkativeness _default
: talkativeness ;
if ( talkativeness >= rollValue ) {
activatedMembers . push ( member ) ;
}
}
// pick 1 at random if no one was activated
let retries = 0 ;
while ( activatedMembers . length === 0 && ++ retries <= members . length ) {
const randomIndex = Math . floor ( Math . random ( ) * members . length ) ;
const character = characters . find ( ( x ) => x . avatar === members [ randomIndex ] ) ;
if ( ! character ) {
continue ;
}
activatedMembers . push ( members [ randomIndex ] ) ;
}
// de-duplicate array of character avatars
activatedMembers = activatedMembers . filter ( onlyUnique ) ;
// map to character ids
const memberIds = activatedMembers
. map ( ( x ) => characters . findIndex ( ( y ) => y . avatar === x ) )
. filter ( ( x ) => x !== - 1 ) ;
return memberIds ;
}
async function deleteGroup ( id ) {
2023-09-09 21:15:47 +02:00
const group = groups . find ( ( x ) => x . id === id ) ;
2023-07-20 19:32:15 +02:00
const response = await fetch ( "/deletegroup" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { id : id } ) ,
} ) ;
2023-09-09 21:15:47 +02:00
if ( group && Array . isArray ( group . chats ) ) {
for ( const chatId of group . chats ) {
await eventSource . emit ( event _types . GROUP _CHAT _DELETED , chatId ) ;
}
}
2023-07-20 19:32:15 +02:00
if ( response . ok ) {
2023-11-04 22:25:22 +01:00
await clearChat ( ) ;
2023-07-20 19:32:15 +02:00
selected _group = null ;
delete tag _map [ id ] ;
resetChatState ( ) ;
2023-09-08 11:10:41 +02:00
await printMessages ( ) ;
2023-07-20 19:32:15 +02:00
await getCharacters ( ) ;
select _rm _info ( "group_delete" , id ) ;
$ ( "#rm_button_selected_ch" ) . children ( "h2" ) . text ( '' ) ;
}
}
export async function editGroup ( id , immediately , reload = true ) {
let group = groups . find ( ( x ) => x . id === id ) ;
if ( ! group ) {
return ;
}
group [ 'chat_metadata' ] = chat _metadata ;
if ( immediately ) {
return await _save ( group , reload ) ;
}
2023-08-19 01:53:05 +02:00
saveGroupDebounced ( group , reload ) ;
2023-07-20 19:32:15 +02:00
}
let groupAutoModeAbortController = null ;
async function groupChatAutoModeWorker ( ) {
if ( ! is _group _automode _enabled || online _status === "no_connection" ) {
return ;
}
if ( ! selected _group || is _send _press || is _group _generating ) {
return ;
}
const group = groups . find ( ( x ) => x . id === selected _group ) ;
if ( ! group || ! Array . isArray ( group . members ) || ! group . members . length ) {
return ;
}
groupAutoModeAbortController = new AbortController ( ) ;
await generateGroupWrapper ( true , 'auto' , { signal : groupAutoModeAbortController . signal } ) ;
}
async function modifyGroupMember ( chat _id , groupMember , isDelete ) {
const id = groupMember . data ( "id" ) ;
2023-08-19 01:53:05 +02:00
const thisGroup = groups . find ( ( x ) => x . id == chat _id ) ;
const membersArray = thisGroup ? . members ? ? newGroupMembers ;
2023-07-20 19:32:15 +02:00
if ( isDelete ) {
2023-08-19 01:53:05 +02:00
const index = membersArray . findIndex ( ( x ) => x === id ) ;
if ( index !== - 1 ) {
membersArray . splice ( membersArray . indexOf ( id ) , 1 ) ;
}
2023-07-20 19:32:15 +02:00
} else {
2023-08-19 01:53:05 +02:00
membersArray . unshift ( id ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-19 01:53:05 +02:00
if ( openGroupId ) {
await editGroup ( openGroupId , false , false ) ;
updateGroupAvatar ( thisGroup ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-19 01:53:05 +02:00
printGroupCandidates ( ) ;
printGroupMembers ( ) ;
const groupHasMembers = getGroupCharacters ( { doFilter : false , onlyMembers : true } ) . length > 0 ;
2023-07-20 19:32:15 +02:00
$ ( "#rm_group_submit" ) . prop ( "disabled" , ! groupHasMembers ) ;
}
async function reorderGroupMember ( chat _id , groupMember , direction ) {
const id = groupMember . data ( "id" ) ;
2023-08-19 02:13:40 +02:00
const thisGroup = groups . find ( ( x ) => x . id == chat _id ) ;
const memberArray = thisGroup ? . members ? ? newGroupMembers ;
2023-08-19 01:53:05 +02:00
const indexOf = memberArray . indexOf ( id ) ;
if ( direction == 'down' ) {
const next = memberArray [ indexOf + 1 ] ;
if ( next ) {
memberArray [ indexOf + 1 ] = memberArray [ indexOf ] ;
memberArray [ indexOf ] = next ;
}
}
if ( direction == 'up' ) {
const prev = memberArray [ indexOf - 1 ] ;
if ( prev ) {
memberArray [ indexOf - 1 ] = memberArray [ indexOf ] ;
memberArray [ indexOf ] = prev ;
}
}
printGroupMembers ( ) ;
2023-07-20 19:32:15 +02:00
// Existing groups need to modify members list
2023-08-19 01:53:05 +02:00
if ( openGroupId ) {
await editGroup ( chat _id , false , false ) ;
2023-08-19 02:13:40 +02:00
updateGroupAvatar ( thisGroup ) ;
2023-08-19 01:53:05 +02:00
}
}
async function onGroupActivationStrategyInput ( e ) {
if ( openGroupId ) {
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
_thisGroup . activation _strategy = Number ( e . target . value ) ;
await editGroup ( openGroupId , false , false ) ;
}
}
2023-10-25 21:39:31 +02:00
async function onGroupGenerationModeInput ( e ) {
if ( openGroupId ) {
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
_thisGroup . generation _mode = Number ( e . target . value ) ;
await editGroup ( openGroupId , false , false ) ;
}
}
2023-08-19 01:53:05 +02:00
async function onGroupNameInput ( ) {
if ( openGroupId ) {
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
_thisGroup . name = $ ( this ) . val ( ) ;
$ ( "#rm_button_selected_ch" ) . children ( "h2" ) . text ( _thisGroup . name ) ;
await editGroup ( openGroupId ) ;
}
}
function isGroupMember ( group , avatarId ) {
if ( group && Array . isArray ( group . members ) ) {
return group . members . includes ( avatarId ) ;
} else {
return newGroupMembers . includes ( avatarId ) ;
}
}
function getGroupCharacters ( { doFilter , onlyMembers } = { } ) {
function sortMembersFn ( a , b ) {
const membersArray = thisGroup ? . members ? ? newGroupMembers ;
const aIndex = membersArray . indexOf ( a . item . avatar ) ;
const bIndex = membersArray . indexOf ( b . item . avatar ) ;
return aIndex - bIndex ;
}
const thisGroup = openGroupId && groups . find ( ( x ) => x . id == openGroupId ) ;
2023-08-19 02:22:10 +02:00
let candidates = characters
. filter ( ( x ) => isGroupMember ( thisGroup , x . avatar ) == onlyMembers )
. map ( ( x , index ) => ( { item : x , id : index , type : 'character' } ) ) ;
2023-08-19 01:53:05 +02:00
if ( onlyMembers ) {
candidates . sort ( sortMembersFn ) ;
} else {
sortEntitiesList ( candidates ) ;
}
if ( doFilter ) {
candidates = groupCandidatesFilter . applyFilters ( candidates ) ;
}
return candidates ;
}
function printGroupCandidates ( ) {
2023-08-19 15:11:09 +02:00
const storageKey = 'GroupCandidates_PerPage' ;
2023-08-19 01:53:05 +02:00
$ ( "#rm_group_add_members_pagination" ) . pagination ( {
dataSource : getGroupCharacters ( { doFilter : true , onlyMembers : false } ) ,
pageRange : 1 ,
position : 'top' ,
showPageNumbers : false ,
prevText : '<' ,
nextText : '>' ,
2023-08-21 20:10:11 +02:00
formatNavigator : PAGINATION _TEMPLATE ,
2023-08-19 01:53:05 +02:00
showNavigator : true ,
2023-08-19 15:11:09 +02:00
showSizeChanger : true ,
pageSize : Number ( localStorage . getItem ( storageKey ) ) || 5 ,
sizeChangerOptions : [ 5 , 10 , 25 , 50 , 100 , 200 ] ,
afterSizeSelectorChange : function ( e ) {
localStorage . setItem ( storageKey , e . target . value ) ;
} ,
2023-08-19 01:53:05 +02:00
callback : function ( data ) {
$ ( "#rm_group_add_members" ) . empty ( ) ;
for ( const i of data ) {
$ ( "#rm_group_add_members" ) . append ( getGroupCharacterBlock ( i . item ) ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-19 01:53:05 +02:00
} ,
} ) ;
}
function printGroupMembers ( ) {
2023-08-19 15:11:09 +02:00
const storageKey = 'GroupMembers_PerPage' ;
2023-11-21 23:48:35 +01:00
$ ( ".rm_group_members_pagination" ) . each ( function ( ) {
2023-10-15 18:42:23 +02:00
$ ( this ) . pagination ( {
dataSource : getGroupCharacters ( { doFilter : false , onlyMembers : true } ) ,
pageRange : 1 ,
position : 'top' ,
showPageNumbers : false ,
prevText : '<' ,
nextText : '>' ,
formatNavigator : PAGINATION _TEMPLATE ,
showNavigator : true ,
showSizeChanger : true ,
pageSize : Number ( localStorage . getItem ( storageKey ) ) || 5 ,
sizeChangerOptions : [ 5 , 10 , 25 , 50 , 100 , 200 ] ,
afterSizeSelectorChange : function ( e ) {
localStorage . setItem ( storageKey , e . target . value ) ;
} ,
callback : function ( data ) {
$ ( ".rm_group_members" ) . empty ( ) ;
for ( const i of data ) {
$ ( ".rm_group_members" ) . append ( getGroupCharacterBlock ( i . item ) ) ;
}
} ,
} ) ;
2023-08-19 01:53:05 +02:00
} ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
function getGroupCharacterBlock ( character ) {
const avatar = getThumbnailUrl ( 'avatar' , character . avatar ) ;
const template = $ ( "#group_member_template .group_member" ) . clone ( ) ;
const isFav = character . fav || character . fav == 'true' ;
template . data ( "id" , character . avatar ) ;
template . find ( ".avatar img" ) . attr ( { "src" : avatar , "title" : character . avatar } ) ;
2023-08-20 18:47:14 +02:00
template . find ( ".ch_name" ) . text ( character . name ) ;
2023-08-19 01:53:05 +02:00
template . attr ( "chid" , characters . indexOf ( character ) ) ;
template . find ( '.ch_fav' ) . val ( isFav ) ;
template . toggleClass ( 'is_fav' , isFav ) ;
template . toggleClass ( 'disabled' , isGroupMemberDisabled ( character . avatar ) ) ;
// Display inline tags
const tags = getTagsList ( character . avatar ) ;
const tagsElement = template . find ( '.tags' ) ;
tags . forEach ( tag => appendTagToList ( tagsElement , tag , { } ) ) ;
if ( ! openGroupId ) {
template . find ( '[data-action="speak"]' ) . hide ( ) ;
template . find ( '[data-action="enable"]' ) . hide ( ) ;
template . find ( '[data-action="disable"]' ) . hide ( ) ;
}
return template ;
}
function isGroupMemberDisabled ( avatarId ) {
const thisGroup = openGroupId && groups . find ( ( x ) => x . id == openGroupId ) ;
return Boolean ( thisGroup && thisGroup . disabled _members . includes ( avatarId ) ) ;
}
function onDeleteGroupClick ( ) {
if ( is _group _generating ) {
toastr . warning ( 'Not so fast! Wait for the characters to stop typing before deleting the group.' ) ;
return ;
2023-07-20 19:32:15 +02:00
}
2023-08-19 01:53:05 +02:00
$ ( "#dialogue_popup" ) . data ( "group_id" , openGroupId ) ;
callPopup ( '<h3>Delete the group?</h3><p>This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.</p>' , "del_group" ) ;
}
async function onFavoriteGroupClick ( ) {
updateFavButtonState ( ! fav _grp _checked ) ;
if ( openGroupId ) {
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
_thisGroup . fav = fav _grp _checked ;
await editGroup ( openGroupId , false , false ) ;
favsToHotswap ( ) ;
}
}
async function onGroupSelfResponsesClick ( ) {
if ( openGroupId ) {
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
const value = $ ( this ) . prop ( "checked" ) ;
_thisGroup . allow _self _responses = value ;
await editGroup ( openGroupId , false , false ) ;
2023-07-20 19:32:15 +02:00
}
}
2023-11-21 23:48:35 +01:00
async function onHideMutedSpritesClick ( value ) {
if ( openGroupId ) {
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
_thisGroup . hideMutedSprites = value ;
console . log ( ` _thisGroup.hideMutedSprites = ${ _thisGroup . hideMutedSprites } ` )
await editGroup ( openGroupId , false , false ) ;
}
}
2023-07-20 19:32:15 +02:00
function select _group _chats ( groupId , skipAnimation ) {
2023-08-19 01:53:05 +02:00
openGroupId = groupId ;
newGroupMembers = [ ] ;
const group = openGroupId && groups . find ( ( x ) => x . id == openGroupId ) ;
2023-07-20 19:32:15 +02:00
const groupName = group ? . name ? ? "" ;
2023-08-19 01:53:05 +02:00
const replyStrategy = Number ( group ? . activation _strategy ? ? group _activation _strategy . NATURAL ) ;
2023-10-25 23:09:22 +02:00
const generationMode = Number ( group ? . generation _mode ? ? group _generation _mode . SWAP ) ;
2023-08-19 01:53:05 +02:00
2023-07-20 19:32:15 +02:00
setMenuType ( ! ! group ? 'group_edit' : 'group_create' ) ;
$ ( "#group_avatar_preview" ) . empty ( ) . append ( getGroupAvatar ( group ) ) ;
2023-08-20 05:53:34 +02:00
$ ( "#rm_group_restore_avatar" ) . toggle ( ! ! group && isValidImageUrl ( group . avatar _url ) ) ;
2023-07-20 19:32:15 +02:00
$ ( "#rm_group_filter" ) . val ( "" ) . trigger ( "input" ) ;
2023-10-25 21:39:31 +02:00
$ ( "#rm_group_activation_strategy" ) . val ( replyStrategy ) ;
$ ( ` #rm_group_activation_strategy option[value=" ${ replyStrategy } "] ` ) . prop ( 'selected' , true ) ;
$ ( "#rm_group_generation_mode" ) . val ( generationMode ) ;
$ ( ` #rm_group_generation_mode option[value=" ${ generationMode } "] ` ) . prop ( 'selected' , true ) ;
2023-09-08 15:36:00 +02:00
$ ( "#rm_group_chat_name" ) . val ( groupName ) ;
2023-07-20 19:32:15 +02:00
if ( ! skipAnimation ) {
selectRightMenuWithAnimation ( 'rm_group_chats_block' ) ;
}
// render characters list
2023-08-19 01:53:05 +02:00
printGroupCandidates ( ) ;
printGroupMembers ( ) ;
2023-07-20 19:32:15 +02:00
const groupHasMembers = ! ! $ ( "#rm_group_members" ) . children ( ) . length ;
$ ( "#rm_group_submit" ) . prop ( "disabled" , ! groupHasMembers ) ;
$ ( "#rm_group_allow_self_responses" ) . prop ( "checked" , group && group . allow _self _responses ) ;
2023-11-21 23:48:35 +01:00
$ ( "#rm_group_hidemutedsprites" ) . prop ( "checked" , group && group . hideMutedSprites ) ;
2023-07-20 19:32:15 +02:00
// bottom buttons
2023-08-19 01:53:05 +02:00
if ( openGroupId ) {
2023-07-20 19:32:15 +02:00
$ ( "#rm_group_submit" ) . hide ( ) ;
$ ( "#rm_group_delete" ) . show ( ) ;
$ ( "#rm_group_scenario" ) . show ( ) ;
2023-10-16 22:03:42 +02:00
$ ( '#group-metadata-controls .chat_lorebook_button' ) . removeClass ( 'disabled' ) . prop ( 'disabled' , false ) ;
2023-07-20 19:32:15 +02:00
} else {
$ ( "#rm_group_submit" ) . show ( ) ;
if ( $ ( "#groupAddMemberListToggle .inline-drawer-content" ) . css ( 'display' ) !== 'block' ) {
$ ( "#groupAddMemberListToggle" ) . trigger ( 'click' ) ;
}
$ ( "#rm_group_delete" ) . hide ( ) ;
$ ( "#rm_group_scenario" ) . hide ( ) ;
2023-10-16 22:03:42 +02:00
$ ( '#group-metadata-controls .chat_lorebook_button' ) . addClass ( 'disabled' ) . prop ( 'disabled' , true ) ;
2023-07-20 19:32:15 +02:00
}
updateFavButtonState ( group ? . fav ? ? false ) ;
// top bar
if ( group ) {
$ ( "#rm_group_automode_label" ) . show ( ) ;
$ ( "#rm_button_selected_ch" ) . children ( "h2" ) . text ( groupName ) ;
}
else {
$ ( "#rm_group_automode_label" ) . hide ( ) ;
}
2023-08-20 11:37:38 +02:00
eventSource . emit ( 'groupSelected' , { detail : { id : openGroupId , group : group } } ) ;
2023-08-19 01:53:05 +02:00
}
2023-07-20 19:32:15 +02:00
2023-08-20 06:15:57 +02:00
/ * *
* Handles the upload and processing of a group avatar .
* The selected image is read , cropped using a popup , processed into a thumbnail ,
* and then uploaded to the server .
2023-08-20 11:37:38 +02:00
*
2023-08-20 06:15:57 +02:00
* @ param { Event } event - The event triggered by selecting a file input , containing the image file to upload .
2023-08-20 11:37:38 +02:00
*
2023-08-20 06:15:57 +02:00
* @ returns { Promise < void > } - A promise that resolves when the processing and upload is complete .
* /
2023-08-19 01:53:05 +02:00
async function uploadGroupAvatar ( event ) {
const file = event . target . files [ 0 ] ;
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( ! file ) {
return ;
}
2023-07-20 19:32:15 +02:00
2023-10-08 10:34:39 +02:00
const result = await getBase64Async ( file ) ;
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
$ ( '#dialogue_popup' ) . addClass ( 'large_dialogue_popup wide_dialogue_popup' ) ;
2023-07-20 19:32:15 +02:00
2023-10-08 10:34:39 +02:00
const croppedImage = await callPopup ( getCropPopup ( result ) , 'avatarToCrop' ) ;
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( ! croppedImage ) {
return ;
}
2023-07-20 19:32:15 +02:00
2023-10-08 10:34:39 +02:00
let thumbnail = await createThumbnail ( croppedImage , 200 , 300 ) ;
2023-08-20 05:53:34 +02:00
//remove data:image/whatever;base64
thumbnail = thumbnail . replace ( /^data:image\/[a-z]+;base64,/ , "" ) ;
2023-08-20 07:41:58 +02:00
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
// filename should be group id + human readable timestamp
2023-10-08 10:34:39 +02:00
const filename = _thisGroup ? ` ${ _thisGroup . id } _ ${ humanizedDateTime ( ) } ` : humanizedDateTime ( ) ;
let thumbnailUrl = await saveBase64AsFile ( thumbnail , String ( openGroupId ? ? '' ) , filename , 'jpg' ) ;
2023-08-19 01:53:05 +02:00
if ( ! openGroupId ) {
2023-08-20 05:53:34 +02:00
$ ( '#group_avatar_preview img' ) . attr ( 'src' , thumbnailUrl ) ;
2023-08-19 01:53:05 +02:00
$ ( '#rm_group_restore_avatar' ) . show ( ) ;
return ;
2023-07-20 19:32:15 +02:00
}
2023-08-20 05:53:34 +02:00
_thisGroup . avatar _url = thumbnailUrl ;
2023-08-19 01:53:05 +02:00
$ ( "#group_avatar_preview" ) . empty ( ) . append ( getGroupAvatar ( _thisGroup ) ) ;
$ ( "#rm_group_restore_avatar" ) . show ( ) ;
await editGroup ( openGroupId , true , true ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
async function restoreGroupAvatar ( ) {
const confirm = await callPopup ( '<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.' , 'confirm' ) ;
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( ! confirm ) {
return ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( ! openGroupId ) {
$ ( "#group_avatar_preview img" ) . attr ( "src" , default _avatar ) ;
2023-07-20 19:32:15 +02:00
$ ( "#rm_group_restore_avatar" ) . hide ( ) ;
2023-08-19 01:53:05 +02:00
return ;
2023-07-20 19:32:15 +02:00
}
2023-08-19 01:53:05 +02:00
let _thisGroup = groups . find ( ( x ) => x . id == openGroupId ) ;
_thisGroup . avatar _url = '' ;
$ ( "#group_avatar_preview" ) . empty ( ) . append ( getGroupAvatar ( _thisGroup ) ) ;
$ ( "#rm_group_restore_avatar" ) . hide ( ) ;
await editGroup ( openGroupId , true , true ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
async function onGroupActionClick ( event ) {
event . stopPropagation ( ) ;
const action = $ ( this ) . data ( 'action' ) ;
const member = $ ( this ) . closest ( '.group_member' ) ;
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'remove' ) {
await modifyGroupMember ( openGroupId , member , true ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'add' ) {
await modifyGroupMember ( openGroupId , member , false ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'enable' ) {
member . removeClass ( 'disabled' ) ;
const _thisGroup = groups . find ( x => x . id === openGroupId ) ;
const index = _thisGroup . disabled _members . indexOf ( member . data ( 'id' ) ) ;
if ( index !== - 1 ) {
_thisGroup . disabled _members . splice ( index , 1 ) ;
2023-07-20 19:32:15 +02:00
}
2023-08-19 01:53:05 +02:00
await editGroup ( openGroupId , false , false ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'disable' ) {
member . addClass ( 'disabled' ) ;
const _thisGroup = groups . find ( x => x . id === openGroupId ) ;
_thisGroup . disabled _members . push ( member . data ( 'id' ) ) ;
await editGroup ( openGroupId , false , false ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'up' || action === 'down' ) {
await reorderGroupMember ( openGroupId , member , action ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'view' ) {
openCharacterDefinition ( member ) ;
}
2023-07-20 19:32:15 +02:00
2023-08-19 01:53:05 +02:00
if ( action === 'speak' ) {
const chid = Number ( member . attr ( 'chid' ) ) ;
if ( Number . isInteger ( chid ) ) {
Generate ( 'normal' , { force _chid : chid } ) ;
}
}
2023-06-04 17:45:03 +02:00
2023-08-19 01:53:05 +02:00
await eventSource . emit ( event _types . GROUP _UPDATED ) ;
2023-07-20 19:32:15 +02:00
}
function updateFavButtonState ( state ) {
fav _grp _checked = state ;
$ ( "#rm_group_fav" ) . val ( fav _grp _checked ) ;
$ ( "#group_favorite_button" ) . toggleClass ( 'fav_on' , fav _grp _checked ) ;
$ ( "#group_favorite_button" ) . toggleClass ( 'fav_off' , ! fav _grp _checked ) ;
}
2023-08-19 20:44:15 +02:00
export async function openGroupById ( groupId ) {
2023-08-23 20:32:38 +02:00
if ( isChatSaving ) {
toastr . info ( "Please wait until the chat is saved before switching characters." , "Your chat is still saving..." ) ;
return ;
}
2023-08-20 12:15:02 +02:00
if ( ! groups . find ( x => x . id === groupId ) ) {
console . log ( 'Group not found' , groupId ) ;
return ;
}
2023-07-20 19:32:15 +02:00
if ( ! is _send _press && ! is _group _generating ) {
if ( selected _group !== groupId ) {
2023-11-04 22:25:22 +01:00
await clearChat ( ) ;
2023-07-20 19:32:15 +02:00
cancelTtsPlay ( ) ;
selected _group = groupId ;
setCharacterId ( undefined ) ;
setCharacterName ( '' ) ;
setEditedMessageId ( undefined ) ;
updateChatMetadata ( { } , true ) ;
chat . length = 0 ;
await getGroupChat ( groupId ) ;
}
select _group _chats ( groupId ) ;
}
}
function openCharacterDefinition ( characterSelect ) {
if ( is _group _generating ) {
2023-08-13 17:44:32 +02:00
toastr . warning ( "Can't peek a character while group reply is being generated" ) ;
2023-07-20 19:32:15 +02:00
console . warn ( "Can't peek a character def while group reply is being generated" ) ;
return ;
}
const chid = characterSelect . attr ( 'chid' ) ;
if ( chid === null || chid === undefined ) {
return ;
}
setCharacterId ( chid ) ;
select _selected _character ( chid ) ;
// Gentle nudge to recalculate tokens
RA _CountCharTokens ( ) ;
// Do a little tomfoolery to spoof the tag selector
applyTagsOnCharacterSelect . call ( characterSelect ) ;
}
function filterGroupMembers ( ) {
2023-08-22 13:30:49 +02:00
const searchValue = String ( $ ( this ) . val ( ) ) . toLowerCase ( ) ;
2023-08-19 20:08:35 +02:00
groupCandidatesFilter . setFilterData ( FILTER _TYPES . SEARCH , searchValue ) ;
2023-07-20 19:32:15 +02:00
}
async function createGroup ( ) {
let name = $ ( "#rm_group_chat_name" ) . val ( ) ;
2023-10-25 21:39:31 +02:00
let allowSelfResponses = ! ! $ ( "#rm_group_allow_self_responses" ) . prop ( "checked" ) ;
let activationStrategy = Number ( $ ( '#rm_group_activation_strategy' ) . find ( ':selected' ) . val ( ) ) ? ? group _activation _strategy . NATURAL ;
let generationMode = Number ( $ ( '#rm_group_generation_mode' ) . find ( ':selected' ) . val ( ) ) ? ? group _generation _mode . SWAP ;
2023-08-19 01:53:05 +02:00
const members = newGroupMembers ;
2023-07-20 19:32:15 +02:00
const memberNames = characters . filter ( x => members . includes ( x . avatar ) ) . map ( x => x . name ) . join ( ", " ) ;
if ( ! name ) {
name = ` Group: ${ memberNames } ` ;
}
const avatar _url = $ ( '#group_avatar_preview img' ) . attr ( 'src' ) ;
const chatName = humanizedDateTime ( ) ;
const chats = [ chatName ] ;
const createGroupResponse = await fetch ( "/creategroup" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( {
name : name ,
members : members ,
2023-08-20 05:53:34 +02:00
avatar _url : isValidImageUrl ( avatar _url ) ? avatar _url : default _avatar ,
2023-10-25 21:39:31 +02:00
allow _self _responses : allowSelfResponses ,
2023-11-21 23:48:35 +01:00
hideMutedSprites : hideMutedSprites ,
2023-10-25 21:39:31 +02:00
activation _strategy : activationStrategy ,
generation _mode : generationMode ,
2023-07-20 19:32:15 +02:00
disabled _members : [ ] ,
chat _metadata : { } ,
fav : fav _grp _checked ,
chat _id : chatName ,
chats : chats ,
} ) ,
} ) ;
if ( createGroupResponse . ok ) {
2023-08-19 01:53:05 +02:00
newGroupMembers = [ ] ;
2023-07-20 19:32:15 +02:00
const data = await createGroupResponse . json ( ) ;
createTagMapFromList ( "#groupTagList" , data . id ) ;
await getCharacters ( ) ;
select _rm _info ( 'group_create' , data . id ) ;
}
}
export async function createNewGroupChat ( groupId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group ) {
return ;
}
const oldChatName = group . chat _id ;
const newChatName = humanizedDateTime ( ) ;
if ( typeof group . past _metadata !== 'object' ) {
group . past _metadata = { } ;
}
2023-11-04 22:25:22 +01:00
await clearChat ( ) ;
2023-07-20 19:32:15 +02:00
chat . length = 0 ;
if ( oldChatName ) {
group . past _metadata [ oldChatName ] = Object . assign ( { } , chat _metadata ) ;
}
group . chats . push ( newChatName ) ;
group . chat _id = newChatName ;
group . chat _metadata = { } ;
updateChatMetadata ( group . chat _metadata , true ) ;
2023-08-22 15:11:07 +02:00
await editGroup ( group . id , true , false ) ;
2023-07-20 19:32:15 +02:00
await getGroupChat ( group . id ) ;
}
export async function getGroupPastChats ( groupId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group ) {
return [ ] ;
}
const chats = [ ] ;
try {
for ( const chatId of group . chats ) {
const messages = await loadGroupChat ( chatId ) ;
let this _chat _file _size = ( JSON . stringify ( messages ) . length / 1024 ) . toFixed ( 2 ) + "kb" ;
let chat _items = messages . length ;
const lastMessage = messages . length ? messages [ messages . length - 1 ] . mes : '[The chat is empty]' ;
const lastMessageDate = messages . length ? ( messages [ messages . length - 1 ] . send _date || Date . now ( ) ) : Date . now ( ) ;
chats . push ( {
'file_name' : chatId ,
'mes' : lastMessage ,
'last_mes' : lastMessageDate ,
'file_size' : this _chat _file _size ,
'chat_items' : chat _items ,
} ) ;
}
} catch ( err ) {
console . error ( err ) ;
}
finally {
return chats ;
}
}
export async function openGroupChat ( groupId , chatId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group || ! group . chats . includes ( chatId ) ) {
return ;
}
2023-11-04 22:25:22 +01:00
await clearChat ( ) ;
2023-07-20 19:32:15 +02:00
chat . length = 0 ;
const previousChat = group . chat _id ;
group . past _metadata [ previousChat ] = Object . assign ( { } , chat _metadata ) ;
group . chat _id = chatId ;
group . chat _metadata = group . past _metadata [ chatId ] || { } ;
group [ 'date_last_chat' ] = Date . now ( ) ;
updateChatMetadata ( group . chat _metadata , true ) ;
2023-08-19 01:53:05 +02:00
await editGroup ( groupId , true , false ) ;
2023-07-20 19:32:15 +02:00
await getGroupChat ( groupId ) ;
}
export async function renameGroupChat ( groupId , oldChatId , newChatId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group || ! group . chats . includes ( oldChatId ) ) {
return ;
}
if ( group . chat _id === oldChatId ) {
group . chat _id = newChatId ;
}
group . chats . splice ( group . chats . indexOf ( oldChatId ) , 1 ) ;
group . chats . push ( newChatId ) ;
group . past _metadata [ newChatId ] = ( group . past _metadata [ oldChatId ] || { } ) ;
delete group . past _metadata [ oldChatId ] ;
await editGroup ( groupId , true , true ) ;
}
export async function deleteGroupChat ( groupId , chatId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group || ! group . chats . includes ( chatId ) ) {
return ;
}
group . chats . splice ( group . chats . indexOf ( chatId ) , 1 ) ;
group . chat _metadata = { } ;
group . chat _id = '' ;
delete group . past _metadata [ chatId ] ;
updateChatMetadata ( group . chat _metadata , true ) ;
const response = await fetch ( '/deletegroupchat' , {
method : 'POST' ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { id : chatId } ) ,
} ) ;
if ( response . ok ) {
if ( group . chats . length ) {
await openGroupChat ( groupId , group . chats [ 0 ] ) ;
} else {
await createNewGroupChat ( groupId ) ;
}
2023-09-09 21:15:47 +02:00
await eventSource . emit ( event _types . GROUP _CHAT _DELETED , chatId ) ;
2023-07-20 19:32:15 +02:00
}
}
export async function importGroupChat ( formData ) {
await jQuery . ajax ( {
type : "POST" ,
url : "/importgroupchat" ,
data : formData ,
beforeSend : function ( ) {
} ,
cache : false ,
contentType : false ,
processData : false ,
success : async function ( data ) {
if ( data . res ) {
const chatId = data . res ;
const group = groups . find ( x => x . id == selected _group ) ;
if ( group ) {
group . chats . push ( chatId ) ;
await editGroup ( selected _group , true , true ) ;
await displayPastChats ( ) ;
}
}
} ,
error : function ( ) {
$ ( "#create_button" ) . removeAttr ( "disabled" ) ;
} ,
} ) ;
}
export async function saveGroupBookmarkChat ( groupId , name , metadata , mesId ) {
const group = groups . find ( x => x . id === groupId ) ;
if ( ! group ) {
return ;
}
group . past _metadata [ name ] = { ... chat _metadata , ... ( metadata || { } ) } ;
group . chats . push ( name ) ;
const trimmed _chat = ( mesId !== undefined && mesId >= 0 && mesId < chat . length )
? chat . slice ( 0 , parseInt ( mesId ) + 1 )
: chat ;
2023-08-19 01:53:05 +02:00
await editGroup ( groupId , true , false ) ;
2023-07-20 19:32:15 +02:00
await fetch ( "/savegroupchat" , {
method : "POST" ,
headers : getRequestHeaders ( ) ,
body : JSON . stringify ( { id : name , chat : [ ... trimmed _chat ] } ) ,
} ) ;
}
function onSendTextareaInput ( ) {
if ( is _group _automode _enabled ) {
// Wait for current automode generation to finish
is _group _automode _enabled = false ;
$ ( "#rm_group_automode" ) . prop ( "checked" , false ) ;
}
}
function stopAutoModeGeneration ( ) {
if ( groupAutoModeAbortController ) {
groupAutoModeAbortController . abort ( ) ;
}
is _group _automode _enabled = false ;
$ ( "#rm_group_automode" ) . prop ( "checked" , false ) ;
}
function doCurMemberListPopout ( ) {
//repurposes the zoomed avatar template to server as a floating group member list
if ( $ ( "#groupMemberListPopout" ) . length === 0 ) {
console . debug ( 'did not see popout yet, creating' )
const memberListClone = $ ( this ) . parent ( ) . parent ( ) . find ( '.inline-drawer-content' ) . html ( )
const template = $ ( '#zoomed_avatar_template' ) . html ( ) ;
const controlBarHtml = ` <div class="panelControlBar flex-container">
< div id = "groupMemberListPopoutheader" class = "fa-solid fa-grip drag-grabber hoverglow" > < / d i v >
< div id = "groupMemberListPopoutClose" class = "fa-solid fa-circle-xmark hoverglow" > < / d i v >
< / d i v > `
const newElement = $ ( template ) ;
2023-10-13 17:29:41 +02:00
newElement . attr ( 'id' , 'groupMemberListPopout' )
. removeClass ( 'zoomed_avatar' )
. addClass ( 'draggable' )
. empty ( )
. append ( controlBarHtml )
. append ( memberListClone )
2023-10-15 18:42:23 +02:00
// Remove pagination from popout
newElement . find ( '.group_pagination' ) . empty ( ) ;
2023-07-20 19:32:15 +02:00
$ ( 'body' ) . append ( newElement ) ;
loadMovingUIState ( ) ;
$ ( "#groupMemberListPopout" ) . fadeIn ( 250 )
dragElement ( newElement )
$ ( '#groupMemberListPopoutClose' ) . off ( 'click' ) . on ( 'click' , function ( ) {
$ ( "#groupMemberListPopout" ) . fadeOut ( 250 , ( ) => { $ ( "#groupMemberListPopout" ) . remove ( ) } )
} )
2023-10-15 18:42:23 +02:00
// Re-add pagination not working in popout
printGroupMembers ( ) ;
2023-07-20 19:32:15 +02:00
} else {
console . debug ( 'saw existing popout, removing' )
$ ( "#groupMemberListPopout" ) . fadeOut ( 250 , ( ) => { $ ( "#groupMemberListPopout" ) . remove ( ) } ) ;
}
}
jQuery ( ( ) => {
2023-08-19 20:44:15 +02:00
$ ( document ) . on ( "click" , ".group_select" , function ( ) {
const groupId = $ ( this ) . data ( "id" ) ;
openGroupById ( groupId ) ;
} ) ;
2023-07-20 19:32:15 +02:00
$ ( "#rm_group_filter" ) . on ( "input" , filterGroupMembers ) ;
$ ( "#rm_group_submit" ) . on ( "click" , createGroup ) ;
$ ( "#rm_group_scenario" ) . on ( "click" , setScenarioOverride ) ;
$ ( "#rm_group_automode" ) . on ( "input" , function ( ) {
const value = $ ( this ) . prop ( "checked" ) ;
is _group _automode _enabled = value ;
eventSource . once ( event _types . GENERATION _STOPPED , stopAutoModeGeneration ) ;
} ) ;
2023-11-21 23:48:35 +01:00
$ ( "#rm_group_hidemutedsprites" ) . on ( "input" , function ( ) {
const value = $ ( this ) . prop ( "checked" ) ;
hideMutedSprites = value ;
onHideMutedSpritesClick ( value ) ;
} ) ;
2023-07-20 19:32:15 +02:00
$ ( "#send_textarea" ) . on ( "keyup" , onSendTextareaInput ) ;
$ ( "#groupCurrentMemberPopoutButton" ) . on ( 'click' , doCurMemberListPopout ) ;
2023-08-19 01:53:05 +02:00
$ ( "#rm_group_chat_name" ) . on ( "input" , onGroupNameInput )
$ ( "#rm_group_delete" ) . off ( ) . on ( "click" , onDeleteGroupClick ) ;
$ ( "#group_favorite_button" ) . on ( 'click' , onFavoriteGroupClick ) ;
$ ( "#rm_group_allow_self_responses" ) . on ( "input" , onGroupSelfResponsesClick ) ;
2023-10-25 21:39:31 +02:00
$ ( "#rm_group_activation_strategy" ) . on ( "change" , onGroupActivationStrategyInput ) ;
$ ( "#rm_group_generation_mode" ) . on ( "change" , onGroupGenerationModeInput ) ;
2023-08-19 01:53:05 +02:00
$ ( "#group_avatar_button" ) . on ( "input" , uploadGroupAvatar ) ;
$ ( "#rm_group_restore_avatar" ) . on ( "click" , restoreGroupAvatar ) ;
$ ( document ) . on ( "click" , ".group_member .right_menu_button" , onGroupActionClick ) ;
2023-07-20 19:32:15 +02:00
} ) ;